Platform: Code4rena
Start Date: 27/04/2022
Pot Size: $50,000 MIM
Total HM: 6
Participants: 59
Period: 5 days
Judge: 0xean
Id: 113
League: ETH
Rank: 23/59
Findings: 2
Award: $177.06
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: IllIllI
Also found by: 0x1337, 0x1f8b, 0xDjango, 0xf15ers, AuditsAreUS, BowTiedWardens, CertoraInc, Funen, GimelSec, MaratCerby, Ruhum, WatchPug, antonttc, berndartmueller, bobi, bobirichman, broccolirob, catchup, cccz, defsec, delfin454000, gs8nrv, gzeon, horsefacts, hubble, hyh, ilan, jah, joestakey, kebabsec, kenta, kenzo, m9800, mics, oyc_109, pauliax, reassor, robee, samruna, sikorico, simon135, throttle, unforgiven, z3s
101.8823 MIM - $101.88
It is good to add a require() statement that checks the return value of token transfers or to use something like OpenZeppelin’s safeTransfer/safeTransferFrom unless one is sure the given token reverts in case of a failure.
Navigate to the following contract.
transfer/transferFrom functions are used instead of safe transfer/transferFrom on the following contracts.
https://github.com/code-423n4/2022-04-abranft/blob/main/contracts/NFTPair.sol#L266 https://github.com/code-423n4/2022-04-abranft/blob/main/contracts/NFTPair.sol#L540
Code Review
Consider using safeTransfer/safeTransferFrom or require() consistently. (https://docs.openzeppelin.com/contracts/2.x/api/token/erc721)
The ecrecover function is used in the contract to recover the address from the signature. The built-in EVM precompile ecrecover is susceptible to signature malleability which could lead to replay attacks (references: https://swcregistry.io/docs/SWC-117, https://swcregistry.io/docs/SWC-121 and https://medium.com/cryptronics/signature-replay-vulnerabilities-in-smart-contracts-3b6f7596df57).
https://github.com/code-423n4/2022-04-abranft/blob/main/contracts/NFTPair.sol#L383
https://github.com/code-423n4/2022-04-abranft/blob/main/contracts/NFTPair.sol#L419
None
Consider using OpenZeppelin’s ECDSA library (which prevents this malleability) instead of the built-in function.
During the code review, It has been observed feeTo address is missing zero address. That will cause to fee lost.
https://github.com/code-423n4/2022-04-abranft/blob/main/contracts/NFTPair.sol#L729
None
Consider implementing zero address check.
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-04-abranft/blob/main/contracts/NFTPair.sol#L511
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.
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-04-abranft/blob/main/contracts/NFTPair.sol#L175
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.
#0 - cryptolyndon
2022-05-13T05:21:20Z
C4-001: See #20 C4-002: We use nonces to prevent replay attacks C4-003: The zero address is pretty much the only wrong address that does NOT result in loss of funds; the BentoBox reverts on transfer. A check would be pointless C4-004: You do know what the contract does, right? C4-005: The contracts are deployed and initialised in a single transaction.
🌟 Selected for report: BowTiedWardens
Also found by: 0x1f8b, 0xNazgul, 0xf15ers, 0xkatana, CertoraInc, Funen, GimelSec, Hawkeye, IllIllI, Kulk0, NoamYakov, Tadashi, Tomio, TrungOre, antonttc, catchup, defsec, delfin454000, fatherOfBlocks, gzeon, horsefacts, joestakey, kenta, oyc_109, pauliax, reassor, robee, samruna, simon135, slywaters, sorrynotsorry, z3s
75.1846 MIM - $75.18
Shortening revert strings to fit in 32 bytes will decrease deploy time gas and will decrease runtime gas when the revert condition has been met.
Revert strings that are longer than 32 bytes require at least one additional mstore, along with additional overhead for computing memory offset, etc.
Revert strings > 32 bytes are here:
https://github.com/code-423n4/2022-04-abranft/blob/main/contracts/NFTPair.sol#L366
Manual Review
Shorten the revert strings to fit in 32 bytes. That will affect gas optimization.
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-04-abranft/blob/main/contracts/NFTPair.sol#L641 https://github.com/code-423n4/2022-04-abranft/blob/main/contracts/NFTPairWithOracle.sol#L674
None
Consider to cache array length.
Strict inequalities add a check of non equality which costs around 3 gas.
https://github.com/code-423n4/2022-04-abranft/blob/main/contracts/NFTPairWithOracle.sol#L739 https://github.com/code-423n4/2022-04-abranft/blob/main/contracts/NFTPair.sol#L717
Code Review
Use >= or <= instead of > and < when possible.
When a variable is declared solidity assigns the default value. In case the contract assigns the value again, it costs extra gas.
Example: uint x = 0 costs more gas than uint x without having any different functionality.
https://github.com/code-423n4/2022-04-abranft/blob/main/contracts/NFTPair.sol#L641 https://github.com/code-423n4/2022-04-abranft/blob/main/contracts/NFTPairWithOracle.sol#L674
Code Review
uint x = 0 costs more gas than uint x without having any different functionality.
A division/multiplication by any number x being a power of 2 can be calculated by shifting log2(x) to the right/left.
While the DIV opcode uses 5 gas, the SHR opcode only uses 3 gas. Furthermore, Solidity's division operation also includes a division-by-0 prevention which is bypassed using shifting.
None
A division/multiplication by any number x being a power of 2 can be calculated by shifting log2(x) to the right/left.
> 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-04-abranft/blob/main/contracts/NFTPair.sol#L717 https://github.com/code-423n4/2022-04-abranft/blob/main/contracts/NFTPairWithOracle.sol#L739
Code Review
Use "!=0" instead of ">0" for the gas optimization.
Using newer compiler versions and the optimizer gives gas optimizations and additional safety checks are available for free.
All Contracts
Solidity 0.8.10 has a useful change which reduced gas costs of external calls which expect a return value: https://blog.soliditylang.org/2021/11/09/solidity-0.8.10-release-announcement/
Code Generator: Skip existence check for external contract if return data is expected. In this case, the ABI decoder will revert if the contract does not exist
All Contracts
None
Consider to upgrade pragma to at least 0.8.10.
++i is more gas efficient than i++ in loops forwarding.
https://github.com/code-423n4/2022-04-abranft/blob/main/contracts/NFTPair.sol#L641 https://github.com/code-423n4/2022-04-abranft/blob/main/contracts/NFTPairWithOracle.sol#L674
Code Review
It is recommend to use unchecked{++i} and change i declaration to uint256.
Using double require instead of operator && can save more gas.
https://github.com/code-423n4/2022-04-abranft/blob/main/contracts/NFTPairWithOracle.sol#L206 https://github.com/code-423n4/2022-04-abranft/blob/main/contracts/NFTPair.sol#L189 https://github.com/code-423n4/2022-04-abranft/blob/main/contracts/NFTPair.sol#L284
Code Review
Example
using &&: function check(uint x)public view{ require(x == 0 && x < 1 ); } // gas cost 21630 using double require: require(x == 0 ); require( x < 1); } } // gas cost 21622
From Pragma 0.8.0, ABI coder v2 is activated by default.
""" https://github.com/code-423n4/2022-04-abranft/blob/main/contracts/NFTPair.sol#L21
https://github.com/code-423n4/2022-04-abranft/blob/main/contracts/NFTPairWithOracle.sol#L21
"""
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
#0 - cryptolyndon
2022-05-14T01:55:10Z
Can't argue with the revert strings I suppose.