Root + Impact
Description
## Summary
The `buyOrder` function is vulnerable to reentrancy attacks due to state
updates occurring after external calls. While the core order filling
logic is protected, the fee accounting can be manipulated by malicious token
contracts, potentially leading to inaccurate fee tracking.
## Vulnerability Details
The `buyOrder` function updates state after external calls, creating a reentrancy vulnerability:
function buyOrder(uint256 _orderId) public {
Order storage order = orders[_orderId];
if (order.seller == address(0)) revert OrderNotFound();
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);
}
--Issues:
- `totalFees` updated after external `safeTransfer` calls
- Malicious token contracts can reenter before fee accounting
- Fee tracking can become inaccurate
- Potential for fee manipulation attacks
## Impact
- Fee accounting manipulation: Malicious tokens can affect fee tracking
- Inaccurate protocol fees: `totalFees` may not reflect actual fees collected
- Potential for fee extraction: Sophisticated attacks could manipulate fee calculations
- Best practice violation: Missing reentrancy protection
## Affected Functions
- `buyOrder()` - Lines 193-210 (reentrancy vulnerability)
Risk
Likelihood:
Impact:
Proof of Concept
# Proof of Concept
1. User calls buyOrder with malicious token
2. Malicious token triggers reentrancy during safeTransfer
3. Second buyOrder call executes before totalFees is updated
4. Fee accounting becomes inaccurate
5. Protocol fee tracking is compromised
## The Problem
State is updated after external calls, allowing reentrancy:
function buyOrder(uint256 _orderId) public {
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);
totalFees += protocolFee;
}
## Attack Scenario
- Malicious token implements `_beforeTokenTransfer` hook
- Hook calls back to `buyOrder` before `totalFees` update
- Second execution manipulates fee calculations
- Fee accounting becomes inaccurate
- Protocol loses track of actual fees collected
Recommended Mitigation
--Vulnerable Code:
// Lines 193-210: buyOrder()
function buyOrder(uint256 _orderId) public {
// ... validation and state updates ...
// External calls that can trigger reentrancy
iUSDC.safeTransferFrom(msg.sender, address(this), protocolFee);
iUSDC.safeTransferFrom(msg.sender, order.seller, sellerReceives);
IERC20(order.tokenToSell).safeTransfer(msg.sender, order.amountToSell);
totalFees += protocolFee; // State updated after external calls
}
## How to Fix
### Solution 1: Use ReentrancyGuard (Recommended)
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract OrderBook is Ownable, ReentrancyGuard {
function buyOrder(uint256 _orderId) public nonReentrant {
// ... existing logic ...
totalFees += protocolFee; // Protected by reentrancy guard
}
}