tycho-ethereum 0.303.2

Ethereum specific implementation of core tycho traits
Documentation
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IERC20 {
    function balanceOf(address account) external view returns (uint256);
    function approve(address spender, uint256 amount) external returns (bool);
}

interface IForwarder {
    function forwardTransfer(address token, address to, uint256 amount) external returns (bool);
    function forwardApprove(address token, address spender, uint256 amount) external returns (bool);
}

/// @title Token Analyzer
/// @notice Injected at a token holder's address via eth_call state override. Simulates a full
/// round-trip ERC20 transfer (holder -> settlement -> recipient) in a single call and reports
/// balances, gas costs, and success flags. No on-chain deployment required.
/// @dev The inbound transfer uses a low-level call rather than a typed interface so that tokens with
/// non-standard transfer() implementations (e.g. USDT, which omits the bool return value) are
/// handled correctly. balanceOf and approve are called via the typed interface since they are
/// consistently implemented across tokens.
contract Analyzer {
    /// @notice Simulate ERC20 transfer in and out, measuring balances and gas at each step.
    /// @param token   The ERC20 token to analyze.
    /// @param amount  The amount to transfer in from this address (the holder).
    /// @param settlement  Intermediary address (injected with Forwarder bytecode).
    /// @param recipient   Final recipient of the outbound transfer.
    /// @return transferInOk    Whether transfer(settlement, amount) succeeded.
    /// @return transferOutOk   Whether forwardTransfer(token, recipient, received) succeeded.
    /// @return approvalOk      Whether forwardApprove(token, recipient, MAX_UINT256) succeeded.
    /// @return balanceBeforeIn Settlement balance before transfer in.
    /// @return balanceAfterIn  Settlement balance after transfer in.
    /// @return balanceAfterOut Settlement balance after transfer out.
    /// @return recipientBefore Recipient balance before any transfer.
    /// @return recipientAfter  Recipient balance after transfer out.
    /// @return gasIn   Gas consumed by the inbound transfer (gasleft() delta).
    /// @return gasOut  Gas consumed by the outbound transfer (gasleft() delta).
    function analyze(
        address token,
        uint256 amount,
        address settlement,
        address recipient
    ) external returns (
        bool transferInOk,
        bool transferOutOk,
        bool approvalOk,
        uint256 balanceBeforeIn,
        uint256 balanceAfterIn,
        uint256 balanceAfterOut,
        uint256 recipientBefore,
        uint256 recipientAfter,
        uint256 gasIn,
        uint256 gasOut
    ) {
        IERC20 erc20 = IERC20(token);

        // Read pre-transfer balances for both the settlement and the final recipient.
        balanceBeforeIn = erc20.balanceOf(settlement);
        recipientBefore = erc20.balanceOf(recipient);

        // Transfer from holder (this address) to settlement using a low-level call so that tokens
        // which omit the bool return value (e.g. USDT) do not cause a revert during ABI decoding.
        // Success condition: call did not revert AND, if return data is present, it decodes true.
        uint256 g1 = gasleft();
        {
            (bool ok, bytes memory data) = token.call(
                abi.encodeWithSelector(0xa9059cbb, settlement, amount)
            );
            transferInOk = ok && (data.length == 0 || abi.decode(data, (bool)));
        }
        gasIn = g1 - gasleft();

        if (!transferInOk) {
            return (false, false, false, balanceBeforeIn, 0, 0, recipientBefore, 0, gasIn, 0);
        }

        balanceAfterIn = erc20.balanceOf(settlement);

        // Guard: a token that returns true but reduces the settlement balance is pathological.
        // Without this check Solidity 0.8 checked arithmetic would revert the entire eth_call,
        // making the result undecodable. Instead we surface it as a transfer failure.
        if (balanceAfterIn < balanceBeforeIn) {
            return (false, false, false, balanceBeforeIn, balanceAfterIn, 0, recipientBefore, 0, gasIn, 0);
        }

        // received may be less than amount for fee-on-transfer tokens.
        uint256 received = balanceAfterIn - balanceBeforeIn;

        // Transfer out from settlement to recipient via the injected Forwarder.
        uint256 g2 = gasleft();
        try IForwarder(settlement).forwardTransfer(token, recipient, received) returns (bool success) {
            transferOutOk = success;
        } catch {
            transferOutOk = false;
        }
        gasOut = g2 - gasleft();

        if (!transferOutOk) {
            balanceAfterOut = erc20.balanceOf(settlement);
            return (true, false, false, balanceBeforeIn, balanceAfterIn, balanceAfterOut, recipientBefore, 0, gasIn, gasOut);
        }

        balanceAfterOut = erc20.balanceOf(settlement);
        recipientAfter = erc20.balanceOf(recipient);

        // Test that settlement can approve (some tokens block approvals from contracts).
        try IForwarder(settlement).forwardApprove(token, recipient, type(uint256).max) returns (bool success) {
            approvalOk = success;
        } catch {
            approvalOk = false;
        }
    }
}