NFT Dealers

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

list() missing onlyWhenRevealed modifier allows listings before collection is revealed

Author Revealed upon completion

Root + Impact

Description

  • The protocol defines two distinct phases: a preparation phase (pre-reveal) where only whitelisting is allowed, and a revealed phase where minting, listing, and trading become active.

  • list() is missing the onlyWhenRevealed modifier, allowing whitelisted users to create listings during the preparation phase. This violates the protocol's stated phase separation and allows the secondary market to be active before the collection is officially revealed.

// mintNft() correctly enforces the reveal gate
function mintNft() external payable onlyWhenRevealed onlyWhitelisted { ... }
// list() does not
@> function list(uint256 _tokenId, uint32 _price) external onlyWhitelisted {
...
}

Risk

Likelihood:

  • Any whitelisted user who holds an NFT (e.g. received via transfer before reveal) can call list() during the preparation phase.

  • The missing modifier is not blocked by any other guard in the function.

Impact:

  • The secondary market can be active before the collection is revealed, breaking the protocol's intended phase separation.

  • Early listings may front-run the reveal, giving certain users an unfair advantage over others.

Proof of Concept

  1. Owner deploys the contract and whitelists Alice. Collection is not yet revealed.

  2. Alice receives an NFT via transfer during the preparation phase.

  3. Alice calls list() — no revert. The listing is created and active before reveal.

  4. Other users cannot mint yet (onlyWhenRevealed blocks them), but Alice's listing is already live on the secondary market.

function test_listBeforeReveal() public {
// Alice mints while revealed
vm.prank(alice); nftDealers.mintNft();
// Flip isCollectionRevealed back to false (slot 10, packed with metadataFrozen)
vm.store(address(nftDealers), bytes32(uint256(10)), bytes32(0));
assertEq(nftDealers.isCollectionRevealed(), false);
// mintNft() correctly reverts before reveal
vm.prank(alice);
vm.expectRevert("Collection is not revealed yet");
nftDealers.mintNft();
// list() has no onlyWhenRevealed — succeeds while collection is unrevealed
vm.prank(alice);
nftDealers.list(1, 50e6); // no revert
(, , , , bool isActive) = nftDealers.s_listings(1);
assertEq(isActive, true); // listing active before reveal
}

Recommended Mitigation

Add onlyWhenRevealed to list() to match the behavior of mintNft() and enforce the protocol's phase separation.

- function list(uint256 _tokenId, uint32 _price) external onlyWhitelisted {
+ function list(uint256 _tokenId, uint32 _price) external onlyWhenRevealed onlyWhitelisted {

Support

FAQs

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

Give us feedback!