CryptoFromPool
Oracle contract for a collateral token that fetches its price from a single Curve pool. The first oracle contracts were deployed without considering the aggregated price of crvUSD, but experience showed that it makes sense to include this value in the calculation. The respective differences are documented in the relevant sections.
GitHub
The source code of the following price oracle contracts can be found on GitHub:
The OneWayLendingFactory.vy
has a create_from_pool
method which deploys the full lending market infrastucture along with a price oracle using a stableswap-ng
, twocrypto-ng
or tricrypto-ng
pool. These pools all have a suitable exponential moving-average (EMA) oracle, which can be used in lending markets.
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. All relevant data required for the oracle to function is passed into the __init__
function during the deployment of the contract.
__init__
The following source code includes all changes up to commit hash 86cae3a; any changes made after this commit are not included.
@external
def __init__(
pool: Pool,
N: uint256,
borrowed_ix: uint256,
collateral_ix: uint256
):
assert borrowed_ix != collateral_ix
assert borrowed_ix < N
assert collateral_ix < N
POOL = pool
N_COINS = N
BORROWED_IX = borrowed_ix
COLLATERAL_IX = collateral_ix
no_argument: bool = False
if N == 2:
success: bool = False
res: Bytes[32] = empty(Bytes[32])
success, res = raw_call(
pool.address,
_abi_encode(empty(uint256), method_id=method_id("price_oracle(uint256)")),
max_outsize=32, is_static_call=True, revert_on_failure=False)
if not success:
no_argument = True
NO_ARGUMENT = no_argument
@external
def __init__(
pool: Pool,
N: uint256,
borrowed_ix: uint256,
collateral_ix: uint256,
agg: StableAggregator
):
assert borrowed_ix != collateral_ix
assert borrowed_ix < N
assert collateral_ix < N
POOL = pool
N_COINS = N
BORROWED_IX = borrowed_ix
COLLATERAL_IX = collateral_ix
AGG = agg
no_argument: bool = False
if N == 2:
success: bool = False
res: Bytes[32] = empty(Bytes[32])
success, res = raw_call(
pool.address,
_abi_encode(empty(uint256), method_id=method_id("price_oracle(uint256)")),
max_outsize=32, is_static_call=True, revert_on_failure=False)
if not success:
no_argument = True
NO_ARGUMENT = no_argument
Example: CRV long market
In the CRV short market, CRV
serves as the collateral token, while crvUSD
is the borrowable token. This lending market utilizes the price oracle sourced from the TriCRV liquidity pool.
When calling the create_from_pool
function, the code automatically checks the index of the tokens within the liquidity pool. Subsequently, it passes these values as constructor arguments during the creation of the oracle contract from the blueprint implementation.
Oracle Price¶
price
¶
CryptoFromPool.price() -> uint256
Getter function for the price. For example, in a lending market using CRV
as collateral and crvUSD
as the borrowable token, it returns the price of CRV
relative to crvUSD
. Conversely, in the inverse market scenario, it returns the price of crvUSD
relative to CRV
. This function is view-only and does not modify the state. For contracts applying the aggregated crvUSD price, it essentially multiplies the collateral price with the aggregated crvUSD price.
Returns: price (uint256
).
Source code
The CryptoFromPool.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 CryptoFromPoolWAgg.vy
oracle contract.
The following source code includes all changes up to commit hash 86cae3a; any changes made after this commit are not included.
@external
@view
def price() -> uint256:
return self._raw_price()
@internal
@view
def _raw_price() -> uint256:
p_borrowed: uint256 = 10**18
p_collateral: uint256 = 10**18
if NO_ARGUMENT:
p: uint256 = POOL.price_oracle()
if COLLATERAL_IX > 0:
p_collateral = p
else:
p_borrowed = p
else:
if BORROWED_IX > 0:
p_borrowed = POOL.price_oracle(BORROWED_IX - 1)
if COLLATERAL_IX > 0:
p_collateral = POOL.price_oracle(COLLATERAL_IX - 1)
return p_collateral * 10**18 / p_borrowed
interface StableAggregator:
def price() -> uint256: view
def price_w() -> uint256: nonpayable
def stablecoin() -> address: view
AGG: public(immutable(StableAggregator))
@external
@view
def price() -> uint256:
return self._raw_price() * AGG.price() / 10**18
@internal
@view
def _raw_price() -> uint256:
p_borrowed: uint256 = 10**18
p_collateral: uint256 = 10**18
if NO_ARGUMENT:
p: uint256 = POOL.price_oracle()
if COLLATERAL_IX > 0:
p_collateral = p
else:
p_borrowed = p
else:
if BORROWED_IX > 0:
p_borrowed = POOL.price_oracle(BORROWED_IX - 1)
if COLLATERAL_IX > 0:
p_collateral = POOL.price_oracle(COLLATERAL_IX - 1)
return p_collateral * 10**18 / p_borrowed
price_w
¶
CryptoFromPool.price_w() -> uint256:
Function to return the price and update the state of the blockchain. This function is called whenever the _exchange
function from the LLAMMA is called. For contracts applying the aggregated crvUSD price, it essentially multiplies the collateral price with the aggregated crvUSD price.
Returns: price (uint256
).
Source code
The CryptoFromPool.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 CryptoFromPoolWAgg.vy
oracle contract.
The following source code includes all changes up to commit hash 86cae3a; any changes made after this commit are not included.
@external
def price_w() -> uint256:
return self._raw_price()
@internal
@view
def _raw_price() -> uint256:
p_borrowed: uint256 = 10**18
p_collateral: uint256 = 10**18
if NO_ARGUMENT:
p: uint256 = POOL.price_oracle()
if COLLATERAL_IX > 0:
p_collateral = p
else:
p_borrowed = p
else:
if BORROWED_IX > 0:
p_borrowed = POOL.price_oracle(BORROWED_IX - 1)
if COLLATERAL_IX > 0:
p_collateral = POOL.price_oracle(COLLATERAL_IX - 1)
return p_collateral * 10**18 / p_borrowed
interface StableAggregator:
def price() -> uint256: view
def price_w() -> uint256: nonpayable
def stablecoin() -> address: view
AGG: public(immutable(StableAggregator))
@external
def price_w() -> uint256:
return self._raw_price() * AGG.price_w() / 10**18
@internal
@view
def _raw_price() -> uint256:
p_borrowed: uint256 = 10**18
p_collateral: uint256 = 10**18
if NO_ARGUMENT:
p: uint256 = POOL.price_oracle()
if COLLATERAL_IX > 0:
p_collateral = p
else:
p_borrowed = p
else:
if BORROWED_IX > 0:
p_borrowed = POOL.price_oracle(BORROWED_IX - 1)
if COLLATERAL_IX > 0:
p_collateral = POOL.price_oracle(COLLATERAL_IX - 1)
return p_collateral * 10**18 / p_borrowed
@internal
def _price_oracle_w() -> uint256[2]:
p: uint256[2] = self.limit_p_o(price_oracle_contract.price_w())
self.prev_p_o_time = block.timestamp
self.old_p_o = p[0]
self.old_dfee = p[1]
return p
@internal
def _exchange(i: uint256, j: uint256, amount: uint256, minmax_amount: uint256, _for: address, use_in_amount: bool) -> uint256[2]:
"""
@notice Exchanges two coins, callable by anyone
@param i Input coin index
@param j Output coin index
@param amount Amount of input/output coin to swap
@param minmax_amount Minimal/maximum amount to get as output/input
@param _for Address to send coins to
@param use_in_amount Whether input or output amount is specified
@return Amount of coins given in and out
"""
assert (i == 0 and j == 1) or (i == 1 and j == 0), "Wrong index"
p_o: uint256[2] = self._price_oracle_w() # Let's update the oracle even if we exchange 0
if amount == 0:
return [0, 0]
...
Contract Info Methods¶
POOL
¶
CryptoFromPool.POOL() -> address: view
Getter for the liquidity pool the from where the oracle is used.
Returns: liquidity pool (address
).
Source code
The following source code includes all changes up to commit hash 86cae3a; any changes made after this commit are not included.
POOL: public(immutable(Pool))
@external
def __init__(
pool: Pool,
N: uint256,
borrowed_ix: uint256,
collateral_ix: uint256
):
assert borrowed_ix != collateral_ix
assert borrowed_ix < N
assert collateral_ix < N
POOL = pool
N_COINS = N
BORROWED_IX = borrowed_ix
COLLATERAL_IX = collateral_ix
no_argument: bool = False
if N == 2:
success: bool = False
res: Bytes[32] = empty(Bytes[32])
success, res = raw_call(
pool.address,
_abi_encode(empty(uint256), method_id=method_id("price_oracle(uint256)")),
max_outsize=32, is_static_call=True, revert_on_failure=False)
if not success:
no_argument = True
NO_ARGUMENT = no_argument
N_COINS
¶
CryptoFromPool.N_COINS() -> uint256: view
Getter for the total number of coins in the liquidity pool.
Returns: coins count (uint256
).
Source code
The following source code includes all changes up to commit hash 86cae3a; any changes made after this commit are not included.
```vyper
N_COINS: public(immutable(uint256))
@external
def __init__(
pool: Pool,
N: uint256,
borrowed_ix: uint256,
collateral_ix: uint256
):
assert borrowed_ix != collateral_ix
assert borrowed_ix < N
assert collateral_ix < N
POOL = pool
N_COINS = N
BORROWED_IX = borrowed_ix
COLLATERAL_IX = collateral_ix
no_argument: bool = False
if N == 2:
success: bool = False
res: Bytes[32] = empty(Bytes[32])
success, res = raw_call(
pool.address,
_abi_encode(empty(uint256), method_id=method_id("price_oracle(uint256)")),
max_outsize=32, is_static_call=True, revert_on_failure=False)
if not success:
no_argument = True
NO_ARGUMENT = no_argument
```
BORROWED_IX
¶
CryptoFromPool.BORROWED_IX() -> uint256: view
Getter for the index of the borrowed coin in the liquidity pool from which the price oracle is taken from.
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))
@external
def __init__(
pool: Pool,
N: uint256,
borrowed_ix: uint256,
collateral_ix: uint256
):
assert borrowed_ix != collateral_ix
assert borrowed_ix < N
assert collateral_ix < N
POOL = pool
N_COINS = N
BORROWED_IX = borrowed_ix
COLLATERAL_IX = collateral_ix
no_argument: bool = False
if N == 2:
success: bool = False
res: Bytes[32] = empty(Bytes[32])
success, res = raw_call(
pool.address,
_abi_encode(empty(uint256), method_id=method_id("price_oracle(uint256)")),
max_outsize=32, is_static_call=True, revert_on_failure=False)
if not success:
no_argument = True
NO_ARGUMENT = no_argument
COLLATERAL_IX
¶
CryptoFromPool.COLLATERAL_IX() -> uint256: view
Getter for the index of the collateral coin in the liquidity pool from which the price oracle is taken from.
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))
@external
def __init__(
pool: Pool,
N: uint256,
borrowed_ix: uint256,
collateral_ix: uint256
):
assert borrowed_ix != collateral_ix
assert borrowed_ix < N
assert collateral_ix < N
POOL = pool
N_COINS = N
BORROWED_IX = borrowed_ix
COLLATERAL_IX = collateral_ix
no_argument: bool = False
if N == 2:
success: bool = False
res: Bytes[32] = empty(Bytes[32])
success, res = raw_call(
pool.address,
_abi_encode(empty(uint256), method_id=method_id("price_oracle(uint256)")),
max_outsize=32, is_static_call=True, revert_on_failure=False)
if not success:
no_argument = True
NO_ARGUMENT = no_argument
NO_ARGUMENT
¶
CryptoFromPool.NO_ARGUMENT() -> bool: view
Getter for the NO_ARGUMENT
storage variable. This is an additional variable to ensure the correct price oracle is fetched from a pool with more than two coins. The variable is set to false
if the pool from which the price oracle is taken has only two coins.
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.
NO_ARGUMENT: public(immutable(bool))
@external
def __init__(
pool: Pool,
N: uint256,
borrowed_ix: uint256,
collateral_ix: uint256
):
assert borrowed_ix != collateral_ix
assert borrowed_ix < N
assert collateral_ix < N
POOL = pool
N_COINS = N
BORROWED_IX = borrowed_ix
COLLATERAL_IX = collateral_ix
no_argument: bool = False
if N == 2:
success: bool = False
res: Bytes[32] = empty(Bytes[32])
success, res = raw_call(
pool.address,
_abi_encode(empty(uint256), method_id=method_id("price_oracle(uint256)")),
max_outsize=32, is_static_call=True, revert_on_failure=False)
if not success:
no_argument = True
NO_ARGUMENT = no_argument
AGG
¶
CryptoFromPoolWAgg.AGG() -> address: view
Info
This AGG
storage variable is only used within the CryptoFromPoolWAgg
contracts.
Getter for the crvUSD PriceAggregator
contract. This value is immutable and set at contract initialization.
Returns: PriceAggregator
(address
).
Source code
The following source code includes all changes up to commit hash 86cae3a; any changes made after this commit are not included.
Arbitrum¶
In addition to the aforementioned functions, oracle contracts on Arbitrum use a Chainlink Uptime Feed Oracle to monitor and validate any potential downtime of the sequencer.
Should the internal _raw_price
function, responsible for fetching the price, encounter an indication from the uptime oracle that the Arbitrum sequencer is presently offline, or if it has experienced recent downtime and the DOWNTIME_WAIT
period of 3988 seconds has not yet elapsed, it will revert.
interface ChainlinkOracle:
def latestRoundData() -> ChainlinkAnswer: view
struct ChainlinkAnswer:
roundID: uint80
answer: int256
startedAt: uint256
updatedAt: uint256
answeredInRound: uint80
@internal
@view
def _raw_price() -> uint256:
# Check that we had no downtime
cl_answer: ChainlinkAnswer = ChainlinkOracle(CHAINLINK_UPTIME_FEED).latestRoundData()
assert cl_answer.answer == 0, "Sequencer is down"
assert block.timestamp >= cl_answer.startedAt + DOWNTIME_WAIT, "Wait after downtime"
...
CHAINLINK_UPTIME_FEED
¶
CryptoFromPool.CHAINLINK_UPTIME_FEED() -> address: view
Getter for the ChainlinkUptimeFeed
contract.
Returns: uptime feed 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.
DOWNTIME_WAIT
¶
CryptoFromPool.DOWNTIME_WAIT() -> uint256: view
Getter for the required time to wait after the sequencer was down.
Returns: time to wait (uint256
).
Source code
The following source code includes all changes up to commit hash 86cae3a; any changes made after this commit are not included.