Foundation Drop contest - ajtra's results

Foundation is a web3 destination.

General Information

Platform: Code4rena

Start Date: 11/08/2022

Pot Size: $40,000 USDC

Total HM: 8

Participants: 108

Period: 4 days

Judge: hickuphh3

Total Solo HM: 2

Id: 152

League: ETH

Foundation

Findings Distribution

Researcher Performance

Rank: 87/108

Findings: 1

Award: $22.31

🌟 Selected for report: 0

🚀 Solo Findings: 0

Index

  1. Post-increment/decrement cost more gas then pre-increment/decrement
  2. Call to KECCAK256 should use IMMUTABLE rather than constant
  3. Array length should not be looked up in every loop of a for-loop
  4. Operatos <= or >= cost more gas than operators < or >
  5. != 0 is cheaper than > 0
  6. Variable1 = Variable1 + (-) Variable2 is cheaper in gas cost than variable1 += (-=) variable2.
  7. Use bytes32 instead of string
  8. Require / Revert strings longer than 32 bytes cost extra gas
  9. Use custom errors rather than REVERT()/REQUIRE() strings to save deployment gas
  10. Using private rather than public for constants, saves gas
  11. Initialize variables with default values are not needed
  12. Using bools for storage incurs overhead
  13. Uncheked arithmetic when it is not possible for them to overflow
  14. Refactoring the code of NFTCollection._baseURI to save gas
  15. Refactoring the code of BytesLibrary.startsWith to save gas

Details

1. Post-increment/decrement cost more gas then pre-increment/decrement

Description

++I (--I) cost less gas than I++ (I--)

Lines in the code

NFTCollectionFactory.sol#L207 NFTCollectionFactory.sol#L231

2. Call to KECCAK256 should use IMMUTABLE rather than constant

Description

Expressions for constant values such as a call to KECCAK256 should use IMMUTABLE rather than constant

Lines in the code

MinterRole.sol#L19 NFTDropMarketFixedPriceSale.sol#L70

3. Array length should not be looked up in every loop of a for-loop

Description

Storage array length checks incur an extra Gwarmaccess (100 gas) per loop. Store the array length in a variable and use it in the for loop helps to save gas

Lines in the code

MarketFees.sol#L126 MarketFees.sol#L198 MarketFees.sol#L484 MarketFees.sol#L503

4. Operatos <= or >= cost more gas than operators < or >

Description

Change all <= / >= operators for < / > and remember to increse / decrese in consecuence to maintain the logic (example, a <= b for a < b + 1)

Lines in the code

NFTCollection.sol#L268 NFTDropCollection.sol#L179 NFTDropCollection.sol#L181 NFTDropCollection.sol#L275 MarketFees.sol#L526 SequentialMintCollection.sol#L89 NFTDropMarketFixedPriceSale.sol#L240

5. != 0 is cheaper than > 0

Description

Replace all > 0 for != 0

Lines in the code

NFTDropCollection.sol#L88 NFTDropCollection.sol#L130 NFTDropCollection.sol#L131 MarketFees.sol#L244

6. Variable1 = Variable1 + (-) Variable2 is cheaper in gas cost than variable1 += (-=) variable2.

Description

Lines in the code

MarketFees.sol#L134 MarketFees.sol#L150 MarketFees.sol#L199 MarketFees.sol#L490 MarketFees.sol#L505 MarketFees.sol#L527

7. Use bytes32 instead of string

Description

Use bytes32 instead of string when it's possible to save some gas. In this project, it's probably that the symbol is not going to be bigger than 32 bytes so it's possible to change it from string to bytes32 to save some gas.

Lines in the code

NFTCollection.sol#L108 NFTCollectionFactory.sol#L138 NFTCollectionFactory.sol#L163 NFTCollectionFactory.sol#L259 NFTCollectionFactory.sol#L288 NFTCollectionFactory.sol#L326 NFTCollectionFactory.sol#L365 NFTCollectionFactory.sol#L388 NFTDropCollection.sol#L123

8. Require / Revert strings longer than 32 bytes cost extra gas

Description

Each extra memory word of bytes past the original 32 incurs an MSTORE which costs 3 gas

Lines in the code

NFTCollection.sol#L158 NFTCollection.sol#L263 NFTCollection.sol#L264 NFTCollection.sol#L268 NFTCollection.sol#L327 NFTCollectionFactory.sol#L173 NFTCollectionFactory.sol#L182 NFTCollectionFactory.sol#L203 NFTCollectionFactory.sol#L227 NFTCollectionFactory.sol#L262 NFTDropCollection.sol#L88 NFTDropCollection.sol#L93 NFTDropCollection.sol#L130 NFTDropCollection.sol#L131 NFTDropCollection.sol#L172 NFTDropCollection.sol#L179 NFTDropCollection.sol#L238 AddressLibrary.sol#L31 ContractFactory.sol#L22 ContractFactory.sol#L31 MinterRole.sol#L22 SequentialMintCollection.sol#L58 SequentialMintCollection.sol#L63 SequentialMintCollection.sol#L74 SequentialMintCollection.sol#L87 SequentialMintCollection.sol#L88 SequentialMintCollection.sol#L89 AdminRole.sol#L19

9. Use custom errors rather than REVERT()/REQUIRE() strings to save deployment gas

Description

Custom errors are available from solidity version 0.8.4. Custom errors save ~50 gas each time they’re hitby avoiding having to allocate and store the revert string. Not defining the strings also save deployment gas.

Lines in the code

NFTCollection.sol#L158 NFTCollection.sol#L263 NFTCollection.sol#L264 NFTCollection.sol#L268 NFTCollection.sol#L327 NFTCollectionFactory.sol#L173 NFTCollectionFactory.sol#L182 NFTCollectionFactory.sol#L203 NFTCollectionFactory.sol#L227 NFTCollectionFactory.sol#L262 NFTDropCollection.sol#L88 NFTDropCollection.sol#L93 NFTDropCollection.sol#L130 NFTDropCollection.sol#L131 NFTDropCollection.sol#L172 NFTDropCollection.sol#L179 NFTDropCollection.sol#L238 AddressLibrary.sol#L31 ContractFactory.sol#L22 ContractFactory.sol#L31 MinterRole.sol#L22 SequentialMintCollection.sol#L58 SequentialMintCollection.sol#L63 SequentialMintCollection.sol#L74 SequentialMintCollection.sol#L87 SequentialMintCollection.sol#L88 SequentialMintCollection.sol#L89 AdminRole.sol#L19

10. Using private rather than public for constants, saves gas

Description

If needed, the value can be read from the verified contract source code. Savings are due to the compiler not having to create non-payable getter functions for deployment calldata, and not adding another entry to the method ID table

Lines in the code

MinterRole.sol#L19 NFTDropMarketFixedPriceSale.sol#L70

11. Initialize variables with default values are not needed

Description

If a variable is not set/initialized, it is assumed to have the default value (0 for uint, false for bool, address(0) for address,...). Explicitly initializing it with its default value is an anti-pattern and wastes gas.

Lines in the code

MarketFees.sol#L126 MarketFees.sol#L198 MarketFees.sol#L484 BytesLibrary.sol#L25 BytesLibrary.sol#L44

12. Using bools for storage incurs overhead

Description

Use uint256(1) and uint256(2) for true/false to avoid a Gwarmaccess (100 gas), and to avoid Gsset (20000 gas) when changing from 'false' to 'true', after having been 'true' in the past.

Lines in the code

NFTCollection.sol#L53 MarketFees.sol#L61 NFTDropMarketFixedPriceSale.sol#L232

13. Uncheked arithmetic when it is not possible for them to overflow

Description

The default “checked” behavior costs more gas when adding/diving/multiplying, because under-the-hood those checks are implemented as a series of opcodes that, prior to performing the actual arithmetic, check for under/overflow and revert if it is detected. if it can statically be determined there is no possible way for your arithmetic to under/overflow (such as a condition in an if statement), surrounding the arithmetic in an unchecked block will save gas.

For all for-loops in the code it is possible to change as the following example.

for (uint256 i;i < X;){ -- code -- unchecked { ++i; } }

Lines in the code

MarketFees.sol#L198

14. Refactoring the code of NFTCollection._baseURI to save gas

Description

In the case of _baseURI it's possible to refactor the code to save gas. 180 of gas is saved with the following change. (-) Remove the line (+) Add the line

  function _baseURI() internal view override returns (string memory) {
-   if (bytes(baseURI_).length != 0) {
-    return baseURI_;
-   }
-   return "ipfs://";
+	return (bytes(baseURI_).length != 0)?baseURI_:"ipfs://";
  }

Lines in the code

NFTCollection.sol#L332-L337

15. Refactoring the code of BytesLibrary.startsWith to save gas

Description

In the case of startsWith it's possible to refactor the code to save gas as the following way. 222 gas will be saved with this change. (-) Remove the line (+) Add the line

  function startsWith(bytes memory callData, bytes4 functionSig) internal pure returns (bool) {
    // A signature is 4 bytes long
-   if (callData.length < 4) {
-     return false;
-   }
-   unchecked {
-    for (uint256 i = 0; i < 4; ++i) {
-      if (callData[i] != functionSig[i]) {
-        return false;
-      }
-    }
-   }

-   return true;
+	return !(callData.length < 4 || callData[0] != functionSig[0]|| callData[1] != functionSig[1]|| callData[2] != functionSig[2]|| callData[3] != functionSig[3]);

  }

Lines in the code

BytesLibrary.sol#L38-L52

#0 - HardlyDifficult

2022-08-17T18:47:08Z

Good report, thanks.

  1. Post-increment/decrement cost more gas then pre-increment/decrement

Agree and will fix.

  1. Call to KECCAK256 should use IMMUTABLE rather than constant

While it seems this should help, changing to immutable shows a regression in our test suite. Gas costs go up by ~50 gas.

  1. Array length should not be looked up in every loop of a for-loop

May be theoretically valid, but won't fix. I tested this: gas-reporter and our gas-stories suite is reporting a small regression using this technique. It also hurts readability a bit so we wouldn't want to include it unless it was a clear win.

  1. Operatos <= or >= cost more gas than operators < or >

This could potentially be valid but won't fix to improve code readability and avoid potential overflow/underflow concerns.

  1. != 0 is cheaper than > 0

Invalid. We tested the recommendation and got the following results:

createNFTDropCollection gas reporter results: using > 0 (current): - 319246 · 319578 · 319361 using != 0 (recommendation): - 319252 · 319584 · 319367 impact: +6 gas
  1. Variable1 = Variable1 + (-) Variable2 is cheaper in gas cost than variable1 += (-=) variable2.

No impact. We made the recommended change and ran gas-reporter for our entire test suite. 0 impact detected. Even if there were a small benefit, we feel this hurts readability and would not be worth trivial savings.

  1. Use bytes32 instead of string

It's possible this technique could work for name or symbol, however in order to stay flexible in case a creator would like to use a long value here -- we will keep these as strings.

  1. Require / Revert strings longer than 32 bytes cost extra gas

Agree but won't fix. We use up to 64 bytes, aiming to respect the incremental cost but 32 bytes is a bit too short to provide descriptive error messages for our users.

  1. Use custom errors rather than REVERT()/REQUIRE() strings to save deployment gas

Agree but won't fix at this time. We use these in the market but not in collections. Unfortunately custom errors are still not as good of an experience for users (e.g. on etherscan). We used them in the market originally because we were nearing the max contract size limit and this was a good way to reduce the bytecode. We'll consider this in the future as tooling continues to improve.

  1. Using private rather than public for constants, saves gas

Agree but won't fix. For ease of use and consistency we will continue to expose some constants publicly.

  1. Initialize variables with default values are not needed

Invalid. This optimization technique is no longer applicable with the current version of Solidity.

  1. Using bools for storage incurs overhead

Valid for cidToMinted, saving ~200 gas. Not seeing any benefit for assumePrimarySale, potentially because it's an immutable variable.

  1. Uncheked arithmetic when it is not possible for them to overflow

getFeesAndRecipients is a read only function not intended to be used on-chain, but as a best practice we will add unchecked there as well.

  1. Refactoring the code of NFTCollection._baseURI to save gas

This information isn't usually read by another contract, but out of curiosity I tested .tokenURI for the standard scenario (no override provided).. Before: 36642 After: 36644 Impact: -2 gas

Invalid. The contract bytecode size also increased by 0.002 KB.

  1. Refactoring the code of BytesLibrary.startsWith to save gas

Given the results from 14 I'm a bit skeptical, however more important is this one hurts readability - I wouldn't want to make a change like that unless it was a clear win.

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