Stratax Contracts

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

Unsafe `transfer()` and `transferFrom()` without SafeERC20 wrapper breaks USDT and other non-standard ERC20 tokens

Author Revealed upon completion

Root + Impact

Stratax.sol uses bare IERC20.transfer(), transferFrom(), and approve() calls which expect a bool return. USDT returns void from these functions. Solidity ^0.8.13 ABI-decodes 0 bytes as bool → decode failure → revert. USDT is completely unusable in Stratax.

Description

  • Stratax imports IERC20 from forge-std/interfaces/IERC20.sol (L4), which defines transfer() as returning bool. USDT's transfer() returns void.

  • Affected locations: transfer() at L283, transferFrom() at L325, and approve() at L495, L510, L530, L534, L559, L583, L593, L597 — covering both open and unwind flows.

  • USDT meets all README token requirements: Aave V3 supported, Chainlink feed, not fee-on-transfer, not rebasing, not exotic, 1inch supported.

// L283: IERC20(_token).transfer(owner, _amount); // @> USDT reverts
// L325: IERC20(_flashLoanToken).transferFrom(...); // @> USDT reverts
// L495+: IERC20(_asset).approve(address(aavePool), ...); // @> USDT reverts
// Root cause: forge-std IERC20 expects bool, USDT returns void → ABI decode failure

Risk

Likelihood: USDT is the 3rd largest cryptocurrency. Any interaction with USDT deterministically reverts — not conditional or edge-case.

Impact: createLeveragedPosition(), recoverTokens(), and all flash loan callbacks revert with USDT. Core protocol functionality broken for a major supported token.

Proof of Concept

USDT's mainnet contract (0xdAC17F958D2ee523a2206206994597C13D831ec7) defines transfer(address, uint) without returns (bool). The forge-std IERC20 interface expects bool. Solidity ^0.8.13 receives 0 bytes, expects 32 bytes for bool decode → reverts.

// USDT: function transfer(address _to, uint _value) public { ... } ← no returns
// IERC20: function transfer(address, uint256) external returns (bool); ← expects bool
// Result: 0 bytes returned → ABI decoder expects 32 bytes → REVERT

Recommended Mitigation

Use OpenZeppelin's SafeERC20 which handles tokens returning void by checking return data length. Use forceApprove() for approve() calls, which also handles USDT's require-zero-before-nonzero allowance pattern.

+ import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
contract Stratax is Initializable {
+ using SafeERC20 for IERC20;
- IERC20(_token).transfer(owner, _amount);
+ IERC20(_token).safeTransfer(owner, _amount);
- IERC20(_flashLoanToken).transferFrom(...);
+ IERC20(_flashLoanToken).safeTransferFrom(...);
- IERC20(_asset).approve(address(aavePool), totalCollateral);
+ IERC20(_asset).forceApprove(address(aavePool), totalCollateral);
}

Support

FAQs

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

Give us feedback!