# Attacker can steal all fees from SFPM in pools with ERC777 tokens

ERC777 tokensToSend hook reenters SFPM during mint, transferring ERC1155 before feesBase is set, letting attacker burn with feesBase=0 and steal all pool fees.

- Payout: Contest reward
- Severity: high
- Program: Panoptic
- Credited: monrel
- Disclosed: 2024-06-24
- Write-up quality: 8/10
- Original: https://github.com/code-423n4/2023-11-panoptic-findings/issues/448
- Library page: https://bugbountylibrary.com/r/c4%3A2023-11-panoptic%3AH-01
- Tags: domain:web3 impact:funds-loss kind:contest platform:code4rena sev:high tech:evm tech:solidity technique:chained technique:source-review vuln:business-logic vuln:reentrancy vuln:reward-distribution

**Target:** SemiFungiblePositionManager (Uniswap V3 LP wrapper)
**Root cause:** mintTokenizedPosition mints ERC1155 and updates accountLiquidity before minting Uniswap liquidity and setting s_accountFeesBase; an ERC777 tokensToSend hook can transfer the ERC1155 in between, so registerTokenTransfer copies liquidity>0 with feesBase=0.
**Steps:**
1. Deploy attacker contract implementing ERC1820 tokensToSend hook and ERC1155Receiver.
2. Call `sfpm.mintTokenizedPosition(tokenId, positionSize, MIN_TICK, MAX_TICK)` on a pool containing an ERC777 token.
3. During `_mintLiquidity` token transfer, tokensToSend fires and calls `sfpm.safeTransferFrom(address(this), owner, tokenId, positionSize, "")` before `s_accountFeesBase[positionKey]` is written.
4. Receiver now holds ERC1155 with non-zero liquidity and feesBase=0.
5. Call `sfpm.burnTokenizedPosition(tokenId, positionSize, MIN_TICK, MAX_TICK)`; amountToCollect = current feesBase - 0, withdrawing all accrued fees.
6. Choose tick range with maximal feeGrowthInside to minimize required liquidity.
**How found:** Code-reading of mintTokenizedPosition ordering: ERC1155 mint and liquidity update precede Uniswap liquidity mint and feesBase write; recognized ERC777 tokensToSend as a reentrancy vector during the token transfer step.
**Exploitation:** Attacker mints a short option in a Uniswap V3 pool with an ERC777 token (e.g. VRA/WETH), reenters via tokensToSend to move the ERC1155 to a fresh address before feesBase is recorded, then burns the position to collect fees computed from zero baseline, draining all SFPM-owned fees in that pool.
**Insight:** When a contract mints an ERC1155/ERC721 before completing state writes and then performs an external token transfer, any ERC777 (or hook-bearing) token in the path lets the recipient reenter and snapshot stale accounting; check ordering of NFT mint vs. fee-baseline writes in LP wrappers.
