OrderBook

First Flight #43
Beginner FriendlySolidity
100 EXP
View results
Submission Details
Severity: low
Valid

Precision loss in fee calculation enables zero fee on small orders

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

// Root cause in the codebase with @> marks to highlight the relevant section
function buyOrder(uint256 _orderId) public {
Order storage order = orders[_orderId];
// Validation checks
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:

  • Occur on small orders

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 {
//Arrange
vm.startPrank(alice);
wbtc.approve(address(book), 2e8);
//Alice has created an order for $33 USDC
uint256 aliceId = book.createSellOrder(address(wbtc), 2e8, 0.000033e6, 2 days);
vm.stopPrank();
//Act
uint256 orderInUSDC = book.getOrder(aliceId).priceInUSDC;
//Due to integer truncation, protocolFee is 0
uint256 protocolFee = (orderInUSDC * book.FEE()) / book.PRECISION();
//Assert
assertEq(protocolFee, 0);
}

Recommended Mitigation

  1. Use higher precision constants

  2. Reject orders below a minimum value

  3. 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;
+ }
Updates

Lead Judging Commences

yeahchibyke Lead Judge
about 1 month ago
yeahchibyke Lead Judge about 1 month ago
Submission Judgement Published
Validated
Assigned finding tags:

Fee can be bypassed

Protocol Suffers Potential Revenue Leakage due to Precision Loss in Fee Calculation

Support

FAQs

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