NFT Dealers

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

uint32 price field makes HIGH fee tier permanently unreachable

Author Revealed upon completion

Root + Impact

Description

  • The protocol implements a progressive fee system with three tiers: 1% (≤1,000 USDC), 3% (≤10,000 USDC), and 5% (>10,000 USDC). Sellers set listing prices via list() and updatePrice().

  • The price field in the Listing struct is typed as uint32, capping the maximum possible price at 4,294,967,295 (~4,294 USDC with 6 decimals). HIGH_FEE_THRESHOLD requires a price above 10,000 USDC — which exceeds uint32 max — making the 5% fee tier permanently unreachable by any listing in the protocol.

struct Listing {
address seller;
@> uint32 price; // max ~4,294 USDC — HIGH_FEE_THRESHOLD (10,000 USDC) is unreachable
address nft;
uint256 tokenId;
bool isActive;
}
uint256 private constant MID_FEE_THRESHOLD = 10_000e6; // 10,000 USDC
// type(uint32).max = 4_294_967_295 < MID_FEE_THRESHOLD = 10_000_000_000

Risk

Likelihood:

  • Every listing in the protocol is subject to this constraint — there is no code path that allows price to exceed uint32 max, so the 5% tier is unreachable for all users at all times.

  • The list() and updatePrice() functions both accept uint32 _price, so the truncation occurs at the point of input.

Impact:

  • The HIGH fee tier (5%) is permanently disabled — the protocol never collects fees at the intended maximum rate.

  • Sellers can list high-value NFTs at prices up to ~4,294 USDC only, undermining the protocol's stated purpose of supporting a progressive fee structure.

Proof of Concept

  1. type(uint32).max = 4,294,967,295 — with USDC's 6 decimals, this equals ~4,294 USDC.

  2. MID_FEE_THRESHOLD = 10_000e6 = 10,000,000,000 — this exceeds uint32 max by ~2.3x.

  3. Any listing at the maximum possible price (~4,294 USDC) falls in the MID fee tier (3%), never HIGH (5%).

function test_uint32PriceCannotReachHighFeeTier() public {
vm.prank(alice); nftDealers.mintNft();
vm.prank(alice); nftDealers.list(1, UINT32_MAX_PRICE); // max possible price ~4,294 USDC
uint256 feeAtMaxPrice = nftDealers.calculateFees(UINT32_MAX_PRICE);
uint256 expectedMidFee = (uint256(UINT32_MAX_PRICE) * 300) / 10_000; // 3%
uint256 expectedHighFee = (uint256(UINT32_MAX_PRICE) * 500) / 10_000; // 5%
// fee is MID (3%) at max price — HIGH (5%) never reachable
assertEq(feeAtMaxPrice, expectedMidFee);
assertNotEq(feeAtMaxPrice, expectedHighFee);
// MID_FEE_THRESHOLD itself exceeds uint32 max
assertGt(MID_FEE_THRESHOLD, uint256(UINT32_MAX_PRICE));
}

Recommended Mitigation

Change price to uint256 across the struct and all functions that accept it. Since _calculateFees() already operates on uint256, no changes to fee logic are needed — only the input type needs to be widened.

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!