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: 43/65
Findings: 1
Award: $39.22
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: lukris02
Also found by: 0x040, 0x1f8b, 0x52, 0xA5DF, 0xNazgul, 0xSky, Bnke0x0, Bronicle, CertoraInc, Chom, CodingNameKiki, Deivitto, Diraco, Dravee, EthLedger, IgnacioB, JC, JansenC, Jeiwan, R2, RaymondFam, ReyAdmirado, Rolezn, SinceJuly, TomJ, Tomo, Yiko, a12jmx, ajtra, ak1, codexploder, cryptphi, csanuragjain, erictee, fatherOfBlocks, gogo, hake, hansfriese, hickuphh3, ignacio, ontofractal, oyc_109, p_crypt0, pashov, peritoflores, rajatbeladiya, rbserver, rokinot, rvierdiiev, tnevler
242.8216 CANTO - $39.22
[L01] Outdated compiler version [L02] Functions should check division by 0 when it's possible
[NC01] Use of block timestamp [NC02] Unnecesary use of variable
It's a best practice to use the latest compiler version. The actual compiled version is 0.8.11 (quite old) and the latest is 0.8.16.
Update the version of solidity to 0.8.16.
BaseV1-core.sol#L2 BaseV1-periphery.sol#L3
In solidity when a number is divided by 0 the transaction is reverted due to an exception.
contract Test { function TestDivision (uint parameter) external returns (uint){ return 1/parameter; } }
The transaction is reverted to the initial state.
Add a check (require or revert) that the parameter granularity is not 0 at the begining of the function.
Example for reserves function (apply to totalSupplyAvg too)
function reserves(uint granularity) external view returns(uint, uint) { + require(granularity != 0,"Division by 0"); (uint[] memory _reserves0, uint[] memory _reserves1)= sampleReserves(granularity, 1); uint reserveAverageCumulative0; uint reserveAverageCumulative1; for (uint i = 0; i < _reserves0.length; ++i) { reserveAverageCumulative0 += _reserves0[i]; //normalize the reserves for TWAP LP Oracle pricing, reserveAverageCumulative1 += _reserves1[i]; // } return (reserveAverageCumulative0 / granularity, reserveAverageCumulative1 / granularity); }
BaseV1-core.sol#L224-L235 BaseV1-core.sol#L260-L269
Block timestamps have historically been used for a variety of applications, such as entropy for random numbers (see the Entropy Illusion for further details), locking funds for periods of time, and various state-changing conditional statements that are time-dependent. Miners have the ability to adjust timestamps slightly, which can prove to be dangerous if block timestamps are used incorrectly in smart contracts.
Block timestamps should not be used for entropy or generating random numbers—i.e., they should not be the deciding factor (either directly or through some derivation) for winning a game or changing an important state.
Time-sensitive logic is sometimes required; e.g., for unlocking contracts (time-locking), completing an ICO after a few weeks, or enforcing expiry dates. It is sometimes recommended to use block.number and an average block time to estimate times; with a 10 second block time, 1 week equates to approximately, 60480 blocks. Thus, specifying a block number at which to change a contract state can be more secure, as miners are unable to easily manipulate the block number.
BaseV1-core.sol#L138 BaseV1-core.sol#L147 BaseV1-core.sol#L250 BaseV1-core.sol#L283
In the following function getUnderlyingPrice if the token is cCANTO then return directly getPriceNote but before the return is asigning the value of address(wcanto) to underlying variable when it's not going to be used.
To mitigate it there are two choices:
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; } }