Stratax Contracts

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

LEFTOVER SUPPLY CAN CAUSE REVERT

Author Revealed upon completion

Root + Impact

Description

When users open or close leveraged positions, the contract uses flash loans to execute the trades. After swapping tokens through 1inch and repaying the flash loan, there's usually a small amount of leftover tokens (due to favorable swap rates). The contract currently tries to be helpful by automatically supplying these leftover tokens back into Aave to strengthen the user's position.

The Problem

Here's where things break: If Aave has frozen the asset, reached its supply cap, or paused the reserve (which happens regularly for risk management), the supply() call will fail. When this happens, the entire transaction reverts — meaning the user can't close their position at all, even though they have more than enough funds to repay the flash loan.

Why this matters: Imagine you're watching your leveraged position approach liquidation during a market crash. You desperately try to close it, but Aave has temporarily frozen USDC deposits due to market volatility. The contract has your money, the swap succeeded, the flash loan can be repaid — but you're stuck. The transaction keeps failing because the contract insists on depositing those extra few cents back into Aave. Meanwhile, your position gets liquidated and you lose everything.

// Root cause in the codebase with @> marks to highlight the relevant section
_executeOpenOperation
if (returnAmount - totalDebt > 0) {
aavePool.supply(_asset, returnAmount - totalDebt, address(this), 0);
}

Risk

Likelihood:

Aave governance regularly freezes assets during market stress

Supply caps get hit during high demand periods


Impact:


Users become trapped in losing positions during critical moments

Guaranteed liquidation in time-sensitive scenarios


Proof of Concept

function test_UnwindRevertsWhenLeftoverSupplyFails_POC() public {
uint256 flashLoanAmount = 3000e6;
uint256 premium = 27e5;
uint256 totalDebt = flashLoanAmount + premium;
uint256 swapReturn = totalDebt + 1;
uint256 leftover = swapReturn - totalDebt;
vm.mockCall(
AAVE_PROTOCOL_DATA_PROVIDER,
abi.encodeWithSignature("getReserveConfigurationData(address)", WETH),
abi.encode(
uint256(0),
uint256(8000),
uint256(8250),
uint256(10500),
uint256(0),
false,
false,
false,
false,
false
)
);
vm.mockCall(
WETH_PRICE_FEED,
abi.encodeWithSignature("latestRoundData()"),
abi.encode(uint80(1), int256(2000e8), uint256(0), uint256(block.timestamp), uint80(1))
);
vm.mockCall(
USDC_PRICE_FEED,
abi.encodeWithSignature("latestRoundData()"),
abi.encode(uint80(1), int256(1e8), uint256(0), uint256(block.timestamp), uint80(1))
);
vm.mockCall(WETH, abi.encodeWithSignature("decimals()"), abi.encode(uint8(18)));
vm.mockCall(USDC, abi.encodeWithSignature("decimals()"), abi.encode(uint8(6)));
uint256 collateralToWithdraw = (flashLoanAmount * 1e8 * (10 ** 18) * 1e4) / (2000e8 * (10 ** 6) * 8250);
// Broad approve mocks — match any amount for USDC and WETH
vm.mockCall(USDC, abi.encodeWithSignature("approve(address,uint256)"), abi.encode(true));
vm.mockCall(WETH, abi.encodeWithSignature("approve(address,uint256)"), abi.encode(true));
vm.mockCall(
AAVE_POOL,
abi.encodeWithSignature("repay(address,uint256,uint256,address)", USDC, flashLoanAmount, 2, address(stratax)),
abi.encode(flashLoanAmount)
);
vm.mockCall(
AAVE_POOL,
abi.encodeWithSignature("withdraw(address,uint256,address)", WETH, collateralToWithdraw, address(stratax)),
abi.encode(collateralToWithdraw)
);
bytes memory swapData = abi.encodeWithSignature("swap(bytes)", "");
vm.mockCall(INCH_ROUTER, swapData, abi.encode(swapReturn, uint256(0)));
vm.mockCallRevert(
AAVE_POOL,
abi.encodeWithSignature("supply(address,uint256,address,uint16)", USDC, leftover, address(stratax), 0),
"Reserve is frozen"
);
Stratax.UnwindParams memory params = Stratax.UnwindParams({
collateralToken: WETH,
collateralToWithdraw: 0,
debtToken: USDC,
debtAmount: flashLoanAmount,
oneInchSwapData: swapData,
minReturnAmount: totalDebt
});
bytes memory encodedParams = abi.encode(Stratax.OperationType.UNWIND, ownerTrader, params);
// The tx reverts even though the flash loan IS repayable (swapReturn > totalDebt)
vm.expectRevert();
vm.prank(AAVE_POOL);
stratax.executeOperation(USDC, flashLoanAmount, premium, address(stratax), encodedParams);
}
}

Recommended Mitigation

+ add this code
if (returnAmount - totalDebt > 0) {
// Try to supply, but don't block the operation if it fails
IERC20(_asset).transfer(owner, returnAmount - totalDebt);
}

Support

FAQs

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

Give us feedback!