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
Rank: 15/59
Findings: 4
Award: $1,265.32
π Selected for report: 0
π Solo Findings: 0
π Selected for report: hake
Also found by: Kenshin, Ruhum, VAD37, WatchPug, csanuragjain, hickuphh3, hyh, kirk-baird, obront, pmerkleplant, rayn, shw, tintin, wuwe1
77.3842 USDC - $77.38
https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Libraries/LibSwap.sol#L33
LibSwap.swap
only transfer msg.sender
token when LibAsset.getOwnBalance(fromAssetId) < fromAmount
https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Libraries/LibSwap.sol#L33-L35
if (!LibAsset.isNativeAsset(fromAssetId) && LibAsset.getOwnBalance(fromAssetId) < fromAmount) { LibAsset.transferFromERC20(fromAssetId, msg.sender, address(this), fromAmount); }
fromAmount
equal to LibAsset.getOwnBalance(fromAssetId)
. By doing this all of the token in the contract will be swapped to specified token and transfer to msg.sender
Consider change to this. Always transfer the token.
if (!LibAsset.isNativeAsset(fromAssetId)) { LibAsset.transferFromERC20(fromAssetId, msg.sender, address(this), fromAmount); }
#0 - H3xept
2022-04-12T08:39:33Z
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).
π Selected for report: kirk-baird
Also found by: TomFrenchBlockchain, VAD37, WatchPug, hyh, rayn, wuwe1
https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Libraries/LibSwap.sol#L42 https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Facets/Swapper.sol#L14-L21
Never use msg.value
inside a loop.
LibSwap.swap
use msg.value
when swapping native assets.https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Libraries/LibSwap.sol#L42
(bool success, bytes memory res) = _swapData.callTo.call{ value: msg.value }(_swapData.callData);
Swapper
call LibSwap.swap
inside a loop.https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Facets/Swapper.sol#L14-L21
for (uint8 i; i < _swapData.length; i++) { require( ls.dexWhitelist[_swapData[i].approveTo] == true && ls.dexWhitelist[_swapData[i].callTo] == true, "Contract call not allowed!" ); LibSwap.swap(_lifiData.transactionId, _swapData[i]); }
GenericSwapFacet
as a exampleAttacker can call swapTokensGeneric
with 1 ether with _swapData.length == 10
. This will swap 10 ether to SwapData.callTo
.
https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Facets/GenericSwapFacet.sol#L22-L30
function swapTokensGeneric(LiFiData memory _lifiData, LibSwap.SwapData[] calldata _swapData) public payable { uint256 receivingAssetIdBalance = LibAsset.getOwnBalance(_lifiData.receivingAssetId); // Swap _executeSwaps(_lifiData, _swapData); uint256 postSwapBalance = LibAsset.getOwnBalance(_lifiData.receivingAssetId) - receivingAssetIdBalance; LibAsset.transferAsset(_lifiData.receivingAssetId, payable(msg.sender), postSwapBalance);
https://samczsun.com/two-rights-might-make-a-wrong/
The amount of native assets to be sent should be specified in _swapData
. Check that total amount <= msg.value
.
#0 - H3xept
2022-04-11T09:20:45Z
Duplicate of #86
https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Facets/CBridgeFacet.sol#L150
_startBridge
will always fail when sending native token.
CBridgeFacet:_startBridge
is not sending native asset will calling sendNative
https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Facets/CBridgeFacet.sol#L150
ICBridge(bridge).sendNative(
sendNative
will revert when msg.value != _amount
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); }
Change to
ICBridge(bridge).sendNative{value: msg.value}(
#0 - H3xept
2022-04-11T10:59:56Z
Duplicate of #35
π Selected for report: hyh
Also found by: Dravee, JMukesh, Jujic, peritoflores, shw, sorrynotsorry, wuwe1
https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Facets/WithdrawFacet.sol#L31
src/Facets/WithdrawFacet.sol 31: payable(sendTo).transfer(_amount);
When withdrawing native token, the withdraw is being handled with aΒ payable.transfer()
call.
This is unsafe asΒ transfer
Β has hard coded gas budget and can fail.
Whenever the user either fails to implement the payable fallback function or cumulative gas cost of the function sequence invoked on a native token transfer exceeds 2300 gas consumption limit the native tokens sent end up undelivered and the corresponding user funds return functionality will fail each time.
https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now
Use call()
instead, without hardcoded gas limits along with checks-effects-interactions pattern or reentrancy guards for reentrancy protection.
#0 - H3xept
2022-04-08T10:08:17Z
Duplicate of #14