OrderBook

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

Price Oracle Manipulation Vulnerability in OrderBook Contract

Root + Impact

Description

  • The OrderBook contract is designed to facilitate peer-to-peer trading of ERC20 tokens, allowing sellers to list tokens at their desired price in USDC and buyers to purchase these tokens directly on-chain. Normally, the contract should ensure that orders represent reasonable market conditions by validating that the relationship between token amounts and prices falls within acceptable ranges.

  • The specific issue is that the contract fails to implement any validation of the price-to-amount ratio when creating or amending sell orders. It only checks that both values are non-zero, allowing users to create orders with arbitrary and potentially manipulative price points, such as selling 1 wei of a token for millions of USDC. This vulnerability undermines the contract's reliability as a price reference and enables market manipulation.

function createSellOrder(
address _tokenToSell,
uint256 _amountToSell,
uint256 _priceInUSDC,
uint256 _deadlineDuration
) public returns (uint256) {
if (!allowedSellToken[_tokenToSell]) revert InvalidToken();
@> if (_amountToSell == 0) revert InvalidAmount();
@> if (_priceInUSDC == 0) revert InvalidPrice();
if (_deadlineDuration == 0 || _deadlineDuration > MAX_DEADLINE_DURATION) revert InvalidDeadline();
// Rest of function...
}
function amendSellOrder(
uint256 _orderId,
uint256 _newAmountToSell,
uint256 _newPriceInUSDC,
uint256 _newDeadlineDuration
) public {
// Other validation checks...
@> if (_newAmountToSell == 0) revert InvalidAmount();
@> if (_newPriceInUSDC == 0) revert InvalidPrice();
if (_newDeadlineDuration == 0 || _newDeadlineDuration > MAX_DEADLINE_DURATION) revert InvalidDeadline();
// Rest of function...
}

Risk

Likelihood:

  • Reason 1: Any user with minimal resources can create orders with extreme price-to-amount ratios without any technical barriers or special conditions required.

  • Reason 2: The exploitation requires no complex setup or coordination, as the contract directly accepts any non-zero values for both amount and price parameters.

Impact:

  • Impact 1: External systems or protocols that use this contract as a price oracle would receive severely manipulated price data, potentially leading to significant financial losses through misinformed trading decisions or incorrect liquidations.

  • Impact 2: The platform's reputation and trustworthiness would be severely damaged when users observe unrealistic prices and manipulated market conditions, leading to reduced adoption and usage.

Proof of Concept

function test_priceManipulation() public {
// Setup: Fund attacker with 1 wETH and accomplice with 10M USDC
weth.mint(attacker, 1 * 10**18);
usdc.mint(accomplice, 10_000_000 * 10**6);
// Step 1: Attacker creates an extreme order
vm.startPrank(attacker);
weth.approve(address(book), 1);
uint256 orderId = book.createSellOrder(
address(weth),
1, // Just 1 wei
1_000_000 * 10**6, // 1 million USDC
1 days
);
vm.stopPrank();
// Step 2: Accomplice buys the manipulated order
vm.startPrank(accomplice);
usdc.approve(address(book), 1_000_000 * 10**6);
book.buyOrder(orderId);
vm.stopPrank();
// Result: Price recorded is 1M USDC per wei of wETH (10^24 USDC per wETH)
// Any system using this contract as a price oracle would be severely misled
}

Recommended Mitigation

// Add state variables for price bounds
mapping(address => uint256) public minPricePerToken;
mapping(address => uint256) public maxPricePerToken;
// Add validation function
function validatePriceAmount(address token, uint256 amount, uint256 price) internal view {
// Calculate price per token (normalized to 18 decimals for consistency)
uint256 pricePerToken = (price * 10**18) / amount;
// Enforce bounds if configured
if (minPricePerToken[token] > 0 && pricePerToken < minPricePerToken[token]) {
revert("Price too low");
}
if (maxPricePerToken[token] > 0 && pricePerToken > maxPricePerToken[token]) {
revert("Price too high");
}
}
// Add this call to both createSellOrder and amendSellOrder functions
validatePriceAmount(_tokenToSell, _amountToSell, _priceInUSDC);
// Add admin function to set bounds
function setPriceBounds(address token, uint256 min, uint256 max) external onlyOwner {
if (max > 0 && min > max) revert("Invalid bounds");
minPricePerToken[token] = min;
maxPricePerToken[token] = max;
}
Updates

Lead Judging Commences

yeahchibyke Lead Judge
about 1 month ago
yeahchibyke Lead Judge about 1 month ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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