- $20,000 worth of CANTO main award pot
- $0 worth of CANTO gas optimization award pot
- Join C4 Discord to register
- Submit findings using the C4 form
- Read our guidelines for more details
- Starts September 7, 2022 20:00 UTC
- Ends September 8, 2022 20:00 UTC
Link to docs
Not all code in the contracts below are in scope. The specific functions in scope are detailed below.
File | SLOC | Coverage |
---|---|---|
Contracts (2) | ||
src/Swap/BaseV1-core.sol 🧮 🔖 Σ | 476 | 78.15% |
src/Swap/BaseV1-periphery.sol 💰 📤 🧮 | 525 | 60.71% |
Total (over 2 files): | 1001 | 70.24% |
The oracle we define below is used in the Canto-Lending Market to determine the value of asset collateral of ($CANTO
, $NOTE
, $USDC
$USDT
, $ETH
, $ATOM
). It is also used to determine the value of lpTokens used as collateral. As such, the oracle is general purpose in that it is able to determine prices, from specified pairs deployed from the router, of non-LP and LP tokens.
Stable pairs follow the x^3y + y^3x = k
CF curve invariant. While volatile pairs follow the regular constant-product CF, xy = k
, where x
and y
are the reserves of the assets in the pool.
Integration tests using CLM in conjunction with the oracle may be found here,
All in one command:
rm -Rf 2022-09-canto || true && git clone https://github.com/code-423n4/2022-09-canto.git && cd 2022-09-canto && nvm install 16.0.0 && yarn install --lock-file && REPORT_GAS=true npx hardhat test
or:
nvm install 16.0.0
yarn install --lock-file
npx hardhat test
Oracle specific tests are found here: ./test/canto/Oracle
This method is defined to adhere to the Compound PriceOracle
interface. The Comptroller calls this method when calculating accountLiquidities
for users who hold balances of ERC20 tokens. These tokens are priced in terms of $NOTE
. Stable coins (USDC
, USDT
, NOTE
) have prices that are statically set to 1e18.
For non-stable tokens, there are two cases,
- The token is an lpToken
If the token to be priced is an lpToken, the price of the lpToken is determined from
getPriceLP
(defined below) - The token is not an lpToken The Price of the token is determined through the reserve ratio determined from the Canto/Token pool * price from Canto/Note pool. All non-stable tokens, are priced in reference to the Canto/Token Pool.
The final price is scaled by 1e18.
- Is it possible for users to create non-stable pairs for stable pairs, and have the router reference them in price determination, and vice-versa for non-stable assets and stable pairs. i.e. we're trying to get USDC/NOTE stable price, but a 3rd party has deployed USDC/NOTE non-stable, and ensure that oracle is looking at correct one
- L495
address(ICErc20(address(ctoken)).underlying())
- L502/506
erc20(underlying).decimals();
This method is used to price lpTokens. There are two cases,
- The pair that lpToken is minted from is stable
The Router will reference the Note/Token pool. The lpToken calculation determines the reserves of Note in the pool (
unitReserves
) and the reserves of the other asset in the pool (assetReserves
) - The pair that lpToken is minted from is not stable
The Router will reference the Canto/Token pool. The lpToken calculation determines the reserves of Canto in the pool (
unitReserves
) and the reserves of the other token in the pool (assetReserves
)
along with the reserves, the last 8 price observations are determined, where the price is represented as follows
prices = sample(token_asset, token_asset_decimals, 8, 1)
This returns the amount of the unitToken received (Canto or Note) from swapping 10 **token.decimals()
of the assetToken.
The totalSupply of lpTokens at each of the most recent 8 observations is also recorded. The lpTokenPrice is determined as follows
sum_i((token_asset_reserves[i] * price[i] + token_unit_reserves[i])/totalSupply[i])
Where token_asset_reserves
, price
, token_unit_reserves
, and totalSupply
are time weighted Averages of reserves, prices, and totalSupply from the pair. These values are determined from the most recent observations, the sample size is determined by user.
- What is the tolerance of the lpToken Pricing to large moves in volatility / reserve sizes in both volatile / not-volatile pairs. I.e - try to induce large moves in reserves sizes to force price to be over/under-estimate of actual reserve ratio.
- Inaccuracy resulting from rounding errors.
- StableSwap invariant for stable pairs
x^3y + xy^3
that is used, may also lead to inaccuracy ingetAmountOut
calculations
- L560/564/569/574
10 ** (erc20(token1).decimals());
- L561/565/571/575
pair.sample(token0, decimals, 8, 1) // call to BaseV1Pair
- L562/566/572/576
pair.sampleReserves(8, 1);
This method is used to determine the price of assets in terms of Canto. This method calls quote
on the volatile pair between Canto/Asset. This returns the amount of Canto received when swapping 10 ** token.decimals()
for Canto in the Canto/Asset pair
- L532
IBaseV1Pair(pair).quote(address(token), decimals, 8)
Returns the price of the asset in Note. This is called for the deriving the price of stable assets. All stable assets are swapped in pools between the asset and Note. This method works similarly to getPriceCanto
. All stable pools obey the curve invariant x^3y + y^3x = k
as such the calculations for amountOut
are different than in Canto/Asset pairs.
- Tolerance of stableswap invariant used to large moves in reserves of either asset.
- L544
IBaseV1Pair(pair).quote(address(token), decimals, 8)
Adds the current values of the reserves and totalSupply times the timeDiff between the last observations to the supply and reserve accumulators. After updateFrequency
difference in blockTimestamps
between the last recorded block timestamp and the current block timestamp, the reserveCumulative
and totalSupplyCumulative
values are written to state. Each time that any of these values changes, the accumulators are adjusted as follows,
reserve0Cumulative += timeDiff * reserve0
reserve1Cumulative += timeDiff * reserve1
totalSupplyCumulataive += timeDiff * totalSupply
Where timeDiff is the difference in block timestamps between the current block timestamp, and the last time time that update was called. This enables time weighted average s to be calculated between observations as follows,
(observation[i].reserve0Cumulative - observation[i-1].resrve0Cumulative)/timeElapsed
This method returns the totalSum of the last n recorded reserves for either pair, this value can then be averaged by the number of requested values, to obtain a time weighted average over the number of periods requested.
This method returns the TW value of the reserves of either asset between observations. The user is able to determine the windowSize, and the number of windows to return TW values for, it is calculated as follows,
timeElapsed = observation[window * (i)].timeStamp - observation[window*(i - 1)].timeStamp
reserve0[i] = (observation[window * (i)].reserve0Cumulative - observation[window*(i - 1)].reserve0Cumulative) / timeElapsed
reserve1[i] = (observation[window * (i)].reserve1Cumulative - observation[window*(i - 1)].reserve1Cumulative) / timeElapsed
This serves the same purpose as reserves
however, the values summed are returned from sampleSupply
(defined below)
This method returns the time weighted value of the reserves of either asset between observations. The user is able to determine the windowSize, and the number of windows to return time weighted values for, it is calculated as follows,
timeElapsed = observation[window * (i)].timeStamp - observation[window*(i - 1)].timeStamp
supply[i] = (observation[window * (i)].totalSupplyCumulative - observation[window*(i - 1)].totalSupplyCumulative) / timeElapsed