Platform: Code4rena
Start Date: 14/06/2022
Pot Size: $50,000 USDC
Total HM: 19
Participants: 99
Period: 5 days
Judge: HardlyDifficult
Total Solo HM: 4
Id: 136
League: ETH
Rank: 22/99
Findings: 5
Award: $505.47
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: hyh
Also found by: 0x29A, 0xNineDec, 0xf15ers, 0xkowloon, GreyArt, IllIllI, KIntern, Kenshin, Lambda, WatchPug, Wayne, berndartmueller, byterocket, cccz, codexploder, horsefacts, kenzo, obront, obtarian, oyc_109, peritoflores, rajatbeladiya, rfa, saian, unforgiven, zer0dot
11.084 USDC - $11.08
https://github.com/code-423n4/2022-06-infinity/blob/main/contracts/core/InfinityExchange.sol#L1230 https://github.com/code-423n4/2022-06-infinity/blob/main/contracts/staking/InfinityStaker.sol#L344-L348
The rescueETH
function is implemented to collect any unexpected ETH transferred to the infinityExchange.sol
contract, But this function will not work as expected.
The function is supposed to return the eth from the contract to the specified destination address, but it transfers only the amount of ETH send by the caller as msg.value
is used here.
/// @dev used for rescuing exchange fees paid to the contract in ETH function rescueETH(address destination) external payable onlyOwner { (bool sent, ) = destination.call{value: msg.value}(''); require(sent, 'failed'); }
Also in InfinityStaker.sol#L344-L348
use address(this).balance
or custom amount specified by the caller for rescuing ETH.
/// @dev used for rescuing exchange fees paid to the contract in ETH function rescueETH(address destination) external payable onlyOwner { (bool sent, ) = destination.call{value: address(this).balance}(''); require(sent, 'failed'); }
Or,
/// @dev used for rescuing exchange fees paid to the contract in ETH function rescueETH(address destination, uint256 amount) external payable onlyOwner { (bool sent, ) = destination.call{value: amount}(''); require(sent, 'failed'); }
#0 - nneverlander
2022-06-22T16:43:44Z
Duplicate
#1 - nneverlander
2022-07-05T13:17:20Z
#2 - HardlyDifficult
2022-07-09T17:03:04Z
276.9248 USDC - $276.92
The _transferNFTs
function use ERC165 to check if the item(nft) supports ERC721 interface or ERC1155 interface and execute transfer accordingly. But if it doesn't supports either, it just exits the function(no revert).
in InfinityExchange.sol#L1062-L1072
/** * @notice Transfer NFTs * @param from address of the sender * @param to address of the recipient * @param item item to transfer */ function _transferNFTs( address from, address to, OrderTypes.OrderItem calldata item ) internal { if (IERC165(item.collection).supportsInterface(0x80ac58cd)) { _transferERC721s(from, to, item); } else if (IERC165(item.collection).supportsInterface(0xd9b67a26)) { _transferERC1155s(from, to, item); } }
Manual Analysis
Revert the transaction if the item doesn't supports any NFT specification
/** * @notice Transfer NFTs * @param from address of the sender * @param to address of the recipient * @param item item to transfer */ function _transferNFTs( address from, address to, OrderTypes.OrderItem calldata item ) internal { if (IERC165(item.collection).supportsInterface(0x80ac58cd)) { _transferERC721s(from, to, item); } else if (IERC165(item.collection).supportsInterface(0xd9b67a26)) { _transferERC1155s(from, to, item); } else { require(false, "The item is not an NFT"); } }
#0 - nneverlander
2022-06-22T16:42:07Z
Duplicate
#1 - HardlyDifficult
2022-07-12T00:07:29Z
136.753 USDC - $136.75
While matching the orders the gas cost is calculated so that it can be refunded back to the contract. The calculation for this gas is incorrect.
The function keeps track of the gasleft
at the beginning of the loop and adds additional amount of gas for pre loop calculation inside the loop which breaks the logic.
In InfinityExchange.sol#L272-L273
The same issue repeats in matchOneToOneOrders
and matchOneToManyOrders
functions on the same file too.
... for (uint256 i = 0; i < numSells; ) { uint256 startGasPerOrder = gasleft() + ((startGas - gasleft()) / numSells); ...
Manual analysis
The amount of gas used in the pre loop part can be calculated outside the loop
uint256 additionalGas = (startGas - gasleft()) / numSells; for (uint256 i = 0; i < numSells; ) { uint256 startGasPerOrder = gasleft() + additionalGas;
To make it more stricter, small amount gas can also be added to account for loop iteration calculation.
#0 - nneverlander
2022-06-22T16:43:09Z
Duplicate
#1 - nneverlander
2022-07-07T03:54:10Z
🌟 Selected for report: joestakey
Also found by: 0x1f8b, 0x29A, 0x52, 0xDjango, 0xNazgul, 0xNineDec, 0xf15ers, 0xkowloon, 0xmint, 8olidity, BowTiedWardens, Chom, Cityscape, Czar102, ElKu, FSchmoede, Funen, GimelSec, GreyArt, IllIllI, KIntern, Kaiziron, Kenshin, Lambda, MadWookie, MiloTruck, PPrieditis, Picodes, Ruhum, Sm4rty, StErMi, TerrierLover, TomJ, Treasure-Seeker, VAD37, WatchPug, Wayne, _Adam, a12jmx, abhinavmir, antonttc, apostle0x01, asutorufos, berndartmueller, cccz, cloudjunky, codexploder, cryptphi, csanuragjain, defsec, delfin454000, fatherOfBlocks, georgypetrov, hake, hansfriese, horsefacts, hyh, k, kenta, nxrblsrpr, oyc_109, peritoflores, rajatbeladiya, reassor, rfa, robee, sach1r0, saian, samruna, shenwilly, simon135, sorrynotsorry, sseefried, throttle, unforgiven, wagmi, zzzitron
48.9768 USDC - $48.98
for eg.
canExecMatchOneToOne()
will not check if the orders are a buy/sell pair.
Rather this check is done later when executing the matches. This should be done early in the canExecMatchOneToOne
function so it gives proper result.🌟 Selected for report: IllIllI
Also found by: 0v3rf10w, 0x1f8b, 0x29A, 0xAsm0d3us, 0xDjango, 0xKitsune, 0xNazgul, 0xf15ers, 0xkatana, 0xkowloon, BowTiedWardens, Chom, ElKu, FSchmoede, Funen, GimelSec, Kaiziron, Kenshin, Lambda, MadWookie, MiloTruck, PPrieditis, Picodes, PwnedNoMore, StErMi, Tadashi, TerrierLover, TomJ, Tomio, Wayne, Waze, _Adam, antonttc, apostle0x01, asutorufos, c3phas, codexploder, defsec, delfin454000, fatherOfBlocks, hake, hansfriese, hyh, joestakey, k, kenta, oyc_109, peritoflores, reassor, rfa, robee, sach1r0, simon135, slywaters, zer0dot
31.7384 USDC - $31.74
There are many calculations which will never over/under flow because of verifications done prior to those calculations. Those can be put inside unchecked block to save gas.
for eg.
Reading from storage uses SLOAD opcode which is more expensive than MLOAD which is used for memory read.
wethTransferGasUnits
(memory) instead of WETH_TRANSFER_GAS_UNITS
(storage).solidity 0.8.4 introduces custom errors which are cheaper than using revert strings in terms of gas. This can be used to save gas.
for eg.
// Before require(condition, "Revert strings"); // After error CustomError(); if (!condition) { revert CustomError(); }
more details can be found here
Constant expressions are not really constants but expressions, hence need to be evaluated every time they are used. Using actual constants can save gas.
canExecMatchOneToMany
function can be optimized to save more gasIn InfinityOrderBookComplication.sol#L68
a. move this block to the end of the loop since first iteration will always evaluate to false || false
b. itemsIntersect
does not needs to be checked in return statement since it is already checked inside the loop body (because of [a])
c. in InfinityOrderBookComplication.sol#L101-L103, isOrdersTimeValid
can be ignored since it will always be true (checked inside the loop, because of [a])
d. return false
immediately if _isTimeValid
is false
e. return false
immediately after the loop if numItems == makerOrder.constraints[0]
is false
For eg. In InfinityExchange.sol#L395
#0 - nneverlander
2022-06-22T16:27:30Z
Thanks