NFT Gas Optimization for NFTs 2 — Questions and Answers
Question 1: What is the main gas advantage of using `calldata` instead of `memory` for external function parameters?
- calldata allows mutation of inputs
- calldata avoids copying data into memory, reducing gas (Correct answer)
- calldata supports larger payloads
- calldata enables cross-contract calls
Correct answer: calldata avoids copying data into memory, reducing gas
Parameters declared as `calldata` are read directly from the transaction input without being copied to memory, saving gas on reads.
Question 2: How does a Merkle tree allowlist reduce gas costs for NFT mints?
- It stores all allowed addresses on-chain in a mapping
- It stores only the Merkle root on-chain; users provide a proof at mint time (Correct answer)
- It pre-mints all allowed tokens before the sale
- It caches allowlist checks in contract events
Correct answer: It stores only the Merkle root on-chain; users provide a proof at mint time
Storing just the 32-byte Merkle root on-chain instead of thousands of addresses avoids expensive storage writes during setup.
Question 3: Which deployment pattern minimizes gas when launching many identical NFT contracts (e.g., per-creator collections)?
- Deploying a full contract each time
- Using the EIP-1167 minimal proxy (clone) pattern (Correct answer)
- Storing all collections in one giant contract
- Using CREATE2 with a salt
Correct answer: Using the EIP-1167 minimal proxy (clone) pattern
EIP-1167 minimal proxies deploy a tiny forwarding contract pointing to a shared implementation, reducing deployment gas by ~10x.
Question 4: What does marking a Solidity variable as `immutable` rather than `constant` allow, and how does it affect gas?
- Immutable variables can be changed after deployment, saving SLOAD gas
- Immutable variables are set at construction time and inlined in bytecode, saving SLOAD gas at runtime (Correct answer)
- Immutable variables are stored in a cheaper storage tier
- Immutable variables skip ABI encoding
Correct answer: Immutable variables are set at construction time and inlined in bytecode, saving SLOAD gas at runtime
Immutable values are embedded directly in contract bytecode after construction, so reading them costs only 3 gas (like a constant) rather than a 2100-gas SLOAD.
Question 5: Why should NFT contracts avoid using `_safeMint` when minting to known EOA addresses in bulk?
- _safeMint is deprecated in OpenZeppelin v5
- _safeMint makes an external call to check ERC-721 receiver support, adding gas overhead (Correct answer)
- _safeMint does not update ownership records
- _safeMint skips event emission
Correct answer: _safeMint makes an external call to check ERC-721 receiver support, adding gas overhead
`_safeMint` calls `onERC721Received` on the recipient if it's a contract, adding an external call and re-entrancy guard overhead unnecessary for plain EOAs.
Question 6: What effect does enabling the Solidity optimizer (runs=200) have on a deployed NFT contract?
- It increases deployment size but lowers per-call gas
- It reduces both deployment size and runtime gas through bytecode optimization (Correct answer)
- It only affects events and not function calls
- It disables overflow checks
Correct answer: It reduces both deployment size and runtime gas through bytecode optimization
The optimizer rewrites bytecode to eliminate redundant operations, typically shrinking contract size and reducing runtime execution gas.
What is the main gas advantage of using `calldata` instead of `memory` for external function parameters?