OrderBook

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

Missing Reentrancy Protection on buyOrder Function

Author Revealed upon completion

buyOrder() — Funds misdirection or reentrancy on token transfer

Description

  • Normally, the buyOrder function transfers funds from the buyer to the seller and updates the order status to inactive after checks.

  • However, due to lack of a nonReentrant modifier, an attacker could exploit the token being a malicious ERC20 that reenters the contract unexpectedly during safeTransfer.

function buyOrder(uint256 _orderId) public {
Order storage order = orders[_orderId];
...
@> order.isActive = false;
@> iUSDC.safeTransferFrom(msg.sender, address(this), protocolFee);
@> iUSDC.safeTransferFrom(msg.sender, order.seller, sellerReceives);
@> IERC20(order.tokenToSell).safeTransfer(msg.sender, order.amountToSell);
...
}

Risk

Likelihood:

  • Happens when a malicious ERC20 token with reentrancy vector is allowed or mimicked in test environments

  • Buy action is one of the most commonly executed operations, increasing exposure

Impact:

  • Reentrancy before state changes can bypass order deactivation or corrupt balances

  • May result in multiple token transfers for a single USDC payment, draining escrow

Proof of Concept

Attacker deploys malicious ERC20 with reentrancy in transfer()

Attacker creates order, buyer buys triggering buyOrder()

During transfer, attacker reenters and calls buyOrder again

Leads to repeated withdrawals

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./OrderBook.sol"; // Import the vulnerable OrderBook contract
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
// Malicious Token Contract with reentrancy on transfer
contract MaliciousToken is ERC20 {
address public targetOrderBook;
uint256 public attackOrderId;
bool private attacked;
constructor() ERC20("Malicious Token", "MAL") {}
function setTarget(address _orderBook, uint256 _orderId) external {
targetOrderBook = _orderBook;
attackOrderId = _orderId;
}
// Override transfer to perform reentrancy attack
function _transfer(address sender, address recipient, uint256 amount) internal override {
super._transfer(sender, recipient, amount);
// Trigger reentrancy during transfer
if (!attacked && targetOrderBook != address(0)) {
attacked = true;
OrderBook(targetOrderBook).buyOrder(attackOrderId);
}
}
function mint(address to, uint256 amount) external {
_mint(to, amount);
}
}

Recommended Mitigation

Import reentrancyguard library from openzeppline and applu nonReentrant to function buyOrder() and cancelSellOrder()

Create Mutexes or Locks in code blocks and follow Checks-Effects-Interactions.

//import ReentrancyGuard
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
//Modify function buyOrder(uint256 _orderId) public {
with function buyOrder(uint256 _orderId) public nonReentrant {
Updates

Lead Judging Commences

yeahchibyke Lead Judge
6 days ago
yeahchibyke Lead Judge about 6 hours ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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