Most of us use price feeds without considering what a price actually is and how it should be represented. During the development of
Now we can easily compose any number of price feeds to get the relative values of any two asset amounts.
If you are a bit confused about all this, so was I, but read on. You are about to learn what does it mean.
Chainlink officially launched back in 2019 and quickly became the
What probably none of us stop to think is what do those 18 decimals represent.
According to the docs, the return value is a fixed point number with 18 decimals for ETH data feeds and 8 decimals for USD data feeds.
A fixed-point number doesn’t have much meaning in itself; it’s not something that you can hold in your hand, even in an imaginary hand. I can imagine myself holding one DAI in the metaverse, but I can’t imagine myself holding the exchange rate of DAI to ETH.
We are used to dealing with abstract ideas, but they have their own cognitive load. If you are not careful of compounding abstractions, you will find it progressively harder to understand what it is that you are doing.
Using an exchange rate is fine when your work is simple, but as you will see, things can easily get more complicated to the point where an exchange rate is not the better option.
At
Chainlink, though, only offers price feeds where one of the assets is ETH or USD. What do I do if I want to use DAI as collateral to lend USDC?
Easy, you get the DAI/ETH price and the ETH/USDC price and multiply them. Presto! You’ve got a DAI/USDC price feed.
You can get DAI/ETH from Chainlink and multiply it by the reverse of the USDC/ETH price, also from Chainlink.
DAI / ETH * ETH / USDC = DAI/USDC
Ah, yeah, we need to use fixed-point arithmetic. The screenshot is python but don’t forget that solidity doesn’t have native fixed-point types, either.
Knowing the price only gets you halfway there, though. Often you need to know something like whether foo DAI is worth more or less than bar USDC. To do that, you need to bring decimals into the fray, and things get a bit more complex. DAI has 18 decimals, USDC has 6 decimals.
To solve that, you multiply the amount of base by the decimals of the quote and divide the result by the decimals of the base.
amountUSDC = decimalsUSDC * amountDAI * priceDAI_USDC / decimalsDAI
So far, so good. Just be careful with the decimals, and with not getting your bases and quotes confused. It’s not so easy to remember whether the value you get from Chainlink is ETH/DAI or DAI/ETH, but with a bit of hair pulling, you’ll get there.
Now you want to use cUSDC as collateral to borrow DAI. Why, you ask?
We want to combine DAI/ETH, ETH/USDC and USDC/cUSDC. We can get a USDC/cUSDC price feed from the cToken contract, and it has…
Fine, so then we convert the USDC/cUSDC exchange rate and upscale it to 18 decimals, and then we multiply the DAI/USDC price for the upscaled USDC/cUSDC. That gets us DAI/cUSDC, unless I’m getting the reverse of the exchange rate in Compound. Let me check the docs… and now, how many decimals is cUSDC… Does it matter that USDC in the middle has 6 decimals?
Fuck, I don’t even know.
There must be a better way.
The breakthrough came when I stopped thinking about prices, and started thinking about amounts.
Our
In plain terms, I don’t ask the oracle: “Hey, what’s the DAI/ETH price?”
I ask: “Hey, how much ETH can I get for 100 DAI?”**
Now, that can be easily composed. If I want to know the DAI/USDC price, and I have
Notice that I’m not worrying about decimals anymore. I’m not wondering what the number I’m getting means, either. That number has a concrete meaning. It’s a handful of coins.
Let’s try DAI/cUSDC, which I couldn’t do in my mind before:
The oracle infrastructure at Yield is not permissionless, but if you are ok with that, you can
We have a simple
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IOracle {
/**
* @notice Doesn't refresh the price, but returns the latest value available without doing any transactional operations:
* @return value in wei
*/
function peek(bytes32 base, bytes32 quote, uint256 amount) external view returns (uint256 value, uint256 updateTime);
/**
* @notice Does whatever work or queries will yield the most up-to-date price, and returns it.
* @return value in wei
*/
function get(bytes32 base, bytes32 quote, uint256 amount) external returns (uint256 value, uint256 updateTime);
}
These two functions take a pair of asset identifiers and an amount of the base asset, and will return the equivalent amount of the quote asset and a timestamp to indicate how fresh the price is.
This interface abstracts all the complexity of getting a price from a given provider, allowing us to ask
To combine data feeds from different sources, we have a
So, for example, to get the DAI/ENS price, we have this:
It’s about time to stop reporting prices as fixed-point numbers, unless that’s exactly what you need.
Implementing oracles that return amounts is easy and allows for easier oracle composition.
At