code-423n4 / 2022-06-putty-findings

5 stars 0 forks source link

Gas Optimizations #217

Open code423n4 opened 2 years ago

code423n4 commented 2 years ago

Summary

Optimizations:

Detailed gas comparison before and after

Comparison summary:

function name AVG MAX
before after units saved percent saved before after units saved percent saved
fillOrder 106845 103354 3491 3.27% 203777 198953 4824 2.37%
withdraw 27840 25990 1850 6.65% 71168 66459 4709 6.62%
exercise 55920 54661 1259 2.25% 133534 128900 4634 3.47%
hashOrder 5484 5430 54 0.98% 7782 7576 206 2.65%
cancel 26509 26429 80 0.30% 34321 34241 80 0.23%
onERC721Received 815 783 32 3.93% 815 783 32 3.93%
baseURI 1321 1299 22 1.67% 1321 1299 22 1.67%
cancelledOrders 550 528 22 4.00% 550 528 22 4.00%
exercisedPositions 528 506 22 4.17% 528 506 22 4.17%
fee 406 384 22 5.42% 406 384 22 5.42%
tokenURI 24547 24525 22 0.09% 46489 46467 22 0.05%
ownerOf 554 555 -1 -0.18% 554 555 -1 -0.18%
setFee 14052 14072 -20 -0.14% 25597 25617 -20 -0.08%
transferFrom 5255 5285 -30 -0.57% 5255 5285 -30 -0.57%
setBaseURI 9153 9197 -44 -0.48% 12303 12347 -44 -0.36%
before after units saved percent saved
Depolyment cost 4774098 4488713 285385 5.98%
Depolyment size 25226 23801 1425 5.65%

Use the same order instead of cloning

Lines: PuttyV2.sol#L685-L689

Gas saved: up to 4K of gas units for each of the following functions:

The idea here is just to use the original order instead of cloning it. Flip the 'isLong' field and then flip it back after it has been hashed. Since the hashOrder function doesn't modify the order, it's safe to pass the original order to it.

I've noticed also some change in the gas usage of internal functions (e.g. onERC721Received increased by 8 units, cancel which is used internally in acceptCounterOffer decreased by ~50). I'm not sure why is that.

     function hashOppositeOrder(Order memory order) public view returns (bytes32 orderHash) {
-        // use decode/encode to get a copy instead of reference
-        Order memory oppositeOrder = abi.decode(abi.encode(order), (Order));
-
-        // get the opposite side of the order (short/long)
-        oppositeOrder.isLong = !order.isLong;
-        orderHash = hashOrder(oppositeOrder);
+        // reverse `isLong` to create the opposite order
+        order.isLong = !order.isLong;
+        orderHash = hashOrder(order);
+        // reverse it back to original state
+        order.isLong = !order.isLong;
     }

Gas-report diff:

 ╭──────────────────────────────────┬─────────────────┬────────┬────────┬────────┬─────────╮
 │ src/PuttyV2.sol:PuttyV2 contract ┆                 ┆        ┆        ┆        ┆         │
 ╞══════════════════════════════════╪═════════════════╪════════╪════════╪════════╪═════════╡
 │ Deployment Cost                  ┆ Deployment Size ┆        ┆        ┆        ┆         │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
-│ 4774098                          ┆ 25226           ┆        ┆        ┆        ┆         │
+│ 4637113                          ┆ 24542           ┆        ┆        ┆        ┆         │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
 │ Function Name                    ┆ min             ┆ avg    ┆ median ┆ max    ┆ # calls │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
-│ cancel                           ┆ 3075            ┆ 26509  ┆ 34321  ┆ 34321  ┆ 4       │
+│ cancel                           ┆ 3017            ┆ 26451  ┆ 34263  ┆ 34263  ┆ 4       │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
-│ exercise                         ┆ 5759            ┆ 55920  ┆ 68002  ┆ 133534 ┆ 18      │
+│ exercise                         ┆ 5701            ┆ 55073  ┆ 67944  ┆ 129547 ┆ 18      │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
-│ fillOrder                        ┆ 10014           ┆ 106845 ┆ 115883 ┆ 203777 ┆ 51      │
+│ fillOrder                        ┆ 9960            ┆ 103570 ┆ 111946 ┆ 199440 ┆ 51      │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
-│ hashOrder                        ┆ 5206            ┆ 5484   ┆ 5206   ┆ 7782   ┆ 62      │
+│ hashOrder                        ┆ 5148            ┆ 5423   ┆ 5148   ┆ 7692   ┆ 62      │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
-│ onERC721Received                 ┆ 815             ┆ 815    ┆ 815    ┆ 815    ┆ 14      │
+│ onERC721Received                 ┆ 827             ┆ 827    ┆ 827    ┆ 827    ┆ 14      │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
@@ -126,9 +126,9 @@ Test result: . 7 passed; 0 failed; finished in 14.82s
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
-│ transferFrom                     ┆ 5255            ┆ 5255   ┆ 5255   ┆ 5255   ┆ 1       │
+│ transferFrom                     ┆ 5263            ┆ 5263   ┆ 5263   ┆ 5263   ┆ 1       │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
-│ withdraw                         ┆ 3075            ┆ 27840  ┆ 24545  ┆ 71168  ┆ 10      │
+│ withdraw                         ┆ 3017            ┆ 25210  ┆ 21396  ┆ 67159  ┆ 10      │
 ╰──────────────────────────────────┴─────────────────┴────────┴────────┴────────┴─────────╯

If for any reason the sponsor isn't comfortable with temporarily-changing the original order, the following optimization can be used instead. It's a bit more expensive but still a huge optimization:

(notice that the arrays aren't cloned, but referenced)

    function hashOppositeOrder(Order memory order) public view returns (bytes32 orderHash) {
        // get a copy instead of reference
        Order memory oppositeOrder = Order(
            order.maker,
            order.isCall,
            !order.isLong, // get the opposite side of the order (short/long)
            order.baseAsset,
            order.strike,
            order.premium,
            order.duration,
            order.expiration,
            order.nonce,
            order.whitelist,
            order.floorTokens,
            order.erc20Assets,
            order.erc721Assets
        );
        orderHash = hashOrder(oppositeOrder);
    }

Using if-else instead of multiple if-s

Lines:

Gas saved: up to 200 gas units at fillOrder, and up to 60 at withdraw.

In order to save condition checks, it's better to use if-else instead of multiple if-s. At the fillOrder function, I've also 'compressed' the premium-transfer and asset-transfer to one block in order to save an additional if-else.

       if (order.isLong) {
             ERC20(order.baseAsset).safeTransferFrom(order.maker, msg.sender, order.premium);
+
+            // filling long call: transfer assets from taker to contract
+            if (order.isCall) {
+                _transferERC20sIn(order.erc20Assets, msg.sender);
+                _transferERC721sIn(order.erc721Assets, msg.sender);
+                _transferFloorsIn(order.floorTokens, floorAssetTokenIds, msg.sender);
+                return positionId;
+            }
+            // filling long put: transfer strike from taker to contract
+            else {
+                // handle the case where the taker uses native ETH instead of WETH to deposit the strike
+                if (weth == order.baseAsset && msg.value > 0) {
+                    // check enough ETH was sent to cover the strike
+                    require(msg.value == order.strike, "Incorrect ETH amount sent");
+
+                    // convert ETH to WETH
+                    // we convert the strike ETH to WETH so that the logic in exercise() works
+                    // - because exercise() assumes an ERC20 interface on the base asset.
+                    IWETH(weth).deposit{value: msg.value}();
+                } else {
+                    ERC20(order.baseAsset).safeTransferFrom(msg.sender, address(this), order.strike);
+                }
+
+                return positionId;
+            }
         } else {
             // handle the case where the user uses native ETH instead of WETH to pay the premium
             if (weth == order.baseAsset && msg.value > 0) {
@@ -337,45 +362,18 @@ contract PuttyV2 is PuttyV2Nft, EIP712("Putty", "2.0"), ERC721TokenReceiver, Own
             } else {
                 ERC20(order.baseAsset).safeTransferFrom(msg.sender, order.maker, order.premium);
             }
-        }

-        // filling short put: transfer strike from maker to contract
-        if (!order.isLong && !order.isCall) {
-            ERC20(order.baseAsset).safeTransferFrom(order.maker, address(this), order.strike);
-            return positionId;
-        }
-
-        // filling long put: transfer strike from taker to contract
-        if (order.isLong && !order.isCall) {
-            // handle the case where the taker uses native ETH instead of WETH to deposit the strike
-            if (weth == order.baseAsset && msg.value > 0) {
-                // check enough ETH was sent to cover the strike
-                require(msg.value == order.strike, "Incorrect ETH amount sent");
-
-                // convert ETH to WETH
-                // we convert the strike ETH to WETH so that the logic in exercise() works
-                // - because exercise() assumes an ERC20 interface on the base asset.
-                IWETH(weth).deposit{value: msg.value}();
-            } else {
-                ERC20(order.baseAsset).safeTransferFrom(msg.sender, address(this), order.strike);
+            // filling short call: transfer assets from maker to contract
+            if (order.isCall) {
+                _transferERC20sIn(order.erc20Assets, order.maker);
+                _transferERC721sIn(order.erc721Assets, order.maker);
+                return positionId;
+            }
+            // filling short put: transfer strike from maker to contract
+            else {
+                ERC20(order.baseAsset).safeTransferFrom(order.maker, address(this), order.strike);
+                return positionId;
             }
-
-            return positionId;
-        }
-
-        // filling short call: transfer assets from maker to contract
-        if (!order.isLong && order.isCall) {
-            _transferERC20sIn(order.erc20Assets, order.maker);
-            _transferERC721sIn(order.erc721Assets, order.maker);
-            return positionId;
-        }
-
-        // filling long call: transfer assets from taker to contract
-        if (order.isLong && order.isCall) {
-            _transferERC20sIn(order.erc20Assets, msg.sender);
-            _transferERC721sIn(order.erc721Assets, msg.sender);
-            _transferFloorsIn(order.floorTokens, floorAssetTokenIds, msg.sender);
-            return positionId;
         }
     }

     @@ -504,9 +502,8 @@ contract PuttyV2 is PuttyV2Nft, EIP712("Putty", "2.0"), ERC721TokenReceiver, Own
             return;
         }
         // transfer assets from putty to owner if put is exercised or call is expired
-        if ((order.isCall && !isExercised) || (!order.isCall && isExercised)) {
+        else {
             _transferERC20sOut(order.erc20Assets);
             _transferERC721sOut(order.erc721Assets);

Gas-report diff:

 ╭──────────────────────────────────┬─────────────────┬────────┬────────┬────────┬─────────╮
 │ src/PuttyV2.sol:PuttyV2 contract ┆                 ┆        ┆        ┆        ┆         │
 ╞══════════════════════════════════╪═════════════════╪════════╪════════╪════════╪═════════╡
 │ Deployment Cost                  ┆ Deployment Size ┆        ┆        ┆        ┆         │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
-│ 4637113                          ┆ 24542           ┆        ┆        ┆        ┆         │
+│ 4612078                          ┆ 24417           ┆        ┆        ┆        ┆         │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
 │ Function Name                    ┆ min             ┆ avg    ┆ median ┆ max    ┆ # calls │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
@@ -108,7 +108,7
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
-│ fillOrder                        ┆ 9960            ┆ 103570 ┆ 111946 ┆ 199440 ┆ 51      │
+│ fillOrder                        ┆ 9960            ┆ 103462 ┆ 111791 ┆ 199224 ┆ 51      │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
@@ -128,7 +128,7
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
-│ withdraw                         ┆ 3017            ┆ 25210  ┆ 21396  ┆ 67159  ┆ 10      │
+│ withdraw                         ┆ 3017            ┆ 25181  ┆ 21396  ┆ 67100  ┆ 10      │
 ╰──────────────────────────────────┴─────────────────┴────────┴────────┴────────┴─────────╯

Internalize unnecessary public functions

Lines:

Having less public functions can reduce the deployment size + save some gas on other functions (Less function hashes to check against at the beginning )

I think the following functions don't have a use as public functions. hashOrder is used by one of the test (and may be also beneficial as public to the users), so I haven't internalized it.

-    function isWhitelisted(address[] memory whitelist, address target) public pure returns (bool) {
+    function isWhitelisted(address[] memory whitelist, address target) internal pure returns (bool) {

@@ -722,7 +722,7 @@ contract PuttyV2 is PuttyV2Nft, EIP712("Putty", "2.0"), ERC721TokenReceiver, Own

-    function encodeERC20Assets(ERC20Asset[] memory arr) public pure returns (bytes memory encoded) {
+    function encodeERC20Assets(ERC20Asset[] memory arr) internal pure returns (bytes memory encoded) {

@@ -736,7 +736,7 @@ contract PuttyV2 is PuttyV2Nft, EIP712("Putty", "2.0"), ERC721TokenReceiver, Own

-    function encodeERC721Assets(ERC721Asset[] memory arr) public pure returns (bytes memory encoded) {
+    function encodeERC721Assets(ERC721Asset[] memory arr) internal pure returns (bytes memory encoded) {

Gas-report diff:

This reduces about 20 units per function, but has increased the cost of ownerOf(by 1), setBaseURI and setFee (40 units each). Considering that the set function are rarely used - I think the optimization is worth it.

 ╭──────────────────────────────────┬─────────────────┬────────┬────────┬────────┬─────────╮
 │ src/PuttyV2.sol:PuttyV2 contract ┆                 ┆        ┆        ┆        ┆         │
 ╞══════════════════════════════════╪═════════════════╪════════╪════════╪════════╪═════════╡
 │ Deployment Cost                  ┆ Deployment Size ┆        ┆        ┆        ┆         │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
-│ 4612078                          ┆ 24417           ┆        ┆        ┆        ┆         │
+│ 4555400                          ┆ 24134           ┆        ┆        ┆        ┆         │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
 │ Function Name                    ┆ min             ┆ avg    ┆ median ┆ max    ┆ # calls │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
-│ baseURI                          ┆ 1321            ┆ 1321   ┆ 1321   ┆ 1321   ┆ 1       │
+│ baseURI                          ┆ 1299            ┆ 1299   ┆ 1299   ┆ 1299   ┆ 1       │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
-│ cancel                           ┆ 3017            ┆ 26451  ┆ 34263  ┆ 34263  ┆ 4       │
+│ cancel                           ┆ 2995            ┆ 26429  ┆ 34241  ┆ 34241  ┆ 4       │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
-│ cancelledOrders                  ┆ 550             ┆ 550    ┆ 550    ┆ 550    ┆ 1       │
+│ cancelledOrders                  ┆ 528             ┆ 528    ┆ 528    ┆ 528    ┆ 1       │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
-│ exercise                         ┆ 5701            ┆ 55073  ┆ 67944  ┆ 129547 ┆ 18      │
+│ exercise                         ┆ 5701            ┆ 55063  ┆ 67944  ┆ 129476 ┆ 18      │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
-│ exercisedPositions               ┆ 528             ┆ 528    ┆ 528    ┆ 528    ┆ 1       │
+│ exercisedPositions               ┆ 506             ┆ 506    ┆ 506    ┆ 506    ┆ 1       │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
-│ fee                              ┆ 406             ┆ 406    ┆ 406    ┆ 406    ┆ 1       │
+│ fee                              ┆ 384             ┆ 384    ┆ 384    ┆ 384    ┆ 1       │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
-│ fillOrder                        ┆ 9960            ┆ 103462 ┆ 111791 ┆ 199224 ┆ 51      │
+│ fillOrder                        ┆ 9916            ┆ 103411 ┆ 111747 ┆ 199092 ┆ 51      │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
-│ hashOrder                        ┆ 5148            ┆ 5423   ┆ 5148   ┆ 7692   ┆ 62      │
+│ hashOrder                        ┆ 5168            ┆ 5443   ┆ 5168   ┆ 7712   ┆ 62      │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
-│ onERC721Received                 ┆ 827             ┆ 827    ┆ 827    ┆ 827    ┆ 14      │
+│ onERC721Received                 ┆ 783             ┆ 783    ┆ 783    ┆ 783    ┆ 14      │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
-│ ownerOf                          ┆ 554             ┆ 554    ┆ 554    ┆ 554    ┆ 4       │
+│ ownerOf                          ┆ 555             ┆ 555    ┆ 555    ┆ 555    ┆ 4       │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
-│ setBaseURI                       ┆ 2853            ┆ 9153   ┆ 12303  ┆ 12303  ┆ 3       │
+│ setBaseURI                       ┆ 2897            ┆ 9197   ┆ 12347  ┆ 12347  ┆ 3       │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
-│ setFee                           ┆ 2498            ┆ 14052  ┆ 14057  ┆ 25597  ┆ 4       │
+│ setFee                           ┆ 2518            ┆ 14072  ┆ 14077  ┆ 25617  ┆ 4       │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
-│ tokenURI                         ┆ 2606            ┆ 24547  ┆ 24547  ┆ 46489  ┆ 2       │
+│ tokenURI                         ┆ 2584            ┆ 24525  ┆ 24525  ┆ 46467  ┆ 2       │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
-│ transferFrom                     ┆ 5263            ┆ 5263   ┆ 5263   ┆ 5263   ┆ 1       │
+│ transferFrom                     ┆ 5285            ┆ 5285   ┆ 5285   ┆ 5285   ┆ 1       │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
-│ withdraw                         ┆ 3017            ┆ 25181  ┆ 21396  ┆ 67100  ┆ 10      │
+│ withdraw                         ┆ 2994            ┆ 25160  ┆ 21378  ┆ 67082  ┆ 10      │
 ╰──────────────────────────────────┴─────────────────┴────────┴────────┴────────┴─────────╯

Logical expressions

  1. The expression order.isCall == isExercised is equivalent to the one it replaces, but is cheaper:

Lines: PuttyV2.sol#L495

Gas saved: ~30-50 units

@@ -490,7 +490,7 @@ contract PuttyV2 is PuttyV2Nft, EIP712("Putty", "2.0"), ERC721TokenReceiver, Own
         /* ~~~ INTERACTIONS ~~~ */

         // transfer strike to owner if put is expired or call is exercised
-        if ((order.isCall && isExercised) || (!order.isCall && !isExercised)) {
+        if (order.isCall == isExercised) {
             // send the fee to the admin/DAO if fee is greater than 0%
             uint256 feeAmount = 0;
             if (fee > 0) {
 ╭──────────────────────────────────┬─────────────────┬────────┬────────┬────────┬─────────╮
 │ src/PuttyV2.sol:PuttyV2 contract ┆                 ┆        ┆        ┆        ┆         │
 ╞══════════════════════════════════╪═════════════════╪════════╪════════╪════════╪═════════╡
 │ Deployment Cost                  ┆ Deployment Size ┆        ┆        ┆        ┆         │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
-│ 4555400                          ┆ 24134           ┆        ┆        ┆        ┆         │
+│ 4550191                          ┆ 24108           ┆        ┆        ┆        ┆         │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
 │ Function Name                    ┆ min             ┆ avg    ┆ median ┆ max    ┆ # calls │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
@@ -128,7 +128,7 @@ Test result: ok. 2 passed; 0 failed; finished in 15.31s
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
-│ withdraw                         ┆ 2994            ┆ 25160  ┆ 21378  ┆ 67082  ┆ 10      │
+│ withdraw                         ┆ 2994            ┆ 25130  ┆ 21341  ┆ 67032  ┆ 10      │
 ╰──────────────────────────────────┴─────────────────┴────────┴────────┴────────┴─────────╯
  1. The first check of isExercised is much cheaper than the timestamp check. So in case isExercised is true, this is going to save some gas.

(In case that it's false it might increase the cost by ~4 units, but considering that both scenarios are about equally likely to happen, we should prefer saving gas on one scenario at the expense of a small increase of the other.)

Lines: PuttyV2.sol#L495

Gas saved: up to 70 units

         // check long position has either been exercised or is expired
-        require(block.timestamp > positionExpirations[longPositionId] || isExercised, "Must be exercised or expired");
+        require(isExercised || block.timestamp > positionExpirations[longPositionId], "Must be exercised or expired");

         /* ~~~ EFFECTS ~~~ */
 ╭──────────────────────────────────┬─────────────────┬────────┬────────┬────────┬─────────╮
 │ src/PuttyV2.sol:PuttyV2 contract ┆                 ┆        ┆        ┆        ┆         │
 ╞══════════════════════════════════╪═════════════════╪════════╪════════╪════════╪═════════╡
 │ Deployment Cost                  ┆ Deployment Size ┆        ┆        ┆        ┆         │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
-│ 4550191                          ┆ 24108           ┆        ┆        ┆        ┆         │
+│ 4550591                          ┆ 24110           ┆        ┆        ┆        ┆         │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
 │ Function Name                    ┆ min             ┆ avg    ┆ median ┆ max    ┆ # calls │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
@@ -128,7 +128,7 @@ Test result: ok. 7 passed; 0 failed; finished in 14.63s
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
-│ withdraw                         ┆ 2994            ┆ 25130  ┆ 21341  ┆ 67032  ┆ 10      │
+│ withdraw                         ┆ 2994            ┆ 25101  ┆ 21270  ┆ 67033  ┆ 10      │
 ╰──────────────────────────────────┴─────────────────┴────────┴────────┴────────┴─────────╯

'Compressing' a variable used only once

Gas saved: 10-13 units

Since the oppositeOrderHash variable is used only once, we can insert it into the place where it used and to save some gas.

         // create opposite long/short position for taker
-        bytes32 oppositeOrderHash = hashOppositeOrder(order);
-        positionId = uint256(oppositeOrderHash);
+        positionId = uint256(hashOppositeOrder(order));
         _mint(msg.sender, positionId);
╭──────────────────────────────────┬─────────────────┬────────┬────────┬────────┬─────────╮
 │ src/PuttyV2.sol:PuttyV2 contract ┆                 ┆        ┆        ┆        ┆         │
 ╞══════════════════════════════════╪═════════════════╪════════╪════════╪════════╪═════════╡
 │ Deployment Cost                  ┆ Deployment Size ┆        ┆        ┆        ┆         │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
-│ 4550591                          ┆ 24110           ┆        ┆        ┆        ┆         │
+│ 4549391                          ┆ 24104           ┆        ┆        ┆        ┆         │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
 │ Function Name                    ┆ min             ┆ avg    ┆ median ┆ max    ┆ # calls │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
@@ -108,7 +108,7 @@ Test result: ok. 2 passed; 0 failed; finished in 14.25s
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
-│ fillOrder                        ┆ 9916            ┆ 103411 ┆ 111747 ┆ 199092 ┆ 51      │
+│ fillOrder                        ┆ 9916            ┆ 103401 ┆ 111734 ┆ 199079 ┆ 51      │

Loop index increase + remove default init

Gas saved: Depends on arrays size, up to 400 units in tests

Lines:

This is a few common optimization:

@@ -491,7 +491,7 @@ contract PuttyV2 is PuttyV2Nft, EIP712("Putty", "2.0"), ERC721TokenReceiver, Own
         // transfer strike to owner if put is expired or call is exercised
         if (order.isCall == isExercised) {
             // send the fee to the admin/DAO if fee is greater than 0%
-            uint256 feeAmount = 0;
+            uint256 feeAmount;
             if (fee > 0) {
                 feeAmount = (order.strike * fee) / 1000;
                 ERC20(order.baseAsset).safeTransfer(owner(), feeAmount);
@@ -549,8 +549,9 @@ contract PuttyV2 is PuttyV2Nft, EIP712("Putty", "2.0"), ERC721TokenReceiver, Own

         positionIds = new uint256[](orders.length);

-        for (uint256 i = 0; i < orders.length; i++) {
+        for (uint256 i; i < orders.length; ) {
             positionIds[i] = fillOrder(orders[i], signatures[i], floorAssetTokenIds[i]);
+            unchecked{++i;}
         }
     }

@@ -587,7 +588,7 @@ contract PuttyV2 is PuttyV2Nft, EIP712("Putty", "2.0"), ERC721TokenReceiver, Own
         @param from Who to transfer the erc20 assets from.
      */
     function _transferERC20sIn(ERC20Asset[] memory assets, address from) internal {
-        for (uint256 i = 0; i < assets.length; i++) {
+        for (uint256 i; i < assets.length; ) {
             address token = assets[i].token;
             uint256 tokenAmount = assets[i].tokenAmount;

@@ -595,6 +596,7 @@ contract PuttyV2 is PuttyV2Nft, EIP712("Putty", "2.0"), ERC721TokenReceiver, Own
             require(tokenAmount > 0, "ERC20: Amount too small");

             ERC20(token).safeTransferFrom(from, address(this), tokenAmount);
+            unchecked {++i;}
         }
     }

@@ -604,8 +606,9 @@ contract PuttyV2 is PuttyV2Nft, EIP712("Putty", "2.0"), ERC721TokenReceiver, Own
         @param from Who to transfer the erc721 assets from.
      */
     function _transferERC721sIn(ERC721Asset[] memory assets, address from) internal {
-        for (uint256 i = 0; i < assets.length; i++) {
+        for (uint256 i; i < assets.length; ) {
             ERC721(assets[i].token).safeTransferFrom(from, address(this), assets[i].tokenId);
+            unchecked {++i;}
         }
     }

@@ -620,8 +623,9 @@ contract PuttyV2 is PuttyV2Nft, EIP712("Putty", "2.0"), ERC721TokenReceiver, Own
         uint256[] memory floorTokenIds,
         address from
     ) internal {
-        for (uint256 i = 0; i < floorTokens.length; i++) {
+        for (uint256 i; i < floorTokens.length; ) {
             ERC721(floorTokens[i]).safeTransferFrom(from, address(this), floorTokenIds[i]);
+            unchecked {++i;}
         }
     }

@@ -630,8 +634,11 @@ contract PuttyV2 is PuttyV2Nft, EIP712("Putty", "2.0"), ERC721TokenReceiver, Own
         @param assets The erc20 tokens and amounts to send.
      */
     function _transferERC20sOut(ERC20Asset[] memory assets) internal {
-        for (uint256 i = 0; i < assets.length; i++) {
+        for (uint256 i; i < assets.length; ) {
             ERC20(assets[i].token).safeTransfer(msg.sender, assets[i].tokenAmount);
+            unchecked {
+                ++i;
+            }
         }
     }

@@ -640,8 +647,11 @@ contract PuttyV2 is PuttyV2Nft, EIP712("Putty", "2.0"), ERC721TokenReceiver, Own
         @param assets The erc721 tokens and token ids to send.
      */
     function _transferERC721sOut(ERC721Asset[] memory assets) internal {
-        for (uint256 i = 0; i < assets.length; i++) {
+        for (uint256 i; i < assets.length; ) {
             ERC721(assets[i].token).safeTransferFrom(address(this), msg.sender, assets[i].tokenId);
+            unchecked {
+                ++i;
+            }
         }
     }

@@ -651,8 +661,11 @@ contract PuttyV2 is PuttyV2Nft, EIP712("Putty", "2.0"), ERC721TokenReceiver, Own
         @param floorTokenIds The token id of each floor token.
      */
     function _transferFloorsOut(address[] memory floorTokens, uint256[] memory floorTokenIds) internal {
-        for (uint256 i = 0; i < floorTokens.length; i++) {
+        for (uint256 i; i < floorTokens.length; ) {
             ERC721(floorTokens[i]).safeTransferFrom(address(this), msg.sender, floorTokenIds[i]);
+            unchecked {
+                ++i;
+            }
         }
     }

@@ -663,8 +676,9 @@ contract PuttyV2 is PuttyV2Nft, EIP712("Putty", "2.0"), ERC721TokenReceiver, Own
         @return If it exists in the whitelist or not.
      */
     function isWhitelisted(address[] memory whitelist, address target) internal pure returns (bool) {
-        for (uint256 i = 0; i < whitelist.length; i++) {
+        for (uint256 i; i < whitelist.length; ) {
             if (target == whitelist[i]) return true;
+            unchecked{++i;}
         }

         return false;
@@ -722,11 +736,12 @@ contract PuttyV2 is PuttyV2Nft, EIP712("Putty", "2.0"), ERC721TokenReceiver, Own
         @return encoded The eip-712 encoded array of erc20 assets.
      */
     function encodeERC20Assets(ERC20Asset[] memory arr) internal pure returns (bytes memory encoded) {
-        for (uint256 i = 0; i < arr.length; i++) {
+        for (uint256 i; i < arr.length; ) {
             encoded = abi.encodePacked(
                 encoded,
                 keccak256(abi.encode(ERC20ASSET_TYPE_HASH, arr[i].token, arr[i].tokenAmount))
             );
+            unchecked{++i;}
         }
     }

@@ -736,11 +751,12 @@ contract PuttyV2 is PuttyV2Nft, EIP712("Putty", "2.0"), ERC721TokenReceiver, Own
         @return encoded The eip-712 encoded array of erc721 assets.
      */
     function encodeERC721Assets(ERC721Asset[] memory arr) internal pure returns (bytes memory encoded) {
-        for (uint256 i = 0; i < arr.length; i++) {
+        for (uint256 i; i < arr.length; ) {
             encoded = abi.encodePacked(
                 encoded,
                 keccak256(abi.encode(ERC721ASSET_TYPE_HASH, arr[i].token, arr[i].tokenId))
             );
+            unchecked{++i;}
         }
     }
 ╭──────────────────────────────────┬─────────────────┬────────┬────────┬────────┬─────────╮
 │ src/PuttyV2.sol:PuttyV2 contract ┆                 ┆        ┆        ┆        ┆         │
 ╞══════════════════════════════════╪═════════════════╪════════╪════════╪════════╪═════════╡
 │ Deployment Cost                  ┆ Deployment Size ┆        ┆        ┆        ┆         │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
-│ 4549391                          ┆ 24104           ┆        ┆        ┆        ┆         │
+│ 4542782                          ┆ 24071           ┆        ┆        ┆        ┆         │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
 │ Function Name                    ┆ min             ┆ avg    ┆ median ┆ max    ┆ # calls │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
@@ -102,15 +102,15 @@ Test result: ok. 2 passed; 0 failed; finished in 14.17s
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
 │ cancelledOrders                  ┆ 528             ┆ 528    ┆ 528    ┆ 528    ┆ 1       │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
-│ exercise                         ┆ 5701            ┆ 55063  ┆ 67944  ┆ 129476 ┆ 18      │
+│ exercise                         ┆ 5701            ┆ 55004  ┆ 67944  ┆ 129101 ┆ 18      │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
-│ fillOrder                        ┆ 9916            ┆ 103401 ┆ 111734 ┆ 199079 ┆ 51      │
+│ fillOrder                        ┆ 9916            ┆ 103354 ┆ 111734 ┆ 198953 ┆ 51      │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
-│ hashOrder                        ┆ 5168            ┆ 5443   ┆ 5168   ┆ 7712   ┆ 62      │
+│ hashOrder                        ┆ 5168            ┆ 5430   ┆ 5168   ┆ 7576   ┆ 62      │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
@@ -128,7 +128,7 @@ Test result: ok. 2 passed; 0 failed; finished in 14.17s
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
-│ withdraw                         ┆ 2994            ┆ 25101  ┆ 21270  ┆ 67033  ┆ 10      │
+│ withdraw                         ┆ 2994            ┆ 25027  ┆ 21270  ┆ 66660  ┆ 10      │
 ╰──────────────────────────────────┴─────────────────┴────────┴────────┴────────┴─────────╯

Removing duplicate ownership check

Gas saved: ~200-350 per function

Lines:

At the functions exercise and withdraw there's an ownership check done at the beginning, but this one is unnecessary because ownership is checked while transferring the NFT to the 0xdead address.

Removing this is going to increase gas cost for reverted calls (since the check is done later. This is probably the reason the avg for withdraw has increased), but considering that reverted calls are more rare than normal calls I think it's worth it. This is also going to change some revert errors ('WRONG FROM' instead of 'NOT MINTED' etc.). But I guess/hope this is not a problem for the sponsor.

         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");

@@ -468,8 +465,6 @@ contract PuttyV2 is PuttyV2Nft, EIP712("Putty", "2.0"), ERC721TokenReceiver, Own

         bytes32 orderHash = hashOrder(order);

-        // check msg.sender owns the position
-        require(ownerOf(uint256(orderHash)) == msg.sender, "Not owner");

         uint256 longPositionId = uint256(hashOppositeOrder(order));
         bool isExercised = exercisedPositions[longPositionId];
 ╭──────────────────────────────────┬─────────────────┬────────┬────────┬────────┬─────────╮
 │ src/PuttyV2.sol:PuttyV2 contract ┆                 ┆        ┆        ┆        ┆         │
 ╞══════════════════════════════════╪═════════════════╪════════╪════════╪════════╪═════════╡
 │ Deployment Cost                  ┆ Deployment Size ┆        ┆        ┆        ┆         │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
-│ 4542782                          ┆ 24071           ┆        ┆        ┆        ┆         │
+│ 4488713                          ┆ 23801           ┆        ┆        ┆        ┆         │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
 │ Function Name                    ┆ min             ┆ avg    ┆ median ┆ max    ┆ # calls │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
@@ -102,7 +102,7 @@ Test result: . 7 passed; 0 failed; finished in 15.29s
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
 │ cancelledOrders                  ┆ 528             ┆ 528    ┆ 528    ┆ 528    ┆ 1       │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
-│ exercise                         ┆ 5701            ┆ 55004  ┆ 67944  ┆ 129101 ┆ 18      │
+│ exercise                         ┆ 5478            ┆ 54661  ┆ 67692  ┆ 128900 ┆ 18      │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
 │ exercisedPositions               ┆ 506             ┆ 506    ┆ 506    ┆ 506    ┆ 1       │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
@@ -128,7 +128,7 @@ Test result: . 7 passed; 0 failed; finished in 15.29s
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
 │ transferFrom                     ┆ 5285            ┆ 5285   ┆ 5285   ┆ 5285   ┆ 1       │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
-│ withdraw                         ┆ 2994            ┆ 25027  ┆ 21270  ┆ 66660  ┆ 10      │
+│ withdraw                         ┆ 2994            ┆ 25990  ┆ 21068  ┆ 66459  ┆ 10      │
 ╰──────────────────────────────────┴─────────────────┴────────┴────────┴────────┴─────────╯

The following tests are failing: 3 of them is because the order of checks have changed, and the other is because the revert msg has changed.

Failed tests:
[FAIL. Reason: Can only exercise long positions] testItCannotExercisePositionThatDoesNotExist() (gas: 30915)
[FAIL. Reason: Can only exercise long positions] testItCannotExercisePositionYouDoNotOwn() (gas: 160124)
[FAIL. Reason: WRONG_FROM] testItCannotWithdrawPositionThatDoesNotExist() (gas: 37344)
[FAIL. Reason: Must be exercised or expired] testItCannotWithdrawPositionThatYouDontOwn() (gas: 160961)

Replace string error msgs with custom errors

This is another common one, all of the requires can be replaced with an if-revert with a custom error. That's going to save some deployment size and some gas on reverted calls.

List of requires:

PuttyV2.sol#L214 PuttyV2.sol#L241 PuttyV2.sol#L278 PuttyV2.sol#L281 PuttyV2.sol#L284 PuttyV2.sol#L287 PuttyV2.sol#L290 PuttyV2.sol#L293 PuttyV2.sol#L297 PuttyV2.sol#L298 PuttyV2.sol#L329 PuttyV2.sol#L353 PuttyV2.sol#L395 PuttyV2.sol#L398 PuttyV2.sol#L401 PuttyV2.sol#L405 PuttyV2.sol#L406 PuttyV2.sol#L429 PuttyV2.sol#L470 PuttyV2.sol#L475 PuttyV2.sol#L481 PuttyV2.sol#L527 PuttyV2.sol#L551 PuttyV2.sol#L552 PuttyV2.sol#L598 PuttyV2.sol#L599 PuttyV2.sol#L765

PuttyV2Nft.sol#L12 PuttyV2Nft.sol#L13 PuttyV2Nft.sol#L26 PuttyV2Nft.sol#L27 PuttyV2Nft.sol#L28 PuttyV2Nft.sol#L41

Detailed comparison of gas before and after

When comparing the gas report before and after doing all the changes (except the custom errors), this is the results:

 ╭──────────────────────────────────┬─────────────────┬────────┬────────┬────────┬─────────╮
 │ src/PuttyV2.sol:PuttyV2 contract ┆                 ┆        ┆        ┆        ┆         │
 ╞══════════════════════════════════╪═════════════════╪════════╪════════╪════════╪═════════╡
 │ Deployment Cost                  ┆ Deployment Size ┆        ┆        ┆        ┆         │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
-│ 4774098                          ┆ 25226           ┆        ┆        ┆        ┆         │
+│ 4488713                          ┆ 23801           ┆        ┆        ┆        ┆         │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
 │ Function Name                    ┆ min             ┆ avg    ┆ median ┆ max    ┆ # calls │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
-│ baseURI                          ┆ 1321            ┆ 1321   ┆ 1321   ┆ 1321   ┆ 1       │
+│ baseURI                          ┆ 1299            ┆ 1299   ┆ 1299   ┆ 1299   ┆ 1       │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
-│ cancel                           ┆ 3075            ┆ 26509  ┆ 34321  ┆ 34321  ┆ 4       │
+│ cancel                           ┆ 2995            ┆ 26429  ┆ 34241  ┆ 34241  ┆ 4       │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
-│ cancelledOrders                  ┆ 550             ┆ 550    ┆ 550    ┆ 550    ┆ 1       │
+│ cancelledOrders                  ┆ 528             ┆ 528    ┆ 528    ┆ 528    ┆ 1       │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
-│ exercise                         ┆ 5759            ┆ 55920  ┆ 68002  ┆ 133534 ┆ 18      │
+│ exercise                         ┆ 5478            ┆ 54661  ┆ 67692  ┆ 128900 ┆ 18      │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
-│ exercisedPositions               ┆ 528             ┆ 528    ┆ 528    ┆ 528    ┆ 1       │
+│ exercisedPositions               ┆ 506             ┆ 506    ┆ 506    ┆ 506    ┆ 1       │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
-│ fee                              ┆ 406             ┆ 406    ┆ 406    ┆ 406    ┆ 1       │
+│ fee                              ┆ 384             ┆ 384    ┆ 384    ┆ 384    ┆ 1       │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
-│ fillOrder                        ┆ 10014           ┆ 106845 ┆ 115883 ┆ 203777 ┆ 51      │
+│ fillOrder                        ┆ 9916            ┆ 103354 ┆ 111734 ┆ 198953 ┆ 51      │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
-│ hashOrder                        ┆ 5206            ┆ 5484   ┆ 5206   ┆ 7782   ┆ 62      │
+│ hashOrder                        ┆ 5168            ┆ 5430   ┆ 5168   ┆ 7576   ┆ 62      │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
-│ onERC721Received                 ┆ 815             ┆ 815    ┆ 815    ┆ 815    ┆ 14      │
+│ onERC721Received                 ┆ 783             ┆ 783    ┆ 783    ┆ 783    ┆ 14      │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
-│ ownerOf                          ┆ 554             ┆ 554    ┆ 554    ┆ 554    ┆ 4       │
+│ ownerOf                          ┆ 555             ┆ 555    ┆ 555    ┆ 555    ┆ 4       │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
 │ positionExpirations              ┆ 526             ┆ 526    ┆ 526    ┆ 526    ┆ 1       │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
 │ positionFloorAssetTokenIds       ┆ 734             ┆ 734    ┆ 734    ┆ 734    ┆ 3       │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
-│ setBaseURI                       ┆ 2853            ┆ 9153   ┆ 12303  ┆ 12303  ┆ 3       │
+│ setBaseURI                       ┆ 2897            ┆ 9197   ┆ 12347  ┆ 12347  ┆ 3       │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
-│ setFee                           ┆ 2498            ┆ 14052  ┆ 14057  ┆ 25597  ┆ 4       │
+│ setFee                           ┆ 2518            ┆ 14072  ┆ 14077  ┆ 25617  ┆ 4       │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
-│ tokenURI                         ┆ 2606            ┆ 24547  ┆ 24547  ┆ 46489  ┆ 2       │
+│ tokenURI                         ┆ 2584            ┆ 24525  ┆ 24525  ┆ 46467  ┆ 2       │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
-│ transferFrom                     ┆ 5255            ┆ 5255   ┆ 5255   ┆ 5255   ┆ 1       │
+│ transferFrom                     ┆ 5285            ┆ 5285   ┆ 5285   ┆ 5285   ┆ 1       │
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
-│ withdraw                         ┆ 3075            ┆ 27840  ┆ 24545  ┆ 71168  ┆ 10      │
+│ withdraw                         ┆ 2994            ┆ 25990  ┆ 21068  ┆ 66459  ┆ 10      │
 ╰──────────────────────────────────┴─────────────────┴────────┴────────┴────────┴─────────╯
HickupHH3 commented 2 years ago

High quality gas opt! Very well documented of gas estimations before and after each change.