Precision loss in fee calculation enables zero fee on small orders resulting in revenue loss for the protocol on small trades.
Description
-
With FEE = 3
and PRECISION = 100
, this represents a 3%
fee. However, for small order sizes (e.g., orders worth 1–33 USDC), the fee calculation can round down to zero due to integer truncation.
-
The protocol loses expected fee revenue due to low PRECISION
function buyOrder(uint256 _orderId) public {
Order storage order = orders[_orderId];
if (order.seller == address(0)) revert OrderNotFound();
if (!order.isActive) revert OrderNotActive();
if (block.timestamp >= order.deadlineTimestamp) revert OrderExpired();
order.isActive = false;
@> uint256 protocolFee = (order.priceInUSDC * FEE) / PRECISION;
uint256 sellerReceives = order.priceInUSDC - protocolFee;
iUSDC.safeTransferFrom(msg.sender, address(this), protocolFee);
iUSDC.safeTransferFrom(msg.sender, order.seller, sellerReceives);
IERC20(order.tokenToSell).safeTransfer(msg.sender, order.amountToSell);
totalFees += protocolFee;
emit OrderFilled(_orderId, msg.sender, order.seller);
}
Risk
Likelihood:
Impact:
-
Users to trade for free, and gain unfair economic advantage.
-
No fees get collected on micro-transactions leading to revenue loss
Proof of Concept
The protocol accumulated 0 fee on the given order
function test_precision_loss_in_fee_calculation() external {
vm.startPrank(alice);
wbtc.approve(address(book), 2e8);
uint256 aliceId = book.createSellOrder(address(wbtc), 2e8, 0.000033e6, 2 days);
vm.stopPrank();
uint256 orderInUSDC = book.getOrder(aliceId).priceInUSDC;
uint256 protocolFee = (orderInUSDC * book.FEE()) / book.PRECISION();
assertEq(protocolFee, 0);
}
Recommended Mitigation
Use higher precision constants
Reject orders below a minimum value
Enforce a mininum fee: Ensure that the protocol always charges at least a minimal non-zero fee.
- uint256 public constant FEE = 3; // 3%
- uint256 public constant PRECISION = 100;
+ FEE = 300_000;
+ PRECISION = 10_000_000;
+ // Require price to be high enough to collect at least 1 unit of fee
+ if ((priceInUSDC * FEE) / PRECISION == 0) revert OrderTooSmall();
+ uint256 protocolFee = (order.priceInUSDC * FEE) / PRECISION;
+ if (protocolFee == 0 && FEE > 0) {
+ protocolFee = MINIMUM_FEE;
+ }