Platform: Code4rena
Start Date: 10/03/2022
Pot Size: $75,000 USDT
Total HM: 25
Participants: 54
Period: 7 days
Judge: pauliax
Total Solo HM: 10
Id: 97
League: ETH
Rank: 37/54
Findings: 2
Award: $183.85
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: hickuphh3
Also found by: 0v3rf10w, 0x1f8b, 0xDjango, 0xNazgul, 0xngndev, 0xwags, Cantor_Dust, CertoraInc, Dravee, IllIllI, PPrieditis, Ruhum, TerrierLover, WatchPug, XDms, benk10, berndartmueller, bitbopper, catchup, cmichel, cryptphi, csanuragjain, danb, defsec, gzeon, hagrid, hubble, jayjonah8, kenta, kyliek, minhquanym, rfa, robee, saian, samruna, throttle, ye0lde, z3s
121.6594 USDT - $121.66
LiquidityProviders
to LiquidityProvider
It's confusing to use the plural for this contract name. Consider renaming the contract (and interface) to LiquidityProvider
(singular).
payable
Function argument _to
is defined as address payable
, the type casting to payable
on L187 is redundant.
function reclaimTokens( address _token, uint256 _amount, address payable _to ) external nonReentrant onlyOwner { require(_to != address(0), "ERR__TO_IS_ZERO"); if (_token == NATIVE) { (bool success, ) = payable(_to).call{ value: _amount }(""); // @audit-info redundant type cast to `payable()` require(success, "ERR__NATIVE_TRANSFER_FAILED"); } else { IERC20Upgradeable(_token).safeTransfer(_to, _amount); } }
Remove redundant type cast:
function reclaimTokens( address _token, uint256 _amount, address payable _to ) external nonReentrant onlyOwner { require(_to != address(0), "ERR__TO_IS_ZERO"); if (_token == NATIVE) { (bool success, ) = _to.call{ value: _amount }(""); // @audit-info removed redundant type cast to `payable()` require(success, "ERR__NATIVE_TRANSFER_FAILED"); } else { IERC20Upgradeable(_token).safeTransfer(_to, _amount); } }
10**18
can be changed to 1e18
For better readability, change 10**18
to 1e18
.
1e10
instead of 10000000000For better readability and to prevent any issues, use the scientific notation 1e10
instead of 10000000000
BASE_DIVISOR
in LiquidityPool.getRewardAmount()
Using large numbers with many zeros (e.g. 10000000000) can cause issues when accidentally having inconsistent number of zeros. I recommend to use the constant variable BASE_DIVISOR
instead.
function getRewardAmount(uint256 amount, address tokenAddress) public view returns (uint256 rewardAmount) { uint256 currentLiquidity = getCurrentLiquidity(tokenAddress); uint256 providedLiquidity = liquidityProviders.getSuppliedLiquidityByToken(tokenAddress); if (currentLiquidity < providedLiquidity) { uint256 liquidityDifference = providedLiquidity - currentLiquidity; if (amount >= liquidityDifference) { rewardAmount = incentivePool[tokenAddress]; } else { // Multiply by BASE_DIVISOR to avoid 0 reward amount for small amount and liquidity difference rewardAmount = (amount * incentivePool[tokenAddress] * BASE_DIVISOR) / liquidityDifference; rewardAmount = rewardAmount / BASE_DIVISOR; } } }
LiquidityPool.sol#L184
LiquidityPool.sol#L185
Multiple spelling mistakes across contracts. Both in code (variables, functions,..) as well as in documentation.
L66: chainid
-> chainId
L75: initalizes
-> initializes
L357: Claculate
-> Calculate
L64: Updation
-> Update
L96: initalizeRewardPool
-> initializeRewardPool
L114: recepientBalance
-> recipientBalance
L178: reightful
-> rightful
L264: comitted
-> committed
hyphen/WhitelistPeriodManager.sol
L81 liqudity
-> liquidity
L102 liqudity
-> liquidity
L113 liqudity
-> liquidity
L128 liqudity
-> liquidity
L226 ERR__LENGTH_MISMACH
-> ERR__LENGTH_MISSMATCH
L246 getMaxCommunityLpPositon
-> getMaxCommunityLpPosition
(Function name! Also correct spelling in interface)
L145 transfered
-> transferred
L275 amnt
-> amount
L300 afetr
-> after
In the README (see here) it states that if someone wants to increase the liquidity of an existing position, increaseLiquidity()
should be called.
I would advise to prevent adding liquidity via _addLiquidity()
when there's already an existing position. Add a require()
to check if there's already an existing position.
function _addLiquidity(address _token, uint256 _amount) internal { require(_amount > 0, "ERR__AMOUNT_IS_0"); require(lpToken.balanceOf(_msgSender()) == 0, "ERR_LIQUIDITY_ALREADY_ADDED"); // @audit-info Check if there's an existing LP position and prevent minting multiple NFTs for same owner per LP uint256 nftId = lpToken.mint(_msgSender()); LpTokenMetadata memory data = LpTokenMetadata(_token, 0, 0); lpToken.updateTokenMetadata(nftId, data); _increaseLiquidity(nftId, _amount); }
The WhitelistPeriodManager.getMaxCommunityLpPositon() function iterates over all LP tokens. As the amount of tokens is unbound and grows by each newly added LP position, it consumes more and more gas und possibly runs out of gas.
As this function is not called in critical situations, I consider this finding as rather low risk.
I recommend to perform the calculation off-chain due to unbound for
loop which can greatly increase in size.
🌟 Selected for report: Dravee
Also found by: 0v3rf10w, 0x1f8b, 0xDjango, 0xNazgul, 0xngndev, 0xwags, Cantor_Dust, CertoraInc, IllIllI, Jujic, Kenshin, Kiep, PPrieditis, TerrierLover, Tomio, WatchPug, antonttc, benk10, berndartmueller, bitbopper, csanuragjain, defsec, gzeon, hagrid, hickuphh3, kenta, minhquanym, oyc_109, pedroais, peritoflores, rfa, robee, saian, samruna, sirhashalot, throttle, wuwe1, z3s
62.1866 USDT - $62.19
Unused state variable.
Remove unused state variables to safe gas.
Following functions should be declared external
, as functions that are never called by the contract internally should be declared external to save gas.
ExecutorManager.getExecutorStatus()
ExecutorManager.getAllExecutors()
LiquidityFarming.setRewardPerSecond()
LiquidityFarming.getNftIdsStaked()
LiquidityFarming.getRewardRatePerSecond()
LiquidityPool.setTrustedForwarder()
LiquidityPool.setLiquidityProviders()
LiquidityPool.getExecutorManager()
LiquidityProviders.getTotalReserveByToken()
LiquidityProviders.getSuppliedLiquidityByToken()
LiquidityProviders.getTotalLPFeeByToken()
LiquidityProviders.getCurrentLiquidity()
LiquidityProviders.increaseCurrentLiquidity()
LiquidityProviders.decreaseCurrentLiquidity()
LiquidityProviders.getFeeAccumulatedOnNft()
LPToken.setSvgHelper()
LPToken.getAllNftIdsByUser()
LPToken.exists()
TokenManager.getEquilibriumFee()
TokenManager.getMaxFee()
TokenManager.getTokensInfo()
TokenManager.getDepositConfig()
TokenManager.getTransferConfig()
Use the external
attribute for functions never called from the contract.
> 0
is less efficient than != 0
for unsigned integers!= 0 is a cheaper (less gas) operation for unsigned integers within require
statements compared to > 0
.
LiquidityProviders.sol#L239
LiquidityProviders.sol#L283
LiquidityProviders.sol#L410
Change > 0
to != 0
.
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.
Revert strings that are longer than 32 bytes require at least one additional mstore, along with additional overhead for computing memory offset, etc.
ExecutorManager.sol#L17
LiquidityPool.sol#L77
unchecked {}
primitive within for loopsGiven the use of Solidity compiler >= 0.8.0, there are default arithmetic checks for mathematical operations which consume additional gas for such checks internally. In expressions where we are absolutely sure of no overflows/underflows, one can use the unchecked
primitive to wrap such expressions to avoid checks and save gas.
Change to
for (uint256 i = 0; i < executorArray.length;) { addExecutor(executorArray[i]); unchecked { ++i; } }
Also here:
ExecutorManager.sol#L47
LiquidityFarming.sol#L233
WhitelistPeriodManager.sol#L180
WhitelistPeriodManager.sol#L228
WhitelistPeriodManager.sol#L248
LPToken.sol#L77
TokenManager.sol#L78
Reading array length at each iteration of the loop takes 6 gas (3 for mload and 3 to place memory_offset) in the stack.
Caching the array length in the stack saves around 3 gas per iteration.
uint length = arr.length; for (uint i; i < length; ++i) { // Operations not effecting the length of the array. }
ExecutorManager.sol#L31
ExecutorManager.sol#L47
LiquidityFarming.sol#L233
WhitelistPeriodManager.sol#L180
WhitelistPeriodManager.sol#L228
WhitelistPeriodManager.sol#L248
LPToken.sol#L77
TokenManager.sol#78
LiquidityPool.sol:sendFundsToUser()
to save gas on revertRequire statements can be placed earlier to reduce gas usage on revert.
function sendFundsToUser( address tokenAddress, uint256 amount, address payable receiver, bytes memory depositHash, uint256 tokenGasPrice, uint256 fromChainId ) external nonReentrant onlyExecutor tokenChecks(tokenAddress) whenNotPaused { require( tokenManager.getTransferConfig(tokenAddress).min <= amount && tokenManager.getTransferConfig(tokenAddress).max >= amount, "Withdraw amnt not in Cap limits" ); require(receiver != address(0), "Bad receiver address"); uint256 initialGas = gasleft(); (bytes32 hashSendTransaction, bool status) = checkHashStatus(tokenAddress, amount, receiver, depositHash); ... }
LiquidityProviders.sol:_increaseLiquidity()
to save gas on revertRequire statements can be placed earlier to reduce gas usage on revert.
function _increaseLiquidity(uint256 _nftId, uint256 _amount) internal onlyValidLpToken(_nftId, _msgSender()) { require(_amount > 0, "ERR__AMOUNT_IS_0"); // @audit-info reorder require to top of function (address token, uint256 totalSuppliedLiquidity, uint256 totalShares) = lpToken.tokenMetadata(_nftId); whiteListPeriodManager.beforeLiquidityAddition(_msgSender(), token, _amount); ... }
LiquidityProviders.sol:increaseNativeLiquidity()
to save gas on revertRequire statements can be placed earlier to reduce gas usage on revert.
function increaseNativeLiquidity(uint256 _nftId) external payable nonReentrant whenNotPaused { require(_isSupportedToken(NATIVE), "ERR__TOKEN_NOT_SUPPORTED"); // @audit-info reorder require to top of function (address token, , ) = lpToken.tokenMetadata(_nftId); require(token == NATIVE, "ERR__WRONG_FUNCTION"); (bool success, ) = address(liquidityPool).call{value: msg.value}(""); ... }
onlyValidLpToken()
See @audit-info
in following code snippet:
modifier onlyValidLpToken(uint256 _tokenId, address _transactor) { (address token, , ) = lpToken.tokenMetadata(_tokenId); // @audit-info not needed require(lpToken.exists(_tokenId), "ERR__TOKEN_DOES_NOT_EXIST"); require(lpToken.ownerOf(_tokenId) == _transactor, "ERR__TRANSACTOR_DOES_NOT_OWN_NFT"); _; }
Removing unused named return variables can reduce gas usage and improve code clarity.
Remove the unused named return variables or use them instead of creating additional variables.
L139 unnecessary named return sender
L457 unnecessary named return sender
L351 unnecessary named return sender
L416 unnecessary named return sender