When a seller lists an NFT, the marketplace should preserve the intended listing price exactly, and buy() should charge that exact amount from the buyer.
Listing price is handled as uint32 in the Listing struct and in list(). Any upstream conversion from a larger integer to uint32 truncates silently modulo 232232, which can reduce a very large intended price to a much smaller value . As a result, buyers can purchase NFTs for far less than the seller intended, breaking pricing integrity and marketplace economics.
Likelihood:
Reason 1: This occurs whenever a listing price is provided through a wider integer domain (e.g., off-chain UI/backend uint256) and then cast/stored into uint32, causing silent modulo truncation on values above 2^32 - 1.
Reason 2: This occurs in normal marketplace operation because buy() trusts the stored listing.price value directly, so any truncated price is treated as canonical sale price without additional sanity checks or max-bound validation.
Impact:
Impact 1: NFTs can be sold at a drastically lower amount than intended by the seller (severe underpricing), creating direct economic loss for listers.
This test uses the same setup as the NFTDealersTest file.
The vulnerability: Since Solidity 0.8.x only protects against arithmetic overflow but not explicit downcasting, a seller can pass a uint256 value that silently truncates when cast to uint32. In this test, price = type(uint32).max + 1 + 1e6 truncates to exactly 1e6 (1 USDC), while the seller intended to list a much larger amount.
The exploit: A buyer purchases the NFT paying only 1 USDC instead of the intended price, acquiring the NFT at a near-zero cost.
Final assertions:
type(uint32).max is below MID_FEE_THRESHOLD, proving that the uint32 type structurally prevents the mid and high fee brackets from ever being reached, confirming that price must be refactored to uint256
Ownership of the NFT is transferred to userWithCash
userWithCash balance decreased by only 1 USDC instead of the actual intended price
Change the price field in the Listing struct and the _price parameter in the list and updatePrice functions from uint32 to uint256. This eliminates the silent truncation on downcasting and ensures that all fee brackets defined in _calculateFees are reachable, aligning the type with the intended price range of the contract.
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.