# Cross-contract reentrancy in liquidation enables conversion of phantom shares to real shares, draining CollateralTracker assets

Cross-contract reentrancy during liquidation lets a malicious liquidator transfer phantom shares on the unsettled token, which revoke then mints as real shares, draining CollateralTracker.

- Payout: Contest reward
- Severity: high
- Program: Panoptic
- Credited: ht111111
- Disclosed: 2026-03-09
- Write-up quality: 8/10
- Original: https://code4rena.com/reports/2025-12-panoptic-next-core#h-02
- Library page: https://bugbountylibrary.com/r/c4%3A2025-12-panoptic-next-core%3AH-02
- Tags: domain:web3 impact:funds-loss kind:contest platform:code4rena sev:high tech:evm tech:solidity technique:chained technique:source-review vuln:accounting-error vuln:business-logic vuln:reentrancy

**Target:** CollateralTracker.settleLiquidation / PanopticPool._liquidate
**Root cause:** PanopticPool delegates phantom shares to both collateral trackers but revokes them sequentially, and settleLiquidation makes an ETH refund external call before the second tracker is settled. revoke() treats missing phantom shares as burned and credits _internalSupply, so shares transferred away during the callback become real supply.
**Steps:**
1. Set up pool with token1 liquidity and deploy malicious liquidator contract.
2. Trigger liquidation with `msg.value = 1 wei` so ct0.settleLiquidation takes the ETH refund path.
3. In the liquidator's `receive()` fallback, call `ct1.transferFrom(liquidatee, attacker, phantomAmount)` — numberOfLegs is 0 since positions were burned pre-settlement.
4. Return; ct1.settleLiquidation runs revoke, sees balance 0, and adds `type(uint248).max - balance` to `_internalSupply`.
5. Redeem the now-real shares for underlying assets.
**How found:** Code reading of the liquidation flow: noticed phantom shares delegated on both trackers but revoked sequentially, an external ETH refund inside settleLiquidation, and revoke logic that assumes missing shares were burned rather than transferred.
**Exploitation:** Attacker acts as liquidator, forces the ETH refund external call in ct0.settleLiquidation, and reenters ct1.transferFrom to move the still-delegated phantom shares off the liquidatee before ct1 settlement. revoke then compensates supply, converting phantom shares into redeemable real shares that are withdrawn for underlying tokens.
**Insight:** When a protocol mints temporary 'phantom'/virtual balances for solvency checks and later reconciles them by assuming any shortfall was burned, look for any path (external call, ERC20 transfer, callback) that can move those balances out before reconciliation — the reconciliation then mints real value.
