tycho-execution 0.300.3

Provides tools for encoding and executing swaps against Tycho router and protocol executors.
Documentation
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.26;

import {TransferManager} from "../TransferManager.sol";
import {IExecutor} from "@interfaces/IExecutor.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {
    SafeERC20
} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Address} from "@openzeppelin/contracts/utils/Address.sol";

error HashflowExecutor__InvalidHashflowRouter();
error HashflowExecutor__InvalidDataLength();

interface IHashflowRouter {
    struct RFQTQuote {
        address pool;
        address externalAccount;
        address trader;
        address effectiveTrader;
        address baseToken;
        address quoteToken;
        uint256 effectiveBaseTokenAmount;
        uint256 baseTokenAmount;
        uint256 quoteTokenAmount;
        uint256 quoteExpiry;
        uint256 nonce;
        bytes32 txid;
        bytes signature; // ECDSA signature of the quote, 65 bytes
    }

    function tradeRFQT(RFQTQuote calldata quote) external payable;
}

contract HashflowExecutor is IExecutor {
    using SafeERC20 for IERC20;

    address public constant NATIVE_TOKEN =
        0x0000000000000000000000000000000000000000;

    /// @notice The Hashflow router address
    address public immutable hashflowRouter;

    constructor(address hashflowRouter_) {
        if (hashflowRouter_ == address(0)) {
            revert HashflowExecutor__InvalidHashflowRouter();
        }
        hashflowRouter = hashflowRouter_;
    }

    function fundsExpectedAddress(
        bytes calldata /* data */
    )
        external
        view
        returns (address receiver)
    {
        return msg.sender;
    }

    function swap(uint256 amountIn, bytes calldata data, address receiver)
        external
        payable
    {
        (IHashflowRouter.RFQTQuote memory quote) = _decodeData(data);

        // Slippage checks
        if (amountIn > quote.baseTokenAmount) {
            // Do not transfer more than the quote's maximum permitted amount.
            amountIn = quote.baseTokenAmount;
        }
        quote.effectiveBaseTokenAmount = amountIn;

        uint256 ethValue = 0;
        if (quote.baseToken == NATIVE_TOKEN) {
            ethValue = quote.effectiveBaseTokenAmount;
        }

        IHashflowRouter(hashflowRouter).tradeRFQT{value: ethValue}(quote);
    }

    function _decodeData(bytes calldata data)
        internal
        pure
        returns (IHashflowRouter.RFQTQuote memory quote)
    {
        if (data.length != 325) {
            revert HashflowExecutor__InvalidDataLength();
        }

        quote.pool = address(bytes20(data[0:20]));
        quote.externalAccount = address(bytes20(data[20:40]));
        quote.trader = address(bytes20(data[40:60]));
        // Assumes we never set the effectiveTrader when requesting a quote.
        quote.effectiveTrader = quote.trader;
        quote.baseToken = address(bytes20(data[60:80]));
        quote.quoteToken = address(bytes20(data[80:100]));
        // Not included in the calldata. Will be set in the swap function.
        quote.effectiveBaseTokenAmount = 0;
        quote.baseTokenAmount = uint256(bytes32(data[100:132]));
        quote.quoteTokenAmount = uint256(bytes32(data[132:164]));
        quote.quoteExpiry = uint256(bytes32(data[164:196]));
        quote.nonce = uint256(bytes32(data[196:228]));
        quote.txid = bytes32(data[228:260]);
        quote.signature = data[260:325];
    }

    function getTransferData(bytes calldata data)
        external
        view
        returns (
            TransferManager.TransferType transferType,
            address receiver,
            address tokenIn,
            address tokenOut,
            bool outputToRouter
        )
    {
        if (data.length != 325) {
            revert HashflowExecutor__InvalidDataLength();
        }

        transferType = TransferManager.TransferType.ProtocolWillDebit;
        tokenIn = address(bytes20(data[60:80]));
        tokenOut = address(bytes20(data[80:100]));
        receiver = hashflowRouter;
        outputToRouter = true;
    }
}