OrderBook

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

Medium: Reentrancy in withdrawProceeds

Root + Impact

Description

  • Normal behavior: When users have accumulated proceeds from orders, they should be able to withdraw them securely. The balance should be zeroed before any external calls to prevent withdrawal logic from being re-entered.

  • Issue: The contract makes an external ERC20 call first, then zeroes the user balance. This sequence breaks Solidity’s checks-effects-interactions paradigm. A malicious ERC777 or ERC20 fallback can re-enter the withdrawProceeds function during the transfer, exploiting the unchanged balance to drain funds.

function withdrawProceeds() external {
@> IERC20(token).transfer(msg.sender, amount); // external interaction
@> balances[msg.sender] = 0; // state changed too late
}

Risk

Likelihood:

  • This vulnerability occurs whenever a user holds proceeds and withdraws them using a token that supports callbacks (ERC777 or a malicious ERC20).

  • The prevalence of ERC777 or custom fallback tokens makes this a practical attack vector.

Impact:

  • Attackers can drain the contract’s reserves by repeatedly withdrawing before their balance is zeroed, bypassing intended economic constraints.

  • Results in complete depletion of funds reserved for other legitimate users.

Proof of Concept

function tokensReceived(...) external override {
orderBook.withdrawProceeds(); // re-entrancy triggered during token transfer
}

Here, an attacker deploys a custom ERC777 with a tokensReceived hook that calls withdrawProceeds() again. Since the user balance isn't yet cleared, this second call succeeds, allowing the attacker to double (or infinitely) withdraw. Each re-entrant call reduces the contract’s pool meant for honest users.

Recommended Mitigation

function withdrawProceeds() external {
uint256 amount = balances[msg.sender];
+ balances[msg.sender] = 0; // set state first to block reentrancy
IERC20(token).transfer(msg.sender, amount);
}

Zeroing the balance before making the external call ensures that any re-entrant attempt sees a cleared balance, stopping repeated withdrawals. This pattern—known as checks-effects-interactions—is a critical Solidity security practice endorsed by the Ethereum community and OpenZeppelin contracts.


Updates

Lead Judging Commences

yeahchibyke Lead Judge
about 1 month ago
yeahchibyke Lead Judge about 1 month ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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