Platform: Code4rena
Start Date: 07/09/2022
Pot Size: $20,000 CANTO
Total HM: 7
Participants: 65
Period: 1 day
Judge: 0xean
Total Solo HM: 3
Id: 159
League: ETH
Rank: 1/65
Findings: 2
Award: $3,984.61
🌟 Selected for report: 1
🚀 Solo Findings: 1
🌟 Selected for report: hickuphh3
Also found by: Critical, __141345__, linmiaomiao, sorrynotsorry
function getUnderlyingPrice(CToken ctoken) external override view returns(uint) { address underlying; { //manual scope to pop symbol off of stack string memory symbol = ctoken.symbol(); if (compareStrings(symbol, "cCANTO")) { underlying = address(wcanto); return getPriceNote(address(wcanto), false); } else { underlying = address(ICErc20(address(ctoken)).underlying()); // We are getting the price for a CErc20 lending market } //set price statically to 1 when the Comptroller is retrieving Price if (compareStrings(symbol, "cNOTE")) { // note in terms of note will always be 1 return 1e18; // Stable coins supported by the lending market are instantiated by governance and their price will always be 1 note } else if (compareStrings(symbol, "cUSDT") && (msg.sender == Comptroller )) { uint decimals = erc20(underlying).decimals(); return 1e18 * 1e18 / (10 ** decimals); //Scale Price as a mantissa to maintain precision in comptroller } else if (compareStrings(symbol, "cUSDC") && (msg.sender == Comptroller)) { uint decimals = erc20(underlying).decimals(); return 1e18 * 1e18 / (10 ** decimals); //Scale Price as a mantissa to maintain precision in comptroller } } if (isPair(underlying)) { // this is an LP Token return getPriceLP(IBaseV1Pair(underlying)); } // this is not an LP Token else { if (isStable[underlying]) { return getPriceNote(underlying, true); // value has already been scaled } return getPriceCanto(underlying) * getPriceNote(address(wcanto), false) / 1e18; } }
The USD value of cUSDT
and cUSDC
is hardcoded and therefore fixed at 1 USD.
This can be dangerous when they depeg, which can happen, and almost happened for USDT and certainly has happened on other major stablecoins, most infamous on UST.
Therefore, we should not trust the USD value of cUSDT
and cUSDC
as statically fixed to 1 USD.
Otherwise, once they depeg, the oracle will be reporting wrong values and cause severe damage to the consumer of the oracle's price feed.
Consider using external sources for the USD value of USDT and USDC.
#0 - nivasan1
2022-09-08T21:26:16Z
duplicate of #73
🌟 Selected for report: Critical
17164.6802 CANTO - $2,772.10
The Comptroller
is expecting oracle.getUnderlyingPrice
to return 0
for errors (Compound style returns, no revert). The current implementation will throw errors, resulting in the consumer of the oracle getting unexpected errors.
function getUnderlyingPrice(CToken ctoken) external override view returns(uint) { address underlying; { //manual scope to pop symbol off of stack string memory symbol = ctoken.symbol(); if (compareStrings(symbol, "cCANTO")) { underlying = address(wcanto); return getPriceNote(address(wcanto), false); } else { underlying = address(ICErc20(address(ctoken)).underlying()); // We are getting the price for a CErc20 lending market } //set price statically to 1 when the Comptroller is retrieving Price if (compareStrings(symbol, "cNOTE")) { // note in terms of note will always be 1 return 1e18; // Stable coins supported by the lending market are instantiated by governance and their price will always be 1 note } else if (compareStrings(symbol, "cUSDT") && (msg.sender == Comptroller )) { uint decimals = erc20(underlying).decimals(); return 1e18 * 1e18 / (10 ** decimals); //Scale Price as a mantissa to maintain precision in comptroller } else if (compareStrings(symbol, "cUSDC") && (msg.sender == Comptroller)) { uint decimals = erc20(underlying).decimals(); return 1e18 * 1e18 / (10 ** decimals); //Scale Price as a mantissa to maintain precision in comptroller } } if (isPair(underlying)) { // this is an LP Token return getPriceLP(IBaseV1Pair(underlying)); } // this is not an LP Token else { if (isStable[underlying]) { return getPriceNote(underlying, true); // value has already been scaled } return getPriceCanto(underlying) * getPriceNote(address(wcanto), false) / 1e18; } }
The Comptroller
is expecting oracle.getUnderlyingPrice
to return 0
for errors (Compound style returns, no revert).
However, the current implementation will revert when errored:
function getPriceLP(IBaseV1Pair pair) internal view returns(uint) { uint[] memory supply = pair.sampleSupply(8, 1); uint[] memory prices; uint[] memory unitReserves; uint[] memory assetReserves; address token0 = pair.token0(); address token1 = pair.token1(); uint decimals;
function sampleSupply(uint points, uint window) public view returns (uint[] memory) { uint[] memory _totalSupply = new uint[](points); uint lastIndex = observations.length-1; require(lastIndex >= points * window, "PAIR::NOT READY FOR PRICING"); uint i = lastIndex - (points * window); // point from which to begin the sample uint nextIndex = 0; uint index = 0; uint timeElapsed; for(; i < lastIndex; i+=window) { nextIndex = i + window; timeElapsed = observations[nextIndex].timestamp - observations[i].timestamp; _totalSupply[index] = (observations[nextIndex].totalSupplyCumulative - observations[i].totalSupplyCumulative) / timeElapsed; index = index + 1; } return _totalSupply; }
Consider using try catch
and return 0 when errored.