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: 16/54
Findings: 3
Award: $792.10
🌟 Selected for report: 1
🚀 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
129.8697 USDT - $129.87
LiquidityFarming.sol
's contract wasn't named after the file (HyphenLiquidityFarming
)initialize()
functions are front-runnable in the solution.I suggest adding some access control to them:
contracts/hyphen/LiquidityFarming.sol: 78: function initialize( contracts/hyphen/LiquidityPool.sol: 87: function initialize( contracts/hyphen/LiquidityProviders.sol: 78: function initialize( contracts/hyphen/WhitelistPeriodManager.sol: 60: function initialize( contracts/hyphen/token/LPToken.sol: 36: function initialize(
Fees in TokenManager.sol:function changeFee()
should be upper-bounded
Some comments are missing. See @audit
tags:
contracts/hyphen/LiquidityFarming.sol: 167 /// @notice Sets the sushi per second to be distributed. Can only be called by the owner. 168: /// @param _rewardPerSecond The amount of Sushi to be distributed per second. //@audit missing @param _baseToken 169 function setRewardPerSecond(address _baseToken, uint256 _rewardPerSecond) public onlyOwner { 194 /// @notice Deposit LP tokens 195: /// @param _nftId LP token nftId to deposit. //@audit missing @param _to (see L228: it's present) 196 function deposit(uint256 _nftId, address payable _to) external whenNotPaused nonReentrant { 313: /// @notice Update reward variables of the given pool. //@audit missing @param _baseToken 314 /// @return pool Returns the pool that was updated. 315 function updatePool(address _baseToken) public whenNotPaused returns (PoolInfo memory pool) { 327 /// @notice View function to see the tokens staked by a given user. 328: /// @param _user Address of user. //@audit missing @return nftIds 329 function getNftIdsStaked(address _user) public view returns (uint256[] memory nftIds) { contracts/hyphen/LiquidityPool.sol: 142 /** 143 * @dev Function used to deposit tokens into pool to initiate a cross chain token transfer. 144 * @param toChainId Chain id where funds needs to be transfered 145 * @param tokenAddress ERC20 Token address that needs to be transfered 146 * @param receiver Address on toChainId where tokens needs to be transfered 147: * @param amount Amount of token being transfered //@audit missing @param tag 148 */ 149 function depositErc20( 150 uint256 toChainId, 151 address tokenAddress, 152 address receiver, 153 uint256 amount, 154 string memory tag 155 ) public tokenChecks(tokenAddress) whenNotPaused nonReentrant { 237 /** 238 * @dev Function used to deposit native token into pool to initiate a cross chain token transfer. 239 * @param receiver Address on toChainId where tokens needs to be transfered 240: * @param toChainId Chain id where funds needs to be transfered //@audit missing @param tag 241 */ 242 function depositNative( 243 address receiver, 244 uint256 toChainId, 245 string memory tag 246 ) external payable whenNotPaused nonReentrant { contracts/hyphen/LiquidityProviders.sol: 74 /** 75 * @dev initalizes the contract, acts as constructor 76: * @param _trustedForwarder address of trusted forwarder //@audit missing @param x3 77 */ 78 function initialize( 79 address _trustedForwarder, 80 address _lpToken, 81 address _tokenManager, 82 address _pauser 83 ) public initializer { contracts/hyphen/WhitelistPeriodManager.sol: 56 /** 57 * @dev initalizes the contract, acts as constructor 58: * @param _trustedForwarder address of trusted forwarder //@audit missing @param x4 59 */ 60 function initialize( 61 address _trustedForwarder, 62 address _liquidityProviders, 63 address _tokenManager, 64 address _lpToken, 65 address _pauser 66 ) public initializer {
- ExecutorManager.getExecutorStatus(address) (contracts/hyphen/ExecutorManager.sol#21-23) - ExecutorManager.getAllExecutors() (contracts/hyphen/ExecutorManager.sol#25-27) - HyphenLiquidityFarming.initialize(address,address,ILiquidityProviders,ILPToken) (contracts/hyphen/LiquidityFarming.sol#78-90) - HyphenLiquidityFarming.setRewardPerSecond(address,uint256) (contracts/hyphen/LiquidityFarming.sol#169-172) - HyphenLiquidityFarming.getNftIdsStaked(address) (contracts/hyphen/LiquidityFarming.sol#329-331) - HyphenLiquidityFarming.getRewardRatePerSecond(address) (contracts/hyphen/LiquidityFarming.sol#333-335) - LiquidityPool.initialize(address,address,address,address,address) (contracts/hyphen/LiquidityPool.sol#87-105) - LiquidityPool.setTrustedForwarder(address) (contracts/hyphen/LiquidityPool.sol#107-111) - LiquidityPool.setLiquidityProviders(address) (contracts/hyphen/LiquidityPool.sol#113-117) - LiquidityPool.getExecutorManager() (contracts/hyphen/LiquidityPool.sol#123-125) - LiquidityProviders.initialize(address,address,address,address) (contracts/hyphen/LiquidityProviders.sol#78-90) - LiquidityProviders.getTotalReserveByToken(address) (contracts/hyphen/LiquidityProviders.sol#96-98) - LiquidityProviders.getSuppliedLiquidityByToken(address) (contracts/hyphen/LiquidityProviders.sol#100-102) - LiquidityProviders.getTotalLPFeeByToken(address) (contracts/hyphen/LiquidityProviders.sol#104-106) - LiquidityProviders.getCurrentLiquidity(address) (contracts/hyphen/LiquidityProviders.sol#108-110) - LiquidityProviders.increaseCurrentLiquidity(address,uint256) (contracts/hyphen/LiquidityProviders.sol#127-129) - LiquidityProviders.decreaseCurrentLiquidity(address,uint256) (contracts/hyphen/LiquidityProviders.sol#131-133) - LiquidityProviders.getFeeAccumulatedOnNft(uint256) (contracts/hyphen/LiquidityProviders.sol#201-222) - WhitelistPeriodManager.initialize(address,address,address,address,address) (contracts/hyphen/WhitelistPeriodManager.sol#60-74) - LPToken.initialize(string,string,address,address) (contracts/hyphen/token/LPToken.sol#36-49) - LPToken.setSvgHelper(address,ISvgHelper) (contracts/hyphen/token/LPToken.sol#56-61) - LPToken.getAllNftIdsByUser(address) (contracts/hyphen/token/LPToken.sol#75-81) - LPToken.exists(uint256) (contracts/hyphen/token/LPToken.sol#98-100) - TokenManager.getEquilibriumFee(address) (contracts/hyphen/token/TokenManager.sol#36-38) - TokenManager.getMaxFee(address) (contracts/hyphen/token/TokenManager.sol#40-42) - TokenManager.getTokensInfo(address) (contracts/hyphen/token/TokenManager.sol#115-124) - TokenManager.getDepositConfig(uint256,address) (contracts/hyphen/token/TokenManager.sol#126-133) - TokenManager.getTransferConfig(address) (contracts/hyphen/token/TokenManager.sol#135-137)
10000000000
should be changed to 1e10
for readability reasons:File: LiquidityPool.sol 20: uint256 private constant BASE_DIVISOR = 10000000000; // Basis Points * 100 for better accuracy //@audit hard to read, use 1e10
BASE_DIVISOR
should be used instead of the hardcoded magic-number 10000000000
:File: LiquidityPool.sol 184: rewardAmount = (amount * incentivePool[tokenAddress] * 10000000000) / liquidityDifference; //@audit use BASE_DIVISOR 185: rewardAmount = rewardAmount / 10000000000;//@audit use BASE_DIVISOR
maps
should be grouped in a struct.From:
File: LiquidityProviders.sol 42: mapping(address => uint256) public totalReserve; // Include Liquidity + Fee accumulated 43: mapping(address => uint256) public totalLiquidity; // Include Liquidity only 44: mapping(address => uint256) public currentLiquidity; // Include current liquidity, updated on every in and out transfer 45: mapping(address => uint256) public totalLPFees; 46: mapping(address => uint256) public totalSharesMinted;
To
42: struct LPFeeDistribution { 43: uint256 totalReserve; // Include Liquidity + Fee accumulated 44: uint256 totalLiquidity; // Include Liquidity only 45: uint256 currentLiquidity; // Include current liquidity, updated on every in and out transfer 46: uint256 totalLPFees; 47: uint256 totalSharesMinted; 48: } 49: 50: mapping(address => LPFeeDistribution) public lpFeeDistribution;
It would be less error-prone, more readable, and it would be possible to delete all related fields with a simple delete lpFeeDistribution[address]
.
#0 - pauliax
2022-05-09T15:45:18Z
Even though it is mentioned very briefly, issue 3 deserves to be upgraded to Medium as per the comment here: https://github.com/code-423n4/2022-03-biconomy-findings/issues/8#issuecomment-1114023886
🌟 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
581.8346 USDT - $581.83
Table of Contents:
The code can be optimized by minimising the number of SLOADs. SLOADs are expensive (100 gas) compared to MLOADs/MSTOREs (3 gas). In the paragraphs below, please see the
@audit-issue
tags in the pieces of code's comments for more information about SLOADs that could be saved by caching the mentioned storage variables in memory variables.
Solidity version 0.8+ comes with implicit overflow and underflow checks on unsigned integers. When an overflow or an underflow isn't possible (as an example, when a comparison is made before the arithmetic operation, or the operation doesn't depend on user input), some gas can be saved by using an
unchecked
block: https://docs.soliditylang.org/en/v0.8.10/control-structures.html#checked-or-unchecked-arithmetic
@audit
tagsThe code is annotated at multiple places with
//@audit
comments to pinpoint the issues. Please, pay attention to them for more details.
File: LPToken.sol 89: function updateTokenMetadata(uint256 _tokenId, LpTokenMetadata memory _lpTokenMetadata) //@audit should be calldata 90: external 91: onlyHyphenPools 92: whenNotPaused 93: { 94: require(_exists(_tokenId), "ERR__TOKEN_DOES_NOT_EXIST"); 95: tokenMetadata[_tokenId] = _lpTokenMetadata; 96: }
calldata
instead of memory
for LpTokenMetadata memory _lpTokenMetadata
When arguments are read-only on external functions, the data location should be calldata
.
Here, LpTokenMetadata memory _lpTokenMetadata
should be LpTokenMetadata calldata _lpTokenMetadata
112: function tokenURI(uint256 tokenId) ... 124: string memory svgData = svgHelper.getTokenSvg( 125: tokenId, 126: tokenMetadata[tokenId].suppliedLiquidity, //@audit tokenMetadata[tokenId].suppliedLiquidity SLOAD 1 127: ILiquidityProviders(liquidityProvidersAddress).totalReserve(tokenAddress) //@audit external call 1 128: ); 129: 130: string memory description = svgHelper.getDescription( 131: tokenMetadata[tokenId].suppliedLiquidity,//@audit tokenMetadata[tokenId].suppliedLiquidity SLOAD 2 132: ILiquidityProviders(liquidityProvidersAddress).totalReserve(tokenAddress) //@audit external call 2 133: ); 134: 135: string memory attributes = svgHelper.getAttributes( 136: tokenMetadata[tokenId].suppliedLiquidity,//@audit tokenMetadata[tokenId].suppliedLiquidity SLOAD 3 137: ILiquidityProviders(liquidityProvidersAddress).totalReserve(tokenAddress) //@audit external call 3 138: );
tokenMetadata[tokenId].suppliedLiquidity
Storage readings are expensive. Caching this in a memory variable can save around 2 SLOADs
ILiquidityProviders(liquidityProvidersAddress).totalReserve(tokenAddress)
External calls are expensive. Caching this in a memory variable can save around 2 external calls.
180: function _beforeTokenTransfer( 181: address from, 182: address to, 183: uint256 tokenId 184: ) internal virtual override(ERC721EnumerableUpgradeable, ERC721Upgradeable) whenNotPaused { 185: super._beforeTokenTransfer(from, to, tokenId); 186: 187: // Only call whitelist period manager for NFT Transfers, not mint and burns 188: if (from != address(0) && to != address(0)) { //@audit-issue 189: whiteListPeriodManager.beforeLiquidityTransfer( 190: from, 191: to, 192: tokenMetadata[tokenId].token, 193: tokenMetadata[tokenId].suppliedLiquidity 194: ); 195: } 196: }
The condition L188 can be short-circuited to provide a happy path with the following optimization:
if (from == address(0) || to == address(0)) { return; } whiteListPeriodManager.beforeLiquidityTransfer( from, to, tokenMetadata[tokenId].token, tokenMetadata[tokenId].suppliedLiquidity );
This way, the gas from evaluating the second condition can be saved in case of minting (in this scenario, we're expecting more minting than burning, therefore making a happy path for it).
struct TokenInfo
To save 1 slot, the struct should go from:
File: ITokenManager.sol 06: struct TokenInfo { 07: uint256 transferOverhead; //@audit 32 bytes 08: bool supportedToken; //@audit 1 byte 09: uint256 equilibriumFee; //@audit 32 bytes 10: uint256 maxFee; //@audit 32 bytes 11: TokenConfig tokenConfig;//@audit 20 bytes 12: }
to
06: struct TokenInfo { 07: uint256 transferOverhead; //@audit 32 bytes 08: uint256 equilibriumFee; //@audit 32 bytes 09: uint256 maxFee; //@audit 32 bytes 10: TokenConfig tokenConfig;//@audit 20 bytes 11: bool supportedToken; //@audit 1 byte 12: }
File: TokenManager.sol 44: function changeFee( 45: address tokenAddress, 46: uint256 _equilibriumFee, 47: uint256 _maxFee 48: ) external override onlyOwner whenNotPaused { 49: require(_equilibriumFee != 0, "Equilibrium Fee cannot be 0"); 50: require(_maxFee != 0, "Max Fee cannot be 0"); 51: tokensInfo[tokenAddress].equilibriumFee = _equilibriumFee; 52: tokensInfo[tokenAddress].maxFee = _maxFee; 53: emit FeeChanged(tokenAddress, tokensInfo[tokenAddress].equilibriumFee, tokensInfo[tokenAddress].maxFee); 54: }
Instead of fetching the storage value multiple times from the array, it's possible to save some gas and help the optimizer by using a storage variable:
TokenInfo storage _tokenInfo = tokensInfo[tokenAddress]; _tokenInfo.equilibriumFee = _equilibriumFee; _tokenInfo.maxFee = _maxFee;
Storage values are being emitted L53. I suggest using:
emit FeeChanged(tokenAddress, _equilibriumFee, _maxFee);
File: TokenManager.sol 69: function setDepositConfig( 70: uint256[] memory toChainId,//@audit should be calldata 71: address[] memory tokenAddresses,//@audit should be calldata 72: TokenConfig[] memory tokenConfig//@audit should be calldata 73: ) external onlyOwner { 74: require( 75: (toChainId.length == tokenAddresses.length) && (tokenAddresses.length == tokenConfig.length), 76: " ERR_ARRAY_LENGTH_MISMATCH" 77: ); 78: for (uint256 index = 0; index < tokenConfig.length; ++index) { //@audit use storage var 79: depositConfig[toChainId[index]][tokenAddresses[index]].min = tokenConfig[index].min; 80: depositConfig[toChainId[index]][tokenAddresses[index]].max = tokenConfig[index].max; 81: } 82: }
calldata
instead of memory
for uint256[] memory toChainId
calldata
instead of memory
for address[] memory tokenAddresses
calldata
instead of memory
for TokenConfig[] memory tokenConfig
I suggest going from:
depositConfig[toChainId[index]][tokenAddresses[index]].min = tokenConfig[index].min; depositConfig[toChainId[index]][tokenAddresses[index]].max = tokenConfig[index].max;
to:
TokenConfig storage _sTokenConfig = depositConfig[toChainId[index]][tokenAddresses[index]]; _sTokenConfig.min = tokenConfig[index].min; _sTokenConfig.max = tokenConfig[index].max;
115: function getTokensInfo(address tokenAddress) public view override returns (TokenInfo memory) { 116: TokenInfo memory tokenInfo = TokenInfo( //@audit can simply return this instead of saving in a memory var (MSTORE + MLOAD to save) 117: tokensInfo[tokenAddress].transferOverhead, 118: tokensInfo[tokenAddress].supportedToken, 119: tokensInfo[tokenAddress].equilibriumFee, 120: tokensInfo[tokenAddress].maxFee, 121: transferConfig[tokenAddress] 122: ); 123: return tokenInfo; 124: }
tokenInfo
is only used once: inline itI suggest the following optimization:
function getTokensInfo(address tokenAddress) public view override returns (TokenInfo memory) { return TokenInfo( tokensInfo[tokenAddress].transferOverhead, tokensInfo[tokenAddress].supportedToken, tokensInfo[tokenAddress].equilibriumFee, tokensInfo[tokenAddress].maxFee, transferConfig[tokenAddress] ); }
I believe we can go further here, as the copy in memory is done manually here. As TokenConfig
is already contained inside the TokenInfo
struct, this should be the best option:
function getTokensInfo(address tokenAddress) public view override returns (TokenInfo memory) { return tokensInfo[tokenAddress]; }
struct NFTInfo
To save 1 slot, I suggest going from:
File: LiquidityFarming.sol 29: struct NFTInfo { 30: address payable staker; //@audit-info 20 bytes 31: uint256 rewardDebt; //@audit-info 32 bytes 32: uint256 unpaidRewards; //@audit-info 32 bytes 33: bool isStaked; //@audit-info 1 byte 34: }
to
File: LiquidityFarming.sol 29: struct NFTInfo { 30: uint256 rewardDebt; //@audit-info 32 bytes 31: uint256 unpaidRewards; //@audit-info 32 bytes 32: address payable staker; //@audit-info 20 bytes 33: bool isStaked; //@audit-info 1 byte 34: }
File: LiquidityFarming.sol 109: function _sendErc20AndGetSentAmount( 110: IERC20Upgradeable _token, 111: uint256 _amount, 112: address _to 113: ) private returns (uint256) { 114: uint256 recepientBalance = _token.balanceOf(_to); 115: _token.safeTransfer(_to, _amount); 116: return _token.balanceOf(_to) - recepientBalance; //@audit should be unchecked (see L114-L115) 117: }
This line can't underflow due to L114-L115. Therefore, it should be wrapped in an unchecked
block.
ILPToken
to save 1 external callHere, if a function is added in ILPToken
to check both conditions in 1 call, it could save 1 external call:
File: LiquidityFarming.sol 199: require( 200: lpToken.isApprovedForAll(msgSender, address(this)) || lpToken.getApproved(_nftId) == address(this), //@audit save 1 external call with a new method 201: "ERR__NOT_APPROVED" 202: );
As nftIdsStaked[msgSender][index] = nftIdsStaked[msgSender][nftIdsStaked[msgSender].length - 1];
can never underflow due to the require statement above it and the for-loop, it should be wrapped in an unchecked
block.
nftsStakedLength
instead of nftIdsStaked[msgSender].length
As no push or pop operations are done yet, I suggest going from:
File: LiquidityFarming.sol 231: uint256 nftsStakedLength = nftIdsStaked[msgSender].length; ... 240: nftIdsStaked[msgSender][index] = nftIdsStaked[msgSender][nftIdsStaked[msgSender].length - 1];
to
File: LiquidityFarming.sol 231: uint256 nftsStakedLength = nftIdsStaked[msgSender].length; ... 240: nftIdsStaked[msgSender][index] = nftIdsStaked[msgSender][nftsStakedLength - 1];
File: LiquidityFarming.sol 265: function getUpdatedAccTokenPerShare(address _baseToken) public view returns (uint256) { ... 274: unchecked { 275: accumulator += 276: rewardRateLog[_baseToken][i].rewardsPerSecond * // @audit rewardRateLog[_baseToken][i] in storage 277: (counter - max(lastUpdatedTime, rewardRateLog[_baseToken][i].timestamp)); //@audit rewardRateLog[_baseToken][i].timestamp SLOAD 1 278: } 279: counter = rewardRateLog[_baseToken][i].timestamp; //@audit rewardRateLog[_baseToken][i].timestamp SLOAD 2 280: if (i == 0) { 281: break; 282: } 283: --i;//@audit should be unchecked (see L280-L281) 284: } ...
rewardRateLog[_baseToken][i].timestamp
in memoryHere, it's possible to save a substantial amount of gas with the following optimization (taking into account the 3 titles above):
while (true) { if (lastUpdatedTime >= counter) { break; } RewardsPerSecondEntry storage _reward = rewardRateLog[_baseToken][i]; //@audit added code uint256 _timestamp = _reward.timestamp; //@audit added code unchecked { accumulator += _reward.rewardsPerSecond * // @audit storage optimization (counter - max(lastUpdatedTime, _timestamp)); //@audit MLOAD counter = _timestamp; //@audit MLOAD if (i == 0) { break; } --i;//@audit now unchecked } }
As function max()
is a private function (not inherited) that is only used once in the contract (L277), it should get inlined.
modifier onlyLiquidityProviders()
is used only once and should get inlinedAs modifier onlyLiquidityProviders()
is only used once (on function transfer()
), it should get inlined.
tokenManager.getDepositConfig(toChainId, tokenAddress)
The code can be optimized from:
File: LiquidityPool.sol 156: require( 157: tokenManager.getDepositConfig(toChainId, tokenAddress).min <= amount && //@audit external call 1 158: tokenManager.getDepositConfig(toChainId, tokenAddress).max >= amount, //@audit external call 2 159: "Deposit amount not in Cap limit" 160: );
to
156: ITokenManager.TokenConfig memory _depositConfig = tokenManager.getDepositConfig(toChainId, tokenAddress); //@audit external call 1 157: require( 158: _depositConfig.min <= amount && //@audit MLOAD 159: _depositConfig.max >= amount, //@audit MLOAD 160: "Deposit amount not in Cap limit" 161: );
File: LiquidityPool.sol 175: function getRewardAmount(uint256 amount, address tokenAddress) public view returns (uint256 rewardAmount) { 176: uint256 currentLiquidity = getCurrentLiquidity(tokenAddress); 177: uint256 providedLiquidity = liquidityProviders.getSuppliedLiquidityByToken(tokenAddress); 178: if (currentLiquidity < providedLiquidity) { 179: uint256 liquidityDifference = providedLiquidity - currentLiquidity; //@audit should be unchecked (see L178)
As providedLiquidity - currentLiquidity
can never underflow due to the if statement above it, it should be wrapped in an unchecked
block.
tokenManager.getDepositConfig(toChainId, NATIVE)
The code can be optimized from:
File: LiquidityPool.sol 247: require( 248: tokenManager.getDepositConfig(toChainId, NATIVE).min <= msg.value && //@audit external call 1 249: tokenManager.getDepositConfig(toChainId, NATIVE).max >= msg.value, //@audit external call 2 250: "Deposit amount not in Cap limit" 251: );
to
File: LiquidityPool.sol 247: ITokenManager.TokenConfig memory _depositConfig = tokenManager.getDepositConfig(toChainId, NATIVE); //@audit external call 1 248: require( 249: _depositConfig.min <= msg.value && //@audit MLOAD 250: _depositConfig.max >= msg.value, //@audit MLOAD 251: "Deposit amount not in Cap limit" 252: );
tokenManager.getTransferConfig(tokenAddress)
The code can be optimized from:
File: LiquidityPool.sol 272: require( 273: tokenManager.getTransferConfig(tokenAddress).min <= amount && //@audit external call 1 274: tokenManager.getTransferConfig(tokenAddress).max >= amount, //@audit external call 2 275: "Withdraw amnt not in Cap limits" 276: );
to
File: LiquidityPool.sol 272: ITokenManager.TokenConfig memory _transferConfig = tokenManager.getTransferConfig(tokenAddress); //@audit external call 1 273: require( 274: _transferConfig.min <= amount && //@audit MLOAD 275: _transferConfig.max >= amount, //@audit MLOAD 276: "Withdraw amnt not in Cap limits" 277: );
Here, there are two require statements:
File: LiquidityPool.sol 272: require( 273: tokenManager.getTransferConfig(tokenAddress).min <= amount && 274: tokenManager.getTransferConfig(tokenAddress).max >= amount, 275: "Withdraw amnt not in Cap limits" 276: ); 277: require(receiver != address(0), "Bad receiver address"); //@audit should be 1st require
The second require statement is a simple condition that is a lot less expensive than the first one. In case of revert on the second require statement, all the gas from the first require would be wasted (2 external calls, or 1 after the optimization). I suggest reordering the require statements to put this one first.
File: LiquidityPool.sol 308: function getAmountToTransfer( ... 316: if (transferFeePerc > tokenManager.getTokensInfo(tokenAddress).equilibriumFee) { //@audit external call 1 317: // Here add some fee to incentive pool also 318: lpFee = (amount * tokenManager.getTokensInfo(tokenAddress).equilibriumFee) / BASE_DIVISOR; //@audit external call 2 319: incentivePool[tokenAddress] = 320: (incentivePool[tokenAddress] + 321: (amount * (transferFeePerc - tokenManager.getTokensInfo(tokenAddress).equilibriumFee))) / //@audit substraction should be unchecked //@audit external call 3 322: BASE_DIVISOR;
tokenManager.getTokensInfo(tokenAddress).equilibriumFee
tokenManager.getTokensInfo(tokenAddress).equilibriumFee
should get cached to avoid 2 unnecessary external calls.
As transferFeePerc - tokenManager.getTokensInfo(tokenAddress).equilibriumFee
can never underflow due to the if statement above it L316, it should be wrapped in an unchecked
block.
File: LiquidityPool.sol 342: function getTransferFee(address tokenAddress, uint256 amount) public view returns (uint256 fee) { ... 348: uint256 equilibriumFee = tokenManager.getTokensInfo(tokenAddress).equilibriumFee; //@audit external call 1 349: uint256 maxFee = tokenManager.getTokensInfo(tokenAddress).maxFee;//@audit external call 2 ...
tokenManager.getTokensInfo(tokenAddress)
I suggest the following optimization:
File: LiquidityPool.sol 348: ITokenManager.TokenInfo memory _tokenInfo = tokenManager.getTokensInfo(tokenAddress); //@audit external call 1 349: uint256 equilibriumFee = _tokenInfo.equilibriumFee; //@audit MLOAD 350: uint256 maxFee = _tokenInfo.maxFee; //@audit MLOAD
27: uint256 public constant BASE_DIVISOR = 10**18; //@audit gas use 1e18
1e18
instead of 10**18
for constant BASE_DIVISOR
Due to how constant
variables are implemented (constant expressions are expressions, not constants), 10**18
will be more expensive than 1e18
.
ILPToken
to save 1 external callHere, the modifier is quite expensive as it makes 2 external calls:
File: LiquidityProviders.sol 53: modifier onlyValidLpToken(uint256 _tokenId, address _transactor) { //@audit expensive modifier. Create 1 method that returns both parametters 54: (address token, , ) = lpToken.tokenMetadata(_tokenId); 55: require(lpToken.exists(_tokenId), "ERR__TOKEN_DOES_NOT_EXIST"); //@audit external call 1 56: require(lpToken.ownerOf(_tokenId) == _transactor, "ERR__TRANSACTOR_DOES_NOT_OWN_NFT"); //@audit external call 2 57: _; 58: }
Consider adding a method in ILPToken
that both checks that _tokenId
exists and returns the token's owner.
File: LiquidityProviders.sol 135: function _increaseCurrentLiquidity(address tokenAddress, uint256 amount) private { 136: currentLiquidity[tokenAddress] += amount; //@audit SLOAD 1 137: emit CurrentLiquidityChanged(tokenAddress, currentLiquidity[tokenAddress]-amount, currentLiquidity[tokenAddress]); //@audit SLOAD 2 & 3 138: }
currentLiquidity[tokenAddress]
Caching this in a memory variable can save around 2 SLOADs. Here's the full optimization:
File: LiquidityProviders.sol 135: function _increaseCurrentLiquidity(address tokenAddress, uint256 amount) private { 136: uint256 _currentLiquidity = currentLiquidity[tokenAddress]; 137: uint256 _increasedLiquidity = _currentLiquidity + amount; 138: currentLiquidity[tokenAddress] = _increasedLiquidity; 139: emit CurrentLiquidityChanged(tokenAddress, _currentLiquidity, _increasedLiquidity); 140: }
File: LiquidityProviders.sol 140: function _decreaseCurrentLiquidity(address tokenAddress, uint256 amount) private { 141: currentLiquidity[tokenAddress] -= amount; //@audit SLOAD 1 142: emit CurrentLiquidityChanged(tokenAddress, currentLiquidity[tokenAddress]+amount, currentLiquidity[tokenAddress]); //@audit SLOAD 2 & 3 143: }
currentLiquidity[tokenAddress]
Caching this in a memory variable can save around 2 SLOADs. Here's the full optimization:
File: LiquidityProviders.sol 140: function _decreaseCurrentLiquidity(address tokenAddress, uint256 amount) private { 141: uint256 _currentLiquidity = currentLiquidity[tokenAddress]; 142: uint256 _decreasedLiquidity = _currentLiquidity - amount; 143: currentLiquidity[tokenAddress] = _decreasedLiquidity; 144: emit CurrentLiquidityChanged(tokenAddress, _currentLiquidity, _decreasedLiquidity); 145: }
File: LiquidityProviders.sol 180: function getTokenPriceInLPShares(address _baseToken) public view returns (uint256) { 181: uint256 supply = totalSharesMinted[_baseToken]; 182: if (supply > 0) { 183: return totalSharesMinted[_baseToken] / totalReserve[_baseToken]; //@audit SLOAD : use supply. 184: } 185: return BASE_DIVISOR; 186: }
supply
instead of totalSharesMinted[_baseToken]
At line 183, I suggest using supply
instead of totalSharesMinted[_baseToken]
. Full code:
File: LiquidityProviders.sol 180: function getTokenPriceInLPShares(address _baseToken) public view returns (uint256) { 181: uint256 supply = totalSharesMinted[_baseToken]; 182: if (supply > 0) { 183: return supply / totalReserve[_baseToken]; 184: } 185: return BASE_DIVISOR; 186: }
File: LiquidityProviders.sol 280: function _increaseLiquidity(uint256 _nftId, uint256 _amount) internal onlyValidLpToken(_nftId, _msgSender()) { 281: (address token, uint256 totalSuppliedLiquidity, uint256 totalShares) = lpToken.tokenMetadata(_nftId); 282: 283: require(_amount > 0, "ERR__AMOUNT_IS_0"); 284: whiteListPeriodManager.beforeLiquidityAddition(_msgSender(), token, _amount); 285: 286: uint256 mintedSharesAmount; 287: // Adding liquidity in the pool for the first time 288: if (totalReserve[token] == 0) { //@audit totalReserve[token] SLOAD 1 289: mintedSharesAmount = BASE_DIVISOR * _amount; 290: } else { 291: mintedSharesAmount = (_amount * totalSharesMinted[token]) / totalReserve[token]; //@audit totalReserve[token] SLOAD 2 //@audit totalSharesMinted[token] SLOAD 1 292: } 293: 294: require(mintedSharesAmount >= BASE_DIVISOR, "ERR__AMOUNT_BELOW_MIN_LIQUIDITY"); 295: 296: totalLiquidity[token] += _amount; 297: totalReserve[token] += _amount; //@audit totalReserve[token] SLOAD 3 298: totalSharesMinted[token] += mintedSharesAmount; //@audit totalSharesMinted[token] SLOAD 2 (after 1st liquidity)
totalReserve[token]
Caching this in memory can save around 2 SLOADs
totalSharesMinted[token]
Caching this in memory can save around 1 SLOAD (only after 1st liquidity adding in the pool for the first time)
modifier onlyLpNft()
is used only once should get inlinedAs modifier onlyLpNft()
is only used once (on function beforeLiquidityTransfer()
), it should get inlined.
File: WhitelistPeriodManager.sol 178: function setIsExcludedAddressStatus(address[] memory _addresses, bool[] memory _status) external onlyOwner { //@audit should be calldata *2 179: require(_addresses.length == _status.length, "ERR__LENGTH_MISMATCH"); 180: for (uint256 i = 0; i < _addresses.length; ++i) { 181: isExcludedAddress[_addresses[i]] = _status[i]; 182: emit ExcludedAddressStatusUpdated(_addresses[i], _status[i]); 183: } 184: }
calldata
instead of memory
for address[] memory _addresses
calldata
instead of memory
for bool[] memory _status
File: WhitelistPeriodManager.sol 219: function setCaps( 220: address[] memory _tokens, //@audit should be calldata 221: uint256[] memory _totalCaps,//@audit should be calldata 222: uint256[] memory _perTokenWalletCaps//@audit should be calldata 223: ) external onlyOwner { 224: require( 225: _tokens.length == _totalCaps.length && _totalCaps.length == _perTokenWalletCaps.length, 226: "ERR__LENGTH_MISMACH" 227: ); 228: for (uint256 i = 0; i < _tokens.length; ++i) { 229: setCap(_tokens[i], _totalCaps[i], _perTokenWalletCaps[i]); 230: } 231: }
calldata
instead of memory
for address[] memory _tokens
calldata
instead of memory
for uint256[] memory _totalCaps
calldata
instead of memory
for uint256[] memory _perTokenWalletCaps
File: WhitelistPeriodManager.sol 260: function ifEnabled(bool _cond) private view returns (bool) { 261: return !areWhiteListRestrictionsEnabled || (areWhiteListRestrictionsEnabled && _cond); // @audit can save 1 SLOAD with (!areWhiteListRestrictionsEnabled || _cond) as the 2nd condition will evaluate only if areWhiteListRestrictionsEnabled == true 262: }
!areWhiteListRestrictionsEnabled || (areWhiteListRestrictionsEnabled && _cond)
should be changed to !areWhiteListRestrictionsEnabled || _cond
as the 2nd part of this statement will only evaluate if areWhiteListRestrictionsEnabled == true
, therefore the explicit check isn't necessary.
Using newer compiler versions and the optimizer give gas optimizations. Also, additional safety checks are available for free.
The advantages here are:
Instances include:
hyphen/token/LPToken.sol:2:pragma solidity 0.8.0; hyphen/token/TokenManager.sol:3:pragma solidity 0.8.0; hyphen/ExecutorManager.sol:3:pragma solidity 0.8.0; hyphen/LiquidityFarming.sol:2:pragma solidity 0.8.0; hyphen/LiquidityPool.sol:3:pragma solidity 0.8.0; hyphen/LiquidityProviders.sol:2:pragma solidity 0.8.0; hyphen/WhitelistPeriodManager.sol:2:pragma solidity 0.8.0;
Consider upgrading pragma to at least 0.8.4.
If a variable is not set/initialized, it is assumed to have the default value (0
for uint
, false
for bool
, address(0)
for address...). Explicitly initializing it with its default value is an anti-pattern and wastes gas.
As an example: for (uint256 i = 0; i < numIterations; ++i) {
should be replaced with for (uint256 i; i < numIterations; ++i) {
Instances include:
hyphen/token/LPToken.sol:77: for (uint256 i = 0; i < nftIds.length; ++i) { hyphen/token/TokenManager.sol:78: for (uint256 index = 0; index < tokenConfig.length; ++index) { hyphen/ExecutorManager.sol:31: for (uint256 i = 0; i < executorArray.length; ++i) { hyphen/ExecutorManager.sol:47: for (uint256 i = 0; i < executorArray.length; ++i) { hyphen/LiquidityFarming.sol:233: for (index = 0; index < nftsStakedLength; ++index) { hyphen/LiquidityFarming.sol:266: uint256 accumulator = 0; hyphen/WhitelistPeriodManager.sol:180: for (uint256 i = 0; i < _addresses.length; ++i) { hyphen/WhitelistPeriodManager.sol:228: for (uint256 i = 0; i < _tokens.length; ++i) { hyphen/WhitelistPeriodManager.sol:247: uint256 maxLp = 0;
I suggest removing explicit initializations for default values.
> 0
is less efficient than != 0
for unsigned integers (with proof)!= 0
costs less gas compared to > 0
for unsigned integers in require
statements with the optimizer enabled (6 gas)
Proof: While it may seem that > 0
is cheaper than !=
, this is only true without the optimizer enabled and outside a require statement. If you enable the optimizer at 10k AND you're in a require
statement, this will save gas. You can see this tweet for more proofs: https://twitter.com/gzeon/status/1485428085885640706
I suggest changing > 0
with != 0
here:
hyphen/LiquidityProviders.sol:239: require(_amount > 0, "ERR__AMOUNT_IS_0"); hyphen/LiquidityProviders.sol:283: require(_amount > 0, "ERR__AMOUNT_IS_0"); hyphen/LiquidityProviders.sol:410: require(lpFeeAccumulated > 0, "ERR__NO_REWARDS_TO_CLAIM");
Also, please enable the Optimizer.
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.
Here, I suggest storing the array's length in a variable before the for-loop, and use it instead:
hyphen/token/LPToken.sol:77: for (uint256 i = 0; i < nftIds.length; ++i) { hyphen/token/TokenManager.sol:78: for (uint256 index = 0; index < tokenConfig.length; ++index) { hyphen/ExecutorManager.sol:31: for (uint256 i = 0; i < executorArray.length; ++i) { hyphen/ExecutorManager.sol:47: for (uint256 i = 0; i < executorArray.length; ++i) { hyphen/WhitelistPeriodManager.sol:180: for (uint256 i = 0; i < _addresses.length; ++i) { hyphen/WhitelistPeriodManager.sol:228: for (uint256 i = 0; i < _tokens.length; ++i) {
In Solidity 0.8+, there's a default overflow check on unsigned integers. It's possible to uncheck this in for-loops and save some gas at each iteration, but at the cost of some code readability, as this uncheck cannot be made inline.
Instances include:
hyphen/token/LPToken.sol:77: for (uint256 i = 0; i < nftIds.length; ++i) { hyphen/token/TokenManager.sol:78: for (uint256 index = 0; index < tokenConfig.length; ++index) { hyphen/ExecutorManager.sol:31: for (uint256 i = 0; i < executorArray.length; ++i) { hyphen/ExecutorManager.sol:47: for (uint256 i = 0; i < executorArray.length; ++i) { hyphen/LiquidityFarming.sol:233: for (index = 0; index < nftsStakedLength; ++index) { hyphen/WhitelistPeriodManager.sol:180: for (uint256 i = 0; i < _addresses.length; ++i) { hyphen/WhitelistPeriodManager.sol:228: for (uint256 i = 0; i < _tokens.length; ++i) { hyphen/WhitelistPeriodManager.sol:248: for (uint256 i = 1; i <= totalSupply; ++i) {
The code would go from:
for (uint256 i; i < numIterations; ++i) { // ... }
to:
for (uint256 i; i < numIterations;) { // ... unchecked { ++i; } }
The risk of overflow is inexistant for a uint256
here.
According to Slither, these functions should be external to save gas:
- ExecutorManager.getExecutorStatus(address) (contracts/hyphen/ExecutorManager.sol#21-23) - ExecutorManager.getAllExecutors() (contracts/hyphen/ExecutorManager.sol#25-27) - HyphenLiquidityFarming.initialize(address,address,ILiquidityProviders,ILPToken) (contracts/hyphen/LiquidityFarming.sol#78-90) - HyphenLiquidityFarming.setRewardPerSecond(address,uint256) (contracts/hyphen/LiquidityFarming.sol#169-172) - HyphenLiquidityFarming.getNftIdsStaked(address) (contracts/hyphen/LiquidityFarming.sol#329-331) - HyphenLiquidityFarming.getRewardRatePerSecond(address) (contracts/hyphen/LiquidityFarming.sol#333-335) - LiquidityPool.initialize(address,address,address,address,address) (contracts/hyphen/LiquidityPool.sol#87-105) - LiquidityPool.setTrustedForwarder(address) (contracts/hyphen/LiquidityPool.sol#107-111) - LiquidityPool.setLiquidityProviders(address) (contracts/hyphen/LiquidityPool.sol#113-117) - LiquidityPool.getExecutorManager() (contracts/hyphen/LiquidityPool.sol#123-125) - LiquidityProviders.initialize(address,address,address,address) (contracts/hyphen/LiquidityProviders.sol#78-90) - LiquidityProviders.getTotalReserveByToken(address) (contracts/hyphen/LiquidityProviders.sol#96-98) - LiquidityProviders.getSuppliedLiquidityByToken(address) (contracts/hyphen/LiquidityProviders.sol#100-102) - LiquidityProviders.getTotalLPFeeByToken(address) (contracts/hyphen/LiquidityProviders.sol#104-106) - LiquidityProviders.getCurrentLiquidity(address) (contracts/hyphen/LiquidityProviders.sol#108-110) - LiquidityProviders.increaseCurrentLiquidity(address,uint256) (contracts/hyphen/LiquidityProviders.sol#127-129) - LiquidityProviders.decreaseCurrentLiquidity(address,uint256) (contracts/hyphen/LiquidityProviders.sol#131-133) - LiquidityProviders.getFeeAccumulatedOnNft(uint256) (contracts/hyphen/LiquidityProviders.sol#201-222) - WhitelistPeriodManager.initialize(address,address,address,address,address) (contracts/hyphen/WhitelistPeriodManager.sol#60-74) - LPToken.initialize(string,string,address,address) (contracts/hyphen/token/LPToken.sol#36-49) - LPToken.setSvgHelper(address,ISvgHelper) (contracts/hyphen/token/LPToken.sol#56-61) - LPToken.getAllNftIdsByUser(address) (contracts/hyphen/token/LPToken.sol#75-81) - LPToken.exists(uint256) (contracts/hyphen/token/LPToken.sol#98-100) - TokenManager.getEquilibriumFee(address) (contracts/hyphen/token/TokenManager.sol#36-38) - TokenManager.getMaxFee(address) (contracts/hyphen/token/TokenManager.sol#40-42) - TokenManager.getTokensInfo(address) (contracts/hyphen/token/TokenManager.sol#115-124) - TokenManager.getDepositConfig(uint256,address) (contracts/hyphen/token/TokenManager.sol#126-133) - TokenManager.getTransferConfig(address) (contracts/hyphen/token/TokenManager.sol#135-137)
Shortening revert strings to fit in 32 bytes will decrease deployment time gas and will decrease runtime gas when the revert condition is met.
Revert strings that are longer than 32 bytes require at least one additional mstore, along with additional overhead for computing memory offset, etc.
Revert strings > 32 bytes:
hyphen/token/LPToken.sol:70: require(_whiteListPeriodManager != address(0), "ERR_INVALID_WHITELIST_PERIOD_MANAGER"); hyphen/ExecutorManager.sol:17: require(executorStatus[msg.sender], "You are not allowed to perform this operation"); hyphen/LiquidityPool.sol:77: require(_msgSender() == address(liquidityProviders), "Only liquidityProviders is allowed");
I suggest shortening the revert strings to fit in 32 bytes, or that using custom errors as described next.
Custom errors from Solidity 0.8.4 are cheaper than revert strings (cheaper deployment cost and runtime cost when the revert condition is met)
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).
Instances include:
hyphen/token/LPToken.sol:52: require(_msgSender() == liquidityProvidersAddress, "ERR_UNAUTHORIZED"); hyphen/token/LPToken.sol:57: require(_svgHelper != ISvgHelper(address(0)), "ERR_INVALID_SVG_HELPER"); hyphen/token/LPToken.sol:58: require(_tokenAddress != address(0), "ERR_INVALID_TOKEN_ADDRESS"); hyphen/token/LPToken.sol:64: require(_liquidityProviders != address(0), "ERR_INVALID_LIQUIDITY_PROVIDERS"); hyphen/token/LPToken.sol:70: require(_whiteListPeriodManager != address(0), "ERR_INVALID_WHITELIST_PERIOD_MANAGER"); hyphen/token/LPToken.sol:94: require(_exists(_tokenId), "ERR__TOKEN_DOES_NOT_EXIST"); hyphen/token/LPToken.sol:120: require(svgHelpers[tokenAddress] != ISvgHelper(address(0)), "ERR__SVG_HELPER_NOT_REGISTERED"); hyphen/token/TokenManager.sol:16: require(tokenAddress != address(0), "Token address cannot be 0"); hyphen/token/TokenManager.sol:17: require(tokensInfo[tokenAddress].supportedToken, "Token not supported"); hyphen/token/TokenManager.sol:49: require(_equilibriumFee != 0, "Equilibrium Fee cannot be 0"); hyphen/token/TokenManager.sol:50: require(_maxFee != 0, "Max Fee cannot be 0"); hyphen/token/TokenManager.sol:74: require( hyphen/token/TokenManager.sol:91: require(tokenAddress != address(0), "Token address cannot be 0"); hyphen/token/TokenManager.sol:92: require(maxCapLimit > minCapLimit, "maxCapLimit > minCapLimit"); hyphen/token/TokenManager.sol:110: require(maxCapLimit > minCapLimit, "maxCapLimit > minCapLimit"); hyphen/ExecutorManager.sol:17: require(executorStatus[msg.sender], "You are not allowed to perform this operation"); hyphen/ExecutorManager.sol:38: require(executorAddress != address(0), "executor address can not be 0"); hyphen/ExecutorManager.sol:39: require(!executorStatus[executorAddress], "Executor already registered"); hyphen/ExecutorManager.sol:54: require(executorAddress != address(0), "executor address can not be 0"); hyphen/LiquidityFarming.sol:101: require(rewardTokens[_baseToken] == address(0), "ERR__POOL_ALREADY_INITIALIZED"); hyphen/LiquidityFarming.sol:102: require(_baseToken != address(0), "ERR__BASE_TOKEN_IS_ZERO"); hyphen/LiquidityFarming.sol:103: require(_rewardToken != address(0), "ERR_REWARD_TOKEN_IS_ZERO"); hyphen/LiquidityFarming.sol:124: require(nft.isStaked, "ERR__NFT_NOT_STAKED"); hyphen/LiquidityFarming.sol:141: require(success, "ERR__NATIVE_TRANSFER_FAILED"); hyphen/LiquidityFarming.sol:146: require(success, "ERR__NATIVE_TRANSFER_FAILED"); hyphen/LiquidityFarming.sol:185: require(_to != address(0), "ERR__TO_IS_ZERO"); hyphen/LiquidityFarming.sol:188: require(success, "ERR__NATIVE_TRANSFER_FAILED"); hyphen/LiquidityFarming.sol:199: require( hyphen/LiquidityFarming.sol:207: require(rewardTokens[baseToken] != address(0), "ERR__POOL_NOT_INITIALIZED"); hyphen/LiquidityFarming.sol:208: require(rewardRateLog[baseToken].length != 0, "ERR__POOL_NOT_INITIALIZED"); hyphen/LiquidityFarming.sol:211: require(!nft.isStaked, "ERR__NFT_ALREADY_STAKED"); hyphen/LiquidityFarming.sol:239: require(index != nftsStakedLength, "ERR__NFT_NOT_STAKED"); hyphen/LiquidityFarming.sol:259: require(nftInfo[_nftId].staker == _msgSender(), "ERR__NOT_OWNER"); hyphen/LiquidityPool.sol:72: require(executorManager.getExecutorStatus(_msgSender()), "Only executor is allowed"); hyphen/LiquidityPool.sol:77: require(_msgSender() == address(liquidityProviders), "Only liquidityProviders is allowed"); hyphen/LiquidityPool.sol:82: require(tokenAddress != address(0), "Token address cannot be 0"); hyphen/LiquidityPool.sol:83: require(tokenManager.getTokensInfo(tokenAddress).supportedToken, "Token not supported"); hyphen/LiquidityPool.sol:94: require(_executorManagerAddress != address(0), "ExecutorManager cannot be 0x0"); hyphen/LiquidityPool.sol:95: require(_trustedForwarder != address(0), "TrustedForwarder cannot be 0x0"); hyphen/LiquidityPool.sol:96: require(_liquidityProviders != address(0), "LiquidityProviders cannot be 0x0"); hyphen/LiquidityPool.sol:108: require(trustedForwarder != address(0), "TrustedForwarder can't be 0"); hyphen/LiquidityPool.sol:114: require(_liquidityProviders != address(0), "LiquidityProviders can't be 0"); hyphen/LiquidityPool.sol:128: require(_executorManagerAddress != address(0), "Executor Manager cannot be 0"); hyphen/LiquidityPool.sol:156: require( hyphen/LiquidityPool.sol:161: require(receiver != address(0), "Receiver address cannot be 0"); hyphen/LiquidityPool.sol:162: require(amount != 0, "Amount cannot be 0"); hyphen/LiquidityPool.sol:247: require( hyphen/LiquidityPool.sol:252: require(receiver != address(0), "Receiver address cannot be 0"); hyphen/LiquidityPool.sol:253: require(msg.value != 0, "Amount cannot be 0"); hyphen/LiquidityPool.sol:272: require( hyphen/LiquidityPool.sol:277: require(receiver != address(0), "Bad receiver address"); hyphen/LiquidityPool.sol:281: require(!status, "Already Processed"); hyphen/LiquidityPool.sol:288: require(address(this).balance >= amountToTransfer, "Not Enough Balance"); hyphen/LiquidityPool.sol:290: require(success, "Native Transfer Failed"); hyphen/LiquidityPool.sol:292: require(IERC20Upgradeable(tokenAddress).balanceOf(address(this)) >= amountToTransfer, "Not Enough Balance"); hyphen/LiquidityPool.sol:373: require(tokenAddress != NATIVE, "Can't withdraw native token fee"); hyphen/LiquidityPool.sol:376: require(_gasFeeAccumulated != 0, "Gas Fee earned is 0"); hyphen/LiquidityPool.sol:385: require(_gasFeeAccumulated != 0, "Gas Fee earned is 0"); hyphen/LiquidityPool.sol:389: require(success, "Native Transfer Failed"); hyphen/LiquidityPool.sol:399: require(receiver != address(0), "Invalid receiver"); hyphen/LiquidityPool.sol:401: require(address(this).balance >= _tokenAmount, "ERR__INSUFFICIENT_BALANCE"); hyphen/LiquidityPool.sol:403: require(success, "ERR__NATIVE_TRANSFER_FAILED"); hyphen/LiquidityPool.sol:406: require(baseToken.balanceOf(address(this)) >= _tokenAmount, "ERR__INSUFFICIENT_BALANCE"); hyphen/LiquidityProviders.sol:55: require(lpToken.exists(_tokenId), "ERR__TOKEN_DOES_NOT_EXIST"); hyphen/LiquidityProviders.sol:56: require(lpToken.ownerOf(_tokenId) == _transactor, "ERR__TRANSACTOR_DOES_NOT_OWN_NFT"); hyphen/LiquidityProviders.sol:64: require(_msgSender() == address(liquidityPool), "ERR__UNAUTHORIZED"); hyphen/LiquidityProviders.sol:69: require(tokenAddress != address(0), "Token address cannot be 0"); hyphen/LiquidityProviders.sol:70: require(_isSupportedToken(tokenAddress), "Token not supported"); hyphen/LiquidityProviders.sol:202: require(lpToken.exists(_nftId), "ERR__INVALID_NFT"); hyphen/LiquidityProviders.sol:239: require(_amount > 0, "ERR__AMOUNT_IS_0"); hyphen/LiquidityProviders.sol:252: require(success, "ERR__NATIVE_TRANSFER_FAILED"); hyphen/LiquidityProviders.sol:268: require(_token != NATIVE, "ERR__WRONG_FUNCTION"); hyphen/LiquidityProviders.sol:269: require( hyphen/LiquidityProviders.sol:283: require(_amount > 0, "ERR__AMOUNT_IS_0"); hyphen/LiquidityProviders.sol:294: require(mintedSharesAmount >= BASE_DIVISOR, "ERR__AMOUNT_BELOW_MIN_LIQUIDITY"); hyphen/LiquidityProviders.sol:319: require(_isSupportedToken(token), "ERR__TOKEN_NOT_SUPPORTED"); hyphen/LiquidityProviders.sol:320: require(token != NATIVE, "ERR__WRONG_FUNCTION"); hyphen/LiquidityProviders.sol:321: require( hyphen/LiquidityProviders.sol:334: require(_isSupportedToken(NATIVE), "ERR__TOKEN_NOT_SUPPORTED"); hyphen/LiquidityProviders.sol:335: require(token == NATIVE, "ERR__WRONG_FUNCTION"); hyphen/LiquidityProviders.sol:337: require(success, "ERR__NATIVE_TRANSFER_FAILED"); hyphen/LiquidityProviders.sol:352: require(_isSupportedToken(_tokenAddress), "ERR__TOKEN_NOT_SUPPORTED"); hyphen/LiquidityProviders.sol:354: require(_amount != 0, "ERR__INVALID_AMOUNT"); hyphen/LiquidityProviders.sol:355: require(nftSuppliedLiquidity >= _amount, "ERR__INSUFFICIENT_LIQUIDITY"); hyphen/LiquidityProviders.sol:403: require(_isSupportedToken(_tokenAddress), "ERR__TOKEN_NOT_SUPPORTED"); hyphen/LiquidityProviders.sol:410: require(lpFeeAccumulated > 0, "ERR__NO_REWARDS_TO_CLAIM"); hyphen/WhitelistPeriodManager.sol:41: require(_msgSender() == address(liquidityProviders), "ERR__UNAUTHORIZED"); hyphen/WhitelistPeriodManager.sol:46: require(_msgSender() == address(lpToken), "ERR__UNAUTHORIZED"); hyphen/WhitelistPeriodManager.sol:51: require(tokenAddress != address(0), "Token address cannot be 0"); hyphen/WhitelistPeriodManager.sol:52: require(_isSupportedToken(tokenAddress), "Token not supported"); hyphen/WhitelistPeriodManager.sol:92: require(ifEnabled(totalLiquidity[_token] + _amount <= perTokenTotalCap[_token]), "ERR__LIQUIDITY_EXCEEDS_PTTC"); hyphen/WhitelistPeriodManager.sol:93: require( hyphen/WhitelistPeriodManager.sol:179: require(_addresses.length == _status.length, "ERR__LENGTH_MISMATCH"); hyphen/WhitelistPeriodManager.sol:187: require(totalLiquidity[_token] <= _totalCap, "ERR__TOTAL_CAP_LESS_THAN_SL"); hyphen/WhitelistPeriodManager.sol:188: require(_totalCap >= perTokenWalletCap[_token], "ERR__TOTAL_CAP_LT_PTWC"); hyphen/WhitelistPeriodManager.sol:203: require(_perTokenWalletCap <= perTokenTotalCap[_token], "ERR__PWC_GT_PTTC"); hyphen/WhitelistPeriodManager.sol:224: require(
I suggest replacing revert strings with custom errors.