NFT Dealers

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

The highest fee tier is unreachable because `list()` caps prices at `uint32`

Author Revealed upon completion

Root + Impact

NFTDealers::list() accepts _price as a uint32, the maximum listable price remains below the threshold required to enter the 5% fee tier, making the highest fee bracket unreachable in practice.

Description

  • The intended behavior is that the protocol should be able to apply all three fee tiers defined by its economic model (1%, 3%, and 5%) depending on the resale price chosen by the user.

  • The issue is that list() restricts _price to uint32, which caps the maximum possible value at 4,294,967,295, or approximately 4294.967295 USDC with 6 decimals. That cap is below MID_FEE_THRESHOLD = 10_000e6, which is the minimum threshold required for the protocol to apply the 5% fee tier. As a result, the highest fee bracket can never be reached and the fee model is effectively truncated.

uint32 private constant LOW_FEE_BPS = 100; // 1%
uint32 private constant MID_FEE_BPS = 300; // 3%
uint32 private constant HIGH_FEE_BPS = 500; // 5%
uint256 private constant LOW_FEE_THRESHOLD = 1000e6; // 1,000 USDC
uint256 private constant MID_FEE_THRESHOLD = 10_000e6; // 10,000 USDC
@> function list(uint256 _tokenId, uint32 _price) external onlyWhitelisted {
require(_price >= MIN_PRICE, "Price must be at least 1 USDC");
...
}

Risk

Likelihood: High

  • The issue is deterministic and follows directly from the data type chosen for _price.

  • No special conditions or rare scenarios are required for it to occur.

Impact: Low

  • The 5% fee tier can never be applied, leaving part of the protocol’s fee model inoperative.

  • This also artificially limits the resale price range the market can support.

Proof of Concept

The following test shows that the maximum value representable by uint32 is still below the minimum threshold required to enter the 5% fee tier.

function test_HighestFeeTier_IsUnreachable_BecausePriceIsUint32() public view {
uint256 maxUint32Price = type(uint32).max;
uint256 highFeeTierThreshold = 10_000e6;
// The maximum listable price is still below the minimum threshold
// required to enter the highest fee tier.
assertLt(maxUint32Price, highFeeTierThreshold);
}

Recommended Mitigation

Change the listing price type to uint256 across the full listing flow so the protocol can support the entire resale range
required to apply all three fee tiers correctly.

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 {
require(_price >= MIN_PRICE, "Price must be at least 1 USDC");
require(ownerOf(_tokenId) == msg.sender, "Not owner of NFT");
(... )
}
- function updatePrice(uint256 _listingId, uint32 _newPrice) external onlySeller(_listingId) {
+ function updatePrice(uint256 _listingId, uint256 _newPrice) external onlySeller(_listingId) {
Listing memory listing = s_listings[_listingId];
uint256 oldPrice = listing.price;
(... )
}

Support

FAQs

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

Give us feedback!