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: 2/88
Findings: 13
Award: $5,032.65
🌟 Selected for report: 1
🚀 Solo Findings: 1
https://github.com/code-423n4/2022-06-illuminate/blob/912be2a90ded4a557f121fe565d12ec48d0c4684/lender/Lender.sol#L465 https://github.com/tempus-finance/tempus-protocol/blob/f431e821c81d8cdeae8ad433d160230563f121de/contracts/TempusController.sol#L59
Confusing at least, potentially wrong
function depositAndFix( ITempusAMM tempusAMM, ITempusPool tempusPool, uint256 tokenAmount, bool isBackingToken, uint256 minTYSRate, uint256 deadline ) external payable override nonReentrant returns (uint256, uint256)
The function returns two values, while the caller expects only one
uint256 returned = ITempus(tempusAddr).depositAndFix(Any(x), Any(t), a - fee, true, r, d) - illuminateToken.balanceOf(address(this));
Also not sure if subtract illuminateToken.balanceOf(address(this)) is the correct logic here.
Maybe do the following?
uint256 returned = ITempus(tempusAddr).depositAndFix(Any(x), Any(t), a - fee, true, r, d)[0] - illuminateToken.balanceOf(address(this));
#0 - KenzoAgada
2022-06-28T10:41:35Z
Duplicate of #37
Also briefly mentions #222 ("Also not sure if subtract illuminateToken.balanceOf(address(this)) is the correct logic here.") but dunno if that's enough for a separate issue
🌟 Selected for report: kirk-baird
Also found by: csanuragjain, datapunk, ladboy233
It may have unexpected rounding issues with dividing first
The repo has changed since the contest started. The original repo has returned += amountLent * (order.premium / order.principal);
instead of the currently correct code in
https://github.com/code-423n4/2022-06-illuminate/blob/912be2a90ded4a557f121fe565d12ec48d0c4684/lender/Lender.sol#L285
fixed
#0 - sourabhmarathe
2022-06-29T17:44:40Z
Duplicate of #155.
#1 - gzeoneth
2022-07-29T17:06:04Z
Duplicate of #48
Transferred token to wrong contract
To redeem, IERC20(principal) should be transferred from lender to redeemer, not marketplace https://github.com/code-423n4/2022-06-illuminate/blob/912be2a90ded4a557f121fe565d12ec48d0c4684/redeemer/Redeemer.sol#L187
Do this instead:
IElementToken(principal).withdrawPrincipal(amount, address(this));
#0 - sourabhmarathe
2022-06-29T17:42:15Z
Duplicate of #182.
29.8781 USDC - $29.88
In https://github.com/code-423n4/2022-06-illuminate/blob/912be2a90ded4a557f121fe565d12ec48d0c4684/lender/Lender.sol#L205, y is checked against u and m, but not p.
From the usage, it seems when p == uint8(MarketPlace.Principals.Yield
, y is a pool for swapping underlying and yield PT, while when p == uint8(MarketPlace.Principals.Illuminate)
, y is a pool for swapping underlying and illuminate PTs.
Without proper checking, it is hard to see if the logic is correct. It might very well be the case, where y is used in wrong scenario.
Check the fyToken in the following fashion:
IYield pool = IYield(y); if (p == uint8(MarketPlace.Principals.Yield && pool.fyToken!=yieldPT) revert WrongY(); else if (p == uint8(MarketPlace.Principals.Yield && pool.fyToken!=yieldPT) revert WrongY();
#0 - JTraversa
2022-07-06T18:06:41Z
Duplicate of #349
#1 - KenzoAgada
2022-07-17T01:28:09Z
Hi @gzeoneth, you marked this as high risk, but the contest readme input sanitation section states that the sponsors purposefully are not "Checking to ensure the protocol enum matches the parameters provided" or checking similar input errors.
#2 - gzeoneth
2022-07-17T04:39:12Z
Hi @gzeoneth, you marked this as high risk, but the contest readme input sanitation section states that the sponsors purposefully are not "Checking to ensure the protocol enum matches the parameters provided" or checking similar input errors.
I believe that only applies to admin functions and not exploitable user function.
#3 - KenzoAgada
2022-07-17T06:17:28Z
Hmm, how come? This is why I think it applies to user functions as well -
Also, in this specific issue the issue itself seems lacking.. - the warden didn't fully mention what's the impact, there's no POC or proper explanation, and the mitigation is also wrong/lacks details.
If we'll say that the issue is exploitable and that the user can supply p==Yield
but y=swapIlluminatePTPool
, then maybe it should be considered as duplicate of the issues that mention that the user can supply his own/any pool to this function. As this is a private case of that.
#4 - gzeoneth
2022-07-18T04:38:30Z
Hmm, how come? This is why I think it applies to user functions as well -
- It includes "Checking for input amounts of 0" - but AFAIS the admin has no functions that supply input amounts.
- It states "we err on the side of having external interfaces validate their input, rather than socializing costs to do checks". The "socializing" part implies to me that other users are involved - which is not relevant for admin-only functions, where the admin is anyway the only one that will pay so there's no socializing. This sentence implies to me that they don't want all the users to pay for checks because one user might submit wrong input.
- There's nothing in that section that mentions it's only for admin functions.
Also, in this specific issue the issue itself seems lacking.. - the warden didn't fully mention what's the impact, there's no POC or proper explanation, and the mitigation is also wrong/lacks details.
If we'll say that the issue is exploitable and that the user can supply
p==Yield
buty=swapIlluminatePTPool
, then maybe it should be considered as duplicate of the issues that mention that the user can supply his own/any pool to this function. As this is a private case of that.
First off, this is considered as a duplicate of #349 with better explanation which is also a large set of similar findings. Input validation is out-of-scope only if it is not exploitable, e.g. if it will only cause the user to lose fund or the admin might set a wrong parameter because the sponsor expect to catch these error in the UI / toolchain. If it is exploitable (e.g. an attack can gain from it), then it is in-scope.
The socializing part is that you don't check things like 0 in the contract but instead doing it in the UI. If you decided to call the contract directly you are at your own risk, but if you can gain from it then that's an in-scope exploit.
#5 - KenzoAgada
2022-07-18T06:27:26Z
I agree. 👍
Honestly I think in my initial message I got the impression you intend this to be a unique finding, and not a duplicate... don't know now what gave me that impression... Sorry.
🌟 Selected for report: Metatron
Also found by: 0x52, WatchPug, auditor0517, cccz, datapunk, hansfriese, hyh, kenzo, kirk-baird, shenwilly, unforgiven
In https://github.com/code-423n4/2022-06-illuminate/blob/912be2a90ded4a557f121fe565d12ec48d0c4684/lender/Lender.sol#L301, yield PTs is purchased to lender.sol (address(this)), yet msg.sender does not have illuminate PTs Minted and distributed.
msg.sender loses fund since its underlying token is transferred into lender, yet it has no receipt to prove it.
Check the length in the following fashion:
IERC5095(principalToken(u, m)).mint(msg.sender, returned);
#0 - sourabhmarathe
2022-06-29T23:55:04Z
Duplicate of #391.
🌟 Selected for report: WatchPug
Also found by: Lambda, csanuragjain, datapunk
689.3003 USDC - $689.30
https://github.com/code-423n4/2022-06-illuminate/blob/912be2a90ded4a557f121fe565d12ec48d0c4684/redeemer/Redeemer.sol#L143 https://github.com/code-423n4/2022-06-illuminate/blob/912be2a90ded4a557f121fe565d12ec48d0c4684/redeemer/Redeemer.sol#L193
Anyone can remove the principal tokens anytime from these protocols
Tempus and Notional allows for early withdrawal, which is OK as protocol design, but since redeem() function can be called by anyone, this can lead to undesired behavior
Test maturity before calling redeem()
#0 - sourabhmarathe
2022-06-29T17:40:25Z
Duplicate of #159.
Transferred the wrong token, later withdraw will fail from APWine
To redeem, IERC20(principal) should be transferred from lender to redeemer, not u
Do this instead:
Safe.transferFrom(IERC20(principal), lender, address(this), amount);
#0 - KenzoAgada
2022-06-28T14:03:14Z
Duplicate of #268
https://github.com/code-423n4/2022-06-illuminate/blob/912be2a90ded4a557f121fe565d12ec48d0c4684/redeemer/Redeemer.sol#L193 https://github.com/notional-finance/wrapped-fcash/blob/019cfa20369d5e0d9e7a38fea936cc649704780d/contracts/wfCashERC4626.sol#L90
No redeem actually happens for Notional
maxRedeem() function is a public view function that returns balance(owner);
Use the redeem function instead: https://github.com/notional-finance/wrapped-fcash/blob/019cfa20369d5e0d9e7a38fea936cc649704780d/contracts/wfCashERC4626.sol#L205
#0 - KenzoAgada
2022-06-28T08:28:48Z
Duplicate of #341
🌟 Selected for report: 0x52
Also found by: datapunk, kenzo, kirk-baird, pashov
https://github.com/code-423n4/2022-06-illuminate/blob/912be2a90ded4a557f121fe565d12ec48d0c4684/marketplace/MarketPlace.sol#L142 https://github.com/code-423n4/2022-06-illuminate/blob/912be2a90ded4a557f121fe565d12ec48d0c4684/marketplace/MarketPlace.sol#L157 https://github.com/code-423n4/2022-06-illuminate/blob/912be2a90ded4a557f121fe565d12ec48d0c4684/marketplace/MarketPlace.sol#L172 https://github.com/code-423n4/2022-06-illuminate/blob/912be2a90ded4a557f121fe565d12ec48d0c4684/marketplace/MarketPlace.sol#L187
In Marketplace.sol The pool.base()/fyToken() transfers out of its own balance.
With transfer(), it is unclear what the workflow looks like to leverage Marketplace for swaps between the zcTokens and underlying assets. With transferFrom(), it would be much clearer. It is also more consistent with mint() and burn() functions.
Change transfer from transferFrom:
Safe.transferFrom(ERC20(address(pool.base())), msg.sender, address(pool), a); OR Safe.transferFrom(ERC20(address(pool.fyToken())), msg.sender, address(pool), a);
#0 - sourabhmarathe
2022-06-29T17:52:51Z
Duplicate of #21.
🌟 Selected for report: datapunk
1134.6506 USDC - $1,134.65
The lend function for tempus will fail with the right market.
checks if (ITempus(principal).yieldBearingToken() != IERC20Metadata(u))
, while it should check ITempus(principal).backingToken()
Do this instead:
if (ITempus(principal).backingToken() != IERC20Metadata(u))
#0 - KenzoAgada
2022-06-28T14:58:54Z
I think should probably be medium severity as user funds not at risk.
🌟 Selected for report: hyh
Also found by: datapunk, unforgiven
https://github.com/code-423n4/2022-06-illuminate/blob/912be2a90ded4a557f121fe565d12ec48d0c4684/marketplace/MarketPlace.sol#L143 https://github.com/code-423n4/2022-06-illuminate/blob/912be2a90ded4a557f121fe565d12ec48d0c4684/marketplace/MarketPlace.sol#L158 https://github.com/code-423n4/2022-06-illuminate/blob/912be2a90ded4a557f121fe565d12ec48d0c4684/marketplace/MarketPlace.sol#L173 https://github.com/code-423n4/2022-06-illuminate/blob/912be2a90ded4a557f121fe565d12ec48d0c4684/marketplace/MarketPlace.sol#L188 https://github.com/code-423n4/2022-06-illuminate/blob/912be2a90ded4a557f121fe565d12ec48d0c4684/lender/Lender.sol#L648
The preview functions return potential swap results, the caller should check these results first to avoid extreme slippages. Otherwise, the caller functions can be sandwich attacked since there is no slippage tolerance limit and the transaction won't revert even if there was 99% slippage when doing the swaps.
Check preview returns against an acceptable slippage limit, and revert otherwise. If it is too high it can lead to Denial of Service attacks but zero slippage tolerance limit is not much better.
#0 - sourabhmarathe
2022-06-28T21:08:17Z
Duplicate of #389.
#1 - gzeoneth
2022-07-24T15:59:52Z
Consider with warden's QA report #128
🌟 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
65.8003 USDC - $65.80
https://github.com/code-423n4/2022-06-illuminate/blob/912be2a90ded4a557f121fe565d12ec48d0c4684/lender/Lender.sol#L153 https://github.com/code-423n4/2022-06-illuminate/blob/912be2a90ded4a557f121fe565d12ec48d0c4684/redeemer/Redeemer.sol#L89
should be
address illuminateToken = principalToken(u, m);
There are multiple places in Lender.sol such as below, where principal token is checked for underlying token and maturity: https://github.com/code-423n4/2022-06-illuminate/blob/912be2a90ded4a557f121fe565d12ec48d0c4684/lender/Lender.sol#L331 The check could happen in Marketplace.sol when they are first set in: https://github.com/code-423n4/2022-06-illuminate/blob/912be2a90ded4a557f121fe565d12ec48d0c4684/marketplace/MarketPlace.sol#L67 https://github.com/code-423n4/2022-06-illuminate/blob/912be2a90ded4a557f121fe565d12ec48d0c4684/marketplace/MarketPlace.sol#L98
Checking Principal address early can:
Move u/m checking to Marketplace.sol from Lender.sol
🌟 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
uint256 max = type(uint256).max
in place of uint256 max = 2**256 - 1;
https://github.com/code-423n4/2022-06-illuminate/blob/912be2a90ded4a557f121fe565d12ec48d0c4684/lender/Lender.sol#L84 https://github.com/code-423n4/2022-06-illuminate/blob/912be2a90ded4a557f121fe565d12ec48d0c4684/lender/Lender.sol#L112
++i
in place of i++
https://github.com/code-423n4/2022-06-illuminate/blob/912be2a90ded4a557f121fe565d12ec48d0c4684/lender/Lender.sol#L96 https://github.com/code-423n4/2022-06-illuminate/blob/912be2a90ded4a557f121fe565d12ec48d0c4684/lender/Lender.sol#L120 https://github.com/code-423n4/2022-06-illuminate/blob/912be2a90ded4a557f121fe565d12ec48d0c4684/lender/Lender.sol#L289
There are multiple places in Lender.sol such as below, there principal token is checked for underlying token and maturity: https://github.com/code-423n4/2022-06-illuminate/blob/912be2a90ded4a557f121fe565d12ec48d0c4684/lender/Lender.sol#L331 The check could happen in Marketplace.sol when they are first set in: https://github.com/code-423n4/2022-06-illuminate/blob/912be2a90ded4a557f121fe565d12ec48d0c4684/marketplace/MarketPlace.sol#L67 https://github.com/code-423n4/2022-06-illuminate/blob/912be2a90ded4a557f121fe565d12ec48d0c4684/marketplace/MarketPlace.sol#L98
Checking Principal address ealy can:
Move u/m checking to Marketplace.sol from Lender.sol