Platform: Code4rena
Start Date: 28/06/2022
Pot Size: $25,000 USDC
Total HM: 14
Participants: 50
Period: 4 days
Judge: GalloDaSballo
Total Solo HM: 7
Id: 141
League: ETH
Rank: 8/50
Findings: 3
Award: $1,207.80
🌟 Selected for report: 2
🚀 Solo Findings: 1
🌟 Selected for report: zzzitron
716.0309 USDC - $716.03
https://github.com/Plex-Engineer/lending-market-v2/blob/443a8c0fed3c5018e95f3881a31b81a555c42b2d/contracts/CErc20Delegator.sol#L237 https://github.com/Plex-Engineer/lending-market-v2/blob/443a8c0fed3c5018e95f3881a31b81a555c42b2d/contracts/CErc20Delegator.sol#L246
When using CToken implementation with CErc20Delegator, the functions borrowRatePerBlock
and supplyRatePerBlock
will revert when the underlying functions try to update some states.
The v1 of borrowRatePerBlock and supplyRatePerBlock were view functions, but they are not anymore. The CErc20Delegator
is still using delegateToViewImplementation
for those functions. Those functions can be used, as long as the implementation does not update any state variables, i.e. the block number increase since the last update is less or equal to the updateFrequency
. However, when these functions are called after sufficient blocks are mined, they are going to revert.
Although one can still call the implementation using delegateToImplementation
, it is not a good usability, especially if those functions are used for external user interface.
The gist shows a simple test. It calls borrowRatePerBlock
and supplyRatePerBlock
first time, it suceeds. Then, it mines for more than 300 times, which is the updateFrequency
parameter. Then it calls again then fails.
Notes on the test file:
tests/Treasury/Accountant.test.ts
solidity
from ethereum-waffle for chai to use reverted
// in hardhat.config.js import chai from "chai"; import { solidity } from "ethereum-waffle"; chai.use(solidity);
hardhat
Instead of using delegateToViewImplementation
use delegateToImplementation
. Alternatively, implement view functions to query these rates in NoteInterest.sol
and CToken.sol
. It will enable to query the rates without spending gas.
#0 - GalloDaSballo
2022-08-16T13:53:08Z
The warden has shown how, due to the usage of delegateToViewImplementation
the function call to borrowRatePerBlock
and supplyRatePerBlock
will revert, provided some time has passed.
There is no more sever loss of availability than a function that doesn't work, however, at this time, because the functions are view
and "niche", the actual underlying functionality is still available via delegateToViewImplementation
, and the internal accounting of the protocol is still functioning, I believe Medium Severity to be appropriate.
🌟 Selected for report: zzzitron
Also found by: 0v3rf10w, 0x1f8b, 0x29A, AlleyCat, Bnke0x0, Chom, Funen, JC, Lambda, Limbooo, Meera, Picodes, Sm4rty, TerrierLover, TomJ, __141345__, asutorufos, aysha, c3phas, cccz, defsec, fatherOfBlocks, grGred, hake, ignacio, ladboy233, mrpathfindr, oyc_109, rfa, sach1r0, samruna, slywaters, ynnad
421.3029 USDC - $421.30
BaseV1Router01
else if (compareStrings(ctoken.symbol(), "cNOTE") && msg.sender == Comptroller) { return 1; // Note price is fixed to 1 }
The getUnderlyingPrice
function returns 1, when Comptroller is calling with CNote
. The Comptroller is using the given price as mantissa, which has 18 decimal precision.
_acceptInitialAdminDelegated
will revert in GovernorBravoDelegator
delegateTo(implementation, abi.encodeWithSignature("_acceptInitialAdmin()"));
When the GovernorBravoDelegator
is used with GovernorBravoDelegate
as the implementation, the function _acceptInitialAdminDelegated
will revert since the GovernorBravoDelegate
does not have _acceptInitialAdmin
function.
BaseV1Pair
reserve0CumulativeLast += _reserve0 * timeElapsed; reserve1CumulativeLast += _reserve1 * timeElapsed;
The BaseV1-core is using solidity version 0.8.11, so it will revert when overflow or underflow happens. Upon cumulating the time weighted reserve, it is desired to overflow. Likewise it is desired to underflow calculating price average. It also saves gas usage, to use unchecked block for those calculations.
NoteInterest.sol:104-105 in initialize
cNote = CErc20(cnoteAddr); oracle = PriceOracle(oracleAddress);
cNote
and oracle
was set without checking zero addressNoteInterest.sol:113 in setAdmin
admin = newAdmin;
NoteRateModel
uint twapMantissa = oracle.getUnderlyingPrice(cNote); // returns price as mantissa
It is good to check the returned value from oracle is sane (i.e. if it is zero) and prepare for a fallback.
BaseV1Router01
modifier ensure(uint deadline) { //require(deadline >= block.timestamp, "BaseV1Router: EXPIRED");
The deadline
is used to prevent the transaction to be withhold and to be used later. However the modifier's line to check the deadline is commented out.
int
without checking in NoteRateModel
int diff = BASE - int(twapMantissa); //possible annoyance if 1e18 - twapMantissa > 2**255, differ int InterestAdjust = (diff * int(adjusterCoefficient))/BASE; int ir = InterestAdjust + int(baseRatePerYear);
The uint256 variables are casted into int
without checking for overflow. It is probably safe in this use case, but to be sure one may use SafeCast.
CNote
When updating an implementation for a proxy, it is important to keep the storage structure. Putting non-constant state variables in a dedicated storage contract is less error-prone approach. Although CErc20Delegate has a storage contract in its inheritance tree, a state variable _accountant
was introduced in CNote
. Therefore, extra care should be paid to the state variable.
GovernorBravoDelegate
The check require(initialProposalId == 0, "GovernorBravo::_initiate: can only initiate once");
is not useful, as initialProposalId
is never updated.
CToken
Although a new argument uint8 stable_
was added, the NatSpec for the parameter is missing.
AccountantDelegator
#0 - GalloDaSballo
2022-08-14T22:53:50Z
Low
Valid Low (as the Sponsor uses another function to acceptAdmin)
Valid Low
Low
L
TODO -> Dup of #90
NC as it will never overflow but it's a code-smell
Low, there will be no risk unless the Sponsor changes the Delegate contract
R
NC
NC
Really good report, there's some personal opinion and style which helps it stand out, also the quantity of findings is good, will give it bonus points
#1 - GalloDaSballo
2022-08-14T22:54:11Z
5L 1R 3NC