Stratax Contracts

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

Non-Standard ERC20 Approve Handling Causes DoS for USDT and Similar Tokens

Author Revealed upon completion

Root + Impact

Root Cause: Stratax uses IERC20 directly for approve, which assumes a boolean return value.
Impact: Medium. Core flows revert for tokens like USDT that do not return a boolean from approve, preventing usage of common assets.

Description

  • Normal behavior: Token interactions should be compatible with non-standard ERC20 tokens that return no data (e.g., USDT).

  • Issue: IERC20(_asset).approve(...) expects a boolean return. For tokens that return no data, the call reverts during decoding, breaking createLeveragedPosition and unwindPosition.

// In _executeOpenOperation
// @> Reverts with tokens that don't return bool on approve
IERC20(_asset).approve(address(aavePool), totalCollateral);

Risk

Likelihood:

  • USDT is widely used and does not follow the standard ERC20 return convention for approve.

Impact:

  • Denial of service for USDT and similar tokens, blocking a major use case.

Proof of Concept

vulnerability Path

Steps:

  1. Deploy Stratax with a mock USDT that returns no value from approve.

  2. Call createLeveragedPosition.

  3. Observe the transaction revert when Stratax executes approve.

// File: test/audit/PoC_USDT_Approval.t.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import {Test} from "forge-std/Test.sol";
import {Stratax} from "../../src/Stratax.sol";
import {UpgradeableBeacon} from "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol";
import {BeaconProxy} from "@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol";
contract MockUSDT {
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
uint8 public decimals = 6;
function transfer(address to, uint256 value) external returns (bool) {
balanceOf[to] += value;
return true;
}
function transferFrom(address from, address to, uint256 value) external returns (bool) {
uint256 allowed = allowance[from][msg.sender];
require(allowed >= value, "insufficient allowance");
allowance[from][msg.sender] = allowed - value;
balanceOf[from] -= value;
balanceOf[to] += value;
return true;
}
function approve(address spender, uint256 value) external {
allowance[msg.sender][spender] = value;
}
}
contract MockAavePool {
function flashLoanSimple(address receiver, address asset, uint256 amount, bytes calldata params, uint16) external {
Stratax(receiver).executeOperation(asset, amount, 0, receiver, params);
}
function supply(address, uint256, address, uint16) external {}
function borrow(address, uint256, uint256, uint16, address) external {}
function repay(address, uint256, uint256, address) external returns (uint256) {
return 0;
}
function withdraw(address, uint256, address) external returns (uint256) {
return 0;
}
function getUserAccountData(address)
external
pure
returns (
uint256,
uint256,
uint256,
uint256,
uint256,
uint256
)
{
return (0, 0, 0, 0, 0, 2e18);
}
}
contract MockDataProvider {
function getReserveConfigurationData(address)
external
pure
returns (uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256)
{
return (0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
}
}
contract PoC_USDT_Approval is Test {
Stratax public stratax;
MockUSDT public usdt;
MockAavePool public aavePool;
MockDataProvider public dataProvider;
address public ownerTrader = address(0x123);
function setUp() public {
usdt = new MockUSDT();
aavePool = new MockAavePool();
dataProvider = new MockDataProvider();
Stratax impl = new Stratax();
UpgradeableBeacon beacon = new UpgradeableBeacon(address(impl), address(this));
bytes memory initData = abi.encodeWithSelector(
Stratax.initialize.selector,
address(aavePool),
address(dataProvider),
address(0),
address(usdt),
address(0)
);
BeaconProxy proxy = new BeaconProxy(address(beacon), initData);
stratax = Stratax(address(proxy));
stratax.transferOwnership(ownerTrader);
usdt.transfer(ownerTrader, 1000 * 1e6);
}
function test_USDT_Approval_Reverts() public {
vm.startPrank(ownerTrader);
usdt.approve(address(stratax), 1000 * 1e6);
vm.expectRevert();
stratax.createLeveragedPosition(address(usdt), 100 * 1e6, 1000 * 1e6, address(0xBEEF), 0, "", 0);
vm.stopPrank();
}
}

Test Result

forge test --match-path test/audit/PoC_USDT_Approval.t.sol -vv
Ran 1 test for test/audit/PoC_USDT_Approval.t.sol:PoC_USDT_Approval
[PASS] test_USDT_Approval_Reverts() (gas: 123740)
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.69ms (448.44µs CPU time)

Recommended Mitigation

Use SafeERC20 for all token interactions to support non-standard tokens.

- IERC20(_asset).approve(address(aavePool), totalCollateral);
+ IERC20(_asset).safeIncreaseAllowance(address(aavePool), totalCollateral);

Support

FAQs

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

Give us feedback!