Stratax Contracts

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

Flash Loan Callback Decoded Params Not Cross-Validated Against _asset and _amount

Author Revealed upon completion

Root + Impact

Location: src/Stratax.sol:202-220

Description

  • executeOperation validates the caller and initiator correctly but routes entirely on the decoded OperationType from _params without verifying that the encoded FlashLoanParams or UnwindParams fields are consistent with the actual flash loan values received from Aave (_asset, _amount).

// src/Stratax.sol:202-220
function executeOperation(
address _asset, // @> actual asset Aave sent
uint256 _amount, // @> actual amount Aave sent
uint256 _premium,
address _initiator,
bytes calldata _params
) external returns (bool) {
require(msg.sender == address(aavePool), "Caller must be Aave Pool");
require(_initiator == address(this), "Initiator must be this contract");
OperationType opType = abi.decode(_params, (OperationType));
// @> _asset and _amount never validated against decoded params before routing
if (opType == OperationType.OPEN) {
return _executeOpenOperation(_asset, _amount, _premium, _params);
}

Risk

Likelihood:

  • Any future code change that passes mismatched params to flashLoanSimple would be silently accepted by the callback

  • If Aave upgrades and delivers a different asset or amount than requested, the discrepancy goes undetected

Impact:

  • Decoded FlashLoanParams.collateralToken could differ from _asset, causing incorrect token approvals and supply calls

  • Inconsistent amounts between encoded params and actual flash loan could produce undercollateralized positions

Proof of Concept

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
// Demonstrates the mismatch: _asset from Aave vs collateralToken in encoded params
// If a future refactor passes WETH as flashLoanToken but encodes USDC as collateralToken:
// In createLeveragedPosition():
// aavePool.flashLoanSimple(address(this), WETH, amount, encodedParams, 0);
// // encodedParams contains FlashLoanParams.collateralToken = USDC
// In executeOperation() callback:
// _asset = WETH (what Aave actually sent)
// flashParams.collateralToken = USDC (what was encoded)
// No require() cross-checks these — both values are used independently
// IERC20(WETH).approve(aavePool, totalCollateral) // uses _asset = WETH
// aavePool.supply(WETH, totalCollateral, ...) // correct token supplied
// emit LeveragePositionCreated(user, WETH, ...) // but event may log wrong data
// // Silent mismatch — no revert, but params are inconsistent

Recommended Mitigation

Cross-check the decoded collateralToken / debtToken against the actual _asset received from Aave at the start of the callback. This ensures the contract's internal accounting always matches what Aave actually delivered, catching encoding errors before any state changes occur.

if (opType == OperationType.OPEN) {
+ (, , FlashLoanParams memory p) = abi.decode(_params, (OperationType, address, FlashLoanParams));
+ require(p.collateralToken == _asset, "Asset mismatch");
return _executeOpenOperation(_asset, _amount, _premium, _params);
} else {
+ (, , UnwindParams memory p) = abi.decode(_params, (OperationType, address, UnwindParams));
+ require(p.debtToken == _asset, "Asset mismatch");
return _executeUnwindOperation(_asset, _amount, _premium, _params);
}

Support

FAQs

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

Give us feedback!