OrderBook

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

L-04. Missing Check - Seller Can Buy Their Own Order

Root + Impact

  • Missing Self-Buy Prevention + Unnecessary Fee Payment

Description

  • Order book systems should prevent sellers from buying their own orders to avoid unnecessary transactions and maintain logical trading behavior.

  • The buyOrder function lacks validation to prevent sellers from purchasing their own orders, allowing them to pay protocol fees unnecessarily while essentially canceling their own order through a more expensive route.

function buyOrder(uint256 _orderId) public {
Order storage order = orders[_orderId];
// Validation checks
if (order.seller == address(0)) revert OrderNotFound();
// @> Missing check: seller can buy their own order
// if (order.seller == msg.sender) revert CannotBuyOwnOrder();
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;
// @> Seller pays protocol fee to buy back their own tokens
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:

  • Users accidentally attempt to buy their own orders due to UI confusion or misunderstanding

  • Automated trading bots may inadvertently target their own orders during scanning operations

Impact:

  • Sellers pay unnecessary protocol fees to retrieve their own tokens instead of using the free cancelSellOrder function

  • Wasteful gas consumption for a transaction that achieves the same result as order cancellation

  • Potential user confusion about order book mechanics and available actions

  • Protocol receives fees from non-genuine trading activity, potentially inflating fee metrics

Proof of Concept

The missing validation allows sellers to buy their own orders, resulting in unnecessary fee payments and gas costs:

Scenario Demonstration: A seller can buy back their own order and pay protocol fees unnecessarily:

function test_POC_buyOrder() public {
usdc.mint(alice, 200_000e6); // Mint 200,000 USDC for Alice
// 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();
assert(aliceId == 1);
assert(wbtc.balanceOf(alice) == 0);
assert(wbtc.balanceOf(address(book)) == 2e8);
vm.startPrank(alice);
usdc.approve(address(book), 200_000e6); // Approve the full 200,000 USDC
book.buyOrder(aliceId); // alice buys alice wbtc order
vm.stopPrank();
assert(wbtc.balanceOf(alice) == 2e8);
assert(usdc.balanceOf(alice) == 199999994600000000);
}

Recommended Mitigation

Add validation to prevent sellers from buying their own orders and guide them toward the proper cancellation mechanism:

+ // Add custom error for self-buy attempts
+ error CannotBuyOwnOrder();
function buyOrder(uint256 _orderId) public {
Order storage order = orders[_orderId];
// Validation checks
if (order.seller == address(0)) revert OrderNotFound();
+ if (order.seller == msg.sender) revert CannotBuyOwnOrder();
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);
totalFees += protocolFee;
emit OrderFilled(_orderId, msg.sender, order.seller);
}
Updates

Lead Judging Commences

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

Support

FAQs

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