NFT Dealers

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

uint32 Price Field Silently Truncates Listings Above ~4,294 USDC

Author Revealed upon completion

Root + Impact

Description

  • Under normal protocol operation, the price a seller sets during list() is the exact amount a buyer pays, and the seller receives that amount minus fees. No precision loss should occur anywhere in this flow.

  • Both Listing.price and the list() parameter are declared as uint32, which has a maximum value of 4,294,967,295 — approximately 4,294 USDC at six decimal places. The protocol's own test suite already exercises prices of 5,500 USDC and 15,500 USDC, demonstrating that the intended price range far exceeds the uint32 ceiling. When list(tokenId, uint32(5000e6)) is called, Solidity silently discards the high-order bits of the explicit cast, storing a value of approximately 705 USDC instead of 5,000 USDC — with no error, no event, and no revert.

struct Listing {
address seller;
@> uint32 price; // @> BUG: maximum ~4,294 USDC; insufficient for an NFT market
address nft;
uint256 tokenId;
bool isActive;
}
@> function list(uint256 _tokenId, uint32 _price) external onlyWhitelisted {
// @> BUG: explicit uint32 cast at call site silently truncates the value
s_listings[_tokenId] = Listing({ price: _price, ... });
}

Risk

Likelihood:

  • Any NFT listed above 4,294 USDC will trigger truncation — this is a deterministic outcome, not a probabilistic one.

  • The protocol's own test cases target 5,500 USDC and 15,500 USDC, confirming high-value listings are intended and expected.

Impact:

  • A seller listing at 15,500 USDC receives approximately 2,615 USDC — a loss exceeding 83% of expected proceeds, with no indication that anything went wrong.

  • Protocol fee revenue is proportionally reduced, causing compounding long-term damage to protocol sustainability.

Proof of Concept

Add this to 2026-03-NFT-dealers/test/NFTDealersTest.t.sol,run forge test --match-test testPoC_H02_PriceOverflowUint32 -vvvv

function testPoC_H02_PriceOverflowUint32() public revealed {
uint256 tokenId = 1;
uint256 intendedPrice = 5000e6; // 5,000 USDC — exceeds uint32 max
uint256 lockAmt = nftDealers.lockAmount();
vm.prank(owner);
nftDealers.whitelistWallet(userWithCash);
vm.startPrank(userWithCash);
usdc.approve(address(nftDealers), lockAmt);
nftDealers.mintNft();
nftDealers.list(tokenId, uint32(intendedPrice)); // silent truncation here
vm.stopPrank();
(, uint32 storedPrice, , , ) = nftDealers.s_listings(tokenId);
// Stored price is NOT the intended 5,000 USDC
assertNotEq(storedPrice, intendedPrice,
"H-02: price was silently truncated on storage");
assertLt(storedPrice, intendedPrice,
"H-02: stored price is less than intended price");
}

Recommended Mitigation

Widen all price-related fields and parameters from uint32 to uint256 throughout the codebase. No overflow is possible within EVM arithmetic at that size.

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 {
+ function updatePrice(uint256 _listingId, uint256 _newPrice) external {

Support

FAQs

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

Give us feedback!