Stratax Contracts

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

Flash Loan Callback Lacks Reentrancy Protection

Author Revealed upon completion

Root + Impact

Description

  • The executeOperation function is the Aave V3 flash loan callback that handles both position opening and unwinding. During execution, it performs multiple external calls: token approvals, Aave supply/borrow/repay/withdraw, and a 1inch swap via low-level .call(). The Checks-Effects-Interactions (CEI) pattern requires that all state changes and checks happen before external interactions.

  • The function has no nonReentrant modifier or reentrancy guard. The 1inch swap executed via _call1InchSwap performs a low-level .call() to an external contract, which can execute arbitrary code. If a malicious token or a token with transfer hooks (e.g., ERC-777 tokens) is involved, the swap callback could re-enter the Stratax contract mid-operation before the flash loan repayment is finalized.

@> function executeOperation(
address _asset,
uint256 _amount,
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));
if (opType == OperationType.OPEN) {
@> return _executeOpenOperation(_asset, _amount, _premium, _params);
} else {
@> return _executeUnwindOperation(_asset, _amount, _premium, _params);
}
}

Risk

Likelihood:

  • The owner selects which tokens are used for positions. When the collateral or borrow token is an ERC-777 token or any token with hooks (such as tokensReceived or tokensToSend), an external contract receives a callback during token transfers inside the flash loan callback execution.

  • The 1inch router .call() also provides an external execution context where arbitrary code can run before the flash loan repayment check completes.

Impact:

  • A re-entrant call could manipulate Aave position state mid-operation (e.g., calling unwindPosition during an _executeOpenOperation to extract collateral before the position is finalized).

  • Double-supply or double-borrow scenarios could leave the contract in an inconsistent state relative to its Aave positions.

Proof of Concept

The attack path below traces how an ERC-777 token used as the borrowToken triggers a tokensReceived hook during the aavePool.borrow() call inside _executeOpenOperation. Because executeOperation has no nonReentrant guard, the hook can re-enter Stratax (e.g., calling unwindPosition) before the open operation's state changes are finalized.

// Attack vector: ERC-777 token used as borrowToken
// 1. Owner calls createLeveragedPosition with ERC-777 as borrowToken
// 2. During _executeOpenOperation, aavePool.borrow() transfers ERC-777 to Stratax
// 3. ERC-777 triggers tokensReceived hook on Stratax (if registered) or on
// the 1inch swap intermediary
// 4. The hook can call back into unwindPosition() or any other state-changing function
// 5. State is corrupted because the open operation hasn't completed
//
// The function chain is:
// createLeveragedPosition
// -> aavePool.flashLoanSimple
// -> executeOperation (NO nonReentrant guard)
// -> _executeOpenOperation
// -> aavePool.borrow (ERC-777 transfer hook fires)
// -> REENTRANT CALL back into Stratax

Recommended Mitigation

Inherit OpenZeppelin's ReentrancyGuardUpgradeable and apply the nonReentrant modifier to executeOperation. Since Stratax uses the Initializable proxy pattern, the upgradeable variant must be used and its __ReentrancyGuard_init() called during initialize.

+ import {ReentrancyGuardUpgradeable} from
+ "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
- contract Stratax is Initializable {
+ contract Stratax is Initializable, ReentrancyGuardUpgradeable {
function initialize(...) external initializer {
+ __ReentrancyGuard_init();
// ...
}
- function executeOperation(...) external returns (bool) {
+ function executeOperation(...) external nonReentrant returns (bool) {
// ...
}

Support

FAQs

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

Give us feedback!