Root + Impact
Description
The protocol defines two phases: preparation
(pre-reveal, whitelist only) and revealed (minting + listing + buying enabled).
The
list() function lacks the onlyWhenRevealedmodifier that mintNft() has, allowing whitelisted users to create active listings during the preparation phase, before the
collection is publicly revealed.
function mintNft() external payable
onlyWhenRevealed onlyWhitelisted { ... }
can be created pre-reveal
function list(uint256 _tokenId, uint32 _price)
external onlyWhitelisted {
...
}
Risk
Likelihood:
Impact:
-
Secondary market listings appear before the collection is public — breaks intended UX and reveal mechanics
-
Potential front-running: insiders list at low prices before reveal, creating arbitrage opportunities against buyers who don't know the collection's value
Proof of Concept
function test_listBeforeReveal() public {
NFTDealers nft = new NFTDealers(owner, address(usdc), "T", "T", "ipfs://", 20e6);
assert(nft.isCollectionRevealed() == false);
vm.prank(owner); nft.revealCollection();
vm.prank(owner); nft.whitelistWallet(seller);
usdc.mint(seller, 20e6);
vm.startPrank(seller);
usdc.approve(address(nft), 20e6);
nft.mintNft();
vm.stopPrank();
vm.prank(seller);
nft.list(1, 500e6);
}
For the simpler findings (LOW severity) where there's no real exploit to run, the PoC field can just be the
code snippet showing the problematic line — e.g. for LOW-1 (self-transfer):
usdc.safeTransfer(address(this), fees);
usdc.safeTransfer(msg.sender, amountToSeller);
The full runnable test (forge test --match-test test_MEDIUM2_ListBeforeReveal -vv) is at
/tmp/nft-dealers-repo/test/NFTDealersPoC.t.sol if you want to run it first to confirm output before pasting.
Recommended Mitigation
- function list(uint256 _tokenId, uint32 _price) external onlyWhitelisted {
+ function list(uint256 _tokenId, uint32 _price) external onlyWhenRevealed onlyWhitelisted {