Stratax Contracts

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

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

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);
Updates

Lead Judging Commences

izuman Lead Judge 16 days ago
Submission Judgement Published
Invalidated
Reason: Out of scope
Assigned finding tags:

WEIRD ERC20 Tokens

Currently there is no support for weird ERC20 tokens i.e. FOT tokens, missing return values, reentrancy etc.

Support

FAQs

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

Give us feedback!