OrderBook

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

[L-01] Reentrancy risk in withdrawFees() due to state update after transfer

Author Revealed upon completion

Root + Impact

Description – Normal behavior:
The withdrawFees() function allows the contract owner to withdraw accumulated USDC fees from the protocol. It transfers the entire totalFees amount to a provided receiver and then sets totalFees = 0 to reset fee accounting.

Issue:\

The transfer of funds (iUSDC.safeTransfer(...)) occurs before the internal state variable totalFees is reset. If a malicious token (masquerading as USDC or mistakenly whitelisted in the future) implements a reentrant transfer() function that calls back into withdrawFees(), it could drain more than intended. This violates the Checks-Effects-Interactions pattern and introduces a classic reentrancy vulnerability.

Impact:
An attacker can withdraw more than once before the state is cleared, leading to double-withdrawal of fees.

In the worst case, complete USDC loss from the contract if reentrancy is triggered repeatedly.

The function is onlyOwner, but it still risks exploit if a malicious token is whitelisted (see [L-02]) or if governance upgrades allow token swaps.

function withdrawFees(address _to) external onlyOwner {...@> iUSDC.safeTransfer(_to, totalFees);@> totalFees = 0;} Risk Likelihood:A malicious token can be whitelisted by mistake or through governance upgrade.

If such a token re-enters during safeTransfer, it can recursively call withdrawFees() before totalFees is cleared.

Impact:
Multiple withdrawals of the same fee amount.

Permanent loss of USDC held by protocol.

Loss of trust in fee-handling logic and financial safety of protocol.

Proof of Concept (PoC) contract MaliciousUSDC { OrderBook public book; address public receiver;

constructor(address _book, address _receiver) {
book = OrderBook(_book);
receiver = _receiver;
}
function transfer(address, uint256) external returns (bool) {
// reentrant call back to withdrawFees
book.withdrawFees(receiver);
return true;
}

}

// Attack:\

// Step 1: Malicious token gets set as USDC (accidentally or via upgrade).

// Step 2: Owner calls withdrawFees()\

// Step 3: Reentrancy drains more than intended

Recommended Mitigation

iUSDC.safeTransfer(_to, totalFees);
totalFees = 0;
uint256 fees = totalFees;
totalFees = 0;
iUSDC.safeTransfer(_to, fees);
//Additionally, consider using OpenZeppelin's ReentrancyGuard and applying nonReentrant modifier to this function for extra protection.
Updates

Lead Judging Commences

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

Support

FAQs

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