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: 81/133
Findings: 2
Award: $68.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.1302 USDC - $47.13
function declaration contains return variable name should not use return statement in function body
'PuttyV2.sol', fillOrder
function fillOrder( Order memory order, bytes calldata signature, uint256[] memory floorAssetTokenIds ) public payable returns (uint256 positionId) {
in line 345 363 370 378, should remove these statements.
We should check the input and storage first before call and other operations. In short, check requirement should put at the first of the function. We can save user gases.
'PuttyV2.sol', exercise
bytes32 orderHash = hashOrder(order); // check user owns the position require(ownerOf(uint256(orderHash)) == msg.sender, "Not owner"); // check position is long require(order.isLong, "Can only exercise long positions"); // check position has not expired require(block.timestamp < positionExpirations[uint256(orderHash)], "Position has expired"); // check floor asset token ids length is 0 unless the position type is put !order.isCall ? require(floorAssetTokenIds.length == order.floorTokens.length, "Wrong amount of floor tokenIds") : require(floorAssetTokenIds.length == 0, "Invalid floor tokenIds length");
change in to
// check position is long require(order.isLong, "Can only exercise long positions"); // check position has not expired require(block.timestamp < positionExpirations[uint256(orderHash)], "Position has expired"); // check floor asset token ids length is 0 unless the position type is put !order.isCall ? require(floorAssetTokenIds.length == order.floorTokens.length, "Wrong amount of floor tokenIds") : require(floorAssetTokenIds.length == 0, "Invalid floor tokenIds length"); bytes32 orderHash = hashOrder(order); // check user owns the position require(ownerOf(uint256(orderHash)) == msg.sender, "Not owner");
sames to fillOrder function
#0 - HickupHH3
2022-07-15T09:46:34Z
L02 is invalid, saving orderHash
to memory first is a gas opt.
π 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.1718 USDC - $21.17
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 https://blog.soliditylang.org/2021/04/21/custom-errors/. Custom errors are defined using the error statement.
'PuttyV2.sol', 292 'PuttyV2.sol', 597 'PuttyV2.sol', 598 'PuttyV2.sol', 213 'PuttyV2.sol', 240 'PuttyV2.sol', 277 'PuttyV2.sol', 280 'PuttyV2.sol', 283 'PuttyV2.sol', 286 'PuttyV2.sol', 289 'PuttyV2.sol', 292 'PuttyV2.sol', 296 'PuttyV2.sol', 297 'PuttyV2.sol', 328 'PuttyV2.sol', 352 'PuttyV2.sol', 394 'PuttyV2.sol', 397 'PuttyV2.sol', 400 'PuttyV2.sol', 404 'PuttyV2.sol', 405 'PuttyV2.sol', 428 'PuttyV2.sol', 469 'PuttyV2.sol', 474 'PuttyV2.sol', 480 'PuttyV2.sol', 526 'PuttyV2.sol', 550 'PuttyV2.sol', 551 'PuttyV2.sol', 597 'PuttyV2.sol', 598 'PuttyV2.sol', 764 'PuttyV2.sol', 283 'PuttyV2.sol', 292 'PuttyV2.sol', 296 'PuttyV2.sol', 297 'PuttyV2.sol', 404 'PuttyV2.sol', 405 'PuttyV2.sol', 550 'PuttyV2.sol', 551 'PuttyV2.sol', 597 'PuttyV2.sol', 292 'PuttyV2.sol', 597 'PuttyV2.sol', 598 'PuttyV2Nft.sol', 11 'PuttyV2Nft.sol', 12 'PuttyV2Nft.sol', 25 'PuttyV2Nft.sol', 26 'PuttyV2Nft.sol', 27 'PuttyV2Nft.sol', 40
0 is less gas efficient than !0 if you enable the optimizer at 10k AND youβre in a require statement. Detailed explanation with the opcodes https://twitter.com/gzeon/status/1485428085885640706
'PuttyV2.sol', 292 'PuttyV2.sol', 326 'PuttyV2.sol', 350 'PuttyV2.sol', 426 'PuttyV2.sol', 497 'PuttyV2.sol', 597 'PuttyV2.sol', 598 'PuttyV2.sol', 292 'PuttyV2.sol', 597 'PuttyV2.sol', 598
The 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 (3 gas), and gets rid of the extra DUP needed to store the stack offset
'PuttyV2.sol', 555 'PuttyV2.sol', 593 'PuttyV2.sol', 610 'PuttyV2.sol', 626 'PuttyV2.sol', 636 'PuttyV2.sol', 646 'PuttyV2.sol', 657 'PuttyV2.sol', 669 'PuttyV2.sol', 727 'PuttyV2.sol', 741
prefix increment ++i is more cheaper than postfix i++
'PuttyV2.sol', 555 'PuttyV2.sol', 593 'PuttyV2.sol', 610 'PuttyV2.sol', 626 'PuttyV2.sol', 636 'PuttyV2.sol', 646 'PuttyV2.sol', 657 'PuttyV2.sol', 669 'PuttyV2.sol', 727 'PuttyV2.sol', 741
some functions use many times storage variables, so code read it from storage again and again. If we can cache it in the memory variables, we will save a lot of gas.
'PuttyV2.sol', withdraw function
if (fee > 0) { feeAmount = (order.strike * fee) / 1000; ERC20(order.baseAsset).safeTransfer(owner(), feeAmount); }
should in to
uint fee_ = fee; if (fee_ > 0) { feeAmount = (order.strike * fee_) / 1000; ERC20(order.baseAsset).safeTransfer(owner(), feeAmount); }