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: 52/133
Findings: 2
Award: $83.79
🌟 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
49.346 USDC - $49.35
uint256(orderHash)
is used in 3 places inside fillOrder()
, it's casted value can be saved into a local variable to reuse variable instead of multiple casting
// recommendation example uint tempOrderHash = uint256(orderHash)
10_000 days
can be saved in a constant variblebytes.concat
instead of `abi.encodePackedskim()
like method to withdraw stucked funds.string
library is imported in PuttyV2Nft.sol
is not used.🌟 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
34.4378 USDC - $34.44
While looping through array, the array length can be cached to save gas instead of computing length in each array iteration.
for eg.
uint256 len = assets.length; for(uint256 i = 0; i < len; i++) { ... }
in:
./contracts/src/PuttyV2.sol:556: for (uint256 i = 0; i < orders.length; i++) { ./contracts/src/PuttyV2.sol:594: for (uint256 i = 0; i < assets.length; i++) { ./contracts/src/PuttyV2.sol:611: for (uint256 i = 0; i < assets.length; i++) { ./contracts/src/PuttyV2.sol:627: for (uint256 i = 0; i < floorTokens.length; i++) { ./contracts/src/PuttyV2.sol:637: for (uint256 i = 0; i < assets.length; i++) { ./contracts/src/PuttyV2.sol:647: for (uint256 i = 0; i < assets.length; i++) { ./contracts/src/PuttyV2.sol:658: for (uint256 i = 0; i < floorTokens.length; i++) { ./contracts/src/PuttyV2.sol:670: for (uint256 i = 0; i < whitelist.length; i++) { ./contracts/src/PuttyV2.sol:728: for (uint256 i = 0; i < arr.length; i++) { ./contracts/src/PuttyV2.sol:742: for (uint256 i = 0; i < arr.length; i++) {
++i
) is cheaper than postfix increment (i++
)Using prefix increment is saves small amount of gas than postfix increment because it returns the updated value hence doesn't requires to store intermediate value. This can be more significant in loops where this operation is done multiple times.
for eg.
// before for(uint256 i = 0; i < len; i++) { ... } // Replace with for(uint256 i = 0; i < len; ++i) { ... }
In:
./contracts/src/PuttyV2.sol:556: for (uint256 i = 0; i < orders.length; i++) { ./contracts/src/PuttyV2.sol:594: for (uint256 i = 0; i < assets.length; i++) { ./contracts/src/PuttyV2.sol:611: for (uint256 i = 0; i < assets.length; i++) { ./contracts/src/PuttyV2.sol:627: for (uint256 i = 0; i < floorTokens.length; i++) { ./contracts/src/PuttyV2.sol:637: for (uint256 i = 0; i < assets.length; i++) { ./contracts/src/PuttyV2.sol:647: for (uint256 i = 0; i < assets.length; i++) { ./contracts/src/PuttyV2.sol:658: for (uint256 i = 0; i < floorTokens.length; i++) { ./contracts/src/PuttyV2.sol:670: for (uint256 i = 0; i < whitelist.length; i++) { ./contracts/src/PuttyV2.sol:728: for (uint256 i = 0; i < arr.length; i++) { ./contracts/src/PuttyV2.sol:742: for (uint256 i = 0; i < arr.length; i++) {
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
!=0
is more gas efficient than >0
for uintsFor uint comparision, != 0
can be used instead of > 0
in order to save gas
In:
./contracts/src/PuttyV2.sol:293: require(order.baseAsset.code.length > 0, "baseAsset is not contract"); ./contracts/src/PuttyV2.sol:327: if (weth == order.baseAsset && msg.value > 0) { ./contracts/src/PuttyV2.sol:351: if (weth == order.baseAsset && msg.value > 0) { ./contracts/src/PuttyV2.sol:427: if (weth == order.baseAsset && msg.value > 0) { ./contracts/src/PuttyV2.sol:498: if (fee > 0) { ./contracts/src/PuttyV2.sol:598: require(token.code.length > 0, "ERC20: Token is not contract"); ./contracts/src/PuttyV2.sol:599: require(tokenAmount > 0, "ERC20: Amount too small");
Constant expressions such as const a = keccak256(x)
is not constant value but constant expression. Hence it will need to be evaluated every time it is referenced.
Making the constant expression immutable
will save gas, as it will not need to be evaluated every time.
There are several functions which are public, but never called from within the contract. Those functions can be changed to external as external functions are cheaper than public.
in PuttyV2.sol
, these functions can be made external
exercise() withdraw() batchFillOrder() acceptCounterOffer() domainSeparatorV4()
if the condition inside if statement will always be true, it can be avoided to save gas.
In PuttyV2.sol#L374 The condition checked will always be true, so it doesn't needs to be checked.
Several operations such as the loop counter increment will never overflow since it starts from zero and only increases. In such cases unchecked math can be used to save gas.
for eg.
// Change for (uint256 i = 0; i < orders.length; i++) { } // To for (uint256 i = 0; i < orders.length; ) { unchecked { i++; } }
The function fillOrder
has a named return positionId
but all cases of return is explicit. This causes more gas on deployment.
Having public constants also makes their getter functions. This will increase code size and hence deployment gas. They can be changed to private to save gas.