# AaveV3StrategyV2 and PendleStrategyV2 ignore the holdingFee when claiming rewards

AaveV3StrategyV2 and PendleStrategyV2 claimRewards() deduct the default performanceFee instead of the custom holdingFee from FeeManager, mischarging protocol/users on every reward claim.

- Payout: Contest reward
- Severity: medium
- Program: Jigsaw
- Credited: KlosMitSoss
- Disclosed: 2025-08-11
- Write-up quality: 6/10
- Original: https://cdn.cantina.xyz/reports/cantina_competition_jigsaw_june2025.pdf#3.2.2
- Library page: https://bugbountylibrary.com/r/cantina%3Ajigsaw_june2025%3A3.2.2
- Tags: domain:web3 impact:funds-loss kind:contest platform:cantina sev:medium tech:evm tech:solidity technique:regression technique:source-review vuln:business-logic vuln:reward-distribution

**Target:** Solidity strategy contracts (PendleStrategyV2, AaveV3StrategyV2) reward-claim path
**Root cause:** claimRewards() reads performanceFee directly from strategyInfo instead of calling _takePerformanceFee(), which consults feeManager.getHoldingFee() for a per-holding custom fee before falling back to the default.
**Steps:**
1. Deploy/upgrade strategy to V2 with feeManager set
2. Set strategyInfo.performanceFee = 0 via strategyManager.updateStrategy
3. Call feeManager.setHoldingCustomFee(userHolding, strategy, 2000) (20%)
4. Invest as user, skip 100 days, call strategyManager.claimRewards(strategy, "")
5. Observe feeAddress reward balance == 0 despite 20% custom fee
**How found:** Compared the V2 claimRewards() implementation against withdraw(), noticing withdraw() routes through _takePerformanceFee()/feeManager.getHoldingFee() while claimRewards() still reads strategyInfo.performanceFee directly — a V1 leftover after the V2 feeManager feature was added.
**Exploitation:** Not an attack; fee accounting bug. Any reward claim silently uses the wrong fee rate, so the protocol under- or over-charges relative to the configured holdingFee, leaking value to users or the protocol on every claim.
**Insight:** When a protocol adds a new fee/override layer (feeManager) in a V2 upgrade, grep every fee-charging entry point for direct reads of the old default (strategyInfo.performanceFee); claim/collect paths are commonly missed while withdraw paths get migrated.
