How I Learned Not to Worry and Love Oracles Most of us use price feeds without considering what a price actually is and how it should be represented. During the development of I struggled with obtaining asset prices as a combination from several sources, and solving that problem led to a breakthrough. Yield v2 Now we can easily compose any number of price feeds to get the relative values of any two asset amounts. , who leads the meme division at , summarized it best: If you are a bit confused about all this, so was I, but read on. You are about to learn what does it mean. Dan Robinson Paradigm Chainlink and the Original Sin Chainlink officially launched back in 2019 and quickly became the . From Chainlink, you can get the price of many tokens against ETH or USD, apart from other more exotic data feeds. dominant source for price data What probably none of us stop to think is what do those 18 decimals represent. According to the docs, the with 18 decimals for ETH data feeds and 8 decimals for USD data feeds. return value is a fixed point number 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. Compositional Prices At , we have built a that can theoretically lend any asset and theoretically accept any asset as collateral. That feature, though, is limited to the asset pairs for which you have a price feed. For lending an asset, you need to know if the posted collateral is more valuable than the debt by a certain collateralization factor. Yield collateralized debt platform 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 DAI is worth more or less than 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. foo bar To solve that, you multiply the amount of base by the decimals of the and divide the result by the decimals of the . quote 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. Complex Compositional Prices Now you want to use cUSDC as collateral to borrow DAI. Why, you ask? says something about rates, shorts, longs, no idea. He says jump, and I only ask how high. Allan 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… . 16 decimals 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. Partially Applied Multiplications The breakthrough came when I stopped thinking about prices, and started thinking about amounts. Our take a parameter for the amount of the base asset, and return the amount of the quote asset that you would get if you trade them. Oracles 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 , I can ask: the DAI/ETH and the USDC/ETH oracles “Hey, how much ETH do I get for 100 DAI?” - “You get 0.04 ETH, fren“ “Cool, I got 0.04 ETH. Now, how much USDC do I get from 0.04 ETH?” - “Easy, that’s 100.1 USDC“ 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: “I’ve got 100 DAI; how much ETH is that?” - “0.04” “Of course, so how much USDC is 0.04 ETH?” - “100.1” “Awesome, then how much cUSDC is 100.1 USDC?” - “102.3” “Righto, so 100 DAI is 102.3 cUSDC, I’m liquidating this guy” How Do We Do It At Yield The oracle infrastructure at Yield is not permissionless, but if you are ok with that, you can to get price feeds, or you can copy what we’ve done and deploy new oracles. In this section I’ll explain how it works. use our contracts We have a simple interface with two functions, and . In many cases, they both return the same, but if a price feed has a view value and a mutable value, will get you the view one, and will get you the mutable. From off-chain, you probably want to use . From another smart contract, inside a mutable function, you probably want to use . IOracle peek get peek get peek get // 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 about the relative values of two assets in terms of amounts. So far, we are pulling data from Chainlink, Compound, Uniswap, Lido, Convex, and Yearn. Pulling data from vaults is trivial as well since they already implement this pattern. any of our oracles ERC4626 To combine data feeds from different sources, we have a that conforms to the same interface, but that stores paths on how to get from A to B, pulling prices from IOracle contracts. Composite Oracle So, for example, to get the DAI/ENS price, we have this: for DAI/ETH (0x3031, 0x3030) Chainlink IOracle for ENS/ETH (0x3037, 0x3030) Uniswap V3 IOracle , that knows that to get DAI/ENS (0x3031, 0x3037), you ask the Chainlink IOracle for how much ETH is the DAI worth, and then you ask the Uniswap V3 IOracle how much ENS is that ETH worth. Composite IOracle Conclusion 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 we have implemented oracles that read from Chainlink, Compound, Uniswap V3, Lido, Yearn, and Convex, and we can combine their data to retrieve the relative values of any two assets. Adding an additional data provider is a trivial affair. Yield