OrderBook

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

Medium: Reentrancy risk on refund-before-state when decreasing order amount

Root + Impact

Description

  • Normal behavior: Sellers might want to reduce the amount of their sell orders, triggering a partial refund. This should adjust internal state immediately so any callbacks from the refund see the correct, updated contract data.

  • Issue: The contract refunds the token difference before updating order.amountToSell. This sequencing allows a re-entrancy exploit: if the refunded token is ERC777 or has a malicious fallback, the contract can be called again while it still reflects the outdated order state.

if (_newAmountToSell < order.amountToSell) {
uint256 diff = order.amountToSell - _newAmountToSell;
@> token.safeTransfer(order.seller, diff); // refund before state change
}
order.amountToSell = _newAmountToSell;

Risk

Likelihood:

  • Triggered whenever a seller reduces their order and the refund uses a token with a callback or fallback capability.

  • This is realistic in systems interacting with modern ERC standards or maliciously crafted tokens.

Impact:

  • The seller could cancel the order or perform other manipulations during the callback, resulting in multiple refunds or inconsistent order data.

  • Can lead to duplicated payouts or protocol accounting failures.

Proof of Concept

function tokensReceived(...) external override {
orderBook.cancelSellOrder(targetOrderId); // re-enters before state updates
}

This PoC illustrates how a malicious token’s callback cancels the order while the contract still thinks the seller is entitled to a refund, creating multiple outflows from a single operation. This breaks the internal accounting assumptions of the contract.

Recommended Mitigation

if (_newAmountToSell < order.amountToSell) {
uint256 diff = order.amountToSell - _newAmountToSell;
+ order.amountToSell = _newAmountToSell; // state first
token.safeTransfer(order.seller, diff);
}

By moving the state change before the external call, the contract ensures that any callback sees the updated state, closing off re-entrancy avenues. This preserves atomic operation integrity, a foundational Solidity design principle.


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.