OrderBook

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

Fee Calculation Precision Loss

Root + Impact - The fee calculation uses integer division which results in zero fees for orders with priceInUSDC less than 34, causing protocol revenue loss and enabling fee avoidance.

Description

  • The protocol is designed to charge a 3% fee on each transaction for revenue generation.

  • The fee calculation (order.priceInUSDC * FEE) / PRECISION uses integer division where FEE = 3 and PRECISION = 100, causing any order with priceInUSDC < 34 to result in zero fees.

// Root cause in the codebase
function buyOrder(uint256 _orderId) public {
// ... validation checks ...
order.isActive = false;
uint256 protocolFee = (order.priceInUSDC * FEE) / PRECISION; // @> Integer division: (33 * 3) / 100 = 99 / 100 = 0
uint256 sellerReceives = order.priceInUSDC - protocolFee;
iUSDC.safeTransferFrom(msg.sender, address(this), protocolFee); // @> Transfers 0 fees to protocol
iUSDC.safeTransferFrom(msg.sender, order.seller, sellerReceives);
// ... rest of function
}

Risk

Likelihood:

  • Users naturally experiment with small orders to test the system

  • Small-value trades (under $34) are common in DeFi for testing

Impact:

  • Protocol loses expected fee revenue on small orders

  • Creates economic incentive to fragment large orders into smaller ones

Proof of Concept - The following scenario demonstrates how the integer division vulnerability leads to complete fee loss:

// Test scenario demonstrating fee loss:
// 1. Create order with priceInUSDC = 33
// 2. protocolFee = (33 * 3) / 100 = 99 / 100 = 0 (integer division)
// 3. sellerReceives = 33 - 0 = 33
// 4. Protocol receives 0 USDC instead of expected 0.99 USDC
// 5. Buyer pays 33 USDC, seller receives 33 USDC, protocol gets nothing
contract TestFeeLoss {
function demonstratePrecisionLoss() public pure returns (uint256) {
uint256 priceInUSDC = 33;
uint256 FEE = 3;
uint256 PRECISION = 100;
uint256 protocolFee = (priceInUSDC * FEE) / PRECISION;
return protocolFee; // Returns 0 instead of expected 0.99
}
}

Recommended Mitigation - The most effective solution is to implement a minimum order value that ensures non-zero fees:

+ uint256 public constant MIN_ORDER_VALUE = 34; // Minimum to ensure non-zero fees
+ error OrderValueTooSmall();
function createSellOrder(
address _tokenToSell,
uint256 _amountToSell,
uint256 _priceInUSDC,
uint256 _deadlineDuration
) public returns (uint256) {
if (!allowedSellToken[_tokenToSell]) revert InvalidToken();
if (_amountToSell == 0) revert InvalidAmount();
if (_priceInUSDC == 0) revert InvalidPrice();
+ if (_priceInUSDC < MIN_ORDER_VALUE) revert OrderValueTooSmall();
if (_deadlineDuration == 0 || _deadlineDuration > MAX_DEADLINE_DURATION) revert InvalidDeadline();
// ... rest of function
}
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.