NFT Dealers

First Flight #58
Beginner FriendlyFoundry
100 EXP
Submission Details
Impact: high
Likelihood: low

Contract Bricking - Constructor Allows Zero Owner

Author Revealed upon completion

Constructor Allows Zero Owner - Permanent Loss of Admin Control (Contract Bricking)

Description

  • Normal behavior

    The contract assigns an owner during deployment, which is responsible for executing privileged administrative functions such as revealing the collection, managing the whitelist, and withdrawing collected fees.


  • Issue

    The constructor does not validate the _owner parameter. If address(0) is provided, all onlyOwner functions become permanently inaccessible, since no entity can ever satisfy msg.sender == address(0). This results in irreversible loss of administrative control and renders critical protocol functionality unusable.

constructor(
address _owner,
address _usdc,
string memory _collectionName,
string memory _symbol,
string memory _collectionImage,
uint256 _lockAmount
) ERC721(_collectionName, _symbol) {
owner = _owner; // @> Missing validation: _owner can be address(0)
usdc = IERC20(_usdc);
collectionName = _collectionName;
tokenSymbol = _symbol;
collectionImage = _collectionImage;
lockAmount = _lockAmount;
}

Risk

Likelihood: Low

  • Misconfiguration during deployment (manual deploy scripts, factory contracts, or parameter injection)

  • No validation prevents assigning an invalid owner

Impact: High

  • Permanent loss of all administrative capabilities

  • Critical protocol features become unusable (reveal, whitelist management, fee withdrawal)

Proof of Concept

This PoC demonstrates that the contract can be deployed with owner = address(0), resulting in an invalid administrative state.
As a result, all administrative functions using owner = _owner become permanently inaccessible, effectively bricking the contract.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import "forge-std/Test.sol";
import "../src/NFTDealers.sol";
import "../src/MockUSDC.sol";
contract ZeroOwner_PoC is Test {
NFTDealers nft;
MockUSDC usdc;
address wrong_owner = address(0); // Invalid owner
function setUp() public {
usdc = new MockUSDC();
// Deploy contract with wrong_owner = address(0)
nft = new NFTDealers(
wrong_owner,
address(usdc),
"NFT Dealers",
"NFTD",
"ipfs://image",
20e6
);
}
function test_owner_is_zero_address() public {
// Confirms broken initialization state
assertEq(nft.owner(), address(0));
}
}

Results

forge test --match-contract ZeroOwner_PoC -vvvv
[⠊] Compiling...
No files changed, compilation skipped
Ran 1 test for test/NFTDealers_ZeroOwner_PoC.t.sol:ZeroOwner_PoC
[PASS] test_owner_is_zero_address() (gas: 11232)
Traces:
[11232] ZeroOwner_PoC::test_owner_is_zero_address()
├─ [2598] NFTDealers::owner() [staticcall]
│ └─ ← [Return] 0x0000000000000000000000000000000000000000
├─ [0] VM::assertEq(0x0000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000) [staticcall]
│ └─ ← [Return]
└─ ← [Stop]
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 824.95µs (60.40µs CPU time)

Recommended Mitigation

Adding a validation check in the constructor prevents deployment with an invalid owner.
This ensures that at least one valid address can execute privileged functions, preserving administrative control and preventing irreversible contract bricking.

constructor(
address _owner,
address _usdc,
string memory _collectionName,
string memory _symbol,
string memory _collectionImage,
uint256 _lockAmount
) ERC721(_collectionName, _symbol) {
+ if (_owner == address(0)) revert InvalidAddress();
owner = _owner;
usdc = IERC20(_usdc);
collectionName = _collectionName;
tokenSymbol = _symbol;
collectionImage = _collectionImage;
lockAmount = _lockAmount;
}

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!