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);
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
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;
uint256 constant SCALE = 1e4;
function calculateFee(uint256 price) public pure returns (uint256) {
return (price * FEE_PERCENT) / SCALE;
}
Set a Minimum Fee
To prevent fees from rounding down to zero, introduce a minimum fee:
uint256 constant MIN_FEE = 1e15;
function calculateFee(uint256 price) public pure returns (uint256) {
uint256 fee = (price * FEE_PERCENT) / SCALE;
return fee < MIN_FEE ? MIN_FEE : fee;
}