Stratax Contracts

First Flight #57
Beginner FriendlyDeFi
100 EXP
Submission Details
Impact: medium
Likelihood: high

Surplus Swap Tokens Re-Deposited to Aave Instead of Returned to Owner

Author Revealed upon completion

Root + Impact

Location: src/Stratax.sol:529-532 (open), src/Stratax.sol:591-595 (unwind)

Description

After repaying the flash loan, any leftover tokens from the 1inch swap are automatically supplied back into Aave as additional collateral in both the open and unwind flows. During an unwind operation the user's intent is to close the position and retrieve funds — but surplus proceeds are locked back into Aave.

// src/Stratax.sol:591-595
if (returnAmount - totalDebt > 0) {
IERC20(_asset).approve(address(aavePool), returnAmount - totalDebt);
aavePool.supply(_asset, returnAmount - totalDebt, address(this), 0); // @> surplus locked into Aave, not sent to user
}

Risk

Likelihood:

  • Every successful unwind where the swap returns more than the flash loan + premium produces surplus — this is the normal case when any slippage buffer is applied

  • The hardcoded 5% buffer in calculateUnwindParams deliberately over-withdraws collateral, guaranteeing surplus in normal conditions

Impact:

  • User cannot immediately access surplus funds after unwinding — an additional Aave withdrawal transaction is required

  • In a multi-position scenario, surplus is silently merged into the shared collateral pool, obscuring per-position accounting

Proof of Concept

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
// Numeric trace of a standard unwind showing surplus locked in Aave:
//
// Setup:
// debtAmount = 1000 USDC (flash loaned)
// flashPremium = 0.9 USDC (0.09% Aave fee)
// totalDebt = 1000.9 USDC
//
// calculateUnwindParams() applies 5% buffer:
// collateralToWithdraw = debt_value_in_collateral * 1.05
// → withdraws ~1050 USDC worth of collateral
//
// Swap (at 1% real slippage):
// returnAmount = 1050 * 0.99 = 1039.5 USDC
//
// After flash loan repaid:
// surplus = 1039.5 - 1000.9 = 38.6 USDC
//
// _executeUnwindOperation line 592-594:
// aavePool.supply(USDC, 38.6, address(this), 0) ← surplus locked back into Aave
//
// User receives: 0 USDC immediately
// User must call: aavePool.withdraw() separately to retrieve 38.6 USDC

Recommended Mitigation

Distinguish between the open and unwind flows when handling surplus. During an unwind the user is exiting — surplus should be sent directly to the user address that was decoded from the flash loan params. During an open, re-supplying to improve health factor is still appropriate.

if (returnAmount - totalDebt > 0) {
- IERC20(_asset).approve(address(aavePool), returnAmount - totalDebt);
- aavePool.supply(_asset, returnAmount - totalDebt, address(this), 0);
+ // On unwind: return surplus directly to user
+ IERC20(_asset).transfer(user, returnAmount - totalDebt);
+ // On open: re-supply to improve health factor (keep existing behaviour)
}

Support

FAQs

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

Give us feedback!