Putty contest - durianSausage's results

An order-book based american options market for NFTs and ERC20s.

General Information

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

Putty

Findings Distribution

Researcher Performance

Rank: 81/133

Findings: 2

Award: $68.30

🌟 Selected for report: 0

πŸš€ Solo Findings: 0

durianSausage USDC Putty contest

Low Risk and Non-Critical Issues

L01: function declaration contains return variable name should not use return statement in function body

problem

function declaration contains return variable name should not use return statement in function body

prof

'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.

L02: We should check the input and storage first before call and other operations.

problem

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.

prof

'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.

durianSausage USDC Putty contest

gas optimization

gas01: custom error save more gas

problem

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.

prof

'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

G02: COMPARISONS WITH ZERO FOR UNSIGNED INTEGERS

problem

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

prof

'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

G03: ARRAY LENTH SHOULD USE MEMoRY VARIABLE LOAD IT

problem

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

prof

'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

G04: PREFIX INCREMENT SAVE MORE GAS

problem

prefix increment ++i is more cheaper than postfix i++

prof

'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

G05: use memory variable cache storage variables
problem

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.

prof

'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); }
AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax Β© 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter