Description
The `OrderBook` contract lacks reentrancy protection on key functions that interact with user funds and token transfers.
Specifically:
`buyOrder()`, `amendSellOrder()`, `cancelSellOrder()`, `withdrawFees()`
While `SafeERC20` is used, it does not protect against reentrancy attacks initiated by malicious or non-standard ERC20 tokens that may invoke reentrant calls via fallback or hook mechanisms (e.g., transfer, approve, transferFrom behaviors).
This violates a widely accepted best practice: using the Checks-Effects-Interactions (CEI) pattern along with nonReentrant modifiers in any function that handles external token transfers.
Risk
Impact:
A malicious token could re-enter functions like `withdrawFees()` or `buyOrder()`, potentially interfering with internal state updates like `totalFees`, `order.isActive`, etc.
Combined with CEI violations (which you’ve already flagged), this can lead to:
- Double-withdrawal of fees
- Inconsistent order status
- Unintended token drains
While such attacks are rare in well-behaved ERC20 tokens, many tokens (especially wrapped tokens, proxies, or composable contracts) have custom fallback behaviors that can break assumptions.
Proof of Concept
Imagine a malicious token that implements the following pattern:
```javascript
function transfer(address to, uint256 amount) public override returns (bool) {
OrderBook(orderBookAddress).withdrawFees(attackerAddress);
return true;
}
```
If the `OrderBook` contract sends such a token or receives it via `safeTransferFrom`, it executes the transfer, which then re-enters into a vulnerable function, bypassing internal logic due to improper CEI or no guard.
Recommended Mitigation
1. Inherit ReentrancyGuard from OpenZeppelin:
```javascript
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract OrderBook is Ownable, ReentrancyGuard {
...
}
```
2. Add nonReentrant modifier to sensitive external functions:
```javascript
function buyOrder(uint256 _orderId) public nonReentrant { ... }
function cancelSellOrder(uint256 _orderId) public nonReentrant { ... }
function amendSellOrder(...) public nonReentrant { ... }
function withdrawFees(address _to) public onlyOwner nonReentrant { ... }
```