Canto v2 contest - zzzitron's results

Execution layer for original work.

General Information

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

Canto

Findings Distribution

Researcher Performance

Rank: 8/50

Findings: 3

Award: $1,207.80

🌟 Selected for report: 2

🚀 Solo Findings: 1

Findings Information

🌟 Selected for report: zzzitron

Labels

bug
2 (Med Risk)
sponsor confirmed

Awards

716.0309 USDC - $716.03

External Links

Lines of code

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

Vulnerability details

Impact

When using CToken implementation with CErc20Delegator, the functions borrowRatePerBlock and supplyRatePerBlock will revert when the underlying functions try to update some states.

Detail

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.

Proof Of Concept

gist for the test

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:

  • The setup is taken from tests/Treasury/Accountant.test.ts
  • using 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);

Tools Used

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.

Awards

421.3029 USDC - $421.30

Labels

bug
QA (Quality Assurance)

External Links

NewBlockChain QA Report

Summary / impression

  • The codebase was improved compared to the version 1.

Low

L00: price not scaled by the BASE in BaseV1Router01

  • BaseV1-periphery.sol:501

    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.

L01: _acceptInitialAdminDelegated will revert in GovernorBravoDelegator

When the GovernorBravoDelegator is used with GovernorBravoDelegate as the implementation, the function _acceptInitialAdminDelegated will revert since the GovernorBravoDelegate does not have _acceptInitialAdmin function.

L02: desirable underflow / overflow in BaseV1Pair

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.

L03: Lack of zero address check

  • NoteInterest.sol:104-105 in initialize

    cNote = CErc20(cnoteAddr); oracle = PriceOracle(oracleAddress);
    • the cNote and oracle was set without checking zero address
  • NoteInterest.sol:113 in setAdmin

    admin = newAdmin;
    • it is good to check admin for zero address, otherwise admin access will be lost forever. To renounce the admin, it is safer to add a separate function.

L04: Oracle failure check in NoteRateModel

  • NoteInterest.sol:145

    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.

L05: commented out deadline check in BaseV1Router01

  • BaseV1-periphery.sol:87

    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.

Non-critical

N00: Cast to 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.

N01: storage for delegate is outside of storage contract in 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.

N02: Meaningless check in GovernorBravoDelegate

The check require(initialProposalId == 0, "GovernorBravo::_initiate: can only initiate once"); is not useful, as initialProposalId is never updated.

N03: Missing param in NatSpec in CToken

Although a new argument uint8 stable_ was added, the NatSpec for the parameter is missing.

N04: Misleading NatSpec in AccountantDelegator

N05: Typos

#0 - GalloDaSballo

2022-08-14T22:53:50Z

L00: price not scaled by the BASE in BaseV1Router01

Low

L01: _acceptInitialAdminDelegated will revert in GovernorBravoDelegator

Valid Low (as the Sponsor uses another function to acceptAdmin)

L02: desirable underflow / overflow in BaseV1Pair

Valid Low

L03: Lack of zero address check

Low

L04: Oracle failure check in NoteRateModel

L

L05: commented out deadline check in BaseV1Router01

TODO -> Dup of #90

N00: Cast to int without checking in NoteRateModel

NC as it will never overflow but it's a code-smell

N01: storage for delegate is outside of storage contract in CNote

Low, there will be no risk unless the Sponsor changes the Delegate contract

N02: Meaningless check in GovernorBravoDelegate

R

N03 & 04: Natspec

NC

N05: Typos

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

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter