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: 66/133
Findings: 2
Award: $68.72
🌟 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.1302 USDC - $47.13
Constant variables instead of magic numbers can help keep the code easier to read and maintain. PuttyV2.sol#L287 - recommend adding constant MAX_DURATION PuttyV2.sol#L499 - recommend adding constant FEE_DENOMINATOR
🌟 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
21.5856 USDC - $21.59
Require checks that involve function arguments/constants should come before any that involve state variables. That way if they fail you are saving having to load any state variables (100 gas for an SLOAD)
PuttyV2.sol#L281 - Recommend moving this require statement down to after the require statement on line 298 PuttyV2.sol#L401- Recommend moving this require statement down to after the require statement on line 406
The following functions are never called in their contracts and can be switched to external to save gas:
PuttyV2.sol#L389 PuttyV2.sol#L466 PuttyV2.sol#L546-L550 PuttyV2.sol#L573-L577 PuttyV2.sol#L753
Whenever referencing a storage variable more than once in a function without modifying you can save ~97 gas per use by caching the value. (normally 100 gas each use vs 103 gas to SLOAD/MSTORE for the first use and then only 3 gas for further uses)
PuttyV2.sol#L498-L499 - can cache fee to save ~97 gas
When incrementing i in for loops there is no chance of overflow so unchecked can be used to save gas. I ran a simple test in remix and found deployment savings of 31,653 gas and on each function call saved ~141 gas per iteration.
contract Test { function loopTest() external { for (uint256 i; i < 1; ++i) { Deployment Cost: 125,637, Cost on function call: 24,601 vs for (uint256 i; i < 1; ) { // for loop body unchecked { ++i } Deployment Cost: 93,984, Cost on function call: 24,460 } } }
For loops that can use unchecked increments: PuttyV2.sol#L556 PuttyV2.sol#L594 PuttyV2.sol#L611 PuttyV2.sol#L627 PuttyV2.sol#L637 PuttyV2.sol#L647 PuttyV2.sol#L658 PuttyV2.sol#L670 PuttyV2.sol#L728 PuttyV2.sol#L742
In for loops pre increments can be used to save a small amount of gas per iteration. I ran a test in remix using a for loop and found the deployment savings of 497 gas and ~5 gas per iteration.
contract Test { function loopTest() external { for (uint256 i; i < 1; i++) { (Deployment cost: 118,408, Cost on function call: 24,532) vs for (uint256 i; i < 1; ++i) { (Deployment cost: 117,911, Cost on function call: 24,527) } } }
For loops that can use pre increments: PuttyV2.sol#L556 PuttyV2.sol#L594 PuttyV2.sol#L611 PuttyV2.sol#L627 PuttyV2.sol#L637 PuttyV2.sol#L647 PuttyV2.sol#L658 PuttyV2.sol#L670 PuttyV2.sol#L728 PuttyV2.sol#L742
As your using a solidity version > 0.8.4 you can replace revert strings with custom errors. This will save in deployment costs and runtime costs. Based on a test in remix, replacing a single revert string with a custom error saved 12,404 gas in deployment cost and 86 gas on each function call.
contract Test { uint256 a; function check() external { require(a != 0, "check failed"); } } (Deployment cost: 114,703, Cost on Function call: 23,392) vs contract Test { uint256 a; error checkFailed(); function check() external { if (a != 0) revert checkFailed(); } } (Deployment cost: 102,299, Cost on Function call: 23,306)
Instances where custom errors can be implemented: PuttyV2.sol#L214 PuttyV2.sol#L241 PuttyV2.sol#L278-L293 PuttyV2.sol#L297-L298 PuttyV2.sol#L329 PuttyV2.sol#L353 PuttyV2.sol#L395-L401 PuttyV2.sol#L405-L406 PuttyV2.sol#L429 PuttyV2.sol#L470 PuttyV2.sol#L475 PuttyV2.sol#L481 PuttyV2.sol#L527 PuttyV2.sol#L551-L552 PuttyV2.sol#L598-L599 PuttyV2.sol#L765 PuttyV2Nft.sol#L12-L13 PuttyV2Nft.sol#L26-L31 PuttyV2Nft.sol#L41
When checking whether a uint is > 0 in a require statement (with optimiser on) you can save a small amount of gas by replacing with != 0. I ran a test in remix and found the savings for a single occurance is 632 in deployment cost and 6 gas on each function call.
contract Test { uint256 a; function check() external { require(a > 0); (Deployment cost: 79,763, Cost on function call: 23,305) vs require(a != 0); (Deployment cost: 79,331, Cost on function call: 23,299) } }
Instances where uint != 0 can be used: PuttyV2.sol#L293 PuttyV2.sol#L598-L599
When using multiple related mappings they can be combined into a mapping => struct. This will allow the use of less storage slots and save in deployment costs (saving ~62,051 gas based on the following remix test). This will also result in cheaper reads and writes going forward.
contract Test { mapping(uint256 => uint256) public positionExpirations; mapping(uint256 => bool) public exercisedPositions; mapping(uint256 => uint256[]) public positionFloorAssetTokenIds; (Deployment Cost: 219,235) vs struct positionInfo { uint256 expirations; bool exercised; uint256[] floorAssetTokenIds; } mapping(uint256 => positionInfo) public positions; (Deployment Cost: 157,184) }
Related Mappings that can be combined in a Struct: PuttyV2.sol#L145-L164