NFT listings store a price in USDC (6 decimals). The contract defines three fee tiers: LOW (<1,000 USDC), MID (1,000–10,000 USDC), and HIGH (>10,000 USDC), expecting the
full price range to be usable.
The price field in the Listing struct is declared uint32, which maxes out at 4,294,967,295 (~4,294 USDC). Any price above that silently truncates on downcast. The HIGH
fee tier (>10,000 USDC) is completely unreachable. A seller intending to list at 10,000 USDC stores 1,410 USDC instead — the buyer pays ~$8,590 less than the seller
expected.
The MID and HIGH fee tiers (for prices > 1000 USDC and > 10,000 USDC) can never be properly reached since uint32 maxes out at ~4,294 USDC.
Prices at the LOW_FEE_THRESHOLD (1000e6 = 1,000,000,000) DO fit in uint32.
Prices at the MID_FEE_THRESHOLD (10,000e6 = 10,000,000,000) do NOT fit in uint32.
The HIGH_FEE tier is completely unreachable. The MID_FEE tier is only partially reachable (1000-4294 USDC). Any attempt to list above ~4294 USDC will either revert or silently truncate.
Impact:
. If a seller lists an NFT and somehow a truncated price is stored (e.g., through an interface or contract that casts to uint32):
Seller intends price of 5,000 USDC (5000e6), but after uint32 truncation, stored price becomes 5000e6 % 2^32 = 705,032,704 (~705 USDC).
Buyer pays only ~705 USDC instead of 5,000 USDC.
Seller loses ~4,295 USDC of expected payment.
The entire fee tier system above ~4,294 USDC is non-functional. The HIGH_FEE_BPS (5%) tier at >10,000 USDC can never be reached, meaning the protocol permanently loses the higher fee revenue it was designed to collect.
Even for valid uint32 prices, the buy() function charges listing.price as a uint32 value. When this is passed to usdc.transferFrom, it's implicitly upcast to uint256 -- this part works correctly for values within uint32 range. But the entire design is fundamentally broken for the price ranges the protocol intends to support.
price field in the Listing struct from uint32 to uint256, and update all function signatures accordingly:The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.
The contest is complete and the rewards are being distributed.