Skip to content

feat: fix Tron USDT transfers via LibAsset bypass [GenericSwapFacetV3 v1.0.2-tron, WithdrawablePeriphery v1.0.0-tron, LibAsset v2.1.3-tron, LiFiDEXAggregator v1.12.0-tron]#9

Open
mirooon wants to merge 5 commits intomainfrom
exsc-241-fix-tron-usdt-transfers-fork
Open

feat: fix Tron USDT transfers via LibAsset bypass [GenericSwapFacetV3 v1.0.2-tron, WithdrawablePeriphery v1.0.0-tron, LibAsset v2.1.3-tron, LiFiDEXAggregator v1.12.0-tron]#9
mirooon wants to merge 5 commits intomainfrom
exsc-241-fix-tron-usdt-transfers-fork

Conversation

@mirooon
Copy link
Copy Markdown
Collaborator

@mirooon mirooon commented Apr 27, 2026

Which Linear task belongs to this PR?

https://linear.app/lifi-linear/issue/EXSC-241/fix-tron-usdt-transfers-failing-on-safetransferlib-return-data-check

Why did I implement it this way?

Problem

Tron's canonical USDT contract
(TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t / 0xa614f803B6FD780986A42c78Ec9c7f77e6DeD13C)
was compiled with an older Solidity version (~0.4.x).

Although its transfer() function is declared as returns (bool), it does not explicitly return true. As a result:

  • The transfer executes successfully and balances update correctly.

  • However, the return data contains 32 zero bytes (false) instead of:

    • no return data (non-standard tokens), or
    • true (standard ERC20 behavior).

This creates incompatibility with SafeTransferLib.safeTransfer, which enforces strict return validation:

  • Accepts:

    • returndatasize() == 0, or
    • return value == 1
  • Rejects:

    • return value == 0 → reverts with TransferFailed

Impact:
All outbound transfer() calls to Tron USDT reverted inside our contracts (via LibAsset.transferERC20SafeTransferLib.safeTransfer), even though the transfer itself succeeded.

Important:
transferFrom() is not affected — Tron USDT correctly returns true.
Inbound flows using transferFrom() remain safe.


Solution

A targeted exception was introduced in LibAsset.transferERC20:

  • Detect:

    • Tron mainnet (chainId = 728126428)
    • Canonical USDT address
  • In this specific case:

    • Use a direct IERC20.transfer() call
    • Skip SafeTransferLib return-data validation
  • Still:

    • Propagate any revert from the token contract

All other tokens and chains continue to use SafeTransferLib.safeTransfer unchanged.


What Was Done

Core Fix

  • src/Libraries/LibAsset.sol (v2.1.3-tron)

    • Added TRON_CHAIN_ID and TRON_USDT constants
    • Updated transferERC20 to apply the bypass only for Tron USDT

Contract Updates

  • src/Facets/GenericSwapFacetV3.sol (v1.0.2-tron)

    • Migrated ERC20 transfer calls to LibAsset.transferERC20 and LibAsset.maxApproveERC20:

      • From: manual safeApprove(0)safeApprove(max)
      • To: LibAsset.maxApproveERC20
    • Ensures the Tron USDT fix is applied consistently across all swap paths

    • Allowlist logic unchanged


  • src/Helpers/WithdrawablePeriphery.sol (v1.0.0-tron)

    Two changes:

    1. Added explicit validation:
      • if (amount == 0) revert ZeroAmount()
    2. Error handling change:
      • Removed ExternalCallFailed
      • Replaced with ETHTransferFailed on native transfer failure

  • src/Periphery/LiFiDEXAggregator.sol (v1.12.0-tron)

    • Migrated all ERC20 transfer / transferFrom calls to:

      • LibAsset.transferERC20
      • LibAsset.transferFromERC20

Versioning (tron fork overlay)

Contract Previous New
LibAsset 2.1.3 2.1.3-tron
GenericSwapFacetV3 1.0.2 1.0.2-tron
WithdrawablePeriphery 1.0.0 1.0.0-tron
LiFiDEXAggregator 1.12.0 1.12.0-tron

Tests & Tooling

  • Added MockTronUSDT contract reproducing the faulty return behavior (transfer() returns false)

  • Added Tron-specific LibAsset test suite:

    • Validates bypass logic
    • Confirms standard tokens still use SafeTransferLib
  • Updated multiple receiver test files due to error selector changes

  • Added/updated internal guidelines:

    • Versioning rules in .agents/rules/100-solidity-basics.mdc
    • Test naming conventions in .agents/rules/400-solidity-tests.mdc

Contracts Not Modified

  • src/Periphery/TokenWrapper.sol

    • Not affected — only wraps native assets (e.g., WETH), never Tron USDT
  • src/Periphery/LidoWrapper.sol

    • Not affected — operates exclusively on stETH/wstETH, which are Ethereum-only assets.
  • Receiver Contracts

    • ReceiverAcrossV3.sol
    • ReceiverChainflip.sol
    • ReceiverStargateV2.sol

    Not modified because:

    • They are not deployed on Tron
    • No current plans to deploy them there

    Note:
    If deployed on Tron in the future:

    • Outbound transfer() must route through LibAsset.transferERC20
    • transferFrom() usage remains safe (returns true on Tron USDT)

Checklist before requesting a review

Checklist for reviewer (DO NOT DEPLOY and contracts BEFORE CHECKING THIS!!!)

  • I have checked that any arbitrary calls to external contracts are validated and or restricted
  • I have checked that any privileged calls (i.e. storage modifications) are validated and or restricted
  • I have ensured that any new contracts have had AT A MINIMUM 1 preliminary audit conducted on by <company/auditor>

mirooon and others added 3 commits April 27, 2026 10:42
Tron's canonical USDT (compiled ~0.4.x) declares transfer() returns (bool)
but never executes return true, causing SafeTransferLib to revert on every
outbound transfer. This PR introduces a targeted bypass in LibAsset for
chain ID 728126428 + address 0xa614f803... only, leaving all other tokens
and chains on the standard safeTransfer path.

All ERC20 transfers in GenericSwapFacetV3, WithdrawablePeriphery, and
LiFiDEXAggregator are now routed through LibAsset so the bypass takes
effect everywhere. WithdrawablePeriphery also gains a ZeroAmount guard.
Rules updated with versioning and test-naming conventions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Use GITHUB_TOKEN as fallback when GIT_ACTIONS_BOT_PAT_CLASSIC is not
  configured (avoids interactive device-flow login in CI)
- Extend version regex to match <X.Y.Z-tron> suffixed versions used in
  this fork

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions github-actions Bot changed the title feat: fix Tron USDT transfers via LibAsset bypass feat: fix Tron USDT transfers via LibAsset bypass [GenericSwapFacetV3 v1.0.2-tron, PolymerCCTPFacet v2.0.1, WithdrawablePeriphery v1.0.0-tron, LibAsset v2.1.3-tron, LiFiDEXAggregator v1.12.0-tron] Apr 27, 2026
@lifi-action-bot lifi-action-bot changed the title feat: fix Tron USDT transfers via LibAsset bypass [GenericSwapFacetV3 v1.0.2-tron, PolymerCCTPFacet v2.0.1, WithdrawablePeriphery v1.0.0-tron, LibAsset v2.1.3-tron, LiFiDEXAggregator v1.12.0-tron] feat: fix Tron USDT transfers via LibAsset bypass [GenericSwapFacetV3 v1.0.2-tron, WithdrawablePeriphery v1.0.0-tron, LibAsset v2.1.3-tron, LiFiDEXAggregator v1.12.0-tron] Apr 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants