Numoen contest - peakbolt's results

Automated exchange for power perpetuals.

General Information

Platform: Code4rena

Start Date: 26/01/2023

Pot Size: $60,500 USDC

Total HM: 7

Participants: 31

Period: 6 days

Judge: berndartmueller

Total Solo HM: 3

Id: 207

League: ETH

Numoen

Findings Distribution

Researcher Performance

Rank: 4/31

Findings: 2

Award: $1,960.28

🌟 Selected for report: 1

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: RaymondFam

Also found by: 0xhacksmithh, Deivitto, peakbolt, rvierdiiev

Labels

bug
2 (Med Risk)
satisfactory
duplicate-263

Awards

533.4249 USDC - $533.42

External Links

Lines of code

https://github.com/code-423n4/2023-01-numoen/blob/main/src/periphery/LendgineRouter.sol#L142-L169 https://github.com/code-423n4/2023-01-numoen/blob/main/src/periphery/LendgineRouter.sol#L87-L124 https://github.com/code-423n4/2023-01-numoen/blob/main/src/core/Lendgine.sol#L95-L99

Vulnerability details

Impact

In LendgineRouter.sol, mintCallback() will transfer token1 (collateral) to Lendgine.sol contract upon minting of Power Tokens. If token1 is a fee-on-transfer token, these transfers will incur fees and the recipient (Lendgine.sol) will receive a deducted amount that is less than the collateral amount, causing the mint to always fail.

As Numoen is permissionless, anyone can create a pool with fee-on-transfer ERC20 tokens. Example of these fee-on-transfer tokens are STA and PAXG. And some tokens (e.g. USDT) has fee-on-transfer support disabled currently but may enable it in the future.

Proof of Concept

Start by calling LengineRouter.mint() to trigger the mintCallback() function.

https://github.com/code-423n4/2023-01-numoen/blob/main/src/periphery/LendgineRouter.sol#L142-L169

mintCallback() will initiate token1 transfers to Lendgine contract (which is the msg.sender) via swap(), SafeTransferLib.safeTransfer() and pay(). Lendgine contract will receive a reduced amount due to fee deduction on transfer.

https://github.com/code-423n4/2023-01-numoen/blob/main/src/periphery/LendgineRouter.sol#L87-L124

As Lendgine contract is expecting the full amount of collateral to be received based on the balance increase, the mint transaction will revert.

uint256 balanceBefore = Balance.balance(token1); IMintCallback(msg.sender).mintCallback(collateral, amount0, amount1, liquidity, data); uint256 balanceAfter = Balance.balance(token1); if (balanceAfter < balanceBefore + collateral) revert InsufficientInputError();

https://github.com/code-423n4/2023-01-numoen/blob/main/src/core/Lendgine.sol#L95-L99

One potential mitigation is to take into account the transfer fee during the transfer request. Another option is to block these customised tokens.

#0 - c4-judge

2023-02-07T18:23:02Z

berndartmueller marked the issue as duplicate of #263

#1 - c4-judge

2023-02-16T09:50:18Z

berndartmueller marked the issue as satisfactory

Findings Information

🌟 Selected for report: peakbolt

Also found by: adeolu, rvierdiiev

Labels

bug
2 (Med Risk)
downgraded by judge
primary issue
satisfactory
selected for report
sponsor acknowledged
M-05

Awards

1426.8567 USDC - $1,426.86

External Links

Lines of code

https://github.com/code-423n4/2023-01-numoen/blob/main/src/periphery/LendgineRouter.sol#L142 https://github.com/code-423n4/2023-01-numoen/blob/main/src/periphery/LendgineRouter.sol#L87-L124 https://github.com/code-423n4/2023-01-numoen/blob/main/src/periphery/LendgineRouter.sol#L119-L123 https://github.com/code-423n4/2023-01-numoen/blob/main/src/periphery/Payment.sol#L44-L46

Vulnerability details

Impact

When the collateral/speculative token (Token1) is WETH, a borrower could mint Power Tokens and deposit the collateral tokens by sending ETH while calling the payable mint() function in LendgineRouter.sol.

The exact collateral amount required to be deposited by the borrower is only calculated during minting (due to external swap), which could be lesser than what the borrower has sent for the mint. This means that there will be excess ETH left in LengineRouter contract and they are not automatically refunded to the borrower.

Anyone that see this opportunity can call refundETH() to retrieve the excess ETH.

The borrower could retrieve the remaining ETH with a separate call to refundETH(). However, as the calls are not atomic, it is possible for a MEV bot to frontrun the borrower and steal the ETH too.

Furthermore, there are no documentation and test cases that advise or handle this issue.

Proof of Concept

First, call payable mint() in LendgineRouter contract with the required ETH amount for collateral.

function mint(MintParams calldata params) external payable checkDeadline(params.deadline) returns (uint256 shares) {

https://github.com/code-423n4/2023-01-numoen/blob/main/src/periphery/LendgineRouter.sol#L142

LendgineRouter.mintCallback() will be triggered, which will perform the external swap of the borrowed token0 to token1 on uniswap. The collateralSwap value (token1) is only calculated and known after the successful swap. Both swapped token1 and borrowed token1 are then sent to Lendgine contract (msg.sender).

// swap all token0 to token1 uint256 collateralSwap = swap( decoded.swapType, SwapParams({ tokenIn: decoded.token0, tokenOut: decoded.token1, amount: SafeCast.toInt256(amount0), recipient: msg.sender }), decoded.swapExtraData ); // send token1 back SafeTransferLib.safeTransfer(decoded.token1, msg.sender, amount1);

https://github.com/code-423n4/2023-01-numoen/blob/main/src/periphery/LendgineRouter.sol#L87-L124

After that, mintCallback() will continue to calculate the remaining token1 required to be paid by the borrower (collateralIn value).

Depending on the external swap, the collateralSwap (token1) value could be higher than expected, resulting in a lower collateralIn value. A small collateralIn value means that less ETH is required to be paid by the borrower (via the pay function), resulting in excess ETH left in the LengineRouter contract. However, the excess ETH is not automatically refunded by the mint() call.

Note: For WETH, the pay() uses the ETH balance deposited and wrap it before transferring to Lendgine contract.

// pull the rest of tokens from the user uint256 collateralIn = collateralTotal - amount1 - collateralSwap; if (collateralIn > decoded.collateralMax) revert AmountError(); pay(decoded.token1, decoded.payer, msg.sender, collateralIn);

https://github.com/code-423n4/2023-01-numoen/blob/main/src/periphery/LendgineRouter.sol#L119-L123

A MEV bot or anyone that see this opportunity can call refundETH() to retrieve the excess ETH.

function refundETH() external payable { if (address(this).balance > 0) SafeTransferLib.safeTransferETH(msg.sender, address(this).balance); }

https://github.com/code-423n4/2023-01-numoen/blob/main/src/periphery/Payment.sol#L44-L46

Automatically refund any excess ETH to the borrower.

#0 - c4-judge

2023-02-06T17:04:45Z

berndartmueller marked the issue as primary issue

#1 - kyscott18

2023-02-08T19:23:06Z

We expect this issue to be mitigated by a user using the multicall feature of our contract. When expecting to receive eth or not spending the total amount of eth sent, a multicall should be called with the second call calling refundEth() to sweep up the rest of the eth left over in the contract. Because the multicall is atomic, no bot can frontrun the user.

This situation is also present in Uniswap V3 and there has been some debate about it. For me, the general consensus is that it is not an issue as refundEth() and multicall() are expected to be used, and not using this is the fault of the user.

#2 - c4-sponsor

2023-02-08T19:23:07Z

kyscott18 marked the issue as sponsor acknowledged

#3 - berndartmueller

2023-02-14T15:51:18Z

It is the responsibility of the user to use the contracts appropriately (e.g. using multicall(..)) to make sure leftover funds are sent out. However, due to the lack of documentation to properly educate about the usage of multicall, I consider Medium severity to be appropriate.

#4 - c4-judge

2023-02-14T15:51:56Z

berndartmueller changed the severity to 2 (Med Risk)

#5 - c4-judge

2023-02-14T15:54:59Z

berndartmueller marked the issue as satisfactory

#6 - c4-judge

2023-02-14T15:55:05Z

berndartmueller marked the issue as selected for report

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