OrderBook

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

Lack of Order Consistency Enables Frontrun Exploit

##Description

Normally, when a buyer interacts with the buyOrder function, they assume the order details ( the amount of tokens being sold and the price) are stable and trustworthy, having read them moments before.

However, the buyOrder function does not validate that the order's amountToSell or priceInUSDC match the buyer’s expectations. A malicious seller can frontrun the buyer and amend their order just before the buyer’s transaction is mined, reducing the token amount but keeping the price unchanged, causing the buyer to overpay and receive fewer tokens.

function buyOrder(uint256 _orderId) public {
Order storage order = orders[_orderId];
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;
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);
}

##Risk

Likelihood: HIGH

This will occur when a buyer places a transaction to buy an order and the seller frontruns the buyer in the same block by modifying the order using amendSellOrder.

Since mempool access is public and sellers control their own orders, this is trivial to perform and profitable in many scenarios.

Impact:

Buyers may lose significant value by receiving fewer tokens than expected while paying the full USDC price.

Trust in the protocol’s fairness and correctness is compromised, especially in high-volume or volatile token markets.

##Proof of Concept

-Alice creates an order to sell 2 WBTC for 180_000 USDC

-Dan reads order off-chain and prepares a transaction to buy it

-Alice frontruns and reduces the amount to 1 WBTC, keeping price the same

-Dan sends USDC and calls buyOrder without re-reading

-Dan ends up receiving only 1 WBTC for 180,000 USDC

function test\_Frontrun\_ModifiedOrder\_ShouldTransferLessTokens() public {
// alice creates sell order for wbtc
vm.startPrank(alice);
wbtc.approve(address(book), 2e8);
uint256 aliceId = book.createSellOrder(address(wbtc), 2e8, 180\_000e6, 2 days);
vm.stopPrank();
// Buyer reads order details (off-chain simulation)
uint256 orderId = 1;
vm.prank(alice);
book.amendSellOrder(aliceId, 1e8, 180_000e6, 0.5 days);
string memory aliceOrderDetails = book.getOrderDetailsString(aliceId);
console2.log(aliceOrderDetails);
assert(wbtc.balanceOf(alice) == 1e8);
assert(wbtc.balanceOf(address(book)) == 1e8);
vm.stopPrank();
//dan buys the order, thinking it's still the same thing
vm.startPrank(dan);
usdc.approve(address(book), 200_000e6);
book.buyOrder(aliceId);
vm.stopPrank();
console2.log(wbtc.balanceOf(dan));
console2.log(usdc.balanceOf(alice));
console2.log(usdc.balanceOf(dan));

##Recommended Mitigation

- function buyOrder(uint256 _orderId) public {}
+ function buyOrder(uint256 _orderId, uint256 expectedAmount, uint256 expectedPrice) public {
Order storage order = orders[_orderId];
+ if (order.amountToSell != expectedAmount) revert AmountMismatch();
+ if (order.priceInUSDC != expectedPrice) revert PriceMismatch();
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;
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);
}

This ensures the buyer’s expectations match the actual on-chain order state and prevents value loss from frontrunning modifications.

Updates

Lead Judging Commences

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

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.