_executeUnwindOperation() contains two bugs:
Ignored parameter: The user-provided _collateralToWithdraw is packed into UnwindParams, encoded, transmitted through the flash loan, decoded — and then never read. A new local variable collateralToWithdraw is declared at L575 that shadows the struct field, making the user parameter dead code.
Wrong Aave parameter: The internal recalculation reads liquidationThreshold (return position 2) from getReserveConfigurationData() where it should read ltv (return position 1) — inconsistent with calculateOpenParams() (L386) which correctly reads ltv. The developer's own comments confirm the intent was to use LTV: the comment at L565 says "Get LTV" and the formula comment at L574 says "/ ltv", but the code reads liqThreshold.
Bug 1 — Parameter ignored:
The UnwindParams struct (L41-54) includes collateralToWithdraw (L45). The user provides this value to unwindPosition() (L238), it's stored at L246, encoded at L253, and decoded at L556. But _executeUnwindOperation() never reads unwindParams.collateralToWithdraw — it declares a new local variable with the same name at L575:
Bug 2 — Wrong Aave field:
IProtocolDataProvider.getReserveConfigurationData() returns (from IProtocolDataProvider.sol:L28-37):
| Position | Field | Example (WETH) |
|---|---|---|
| 0 | decimals |
18 |
| 1 | ltv |
8000 (80%) |
| 2 | liquidationThreshold |
8250 (82.5%) |
| 3 | liquidationBonus |
10500 |
| ... | ... | ... |
Position creation (calculateOpenParams, L386) correctly reads position 1:
Position unwinding (_executeUnwindOperation, L566-567) reads position 2 instead:
Since liquidationThreshold > ltv, the formula divides by a larger number, withdrawing less collateral than needed.
Likelihood:
Bug 2 triggers on every unwindPosition() call — the wrong Aave field is always used
Bug 1 means the user has no way to override the incorrect calculation
No special conditions required — it's a permanent code defect
Impact:
Insufficient collateral withdrawn: For WETH (ltv=8000, liqThreshold=8250), the function withdraws 3.03% less collateral than the position requires. For assets with larger ltv/liqThreshold gaps, the shortfall is greater.
DoS on unwind: Less collateral → swap produces fewer debt tokens → flash loan repayment fails at L588 (require(returnAmount >= totalDebt, "Insufficient funds to repay flash loan")) → transaction reverts. The user cannot unwind their position unless market conditions are favorable enough to absorb the 3% shortfall.
No user workaround: Because _collateralToWithdraw is ignored (Bug 1), the user cannot manually specify a larger withdrawal amount to compensate.
Two tests proving both bugs. Verified: both pass with forge test --match-path test/UnwindBugs.t.sol -vvv.
Two fixes are needed — one for each bug:
Fix 1: Either use the user-provided parameter or remove the dead field from the struct:
Option A — Use the parameter (gives user control over partial unwinds):
Option B — Remove the dead field from UnwindParams and unwindPosition() if internal calculation is preferred (after fixing Bug 1).
Either approach eliminates the dead code and makes the API honest about what it actually does.
Fix 2: Read ltv (position 1) instead of liquidationThreshold (position 2), matching calculateOpenParams():
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.