The following PoC demonstrates the fee cliff by comparing seller proceeds at threshold boundaries. A seller charging 1001 USDC nets less than a seller charging 1000 USDC.
pragma solidity ^0.8.30;
import { Test } from "forge-std/Test.sol";
import { NFTDealers } from "src/NFTDealers.sol";
import { MockUSDC } from "src/MockUSDC.sol";
contract M02_PoC is Test {
NFTDealers public nftDealers;
MockUSDC public usdc;
address owner = makeAddr("owner");
function setUp() public {
usdc = new MockUSDC();
nftDealers = new NFTDealers(owner, address(usdc), "NFT Dealers", "NFTD", "ipfs://image", 20 * 1e6);
}
function test_FeeThresholdCliff() public {
uint256 price1 = 1000 * 1e6;
uint256 price2 = 1001 * 1e6;
uint256 fee1 = nftDealers.calculateFees(price1);
uint256 fee2 = nftDealers.calculateFees(price2);
uint256 net1 = price1 - fee1;
uint256 net2 = price2 - fee2;
console.log("Price 1000 USDC - Fee:", fee1/1e6, "USDC - Net:", net1/1e6, "USDC");
console.log("Price 1001 USDC - Fee:", fee2/1e6, "USDC - Net:", net2/1e6, "USDC");
if (net2 < net1) {
console.log("VULNERABILITY: Seller nets LESS by charging MORE");
console.log("Loss:", (net1 - net2)/1e6, "USDC");
}
}
}
The comprehensive test suite below validates the vulnerability across three scenarios: (1) Low threshold cliff - seller loses 19 USDC by charging 1 USDC more, (2) Mid threshold cliff - seller loses 199 USDC by charging 1 USDC more, (3) Optimal pricing analysis shows market will cluster at thresholds. All tests pass and confirm the vulnerability.
pragma solidity ^0.8.30;
* ============================================================
* POC-M02: Fee Threshold Cliff Creates Economic Disincentives
* Sellers net LESS by charging MORE at boundaries
* Severity : MEDIUM
* Contract : NFTDealers.sol
* Function : _calculateFees()
* Author: Sudan249 AKA 0xAljzoli
* ============================================================
*/
import { Test } from "forge-std/Test.sol";
import { console } from "forge-std/console.sol";
import "./AuditBase.sol";
contract POC_M02_FeeThresholdCliff is AuditBase {
uint256 constant LOW_THRESHOLD = 1000 * 1e6;
uint256 constant MID_THRESHOLD = 10_000 * 1e6;
function test_M02_A_lowThresholdCliff_sellerLosesMoney() public {
uint256 priceAt = LOW_THRESHOLD;
uint256 priceAbove = LOW_THRESHOLD + 1e6;
uint256 feeAt = nftDealers.calculateFees(priceAt);
uint256 feeAbove = nftDealers.calculateFees(priceAbove);
uint256 netAt = priceAt - feeAt;
uint256 netAbove = priceAbove - feeAbove;
console.log("=== LOW THRESHOLD CLIFF ===");
console.log("Price at threshold:", priceAt / 1e6, "USDC");
console.log("Fee at threshold:", feeAt / 1e6, "USDC");
console.log("Net at threshold:", netAt / 1e6, "USDC");
console.log("Price above threshold:", priceAbove / 1e6, "USDC");
console.log("Fee above threshold:", feeAbove / 1e6, "USDC");
console.log("Net above threshold:", netAbove / 1e6, "USDC");
if (netAbove < netAt) {
console.log("VULNERABILITY CONFIRMED: Seller nets LESS by charging MORE");
console.log("Price increase:", (priceAbove - priceAt) / 1e6, "USDC");
console.log("Net decrease:", (netAt - netAbove) / 1e6, "USDC");
}
assertGt(netAt, netAbove, "Seller should not net less when charging more");
}
function test_M02_B_midThresholdCliff_evenWorse() public {
uint256 priceAt = MID_THRESHOLD;
uint256 priceAbove = MID_THRESHOLD + 1e6;
uint256 feeAt = nftDealers.calculateFees(priceAt);
uint256 feeAbove = nftDealers.calculateFees(priceAbove);
uint256 netAt = priceAt - feeAt;
uint256 netAbove = priceAbove - feeAbove;
console.log("=== MID THRESHOLD CLIFF ===");
console.log("Price at threshold:", priceAt / 1e6, "USDC");
console.log("Fee at threshold:", feeAt / 1e6, "USDC");
console.log("Net at threshold:", netAt / 1e6, "USDC");
console.log("Price above threshold:", priceAbove / 1e6, "USDC");
console.log("Fee above threshold:", feeAbove / 1e6, "USDC");
console.log("Net above threshold:", netAbove / 1e6, "USDC");
uint256 loss = netAt - netAbove;
console.log("Seller LOSS by charging 1 USDC more:", loss / 1e6, "USDC");
if (loss > 0) {
console.log("VULNERABILITY CONFIRMED: Massive disincentive at mid threshold");
}
assertGt(netAt, netAbove, "Seller should not net less when charging more");
}
function test_M02_C_optimalPricing_marketDistortion() public {
console.log("=== OPTIMAL PRICING ANALYSIS ===");
uint256[] memory testPrices = new uint256[](6);
testPrices[0] = 999 * 1e6;
testPrices[1] = 1000 * 1e6;
testPrices[2] = 1001 * 1e6;
testPrices[3] = 9999 * 1e6;
testPrices[4] = 10_000 * 1e6;
testPrices[5] = 10_001 * 1e6;
uint256 maxNet = 0;
uint256 optimalPrice = 0;
for (uint256 i = 0; i < testPrices.length; i++) {
uint256 price = testPrices[i];
uint256 fee = nftDealers.calculateFees(price);
uint256 net = price - fee;
console.log("Test", i);
console.log(" Price:", price / 1e6, "USDC");
console.log(" Fee:", fee / 1e6, "USDC");
console.log(" Net:", net / 1e6, "USDC");
if (net > maxNet) {
maxNet = net;
optimalPrice = price;
}
}
console.log("OPTIMAL PRICE:", optimalPrice / 1e6, "USDC");
console.log("MAX NET:", maxNet / 1e6, "USDC");
console.log("VULNERABILITY: Market will cluster at threshold boundaries");
}
function test_M02_D_marginalFeeCalculation_fix() public pure {
console.log("=== PROPOSED FIX: MARGINAL FEE CALCULATION ===");
uint256 price = 1001 * 1e6;
uint256 lowThreshold = 1000 * 1e6;
uint256 currentFee = (price * 300) / 10000;
uint256 feeOnThreshold = (lowThreshold * 100) / 10000;
uint256 excess = price - lowThreshold;
uint256 feeOnExcess = (excess * 300) / 10000;
uint256 marginalFee = feeOnThreshold + feeOnExcess;
console.log("Price:", price / 1e6, "USDC");
console.log("Current Fee:", currentFee / 1e6, "USDC");
console.log("Marginal Fee:", marginalFee / 1e6, "USDC");
console.log("FIX: Use marginal calculation to eliminate cliff effect");
}
}
The fix implements marginal fee calculation (like income tax) where the lower rate applies to the threshold amount and the higher rate applies only to the excess. This ensures sellers always net more by charging more.