OrderBook

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

Lack of Minimum Price Check Allows for Fee Evasion

No minimal price limit that might leads protocol no fee received

Description

  • The protocol is designed to receive a 3% fee in USDC for each trade.

  • However, the createSellOrder() function does not enforce a minimum _priceInUSDC. If a user sets a price lower than 34, the fee calculation will round down to zero due to integer division, and the protocol will not receive any fee when the order is filled.

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; // e.q., 33 * 3 / 100 = 0
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: High

  • A user creates a sell order where the priceInUSDC is between 1 and 33.

  • This can happen unintentionally with low-value assets or intentionally by users wishing to avoid protocol fees.

Impact: Medium

  • The protocol does not collect its intended fee, leading to a direct loss of revenue.

  • If exploited systematically, this could undermine the platform's economic model, as fee collection is not guaranteed.

Proof of Concept

The following test demonstrates that when an order is created with a price of 33 USDC, the resulting protocol fee is zero.

function test_sellOrderWithLittlePrice() public {
vm.startPrank(alice);
wbtc.approve(address(book), 2e8);
uint256 aliceId = book.createSellOrder(address(wbtc), 2e8, 33, 2 days);
vm.stopPrank();
uint256 price = book.getOrder(aliceId).priceInUSDC;
uint256 fee = price * book.FEE() / book.PRECISION();
assertEq(fee, 0, "Fee should be zero for a price of 33");
vm.startPrank(dan);
usdc.approve(address(book), 200_000e6);
book.buyOrder(aliceId); // dan buys alice wbtc order
vm.stopPrank();
assert(wbtc.balanceOf(dan) == 2e8);
uint256 priceReceived = price - fee;
assert(usdc.balanceOf(alice) == priceReceived);
assert(book.totalFees() == 0); // Assert that no fee was collected
}

Recommended Mitigation

Enforce a minimum price in both the createSellOrder and amendSellOrder functions to ensure a fee is always collected. The minimum price should be 34, as this is the lowest value that results in a non-zero fee.

// In OrderBook.sol
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 < 34) revert InvalidPrice(); // Price must be high enough to generate a fee
if (_deadlineDuration == 0 || _deadlineDuration > MAX_DEADLINE_DURATION) revert InvalidDeadline();
// ...
}
function amendSellOrder(
uint256 _orderId,
uint256 _newAmountToSell,
uint256 _newPriceInUSDC,
uint256 _newDeadlineDuration
) public {
Order storage order = orders[_orderId];
// Validation checks
if (order.seller == address(0)) revert OrderNotFound(); // Check if order exists
if (order.seller != msg.sender) revert NotOrderSeller();
if (!order.isActive) revert OrderAlreadyInactive();
if (block.timestamp >= order.deadlineTimestamp) revert OrderExpired(); // Cannot amend expired order
if (_newAmountToSell == 0) revert InvalidAmount();
- if (_newPriceInUSDC == 0) revert InvalidPrice();
+ if (_newPriceInUSDC < 34) revert InvalidPrice(); // Price must be high enough to generate a fee
if (_newDeadlineDuration == 0 || _newDeadlineDuration > MAX_DEADLINE_DURATION) revert InvalidDeadline();
// ...
}
Updates

Lead Judging Commences

yeahchibyke Lead Judge
13 days ago
yeahchibyke Lead Judge 9 days 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.