Stratax Contracts

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

Unchecked transferFrom Return Value Allows Flash Loan Without Collateral

Author Revealed upon completion

Root + Impact

Location: src/Stratax.sol:325

Description

  • createLeveragedPosition pulls user collateral via transferFrom before initiating the flash loan. The return value is not checked.

  • If the token returns false instead of reverting, the contract records _collateralAmount in the flash loan params, but never received the tokens. The flash loan callback then attempts to supply _amount + flashParams.collateralAmount to Aave with only the flash-loaned funds present.

// src/Stratax.sol:325
IERC20(_flashLoanToken).transferFrom(msg.sender, address(this), _collateralAmount); // @> return value ignored
// ...
bytes memory encodedParams = abi.encode(OperationType.OPEN, msg.sender, params); // @> collateralAmount encoded as if received
aavePool.flashLoanSimple(address(this), _flashLoanToken, _flashLoanAmount, encodedParams, 0);

Risk

Likelihood:

  • Non-reverting ERC20 tokens return false on failed transfers — USDT, BNB among others exhibit this behaviour

  • The collateral transfer is the first action in the function, before any further validation

Impact:

  • Flash loan is initiated with inflated collateral params; Aave supply() call reverts mid-callback with the flash loan unrepaid

  • Depending on prior contract token balances, the callback could partially succeed with incorrect position accounting

Proof of Concept

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import {Test} from "forge-std/Test.sol";
import {Stratax} from "../../src/Stratax.sol";
contract NonRevertingToken {
mapping(address => uint256) public balanceOf;
function transferFrom(address, address, uint256) external pure returns (bool) {
return false; // user has no balance — returns false instead of reverting
}
function approve(address, uint256) external pure returns (bool) { return true; }
}
contract UncheckedTransferFromPoCTest is Test {
Stratax stratax;
NonRevertingToken token;
function test_flashLoanInitiatedWithoutCollateral() public {
token = new NonRevertingToken();
address owner = stratax.owner();
vm.prank(owner);
// transferFrom returns false — collateral never received
// flash loan is initiated as if 1000e6 collateral was deposited
// _executeOpenOperation then tries to supply flashAmount + 1000e6
// Aave supply reverts because only flashAmount is in the contract
vm.expectRevert(); // reverts inside flash loan callback
stratax.createLeveragedPosition(
address(token), 500e6, 1000e6, address(0), 0, "", 0
);
}
}

Recommended Mitigation

Replace the bare transferFrom with SafeERC20.safeTransferFrom() which reverts immediately if the transfer fails, preventing the flash loan from being initiated with unverified collateral amounts.

- IERC20(_flashLoanToken).transferFrom(msg.sender, address(this), _collateralAmount);
+ SafeERC20.safeTransferFrom(IERC20(_flashLoanToken), msg.sender, address(this), _collateralAmount);

Support

FAQs

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

Give us feedback!