Stratax Contracts

First Flight #57
Beginner FriendlyDeFi
100 EXP
View results
Submission Details
Severity: high
Valid

Permanent Collateral Lock in `unwindPosition` Calculation

Root + Impact

Description

  • Users engaging in leveraged positions expect to retrieve all their remaining collateral after fully repaying their debt.

  • The unwinding logic calculates the withdrawal amount based on the liquidation threshold risk parameter instead of the user's actual equity, leaving the surplus safety margin permanently locked in the protocol.

// src/Stratax.sol:575
// @> Root cause: Formula uses liqThreshold to calculate min required collateral, ignoring actual surplus
uint256 collateralToWithdraw = (
_amount * debtTokenPrice * (10 ** IERC20(unwindParams.collateralToken).decimals()) * LTV_PRECISION
) / (collateralTokenPrice * (10 ** IERC20(_asset).decimals()) * liqThreshold);

Risk

Likelihood:

  • The calculation is hardcoded into the only available unwind function path.

  • Every user attempting a full unwind will experience this fund lock.

Impact:

  • Users lose access to significant portions of their principal (often >20%).

  • The protocol fails to fulfill its primary solvency guarantee of returning user funds.

Proof of Concept

This test case demonstrates the issue by simulating a full unwind operation. It sets up a scenario with 100 ETH collateral and 50k USDC debt, which is a healthy position. When the user attempts to fully repay the debt, the protocol calculates the withdrawal amount using the flawed formula. The assertion confirms that the actual withdrawn amount (~31.25 ETH) is significantly less than the user's total collateral (100 ETH), effectively locking the remaining ~68.75 ETH.

function test_ExecutionLocksCollateral() public {
uint256 debtToRepay = 50_000 * 1e6; // $50k Debt
// User has 100 ETH Collateral ($200k value)
// Position is healthy.
vm.prank(stratax.owner());
stratax.unwindPosition(
address(weth),
1,
address(usdc),
debtToRepay,
hex"",
0
);
// Expected Logic in _executeUnwindOperation:
// Returns minimal collateral to cover healthy factor=1.0 at 80% LT
// Formula: Returns only ~31.25 ETH.
// Funds Locked: 100 ETH - 31.25 ETH = 68.75 ETH is left in Aave.
uint256 actualWithdrawn = pool.lastWithdrawAmount();
uint256 restrictedAmount = 31250000000000000000;
assertEq(actualWithdrawn, restrictedAmount, "Should withdraw collateral based on Risk (LT)");
// The remaining collateral is now inaccessible because debt is 0.
}

Recommended Mitigation

Remove the restrictive calculation that bases withdrawal on the Liquidation Threshold. Instead, allow the user to specify the amount of collateral they wish to withdraw in unwindParams, or default to withdrawing the full collateral balance when performing a full debt repayment.

- uint256 collateralToWithdraw = (
- _amount * debtTokenPrice * (10 ** IERC20(unwindParams.collateralToken).decimals()) * LTV_PRECISION
- ) / (collateralTokenPrice * (10 ** IERC20(_asset).decimals()) * liqThreshold);
+ // Allow user to specify amount, or default to full withdraw if full repay
+ uint256 collateralToWithdraw = unwindParams.collateralToWithdraw;
Updates

Lead Judging Commences

izuman Lead Judge 16 days ago
Submission Judgement Published
Validated
Assigned finding tags:

Missing Aave management functions

Missing Aave management functions for positions to withdraw, repay, borrow etc.

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!