Platform: Code4rena
Start Date: 29/06/2022
Pot Size: $50,000 USDC
Total HM: 20
Participants: 133
Period: 5 days
Judge: hickuphh3
Total Solo HM: 1
Id: 142
League: ETH
Rank: 63/133
Findings: 2
Award: $70.30
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: xiaoming90
Also found by: 0x1f8b, 0x29A, 0x52, 0xDjango, 0xNazgul, 0xNineDec, 0xSolus, 0xf15ers, 0xsanson, AmitN, Bnke0x0, BowTiedWardens, Chom, David_, ElKu, Funen, GalloDaSballo, GimelSec, Hawkeye, IllIllI, JC, JohnSmith, Kaiziron, Kenshin, Lambda, Limbooo, MadWookie, Metatron, MiloTruck, Nethermind, Picodes, ReyAdmirado, Sneakyninja0129, StErMi, TomJ, Treasure-Seeker, TrungOre, Waze, Yiko, _Adam, __141345__, antonttc, async, aysha, catchup, cccz, cryptphi, csanuragjain, danb, datapunk, defsec, delfin454000, dirk_y, doddle0x, durianSausage, exd0tpy, fatherOfBlocks, gogo, hake, hansfriese, horsefacts, hubble, itsmeSTYJ, joestakey, oyc_109, pedroais, peritoflores, rajatbeladiya, reassor, robee, rokinot, samruna, saneryee, sashik_eth, shenwilly, shung, simon135, sseefried, unforgiven, zer0dot, zzzitron
47.238 USDC - $47.24
Filled order can be canceled, which is confusing. Alice may try to cancel order if she does not like it anymore. Order will become canceled, but it does not change anything. Order maker should not be able to cancel it after it is filled.
https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L526-L535
Add a check
bytes32 orderHash = hashOrder(order); require(ownerOf(uint256(orderHash)), "Order already filled");
indexed
fieldsEach event
should use three indexed
fields if there are three or more fields
https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L185
event FilledOrder(bytes32 indexed orderHash, uint256[] floorAssetTokenIds, Order order);
https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L193
event ExercisedOrder(bytes32 indexed orderHash, uint256[] floorAssetTokenIds, Order order);
public
functions not called by the contract should be declared external
insteadContracts are allowed to override their parents’ functions and change the visibility from external
to public
.
https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L389 https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L550 https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L577 https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L764
🌟 Selected for report: GalloDaSballo
Also found by: 0v3rf10w, 0x1f8b, 0xA5DF, 0xDjango, 0xHarry, 0xKitsune, 0xNazgul, 0xNineDec, 0xc0ffEE, 0xf15ers, 0xkatana, 0xsanson, ACai, Aymen0909, Bnke0x0, BowTiedWardens, Chom, ElKu, Fitraldys, Funen, Haruxe, Hawkeye, IllIllI, JC, JohnSmith, Kaiziron, Kenshin, Lambda, Limbooo, MadWookie, Metatron, MiloTruck, Picodes, PwnedNoMore, Randyyy, RedOneN, ReyAdmirado, Ruhum, Sm4rty, StErMi, StyxRave, TerrierLover, TomJ, Tomio, UnusualTurtle, Waze, Yiko, _Adam, __141345__, ajtra, ak1, apostle0x01, asutorufos, c3phas, cRat1st0s, catchup, codetilda, cryptphi, datapunk, defsec, delfin454000, durianSausage, exd0tpy, fatherOfBlocks, gogo, grrwahrr, hake, hansfriese, horsefacts, ignacio, jayfromthe13th, joestakey, ladboy233, m_Rassska, mektigboy, minhquanym, mrpathfindr, natzuu, oyc_109, rajatbeladiya, reassor, rfa, robee, rokinot, sach1r0, saian, sashik_eth, simon135, slywaters, swit, z3s, zeesaw, zer0dot
23.0597 USDC - $23.06
<array>.length
should not be looked up in every loop of a for-loopThe overheads outlined below are PER LOOP, excluding the first loop
storage arrays incur a Gwarmaccess (100 gas) memory arrays use `MLOAD` (3 gas) calldata arrays use `CALLDATALOAD` (3 gas)
Caching the length changes each of these to a DUP<N>
(3 gas), and gets rid of the extra DUP<N> needed to store the stack offset
There are 10 instances of this issue:
https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L556
for (uint256 i = 0; i < orders.length; i++) {
https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L594
for (uint256 i = 0; i < assets.length; i++) {
https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L611
for (uint256 i = 0; i < assets.length; i++) {
https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L627
for (uint256 i = 0; i < floorTokens.length; i++) {
https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L637
for (uint256 i = 0; i < assets.length; i++) {
https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L647
for (uint256 i = 0; i < assets.length; i++) {
https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L658
for (uint256 i = 0; i < floorTokens.length; i++) {
https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L670
for (uint256 i = 0; i < whitelist.length; i++) {
https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L728
for (uint256 i = 0; i < arr.length; i++) {
https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L742
for (uint256 i = 0; i < arr.length; i++) {
Create a variable for array length:
uint length = nameOfArray.length; for(uint i; i < length; ) { ... }
Prefix increments are cheaper than postfix increments. Saves 6 gas PER LOOP
Instances include: There are 10 instances of this issue:
https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L556
for (uint256 i = 0; i < orders.length; i++) {
https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L594
for (uint256 i = 0; i < assets.length; i++) {
https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L611
for (uint256 i = 0; i < assets.length; i++) {
https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L627
for (uint256 i = 0; i < floorTokens.length; i++) {
https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L637
for (uint256 i = 0; i < assets.length; i++) {
https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L647
for (uint256 i = 0; i < assets.length; i++) {
https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L658
for (uint256 i = 0; i < floorTokens.length; i++) {
https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L670
for (uint256 i = 0; i < whitelist.length; i++) {
https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L728
for (uint256 i = 0; i < arr.length; i++) {
https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L742
for (uint256 i = 0; i < arr.length; i++) {
change i++
to ++i
If needed, the value can be read from the verified contract source code. Savings are due to the compiler not having to create non-payable getter functions for deployment calldata, and not adding another entry to the method ID table
There are 3 instances of this issue:
https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L89-L90
bytes32 public constant ERC721ASSET_TYPE_HASH = keccak256(abi.encodePacked("ERC721Asset(address token,uint256 tokenId)"));
https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L95-L96
bytes32 public constant ERC20ASSET_TYPE_HASH = keccak256(abi.encodePacked("ERC20Asset(address token,uint256 tokenAmount)"));
https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L101-L122
bytes32 public constant ORDER_TYPE_HASH = keccak256( abi.encodePacked( "Order(", "address maker,", "bool isCall,", "bool isLong,", "address baseAsset,", "uint256 strike,", "uint256 premium,", "uint256 duration,", "uint256 expiration,", "uint256 nonce,", "address[] whitelist,", "address[] floorTokens,", "ERC20Asset[] erc20Assets,", "ERC721Asset[] erc721Assets", ")", "ERC20Asset(address token,uint256 tokenAmount)", "ERC721Asset(address token,uint256 tokenId)" ) );
Change public
to private
https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L345
return positionId;
https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L363
return positionId;
https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L370
return positionId;
https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L378
return positionId;
Remove positionId
If the loop is updating some state variables of a contract, it should be bounded; otherwise, your contract could get stuck if the loop iteration is hitting the block's gas limit. If a loop is consuming more gas than the block's gas limit, that transaction will not be added to the blockchain; in turn, the transaction would revert. Hence, there is a transaction failure. Always consider having bounded loops in which you are updating contract state variables for each iteration.
https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L556-L558
for (uint256 i = 0; i < orders.length; i++) { positionIds[i] = fillOrder(orders[i], signatures[i], floorAssetTokenIds[i]); }
> 0
is less efficient than != 0
for unsigned integers.
!= 0
costs less gas compared to > 0
for unsigned integers in require statements with the optimizer enabled (6 gas)
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.
Instances include:
https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L293
require(order.baseAsset.code.length > 0, "baseAsset is not contract");
https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L327
if (weth == order.baseAsset && msg.value > 0) {
https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L351
if (weth == order.baseAsset && msg.value > 0) {
https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L427
if (weth == order.baseAsset && msg.value > 0) {
https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L498
if (fee > 0) {
https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L598
require(token.code.length > 0, "ERC20: Token is not contract");
https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L599
require(tokenAmount > 0, "ERC20: Amount too small");
Replace > 0
with != 0
Custom errors from Solidity 0.8.4 are cheaper than revert strings (cheaper deployment cost and runtime cost when the revert condition is met) while providing the same amount of information, as explained here https://blog.soliditylang.org/2021/04/21/custom-errors/
Custom errors are defined using the error statement
Instances include: https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L214 https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L241 https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L278-L293 https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L297-L298 https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L329 https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L353 https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L395-L401 https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L405-L406 https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L429 https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L470 https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L475 https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L481 https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L527 https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L551-L552 https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L598-L599 https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L765
Replace require
statements with custom errors.
If a variable is not set/initialized, it is assumed to have the default value (0
, false
, 0x0
etc depending on the data type).
Explicitly initializing it with its default value is an anti-pattern and wastes gas.
Instances include:
https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L497
uint256 feeAmount = 0;
https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L556
for (uint256 i = 0; i < orders.length; i++) {
https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L594
for (uint256 i = 0; i < assets.length; i++) {
https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L611
for (uint256 i = 0; i < assets.length; i++) {
https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L627
for (uint256 i = 0; i < floorTokens.length; i++) {
https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L637
for (uint256 i = 0; i < assets.length; i++) {
https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L647
for (uint256 i = 0; i < assets.length; i++) {
https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L658
for (uint256 i = 0; i < floorTokens.length; i++) {
https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L670
for (uint256 i = 0; i < whitelist.length; i++) {
https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L728
for (uint256 i = 0; i < arr.length; i++) {
https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L742
for (uint256 i = 0; i < arr.length; i++) {
Remove explicit initialization for default values.
uint256 feeAmount;
The default “checked” behavior costs more gas when adding/diving/multiplying, because under-the-hood those checks are implemented as a series of opcodes that, prior to performing the actual arithmetic, check for under/overflow and revert if it is detected.
if it can statically be determined there is no possible way for your arithmetic to under/overflow (such as a condition in an if statement), surrounding the arithmetic in an unchecked block will save gas.
Instances include:
https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L503
ERC20(order.baseAsset).safeTransfer(msg.sender, order.strike - feeAmount);
https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L556
for (uint256 i = 0; i < orders.length; i++) {
https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L594
for (uint256 i = 0; i < assets.length; i++) {
https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L611
for (uint256 i = 0; i < assets.length; i++) {
https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L627
for (uint256 i = 0; i < floorTokens.length; i++) {
https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L637
for (uint256 i = 0; i < assets.length; i++) {
https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L647
for (uint256 i = 0; i < assets.length; i++) {
https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L658
for (uint256 i = 0; i < floorTokens.length; i++) {
https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L670
for (uint256 i = 0; i < whitelist.length; i++) {
https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L728
for (uint256 i = 0; i < arr.length; i++) {
https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L742
for (uint256 i = 0; i < arr.length; i++) {
Place the arithmetic operations in an unchecked
block
for (uint i; i < length;) { ... unchecked{ ++i; } }
Anytime you are reading from storage more than once, it is cheaper in gas cost to cache the variable in memory: a SLOAD
cost 100gas, while MLOAD
and MSTORE
cost 3 gas.
Instances include:
https://github.com/code-423n4/2022-06-putty/blob/main/contracts/src/PuttyV2.sol#L498-L499
fee
is read twice
Create a variable and use it:
uint _fee = fee; if (_fee > 0) { feeAmount = (order.strike * _fee) / 1000;