# Balance-delta accounting reentrancy

Balance snapshots around external calls that nested calls can inflate, skew, or zero out

_Area: DeFi accounting and math bugs. Seen in 22 disclosed reports; latest 2025-08-11. Updated 2026-10-23._

## How it works
A contract measures value received or spent as `balanceOf(after) - balanceOf(before)` around an external call. The assumption is that the delta reflects only this operation's own effect. That breaks when code the caller controls runs inside the snapshot window: a reentrant call, a token/pool callback, or an account hook. The nested movement lands inside the outer window, so the outer function credits value it never received, double-counts value it did, or reports zero when a third party moved the funds first.

## Where it shows up
- `balanceOf`/`selfbalance()` snapshots taken immediately before and after a call to an external, caller-influenced address (pool, adapter, token, NFT, fee recipient, account hook).
- Functions that mint shares or tokens from a measured delta, with no `nonReentrant`.
- Batch wrappers around a guarded internal function where the wrapper is unguarded or accepts empty input that skips the guarded path [E1](https://bugbountylibrary.com/r/c4%3A2022-11-non-fungible%3AH-01.md).
- Swap paths where only first/last tokens or user-supplied steps are trusted instead of the router's returned deltas [E2](https://bugbountylibrary.com/r/sherlock%3A2025-06-notional-exponent%3AH-1.md)[E5](https://bugbountylibrary.com/r/cantina%3Ajigsaw_june2025%3A3.1.7.md).
- Refund/dust helpers that send `selfbalance()` rather than a tracked remainder [E1](https://bugbountylibrary.com/r/c4%3A2022-11-non-fungible%3AH-01.md).
- User-controlled hooks (ERC-7579, ERC-4337) that execute between pre/post snapshots [E3](https://bugbountylibrary.com/r/cantina%3Asuperform_may2025%3A3.2.11.md).
- Income sources that anyone can push to the contract before the snapshot [E8](https://bugbountylibrary.com/r/sherlock%3A2024-06-leveraged-vaults%3AH-7.md).

## How to review
Grep for snapshot pairs, then verify guard coverage on the outer function, the batch wrapper, sibling functions crediting the same variable, and empty-input paths. The invariant: any value credited to a user must come from a delta no nested call can influence. Prefer the router's returned `amountOut` over a balance delta [E5](https://bugbountylibrary.com/r/cantina%3Ajigsaw_june2025%3A3.1.7.md). Note that `nonReentrant` alone is insufficient across contracts: a callback in one contract can reenter a sibling contract that shares the same accounting variable, so shared-state guards matter [E2](https://bugbountylibrary.com/r/sherlock%3A2025-06-notional-exponent%3AH-1.md)[E5](https://bugbountylibrary.com/r/cantina%3Ajigsaw_june2025%3A3.1.7.md).

## Variants seen
- Reentry through a malicious fee recipient reaching a dust helper [E1](https://bugbountylibrary.com/r/c4%3A2022-11-non-fungible%3AH-01.md).
- Attacker-controlled middle pool in a multihop swap polluting the delta [E2](https://bugbountylibrary.com/r/sherlock%3A2025-06-notional-exponent%3AH-1.md).
- Account hook draining funds between pre/post snapshots, zeroing a fee [E3](https://bugbountylibrary.com/r/cantina%3Asuperform_may2025%3A3.2.11.md).
- Malicious NFT `safeTransferFrom` satisfying an outer payment check via a nested sell [E4](https://bugbountylibrary.com/r/c4%3A2023-05-particle%3AH-04.md).
- Malicious token in a swap path reentering a deposit to double-count collateral [E5](https://bugbountylibrary.com/r/cantina%3Ajigsaw_june2025%3A3.1.7.md).
- Permissionless external claim pushing rewards before the snapshot, yielding a zero delta [E8](https://bugbountylibrary.com/r/sherlock%3A2024-06-leveraged-vaults%3AH-7.md).

## Impact and severity
High: the delta is trusted for minting or payout, so one reentry steals funds, over-mints shares, or strands rewards [E1](https://bugbountylibrary.com/r/c4%3A2022-11-non-fungible%3AH-01.md)[E2](https://bugbountylibrary.com/r/sherlock%3A2025-06-notional-exponent%3AH-1.md)[E4](https://bugbountylibrary.com/r/c4%3A2023-05-particle%3AH-04.md)[E5](https://bugbountylibrary.com/r/cantina%3Ajigsaw_june2025%3A3.1.7.md)[E8](https://bugbountylibrary.com/r/sherlock%3A2024-06-leveraged-vaults%3AH-7.md). Worse when the external address is fully attacker-controlled and the guard is missing on the outer or sibling path.

## Why fixes fail
Guarding only the internal function leaves the unguarded batch wrapper and empty-array path open [E1](https://bugbountylibrary.com/r/c4%3A2022-11-non-fungible%3AH-01.md); guarding one path but not the sibling crediting the same variable leaves the delta exploitable [E2](https://bugbountylibrary.com/r/sherlock%3A2025-06-notional-exponent%3AH-1.md)[E5](https://bugbountylibrary.com/r/cantina%3Ajigsaw_june2025%3A3.1.7.md).

## Evidence
- E1: https://bugbountylibrary.com/r/c4%3A2022-11-non-fungible%3AH-01.md
- E2: https://bugbountylibrary.com/r/sherlock%3A2025-06-notional-exponent%3AH-1.md
- E3: https://bugbountylibrary.com/r/cantina%3Asuperform_may2025%3A3.2.11.md
- E4: https://bugbountylibrary.com/r/c4%3A2023-05-particle%3AH-04.md
- E5: https://bugbountylibrary.com/r/cantina%3Ajigsaw_june2025%3A3.1.7.md
- E6: https://bugbountylibrary.com/r/dhl%3A20250728-avaxbifknpair.md
- E7: https://bugbountylibrary.com/r/cantina%3Adoppler%3A3.1.7.md
- E8: https://bugbountylibrary.com/r/sherlock%3A2024-06-leveraged-vaults%3AH-7.md
