NFT Dealers

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

Fee Cliff at Tier Boundaries Creates Perverse Pricing Incentive

Author Revealed upon completion

Links

  • src/NFTDealers.sol:233-240_calculateFees() tier logic

  • src/NFTDealers.sol:28LOW_FEE_THRESHOLD = 1000e6

Vulnerability Details

The fee tiers use flat rates per bracket rather than progressive/marginal rates. This creates a sharp discontinuity at the $1,000 boundary:

// NFTDealers.sol:233-240
function _calculateFees(uint256 _price) internal pure returns (uint256) {
if (_price <= LOW_FEE_THRESHOLD) { // <= $1,000: 1%
return (_price * LOW_FEE_BPS) / MAX_BPS;
} else if (_price <= MID_FEE_THRESHOLD) { // <= $10,000: 3%
return (_price * MID_FEE_BPS) / MAX_BPS;
}
return (_price * HIGH_FEE_BPS) / MAX_BPS; // > $10,000: 5%
}

Fee calculation at the boundary:

  • Listing at $1,000: fee = ($1,000 * 100) / 10,000 = $10.00 (1%)

  • Listing at $1,001: fee = ($1,001 * 300) / 10,000 = $30.03 (3%)

A $1 increase in price results in a ~$20 increase in fees. This creates a rational incentive for sellers to price NFTs at exactly $1,000 rather than $1,001-$1,333, since the higher price actually nets them less after fees.

Impact

Sellers are economically incentivized to cluster prices just below the $1,000 threshold. Any NFT worth between $1,001 and approximately $1,333 would net the seller less after fees if priced accurately than if priced at exactly $1,000. This distorts marketplace pricing and reduces fee revenue for the protocol owner.

Recommended Mitigation

Consider implementing marginal/progressive fee rates where only the portion of the price exceeding each threshold is taxed at the higher rate:

function _calculateFees(uint256 _price) internal pure returns (uint256) {
if (_price <= LOW_FEE_THRESHOLD) {
return (_price * LOW_FEE_BPS) / MAX_BPS;
} else if (_price <= MID_FEE_THRESHOLD) {
uint256 lowFee = (LOW_FEE_THRESHOLD * LOW_FEE_BPS) / MAX_BPS;
uint256 midFee = ((_price - LOW_FEE_THRESHOLD) * MID_FEE_BPS) / MAX_BPS;
return lowFee + midFee;
}
uint256 lowFee = (LOW_FEE_THRESHOLD * LOW_FEE_BPS) / MAX_BPS;
uint256 midFee = ((MID_FEE_THRESHOLD - LOW_FEE_THRESHOLD) * MID_FEE_BPS) / MAX_BPS;
uint256 highFee = ((_price - MID_FEE_THRESHOLD) * HIGH_FEE_BPS) / MAX_BPS;
return lowFee + midFee + highFee;
}

Support

FAQs

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

Give us feedback!