NFT Dealers

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

`Listing.price` is `uint32` — NFTs can't be listed above ~4,294 USDC, and the 5% fee tier is permanently dead

Author Revealed upon completion

Root + Impact

Description

The price field in the Listing struct is declared as uint32. Since USDC uses 6 decimals, type(uint32).max = 4,294,967,295 caps the maximum price at roughly 4,294 USDC.

The protocol has three fee tiers: 1% up to 1,000 USDC, 3% up to 10,000 USDC, and 5% above that. But 10,000 USDC requires storing 10_000e6 = 10,000,000,000 — which doesn't fit in a uint32. So the 5% tier can never be triggered. The 3% tier only works partially (1,001–4,294 USDC).

For an NFT marketplace, capping prices at ~$4,294 is a serious limitation that makes the platform unusable for any moderately valuable collection.

struct Listing {
address seller;
uint32 price; // @> max ≈ 4,294.97 USDC
address nft;
uint256 tokenId;
bool isActive;
}
uint256 private constant MID_FEE_THRESHOLD = 10_000e6; // 10,000 USDC — can never fit in uint32
// HIGH_FEE_BPS (5%) is dead code

Risk

Likelihood: Every listing is affected. Any user trying to set a price above ~4,294 USDC will get a revert — Solidity 0.8+ panics on integer overflow.

Impact: The protocol's fee model is broken (owner permanently loses the 5% fee revenue), and the marketplace can't support NFTs worth more than a few thousand dollars. The HIGH_FEE_BPS constant and its branch in _calculateFees are dead code.


Proof of Concept

  1. Protocol sets HIGH_FEE_BPS = 500 (5%) for sales above 10,000 USDC.

  2. A user tries list(tokenId, 10_000e6) — the value 10,000,000,000 exceeds uint32 max of 4,294,967,295. Transaction reverts.

  3. Even at the absolute maximum price (type(uint32).max ≈ 4,294 USDC), _calculateFees returns the 3% tier. The 5% branch never executes.

function testUint32PriceCap() public {
mintNFTForTesting();
// 10,000 USDC exceeds uint32 capacity
uint256 desiredPrice = 10_000e6;
assertTrue(desiredPrice > type(uint32).max);
// Max possible listing price
uint32 maxPrice = type(uint32).max; // ≈ 4,294.97 USDC
vm.prank(userWithCash);
nftDealers.list(1, maxPrice);
// Fee is always 3% tier — 5% is unreachable
uint256 fee = nftDealers.calculateFees(maxPrice);
uint256 midFee = (uint256(maxPrice) * 300) / 10_000;
assertEq(fee, midFee);
}

Recommended Mitigation

Change price to uint256 so the full USDC range is supported and all fee tiers work as intended.

struct Listing {
address seller;
- uint32 price;
+ uint256 price;
address nft;
uint256 tokenId;
bool isActive;
}
- function list(uint256 _tokenId, uint32 _price) external onlyWhitelisted {
+ function list(uint256 _tokenId, uint256 _price) external onlyWhitelisted {
- function updatePrice(uint256 _listingId, uint32 _newPrice) external onlySeller(_listingId) {
+ function updatePrice(uint256 _listingId, uint256 _newPrice) external onlySeller(_listingId) {

Support

FAQs

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

Give us feedback!