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: 13/59
Findings: 5
Award: $1,374.60
🌟 Selected for report: 0
🚀 Solo Findings: 0
https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Facets/DexManagerFacet.sol#L55
When batchRemoveDex() function is called with an array of dexes to be removed, the first dex in the _dexs array would be removed, but then the inner for loop would return from the function without removing the rest of the dexes. Hence, the rest of the dexes would still be whitelisted and operational.
1- Add 2 dexes using batchAddDex(): 2- Call approvedDexs(). It will return the 2 added adresses: address[]: 0xDEF171Fe48CF0115B1d80b88dc8eAB59176FEe57,0x216B4B4Ba9F3e719726886d34a177484278Bfcae 3- Call batchRemoveDex() passing this address array as argument 4- Call approvedDexs() again. It will return 1 adress whereas there should be none: address[]: 0x216B4B4Ba9F3e719726886d34a177484278Bfcae
Remix
Replace the return statement on line 55 with break, so that only the inner loop would break, but the outer loop will continue execution.
#0 - H3xept
2022-04-11T10:40:52Z
Duplicate of #34
#1 - gzeoneth
2022-04-16T16:34:36Z
Changing to Med Risk to align with #34
🌟 Selected for report: WatchPug
Also found by: VAD37, catchup, csanuragjain, rayn
https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Libraries/LibAsset.sol#L68
approveERC20() is using unlimited approval. Although unlimited approval is used by various DeFi platforms to minimize transaction fees and improve user experience, it introduces security risks as well.
https://kalis.me/unlimited-erc20-allowances/ https://blocksecteam.medium.com/unlimited-approval-in-erc20-convenience-or-security-1c8dce421ed7 https://medium.com/@rodrigoherrerai/understanding-the-problem-of-erc20-unlimited-approval-from-first-principles-d2eaf6b4ea0e
Manual analysis
Consider changing approveERC20() to approve only the required amount, or rather using increaseERC20Allowance() and decreaseERC20Allowance().
#0 - H3xept
2022-04-12T10:12:41Z
Duplicate of #130
#1 - gzeoneth
2022-04-16T17:02:16Z
Duplicate of #160
🌟 Selected for report: hake
Also found by: Jujic, WatchPug, catchup, danb, defsec, kirk-baird, nedodn, shenwilly, sorrynotsorry
196.5762 USDC - $196.58
https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Facets/DexManagerFacet.sol#L44 https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Facets/DexManagerFacet.sol#L62
removeDex(), batchRemoveDex() should have a timelock. Such functions that make critical changes should have a timelock, so that users can see the upcoming changes and have time to react to these changes.
See below for similar findings: https://consensys.net/diligence/audits/2020/12/1inch-liquidity-protocol/#unpredictable-behavior-for-users-due-to-admin-front-running-or-general-bad-timing https://github.com/code-423n4/2021-11-overlay-findings/issues/120
Manual analysis
Add timelock for critical changes which will effect user experience and decisions.
#0 - H3xept
2022-04-12T09:07:37Z
Our team is currently focusing on creating a stable and trustworthy system as fast as possible. We agree with the increased safety a DAO/Multisign mechanism and will provide them in the future. Timelocks are currently not planned, as we want to be able to react fast if we have to disable bridges for security reasons (e.g. if the underlying bridge is being exploited).
#1 - gzeoneth
2022-04-16T17:47:03Z
🌟 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
132.5612 USDC - $132.56
C4 finding submitted: (risk = 1 (Low Risk)) Missing non-zero address check when setting the contract owner
https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Libraries/LibDiamond.sol#L44
setContractOwner(address _newOwner) does not check _newOwner against zero address. Zero address check should be performed especially when changing critical addresses.
Manual analysis
Add zero address check
C4 finding submitted: (risk = 1 (Low Risk)) Setting new contract owner should be a 2-step process
https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Libraries/LibDiamond.sol#L44
setContractOwner(address _newOwner) sets the new contract owner directly. If a wrong new owner address is entered, the ownership will be lost and can not be fixed.
https://github.com/code-423n4/2022-01-livepeer-findings/issues/143
Manual analysis
Use a 2-step process where the current owner sets the new owner candidate, and the candidate claims the ownership.
C4 finding submitted: (risk = 1 (Low Risk)) Missing non-zero address check when setting the cBridge router contract
https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Facets/CBridgeFacet.sol#L45
initCbridge() does not check _cBridge against zero address.
Manual analysis
Add zero address check
C4 finding submitted: (risk = 0 (Non-critical)) Floating pragma
All the contracts in scope, some of which https://github.com/code-423n4/2022-03-lifinance/blob/main/src/LiFiDiamond.sol#L2 https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Facets/AnyswapFacet.sol#L2 https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Facets/CBridgeFacet.sol#L2
Contracts should be deployed with the same compiler version and flags that they have been tested with thoroughly. Locking the pragma helps to ensure that contracts do not accidentally get deployed using, for example, an outdated compiler version that might introduce bugs that affect the contract system negatively.
https://swcregistry.io/docs/SWC-103
Manual analysis
Lock the pragma
C4 finding submitted: (risk = 0 (Non-critical)) Unused import
https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Facets/AnyswapFacet.sol#L6 https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Facets/AnyswapFacet.sol#L9
LibDiamond.sol is imported (twice) in AnyswapAsset.sol, but it is not used.
Manual analysis
Those lines can be deleted.
#0 - H3xept
2022-04-07T13:14:37Z
There's no check in LibDiamond but the function is mainly called from the Ownership facet. There we pushed a fix in lifinance/lifi-contracts@f35ed79a266a69b363d72332b7861d15d18b98cb
#1 - H3xept
2022-04-07T13:16:03Z
Fixed in lifinance/lifi-contracts@7da994262d90dc173823588d43ce74a0e5f5576a
#2 - H3xept
2022-04-07T13:17:09Z
We will tackle this once the audit resolves.
#3 - H3xept
2022-04-07T13:17:47Z
Removed in previous commit. Duplicate of #165
#4 - H3xept
2022-04-08T15:14:39Z
Duplicate of #192
#5 - H3xept
2022-04-11T12:22:39Z
Duplicate of #143
🌟 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
76.2934 USDC - $76.29
Error messages longer than 32 bytes
There are a lot of lines. Some of them written below. https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Libraries/LibDiamond.sol#L56 https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Libraries/LibDiamond.sol#L76 https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Libraries/LibDiamond.sol#L84 https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Libraries/LibDiamond.sol#L86 https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Libraries/LibDiamond.sol#L95 https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Libraries/LibDiamond.sol#L102 https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Libraries/LibDiamond.sol#L104 https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Libraries/LibDiamond.sol#L113 https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Libraries/LibDiamond.sol#L121
Error strings longer than 32 bytes are more expensive. Also you can use custom errors for revert statements which is more gas-efficient
https://blog.polymath.network/solidity-tips-and-tricks-to-save-gas-and-reduce-bytecode-size-c44580b218e6#c17b https://blog.soliditylang.org/2021/04/21/custom-errors/
Manual analysis
Limit the error strings to 32 bytes max. Use custom errors for revert statements.
Checks can be performed before calling diamondStorage()
https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Libraries/LibDiamond.sol#L85 https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Libraries/LibDiamond.sol#L103 https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Libraries/LibDiamond.sol#L122
Zero facetAddress check can be performed before "DiamondStorage storage ds = diamondStorage();" If that check fails, unnecessary execution would be avoided.
Manual analysis
Call the zero address check before "DiamondStorage storage ds = diamondStorage();"
For loop index increments can be made unchecked. Also prefix increments are cheaper than postfix increments.
https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Facets/DexManagerFacet.sol#L33 https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Facets/DexManagerFacet.sol#L52 https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Facets/DexManagerFacet.sol#L65 https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Facets/DexManagerFacet.sol#L70 https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Facets/DiamondLoupeFacet.sol#L24 https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Facets/HopFacet.sol#L48 https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Libraries/LibDiamond.sol#L67 https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Libraries/LibDiamond.sol#L92 https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Libraries/LibDiamond.sol#L110 https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Libraries/LibDiamond.sol#L125 https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Facets/Swapper.sol#L14
There is no risk of overflowing the index of the for loops. Therefore, they can be changed to unchecked to save gas. This would save more gas as iterations in the loop increases.
Manual analysis
Change the index increments to unchecked and prefix such as: for (uint256 i; i < s.dexs.length; ) { if (s.dexs[i] == _dex) { _removeDex(i); return; } unchecked { ++i; } }
Some internal functions can be made private
https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Facets/AnyswapFacet.sol#L131 https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Facets/CBridgeFacet.sol#L142 https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Facets/CBridgeFacet.sol#L176 https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Facets/CBridgeFacet.sol#L184 https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Facets/HopFacet.sol#L134 https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Facets/HopFacet.sol#L180 https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Facets/HopFacet.sol#L185 https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Facets/NXTPFacet.sol#L175 https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Facets/NXTPFacet.sol#L190
Calling private functions are cheaper than calling internal functions. Therefore, it is better to declare functions private if they are not called from inherited contracts.
Manual analysis
Change the visibility to private where possible.
It's better to use calldata for the read-only function arguments on external functions
https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Facets/HopFacet.sol#L41 https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Facets/HopFacet.sol#L42
When external functions' read-only arguments are used as memory, intermediate memory operations are performed which costs gas. You can directly read these arguments from calldata.
Manual analysis
Change from memory data location to calldata.
No need to cache _functionSelectors[selectorIndex]
https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Libraries/LibDiamond.sol#L93 https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Libraries/LibDiamond.sol#L111 https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Libraries/LibDiamond.sol#L126
At addFunctions(), replaceFunctions(), removeFunctions(); _functionSelectors[selectorIndex] is cached to selector and selector is used. _functionSelectors[selectorIndex] can be directly used where necessary.
Manual analysis
For example addFunctions() can be changed as below and so as the other 2 functions: for (uint256 selectorIndex; selectorIndex < _functionSelectors.length; selectorIndex++) { //bytes4 selector = _functionSelectors[selectorIndex]; address oldFacetAddress = ds.selectorToFacetAndPosition[_functionSelectors[selectorIndex]].facetAddress; require(oldFacetAddress == address(0), "LibDiamondCut: Can't add function that already exists"); addFunction(ds, _functionSelectors[selectorIndex], selectorPosition, _facetAddress); selectorPosition++; }
Unused constant definition in LibSwap.sol
https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Libraries/LibSwap.sol#L8
The MAX_INT is defined, but it is not used anywhere.
Manual analysis
Remove the definition
Operation can be made unchecked
https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Facets/NXTPFacet.sol#L166
Subtraction operation can be safely made unchecked, since underflow is not possible. Saves ~2K gas without optimization.
Remix
Change the line as: unchecked { finalBalance = postSwapBalance - startingBalance; }
Define _postSwapBalance first and reuse it,
https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Facets/AnyswapFacet.sol#L103
_postSwapBalance can be defined before the require statement and used within the require. That would save making one less subtraction operation. Saves ~2.4K gas without optimization.
Remix
Change the code as: // Swap _executeSwaps(_lifiData, _swapData);
uint256 _postSwapBalance = address(this).balance - _fromBalance; require(_postSwapBalance >= _anyswapData.amount, "ERR_INVALID_AMOUNT"); require(_postSwapBalance > 0, "ERR_INVALID_AMOUNT"); _anyswapData.amount = _postSwapBalance;
Reorder lines to check for the contract owner first
https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Facets/CBridgeFacet.sol#L44 https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Facets/HopFacet.sol#L46 https://github.com/code-423n4/2022-03-lifinance/blob/main/src/Facets/NXTPFacet.sol#L35
It is better to make the contract owner check before calling the getStorage(). In case the check fails, getStorage() would not be executed unnecessarily.
Manual analysis
Change the code as: function initCbridge(address _cBridge, uint64 _chainId) external { LibDiamond.enforceIsContractOwner(); Storage storage s = getStorage(); s.cBridge = _cBridge; s.cBridgeChainId = _chainId; emit Inited(s.cBridge, s.cBridgeChainId); }
#0 - H3xept
2022-04-04T08:30:51Z
#1 - H3xept
2022-04-04T08:36:53Z
Fixed in lifinance/lifi-contracts@d9f88eecc014d5ba1c0e14949dcef5a393f40e9e
#2 - H3xept
2022-04-04T08:37:22Z
We internally decided to avoid unchecked statements for now.
#3 - H3xept
2022-04-04T08:40:04Z
Re: Remove MAX_INT Fixed in previous commit.
#4 - H3xept
2022-04-04T08:42:04Z
Fixed in lifinance/lifi-contracts@87a27cee2fbde337c4ab873971f37573d2240994
#5 - H3xept
2022-04-04T08:42:54Z
Fixed in lifinance/lifi-contracts@36039dd114fc23acebb60cd195e1175a076e71b8
#6 - H3xept
2022-04-08T15:17:04Z
Duplicate of #100
#7 - H3xept
2022-04-11T10:28:19Z
Duplicate of #100
#8 - H3xept
2022-04-11T12:40:31Z
Duplicate of #152