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: 19/88
Findings: 7
Award: $784.13
🌟 Selected for report: 1
🚀 Solo Findings: 0
🌟 Selected for report: kenzo
Also found by: 0x29A, GimelSec, Lambda, StErMi, cccz, csanuragjain, kirk-baird, sashik_eth, shenwilly
https://github.com/code-423n4/2022-06-illuminate/blob/912be2a90ded4a557f121fe565d12ec48d0c4684/marketplace/ERC5095.sol#L100-L101 https://github.com/code-423n4/2022-06-illuminate/blob/912be2a90ded4a557f121fe565d12ec48d0c4684/marketplace/ERC5095.sol#L116-L117
In the current implementation of ERC5095
, withdrawing or redeeming an allowance didn't reduce the allowance. An address that has been given allowance once can abuse it and keep redeeming the owner's iPT token.
withdraw
and reduce
only check the allowance, but didn't reduce it.
redeem
to redeem Alice's 100 iPT into the underlying 100 USDC token.redeem
until Alice's fund is depleted.Reduce the approval in withdraw
and redeem
:
require(_allowance[holder][msg.sender] >= underlyingAmount, 'not enough approvals'); _approve(owner, spender, _allowance[holder][msg.sender] - underlyingAmount); return IRedeemer(redeemer).authRedeem(underlying, maturity, holder, receiver, underlyingAmount);
#0 - KenzoAgada
2022-06-28T06:28:44Z
Duplicate of #245
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#L301
A malicious user can input malicious contract address y
and mint infinite iPT token.
Lending via Yield or Swivel relies on swapping the underlying token via yieldspace pool y
. However, the current implementation has two issues.
First, the user can input arbitrary y
address. This allows an attacker to provide their own malicious contract. Second, the returned PT amount is not validated. As the value of returned
is obtained from yield()
, which calls y
:
uint128 returned = IYield(y).sellBasePreview(Cast.u128(a));
A malicious y
contract can return a large value without actually transferring back the PT, and the attacker will receive free iPT. In addition, there's no cost to do this attack as the underlying token is transferred back to the malicious contract.
y
contract that implements IYield contract and mimic the behaviour of a correct Yield contract, except for two functions:
sellBasePreview()
returns maximum amount of token.sellBase()
is a no-op.lend
, using the proper parameters except for y
which will use the malicious contract.yield()
will return a maximum amount (and not transferring back any PT).Instead of relying on returned
, use the difference of PT balance before and after yield()
to ensure that Lender
received the correct amount of PT:
address yieldPT = IMarketPlace(marketPlace).markets(u, m, 2); uint256 preBalance = IERC20(yieldPT).balanceOf(address(this)); yield(u, y, a - calculateFee(a), address(this)); uint256 postBalance = IERC20(yieldPT).balanceOf(address(this)); IERC5095(principalToken(u, m)).mint(msg.sender, postBalance - preBalance);
In addition, consider being more strict by implementing a whitelist for y
to prevent future vulnerabilities by malicious contract.
#0 - sourabhmarathe
2022-06-30T00:10:26Z
Duplicate of #349.
🌟 Selected for report: Metatron
Also found by: 0x52, WatchPug, auditor0517, cccz, datapunk, hansfriese, hyh, kenzo, kirk-baird, shenwilly, unforgiven
https://github.com/code-423n4/2022-06-illuminate/blob/912be2a90ded4a557f121fe565d12ec48d0c4684/lender/Lender.sol#L307-L367 https://github.com/code-423n4/2022-06-illuminate/blob/912be2a90ded4a557f121fe565d12ec48d0c4684/lender/Lender.sol#L237-L305
Lending via Swivel & Element lend
didn't mint iPT token to the sender, causing loss of funds.
Unlike other lend
functions which swap underlying token from msg.sender
to the corresponding PT and mint iPT back to them, the current Swivel & Element lend
implementations are missing the iPT mint function.
lend
with 1000 USDC as the underlying token.Lender
but Alice didn't receive any iPT.Add the iPT minting function.
#0 - sourabhmarathe
2022-06-29T13:40:13Z
Duplicate of #391
🌟 Selected for report: hyh
Also found by: 0x1f8b, 0x29A, Chom, Soosh, cccz, csanuragjain, hansfriese, itsmeSTYJ, kenzo, pashov, shenwilly, unforgiven
Redeeming iPT via redeem
will always fail. A griefer can burn token from any holder.
Calling Illuminate redeem
is supposed to burn iPT and transfer back the underlying token to the user. However, there are several issues with the current implementation:
uint256 amount = token.balanceOf(msg.sender); ... // Burn the prinicipal token from Illuminate token.burn(o, amount); // Transfer the original underlying token back to the user Safe.transferFrom(IERC20(u), lender, address(this), amount);
First, the amount
is obtained by querying msg.sender
's balance. However, in the next step, it's token from o
address that is being burned instead of msg.sender
.
The final line should be a transfer of underlying token from Redeemer contract to the user. Instead it tries to transfer underlying token from Lender to this contract, which will always fail as Lender is not supposed to hold any underlying token.
This issue also opens up the possibility of griefing by sending the underlying token to Lender and calling redeem to burn iPT from any holder.
redeem
with Bob's address as o
.Julian clarified that the function allows anyone to burn from any address, but the owner of the iPT themselves will receive the underlying tokens. Therefore, the code should be fixed so that it will query o
(the token holder) and transfer the underlying token to them:
uint256 amount = token.balanceOf(o); ... // Burn the prinicipal token from Illuminate token.burn(o, amount); // Transfer the original underlying token back to the user Safe.transfer(IERC20(u), o, amount);
As a side note, allowing anyone to burn and redeem the iPT of any holder could be dangerous in context of composability. Contracts that aren't aware of this mechanism might end up having funds stuck in them. For example, suppose there is a USDC-iPT/ETH Uniswap pool. When the iPT has matured and someone redeemed the iPT in the pool into USDC, the USDC will be stuck forever and LPs of the pool would lose fund.
Consider limiting redeem
to msg.sender
instead of allowing arbitrary redemption of any iPT holder.
#0 - sourabhmarathe
2022-06-28T20:38:54Z
Duplicate of #387.
287.1428 USDC - $287.14
Redeeming APWine and Tempus PT will always fail, causing a portion of iPT to not be able to be redeemed for the underlying token.
The issue is caused by the incorrect implementation of redeem
:
uint256 amount = IERC20(principal).balanceOf(lender); Safe.transferFrom(IERC20(u), lender, address(this), amount);
The first line correctly calculates the balance of PT token available in Lender
. However, the second line tries to transfer the underlying token u
instead of principal
from Lender to Redeemer
. Therefore, the redeeming process will always fail as both APWine.withdraw
and ITempus.redeemToBacking
will try to redeem non-existent PT.
Fix the transfer line:
Safe.transferFrom(IERC20(principal), lender, address(this), amount);
#0 - KenzoAgada
2022-06-28T14:02:37Z
(Referring all dups here, severity should be upgraded as user funds at risk)
#1 - gzeoneth
2022-07-16T17:59:54Z
(Referring all dups here, severity should be upgraded as user funds at risk)
Agree
🌟 Selected for report: kirk-baird
Also found by: 0xDjango, GalloDaSballo, Kumpa, kenzo, pashov, shenwilly, tintin, unforgiven
A malicious or compromised governance can arbitrarily approve any tokens (especially PT tokens) inside the contract and steal them.
While the approve
function is needed to give allowance to external protocols that integrate with Illuminate, it opens up the possibility for governance to rug users.
Modify the approve
function to use a delay mechanism like withdrawing.
In addition, add a approval revoke function that can be executed instantly in case of emergency.
#0 - sourabhmarathe
2022-06-30T00:24:41Z
Because of the guarded launch, we wanted to retain certain controls over the protocol that would allow quick response to external integration vulnerabilities. That said, this is something we can remediate in the future. However, we are unsure if it this qualifies as a medium severity issue as it relates to admin
control under a guarded launch.
We will leave the severity up to the judges as we are uncertain about it at this time.
#1 - JTraversa
2022-07-06T23:36:18Z
Duplicate of #44
🌟 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
MarketPlace.sol
sellPrincipalToken()
, buyPrincipalToken()
, sellUnderlying()
, and buyUnderlying()
transfer token from the contract to the pool, instead of transferring them from msg.sender
to the pool directly. This means msg.sender
must send the token to the contract first before calling the trade function. As the function is permissionless, any PT sent to the contract can be stolen by a frontrunner.
If these functions are intended to be called atomically using external contract, the proper way to use it should be documented.
If not, considering updating transfer
to transferFrom
from msg.sender directly to the pool.
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
There is a natspec comment indicating an event should be emitted when changing the feenominator, but the event is missing.
Implement the event or remove the comment.
The @notice
comment of setSwivel
is wrong as it describe setFee's behaviour instead.
Update it so it correctly describes behaviour of setSwivel
.
L631 remaing
should be remaining
.
L125 prinicipal
should be principal
.
L189 prinicipal
should be principal
.