calculateOpenParams() uses a cached flashLoanFeeBps storage variable (L433) for its flash loan repayment sanity check instead of querying Aave's actual fee via IPool.FLASHLOAN_PREMIUM_TOTAL(). If Aave governance changes the flash loan premium, the cached value becomes stale and the view helper produces incorrect validation results.
The contract imports IPool (which exposes FLASHLOAN_PREMIUM_TOTAL()) but never queries it — instead, it duplicates this state in flashLoanFeeBps (L115), initialized to 9 at L186 and updatable only via the owner-gated setFlashLoanFee() (L272).
The stale fee affects the sanity check at L433-441:
Likelihood:
Aave fee changes are governance-gated, publicly announced, and historically rare
The owner has setFlashLoanFee() to resync, but must do so manually
The 5% safety margin makes the sanity check at L441 trivially satisfied even with significant fee drift
Impact:
calculateOpenParams() may validate parameters against an incorrect fee baseline — misleading but not dangerous since the function is view-only
No funds at risk: if parameters derived from the helper are insufficient, the actual execution reverts safely at L522 (require(returnAmount >= totalDebt)) with no state changes
Inconsistent code pattern: the contract duplicates state it doesn't own (flashLoanFeeBps shadows IPool.FLASHLOAN_PREMIUM_TOTAL())
The PoC demonstrates that flashLoanFeeBps and Aave's FLASHLOAN_PREMIUM_TOTAL can diverge, causing calculateOpenParams() to use a stale fee in its sanity check. It also shows that the BORROW_SAFETY_MARGIN absorbs the discrepancy in practice.
Query FLASHLOAN_PREMIUM_TOTAL() dynamically in calculateOpenParams() instead of using the cached storage variable. This eliminates the state duplication and ensures the helper always reflects Aave's actual fee:
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.
The contest is complete and the rewards are being distributed.