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: 25/59
Findings: 4
Award: $694.40
π Selected for report: 0
π Solo Findings: 0
π Selected for report: hake
Also found by: Jujic, WatchPug, catchup, danb, defsec, kirk-baird, nedodn, shenwilly, sorrynotsorry
https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Libraries/LibDiamond.sol
To give more trust to users: functions that set key/critical variables should be put behind a timelock.
Remix
Add a timelock to setter functions of key/critical variables.
#0 - H3xept
2022-04-12T10:09:58Z
Duplicate of #65
π Selected for report: hyh
Also found by: Dravee, JMukesh, Jujic, peritoflores, shw, sorrynotsorry, wuwe1
The use of transfer in WithdrawFacet.sol to
send ether is now considered bad practice as gas costs can change which would break the code.
if (_assetAddress == NATIVE_ASSET) { address self = address(this); // workaround for a possible solidity bug assert(_amount <= self.balance); payable(sendTo).transfer(_amount);
https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/
https://chainsecurity.com/istanbul-hardfork-eips-increasing-gas-costs-and-more/
Recommend using call instead, and make sure to check for reentrancy.
#0 - H3xept
2022-04-08T15:27:37Z
Duplicate of #14
π Selected for report: hake
Also found by: 0v3rf10w, 0xDjango, 0xkatana, BouSalman, CertoraInc, Dravee, Hawkeye, IllIllI, JMukesh, Jujic, Kenshin, PPrieditis, Picodes, PranavG, Ruhum, SolidityScan, VAD37, WatchPug, aga7hokakological, catchup, csanuragjain, cthulhu_cult, defsec, dimitri, hickuphh3, hubble, hyh, kenta, kirk-baird, obront, peritoflores, rayn, robee, saian, samruna, shenwilly, shw, sorrynotsorry, tchkvsky, teryanarmen, ych18
113.8424 USDC - $113.84
In the DexManagerFacet
contract the owner register the address of a DEX contract to be approved for swapping but it is possible erroneously add zero address on s.dexWhitelist[_dex]
whitelist because there is no check for zero address. In this case, the user who swap tokens on this zero Dex can lose their funds.
The same situation in the batchAddDex()
function.
Remix
Add the following line to the beginning of addDex() function: require(_dex != address(0));
#0 - maxklenk
2022-03-31T08:35:53Z
Thanks for your input, this could be cleaned up by adding such a check. But I think this is neither a bug nor has a high risk attached to it, as two clearly invalid contract calls are involved.
#1 - H3xept
2022-04-12T08:46:45Z
Fixed in lifinance/lifi-contracts@9daa1a2bb3a2cf5be938a75746c46fa272b1010a
#2 - gzeoneth
2022-04-16T17:24:14Z
Downgrading to Low/QA. Treating as warden's QA Report.
#3 - JeeberC4
2022-04-17T04:22:18Z
Preserving original title: User tokens can be burned on swap
π Selected for report: Dravee
Also found by: 0v3rf10w, 0xDjango, 0xNazgul, 0xkatana, ACai, CertoraInc, FSchmoede, Funen, Hawkeye, IllIllI, Jujic, Kenshin, PPrieditis, Picodes, SolidityScan, TerrierLover, Tomio, WatchPug, catchup, csanuragjain, defsec, dimitri, hake, hickuphh3, kenta, minhquanym, obront, peritoflores, rayn, rfa, robee, saian, samruna, tchkvsky, teryanarmen, ych18
80.6218 USDC - $80.62
There is no risk of overflow caused by increamenting the iteration index in for loops (the i++ in for for (uint8 i; i < _swapData.length; i++) {. Increments perform overflow checks that are not necessary in this case.
Surround the increment expressions with an unchecked { ... } block to avoid the default overflow checks.
uint8
is less efficient than uint256
in loop iterationsThis loop use uint8 for an index parameter (i). It does not give any efficiency, actually, it is the opposite as EVM operates on default of 256-bit values so uint8 is more expensive in this case as it needs a conversion. It only gives improvements in cases where you can pack variables together, e.g. structs.
for (uint8 i; i < _swapData.length; i++) {.
Replace uint8 with uint256 in loop iterations.
!= 0 is a cheaper operation compared to > 0, when dealing with uint.
https://github.com/code-423n4/2022-03-lifinance/blob/699c2305fcfb6fe8862b75b26d1d8a2f46a551e6/src/Facets/CBridgeFacet.sol#L116 There are several other places throughout the codebase where the same optimization can be used.
Custom errors are cheaper than revert strings.
Source: https://blog.soliditylang.org/2021/04/21/custom-errors/: Starting from Solidity v0.8.4, there is a convenient and gas-efficient way to explain to users why an operation failed through the use of custom errors. Until now, you could already use strings to give more information about failures (e.g., revert("Insufficient funds.");), but they are rather expensive, especially when it comes to deploy cost, and it is difficult to use dynamic information in them. Custom errors are defined using the error statement, which can be used inside and outside of contracts (including interfaces and libraries).
Replace revert strings with custom errors.
Shortening revert strings to fit in 32 bytes will decrease deployment time gas and will decrease runtime gas when the revert condition has been met.
require(s.cBridgeChainId != _cBridgeData.dstChainId, "Cannot bridge to the same network.");
There are several other places throughout the codebase where the same optimization can be used.
Shorten the revert strings to fit in 32 bytes
For the arithmetic operations that will never over/underflow, using the unchecked directive (Solidity v0.8 has default overflow/underflow checks) can save some gas from the unnecessary internal over/underflow checks.
if (postSwapBalance > startingBalance) { finalBalance = postSwapBalance - startingBalance;
Consider using 'unchecked' where it is safe to do so.
More expensive gas usage
require( ls.dexWhitelist[_swapData[i].approveTo] == true && ls.dexWhitelist[_swapData[i].callTo] == true, "Contract call not allowed!" );
Instead of using operator && on single require check using double require check can save more gas
Where possible, use equivalent function parameters or local variables in event emits instead of state variables to prevent expensive SLOADs. Post-Berlin, SLOADs on state variables accessed first-time in a transaction increased from 800 gas to 2100, which is a 2.5x increase.
https://github.com/code-423n4/2022-03-lifinance/blob/699c2305fcfb6fe8862b75b26d1d8a2f46a551e6/src/Facets/CBridgeFacet.sol#L47
The Inited
event i uses state variable s.cBridge
and s.cBridgeChainId
instead of using the equivalent function parameters _cBridge
and _chainId
which were just used to set these state variables.
function initCbridge(address _cBridge, uint64 _chainId) external { Storage storage s = getStorage(); LibDiamond.enforceIsContractOwner(); s.cBridge = _cBridge; s.cBridgeChainId = _chainId; emit Inited(s.cBridge, s.cBridgeChainId); }
Use equivalent function parameters or local variables in event emits instead of state variables.
Gas savings
Solidity 0.8.10 has a useful change which reduced gas costs of external calls which expect a return value: https://blog.soliditylang.org/2021/11/09/solidity-0.8.10-release-announcement/
Code Generator: Skip existence check for external contract if return data is expected. In this case, the ABI decoder will revert if the contract does not exist
LIFI protocol is using 0.8.6: Updating to the newer version of solc will allow contracts to take advantage of these lower costs for external calls.
Update to 0.8.10+
Some of the variable can be cached to slightly reduce gas usage.
LibAsset
can be cached.
https://github.com/code-423n4/2022-03-lifinance/blob/699c2305fcfb6fe8862b75b26d1d8a2f46a551e6/src/Facets/NXTPFacet.sol#L46-L76
IERC20(_assetAddress)
can be cached.
https://github.com/code-423n4/2022-03-lifinance/blob/699c2305fcfb6fe8862b75b26d1d8a2f46a551e6/src/Facets/WithdrawFacet.sol#L33-L35
Consider caching those variable for read and make sure write back to storage
when using 2**256 - 1 it takes more gas, than using type(uint256).max
https://github.com/code-423n4/2022-03-lifinance/blob/699c2305fcfb6fe8862b75b26d1d8a2f46a551e6/src/Libraries/LibAsset.sol#L15 https://github.com/code-423n4/2022-03-lifinance/blob/699c2305fcfb6fe8862b75b26d1d8a2f46a551e6/src/Libraries/LibSwap.sol#L8
Use type(uint256).max
#0 - H3xept
2022-04-08T14:32:05Z
Duplicate of #197
#1 - H3xept
2022-04-08T14:51:28Z
Duplicate of #44
#2 - H3xept
2022-04-11T10:29:05Z
Duplicate of #100
#3 - H3xept
2022-04-11T10:54:40Z
Duplicate of #196
#4 - H3xept
2022-04-11T11:10:34Z
Duplicate of #100
#5 - H3xept
2022-04-11T11:54:19Z
Duplicate of #196
#6 - H3xept
2022-04-11T11:59:01Z
We internally decided to avoid unchecked operations for now.
#7 - H3xept
2022-04-11T12:04:29Z
We internally decided to avoid previx increments for now.