Illuminate contest - unforgiven's results

Your Sole Source For Fixed-Yields.

General Information

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

Illuminate

Findings Distribution

Researcher Performance

Rank: 10/88

Findings: 9

Award: $1,544.42

🌟 Selected for report: 0

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: kenzo

Also found by: Metatron, WatchPug, cccz, unforgiven

Labels

bug
duplicate
3 (High Risk)

Awards

496.2962 USDC - $496.30

External Links

Lines of code

https://github.com/code-423n4/2022-06-illuminate/blob/912be2a90ded4a557f121fe565d12ec48d0c4684/lender/Lender.sol#L463-L469

Vulnerability details

Impact

Functions lend(tempus) in Lender convert user's underlying token to PT tokens and mint iPT token for user based on PT token amount. the code should subtract tempus PT token balance of Lender address from the return value of Tempus.depositAndFix() call but it subtracts iPT balance of Lender contract. and the code should store the balance before calling depositAndFix(). because of this bug code will mint wrong number of iPT tokens for user which can be huge number of unbacked iPT.

Proof of Concept

function depositAndFix in Tempus project executes the swap from Tempus' principal token for the underlying asset. code should subtract the balance of the lender's tempus principal token to get the amount transacted in this particular transaction -- otherwise the caller would be minted all tokens deposited. By subtracting the balance, code can get the number of tokens the user has deposited in this particular call. This is the related code in lend(tempus):

// Swap on the Tempus Router using the provided market and params IERC5095 illuminateToken = IERC5095(principalToken(u, m)); uint256 returned = ITempus(tempusAddr).depositAndFix(Any(x), Any(t), a - fee, true, r, d) - illuminateToken.balanceOf(address(this)); // Mint Illuminate zero coupons illuminateToken.mint(msg.sender, returned);

As you can see it uses IERC5095(principalToken(u, m)); balance which is iPT balance of Lender address instead of using IMarketPlace(marketPlace).markets(u, m, p) token balance of Lender address. also the code calcualtes balance after calling depositAndFix() which is another bug. it should store the balance of tempus PT token of Lender then call depositAndFix() and subtract stored balance from the return value to find exact amount of minted tempus PT tokens, and mint iPT token based on that amount. now the logic is wrong and it's gonna mint a lot of unbacked iPT tokens and other users funds would be lost.

Tools Used

VIM

fix the logic based what was intended.

#0 - KenzoAgada

2022-06-28T10:33:15Z

Duplicate of #222

Findings Information

Awards

29.8781 USDC - $29.88

Labels

bug
duplicate
3 (High Risk)

External Links

Lines of code

https://github.com/code-423n4/2022-06-illuminate/blob/912be2a90ded4a557f121fe565d12ec48d0c4684/lender/Lender.sol#L219-L222 https://github.com/code-423n4/2022-06-illuminate/blob/912be2a90ded4a557f121fe565d12ec48d0c4684/lender/Lender.sol#L301 https://github.com/code-423n4/2022-06-illuminate/blob/912be2a90ded4a557f121fe565d12ec48d0c4684/lender/Lender.sol#L524-L530 https://github.com/code-423n4/2022-06-illuminate/blob/912be2a90ded4a557f121fe565d12ec48d0c4684/lender/Lender.sol#L362-L365 https://github.com/code-423n4/2022-06-illuminate/blob/912be2a90ded4a557f121fe565d12ec48d0c4684/lender/Lender.sol#L641-L657

Vulnerability details

Impact

Functions lend() in Lender convert user's underlying token to PT tokens and mint iPT token for user based on PT token amount. but in some of lend() functions (lend(yield), lend(swivel), lend(element) and lend(sense)), code calls a external address that is specified by user and use the return value of that call to mint iPT tokens. there is no whitelist for that external address so attacker can specify malicious contract's address when Lender calls that address, contract would return a big amount and those lender functions would mint big amount of iPT token for attacker.

Proof of Concept

All the lend functions: (lend(yield), lend(swivel), lend(element) and lend(sense)) have this bug which trusts and mints iPT token based on return value of external call to user specified address(in some of the logic for minting iPT is missing which is another bug). so for simplicity we show only how this works for lend(yield) in details. This is lend(yeild) code:

/// @notice lend method signature for both illuminate and yield /// @param p value of a specific principal according to the MarketPlace Principals Enum /// @param u address of an underlying asset /// @param m maturity (timestamp) of the market /// @param a amount of underlying tokens to lend /// @param y yieldspace pool that will execute the swap for the principal token /// @return uint256 the amount of principal tokens lent out function lend( uint8 p, address u, uint256 m, uint256 a, address y ) public unpaused(p) returns (uint256) { // check the principal is illuminate or yield if (p != uint8(MarketPlace.Principals.Illuminate) && p != uint8(MarketPlace.Principals.Yield)) { revert Invalid('principal'); } // uses yield token interface... IYield pool = IYield(y); // the yield token must match the market pair if (address(pool.base()) != u) { revert NotEqual('underlying'); } else if (pool.maturity() > m) { revert NotEqual('maturity'); } // transfer from user to illuminate Safe.transferFrom(IERC20(u), msg.sender, address(this), a); if (p == uint8(MarketPlace.Principals.Yield)) { // Purchase yield PTs to lender.sol (address(this)) uint256 returned = yield(u, y, a - calculateFee(a), address(this)); // Mint and distribute equivalent illuminate PTs IERC5095(principalToken(u, m)).mint(msg.sender, returned); emit Lend(p, u, m, returned); return returned; } else { // Purchase illuminate PTs directly to msg.sender uint256 returned = yield(u, y, a - calculateFee(a), msg.sender); emit Lend(p, u, m, returned); return returned; } }

As you can see it calls yield(u, y, a - calculateFee(a), address(this)); with y which is defined by user. the return value of yield call is stored in returned which is used in minting iPT token in line IERC5095(principalToken(u, m)).mint(msg.sender, returned);. This is yield() code:

function yield( address u, address y, uint256 a, address r ) internal returns (uint256) { // preview exact swap slippage on yield uint128 returned = IYield(y).sellBasePreview(Cast.u128(a)); // send the remaing amount to the given yield pool Safe.transfer(IERC20(u), y, a); // lend out the remaining tokens in the yield pool IYield(y).sellBase(r, returned); return returned; }

As you can see it makes external calls to y and return the value of those calls. and the value of y was defined by caller. to exploit this attacker would do this steps:

  1. approve Lender to spend underlying token of attacker address.
  2. call lend(yield) with yield enum id, and set y (the yield pool to lend to) to attacker control address which is a malicious contract.
  3. contract would make external call to y to swap attacker underlying tokens(in yeild() function), and malicious contract in y address would return a huge number.
  4. contract code would mint iPT token for attacker address based on that huge number.
  5. attacker would redeem those tokens.

the root cause of the bug is that code is trusting that y is valid address but it is specified by caller and code trusts the return value of external call to arbitrary address. This is bug exists in other lend() functions lend(swivel), lend(element) and lend(sense). they make external call to user specified address and use the return value to mint iPT token. this is their codes related to this bug: https://github.com/code-423n4/2022-06-illuminate/blob/912be2a90ded4a557f121fe565d12ec48d0c4684/lender/Lender.sol#L301

https://github.com/code-423n4/2022-06-illuminate/blob/912be2a90ded4a557f121fe565d12ec48d0c4684/lender/Lender.sol#L524-L530

https://github.com/code-423n4/2022-06-illuminate/blob/912be2a90ded4a557f121fe565d12ec48d0c4684/lender/Lender.sol#L362-L365

Tools Used

VIM

check the user specified address, or check the change of PT balance of contract for minting iPT token.

#0 - sourabhmarathe

2022-06-29T12:41:41Z

Duplicate of #349.

Findings Information

🌟 Selected for report: Metatron

Also found by: 0x52, WatchPug, auditor0517, cccz, datapunk, hansfriese, hyh, kenzo, kirk-baird, shenwilly, unforgiven

Labels

bug
duplicate
3 (High Risk)
sponsor confirmed

Awards

98.9071 USDC - $98.91

External Links

Lines of code

https://github.com/code-423n4/2022-06-illuminate/blob/912be2a90ded4a557f121fe565d12ec48d0c4684/lender/Lender.sol#L247-L305 https://github.com/code-423n4/2022-06-illuminate/blob/912be2a90ded4a557f121fe565d12ec48d0c4684/lender/Lender.sol#L317-L367

Vulnerability details

Impact

lend() functions in Lender are supposed to transfer underlying token from user and lend it to receive PT tokens and then mint the same amount of iPT token for user. but in lend(swivel) and lend(element) code don't mint iPT token for user, so user funds would be lost if s/he calls those functions.

Proof of Concept

This is lend(swivel) and lend(element) code:

function lend( uint8 p, address u, uint256 m, uint256 a, uint256 r, uint256 d, address e, bytes32 i ) public unpaused(p) returns (uint256) { // Get the principal token for this market for element address principal = IMarketPlace(marketPlace).markets(u, m, p); // the element token must match the market pair if (IElementToken(principal).underlying() != u) { revert NotEqual('underlying'); } else if (IElementToken(principal).unlockTimestamp() > m) { revert NotEqual('maturity'); } // Transfer underlying token from user to illuminate Safe.transferFrom(IERC20(u), msg.sender, address(this), a); // Track the accumulated fees fees[u] += calculateFee(a); uint256 purchased; { // Create the variables needed to execute an element swap Element.FundManagement memory fund = Element.FundManagement({ sender: address(this), recipient: payable(address(this)), fromInternalBalance: false, toInternalBalance: false }); Element.SingleSwap memory swap = Element.SingleSwap({ userData: '0x00000000000000000000000000000000000000000000000000000000000000', poolId: i, amount: a - calculateFee(a), kind: Element.SwapKind.In, assetIn: Any(u), assetOut: Any(principal) }); // Conduct the swap on element purchased = IElement(e).swap(swap, fund, r, d); } emit Lend(p, u, m, purchased); return purchased; }
function lend( uint8 p, address u, uint256 m, uint256[] memory a, address y, Swivel.Order[] calldata o, Swivel.Components[] calldata s ) public unpaused(p) returns (uint256) { // lent represents the number of underlying tokens lent uint256 lent; { // returned represents the number of underlying tokens to lend to yield uint256 returned; uint256 totalFee; // iterate through each order a calculate the total lent and returned for (uint256 i = 0; i < o.length; ) { Swivel.Order memory order = o[i]; // Require the Swivel order provided matches the underlying and maturity market provided if (order.underlying != u) { revert NotEqual('underlying'); } else if (order.maturity > m) { revert NotEqual('maturity'); } { uint256 amount = a[i]; // Determine the fee uint256 fee = calculateFee(amount); // Track accumulated fees totalFee += fee; // Amount lent for this order uint256 amountLent = amount - fee; // Sum the total amount lent to Swivel (amount of ERC5095 tokens to mint) minus fees lent += amountLent; // Sum the total amount of premium paid from Swivel (amount of underlying to lend to yield) returned += amountLent * order.premium / order.principal; } unchecked { i++; } } // Track accumulated fee fees[u] += totalFee; // transfer underlying tokens from user to illuminate Safe.transferFrom(IERC20(u), msg.sender, address(this), lent); // fill the orders on swivel protocol ISwivel(swivelAddr).initiate(o, a, s); yield(u, y, returned, address(this)); } emit Lend(p, u, m, lent); return lent; }

As you can see there is no logic to mint iPT tokens for msg.sender in them similar to IERC5095(principalToken(u, m)).mint(msg.sender, returned) which is in other lend() functions. so if user calls these functions he is gonna lose his underlying tokens and no iPT token is gonna mint for user.

Tools Used

VIM

fix the logic and min iPT token for user.

#0 - sourabhmarathe

2022-06-29T13:39:53Z

Duplicate of #391.

Findings Information

🌟 Selected for report: Picodes

Also found by: Chom, Lambda, auditor0517, cryptphi, csanuragjain, hansfriese, hyh, kenzo, kirk-baird, pashov, unforgiven, zer0dot

Labels

bug
duplicate
3 (High Risk)
sponsor confirmed

Awards

82.1689 USDC - $82.17

External Links

Lines of code

https://github.com/code-423n4/2022-06-illuminate/blob/912be2a90ded4a557f121fe565d12ec48d0c4684/redeemer/Redeemer.sol#L127-L129

Vulnerability details

Impact

function redeem(for Illuminate, APWine and Tempus protocols) suppose to redeems underlying token for Illuminate, APWine and Tempus protocols. when it is called with Illuminate principle token enum id, it should burn iPT tokens and transfer underlying to user(or perform those actions for o, address of the controller or contract that manages the underlying token) but in the code, it burns iPT tokens but it doesn't transfer underlying tokens to user. so it's not possible to withdraw illuminate iPT tokens from contract.

Proof of Concept

This is part of redeem(for Illuminate, APWine and Tempus protocols) code which is responsible for MarketPlace.Principals.Illuminate:

function redeem( uint8 p, address u, uint256 m, address o ) public returns (bool) { // Get the address of the principal token being redeemed address principal = IMarketPlace(marketPlace).markets(u, m, p); if (p == uint8(MarketPlace.Principals.Illuminate)) { // Get Illuminate's principal token IERC5095 token = IERC5095(principal); // Get the amount of tokens to be redeemed from the sender uint256 amount = token.balanceOf(msg.sender); // Make sure the market has matured if (block.timestamp < token.maturity()) { revert Invalid('not matured'); } // 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); emit Redeem(0, u, m, amount); }

As you can see in the line token.burn(o, amount) it burns that amount from o address which is specified in function arguments. and then in line Safe.transferFrom(IERC20(u), lender, address(this), amount); it transfers underlying from lender to redeemer. there is no logic to transfer underlying to msg.sender or o address. So even so the iPT balance of user has been burned the user don't receive any tokens and his funds are lost. (to my understanding, underlying token should be already in Redeemer after maturity and contract only need to send them from Redeemer to user)

Tools Used

VIM

fix the line and send user underlying token to user address

#0 - sourabhmarathe

2022-06-29T14:16:08Z

Duplicate of #384.

Findings Information

🌟 Selected for report: hyh

Also found by: 0x1f8b, 0x29A, Chom, Soosh, cccz, csanuragjain, hansfriese, itsmeSTYJ, kenzo, pashov, shenwilly, unforgiven

Labels

bug
duplicate
3 (High Risk)

Awards

82.1689 USDC - $82.17

External Links

Lines of code

https://github.com/code-423n4/2022-06-illuminate/blob/912be2a90ded4a557f121fe565d12ec48d0c4684/redeemer/Redeemer.sol#L107-L131

Vulnerability details

Impact

function redeem(for Illuminate, APWine and Tempus protocols) suppose to redeems underlying token for Illuminate, APWine and Tempus protocols. when it is called with Illuminate principle token enum id, it should burn iPT tokens and transfer underlying to user(or perform those actions for o, address of the controller or contract that manages the underlying token) but in the code, it takes iPT balance of msg.sender and burn that amount from iPT balance of o.

Proof of Concept

This is part of redeem(for Illuminate, APWine and Tempus protocols) code which is responsible for MarketPlace.Principals.Illuminate:

function redeem( uint8 p, address u, uint256 m, address o ) public returns (bool) { // Get the address of the principal token being redeemed address principal = IMarketPlace(marketPlace).markets(u, m, p); if (p == uint8(MarketPlace.Principals.Illuminate)) { // Get Illuminate's principal token IERC5095 token = IERC5095(principal); // Get the amount of tokens to be redeemed from the sender uint256 amount = token.balanceOf(msg.sender); // Make sure the market has matured if (block.timestamp < token.maturity()) { revert Invalid('not matured'); } // 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); emit Redeem(0, u, m, amount); }

As you can see in the line uint256 amount = token.balanceOf(msg.sender) it takes msg.sender's iPT balances amount and in the line token.burn(o, amount) it burns that amount from o address which is specified in function arguments. it should burn amount from msg.sender or it should take o balance amount to burn from o, based on what was intended here. but here it's a critical bug which gives one permission to burn other user's iPT tokens.

Tools Used

VIM

either take o balance amount and burn that amount from o balance or burn msg.sender iPT balance amount from msg.sender balance.

#0 - sourabhmarathe

2022-06-28T20:40:50Z

Duplicate of #387.

Findings Information

🌟 Selected for report: shenwilly

Also found by: Chom, Picodes, cccz, datapunk, kenzo, unforgiven

Labels

bug
duplicate
3 (High Risk)
sponsor confirmed

Awards

287.1428 USDC - $287.14

External Links

Lines of code

https://github.com/code-423n4/2022-06-illuminate/blob/912be2a90ded4a557f121fe565d12ec48d0c4684/redeemer/Redeemer.sol#L133-L137

Vulnerability details

Impact

function redeem(for Illuminate, APWine and Tempus protocols) suppose to redeems underlying token for Illuminate, APWine and Tempus protocols. when it is called with APWine and Tempus principle token enum id, it should transfer the related PT token from Lender to Redeemer then call those protocols to withdraw underlying. the bug is in the code, it gets PT token balance amount of Lender and then it tries to transfer underlying token from Lender to Redeemer. so the logic is broken and redeem() will not work for APWine and Tempus and user fund in those protocols would be locked.

Proof of Concept

This is part of redeem(or Illuminate, APWine and Tempus protocols) code that is responsible for APWine and Tempus protocols:

else { // Get the amount of tokens to be redeemed from the principal token uint256 amount = IERC20(principal).balanceOf(lender); // Transfer the principal token from the lender contract to here Safe.transferFrom(IERC20(u), lender, address(this), amount); if (p == uint8(MarketPlace.Principals.Apwine)) { // Redeem the underlying token from APWine to Illuminate IAPWine(apwineAddr).withdraw(o, amount); } else if (p == uint8(MarketPlace.Principals.Tempus)) { // Redeem the tokens from the Tempus contract to Illuminate ITempus(tempusAddr).redeemToBacking(o, amount, 0, address(this)); } else { revert Invalid('principal'); } emit Redeem(0, u, m, amount); }

as you can see in the line uint256 amount = IERC20(principal).balanceOf(lender); it gets the balance of lender in PT token then in the line Safe.transferFrom(IERC20(u), lender, address(this), amount); it transfers that amount of underlying token from lender to redeemer instead of PT token, so it is going to revert and logic is wrong and users could never redeem PT token of APWine and Tempus protocols and protocol funds and users fund would be lock forever.

Tools Used

VIM

fix the transfer and change it to PT token.

#0 - KenzoAgada

2022-06-28T14:03:10Z

Duplicate of #268

Findings Information

🌟 Selected for report: kirk-baird

Also found by: 0xDjango, GalloDaSballo, Kumpa, kenzo, pashov, shenwilly, tintin, unforgiven

Labels

bug
duplicate
2 (Med Risk)

Awards

54.27 USDC - $54.27

External Links

Lines of code

https://github.com/code-423n4/2022-06-illuminate/blob/912be2a90ded4a557f121fe565d12ec48d0c4684/lender/Lender.sol#L73-L124

Vulnerability details

Impact

This is no limit or check in approve() functions in Lender and admin has ability to withdraw all the contract funds immediately. if admin private key leaks or admin make some mistake users fund would be lost.

Proof of Concept

This is approve() functions in Lender:

/// @notice approves the redeemer contract to spend the principal tokens held by the lender contract. /// @param u underlying token's address, used to define the market being approved /// @param m maturity of the underlying token, used to define the market being approved /// @param r the address being approved, in this case the redeemer contract /// @return bool true if the approval was successful, false otherwise function approve( address u, uint256 m, address r ) external authorized(admin) returns (bool) { // max is the maximum integer value for a 256 unsighed integer uint256 max = 2**256 - 1; // approve the underlying for max per given principal for (uint8 i; i < 9; ) { // get the principal token's address address token = IMarketPlace(marketPlace).markets(u, m, i); // check that the token is defined for this particular market if (token != address(0)) { // max approve the token Safe.approve(IERC20(token), r, max); } unchecked { i++; } } return true; } /// @notice bulk approves the usage of addresses at the given ERC20 addresses. /// @dev the lengths of the inputs must match because the arrays are paired by index /// @param u array of ERC20 token addresses that will be approved on /// @param a array of addresses that will be approved /// @return true if successful function approve(address[] calldata u, address[] calldata a) external authorized(admin) returns (bool) { uint256 len = u.length; if (len != a.length) { revert NotEqual('array length'); } uint256 max = 2**256 - 1; for (uint256 i; i < len; ) { IERC20 uToken = IERC20(u[i]); if (address(0) != (address(uToken))) { Safe.approve(uToken, a[i], max); } unchecked { i++; } } return true; }

As you can see admin can give any address permission to spend all of this contract tokens without any time delay or limit or any whitelist check. this is dangerous for users and protocol because they can lose all their funds immediately in just one transaction. if admin make some mistake or the private key leaks then funds would be lost.

Tools Used

VIM

add some propose+delay mechanism for adding new approvals. removing all approvals can be done immediately

#0 - sourabhmarathe

2022-06-29T16:38:26Z

Duplicate of #44.

Findings Information

🌟 Selected for report: kirk-baird

Also found by: cccz, kenzo, unforgiven

Labels

bug
duplicate
2 (Med Risk)

Awards

206.7901 USDC - $206.79

External Links

Lines of code

https://github.com/code-423n4/2022-06-illuminate/blob/912be2a90ded4a557f121fe565d12ec48d0c4684/lender/Lender.sol#L161-L183

Vulnerability details

Impact

Function mint() in Lender is supposed mint swaps the sender's principal tokens for illuminate's ERC5095 tokens. but it's possible to provide iPT token and mint new iPT tokens. those iPT tokens would stuck in Lender address and can cause serious problems because they are not backed by PT or underlying tokens. one problems is that it would be possible to call redeem(Illuminate) in Redeemer for Lender address to redeem Lender iPT balance, and it would cause underlying token to be transferred to Lender address and other users can't Redeem their iPT tokens.

Proof of Concept

This is mint() code in Lender:

/// @notice mint swaps the sender's principal tokens for illuminate's ERC5095 tokens in effect, this opens a new fixed rate position for the sender on illuminate /// @param p value of a specific principal according to the MarketPlace Principals Enum /// @param u address of an underlying asset /// @param m maturity (timestamp) of the market /// @param a amount being minted /// @return bool true if the mint was successful, false otherwise function mint( uint8 p, address u, uint256 m, uint256 a ) public returns (bool) { //use market interface to fetch the market for the given market pair address principal = IMarketPlace(marketPlace).markets(u, m, p); //use safe transfer lib and ERC interface... Safe.transferFrom(IERC20(principal), msg.sender, address(this), a); //use ERC5095 interface... IERC5095(principalToken(u, m)).mint(msg.sender, a); emit Mint(p, u, m, a); return true; }

As you can see there is no check that p != 0 so it's possible to wrap iPT token and mint new iPT tokens. those iPT tokens would be transferred to Lender address and by this process attacker could create unbacked iPT tokens. because total amount of iPT tokens would be larger than total amount of PT tokens. to further exploit this attacker can call redeem(illuminate) in Redeemer for Lender address. the redeem() would burn iPT balance of Lender and transfer underlying token to Lender address. so underlying token would be used to burn unbacked iPT token and those underlying token would stuck in Lender address and other users couldn't withdraw redeem their iPT tokens because of lack of underlying token. There may be other scenarios that this wraping iPT with iPT could cause serious problems too.

P.S: of course because some unlimited functionality and access in Lender for admin it's possible to recover the lost fund but contract shouldn't relay on admin action to work correct and the real serious bug is that the 1:1 relation between iPT and PT would be broken and totalSupply() of iPT token wouldn't be equal to total amount of PT tokens.

Tools Used

VIM

make sure it's not possible to mint iPT tokens by wrapping iPT tokens.

#0 - sourabhmarathe

2022-06-29T14:26:29Z

Duplicate of #273.

#1 - gzeoneth

2022-07-16T18:07:23Z

Duplicate of #42

Findings Information

🌟 Selected for report: hyh

Also found by: datapunk, unforgiven

Labels

bug
duplicate
2 (Med Risk)

Awards

206.7901 USDC - $206.79

External Links

Lines of code

https://github.com/code-423n4/2022-06-illuminate/blob/912be2a90ded4a557f121fe565d12ec48d0c4684/marketplace/MarketPlace.sol#L131-L189

Vulnerability details

Impact

functions buyPrincipalToken(), sellPrincipalToken(), sellUnderlying() and buyUnderlying() swaps user tokens and don't specify any slippage so it's possible to perform frontrun / sandwich attack and cause fund lose for users and it's possible to perform it through MEV

Proof of Concept

This is buyPrincipalToken(), sellPrincipalToken(), sellUnderlying() and buyUnderlying() functions code:

/// @notice sells the PT for the PT via the pool /// @param u address of the underlying asset /// @param m maturity (timestamp) of the market /// @param a amount of PT to swap /// @return uint128 amount of PT bought function sellPrincipalToken( address u, uint256 m, uint128 a ) external returns (uint128) { IPool pool = IPool(pools[u][m]); Safe.transfer(IERC20(address(pool.fyToken())), address(pool), a); return pool.sellFYToken(msg.sender, pool.sellFYTokenPreview(a)); } /// @notice buys the underlying for the PT via the pool /// @param u address of the underlying asset /// @param m maturity (timestamp) of the market /// @param a amount of underlying tokens to sell /// @return uint128 amount of PT received function buyPrincipalToken( address u, uint256 m, uint128 a ) external returns (uint128) { IPool pool = IPool(pools[u][m]); Safe.transfer(IERC20(address(pool.base())), address(pool), a); return pool.buyFYToken(msg.sender, pool.buyFYTokenPreview(a), a); } /// @notice sells the underlying for the PT via the pool /// @param u address of the underlying asset /// @param m maturity (timestamp) of the market /// @param a amount of underlying to swap /// @return uint128 amount of underlying sold function sellUnderlying( address u, uint256 m, uint128 a ) external returns (uint128) { IPool pool = IPool(pools[u][m]); Safe.transfer(IERC20(address(pool.base())), address(pool), a); return pool.sellBase(msg.sender, pool.sellBasePreview(a)); } /// @notice buys the underlying for the PT via the pool /// @param u address of the underlying asset /// @param m maturity (timestamp) of the market /// @param a amount of PT to swap /// @return uint128 amount of underlying bought function buyUnderlying( address u, uint256 m, uint128 a ) external returns (uint128) { IPool pool = IPool(pools[u][m]); Safe.transfer(IERC20(address(pool.fyToken())), address(pool), a); return pool.buyBase(msg.sender, pool.buyBasePreview(a), a); }

As you can see there is no slippage passed when calling pool and user can't specify slippage.

Tools Used

VIM

check for slippage when swapping like other mintWithUnderlying() and burnForUnderlying() functions.

#0 - sourabhmarathe

2022-06-28T21:08:09Z

Duplicate of #389.

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