# Transfer-before-state-write double spend

Value leaves the contract before its debit is recorded, and the failure path does not roll it back

_Area: DeFi accounting and math bugs. Seen in 3 disclosed reports; latest 2025-02-26. Updated 2026-10-29._

## How it works

The contract assumes that once it sends value, the matching bookkeeping will follow. It does not. If the send happens first and the state write after it can fail, revert, or be skipped, the value is gone while the ledger still shows it owed. The broken assumption is atomicity: send and debit are treated as one step when they are two, and the error path between them is not a rollback.

## Where it shows up

- A transfer, `SendCoins`, or native send placed before the balance/stake/queue update it should offset.
- Error handling that logs and continues per item instead of reverting the whole operation (EndBlocker loops, batch processing).
- Outer wrappers that swallow inner reverts, leaving funds in the contract.
- Fallback or refund paths that forward `address(this).balance` or the contract's own holdings.
- Deposit helpers that trust a `from` parameter instead of checking `msg.value`.
- Boolean lock/guard conditions whose polarity is easy to invert.

## How to review

For every outbound value transfer, find the state write that records the debit and confirm the ordering and failure behavior. The invariant: at no point can value have left the contract while the corresponding liability is still recorded, and no failure path may skip the debit. Check that a failed inner call reverts the outer call, or that the debit is written before the send. Check that refund/fallback paths cannot reach funds that were never debited. Check that deposit functions verify `msg.value` rather than inferring payment from arguments. Read every guard boolean for inversion.

## Variants seen

- Swallowed inner revert strands relayer-sent native tokens; fallback sweeps the whole balance [E1](https://bugbountylibrary.com/r/c4%3A2023-09-maia%3AM-07.md).
- Zero-value deposit funded from contract balance via a trusted `from` parameter, plus inverted cancel lock enabling immediate drain [E2](https://bugbountylibrary.com/r/dhl%3A20250226-henlokart.md).
- Stake removal sends coins before updating stake state; per-item error handling leaves funds paid and stake still queued, allowing double withdrawal [E3](https://bugbountylibrary.com/r/sherlock%3A2024-06-allora%3AH-7.md).

## Impact and severity

Direct loss of contract-held funds: stranded airdrops swept [E1](https://bugbountylibrary.com/r/c4%3A2023-09-maia%3AM-07.md), 0.59 ETH drained [E2](https://bugbountylibrary.com/r/dhl%3A20250226-henlokart.md), double withdrawal of unstaked funds [E3](https://bugbountylibrary.com/r/sherlock%3A2024-06-allora%3AH-7.md). Severity is high where the error path is silent and the sweepable balance is shared, since one failure exposes all funds at once.

## Why fixes fail

Fixing only the ordering, or only the error handling, leaves the other half: a correct order still leaks if the wrapper swallows reverts [E1](https://bugbountylibrary.com/r/c4%3A2023-09-maia%3AM-07.md), and a reverting wrapper still leaks if the send precedes the write [E3](https://bugbountylibrary.com/r/sherlock%3A2024-06-allora%3AH-7.md).

## Evidence
- E1: https://bugbountylibrary.com/r/c4%3A2023-09-maia%3AM-07.md
- E2: https://bugbountylibrary.com/r/dhl%3A20250226-henlokart.md
- E3: https://bugbountylibrary.com/r/sherlock%3A2024-06-allora%3AH-7.md
