OrderBook

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

Expired orders never release tokens (Stuck Funds)

Root + Impact

Description

  • By design, once an order’s deadline passes, it cannot be filled:

if (block.timestamp >= order.deadlineTimestamp) revert OrderExpired();

However, the contract does not automatically cancel or refund expired orders. The order’s isActive flag remains true indefinitely. In other words, if the seller (who is a regular user) forgets to call cancelSellOrder, the locked tokens remain in the contract forever, inaccessible. This is unlike a time-lock or vesting pattern, where after a deadline the beneficiary can withdraw funds.

Risk

Likelihood: High

  • Expiry is likely to be reached (max 3 days), and users may neglect to cancel.

  • There’s no automated cleanup or public mechanism to reclaim expired orders; only the original seller can call cancelSellOrder.

Impact: Medium

  • Sellers may lose access to their tokens after expiration if they fail to cancel. This is essentially a denial-of-access/loss for users. If multiple orders expire without cancellation, significant funds could become trapped.

Proof of Concept

// Seller creates an order with 1-day deadline
uint256 orderId = book.createSellOrder(token, 100, 1000e6, 1 days);
// Simulate time passing beyond the deadline
vm.warp(block.timestamp + 2 days);
// Buyer attempt to buy (will revert due to expiration)
book.buyOrder(orderId); // Fails with OrderExpired
// Seller has no further way to recover tokens except manual cancel
// (But if seller is offline or malicious, funds remain locked)

Recommended Mitigation

+ // Allow anyone to cancel and refund an expired order to unlock tokens
+ function cancelExpiredOrder(uint256 _orderId) external {
+ Order storage order = orders[_orderId];
+ if (!order.isActive) revert OrderAlreadyInactive();
+ if (block.timestamp < order.deadlineTimestamp) revert InvalidDeadline();
+ order.isActive = false;
+ IERC20(order.tokenToSell).safeTransfer(order.seller, order.amountToSell);
+ emit OrderCancelled(_orderId, order.seller);
+ }

This cancelExpiredOrder function allows the seller (or any user) to reclaim tokens after the deadline, similar to a timelock withdrawal. It prevents tokens from remaining indefinitely trapped when orders expire.

Updates

Lead Judging Commences

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

Expired orders can cause backlog

By design only `seller` can call `cancelSellOrder()` on their `order`. But when an `order` expires, and the `seller` doesn't have access to the protocol, the expired `order `should be be able to be cancelled by an `admin`.

Support

FAQs

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