# ZentraFinance: repayWithATokens burn clamp clears debt for free

Zentra's forked Aave aToken burn clamps to zero balance, so repayWithATokens clears debt while burning nothing, draining ~140K ctUSD.

- Payout: Real-world hack
- Severity: critical
- Program: ZentraFinance
- Disclosed: 2026-09-09
- Write-up quality: 8/10
- Original: https://github.com/SunWeb3Sec/DeFiHackLabs/blob/main/src/test/2026-09/ZentraFinance_exp.sol
- Library page: https://bugbountylibrary.com/r/dhl%3A20260909-zentrafinance
- Tags: domain:web3 impact:funds-loss kind:incident platform:defihacklabs sev:critical tech:evm tech:solidity technique:incomplete-fix technique:source-review vuln:business-logic vuln:precision-loss

**Target:** Aave V3 fork lending pool (AToken accounting) on Citrea
**Root cause:** AToken._burnScaled clamps amountScaled to the user's scaled balance without a max-difference check, so a caller holding zero aTokens has the burn reduced to zero (no-op) while Pool.executeRepay still clears the full variable debt.
**Steps:**
1. Flash 200,000 USDC.e from Algebra DEX pool `0x172D...1165`
2. `POOL.supply(USDC.e, 200000e6, self, 0)` to mint zUSDC collateral
3. `POOL.borrow(ctUSD, 140000e6, 2, 0, self)` to mint variableDebtZenctUSD
4. `POOL.repayWithATokens(ctUSD, debt+1, 2)` while holding zero zctUSD -> burn clamps to 0, debt cleared
5. `POOL.withdraw(USDC.e, max, self)` to reclaim full collateral
6. Repeat: supply 50 ctUSD, borrow 30 USDC.e, `repayWithATokens(USDC.e, debt+1, 2)` holding zero zUSDC, withdraw ctUSD
7. Repay flash 200,000 + 20 fee; keep ~140,000 ctUSD
**How found:** Code review of the forked AToken impl: noticed the _burnScaled safety clamp `if (amountScaled > scaledBalance) amountScaled = scaledBalance;` lacked a `<=1 wei` difference check, and that repayWithATokens is permissionless with onBehalfOf=msg.sender, enabling a zero-aToken caller to zero out the burn.
**Exploitation:** Single tx: flash 200K USDC.e, supply as collateral, borrow 140K ctUSD, call permissionless repayWithATokens with debt+1 while holding zero aTokens so the burn no-ops but debt is marked repaid, then withdraw collateral; repeat small leg for USDC.e; repay flash and keep borrowed funds.
**Insight:** When auditing Aave forks, diff the AToken _burnScaled/_burn clamp against upstream: a missing max-difference require turns a 1-wei ceil-rounding guard into a free-debt primitive via repayWithATokens, which is permissionless and uses msg.sender as onBehalfOf.
