Pieces Protocol

First Flight #32
Beginner FriendlyFoundrySolidityNFT
100 EXP
View results
Submission Details
Severity: low
Valid

[M-1] Improper Fee Handling Can't Handle Small Sell Price

Summary

The fee calculation in buyOrder function is fixed at price / 100, which might not handle low-value transactions gracefully, resulting in zero fees or rounding issues.

Vulnerability Details

Add following test case to TokenDividerTest.t.sol:

function testBuyErc20PriceDivision() public nftDivided {
ERC20Mock erc20Mock = ERC20Mock(tokenDivider.getErc20InfoFromNft(address(erc721Mock)).erc20Address);
uint256 ownerBalanceBefore = address(tokenDivider.owner()).balance;
uint256 user2TokenBalanceBefore = tokenDivider.getBalanceOf(USER2, address(erc20Mock));
vm.startPrank(USER);
erc20Mock.approve(address(tokenDivider), AMOUNT);
tokenDivider.sellErc20(address(erc721Mock), 99 wei, AMOUNT);
vm.stopPrank();
vm.prank(USER2);
tokenDivider.buyOrder{value: (1 ether)}(0, USER);
vm.stopPrank();
uint256 ownerBalanceAfter = address(tokenDivider.owner()).balance;
uint256 user2TokenBalanceAfter = tokenDivider.getBalanceOf(USER2, address(erc20Mock));
assertEq(user2TokenBalanceAfter - user2TokenBalanceBefore, AMOUNT);
// The TokenDivider contract owner does not receive any fees from this transaction
assertEq(ownerBalanceAfter, ownerBalanceBefore);
}

Impact

  • When price is lower than 100 wei, dividing it by 100 often results in 0 fee. meaning no fees are collected
    When the price is lower than 100 wei. Buyer don't need to pay any tax.

  • Even for higher values, rounding issues can arise, especially when precise fee percentages are critical.

Tools Used

Foundry

Recommendations

  1. Introduce Fee Scaling

To handle decimals accurately, use a scaling factor (e.g., 1e4 for two decimal places of precision):

uint256 constant FEE_PERCENT = 100; // Fee percentage as 1.00% (scaled by 1e4)
uint256 constant SCALE = 1e4; // Scale factor for precision
function calculateFee(uint256 price) public pure returns (uint256) {
return (price * FEE_PERCENT) / SCALE;
}
  1. Set a Minimum Fee

To prevent fees from rounding down to zero, introduce a minimum fee:

uint256 constant MIN_FEE = 1e15; // Minimum fee (e.g., 0.001 ETH in wei)
function calculateFee(uint256 price) public pure returns (uint256) {
uint256 fee = (price * FEE_PERCENT) / SCALE;
return fee < MIN_FEE ? MIN_FEE : fee;
}
Updates

Lead Judging Commences

fishy Lead Judge 5 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Precision loss

Support

FAQs

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