# PredyFinance: reentrancy in trade callback drains pool

PredyFinance pool reentrancy via trade callback let attacker register pair, take all WETH/USDC, supply as LP to bypass safety checks, then withdraw.

- Payout: Real-world hack
- Severity: high
- Program: PredyFinance
- Analysis by: DeFiHackLabs
- Disclosed: 2024-05-14
- Write-up quality: 7/10
- Original: https://github.com/SunWeb3Sec/DeFiHackLabs/blob/main/src/test/2024-05/PredyFinance_exp.sol
- Library page: https://bugbountylibrary.com/r/dhl%3A20240514-predyfinance
- Tags: domain:web3 impact:funds-loss kind:incident platform:defihacklabs sev:high tech:evm tech:solidity technique:chained vuln:business-logic vuln:reentrancy

**Target:** PredyPool smart contract on Arbitrum
**Root cause:** The trade function calls an attacker-controlled callback (predyTradeAfterCallback) before finalizing state, allowing reentrant calls to take() and supply() that manipulate LP balances and bypass PositionCalculator.checkSafe() and finalizeLock() checks.
**Steps:**
1. Attacker deploys contract and approves PredyPool for USDC and WETH.
2. Calls registerPair with malicious params (priceFeed = attacker contract, riskRatio = 100000001) to create a pair owned by attacker.
3. Calls trade with zero amounts, triggering predyTradeAfterCallback.
4. In callback: take(true, attacker, WETH.balanceOf(pool)) to drain WETH, then supply(pairId, true, WETH.balanceOf(attacker)) to bypass checkSafe().
5. In callback: take(false, attacker, USDC.balanceOf(pool)) to drain USDC, then supply(pairId, false, USDC.balanceOf(attacker)) to bypass finalizeLock().
6. After trade returns, calls withdraw(pairId, true, WETH.balanceOf(pool)) and withdraw(pairId, false, USDC.balanceOf(pool)) to extract all supplied assets.
**Exploitation:** Attacker registers a pair with themselves as owner and a malicious price feed, then calls trade() with zero amounts to trigger the callback. Inside the callback, they use take() to drain all WETH and USDC from the pool, then supply() the same amounts back as LP to satisfy safety checks. After the trade, they withdraw the LP tokens, netting the entire pool balance (~$464K).
**Insight:** When a protocol calls an external callback during trade settlement, check whether state changes in the callback can bypass post-trade safety checks like checkSafe() or finalizeLock(); registering a pair with attacker-controlled parameters (e.g., priceFeed) can enable such reentrancy.
