OrderBook

First Flight #43
Beginner FriendlySolidity
100 EXP
View results
Submission Details
Impact: medium
Likelihood: high
Invalid

Full Order Purchase Requirement Limits Protocol Practicality

Root + Impact

Description

The current protocol design requires users to purchase the entire amount (i.e `buyOrder`) specified in an order, with no support for partial purchases. For instance, if an order is listed as 5 WETH for 12,000 USDC, a buyer must pay the full 12,000 USDC to acquire all 5 WETH. They cannot buy a smaller portion, such as 1 WETH for 2,400 USDC, even if that’s all they can afford or need.
@> function buyOrder(uint256 _orderId) public {
// rest of logic
}

Risk

Likelihood:

  • Rigid Order Structure

  • Reduced Accessibility

Impact:

This inflexible requirement negatively affects the protocol in several ways:
- Financial Barrier: Users with limited funds are excluded from participating in orders that exceed their budget, reducing the pool of potential buyers.
- Market Inefficiency: Large orders may remain unfilled if no single buyer can afford the full amount, decreasing liquidity and slowing market activity.
- User Frustration: The lack of flexibility may discourage users, pushing them toward competing platforms that allow partial order filling.

Proof of Concept

Imagine a seller lists an order to sell 5 WETH for 12,000 USDC. A buyer with 3,000 USDC wants to purchase 1.25 WETH (at a proportional cost of 3,000 USDC). Under the current protocol, this buyer cannot proceed because partial purchases are not supported. As a result, the buyer is excluded, and the seller’s order may go unfilled if no one can afford the entire 12,000 USDC.

Recommended Mitigation

To improve practicality and accessibility, consider the following solutions:
- Enable Partial Order Filling: Update the protocol (e.g., the buyOrder function) to allow buyers to specify a desired token amount, up to the total available in the order.
- Proportional Pricing: Calculate the cost based on the portion purchased (e.g., buying 2.5 WETH from the example order would cost 6,000 USDC).
- Update Order State: After a partial purchase, adjust the order’s amountToSell to reflect the remaining tokens (e.g., reduce it from 5 WETH to 3.75 WETH after a 1.25 WETH purchase).
- Adjust Fees: Base any transaction fees on the purchased amount, not the full order, to maintain fairness.
These changes would make the protocol more flexible, inclusive, and efficient, likely increasing user adoption and market activity.
// --- Constants ---
//resr of contrants
+ uint256 public constant MIN_PARTIAL_AMOUNT = 1000; // Minimum amount for partial orders (adjustable)
// --- Events ---
//rest of events
event OrderCreated(
+ uint256 pricePerUnit,
);
event OrderFilled(
//rest of params
+ uint256 amountBought,
+ uint256 totalPrice,
+ bool orderFullyFilled
);
event OrderPartiallyFilled(
+ uint256 indexed orderId,
+ address indexed buyer,
+ address indexed seller,
+ uint256 amountBought,
+ uint256 remainingAmount,
+ uint256 totalPrice
);
// --- Errors ---
// rest of error logs
+ error InsufficientOrderAmount();
+ error AmountTooSmall();
+ function buyOrder(uint256 _orderId, uint256 _amountToBuy) 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();
+ if (_amountToBuy == 0) revert InvalidAmount();
+ if (_amountToBuy > order.amountToSell) revert InsufficientOrderAmount();
+ // Check minimum partial amount (prevents dust orders)
+ if (_amountToBuy < MIN_PARTIAL_AMOUNT && _amountToBuy != order.amountToSell) {
+ revert AmountTooSmall();
}
+ // Calculate total price for the amount being bought
+ uint256 totalPrice = (_amountToBuy * order.pricePerUnit) / (10 ** 18); // Assuming 18 decimals for most tokens
uint256 protocolFee = (totalPrice * FEE) / PRECISION;
uint256 sellerReceives = totalPrice - protocolFee;
// Update order state
+ order.amountToSell -= _amountToBuy;
+ bool orderFullyFilled = (order.amountToSell == 0);
+ if (orderFullyFilled) {
+ order.isActive = false;
+ }
// Execute transfers
iUSDC.safeTransferFrom(msg.sender, address(this), protocolFee);
iUSDC.safeTransferFrom(msg.sender, order.seller, sellerReceives);
- IERC20(order.tokenToSell).safeTransfer(msg.sender, order.amountToSell); //external call
+ IERC20(order.tokenToSell).safeTransfer(msg.sender, _amountToBuy);
totalFees += protocolFee;
// Emit appropriate events
+ if (orderFullyFilled) {
+ emit OrderFilled(_orderId, msg.sender, order.seller, _amountToBuy, totalPrice, true);
+ } else {
+ emit OrderPartiallyFilled(_orderId, msg.sender, order.seller, _amountToBuy, order.amountToSell, totalPrice);
+ }
}
Updates

Lead Judging Commences

yeahchibyke Lead Judge
about 1 month ago
yeahchibyke Lead Judge about 1 month ago
Submission Judgement Published
Invalidated
Reason: Design choice

Support

FAQs

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