# An attacker can re-enter the InfinityPool contract to double account when adding liquidity

pour()'s payment callback lets an attacker re-enter via doActions (no nonReentrant) to inflate pool balance, then drain/collect both positions and steal another LP's deposit.

- Payout: Contest reward
- Severity: high
- Program: InfinityPools
- Credited: Spearmint
- Disclosed: 2025-01-13
- Write-up quality: 7/10
- Original: https://cdn.cantina.xyz/reports/cantina_competition_infinitypools_october2024.pdf#3.1.2
- Library page: https://bugbountylibrary.com/r/cantina%3Ainfinitypools_october2024%3A3.1.2
- Tags: domain:web3 impact:funds-loss kind:contest platform:cantina sev:high tech:evm tech:solidity technique:chained technique:source-review vuln:business-logic vuln:reentrancy

**Target:** InfinityPool.pour() payment callback / doActions()
**Root cause:** pour() verifies payment by balance delta after invoking an external infinityPoolPaymentCallback, but doActions() lacks nonReentrant, so the callback can re-enter and add liquidity, inflating the balance used to satisfy the check.
**Steps:**
1. Alice calls `infinityPool.pour(0, 1, liquidityAmount, "")` to add liquidity.
2. Attacker calls `infinityPool.pour(0, 1, liquidityAmount, data)` with non-empty data to trigger `infinityPoolPaymentCallback`.
3. Inside the callback, attacker calls `infinityPool.doActions([POUR], [abi.encode(0,1,liquidityAmount)], attacker, "")` to re-enter and add liquidity without paying.
4. The re-entrant pour increases pool token balance, satisfying the `balance0Before + expectedToken0` check for the outer pour.
5. Attacker waits, calls `drain(1,...)` and `drain(2,...)`, then `collect(1,...)` and `collect(2,...)` to withdraw both positions, taking Alice's deposit.
**How found:** Reviewer noticed pour() relies on a balance-delta check around an external callback and that doActions() lacks the nonReentrant modifier, enabling re-entry during the payment callback.
**Exploitation:** Attacker contract calls pour with a data payload; the payment callback re-enters doActions to POUR again, crediting a second position funded by the pool's existing balance rather than the attacker. After epochs, drain and collect withdraw both positions, siphoning the honest LP's deposit.
**Insight:** When a function validates payment via before/after balance deltas around an external callback, check every state-changing entry point reachable from that callback for reentrancy guards; an unguarded sibling function like doActions can satisfy the delta check with someone else's funds.
