OrderBook

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

Missing seller validation enables wash trading

Description

The OrderBook contract's buyOrder function lacks validation to prevent sellers from buying their own orders. This oversight enables wash trading, a form of market manipulation where the same entity acts as both buyer and seller in a transaction to create artificial market activity.

function buyOrder(uint256 _orderId) public {
Order storage order = orders[_orderId];
// Validation checks
if (order.seller == address(0)) revert OrderNotFound();
if (!order.isActive) revert OrderNotActive();
if (block.timestamp >= order.deadlineTimestamp) revert OrderExpired();
// Missing validation: if (msg.sender == order.seller) revert CannotBuyOwnOrder();
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);
}

Risk

Likelihood: High

  • Sellers have a direct financial incentive to engage in wash trading to manipulate market perception.

  • The protocol has no mechanisms to detect or prevent this behavior.

  • Wash trading is a common practice in unregulated markets where it's not explicitly prevented.

Impact: Medium to High

  • Artificial trading volume can mislead other users about the actual liquidity and demand for assets.

  • Wash traders can manipulate price discovery mechanisms and market sentiment.

  • The protocol's reputation could be damaged when wash trading is discovered.

  • The protocol collects fees from wash trades, potentially creating misleading revenue metrics.

Proof of Concept

The following test case demonstrates how a user can engage in wash trading by buying their own sell orders:

function testWashTrading() public {
// Setup: Alice creates a sell order
vm.startPrank(alice);
weth.mint(alice, 1e18);
weth.approve(address(book), 1e18);
uint256 orderId = book.createSellOrder(address(weth), 1e18, 2000e6, 1 days);
// Alice approves USDC spending to buy her own order
usdc.mint(alice, 2000e6);
usdc.approve(address(book), 2000e6);
// Alice buys her own order - this should be prevented but isn't
book.buyOrder(orderId);
vm.stopPrank();
// Result: Alice has successfully engaged in wash trading
// She paid a fee to the protocol but created artificial trading volume
// Verify the order was marked as filled
OrderBook.Order memory order = book.getOrder(orderId);
assertFalse(order.isActive);
// Verify the protocol collected fees from this wash trade
uint256 protocolFee = (2000e6 * 3) / 100; // 3% fee
assertEq(book.totalFees(), protocolFee);
}

Explanation

  1. The Wash Trading Process:

    • Alice creates a sell order for 1 WETH at a price of 2000 USDC

    • Instead of waiting for another trader, Alice buys her own order

    • The contract allows this transaction to proceed without any restrictions

  2. Token Flow Analysis:

    • Alice initially transfers 1 WETH to the contract when creating the sell order

    • When buying her own order, Alice transfers 2000 USDC (split between herself and protocol fees)

    • Alice receives back her original 1 WETH

    • Net result: Alice essentially paid only the protocol fee (60 USDC) while creating fake trading volume

  3. Market Manipulation Implications:

    • This activity creates an illusion of market activity and liquidity where none exists

    • The recorded trading volume is artificial and misleading to other market participants

    • The transaction appears as legitimate trading activity in the protocol's history

  4. Protocol Impact:

    • The protocol earns fees from wash trades, potentially creating misleading revenue metrics

    • Trading volume statistics become unreliable for measuring actual market interest

    • Market participants may make trading decisions based on artificial activity

Recommended Mitigation

Add a simple check to prevent sellers from buying their own orders:

function buyOrder(uint256 _orderId) public {
Order storage order = orders[_orderId];
// Validation checks
if (order.seller == address(0)) revert OrderNotFound();
if (!order.isActive) revert OrderNotActive();
if (block.timestamp >= order.deadlineTimestamp) revert OrderExpired();
+ if (msg.sender == order.seller) revert CannotBuyOwnOrder();
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);
}

Additionally, add the new error:

+ error CannotBuyOwnOrder();

This simple check prevents the most basic form of wash trading where a single address acts as both buyer and seller. However, it's worth noting that more sophisticated wash trading involving multiple colluding addresses would require additional off-chain monitoring and analysis.

Updates

Lead Judging Commences

yeahchibyke Lead Judge
14 days ago
yeahchibyke Lead Judge 10 days ago
Submission Judgement Published
Invalidated
Reason: Design choice

Support

FAQs

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