Biconomy Hyphen 2.0 contest - TerrierLover'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: 30/54

Findings: 2

Award: $295.96

🌟 Selected for report: 0

🚀 Solo Findings: 0

Awards

119.3436 USDT - $119.34

Labels

bug
QA (Quality Assurance)

External Links

onlyExecutor() is not used in ExecutorManager.sol

Target codebase

onlyExecutor() is defined but not used at all. Can delete it. https://github.com/code-423n4/2022-03-biconomy/blob/main/contracts/hyphen/ExecutorManager.sol#L15-L19

// MODIFIERS modifier onlyExecutor() { require(executorStatus[msg.sender], "You are not allowed to perform this operation"); _; }

Proposed changes

Simply delete onlyExecutor modifier


Inconsitent naming of the argument

Target codebase

transfer function has _tokenAddress and _tokenAmount arguments with _. This is not aligned with other patterns (_ is added when there are naming conflicts with state variables) https://github.com/code-423n4/2022-03-biconomy/blob/main/contracts/hyphen/LiquidityPool.sol#L395-L397

function transfer( address _tokenAddress, address receiver, uint256 _tokenAmount ) external whenNotPaused onlyLiquidityProviders nonReentrant {

Proposed changes

To be consistent with other functions in this contract, should not add _ for _tokenAddress and _tokenAmount.


Delete unnecessary comment

Target codebase

This comment does not do anything. This should simply be removed. https://github.com/code-423n4/2022-03-biconomy/blob/main/contracts/hyphen/LiquidityPool.sol#L374

Proposed changes

// uint256 gasFeeAccumulated = gasFeeAccumulatedByToken[tokenAddress];

Can add indexed at ExecutorAdded and ExecutorRemoved in ExecutorManager.sol

Target codebase

https://github.com/code-423n4/2022-03-biconomy/blob/main/contracts/hyphen/ExecutorManager.sol#L12-L13

contract ExecutorManager is IExecutorManager, Ownable { address[] internal executors; mapping(address => bool) internal executorStatus; event ExecutorAdded(address executor, address owner); event ExecutorRemoved(address executor, address owner);

Proposed changes

Add indexed at address arguments.

event ExecutorAdded(address indexed executor, address indexed owner); event ExecutorRemoved(address indexed executor, address indexed owner);

Awards

176.617 USDT - $176.62

Labels

bug
G (Gas Optimization)

External Links

Declaration of initialGas is not needed at LiquidityPool.sol

Target codebase

initialGas is defined at the top of the function, but this is not needed at all.

https://github.com/code-423n4/2022-03-biconomy/blob/main/contracts/hyphen/LiquidityPool.sol#L271-L284

uint256 initialGas = gasleft(); ... uint256 amountToTransfer = getAmountToTransfer(initialGas, tokenAddress, amount, tokenGasPrice);

Proposed change

Simply removing initialGas variable, and call gasleft() directly.

uint256 amountToTransfer = getAmountToTransfer(gasleft(), tokenAddress, amount, tokenGasPrice);

Gas changes with the proposed changes

Deployments gas fee of LiquidityPool

  • Before: 3841453
  • After: 3839429
  • Reduction: -2024

Usage of unchecked directory on ++i or ++index would reduce costs

Target codebase

ExecutorManager.sol

Both functions have onlyOwner modifier, so the owner can control the number of arrays in executorArray. So can use unchecked directory for ++i. https://github.com/code-423n4/2022-03-biconomy/blob/main/contracts/hyphen/ExecutorManager.sol#L31

function addExecutors(address[] calldata executorArray) external override onlyOwner { for (uint256 i = 0; i < executorArray.length; ++i) { addExecutor(executorArray[i]); } }

https://github.com/code-423n4/2022-03-biconomy/blob/main/contracts/hyphen/ExecutorManager.sol#L47

function removeExecutors(address[] calldata executorArray) external override onlyOwner { for (uint256 i = 0; i < executorArray.length; ++i) { removeExecutor(executorArray[i]); } }

LiquidityFarming.sol

index is less likely to overflow. https://github.com/code-423n4/2022-03-biconomy/blob/main/contracts/hyphen/LiquidityFarming.sol#L233

for (index = 0; index < nftsStakedLength; ++index) { if (nftIdsStaked[msgSender][index] == _nftId) { break; } }

WhitelistPeriodManager.sol

Following two functions use onlyOwner modifier, so may be able to use unchecked for ++i. https://github.com/code-423n4/2022-03-biconomy/blob/main/contracts/hyphen/WhitelistPeriodManager.sol#L180-L183

for (uint256 i = 0; i < _addresses.length; ++i) { isExcludedAddress[_addresses[i]] = _status[i]; emit ExcludedAddressStatusUpdated(_addresses[i], _status[i]); }

https://github.com/code-423n4/2022-03-biconomy/blob/main/contracts/hyphen/WhitelistPeriodManager.sol#L228-L230

for (uint256 i = 0; i < _tokens.length; ++i) { setCap(_tokens[i], _totalCaps[i], _perTokenWalletCaps[i]); }

i can be go up to totalSupply which is uint256. It will not be overflown, so can use unchecked directory. https://github.com/code-423n4/2022-03-biconomy/blob/main/contracts/hyphen/WhitelistPeriodManager.sol#L248-L253

for (uint256 i = 1; i <= totalSupply; ++i) { uint256 liquidity = totalLiquidityByLp[_token][lpToken.ownerOf(i)]; if (liquidity > maxLp) { maxLp = liquidity; } }

LPToken.sol

It is less likely for owners to have the max value of NFT. Hense, ++i can be wrapped with unchecked.

https://github.com/code-423n4/2022-03-biconomy/blob/main/contracts/hyphen/token/LPToken.sol#L77-L79

function getAllNftIdsByUser(address _owner) public view returns (uint256[] memory) { uint256[] memory nftIds = new uint256[](balanceOf(_owner)); for (uint256 i = 0; i < nftIds.length; ++i) { nftIds[i] = tokenOfOwnerByIndex(_owner, i); } return nftIds; }

TokenManager.sol

onlyOwner modifier is used, so may be able to use unchecked for ++index. https://github.com/code-423n4/2022-03-biconomy/blob/main/contracts/hyphen/token/TokenManager.sol#L78-L81

for (uint256 index = 0; index < tokenConfig.length; ++index) { depositConfig[toChainId[index]][tokenAddresses[index]].min = tokenConfig[index].min; depositConfig[toChainId[index]][tokenAddresses[index]].max = tokenConfig[index].max; }

https://github.com/code-423n4/2022-03-biconomy/blob/main/contracts/hyphen/ExecutorManager.sol#L31

function addExecutors(address[] calldata executorArray) external override onlyOwner { for (uint256 i = 0; i < executorArray.length; ++i) { addExecutor(executorArray[i]); } }

https://github.com/code-423n4/2022-03-biconomy/blob/main/contracts/hyphen/ExecutorManager.sol#L47

function removeExecutors(address[] calldata executorArray) external override onlyOwner { for (uint256 i = 0; i < executorArray.length; ++i) { removeExecutor(executorArray[i]); } }

Proposed changes

ExecutorManager.sol

https://github.com/code-423n4/2022-03-biconomy/blob/main/contracts/hyphen/ExecutorManager.sol#L31

function addExecutors(address[] calldata executorArray) external override onlyOwner { for (uint256 i = 0; i < executorArray.length; ) { addExecutor(executorArray[i]); unchecked { ++i; } } }

https://github.com/code-423n4/2022-03-biconomy/blob/main/contracts/hyphen/ExecutorManager.sol#L47

function removeExecutors(address[] calldata executorArray) external override onlyOwner { for (uint256 i = 0; i < executorArray.length; ) { removeExecutor(executorArray[i]); unchecked { ++i; } } }

LiquidityFarming.sol

https://github.com/code-423n4/2022-03-biconomy/blob/main/contracts/hyphen/LiquidityFarming.sol#L233

for (index = 0; index < nftsStakedLength; ) { if (nftIdsStaked[msgSender][index] == _nftId) { break; } unchecked { ++index; } }

WhitelistPeriodManager.sol

https://github.com/code-423n4/2022-03-biconomy/blob/main/contracts/hyphen/WhitelistPeriodManager.sol#L180-L183

for (uint256 i = 0; i < _addresses.length; ) { isExcludedAddress[_addresses[i]] = _status[i]; emit ExcludedAddressStatusUpdated(_addresses[i], _status[i]); unchecked { ++i; } }

https://github.com/code-423n4/2022-03-biconomy/blob/main/contracts/hyphen/WhitelistPeriodManager.sol#L228-L230

for (uint256 i = 0; i < _tokens.length; ) { setCap(_tokens[i], _totalCaps[i], _perTokenWalletCaps[i]); unchecked { ++i; } }

https://github.com/code-423n4/2022-03-biconomy/blob/main/contracts/hyphen/WhitelistPeriodManager.sol#L248-L253

for (uint256 i = 1; i <= totalSupply; ) { uint256 liquidity = totalLiquidityByLp[_token][lpToken.ownerOf(i)]; if (liquidity > maxLp) { maxLp = liquidity; } unchecked { ++i; } }

LPToken.sol

https://github.com/code-423n4/2022-03-biconomy/blob/main/contracts/hyphen/token/LPToken.sol#L77-L79

function getAllNftIdsByUser(address _owner) public view returns (uint256[] memory) { uint256[] memory nftIds = new uint256[](balanceOf(_owner)); for (uint256 i = 0; i < nftIds.length; ) { nftIds[i] = tokenOfOwnerByIndex(_owner, i); unchecked { ++i; } } return nftIds; }

TokenManager.sol

https://github.com/code-423n4/2022-03-biconomy/blob/main/contracts/hyphen/token/TokenManager.sol#L78-L81

for (uint256 index = 0; index < tokenConfig.length; ) { depositConfig[toChainId[index]][tokenAddresses[index]].min = tokenConfig[index].min; depositConfig[toChainId[index]][tokenAddresses[index]].max = tokenConfig[index].max; unchecked { ++index; } }

https://github.com/code-423n4/2022-03-biconomy/blob/main/contracts/hyphen/ExecutorManager.sol#L31

function addExecutors(address[] calldata executorArray) external override onlyOwner { for (uint256 i = 0; i < executorArray.length; ) { addExecutor(executorArray[i]); unchecked { ++i; } } }

https://github.com/code-423n4/2022-03-biconomy/blob/main/contracts/hyphen/ExecutorManager.sol#L47

function removeExecutors(address[] calldata executorArray) external override onlyOwner { for (uint256 i = 0; i < executorArray.length; ) { removeExecutor(executorArray[i]); unchecked { ++i; } } }

Gas changes with the proposed changes

Methods - average gas change

ContractMethodsBeforeAfterChange
HyphenLiquidityFarmingextractRewards135652131459-4193
TokenManagersetDepositConfig7445574391-64
WhitelistPeriodManagersetCaps180517180381136

Deployments - average gas change

ContractBeforeAfterChange
ExecutorManager548814536945-11869
HyphenLiquidityFarming29813862973842-7544
LPToken31487073141187-7520
TokenManager11576541147933-9721
WhitelistPeriodManager20061851995210-10975

There are duplicated logics in LiquidityPool.sol

Target codebase

https://github.com/code-423n4/2022-03-biconomy/blob/main/contracts/hyphen/LiquidityPool.sol#L287-L294

if (tokenAddress == NATIVE) { require(address(this).balance >= amountToTransfer, "Not Enough Balance"); (bool success, ) = receiver.call{value: amountToTransfer}(""); require(success, "Native Transfer Failed"); } else { require(IERC20Upgradeable(tokenAddress).balanceOf(address(this)) >= amountToTransfer, "Not Enough Balance"); SafeERC20Upgradeable.safeTransfer(IERC20Upgradeable(tokenAddress), receiver, amountToTransfer); }

https://github.com/code-423n4/2022-03-biconomy/blob/main/contracts/hyphen/LiquidityPool.sol#L400-L408

if (_tokenAddress == NATIVE) { require(address(this).balance >= _tokenAmount, "ERR__INSUFFICIENT_BALANCE"); (bool success, ) = receiver.call{value: _tokenAmount}(""); require(success, "ERR__NATIVE_TRANSFER_FAILED"); } else { IERC20Upgradeable baseToken = IERC20Upgradeable(_tokenAddress); require(baseToken.balanceOf(address(this)) >= _tokenAmount, "ERR__INSUFFICIENT_BALANCE"); SafeERC20Upgradeable.safeTransfer(baseToken, receiver, _tokenAmount); }

Proposed changes

First, define private function _sendNativeOrERC20Token as follows, and use this function at the above codebase.

function _sendNativeOrERC20Token( address tokenAddress, address receiver, uint256 amount ) private { if (tokenAddress == NATIVE) { require(address(this).balance >= amount, "Not Enough Balance"); (bool success, ) = receiver.call{value: amount}(""); require(success, "Native Transfer Failed"); } else { require(IERC20Upgradeable(tokenAddress).balanceOf(address(this)) >= amount, "Not Enough Balance"); SafeERC20Upgradeable.safeTransfer(IERC20Upgradeable(tokenAddress), receiver, amount); } }
function sendFundsToUser( address tokenAddress, uint256 amount, address payable receiver, bytes memory depositHash, uint256 tokenGasPrice, uint256 fromChainId ) external nonReentrant onlyExecutor tokenChecks(tokenAddress) whenNotPaused { ...Omitted... uint256 amountToTransfer = getAmountToTransfer(initialGas, tokenAddress, amount, tokenGasPrice); liquidityProviders.decreaseCurrentLiquidity(tokenAddress, amountToTransfer); _sendNativeOrERC20Token( tokenAddress, receiver, amountToTransfer ); emit AssetSent(tokenAddress, amount, amountToTransfer, receiver, depositHash, fromChainId); }
function transfer( address _tokenAddress, address receiver, uint256 _tokenAmount ) external whenNotPaused onlyLiquidityProviders nonReentrant { require(receiver != address(0), "Invalid receiver"); _sendNativeOrERC20Token( _tokenAddress, receiver, _tokenAmount ); }

Gas changes with the proposed changes

Methods - average gas change

ContractMethodsBeforeAfterChange
HyphenLiquidityFarmingextractRewards135652131459-4193
LiquidityPooldepositNative9765197433-218
LiquidityPoolwithdrawNativeGasFee5414354044-99

Deployments - average gas change

ContractBeforeAfterChange
LiquidityPool38414533752872-88581

Duplicated logic exists in getAmountToTransfer function in LiquidityPool.sol

Target codebase

In the following codes, (amount * transferFeePerc) / BASE_DIVISOR is called multiple times. It can reduce the gas by removing the duplication.

https://github.com/code-423n4/2022-03-biconomy/blob/main/contracts/hyphen/LiquidityPool.sol#L314-L326

uint256 transferFeePerc = getTransferFee(tokenAddress, amount); uint256 lpFee; if (transferFeePerc > tokenManager.getTokensInfo(tokenAddress).equilibriumFee) { // Here add some fee to incentive pool also lpFee = (amount * tokenManager.getTokensInfo(tokenAddress).equilibriumFee) / BASE_DIVISOR; incentivePool[tokenAddress] = (incentivePool[tokenAddress] + (amount * (transferFeePerc - tokenManager.getTokensInfo(tokenAddress).equilibriumFee))) / BASE_DIVISOR; } else { lpFee = (amount * transferFeePerc) / BASE_DIVISOR; } uint256 transferFeeAmount = (amount * transferFeePerc) / BASE_DIVISOR;

Proposed changes

Here are two changes which can be added:

  • Define transferFeeAmount variable outside of if-else block
  • If block can use transferFeeAmount variable by refactoring calculation a bit
  • lpFee variable in else block can directly use transferFeeAmount variable
uint256 transferFeePerc = getTransferFee(tokenAddress, amount); uint256 lpFee; uint256 transferFeeAmount = (amount * transferFeePerc) / BASE_DIVISOR; if (transferFeePerc > tokenManager.getTokensInfo(tokenAddress).equilibriumFee) { // Here add some fee to incentive pool also lpFee = (amount * tokenManager.getTokensInfo(tokenAddress).equilibriumFee) / BASE_DIVISOR; incentivePool[tokenAddress] = incentivePool[tokenAddress] + transferFeeAmount - lpFee; } else { lpFee = transferFeeAmount; }

Gas changes with the proposed changes

Methods - average gas change

ContractMethodsBeforeAfterChange
LiquidityPoolsendFundsToUser245626242482-3144

Deployments - average gas change

ContractBeforeAfterChange
LiquidityPool38414533801684-39769

currentLiquidity variable does not need to be defined at getTransferFee function in LiquidityPool.sol

Target codebase

https://github.com/code-423n4/2022-03-biconomy/blob/main/contracts/hyphen/LiquidityPool.sol#L342-L346

function getTransferFee(address tokenAddress, uint256 amount) public view returns (uint256 fee) { uint256 currentLiquidity = getCurrentLiquidity(tokenAddress); uint256 providedLiquidity = liquidityProviders.getSuppliedLiquidityByToken(tokenAddress); uint256 resultingLiquidity = currentLiquidity - amount;

Proposed changes

Just call getCurrentLiquidity(tokenAddress) when defining resultingLiquidity variable

function getTransferFee(address tokenAddress, uint256 amount) public view returns (uint256 fee) { uint256 providedLiquidity = liquidityProviders.getSuppliedLiquidityByToken(tokenAddress); uint256 resultingLiquidity = getCurrentLiquidity(tokenAddress) - amount;

Gas changes with the proposed changes

Methods - average gas change

ContractMethodsBeforeAfterChange
LiquidityPoolsendFundsToUser245626245619-7

Deployments - average gas change

ContractBeforeAfterChange
LiquidityPool38414533840541-912

Avoid calling tokenManager.getTokensInfo(tokenAddress) multiple time

Target codebase

In these codes, tokenManager.getTokensInfo(tokenAddress) is called multiple times. Calling function multiple time would increase the gas usage.

https://github.com/code-423n4/2022-03-biconomy/blob/main/contracts/hyphen/LiquidityPool.sol#L316-L331

if (transferFeePerc > tokenManager.getTokensInfo(tokenAddress).equilibriumFee) { // Here add some fee to incentive pool also lpFee = (amount * tokenManager.getTokensInfo(tokenAddress).equilibriumFee) / BASE_DIVISOR; incentivePool[tokenAddress] = (incentivePool[tokenAddress] + (amount * (transferFeePerc - tokenManager.getTokensInfo(tokenAddress).equilibriumFee))) / BASE_DIVISOR; } else { lpFee = (amount * transferFeePerc) / BASE_DIVISOR; } uint256 transferFeeAmount = (amount * transferFeePerc) / BASE_DIVISOR; liquidityProviders.addLPFee(tokenAddress, lpFee); uint256 totalGasUsed = initialGas - gasleft(); totalGasUsed = totalGasUsed + tokenManager.getTokensInfo(tokenAddress).transferOverhead;

Proposed changes

Here are example changes. Basically, it defines ITokenManager.TokenInfo memory tokenInfo = tokenManager.getTokensInfo(tokenAddress), and use tokenInfo variable instead of calling tokenManager.getTokensInfo(tokenAddress) multiple times.

ITokenManager.TokenInfo memory tokenInfo = tokenManager.getTokensInfo(tokenAddress); if (transferFeePerc > tokenInfo.equilibriumFee) { // Here add some fee to incentive pool also lpFee = (amount * tokenInfo.equilibriumFee) / BASE_DIVISOR; incentivePool[tokenAddress] = (incentivePool[tokenAddress] + (amount * (transferFeePerc - tokenInfo.equilibriumFee))) / BASE_DIVISOR; } else { lpFee = (amount * transferFeePerc) / BASE_DIVISOR; } uint256 transferFeeAmount = (amount * transferFeePerc) / BASE_DIVISOR; liquidityProviders.addLPFee(tokenAddress, lpFee); uint256 totalGasUsed = initialGas - gasleft(); totalGasUsed = totalGasUsed + tokenInfo.transferOverhead; totalGasUsed = totalGasUsed + baseGas;

Gas changes with the proposed changes

Methods - average gas change

ContractMethodsBeforeAfterChange
LiquidityPoolsendFundsToUser245626236716-8910

Deployments - average gas change

ContractBeforeAfterChange
LiquidityPool38414533760632-80821

Duplicated logic exists in withdrawErc20GasFee and withdrawNativeGasFee

Target codebase

https://github.com/code-423n4/2022-03-biconomy/blob/main/contracts/hyphen/LiquidityPool.sol#L372-L392

function withdrawErc20GasFee(address tokenAddress) external onlyExecutor whenNotPaused nonReentrant { require(tokenAddress != NATIVE, "Can't withdraw native token fee"); // uint256 gasFeeAccumulated = gasFeeAccumulatedByToken[tokenAddress]; uint256 _gasFeeAccumulated = gasFeeAccumulated[tokenAddress][_msgSender()]; require(_gasFeeAccumulated != 0, "Gas Fee earned is 0"); gasFeeAccumulatedByToken[tokenAddress] = gasFeeAccumulatedByToken[tokenAddress] - _gasFeeAccumulated; gasFeeAccumulated[tokenAddress][_msgSender()] = 0; SafeERC20Upgradeable.safeTransfer(IERC20Upgradeable(tokenAddress), _msgSender(), _gasFeeAccumulated); emit GasFeeWithdraw(tokenAddress, _msgSender(), _gasFeeAccumulated); } function withdrawNativeGasFee() external onlyExecutor whenNotPaused nonReentrant { uint256 _gasFeeAccumulated = gasFeeAccumulated[NATIVE][_msgSender()]; require(_gasFeeAccumulated != 0, "Gas Fee earned is 0"); gasFeeAccumulatedByToken[NATIVE] = gasFeeAccumulatedByToken[NATIVE] - _gasFeeAccumulated; gasFeeAccumulated[NATIVE][_msgSender()] = 0; (bool success, ) = payable(_msgSender()).call{value: _gasFeeAccumulated}(""); require(success, "Native Transfer Failed"); emit GasFeeWithdraw(address(this), _msgSender(), _gasFeeAccumulated); }

Following codes are duplicated ones seen in withdrawErc20GasFee and withdrawNativeGasFee.

require(_gasFeeAccumulated != 0, "Gas Fee earned is 0"); gasFeeAccumulatedByToken[tokenAddress] = gasFeeAccumulatedByToken[tokenAddress] - _gasFeeAccumulated; gasFeeAccumulated[tokenAddress][_msgSender()] = 0;

The difference is whether it uses tokenAddress or NATIVE variable.

Proposed changes

Here are example changes. Basically it defines _withdrawCommon function.

function withdrawErc20GasFee(address tokenAddress) external onlyExecutor whenNotPaused nonReentrant { require(tokenAddress != NATIVE, "Can't withdraw native token fee"); // uint256 gasFeeAccumulated = gasFeeAccumulatedByToken[tokenAddress]; uint256 _gasFeeAccumulated = gasFeeAccumulated[tokenAddress][_msgSender()]; _withdrawCommon(tokenAddress, _gasFeeAccumulated); SafeERC20Upgradeable.safeTransfer(IERC20Upgradeable(tokenAddress), _msgSender(), _gasFeeAccumulated); emit GasFeeWithdraw(tokenAddress, _msgSender(), _gasFeeAccumulated); } function withdrawNativeGasFee() external onlyExecutor whenNotPaused nonReentrant { uint256 _gasFeeAccumulated = gasFeeAccumulated[NATIVE][_msgSender()]; _withdrawCommon(NATIVE, _gasFeeAccumulated); (bool success, ) = payable(_msgSender()).call{value: _gasFeeAccumulated}(""); require(success, "Native Transfer Failed"); emit GasFeeWithdraw(address(this), _msgSender(), _gasFeeAccumulated); } function _withdrawCommon(address tokenOrNativeAddress, uint256 gasFeeAccumulated_) private { require(gasFeeAccumulated_ != 0, "Gas Fee earned is 0"); gasFeeAccumulatedByToken[tokenOrNativeAddress] = gasFeeAccumulatedByToken[tokenOrNativeAddress] - gasFeeAccumulated_; gasFeeAccumulated[tokenOrNativeAddress][_msgSender()] = 0; }

Gas changes with the proposed changes

Methods - average gas change

ContractMethodsBeforeAfterChange
LiquidityPooldepositNative9765197592-59
LiquidityPoolsendFundsToUser245626245593-33
LiquidityPoolwithdrawNativeGasFee5414354265+122

In LiquidityPool.sol, depositNative and sendFundsToUser function can reduce the gas, but withdrawNativeGasFee sees the gas increase.

Deployments - average gas change

ContractBeforeAfterChange
LiquidityPool38414533809973-31480

Unchecked can be used at getRewardAmount function

Target codebase

There is an opportunity to use unchecked for providedLiquidity - currentLiquidity. It is obvious that providedLiquidity - currentLiquidity will never be underflown since the if checks currentLiquidity < providedLiquidity. https://github.com/code-423n4/2022-03-biconomy/blob/main/contracts/hyphen/LiquidityPool.sol#L178-L187

if (currentLiquidity < providedLiquidity) { uint256 liquidityDifference = providedLiquidity - currentLiquidity; if (amount >= liquidityDifference) { rewardAmount = incentivePool[tokenAddress]; } else { // Multiply by 10000000000 to avoid 0 reward amount for small amount and liquidity difference rewardAmount = (amount * incentivePool[tokenAddress] * 10000000000) / liquidityDifference; rewardAmount = rewardAmount / 10000000000; } }

Proposed changes

Wrap by unchecked for providedLiquidity - currentLiquidity.

if (currentLiquidity < providedLiquidity) { uint256 liquidityDifference; unchecked { liquidityDifference = providedLiquidity - currentLiquidity; } if (amount >= liquidityDifference) { rewardAmount = incentivePool[tokenAddress]; } else { // Multiply by 10000000000 to avoid 0 reward amount for small amount and liquidity difference rewardAmount = (amount * incentivePool[tokenAddress] * 10000000000) / liquidityDifference; rewardAmount = rewardAmount / 10000000000; } }

Gas changes with the proposed changes

Methods - average gas change

ContractMethodsBeforeAfterChange
LiquidityPooldepositErc20142899142875-24
LiquidityPooldepositNative9765197598-53

Deployments - average gas change

ContractBeforeAfterChange
LiquidityPool38414533839045-2408

getUpdatedAccTokenPerShare function has some codes where unchecked can be used

Target codebase

There are three gas improvement opportunities.

uint256 accumulator = 0; uint256 lastUpdatedTime = poolInfo[_baseToken].lastRewardTime; uint256 counter = block.timestamp; uint256 i = rewardRateLog[_baseToken].length - 1; while (true) { if (lastUpdatedTime >= counter) { break; } unchecked { accumulator += rewardRateLog[_baseToken][i].rewardsPerSecond * (counter - max(lastUpdatedTime, rewardRateLog[_baseToken][i].timestamp)); } counter = rewardRateLog[_baseToken][i].timestamp; if (i == 0) { break; } --i; }

Proposed changes

uint256 accumulator; uint256 lastUpdatedTime = poolInfo[_baseToken].lastRewardTime; uint256 counter = block.timestamp; uint256 i; unchecked { i = rewardRateLog[_baseToken].length - 1; } while (true) { if (lastUpdatedTime >= counter) { break; } unchecked { accumulator += rewardRateLog[_baseToken][i].rewardsPerSecond * (counter - max(lastUpdatedTime, rewardRateLog[_baseToken][i].timestamp)); } counter = rewardRateLog[_baseToken][i].timestamp; if (i == 0) { break; } unchecked { --i; } }

Gas changes with the proposed changes

Methods - average gas change

ContractMethodsBeforeAfterChange
HyphenLiquidityFarmingextractRewards148287135526-12761
HyphenLiquidityFarmingwithdraw148287135526-17490

Deployments - average gas change

ContractBeforeAfterChange
HyphenLiquidityFarming29813862972318-9068

#0 - ankurdubey521

2022-04-01T10:10:39Z

Thanks a lot for including the data related to the savings in gas

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