OrderBook

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

Lack of MEV protection in `OrderBook::createSellOrder`, `OrderBook::amendSellOrder`, `OrderBook::cancelSellOrder`, and `OrderBook::buyOrder` functions

[H-2] Lack of MEV protection in OrderBook::createSellOrder, OrderBook::amendSellOrder, OrderBook::cancelSellOrder, and OrderBook::buyOrder functions enables bots to steal petential profits from sellers and buyers

Description: Bots can frontrun or backrun createSellOrder, amendSellOrder, cancelSellOrder, buyOrder user transactions.

Impact: This results in either of the following happening:

  • Sellers won't be able to amend or cancel their underpriced sell orders losing potential profits as a result

  • Buyers won't be able to fill underpriced sell orders losing potential profits as a result

Proof of Concept:

Let's assume the market price of a token is 2,000 USDC.
Consider following scenarios:

Scenario 1

A bot sees a createSellOrder transaction (e.g., 100 tokens at 180,000 USDC, below market price) in the mempool and front-runs with a competing order at a slightly better price (e.g., 179,999 USDC) resulting in bot’s order attracting buyers first, leaving the user’s order unfilled or less competitive

Scenario 2

After a createSellOrder creates an underpriced order (e.g., 100 tokens at 180,000 USDC), a bot back-runs with buyOrder to buy it before others, reselling for 200,000 USDC, resulting potential buyers missing the bargain.

Scenario 3

Market price of a token increases from 2,000 USDC to 2,200 USDC. A user submits amendSellOrder to correct underpricing and increase an order’s price (e.g., from 180,000 USDC to 200,000 USDC for 100 tokens). A bot front-runs with buyOrder to buy at the original lower price. As a result amendSellOrder transaction fails wasting gas, user loses tokens and receives below-market USDC.

Scenario 4

After an amendSellOrder lowers the price to 180,000 USDC, a bot back-runs with buyOrder to buy the underpriced token, reselling for 200,000 USDC, resulting in potential buyers missing the bargain.

Scenario 5

A user submits cancelSellOrder for an underpriced order (e.g., 100 wETH at 180,000 USDC). A bot front-runs with buyOrder to buy before cancellation. As a result cancelSellOrder fails wasting gas, user loses tokens and receives below-market USDC.

Scenario 6

A user submits buyOrder for an underpriced order (100 tokens at 180,000 USDC). A bot front-runs with its own buyOrder, buying first and reselling for 200,000 USDC. User’s transaction fails, wasting gas

Recommended Mitigation:

Implement Commit-Reveal Scheme

Require users to submit orders in two steps: a commit phase (submitting a hashed order) and a reveal phase (revealing order details). This hides order parameters (e.g., amountToSell, priceInUSDC) from the public mempool during the commit phase, preventing bots from front-running or back-running based on visible order details.
This will prevent front-running of createSellOrder and amendSellOrder by hiding order prices and amounts, making it impossible for bots to submit competing orders (e.g., slightly cheaper orders). This will also reduce back-running of buyOrder by obscuring which orders are being created or amended, limiting bots’ ability to target underpriced orders. In addition, this will mitigate sandwich attacks by reducing mempool visibility of order parameters.

Implementation for createSellOrder:

Remove createSellOrder function and add commitSellOrder and revealSellOrder functions instead.

mapping(address => bytes32) private commitments;
uint256 public constant COMMITMENT_DELAY = 1; // 1 block delay
function commitSellOrder(bytes32 _commitment) external {
commitments[msg.sender] = _commitment;
emit CommitmentMade(msg.sender, _commitment, block.number);
}
function revealSellOrder(
address _tokenToSell,
uint256 _amountToSell,
uint256 _priceInUSDC,
uint256 _deadlineDuration,
bytes32 _salt
) external returns (uint256) {
bytes32 commitment = keccak256(abi.encode(msg.sender, _tokenToSell, _amountToSell, _priceInUSDC, _deadlineDuration, _salt));
if (commitments[msg.sender] != commitment) revert InvalidCommitment();
if (block.number <= COMMITMENT_DELAY + block.number) revert CommitmentNotMature();
delete commitments[msg.sender];
if (!allowedSellToken[_tokenToSell]) revert InvalidToken();
if (_amountToSell == 0) revert InvalidAmount();
if (_priceInUSDC == 0) revert InvalidPrice();
if (_deadlineDuration == 0 || _deadlineDuration > MAX_DEADLINE_DURATION) revert InvalidDeadline();
uint256 deadlineTimestamp = block.timestamp + _deadlineDuration;
uint256 orderId = _nextOrderId++;
IERC20(_tokenToSell).safeTransferFrom(msg.sender, address(this), _amountToSell);
orders[orderId] = Order({
id: orderId,
seller: msg.sender,
tokenToSell: _tokenToSell,
amountToSell: _amountToSell,
priceInUSDC: _priceInUSDC,
deadlineTimestamp: deadlineTimestamp,
isActive: true
});
emit OrderCreated(orderId, msg.sender, _tokenToSell, _amountToSell, _priceInUSDC, deadlineTimestamp);
return orderId;
}

Users submit a hash of order details (including a salt for uniqueness) in commitSellOrder, then reveal details in revealSellOrder after a short delay (e.g., 1 block).
Apply similar logic to amendSellOrder and cancelSellOrder (e.g., commit a hash of new parameters or cancellation intent).
For buyOrder, consider a commit-reveal to hide the targeted orderId.

Pros: Effectively hides order details from the mempool, reducing front-running, back-running, and sandwich attacks.

Cons: Increases complexity and gas costs (two transactions per order). Users must wait for the commitment delay, potentially reducing user experience.

Private Transaction Relays

Encourage or require users to submit transactions via private transaction relays (e.g., Flashbots, Eden Network, or MEV-geth) instead of the public mempool. These relays send transactions directly to miners/validators, bypassing public visibility.

Pros: Simple to implement (no contract changes), effective at hiding transactions, widely supported by existing infrastructure.

Cons: Relies on external infrastructure, which may not be available on all networks. Users may incur higher fees or delays with relays. Gas price caps could limit transaction inclusion during network congestion.

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:

Amends or cancellation of sell orders can be front-run

When a seller wants to amend or cancel their sell orders, a malicious entity can front-run their transactions and buy out the orders. This can be especially harmful when real-world prices of listed assets fluctuate and sellers want to adjust the prices listed in their orders.

Buy orders can be front-run and amended maliciously

A malicious seller can front-run a buy order for their order, and decrease the amount of assets to be sold. If the price is unchanged, the buy transaction fulfills, but the buyer gets lesser amount than expected.

Support

FAQs

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