OrderBook

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

No Slippage Protection for Buyers Against Price Changes

Root + Impact

Description

  • The OrderBook contract is designed to provide fair trading where buyers can confidently purchase orders knowing they won't pay more than expected, similar to how traditional DEXs provide slippage protection to prevent unfavorable execution prices.

  • The buyOrder() function lacks slippage protection, forcing buyers to accept whatever price is current at execution time without allowing them to specify a maximum acceptable price. This creates vulnerability to price manipulation and unfavorable execution, especially during volatile market conditions or network congestion.

// Root cause in the codebase with @> marks to highlight the relevant section
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; // Uses current price
@> uint256 sellerReceives = order.priceInUSDC - protocolFee; // No max price check
@> iUSDC.safeTransferFrom(msg.sender, address(this), protocolFee); // Buyer pays unknown amount
@> iUSDC.safeTransferFrom(msg.sender, order.seller, sellerReceives); // No slippage protection
IERC20(order.tokenToSell).safeTransfer(msg.sender, order.amountToSell);
}

Risk

Likelihood:

  • Reason 1: Occurs whenever there's network congestion or sellers amend orders while buyer transactions are pending

  • Reason 2: No protection mechanism exists in the current implementation, making this inevitable during price volatility

Impact:

  • Impact 1: Financial losses for buyers who pay more than intended due to lack of price protection

  • Impact 2: Platform becomes unusable during market volatility, losing competitive advantage to DEXs with proper slippage protection

Proof of Concept

The vulnerability becomes critical when combined with order amendments or during network congestion, where buyers cannot control their execution price and may face significant unexpected costs.

// Simplified PoC demonstrating slippage vulnerability
contract SlippageVulnerabilityPoC {
function testNoSlippageProtection() public {
// 1. Seller creates order with initial price
uint256 orderId = orderBook.createSellOrder(
address(weth), 1 ether, 200000e6, 1 hours // 200,000 USDC
);
// 2. Buyer sees order and expects to pay this amount
uint256 expectedPrice = 200000e6; // $2,000/ETH
uint256 buyerBalance = usdc.balanceOf(buyer);
// 3. Price increases before buyer's transaction executes
orderBook.amendSellOrder(orderId, 1 ether, 250000e6, 1 hours); // $2,500/ETH
// 4. Buyer has no protection - must accept new price
vm.prank(buyer);
orderBook.buyOrder(orderId); // No maxPrice parameter!
// 5. Verify buyer paid more than intended
uint256 actualCost = buyerBalance - usdc.balanceOf(buyer);
uint256 unexpectedLoss = actualCost - expectedPrice;
console.log("Expected cost:", expectedPrice);
console.log("Actual cost paid:", actualCost);
console.log("Unexpected slippage loss:", unexpectedLoss);
assert(actualCost > expectedPrice); // Buyer paid 50,000 USDC more
assert(unexpectedLoss == 50000e6); // 25% slippage with no protection
// Output: Unexpected slippage loss: 50,000 USDC
}
function testVolatileMarketConditions() public {
// Demonstrate how this becomes unusable in volatile markets
uint256 orderId = orderBook.createSellOrder(
address(weth), 1 ether, 200000e6, 1 hours
);
// Multiple price changes while buyer transaction pending
orderBook.amendSellOrder(orderId, 1 ether, 220000e6, 1 hours); // +10%
orderBook.amendSellOrder(orderId, 1 ether, 250000e6, 1 hours); // +25%
orderBook.amendSellOrder(orderId, 1 ether, 300000e6, 1 hours); // +50%
// Buyer transaction executes at final price with no control
vm.prank(buyer);
orderBook.buyOrder(orderId); // Forced to pay 300,000 USDC instead of 200,000
console.log("Final execution 50% above expected price - platform unusable");
}
}

Recommended Mitigation

Add slippage protection by requiring buyers to specify their maximum acceptable price, preventing execution at unfavorable prices.

- function buyOrder(uint256 _orderId) public {
+ function buyOrder(uint256 _orderId, uint256 _maxPriceWillingToPay) 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();
+ if (order.priceInUSDC > _maxPriceWillingToPay) revert PriceTooHigh();
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);
totalFees += protocolFee;
emit OrderFilled(_orderId, msg.sender, order.seller);
}
+ // Add custom error
+ error PriceTooHigh();
Updates

Lead Judging Commences

yeahchibyke Lead Judge
about 2 months ago
yeahchibyke Lead Judge about 1 month ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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