This oracle contract takes the price oracle from a Curve liquidity pool and applies the redemption of the vault token to it. This is often used when having ERC-4626 Vault tokens with pricePerShare, convertToAsset, or other similar functions which essentially return the price of one vault token compared to the underlying assets. The first oracle contracts were deployed without considering the aggregated price of crvUSD, but experience has shown that it makes sense to include this value in the calculation. The respective differences are documented in the relevant sections.
These kinds of oracle contracts need to be deployed manually, as there is currently no Factory to do so.
GitHub
The source code for the following price oracle contracts can be found on GitHub:
CryptoFromPoolVaultWAgg.vy is only suitable for vaults which cannot be affected by donation attacks.
Oracle Immutability
The oracle contracts are fully immutable. Once deployed, they cannot change any parameters, stop the price updates, or alter the pools used to calculate the prices. However, because the contract relies on other pools, it's important to keep in mind that changing parameters in the pool, such as the periodicity of the oracle, can influence these oracle contracts. All relevant data required for the oracle to function is passed into the __init__ function during the deployment of the contract.
The oracle price is calculated by taking the price_oracle of a Curve pool and then adjusting it by the redemption rate of a vault, using methods such as convertToAssets, pricePerShare or really any other equvalent function which returns the rate of the vault token and the underlying asset.
Additionally, the CryptoFromPoolVault.vy contract has a built-in mechanism that considers a certain maximum speed of price change within the vault when calculating the oracle price. This feature is not included in the CryptoFromPoolVaultWAgg.vy oracle contract.
Source Code
The formula to calculate the applied redemption rate is the following:
In this example, pricePerShare is used, but it can really be any equivalent method that returns the redemption rate of the vault token with respect to its underlying token.
cached_price_per_share and cached_timestamp are internal variables that are updated whenever the price_w function is called. The first value is set to the current redemption rate within the vault at the block when the function is called, and the second value to the current timestamp (block.timestamp).
PPS_MAX_SPEED:constant(uint256)=10**16/60# Max speed of pricePerShare changecached_price_per_share:public(uint256)cached_timestamp:public(uint256)@internal@viewdef_pps()->uint256:returnmin(VAULT.pricePerShare(),self.cached_price_per_share*(10**18+PPS_MAX_SPEED*(block.timestamp-self.cached_timestamp))/10**18)@internaldef_pps_w()->uint256:pps:uint256=min(VAULT.pricePerShare(),self.cached_price_per_share*(10**18+PPS_MAX_SPEED*(block.timestamp-self.cached_timestamp))/10**18)self.cached_price_per_share=ppsself.cached_timestamp=block.timestampreturnpps
Getter for the price of the collateral asset denominated against the borrowed token and applying the conversion rate form a vault.
Returns: oracle price (uint256).
Source code
The CryptoFromPoolVault.vy oracle contract does not take the aggregated price of crvUSD from the PriceAggregator.vy contract into account. Experience has shown that it makes sense to include this value in the oracle calculations. This is implemented in the CryptoFromPoolVaultWAgg.vy oracle contract.
The following source code includes all changes up to commit hash 86cae3a; any changes made after this commit are not included.
interfacePool:defprice_oracle(i:uint256=0)->uint256:view# Universal method!interfaceStableAggregator:defprice()->uint256:viewdefprice_w()->uint256:nonpayabledefstablecoin()->address:viewinterfaceVault:defconvertToAssets(shares:uint256)->uint256:viewPOOL:public(immutable(Pool))BORROWED_IX:public(immutable(uint256))COLLATERAL_IX:public(immutable(uint256))N_COINS:public(immutable(uint256))NO_ARGUMENT:public(immutable(bool))VAULT:public(immutable(Vault))AGG:public(immutable(StableAggregator))PPS_MAX_SPEED:constant(uint256)=10**16/60# Max speed of pricePerShare changecached_price_per_share:public(uint256)cached_timestamp:public(uint256)@external@viewdefprice()->uint256:returnself._raw_price(self._pps())@internal@viewdef_raw_price(pps:uint256)->uint256:p_borrowed:uint256=10**18p_collateral:uint256=10**18ifNO_ARGUMENT:p:uint256=POOL.price_oracle()ifCOLLATERAL_IX>0:p_collateral=pelse:p_borrowed=pelse:ifBORROWED_IX>0:p_borrowed=POOL.price_oracle(BORROWED_IX-1)ifCOLLATERAL_IX>0:p_collateral=POOL.price_oracle(COLLATERAL_IX-1)returnp_collateral*pps/p_borrowed@internal@viewdef_pps()->uint256:returnmin(VAULT.pricePerShare(),self.cached_price_per_share*(10**18+PPS_MAX_SPEED*(block.timestamp-self.cached_timestamp))/10**18)
This function calculates and writes the price while updating cached_rate and cached_timestamp. It method is called whenever the _exchange function is called within the AMM contract of the lending market.
Returns: oracle price (uint256).
Source code
The CryptoFromPoolVault.vy oracle contract does not take the aggregated price of crvUSD from the PriceAggregator.vy contract into account. Experience has shown that it makes sense to include this value in the oracle calculations. This is implemented in the CryptoFromPoolVaultWAgg.vy oracle contract.
The following source code includes all changes up to commit hash 86cae3a; any changes made after this commit are not included.
interfacePool:defprice_oracle(i:uint256=0)->uint256:view# Universal method!interfaceStableAggregator:defprice()->uint256:viewdefprice_w()->uint256:nonpayabledefstablecoin()->address:viewinterfaceVault:defconvertToAssets(shares:uint256)->uint256:viewPOOL:public(immutable(Pool))BORROWED_IX:public(immutable(uint256))COLLATERAL_IX:public(immutable(uint256))N_COINS:public(immutable(uint256))NO_ARGUMENT:public(immutable(bool))VAULT:public(immutable(Vault))AGG:public(immutable(StableAggregator))PPS_MAX_SPEED:constant(uint256)=10**16/60# Max speed of pricePerShare changecached_price_per_share:public(uint256)cached_timestamp:public(uint256)@externaldefprice_w()->uint256:returnself._raw_price(self._pps_w())@internal@viewdef_raw_price(pps:uint256)->uint256:p_borrowed:uint256=10**18p_collateral:uint256=10**18ifNO_ARGUMENT:p:uint256=POOL.price_oracle()ifCOLLATERAL_IX>0:p_collateral=pelse:p_borrowed=pelse:ifBORROWED_IX>0:p_borrowed=POOL.price_oracle(BORROWED_IX-1)ifCOLLATERAL_IX>0:p_collateral=POOL.price_oracle(COLLATERAL_IX-1)returnp_collateral*pps/p_borrowed@internaldef_pps_w()->uint256:pps:uint256=min(VAULT.pricePerShare(),self.cached_price_per_share*(10**18+PPS_MAX_SPEED*(block.timestamp-self.cached_timestamp))/10**18)self.cached_price_per_share=ppsself.cached_timestamp=block.timestampreturnpps
Getter for the vault contract from which the redemption rate (convertToAssets or similar functions) is fetched. This value is immutable and set at contract initialization.
Returns: vault contract (address).
Source code
The following source code includes all changes up to commit hash 86cae3a; any changes made after this commit are not included.
Getter for the coin index of the borrowed token within the pool from which price_oracle is fetched. This value is immutable and set at contract initialization.
Returns: coin index (uint256).
Source code
The following source code includes all changes up to commit hash 86cae3a; any changes made after this commit are not included.
BORROWED_IX:public(immutable(uint256))
BORROWED_IX:public(immutable(uint256))
BORROWED_IX of 1 means the borrowed token in the pool, from which the price oracle value is fetched, is at coin index 1 (Pool.coins(1)).
Getter for the coin index of the collateral token within the pool from which price_oracle is fetched. This value is immutable and set at contract initialization.
Returns: coin index (uint256).
Source code
The following source code includes all changes up to commit hash 86cae3a; any changes made after this commit are not included.
COLLATERAL_IX:public(immutable(uint256))
COLLATERAL_IX:public(immutable(uint256))
COLLATERAL_IX of 0 means the borrowed token in the pool, from which the price oracle value is fetched, is at coin index 0 (Pool.coins(0)).
Getter for the NO_ARGUMENT storage variable. This is an additional variable to ensure the correct price oracle is fetched from a POOL. This value is immutable and set at contract initialization.
Returns: true or false (bool).
Source code
The following source code includes all changes up to commit hash 86cae3a; any changes made after this commit are not included.