NFT Smart Contract Security 3 — Questions and Answers
Question 1: What vulnerability arises from using delegatecall to an untrusted contract in an upgradeable NFT proxy?
- The called code runs in the proxy's storage context and can overwrite critical state (Correct answer)
- It permanently disables the fallback function
- It doubles all gas costs
- It prevents the token from being transferred
Correct answer: The called code runs in the proxy's storage context and can overwrite critical state
delegatecall executes external code against the caller's storage, so a malicious implementation can corrupt or hijack proxy state.
Question 2: In a proxy-based upgradeable contract, what is a storage collision?
- A new implementation's variable layout overlaps the proxy's slots, corrupting data (Correct answer)
- Two NFTs are assigned the same token ID
- Two functions have the same selector
- The mempool reorders transactions
Correct answer: A new implementation's variable layout overlaps the proxy's slots, corrupting data
Mismatched storage layouts between proxy and implementation cause variables to read/write the wrong slots.
Question 3: Why should an upgradeable NFT contract avoid constructors and use an initializer instead?
- Constructor code runs in the implementation's context, not the proxy, so proxy state would be uninitialized (Correct answer)
- Constructors are banned in Solidity 0.8
- Initializers are cheaper to call
- Constructors cannot accept parameters
Correct answer: Constructor code runs in the implementation's context, not the proxy, so proxy state would be uninitialized
Because the proxy delegatecalls to the implementation, constructor logic never runs in proxy storage, so an initializer function is required.
Question 4: What protects an initializer function from being called more than once?
- The initializer modifier that tracks an _initialized flag (Correct answer)
- A require(msg.value > 0) check
- The payable keyword
- The view modifier
Correct answer: The initializer modifier that tracks an _initialized flag
OpenZeppelin's initializer modifier sets a flag so the initialization logic can only execute once.
Question 5: What is a front-running risk when revealing NFT metadata after a mint?
- Observers can read pending reveal transactions and snipe rare tokens before reveal (Correct answer)
- The reveal transaction will always revert
- Metadata gets stored twice
- The token supply doubles
Correct answer: Observers can read pending reveal transactions and snipe rare tokens before reveal
If rarity can be computed from a pending reveal, attackers can front-run to acquire or avoid specific tokens.
Question 6: How can a commit-reveal scheme mitigate front-running in NFT trait assignment?
- Participants commit a hashed value first, then reveal later, hiding the data during the vulnerable window (Correct answer)
- It encrypts the entire blockchain
- It removes the need for gas fees
- It makes all transactions private permanently
Correct answer: Participants commit a hashed value first, then reveal later, hiding the data during the vulnerable window
Commit-reveal hides the meaningful value behind a hash until a later reveal, so it can't be acted on in advance.
Question 7: Why is unchecked external call return value a security concern in NFT contracts?
- A failed transfer may be silently ignored, leaving the contract in an inconsistent state (Correct answer)
- It always reverts the entire transaction
- It increases the token's total supply
- It disables event emission
Correct answer: A failed transfer may be silently ignored, leaving the contract in an inconsistent state
Low-level calls return a success boolean that, if unchecked, lets failures pass silently and corrupt accounting.
What vulnerability arises from using delegatecall to an untrusted contract in an upgradeable NFT proxy?