BORROW_SAFETY_MARGIN = 9500(95%) is applied only insidecalculateOpenParams, the off-chain view helper used to size the borrow before calling createLeveragedPosition`.
The on-chain execution path — createLeveragedPosition → _executeOpenOperation — forwards _borrowAmount directly to aavePool.borrow() without any safety margin check.
The sole on-chain enforcement is a post-position health factor check of HF > 1.0, which is Aave's own liquidation floor, not the 5% buffer the safety margin was designed to maintain.
A caller who constructs _borrowAmount directly — bypassing calculateOpenParams — can borrow up to the Aave LTV ceiling rather than the protocol's intended 95% of that ceiling. The resulting position opens with a materially thinner liquidation buffer than the protocol design assumes.
Likelihood:
The function is onlyOwner, so the bypassing caller is the owner acting against their own position — misconfiguration rather than a third-party exploit
Off-chain tooling, automation scripts, or future integrations that compute parameters independently may inadvertently omit the safety margin
The bypass is invisible: no event, no error, and no on-chain indicator distinguishes a safety-margin-respecting call from one that doesn't
Impact:
Concrete numbers for WETH (ltv = 8000, liqThreshold = 8250) at 2x leverage with 1 WETH collateral:
| Path | Borrow amount | Resulting HF | Buffer above liquidation |
|---|---|---|---|
Via calculateOpenParams (×0.95) |
1.52 WETH equiv. USDC | ≈ 1.085 | 8.5% |
| Direct call (Aave ceiling, ×1.00) | 1.60 WETH equiv. USDC | ≈ 1.031 | 3.1% |
A position opened at HF ≈ 1.031 (3.1% above liquidation) is liquidated by a collateral price move of roughly 3% — plausible within minutes during high volatility
When Aave liquidates a position, liquidators seize collateral at a bonus (typically 5–10%). The liquidation bonus comes out of the owner's collateral, producing a worse outcome than a timely voluntary unwind would have
The bypass works because calculateOpenParams and createLeveragedPosition are two separate, independent functions. The safety margin exists only in the view helper; the execution path has no reference to it.
Replace the post-position health factor floor with one that encodes the safety margin, making BORROW_SAFETY_MARGIN a genuine on-chain invariant regardless of how _borrowAmount was computed:
This change means any call to createLeveragedPosition — whether via calculateOpenParams or not — must result in a position with at least the intended buffer, closing the bypass.
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.