Truncated Protocol Fee Results in Zero-Fee Collection
Description
The buyOrder
function computes a protocol fee using the expression:
uint256 protocolFee = (order.priceInUSDC * FEE) / PRECISION;
Where:
This calculation truncates decimal values due to the way Solidity handles integer division.
Risk
Likelihood: Medium
The truncation occurs whenever priceInUSDC
is low enough that (priceInUSDC * FEE) / PRECISION
results in a value less than 1. This is very likely to occur in low-value trades.
Impact: Low
Due to value truncation, the protocol fails to collect the intended fee on certain trades. This undermines the protocol’s revenue model, as both buyers and sellers can benefit from fee-free transactions simply by engaging in low-priced orders.
Proof of Concept
Example of sample scenario where value truncation happens:
Bob has 2 WETH, and wish to sell 0.005 ETH for 20 USDC
Dan buys Bob's listing
Due to value truncation, Bob is able to get the entire 20.00 USDC amount in return, bypassing the intended protocol fee
function test_value_truncation() public {
console2.log("Bob WETH balance before selling: ", weth.balanceOf(bob));
console2.log("Dan WETH balance before purchase: ", weth.balanceOf(dan));
console2.log("Total Fees Collected Before: ", book.totalFees());
console2.log("Bob USDC balance before: ", usdc.balanceOf(bob));
vm.startPrank(bob);
weth.approve(address(book), 5000000000000000);
book.createSellOrder({
_tokenToSell: address(weth),
_amountToSell: 5000000000000000,
_priceInUSDC: 20,
_deadlineDuration: 1 days
});
vm.stopPrank();
vm.startPrank(dan);
usdc.approve(address(book), 20);
book.buyOrder(1);
vm.stopPrank();
console2.log("\n Bob WETH balance after selling: ", weth.balanceOf(bob));
console2.log("Dan WETH balance after purchase: ", weth.balanceOf(dan));
console2.log("Total Fees Collected After: ", book.totalFees());
console2.log("Bob USDC balance before: ", usdc.balanceOf(bob));
}
the console output as below:
[PASS] test_value_truncation() (gas: 295505)
Logs:
Bob WETH balance before selling: 2000000000000000000
Dan WETH balance before purchase: 0
@> Total Fees Collected Before: 0
@> Bob USDC balance before: 0
Bob WETH balance after selling: 1995000000000000000
Dan WETH balance after purchase: 5000000000000000
@> Total Fees Collected After: 0
@> Bob USDC balance before: 20
Protocol fees are not deducted, despite the trade execution. Fee logic failed for small trades due to integer truncation.
Recommended Mitigation
- uint256 public constant FEE = 3; // 3%
- uint256 public constant PRECISION = 100;
+ uint256 public constant FEE = 3e18; // 3%
+ uint256 public constant PRECISION = 100e18;