OrderBook

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

Missing nonReentrant modifier on buyOrder, Lack of explicit reentrancy protection on buyOrder

Author Revealed upon completion

Root + Impact

Description

  • Describe the normal behavior in one or more sentences

  • Explain the specific issue or problem in one or more sentences

// Root cause in the codebase with @> marks to highlight the relevant section
Description (normal behavior):
The buyOrder function allows a buyer to purchase tokens from an active order, transferring USDC and the selling token atomically. It updates the order status before transferring funds.
Description (issue):
The function currently does not use a nonReentrant modifier. Although the order status is set to inactive before external calls, future modifications or external contract integrations could reintroduce reentrancy risk.
function buyOrder(uint256 _orderId) public {
...
order.isActive = false; // @> State updated here
...
iUSDC.safeTransferFrom(msg.sender, address(this), protocolFee);
iUSDC.safeTransferFrom(msg.sender, order.seller, sellerReceives);
IERC20(order.tokenToSell).safeTransfer(msg.sender, order.amountToSell);
}
Risk
Likelihood:
Medium.
This becomes exploitable if future logic introduces callbacks, external hooks, or if an ERC20 token’s transfer triggers external code execution.
Impact:
Potential loss of tokens or double-spending of orders if reentrancy is exploited.
Could lead to draining of tokens from the contract or unintended fills.

Risk

Likelihood:

  • Reason 1 // Describe WHEN this will occur (avoid using "if" statements)

  • Reason 2

Impact:

  • Impact 1

  • Impact 2

Proof of Concept

Explanation: While currently safe, adding an example hypothetical scenario:
// Malicious token contract
function transfer(address recipient, uint256 amount) external returns (bool) {
// During a transfer from OrderBook, attacker re-enters buyOrder()
}

Recommended Mitigation

- remove this code
+ add this code
+ import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol";
- contract OrderBook is Ownable {
+ contract OrderBook is Ownable, ReentrancyGuard {
- function buyOrder(uint256 _orderId) public {
+ function buyOrder(uint256 _orderId) public nonReentrant {
Explanation:
---
✅ The problem we are addressing
The original buyOrder function does not explicitly protect against reentrancy attacks.
What is a reentrancy attack?
A malicious external contract can call back into your contract before the first call finishes, potentially messing with your internal state or draining funds.
Famous example: The DAO hack on Ethereum (2016).
Why is it relevant here?
Your buyOrder function involves:
Updating order state
Transferring ERC20 tokens (which can call external code in non-standard tokens)
Handling user-controlled addresses
Currently, you do update order.isActive before transfers, which is good, but explicit prevention (defense-in-depth) is considered best practice in DeFi.
---
🔬 Mitigation explained line by line
1️⃣ Add ReentrancyGuard import
+import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol";
What is it?
A standard OpenZeppelin module that helps prevent reentrancy by using a status flag internally.
It defines a modifier called nonReentrant that can be applied to functions.
---
2️⃣ Modify contract inheritance
- contract OrderBook is Ownable {
+ contract OrderBook is Ownable, ReentrancyGuard {
What does this do?
Your contract now inherits ReentrancyGuard.
This enables you to use the nonReentrant modifier on any function you choose.
---
3️⃣ Add nonReentrant modifier to buyOrder
- function buyOrder(uint256 _orderId) public {
+ function buyOrder(uint256 _orderId) public nonReentrant {
What does nonReentrant do?
When buyOrder starts execution, it sets an internal status flag to "entered."
If, during that call, something tries to call buyOrder (or any other function using nonReentrant) again before the first call finishes, it reverts immediately.
After the function finishes, it resets the status to "not entered."
---
✅ Why is this a good mitigation?
Even if a malicious token or contract tries to re-enter during an external call (e.g., during safeTransfer), the second call will fail.
This explicitly protects the contract and signals to future maintainers and auditors that reentrancy has been considered.
---
🟢 Summary of each step
Change Explanation
import {ReentrancyGuard} Pulls in the OpenZeppelin helper for reentrancy protection.
, ReentrancyGuard in inheritance Adds reentrancy lock functionality to your contract.
nonReentrant on buyOrder Explicitly prevents nested (reentrant) calls during execution.
---
💬 Final note
Even though your original logic is fairly safe due to early state updates, adding nonReentrant is considered a best practice in DeFi, especially for high-value protocols.
---
✅ Let me know if you'd also like to mark other functions (e.g., withdrawFees) as nonReentrant, or see a diagram of how reentrancy works step by step!
Updates

Lead Judging Commences

yeahchibyke Lead Judge about 5 hours ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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