Canto v2 contest - Critical'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: 6/50

Findings: 2

Award: $2,148.10

🌟 Selected for report: 0

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: 0x1f8b

Also found by: Critical

Labels

bug
duplicate
3 (High Risk)
sponsor confirmed

Awards

1074.0464 USDC - $1,074.05

External Links

Lines of code

https://github.com/Plex-Engineer/lending-market-v2/blob/main/contracts/Accountant/AccountantDelegate.sol#L75-L102

Vulnerability details

function sweepInterest() external override {
    if (msg.sender != admin ) {
        revert SenderNotAdmin(msg.sender);
    }
    //Total balance of Treasury => Note + CNote Balance, 
    Exp memory exRate = Exp({mantissa: cnote.exchangeRateStored()}); //used stored interest rates in determining amount to sweep
    
    //underflow impossible
    uint noteDiff = sub_(note.totalSupply(), note.balanceOf(address(this))); //Note deficit in Accountant
    uint cNoteBal = cnote.balanceOf(address(this)); //current cNote Balance
    uint cNoteAmt = mul_(cNoteBal, exRate); // cNote Balance converted to Note

    require(cNoteAmt >= noteDiff, "AccountantDelegate::sweepInterest:Error calculating interest to sweep");

    uint amtToSweep = sub_(cNoteAmt, noteDiff); // amount to sweep in Note, 
    uint cNoteToSweep = div_(amtToSweep, exRate); // amount of cNote to sweep = amtToSweep(Note) / exRate

    cNoteToSweep = (cNoteToSweep > cNoteBal) ? cNoteBal :  cNoteToSweep; 
    bool success = cnote.transfer(treasury, amtToSweep);
    if (!success) {
        revert  SweepError(treasury , amtToSweep); //handles if transfer of tokens is not successful
    }

    TreasuryInterface Treas = TreasuryInterface(treasury);
    Treas.redeem(address(cnote),amtToSweep);

    require(cnote.balanceOf(treasury) == 0, "AccountantDelegate::sweepInterestError");
}

L101 will revert when there is more than 0 of cNote in treasury's balance.

The attacker can send 1 wei of cnote to treasury and sweepInterest() will malfunction.

Remove L101.

#0 - GalloDaSballo

2022-08-16T17:21:35Z

Dup of #28

Findings Information

🌟 Selected for report: cccz

Also found by: Critical

Labels

bug
duplicate
3 (High Risk)

Awards

1074.0464 USDC - $1,074.05

External Links

Lines of code

https://github.com/Plex-Engineer/lending-market-v2/blob/main/contracts/Accountant/AccountantDelegate.sol#L75-L102

Vulnerability details

function sweepInterest() external override {
    if (msg.sender != admin ) {
        revert SenderNotAdmin(msg.sender);
    }
    //Total balance of Treasury => Note + CNote Balance, 
    Exp memory exRate = Exp({mantissa: cnote.exchangeRateStored()}); //used stored interest rates in determining amount to sweep
    
    //underflow impossible
    uint noteDiff = sub_(note.totalSupply(), note.balanceOf(address(this))); //Note deficit in Accountant
    uint cNoteBal = cnote.balanceOf(address(this)); //current cNote Balance
    uint cNoteAmt = mul_(cNoteBal, exRate); // cNote Balance converted to Note

    require(cNoteAmt >= noteDiff, "AccountantDelegate::sweepInterest:Error calculating interest to sweep");

    uint amtToSweep = sub_(cNoteAmt, noteDiff); // amount to sweep in Note, 
    uint cNoteToSweep = div_(amtToSweep, exRate); // amount of cNote to sweep = amtToSweep(Note) / exRate

    cNoteToSweep = (cNoteToSweep > cNoteBal) ? cNoteBal :  cNoteToSweep; 
    bool success = cnote.transfer(treasury, amtToSweep);
    if (!success) {
        revert  SweepError(treasury , amtToSweep); //handles if transfer of tokens is not successful
    }

    TreasuryInterface Treas = TreasuryInterface(treasury);
    Treas.redeem(address(cnote),amtToSweep);

    require(cnote.balanceOf(treasury) == 0, "AccountantDelegate::sweepInterestError");
}

In the context, cNoteToSweep represents the interest earnings in cNote.

However, the cNoteToSweep is not used when transferring cNote to the treasury at L93, instead, amtToSweep is used.

As a result, when exRate > 1, more cNote than expected will be transferred to the treasury, and this will lead to a shortage of cNote for the accountant, further leading to AccountantSupplyError when repayBorrow in cNote.

Proof of Concept

  1. Alice borrowed 1,000,000e18 note
  2. Bob borrowed 1,000,000e18 note
  3. Some time later, due to the accumulation of interest, exRate = 1.1e18
  4. Admin called sweepInterest():
  • noteDiff = 2,000,000e18
  • cNoteAmt = 2,200,000e18
  • amtToSweep = 200,000e18
  • cNoteToSweep = ~181,818e18 (181818181818181818181818)
  • cNoteBal.balanceOf(accountant) = 2,000,000e18 - 200,000e18 = 1,800,000e18
  1. Alice called repayBorrow() to repay 1,100,000e18 note:
  • accountant.redeemMarket(1,100,000e18)
  • cNoteBal.balanceOf(accountant) = 1,800,000e18 - 1,000,000e18 = 700,000e18
  1. Bob called repayBorrow() to repay 1,100,000e18 note, the transaction will revert with AccountantSupplyError, Bob was forced to continue paying interests.

Change from amtToSweep to cNoteToSweep in L93

#0 - nivasan1

2022-07-04T19:12:12Z

duplicate of #11

#1 - GalloDaSballo

2022-08-16T17:24:45Z

Dup of #11

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