# Early return skips accounting step

Early returns, empty branches, or try/catch that bypass accrual, refund, expiry, or cleanup before state is consumed

_Area: DeFi accounting and math bugs. Seen in 24 disclosed reports; latest 2026-05-18. Updated 2026-10-22._

## How it works
A function is supposed to do two things: change state and record the accounting that reflects it. An early return, an empty branch, or a swallowed failure lets the first half happen while the second half is skipped. The result is bookkeeping that no longer matches reality: balances, margins, rewards, or request statuses drift from what actually happened. The broken assumption is that every path through the function performs the same required steps.

## Where it shows up
- Guards combining equality with `||` on a signed amount, so a negative delta short-circuits and skips a decrement [E4](https://bugbountylibrary.com/r/sherlock%3A2024-05-elfi-protocol%3AH-29.md)
- Reject/refund branches that mark a request processed without reversing the original transfer [E6](https://bugbountylibrary.com/r/cantina%3Ahmx_january2025%3A3.2.2.md)
- Multi-step flows wrapped in try/catch where a later step clears state an earlier failed step should have consumed [E3](https://bugbountylibrary.com/r/c4%3A2024-01-salty%3AM-24.md)
- Exit/close paths that omit a required collect or cleanup call before a callee's emptiness check [E7](https://bugbountylibrary.com/r/cantina%3Aolas_lockbox_jan2024%3A3.2.1.md)
- One operation (redeem) missing precondition checks its siblings enforce [E5](https://bugbountylibrary.com/r/c4%3A2023-10-badger%3AM-02.md)
- Invariants keyed on a derived value (e.g. `closable==0`) that can be true while the real state still allows change [E1](https://bugbountylibrary.com/r/sherlock%3A2023-10-perennial%3AH-1.md)
- Shared `require`s on symmetric delegate/undelegate paths where expiry is valid only one way [E8](https://bugbountylibrary.com/r/c4%3A2023-08-verwa%3AH-04.md)

## How to review
For each function, list the state changes and the accounting records that must move together, then walk every branch (early return, empty else, catch, reject) and confirm the pair still holds. The invariant: on every exit path, recorded accounting equals actual token/position state. For request handlers, a consumed requestId must correspond to a completed transfer or refund. For wrapped CPI closes, all callee preconditions (fees and rewards owed) must be zeroed first. For guards, test the condition with negative, zero, and positive amounts.

## Variants seen
- Equality-based early return skipping a decrement on negative amounts [E4](https://bugbountylibrary.com/r/sherlock%3A2024-05-elfi-protocol%3AH-29.md)
- Reject branch consuming request without refund [E6](https://bugbountylibrary.com/r/cantina%3Ahmx_january2025%3A3.2.2.md)
- try/catch tolerance plus state clearing misallocating rewards [E3](https://bugbountylibrary.com/r/c4%3A2024-01-salty%3AM-24.md)
- Missing collect before close reverting withdrawals [E7](https://bugbountylibrary.com/r/cantina%3Aolas_lockbox_jan2024%3A3.2.1.md)
- Missing recovery-mode guard on redemption [E5](https://bugbountylibrary.com/r/c4%3A2023-10-badger%3AM-02.md)
- Derived-value invariant bypassed by crafted pending orders [E1](https://bugbountylibrary.com/r/sherlock%3A2023-10-perennial%3AH-1.md)
- Expiry check blocking undelegate/withdraw permanently [E8](https://bugbountylibrary.com/r/c4%3A2023-08-verwa%3AH-04.md)
- Mass action ending a game before cleanup runs [E2](https://bugbountylibrary.com/r/sherlock%3A2023-10-looksrare%3AH-3.md)

## Why fixes fail
Fixing one branch (e.g. adding a refund) leaves sibling paths with the same gap; the pattern recurs wherever a new early exit is added without re-checking the accounting pair.

## Impact and severity
Funds stuck or stolen, rewards misallocated, positions over-utilized, withdrawals permanently blocked [E7](https://bugbountylibrary.com/r/cantina%3Aolas_lockbox_jan2024%3A3.2.1.md)[E8](https://bugbountylibrary.com/r/c4%3A2023-08-verwa%3AH-04.md). Severity rises when no recovery function exists [E6](https://bugbountylibrary.com/r/cantina%3Ahmx_january2025%3A3.2.2.md) or when the skipped step is the only thing keeping an invariant true [E1](https://bugbountylibrary.com/r/sherlock%3A2023-10-perennial%3AH-1.md).

## Evidence
- E1: https://bugbountylibrary.com/r/sherlock%3A2023-10-perennial%3AH-1.md
- E2: https://bugbountylibrary.com/r/sherlock%3A2023-10-looksrare%3AH-3.md
- E3: https://bugbountylibrary.com/r/c4%3A2024-01-salty%3AM-24.md
- E4: https://bugbountylibrary.com/r/sherlock%3A2024-05-elfi-protocol%3AH-29.md
- E5: https://bugbountylibrary.com/r/c4%3A2023-10-badger%3AM-02.md
- E6: https://bugbountylibrary.com/r/cantina%3Ahmx_january2025%3A3.2.2.md
- E7: https://bugbountylibrary.com/r/cantina%3Aolas_lockbox_jan2024%3A3.2.1.md
- E8: https://bugbountylibrary.com/r/c4%3A2023-08-verwa%3AH-04.md
