LI.FI contest - shw's results

Bridge & DEX Aggregation.

General Information

Platform: Code4rena

Start Date: 24/03/2022

Pot Size: $75,000 USDC

Total HM: 15

Participants: 59

Period: 7 days

Judge: gzeon

Id: 103

League: ETH

LI.FI

Findings Distribution

Researcher Performance

Rank: 12/59

Findings: 6

Award: $1,962.85

🌟 Selected for report: 1

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: hake

Also found by: Kenshin, Ruhum, VAD37, WatchPug, csanuragjain, hickuphh3, hyh, kirk-baird, obront, pmerkleplant, rayn, shw, tintin, wuwe1

Labels

bug
duplicate
2 (Med Risk)
disagree with severity
sponsor acknowledged

Awards

77.3842 USDC - $77.38

External Links

Lines of code

https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Libraries/LibSwap.sol#L33-L35

Vulnerability details

Impact

There is a WithdrawFacet such that only the owner/admin can recover the lost funds in the contract. However, any user can retrieve the funds by using the swapTokensGeneric function, which might be unexpected behavior.

Proof of Concept

  1. Suppose that 1000 USDC is left in the contract. Now, some random user wants to withdraw it.
  2. They call the swapTokensGeneric function with corresponding parameters to swap 1000 USDC to DAI via the Uniswap V2 router.
  3. The swapTokensGeneric function calls _executeSwaps, which calls LibSwap.swap. In this function, the contract checks whether LibAsset.getOwnBalance(fromAssetId) < fromAmount or not. If it has enough balance, it does not call LibAsset.transferFromERC20 to request the tokens from the caller.
  4. Therefore, the user doesn't have to send more USDC to the contract because the funds are enough. As a result, they get the swapped DAI without providing more USDC.

GenericSwapFacet.sol#L22-L44 Swapper.sol#L12-L23 LibSwap.sol#L33-L35

If recovering funds by anyone is not the desired behavior, consider always transferring the funds from users before the first swap.

#0 - H3xept

2022-04-12T08:39:28Z

Duplicate of #66

We are aware that the contract allows users to use latent funds, although we disagree on it being an issue as no funds (ERC20 or native) should ever lay in the contract. To make sure that no value is ever kept by the diamond, we now provide refunds for outstanding user value (after bridges/swaps).

Findings Information

🌟 Selected for report: hickuphh3

Also found by: WatchPug, hyh, rayn, shw, wuwe1

Labels

bug
duplicate
2 (Med Risk)

Awards

499.3553 USDC - $499.36

External Links

Lines of code

https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Facets/CBridgeFacet.sol#L150-L156

Vulnerability details

Impact

The _startBridge function in CBridgeFacet is to bridge the tokens to CBridge by calling the sendNative or send function on the bridge contract. However, when calling the sendNative function, no native token is sent to the bridge. The sendNative call always fails because the CBridge side checks whether the exact _cBridgeData.amount of native tokens are provided. If not, it reverts the transaction.

Proof of Concept

Take the Ethereum CBridge V2 as an example. The sendNative function ensures the exact amount of native tokens are provided at line 64.

function sendNative(
 address _receiver,
 uint256 _amount,
 uint64 _dstChainId,
 uint64 _nonce,
 uint32 _maxSlippage
) external payable nonReentrant whenNotPaused {
 require(msg.value == _amount, "Amount mismatch");
 require(nativeWrap != address(0), "Native wrap not set");
 bytes32 transferId = _send(_receiver, nativeWrap, _amount, _dstChainId, _nonce, _maxSlippage);
 IWETH(nativeWrap).deposit{value: _amount}();
 emit Send(transferId, msg.sender, _receiver, nativeWrap, _amount, _dstChainId, _nonce, _maxSlippage);
}

CBridgeFacet.sol#L150-L156

Consider sending _cBridgeData.amount amount of native tokens when calling the sendNative function, for example:

ICBridge(bridge).sendNative{ value: _cBridgeData.amount }(
 _cBridgeData.receiver,
 _cBridgeData.amount,
 _cBridgeData.dstChainId,
 _cBridgeData.nonce,
 _cBridgeData.maxSlippage
);

Also, add a payable keyword to the sendNative function in the ICBridge interface.

#0 - H3xept

2022-04-11T11:00:20Z

Duplicate of #35

#1 - gzeoneth

2022-04-16T16:25:43Z

Changing to Med Risk as no fund is lost.

Findings Information

🌟 Selected for report: hickuphh3

Also found by: 0xDjango, WatchPug, catchup, csanuragjain, hyh, shw, ych18

Labels

bug
duplicate
2 (Med Risk)

Awards

303.3583 USDC - $303.36

External Links

Lines of code

https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Facets/DexManagerFacet.sol#L73

Vulnerability details

Impact

The batchRemoveDex function does not work as expected. It should remove all the given DEX addresses from the dexWhitelist. However, it only removes the first successfully found DEX address and then stops removing the rest. The functionality is broken, and therefore the admin can only remove one DEX address at a time.

Proof of Concept

There is a return statement at line 73, so after the first DEX address is found in the array and is removed, the function directly returns.

DexManagerFacet.sol#L73

Consider removing line 73 (the return in the function).

#0 - H3xept

2022-04-11T10:41:40Z

Duplicate of #34

#1 - gzeoneth

2022-04-16T16:34:28Z

Changing to Med Risk to align with #34

Findings Information

🌟 Selected for report: shw

Also found by: Picodes, hickuphh3, hyh, pmerkleplant

Labels

bug
2 (Med Risk)

Awards

665.807 USDC - $665.81

External Links

Lines of code

https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Facets/CBridgeFacet.sol#L68

Vulnerability details

Impact

When a user bridges a native token via the startBridgeTokensViaCBridge function of CBridgeFacet, the contract checks whether msg.value >= _cBridgeData.amount holds. In other words, if a user accidentally sends more native tokens than he has to, the contract accepts it but only bridges the _cBridgeData.amount amount of tokens. The rest of the tokens are left in the contract and can be recovered by anyone (see another submission for details).

Notice that in the similar functions of other facets (e.g., AnyswapFacet, HopFacet), the provided native token is ensured to be the exact bridged amount, which effectively prevents the above scenario of loss of funds.

Proof of Concept

CBridgeFacet.sol#L68

Consider changing >= to == at line 68.

#0 - H3xept

2022-04-01T09:47:01Z

Fixed by lifinance/lifi-contracts@bb21af9a30ea73393101fc80efaa3a1f7cf25bd1

#1 - gzeoneth

2022-04-16T17:42:08Z

Sponsor confirmed with fix.

Findings Information

🌟 Selected for report: hyh

Also found by: Dravee, JMukesh, Jujic, peritoflores, shw, sorrynotsorry, wuwe1

Labels

bug
duplicate
2 (Med Risk)

Awards

303.3583 USDC - $303.36

External Links

Lines of code

https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Facets/WithdrawFacet.sol#L31

Vulnerability details

Impact

The withdraw function in WithdrawFacet uses the native transfer keyword to send ETH, which is considered unsafe because of the fixed gas budget, and its functionality could be broken in some circumstances:

  1. The receiver consumes more than 2300 amounts of gas when receiving the ETH.
  2. Even if the receiver consumes less than 2300 amount of gas, the consumed gas amount could change in the future when hard forks happen and therefore could exceed the limit.

Proof of Concept

WithdrawFacet.sol#L31

Consider using a low-level call to send ETH, for example, the LibAsset.transferNativeAsset function.

#0 - H3xept

2022-04-08T15:28:25Z

Duplicate of #14

Awards

113.5781 USDC - $113.58

Labels

bug
sponsor disputed
QA (Quality Assurance)

External Links

Lines of code

https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Facets/NXTPFacet.sol#L54-L59 https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Facets/AnyswapFacet.sol#L46 https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Facets/CBridgeFacet.sol#L64 https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Facets/HopFacet.sol#L69

Vulnerability details

Impact

The startBridgeTokensVia... functions do not handle fee-on-transfer tokens (e.g, USDT) correctly. If some fee is deducted during the token transfer (LibAsset.transferFromERC20), the transaction will revert because of the following require check. As a result, users cannot bridge fee-on-transfer tokens via the startBridgeTokensVia... functions.

Proof of Concept

Take NXTPFacet as an example. At line 55, _nxtpData.amount is provided as the parameter of LibAsset.transferFromERC20. In cases where a fee-on-transfer token is transferred, the received amount could be less than _nxtpData.amount.

Then, at line 56, the contract ensures whether its balance is increased by exactly _nxtpData.amount, which is false in this case, causing the tx to be reverted.

NXTPFacet.sol#L54-L59

Consider calculating the received amount of tokens after the transfer, and modify _nxtpData.amount accordingly, for example:

LibAsset.transferFromERC20(sendingAssetId, msg.sender, address(this), _nxtpData.amount);
_nxtpData.amount = LibAsset.getOwnBalance(sendingAssetId) - _sendingAssetIdBalance;

#0 - maxklenk

2022-04-01T07:38:14Z

Thanks for pointing out that Fee-on-transfer tokens are not supported. We are aware of that and will document it accordingly in our documentation. We will not be able to support those tokens as long as the exchanges and bridges do not support them as well. The balance check prevents continuing the transaction with such a token.

#1 - gzeoneth

2022-04-16T18:03:51Z

Downgrading to Low/QA. Treating as warden's QA Report.

#2 - JeeberC4

2022-04-17T04:25:32Z

Preserving original title: Fee-on-transfer tokens are not handled correctly

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