OrderBook

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

Inconsistent Expired Order Handling + Market Manipulation

Author Revealed upon completion

Description

  • The contract should handle expired orders consistently across all functions to prevent manipulation and ensure fair trading.

  • Different functions have inconsistent behavior for expired orders: amendSellOrder prevents modifications, buyOrder prevents purchases, but cancelSellOrder allows cancellation, creating an unfair advantage for sellers.

function amendSellOrder(uint256 _orderId, uint256 _newAmountToSell, uint256 _newPriceInUSDC, uint256 _newDeadlineDuration) public {
Order storage order = orders[_orderId];
// ...
if (block.timestamp >= order.deadlineTimestamp) revert OrderExpired(); // Cannot amend expired order
// ...
}
function cancelSellOrder(uint256 _orderId) public {
Order storage order = orders[_orderId];
// ...
order.isActive = false;
IERC20(order.tokenToSell).safeTransfer(order.seller, order.amountToSell);
// ...
}
function buyOrder(uint256 _orderId) public {
Order storage order = orders[_orderId];
// ...
if (block.timestamp >= order.deadlineTimestamp) revert OrderExpired(); // Cannot buy expired orders
// ...
}

Risk

Likelihood:

  • When market conditions change unfavorably for sellers after order expiration

  • When sellers want to wait for better market conditions before retrieving their tokens

  • When buyers attempt to purchase expired orders at favorable prices

Impact:

  • Sellers can manipulate order timing by letting orders expire then cancelling when market moves against them

  • Buyers lose opportunity to purchase tokens at agreed prices due to seller manipulation

  • Market becomes unfair and trust in the protocol decreases

Proof of Concept

Market Manipulation Scenario: This demonstrates how sellers can exploit inconsistent expiration handling to manipulate market timing.

function testExpiredOrderManipulation() public {
// Alice creates sell order at high price
vm.startPrank(alice);
wbtc.approve(address(book), 1e8);
uint256 orderId = book.createSellOrder(address(wbtc), 1e8, 100000e6, 1 hours);
vm.stopPrank();
// Time passes and order expires
vm.warp(block.timestamp + 2 hours);
// Market price drops, Dan wants to buy at the previously high price
vm.startPrank(dan);
usdc.approve(address(book), 100000e6);
vm.expectRevert(OrderBook.OrderExpired.selector);
book.buyOrder(orderId); // Cannot buy expired order
vm.stopPrank();
// But Alice can still cancel and retrieve tokens
vm.startPrank(alice);
book.cancelSellOrder(orderId); // This succeeds!
vm.stopPrank();
// Alice gets her tokens back despite order being expired
assertEq(wbtc.balanceOf(alice), 1e8);
}

Explanation of Manipulation:

  1. Alice creates a sell order at 100,000 USDC when market conditions are favorable

  2. Order expires after 1 hour, but market price drops significantly

  3. Dan sees the expired order at an attractive price and wants to buy

  4. buyOrder correctly prevents purchase of expired orders

  5. However, cancelSellOrder allows Alice to retrieve her tokens from expired orders

  6. Alice benefits from asymmetric behavior: buyers can't buy expired orders, but sellers can cancel them

  7. This creates unfair market manipulation where sellers can "wait and see" after expiration

  8. In a fair system, expired orders should either be automatically fulfilled or become permanently unavailable

Recommended Mitigation

function cancelSellOrder(uint256 _orderId) public {
Order storage order = orders[_orderId];
// Validation checks
if (order.seller == address(0)) revert OrderNotFound();
if (order.seller != msg.sender) revert NotOrderSeller();
if (!order.isActive) revert OrderAlreadyInactive();
+ if (block.timestamp >= order.deadlineTimestamp) revert OrderExpired(); // Prevent cancellation of expired orders
// Mark as inactive
order.isActive = false;
// Return locked tokens to the seller
IERC20(order.tokenToSell).safeTransfer(order.seller, order.amountToSell);
emit OrderCancelled(_orderId, order.seller);
}

Why this works:

  • Ensures consistent behavior: if orders can't be bought after expiration, they can't be cancelled either

  • Eliminates seller advantage and manipulation opportunities

  • Creates fair market conditions for all participants

  • Expired orders become permanently inactive, requiring no further action

Alternative approaches to consider:

  1. Auto-execution: Allow purchases of expired orders at a penalty rate

  2. Grace period: Allow short window for cancellation after expiration

  3. Liquidation mechanism: Automatically liquidate expired orders to a treasury

  4. Partial fills: Allow buyers to partially fill orders before full expiration

Updates

Lead Judging Commences

yeahchibyke Lead Judge
6 days ago
yeahchibyke Lead Judge about 5 hours ago
Submission Judgement Published
Invalidated
Reason: Design choice

Support

FAQs

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