Platform: Code4rena
Start Date: 21/06/2022
Pot Size: $55,000 USDC
Total HM: 29
Participants: 88
Period: 5 days
Judge: gzeon
Total Solo HM: 7
Id: 134
League: ETH
Rank: 32/88
Findings: 4
Award: $259.74
🌟 Selected for report: 0
🚀 Solo Findings: 0
29.8781 USDC - $29.88
https://github.com/code-423n4/2022-06-illuminate/blob/912be2a90ded4a557f121fe565d12ec48d0c4684/lender/Lender.sol#L219 https://github.com/code-423n4/2022-06-illuminate/blob/912be2a90ded4a557f121fe565d12ec48d0c4684/lender/Lender.sol#L648
The y
parameter, which is supposed to be a Yieldspace pool address, is not validated, thus, a malicious user-provided contract can be passed. This would allow a user to spoof the value returned by the IYield(y).sellBasePreview()
function, causing the returned amount of iPTs to mint to be much larger.
The exploiter could then redeem the tokens with the public functions in the Redeemer contract, then redeem their own Illuminate tokens and drain the protocol of any redeemable unclaimed funds in a single transaction. Note that this also doesn't require any underlying as a u
of zero can be passed as well.
Here's a quick example of a contract that could be passed as a y
parameter:
... // Where "u" is the same as the expected value in the Lender contract function base() external returns (IERC20) { return u; } // Where "m" is the same as the expected value in the Lender contract function maturity() external returns (uint32) { return m; } // Where "iPT" is the Illuminate token is the same as the expected value in the Lender contract. // This can also be any arbitrary value. function sellBasePreview(uint128) external returns (uint128) { return type(uint128).max - uint128(iPT.totalSupply()); } function sellBase(address, uint128) external returns (uint128) { } ...
Manual review.
It's probably best to store the appropriate Yieldspace pool addresses instead of allowing them to be passed from the user.
#0 - sourabhmarathe
2022-06-29T12:43:47Z
Duplicate of #349.
🌟 Selected for report: Picodes
Also found by: Chom, Lambda, auditor0517, cryptphi, csanuragjain, hansfriese, hyh, kenzo, kirk-baird, pashov, unforgiven, zer0dot
82.1689 USDC - $82.17
Incorrect transfer parameters prevent users from ever redeeming their iPTs. Funds should be transferred from the redeemer to the caller, not from the lender to the redeemer. The comment above the concerned code explains the expected behavior.
See the affected code here.
Manual review.
Replace:
Safe.transferFrom(IERC20(u), lender, address(this), amount);
With
Safe.transferFrom(IERC20(u), address(this), msg.sender, amount);
#0 - sourabhmarathe
2022-06-29T17:16:33Z
Duplicate of #384.
🌟 Selected for report: defsec
Also found by: 0x1f8b, 0x29A, 0xDjango, 0xNazgul, 0xNineDec, 0xf15ers, 0xkowloon, 0xmint, Bnke0x0, BowTiedWardens, Chom, ElKu, Funen, GalloDaSballo, GimelSec, IllIllI, JC, Kenshin, Kulk0, Lambda, Limbooo, MadWookie, Metatron, Picodes, Soosh, StErMi, TomJ, WatchPug, Waze, Yiko, _Adam, ak1, asutorufos, aysha, bardamu, catchup, datapunk, delfin454000, dipp, fatherOfBlocks, grGred, hake, hansfriese, hyh, joestakey, kebabsec, kenzo, kirk-baird, oyc_109, pashov, poirots, rfa, robee, saian, sashik_eth, shenwilly, simon135, slywaters, z3s, zeesaw, zer0dot
85.2275 USDC - $85.23
The admin can input any address in the Approve()
function of the Lender contract, when it should only be the redeemer being approved. If the admin is compromised, they could potentially steal all funds.
Interface functions that are view
functions are not marked as view
in their interfaces (example taken from lender/interfaces). For a limited example, in the IYield interface, the functions base()
, maturity()
and sellBasePreview()
are not marked as view
. This could pose issues of unexpected reentrancy which may have been averted if the functions were called with staticcall
.
Safe.sol
contracts take IERC20 parameters when they could just take addresses without needing to cast them to IERC20 in the MarketPlace.sol
contract. Shouldn't have an impact other than cleaner code.
Safe.sol
assembly is not memory safe. Just something to keep in mind since the free memory pointer is never updated as this might cause undefined behaviour.
Safe.sol
has both success()
and didLastOptionalReturnCallSucceed()
functions, when they're both essentially appear to be the same.
The only time the authorized()
modifier is used with a different address than the admin
as a parameter is in the Redeemer contract, once. It's probably better to just simplify it by removing the passed parameter altogether (and explicitly check the caller in the authRedeem()
function). This improves readability and likely gas as well.
Lender setSwivel()
function has an incorrect NatSpec comment.
MarketPlace SetAdmin()
function is prone to accidents, consider implementing a pull pattern. This is often addressed by having the admin set as a timelock executor owned by a multisig or governance though.
Lender, line 55, there's a comment about a missing event.
🌟 Selected for report: BowTiedWardens
Also found by: 0v3rf10w, 0x1f8b, 0x29A, 0xKitsune, 0xNazgul, 0xf15ers, 0xkatana, 0xkowloon, Bnke0x0, ElKu, Fitraldys, Funen, GalloDaSballo, IllIllI, JC, Kaiziron, Lambda, MadWookie, Noah3o6, Nyamcil, RoiEvenHaim, TomJ, Tomio, UnusualTurtle, Waze, _Adam, ajtra, asutorufos, bardamu, c3phas, catchup, datapunk, defsec, delfin454000, fatherOfBlocks, grGred, hake, hansfriese, hyh, ignacio, joestakey, kebabsec, ladboy233, oyc_109, pashov, poirots, rfa, robee, sach1r0, samruna, sashik_eth, simon135, slywaters, z3s, zer0dot
62.4602 USDC - $62.46