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: 25/54
Findings: 5
Award: $464.12
🌟 Selected for report: 1
🚀 Solo Findings: 0
99.257 USDT - $99.26
https://github.com/code-423n4/2022-03-biconomy/blob/main/contracts/hyphen/LiquidityPool.sol#L170 https://github.com/code-423n4/2022-03-biconomy/blob/main/contracts/hyphen/LiquidityProviders.sol#L273
The Biconomy protocol do not appear to support rebasing/deflationary/inflationary tokens whose balance changes during transfers or over time. The necessary checks include at least verifying the amount of tokens transferred to contracts before and after the actual transfer to infer any fees/interest.
https://github.com/code-423n4/2022-03-biconomy/blob/main/contracts/hyphen/LiquidityPool.sol#L170
Code Review
#0 - pauliax
2022-04-26T11:16:37Z
80.3981 USDT - $80.40
https://github.com/code-423n4/2022-03-biconomy/blob/db8a1fdddd02e8cc209a4c73ffbb3de210e4a81a/contracts/hyphen/token/TokenManager.sol#L51 https://github.com/code-423n4/2022-03-biconomy/blob/db8a1fdddd02e8cc209a4c73ffbb3de210e4a81a/contracts/hyphen/token/TokenManager.sol#L52
The equilibriumFee and maxFee does not have any upper or lower bounds. Values that are too large will lead to reversions in several critical functions or the LP user will lost all funds when paying the fee.
Owner can identify fee amount. That directly affect to LP management. (https://github.com/code-423n4/2022-03-biconomy/blob/db8a1fdddd02e8cc209a4c73ffbb3de210e4a81a/contracts/hyphen/LiquidityPool.sol#L352)
Here you can see there is no upper bound has been defined.
function changeFee( address tokenAddress, uint256 _equilibriumFee, uint256 _maxFee ) external override onlyOwner whenNotPaused { require(_equilibriumFee != 0, "Equilibrium Fee cannot be 0"); require(_maxFee != 0, "Max Fee cannot be 0"); tokensInfo[tokenAddress].equilibriumFee = _equilibriumFee; tokensInfo[tokenAddress].maxFee = _maxFee; emit FeeChanged(tokenAddress, tokensInfo[tokenAddress].equilibriumFee, tokensInfo[tokenAddress].maxFee); }
Code Review
Consider defining upper and lower bounds on the equilibriumFee and maxFee.
#0 - pauliax
2022-04-30T17:25:49Z
Valid concern. I am grouping all the issues related to the validation of fee variables together and making this a primary one as it contains the most comprehensive description.
🌟 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
124.3732 USDT - $124.37
"LiquidityFarming" inherit OpenZeppelin's OwnableUpgradable contract which enables the onlyOwner role to transfer ownership to another address. It's possible that the onlyOwner role mistakenly transfers ownership to the wrong address, resulting in a loss of the onlyOwner role. The current ownership transfer process involves the current owner calling Unlock.transferOwnership(). This function checks the new owner is not the zero address and proceeds to write the new owner's address into the owner's state variable. If the nominated EOA account is not a valid account, it is entirely possible the owner may accidentally transfer ownership to an uncontrolled account, breaking all functions with the onlyOwner() modifier. Lack of two-step procedure for critical operations leaves them error-prone if the address is incorrect, the new address will take on the functionality of the new role immediately
for Ex : -Alice deploys a new version of the whitehack group address. When she invokes the whitehack group address setter to replace the address, she accidentally enters the wrong address. The new address now has access to the role immediately and is too late to revert
None
Implement zero address check and Consider implementing a two step process where the owner nominates an account and the nominated account needs to call an acceptOwnership() function for the transfer of ownership to fully succeed. This ensures the nominated EOA account is a valid and active account.
The admin only functions that change critical parameters should emit events. Events allow capturing the changed parameters so that off-chain tools/interfaces can register such changes with timelocks that allow users to evaluate them and consider if they would like to engage/exit based on how they perceive the changes as affecting the trustworthiness of the protocol or profitability of the implemented financial services. The alternative of directly querying on-chain contract state for such changes is not considered practical for most users/usages.
Missing events and timelocks do not promote transparency and if such changes immediately affect users’ perception of fairness or trustworthiness, they could exit the protocol causing a reduction in liquidity which could negatively impact protocol TVL and reputation.
There are owner functions that do not emit any events in the contracts.
https://github.com/code-423n4/2022-03-biconomy/blob/main/contracts/hyphen/LiquidityProviders.sol#L164 https://github.com/code-423n4/2022-03-biconomy/blob/main/contracts/hyphen/LiquidityProviders.sol#L156 https://github.com/code-423n4/2022-03-biconomy/blob/main/contracts/hyphen/LiquidityPool.sol#L120 https://github.com/code-423n4/2022-03-biconomy/blob/main/contracts/hyphen/token/TokenManager.sol#L102
See similar High-severity H03 finding OpenZeppelin’s Audit of Audius (https://blog.openzeppelin.com/audius-contracts-audit/#high) and Medium-severity M01 finding OpenZeppelin’s Audit of UMA Phase 4 (https://blog.openzeppelin.com/uma-audit-phase-4/)
None
Add events to all admin/privileged functions that change critical parameters.
Pausable contract is not inherited from Upgradeable contracts. The contract is not upgradeable. For the using the upgradeable contract, The following steps should be followed.
- Instead of Pausable, we use the upgradable variant PausableUpgradeable - Replace the constructor with the initializer method, with the initializer modifier. Note that you need to - manually call the __{contract}_init(); method of every parent contract with appropriate parameters.
https://github.com/code-423n4/2022-03-biconomy/blob/main/contracts/hyphen/token/LPToken.sol#L23
None
Ensure that the function is inherited from the upgradeable contracts. PausableUpgradable contract be seen from the below.
The system is heavily relies on the ExecutorManager. Therefore, It contains centralization risk If the execution manager is EOA and captured.
https://github.com/code-423n4/2022-03-biconomy/blob/main/contracts/hyphen/LiquidityPool.sol#L270
None
We advise the client to carefully manage the executor accounts' private key to avoid any potential risks of being hacked. In general, we strongly recommend centralized privileges or roles in the protocol to be improved via a decentralized mechanism or smart-contract-based accounts with enhanced security practices, e.g., Multi-Signature wallets.
##Â Impact : LOW
All contract initializers were missing access controls, allowing any user to initialize the contract. By front-running the contract deployers to initialize the contract, the incorrect parameters may be supplied, leaving the contract needing to be redeployed.
https://github.com/code-423n4/2022-03-biconomy/blob/main/contracts/hyphen/token/LPToken.sol#L36
https://github.com/code-423n4/2022-03-biconomy/blob/main/contracts/hyphen/LiquidityPool.sol#L87
https://github.com/code-423n4/2022-03-biconomy/blob/main/contracts/hyphen/LiquidityProviders.sol#L78
Manual Code Review
While the code that can be run in contract constructors is limited, setting the owner in the contract's constructor to the msg.sender
and adding the onlyOwner
modifier to all initializers would be a sufficient level of access control.
Block timestamps have historically been used for a variety of applications, such as entropy for random numbers (see the Entropy Illusion for further details), locking funds for periods of time, and various state-changing conditional statements that are time-dependent. Miners have the ability to adjust timestamps slightly, which can prove to be dangerous if block timestamps are used incorrectly in smart contracts.
https://github.com/code-423n4/2022-03-biconomy/blob/main/contracts/hyphen/LiquidityFarming.sol#L105 https://github.com/code-423n4/2022-03-biconomy/blob/main/contracts/hyphen/LiquidityFarming.sol#L170 https://github.com/code-423n4/2022-03-biconomy/blob/main/contracts/hyphen/LiquidityFarming.sol#L268
Manual Code Review
Block timestamps should not be used for entropy or generating random numbers—i.e., they should not be the deciding factor (either directly or through some derivation) for winning a game or changing an important state.
Time-sensitive logic is sometimes required; e.g., for unlocking contracts (time-locking), completing an ICO after a few weeks, or enforcing expiry dates. It is sometimes recommended to use block.number and an average block time to estimate times; with a 10 second block time, 1 week equates to approximately, 60480 blocks. Thus, specifying a block number at which to change a contract state can be more secure, as miners are unable to easily manipulate the block number.
##Â Impact : LOW
In the smart contracts, TransferOverhead has been used gas over head calculation. However this variable is not anywhere in the contract. Only It is set in the contract.
Manual Code Review
Ensure that gas Over head is adjusted in the LQ pool. If It is not used, consider deleting variable.
#0 - pauliax
2022-05-15T19:45:33Z
"C4-004 : Centralization Risk" could be Medium: https://github.com/code-423n4/2022-03-biconomy-findings/issues/80#issuecomment-1109662505
🌟 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
60.8297 USDT - $60.83
Using newer compiler versions and the optimizer gives gas optimizations and additional safety checks are available for free.
The advantages of versions 0.8.* over <0.8.0 are:
All Contracts
None
Consider to upgrade pragma to at least 0.8.4.
From Pragma 0.8.0, ABI coder v2 is activated by default. The pragma abicoder v2 can be deleted from the repository. That will provide gas optimization.
https://github.com/code-423n4/2022-03-biconomy/blob/main/contracts/hyphen/LiquidityPool.sol#L4
None
ABI coder v2 is activated by default. It is recommended to delete redundant codes.
From Solidity v0.8.0 Breaking Changes https://docs.soliditylang.org/en/v0.8.0/080-breaking-changes.html
Cache array length in for loops can save gas
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.
https://github.com/code-423n4/2022-03-biconomy/blob/main/contracts/hyphen/WhitelistPeriodManager.sol#L228 https://github.com/code-423n4/2022-03-biconomy/blob/main/contracts/hyphen/token/LPToken.sol#L77
Code Review
Consider to cache array length.
uint is always >= 0
These checks are pretty much useless as uint can never be negative. Remove them to save some gas.
Example :
for( uint i; i<5;i++)
https://github.com/code-423n4/2022-03-biconomy/blob/main/contracts/hyphen/WhitelistPeriodManager.sol#L228 https://github.com/code-423n4/2022-03-biconomy/blob/main/contracts/hyphen/LiquidityFarming.sol#L233
None
Consider to replace uint=0 to uint.
> 0 can be replaced with != 0 for gas optimization
!= 0
is a cheaper operation compared to > 0
, when dealing with uint.
https://github.com/code-423n4/2022-03-biconomy/blob/main/contracts/hyphen/LiquidityFarming.sol#L318 https://github.com/code-423n4/2022-03-biconomy/blob/main/contracts/hyphen/LiquidityFarming.sol#L132 https://github.com/code-423n4/2022-03-biconomy/blob/main/contracts/hyphen/LiquidityProviders.sol#L182 https://github.com/code-423n4/2022-03-biconomy/blob/main/contracts/hyphen/LiquidityProviders.sol#L239 https://github.com/code-423n4/2022-03-biconomy/blob/main/contracts/hyphen/LiquidityProviders.sol#L283 https://github.com/code-423n4/2022-03-biconomy/blob/main/contracts/hyphen/LiquidityProviders.sol#L410
Code Review
Use "!=0" instead of ">0" for the gas optimization.
In the solidity exponential is more costly than 1e18 definition.
None
Consider changing 10**18 definition with 1e18.
During the code review, It has been observed that the the following contract code is not used. Unused/redundant parameters can be deleted for the gas optimization.
Warning: Unused function parameter. Remove or comment out the variable name to silence this warning. --> src/hyphen/token/svg-helpers/SvgHelperBase.sol:126:29: | 126 | function getDescription(uint256 _suppliedLiquidity, uint256 _totalSuppliedLiquidity) | ^^^^^^^^^^^^^^^^^^^^^^^^^^ Warning: Unused function parameter. Remove or comment out the variable name to silence this warning. --> /src/hyphen/token/svg-helpers/SvgHelperBase.sol:126:57: | 126 | function getDescription(uint256 _suppliedLiquidity, uint256 _totalSuppliedLiquidity) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
None
Delete unused function parameters.
In the LPToken.sol, The external contract is imported.
However, the contract is already available in the metatx directory.
https://github.com/code-423n4/2022-03-biconomy/blob/db8a1fdddd02e8cc209a4c73ffbb3de210e4a81a/contracts/hyphen/token/LPToken.sol#L5
import "@openzeppelin/contracts-upgradeable/metatx/ERC2771ContextUpgradeable.sol";
https://github.com/code-423n4/2022-03-biconomy/blob/main/contracts/hyphen/metatx/ERC2771ContextUpgradeable.sol
Code Review
Consider to use available contracts instead of importing external ones.
The use of _msgSender() when there is no implementation of a meta transaction mechanism that uses it, such as EIP-2771, very slightly increases gas consumption.
_msgSender() is utilized three times where msg.sender could have been used in the following function.
https://github.com/code-423n4/2022-03-biconomy/blob/main/contracts/hyphen/LiquidityPool.sol#L77
None
Replace _msgSender() with msg.sender if there is no mechanism to support meta-transactions like EIP-2771 implemented.