Platform: Code4rena
Start Date: 14/06/2022
Pot Size: $100,000 USDC
Total HM: 26
Participants: 59
Period: 7 days
Judge: GalloDaSballo
Total Solo HM: 9
Id: 133
League: ETH
Rank: 11/59
Findings: 7
Award: $2,423.58
🌟 Selected for report: 1
🚀 Solo Findings: 0
2649.5985 CANTO - $427.91
427.9102 USDC - $427.91
https://github.com/Plex-Engineer/lending-market/blob/755424c1f9ab3f9f0408443e6606f94e4f08a990/contracts/CNote.sol#L43 https://github.com/Plex-Engineer/lending-market/blob/755424c1f9ab3f9f0408443e6606f94e4f08a990/contracts/CNote.sol#L114 https://github.com/Plex-Engineer/lending-market/blob/755424c1f9ab3f9f0408443e6606f94e4f08a990/contracts/CNote.sol#L198 https://github.com/Plex-Engineer/lending-market/blob/755424c1f9ab3f9f0408443e6606f94e4f08a990/contracts/CNote.sol#L310
The contract expects the balance of the underlying token to == 0 at all points when calling the contract functions by requiring getCashPrior() == 0, which checks token.balanceOf(address(this)) where token is the underlying asset.
An attacker can transfer any amount of the underlying asset directly to the contract and make all of the functions requiring getCashPrior() == 0 to revert.
CNote.sol#L43 CNote.sol#L114 CNote.sol#198 CNote.sol#310
Instead of checking the underlying token balance via balanceOf(address(this)) the contract could hold an internal balance of the token, mitigating the impact of tokens being forcefully transferred to the contract.
#0 - GalloDaSballo
2022-08-10T22:23:04Z
The warden has shown how, via a simple transfer of 1 wei of token, the invariant of getCashPrior() == 0
can be broken, bricking the functionality.
Because of:
I agree with High Severity
Mitigation would require using delta balances and perhaps re-thinking the need for those intermediary checks
2649.5985 CANTO - $427.91
427.9102 USDC - $427.91
By leaving _mint_to_Accountant() with no access control when accountant = address(0) it allows an attacker to call the function, mint the entire supply to themselves, and gain the accountant and admin roles. Additionally, the parameter "address accountantDelegator" is expected but never used in the function.
Add admin access control to the _mint_to_Accountant() function
#0 - nivasan1
2022-06-23T04:13:00Z
duplicate of #195
#1 - GalloDaSballo
2022-08-12T00:36:34Z
Dup of #173
1635.5547 CANTO - $264.14
264.1421 USDC - $264.14
The function does not have access control before the accountant address is set, allowing anyone to call the function, gain admin privileges, and set the accountant address.
Include access control even when _accountant is not set or set _accountant during deployment
#0 - nivasan1
2022-06-23T04:12:33Z
duplicate of #195
#1 - GalloDaSballo
2022-08-12T00:35:42Z
Dup of #195
🌟 Selected for report: joestakey
Also found by: 0x1f8b, 0x29A, 0x52, 0xDjango, 0xNazgul, 0xf15ers, 0xmint, Bronicle, Dravee, Funen, JMukesh, Limbooo, MadWookie, Picodes, Ruhum, TerrierLover, TomJ, Tutturu, WatchPug, Waze, _Adam, asutorufos, c3phas, catchup, cccz, codexploder, cryptphi, csanuragjain, defsec, fatherOfBlocks, gzeon, hake, hansfriese, hyh, ignacio, k, nxrblsrpr, oyc_109, robee, sach1r0, saian, simon135, technicallyty, zzzitron
72.5532 USDC - $72.55
687.9945 CANTO - $111.11
doTransferOut(borrower, borrowAmount); require(getCashPrior() == 0,"CNote::borrowFresh: Error in doTransferOut, impossible Liquidity in LendingMarket"); //Amount minted by Accountant is always flashed from account /* We write the previously calculated values into storage */ accountBorrows[borrower].principal = accountBorrowsNew; accountBorrows[borrower].interestIndex = borrowIndex; totalBorrows = totalBorrowsNew;
doTransferOut(redeemer, redeemAmount); /* We write previously calculated values into storage */ totalSupply = totalSupply - redeemTokens; accountTokens[redeemer] = accountTokens[redeemer] - redeemTokens; /* We emit a Transfer event, and a Redeem event */ emit Transfer(redeemer, address(this), redeemTokens); emit Redeem(redeemer, redeemAmount, redeemTokens); /* We call the defense hook */ comptroller.redeemVerify(address(this), redeemer, redeemAmount, redeemTokens);
Mitigation: Move all state updates before doTransferOut()
Reason: Constructor visibility is ignored since 0.7.0 Mitigation: Remove keyword "public"
constructor() public { // Set admin to caller admin = msg.sender; }
#0 - GalloDaSballo
2022-08-03T23:45:11Z
Valid Low
NC
1L 1NC