Biconomy Hyphen 2.0 contest - Dravee's results

Next-Gen Multichain Relayer Protocol.

General Information

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

Biconomy

Findings Distribution

Researcher Performance

Rank: 16/54

Findings: 3

Award: $792.10

🌟 Selected for report: 1

🚀 Solo Findings: 0

Awards

129.8697 USDT - $129.87

Labels

bug
QA (Quality Assurance)

External Links

QA Report

  1. It came as a surprise that LiquidityFarming.sol's contract wasn't named after the file (HyphenLiquidityFarming)
  2. All 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(
  1. Fees in TokenManager.sol:function changeFee() should be upper-bounded

  2. 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 {
  1. Slither revealed several functions that should have an external visibility:
 - 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)
  1. 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
  1. The constant 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
  1. The "LP Fee Distribution" 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

Awards

581.8346 USDT - $581.83

Labels

bug
G (Gas Optimization)

External Links

Gas Report

Table of Contents:

Foreword

  • Storage-reading optimizations

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.

  • Unchecking arithmetics operations that can't underflow/overflow

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 tags

The code is annotated at multiple places with //@audit comments to pinpoint the issues. Please, pay attention to them for more details.

File: LPToken.sol

function updateTokenMetadata()

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:     }
Use 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

function tokenURI()

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:         );
Cache tokenMetadata[tokenId].suppliedLiquidity

Storage readings are expensive. Caching this in a memory variable can save around 2 SLOADs

Cache ILiquidityProviders(liquidityProvidersAddress).totalReserve(tokenAddress)

External calls are expensive. Caching this in a memory variable can save around 2 external calls.

function _beforeTokenTransfer()

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:     }
Short-circuiting can save gas

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).

File: TokenManager.sol

Tight Packing 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:     }

function changeFee()

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:     }
Storage optimization

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;
Emitting storage value

Storage values are being emitted L53. I suggest using:

emit FeeChanged(tokenAddress, _equilibriumFee, _maxFee);

function setDepositConfig()

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:     }
Use calldata instead of memory for uint256[] memory toChainId
Use calldata instead of memory for address[] memory tokenAddresses
Use calldata instead of memory for TokenConfig[] memory tokenConfig
Storage usage optimization

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;

function getTokensInfo()

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:     }
The variable tokenInfo is only used once: inline it

I 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];
    }

File: LiquidityFarming.sol

Tight Packing 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:     }

function _sendErc20AndGetSentAmount()

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:     }
Uncheck L116

This line can't underflow due to L114-L115. Therefore, it should be wrapped in an unchecked block.

function deposit()

Consider adding a function in ILPToken to save 1 external call

Here, 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:         );

function withdraw()

Uncheck L240

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.

Use the existing variable 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];

function getUpdatedAccTokenPerShare()

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:         }
...
Storage usage optimization
Cache rewardRateLog[_baseToken][i].timestamp in memory
Uncheck L283

Here, 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
            }
        }

function max()

A private function used only once should get inlined

As function max() is a private function (not inherited) that is only used once in the contract (L277), it should get inlined.

File: LiquidityPool.sol

modifier onlyLiquidityProviders()

modifier onlyLiquidityProviders() is used only once and should get inlined

As modifier onlyLiquidityProviders() is only used once (on function transfer()), it should get inlined.

function depositErc20()

Avoid multiple external calls on 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:         );

function getRewardAmount()

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)
Uncheck L179

As providedLiquidity - currentLiquidity can never underflow due to the if statement above it, it should be wrapped in an unchecked block.

function depositNative()

Avoid multiple external calls on 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:         );

function sendFundsToUser()

Avoid multiple external calls on 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:         );
Reorder require statements to save gas on revert

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.

function getAmountToTransfer()

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;
Avoid multiple external calls on tokenManager.getTokensInfo(tokenAddress).equilibriumFee

tokenManager.getTokensInfo(tokenAddress).equilibriumFee should get cached to avoid 2 unnecessary external calls.

Uncheck L321

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.

function getTransferFee()

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
...
Avoid multiple external calls on 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

File: LiquidityProviders.sol

Storage

27:     uint256 public constant BASE_DIVISOR = 10**18; //@audit gas use 1e18
Use 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.

modifier onlyValidLpToken()

Consider adding a function in ILPToken to save 1 external call

Here, 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.

function _increaseCurrentLiquidity()

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:     }
Cache 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:     }

function _decreaseCurrentLiquidity()

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:     }
Cache 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:     }

function getTokenPriceInLPShares()

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:     }
Use 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:     }

function _increaseLiquidity()

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)
Cache totalReserve[token]

Caching this in memory can save around 2 SLOADs

Cache totalSharesMinted[token]

Caching this in memory can save around 1 SLOAD (only after 1st liquidity adding in the pool for the first time)

File: WhitelistPeriodManager.sol

modifier onlyLpNft()

modifier onlyLpNft() is used only once should get inlined

As modifier onlyLpNft() is only used once (on function beforeLiquidityTransfer()), it should get inlined.

function setIsExcludedAddressStatus()

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:     }
Use calldata instead of memory for address[] memory _addresses
Use calldata instead of memory for bool[] memory _status

function setCaps()

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:     }
Use calldata instead of memory for address[] memory _tokens
Use calldata instead of memory for uint256[] memory _totalCaps
Use calldata instead of memory for uint256[] memory _perTokenWalletCaps

function ifEnabled()

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:     }
The condition can be optimized to save a SLOAD

!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.

General recommendations

Version

Upgrade pragma to at least 0.8.4

Using newer compiler versions and the optimizer give gas optimizations. Also, additional safety checks are available for free.

The advantages here are:

  • Low level inliner (>= 0.8.2): Cheaper runtime gas (especially relevant when the contract has small functions).
  • Optimizer improvements in packed structs (>= 0.8.3)
  • Custom errors (>= 0.8.4): cheaper deployment cost and runtime cost. Note: the runtime cost is only relevant when the revert condition is met. In short, replace revert strings by custom errors.

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.

Variables

No need to explicitly initialize variables with default values

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.

Pre-increments cost less gas compared to post-increments

Comparisons

> 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.

For-Loops

An array's length should be cached to save gas in for-loops

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) {
Increments can be unchecked

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.

ethereum/solidity#10695

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.

Visibility

Functions that should be external

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)

Errors

Reduce the size of error messages (Long revert Strings)

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.

Use Custom Errors instead of Revert Strings to save Gas

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.

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter