OpenSea Seaport contest - 0x29A's results

A marketplace contract for safely and efficiently creating and fulfilling orders for ERC721 and ERC1155 items.

General Information

Platform: Code4rena

Start Date: 20/05/2022

Pot Size: $1,000,000 USDC

Total HM: 4

Participants: 59

Period: 14 days

Judge: leastwood

Id: 128

League: ETH

OpenSea

Findings Distribution

Researcher Performance

Rank: 39/59

Findings: 1

Award: $485.02

🌟 Selected for report: 0

🚀 Solo Findings: 0

Gas optimizations

Cache arrays length on loops

Caching the array length in memory can yield significant gas savings when the array length is high.

On file contracts/lib/OrderCombiner.sol you can cache the offers length and the consideration length;

diff --git a/contracts/lib/OrderCombiner.sol b/contracts/lib/OrderCombiner.sol
index babef49..6a5dd59 100644
--- a/contracts/lib/OrderCombiner.sol
+++ b/contracts/lib/OrderCombiner.sol
@@ -242,9 +242,10 @@ contract OrderCombiner is OrderFulfiller, FulfillmentApplier {
 
                 // Retrieve array of offer items for the order in question.
                 OfferItem[] memory offer = advancedOrder.parameters.offer;
-
+                // cache offer length
+                uint256 offerLength = offer.length;
                 // Iterate over each offer item on the order.
-                for (uint256 j = 0; j < offer.length; ++j) {
+                for (uint256 j = 0; j < offerLength; ++j) {
                     // Retrieve the offer item.
                     OfferItem memory offerItem = offer[j];
 
@@ -594,8 +595,9 @@ contract OrderCombiner is OrderFulfiller, FulfillmentApplier {
                     advancedOrder.parameters.consideration
                 );
 
+                uint256 considerationLength = consideration.length;
                 // Iterate over each consideration item to ensure it is met.
-                for (uint256 j = 0; j < consideration.length; ++j) {
+                for (uint256 j = 0; j < considerationLength; ++j) {
                     // Retrieve remaining amount on the consideration item.
                     uint256 unmetAmount = consideration[j].startAmount;

On file contracts/lib/OrderFulfiller.sol you can cache the offers length and the consideration length;

diff --git a/contracts/lib/OrderFulfiller.sol b/contracts/lib/OrderFulfiller.sol
index 0060ba9..3878254 100644
--- a/contracts/lib/OrderFulfiller.sol
+++ b/contracts/lib/OrderFulfiller.sol
@@ -214,7 +214,8 @@ contract OrderFulfiller is
             }
 
             // Iterate over each offer on the order.
-            for (uint256 i = 0; i < orderParameters.offer.length; ) {
+            uint256 totalOrderParameters = orderParameters.offer.length;
+            for (uint256 i = 0; i < totalOrderParameters; ) {
                 // Retrieve the offer item.
                 OfferItem memory offerItem = orderParameters.offer[i];

Use pre increment and pre decrement instead of post increment and post decrement

++i costs less gas compared to i++ or i += 1 for unsigned integers. This is because the pre-increment operation is cheaper. Source

diff --git a/contracts/lib/OrderCombiner.sol b/contracts/lib/OrderCombiner.sol
index babef49..915ed2f 100644
--- a/contracts/lib/OrderCombiner.sol
+++ b/contracts/lib/OrderCombiner.sol
@@ -226,7 +226,7 @@ contract OrderCombiner is OrderFulfiller, FulfillmentApplier {
                 orderHashes[i] = orderHash;
 
                 // Decrement the number of fulfilled orders.
-                maximumFulfilled--;
+                --maximumFulfilled;
 
                 // Place the start time for the order on the stack.
                 uint256 startTime = advancedOrder.parameters.startTime;
@@ -487,7 +487,7 @@ contract OrderCombiner is OrderFulfiller, FulfillmentApplier {
                 // If offerer and recipient on the execution are the same...
                 if (execution.item.recipient == execution.offerer) {
                     // increment total filtered executions.
-                    totalFilteredExecutions += 1;
+                    ++totalFilteredExecutions;
                 } else {
                     // Otherwise, assign the execution to the executions array.
                     executions[i - totalFilteredExecutions] = execution;
@@ -512,7 +512,7 @@ contract OrderCombiner is OrderFulfiller, FulfillmentApplier {
                 // If offerer and recipient on the execution are the same...
                 if (execution.item.recipient == execution.offerer) {
                     // increment total filtered executions.
-                    totalFilteredExecutions += 1;
+                    ++totalFilteredExecutions;
                 } else {
                     // Otherwise, assign the execution to the executions array.
                     executions[
@@ -765,7 +765,7 @@ contract OrderCombiner is OrderFulfiller, FulfillmentApplier {
                 // If offerer and recipient on the execution are the same...
                 if (execution.item.recipient == execution.offerer) {
                     // increment total filtered executions.
-                    totalFilteredExecutions += 1;
+                    ++totalFilteredExecutions;
                 } else {
                     // Otherwise, assign the execution to the executions array.
                     executions[i - totalFilteredExecutions] = execution;

Assembly casting of address/bytes32 to uint256

function toUint256(bytes32 value) internal pure returns (uint256 result) {
    assembly {
        result := value
    }
}

function toUint256(address value) internal pure returns (uint256 result) {
    assembly {
        result := value
    }
}

In ConduitController.sol

  • L63(9 gas):
- if (address(uint160(bytes20(conduitKey))) != msg.sender) {
+ if (toUint256(conduitKey) != toUint256(msg.sender)) {
  • L197(18 gas):
- if (newPotentialOwner == address(0)) {
+ if (toUint256(newPotentialOwner) == 0) {
  • L237(13 gas):
- if (msg.sender != _conduits[conduit].potentialOwner) {
+ if (toUint256(msg.sender) != toUint256(_conduits[conduit].potentialOwner)) {
  • L321-L334(13 gas):
conduit = address(
    uint160(
        uint256(
            keccak256(
                abi.encodePacked(
                    bytes1(0xff),
                    address(this),
                    conduitKey,
                    _CONDUIT_CREATION_CODE_HASH
                )
            )
        )
    )
);

- if (msg.sender != _conduits[conduit].potentialOwner) {
+ if (toUint256(msg.sender) != toUint256(_conduits[conduit].potentialOwner)) {
  • L485(13 gas):
- if (msg.sender != _conduits[conduit].owner) {
+ if (toUint256(msg.sender) != toUint256(_conduits[conduit].owner)) {

In OrderValidator.sol, L280(18-36 gas):

- if (msg.sender != offerer && msg.sender != zone) {
+ if (toUint256(msg.sender) != toUint256(offerer) && toUint256(msg.sender) != toUint256(zone)) {

In SignatureVerification.sol, L97(18 gas):

- } else if (recoveredSigner != signer) {
+ } else if (toUint256(recoveredSigner) != toUint256(signer)) {

In ZoneInteraction.sol

  • L48-L49, (18-36 gas):
- msg.sender != zone &&
- msg.sender != offerer
+ toUint256(msg.sender) != toUint256(zone) &&
+ toUint256(msg.sender) != toUint256(offerer)
  • L117-L118, (18-36 gas):
- msg.sender != zone &&
- msg.sender != offerer
+ toUint256(msg.sender) != toUint256(zone) &&
+ toUint256(msg.sender) != toUint256(offerer)

In Verifiers.sol, L76(18 gas):

- if (offerer == msg.sender) {
+ if (toUint256(offerer) == toUint256(msg.sender)) {

#0 - HardlyDifficult

2022-06-26T15:41:17Z

Cache arrays length on loops Use pre increment and pre decrement instead of post increment and post decrement

These should offer some savings.

Assembly casting of address/bytes32 to uint256

Although these recommendations are not great for code readability, they do offer savings and follow the general pattern in this project of using Yul to optimize as much as possible.

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