RFQVaultExecutor

0x97caca78ac2a94c67643d07843f85afaa44a3ea5

Verification
Verified
v0.8.34+commit.80d5c536
Type
Contract
5,158 bytes
ABI entries
37
6 read · 11 write
License
none

Contract information

Address
0x97caca78ac2a94c67643d07843f85afaa44a3ea5
Chain
Robinhood Chain (4663)
Compiler
v0.8.34+commit.80d5c536
Optimization
Enabled
Creation tx
0xc3302d6c37…0499f9118f

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (6)

isMarkedForRefund(bytes32)uint256
nominee()address
nonceUsed(uint256)uint256
owner()address
quoteIdUsed(bytes32)uint256
solverSigner()address

Events (6)

ERC20DepositedFulfilledNativeDepositedOwnerClaimedOwnerNominatedRefunded

ABI

[
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "_owner",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "_solverSigner",
        "type": "address"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "index",
        "type": "uint256"
      }
    ],
    "name": "ActionFailed",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidNonce",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidOwner",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidQuoteId",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidSigner",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidSolverSigner",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidTokenAddress",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "OnlyNominee",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "OnlyOwner",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "SwapOutputInsufficient",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "TransferFailed",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ZeroAddress",
    "type": "error"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "bytes32",
        "name": "quoteId",
        "type": "bytes32"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "ERC20Deposited",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "bytes32",
        "name": "quoteId",
        "type": "bytes32"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "receiver",
        "type": "address"
      }
    ],
    "name": "Fulfilled",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "bytes32",
        "name": "quoteId",
        "type": "bytes32"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "NativeDeposited",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "claimer",
        "type": "address"
      }
    ],
    "name": "OwnerClaimed",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "nominee",
        "type": "address"
      }
    ],
    "name": "OwnerNominated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "bytes32",
        "name": "quoteId",
        "type": "bytes32"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "receiver",
        "type": "address"
      }
    ],
    "name": "Refunded",
    "type": "event"
  },
  {
    "inputs": [],
    "name": "claimOwner",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "quoteId",
        "type": "bytes32"
      },
      {
        "internalType": "uint256",
        "name": "nonce",
        "type": "uint256"
      },
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      },
      {
        "internalType": "address",
        "name": "receiver",
        "type": "address"
      },
      {
        "internalType": "bytes",
        "name": "signature",
        "type": "bytes"
      }
    ],
    "name": "fulfil",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "quoteId",
        "type": "bytes32"
      }
    ],
    "name": "isMarkedForRefund",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "isMarked",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "quoteId",
        "type": "bytes32"
      },
      {
        "internalType": "uint256",
        "name": "nonce",
        "type": "uint256"
      },
      {
        "internalType": "bytes",
        "name": "signature",
        "type": "bytes"
      }
    ],
    "name": "markForRefund",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "nominee_",
        "type": "address"
      }
    ],
    "name": "nominateOwner",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "nominee",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "nonce",
        "type": "uint256"
      }
    ],
    "name": "nonceUsed",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "isUsed",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "owner",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "nonce",
        "type": "uint256"
      },
      {
        "components": [
          {
            "internalType": "address",
            "name": "target",
            "type": "address"
          },
          {
            "internalType": "uint256",
            "name": "value",
            "type": "uint256"
          },
          {
            "internalType": "bytes",
            "name": "data",
            "type": "bytes"
          }
        ],
        "internalType": "struct RFQVaultExecutor.Action[]",
        "name": "actions",
        "type": "tuple[]"
      },
      {
        "internalType": "bytes",
        "name": "signature",
        "type": "bytes"
      }
    ],
    "name": "performActions",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "quoteId",
        "type": "bytes32"
      }
    ],
    "name": "quoteIdUsed",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "isUsed",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "quoteId",
        "type": "bytes32"
      },
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "receiveERC20",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "quoteId",
        "type": "bytes32"
      }
    ],
    "name": "receiveNative",
    "outputs": [],
    "stateMutability": "payable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "quoteId",
        "type": "bytes32"
      },
      {
        "internalType": "uint256",
        "name": "nonce",
        "type": "uint256"
      },
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      },
      {
        "internalType": "address",
        "name": "receiver",
        "type": "address"
      },
      {
        "internalType": "bytes",
        "name": "signature",
        "type": "bytes"
      }
    ],
    "name": "refund",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "rescueTo_",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "amount_",
        "type": "uint256"
      }
    ],
    "name": "rescueFunds",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "_solverSigner",
        "type": "address"
      }
    ],
    "name": "setSolverSigner",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "solverSigner",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "quoteId",
        "type": "bytes32"
      },
      {
        "internalType": "uint256",
        "name": "nonce",
        "type": "uint256"
      },
      {
        "internalType": "address",
        "name": "approvalToken",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "approvalSpender",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "approvalAmount",
        "type": "uint256"
      },
      {
        "internalType": "address",
        "name": "outputToken",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "minOutput",
        "type": "uint256"
      },
      {
        "components": [
          {
            "internalType": "address",
            "name": "target",
            "type": "address"
          },
          {
            "internalType": "uint256",
            "name": "value",
            "type": "uint256"
          },
          {
            "internalType": "bytes",
            "name": "data",
            "type": "bytes"
          }
        ],
        "internalType": "struct RFQVaultExecutor.Action",
        "name": "swapAction",
        "type": "tuple"
      },
      {
        "internalType": "address",
        "name": "receiver",
        "type": "address"
      },
      {
        "internalType": "bytes",
        "name": "signature",
        "type": "bytes"
      }
    ],
    "name": "swapAndFulfil",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "stateMutability": "payable",
    "type": "receive"
  }
]

Source code

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity 0.8.34;

import {SafeTransferLib} from "solady/src/utils/SafeTransferLib.sol";

import {Ownable} from "../common/utils/Ownable.sol";
import {RescueFundsLib} from "../common/lib/RescueFundsLib.sol";
import {CurrencyLib} from "../common/lib/CurrencyLib.sol";

/// @title RFQVaultExecutor
/// @notice Solver-controlled vault that receives deposits and releases funds via signed fulfil/refund flows.
contract RFQVaultExecutor is Ownable {
    struct Action {
        address target;
        uint256 value;
        bytes data;
    }

    error ActionFailed(uint256 index);
    error InvalidQuoteId(); // 0x140dcdb5
    error InvalidNonce(); // 0x756688fe
    error InvalidSigner(); // 0x815e1d64
    error SwapOutputInsufficient();
    error InvalidSolverSigner();
    error InvalidOwner();

    uint256 internal constant FULFIL_DISCRIMINATOR = 1;
    uint256 internal constant REFUND_DISCRIMINATOR = 2;
    uint256 internal constant SWAP_AND_FULFIL_DISCRIMINATOR = 3;
    uint256 internal constant MARK_FOR_REFUND_DISCRIMINATOR = 4;

    address internal SOLVER_SIGNER;

    mapping(bytes32 quoteId => uint256 isUsed) public quoteIdUsed;
    mapping(uint256 nonce => uint256 isUsed) public nonceUsed;
    mapping(bytes32 quoteId => uint256 isMarked) public isMarkedForRefund;

    event NativeDeposited(bytes32 quoteId, uint256 amount);
    event ERC20Deposited(bytes32 quoteId, address token, uint256 amount);
    event Fulfilled(
        bytes32 quoteId,
        address token,
        uint256 amount,
        address receiver
    );
    event Refunded(
        bytes32 quoteId,
        address token,
        uint256 amount,
        address receiver
    );

    constructor(address _owner, address _solverSigner) Ownable(_owner) {
        if (_owner == address(0)) revert InvalidOwner();
        if (_solverSigner == address(0)) revert InvalidSolverSigner();
        SOLVER_SIGNER = _solverSigner;
    }

    /// @notice Accept native deposits and emit a correlating event.
    function receiveNative(bytes32 quoteId) external payable {
        emit NativeDeposited(quoteId, msg.value);
    }

    /// @notice Pull ERC20 tokens from the caller into the vault and emit a correlating event.
    function receiveERC20(
        bytes32 quoteId,
        address token,
        uint256 amount
    ) external {
        SafeTransferLib.safeTransferFrom(
            token,
            msg.sender,
            address(this),
            amount
        );
        emit ERC20Deposited(quoteId, token, amount);
    }

    /// @notice Transfer vault funds to a receiver for a signed quote.
    function fulfil(
        bytes32 quoteId,
        uint256 nonce,
        address token,
        uint256 amount,
        address receiver,
        bytes calldata signature
    ) external {
        bytes32 messageHash = _hashFulfilOrRefund(
            FULFIL_DISCRIMINATOR,
            quoteId,
            nonce,
            token,
            amount,
            receiver
        );
        _verifyAndMarkQuoteId(messageHash, signature, quoteId);

        CurrencyLib.transfer(token, receiver, amount);
        emit Fulfilled(quoteId, token, amount, receiver);
    }

    /// @notice Swap vault inventory then transfer swap output to the receiver.
    function swapAndFulfil(
        bytes32 quoteId,
        uint256 nonce,
        address approvalToken,
        address approvalSpender,
        uint256 approvalAmount,
        address outputToken,
        uint256 minOutput,
        Action calldata swapAction,
        address receiver,
        bytes calldata signature
    ) external {
        bytes32 messageHash = _hashSwapAndFulfil(
            quoteId,
            nonce,
            approvalToken,
            approvalSpender,
            approvalAmount,
            outputToken,
            minOutput,
            swapAction,
            receiver
        );
        _verifyAndMarkQuoteId(messageHash, signature, quoteId);

        if (approvalToken != address(0)) {
            SafeTransferLib.safeApproveWithRetry(
                approvalToken,
                approvalSpender,
                approvalAmount
            );
        }

        uint256 beforeBalance = CurrencyLib.balanceOf(
            outputToken,
            address(this)
        );
        if (!_performAction(swapAction)) {
            revert ActionFailed(0);
        }

        uint256 swapOutput;
        unchecked {
            swapOutput =
                CurrencyLib.balanceOf(outputToken, address(this)) -
                beforeBalance;
        }

        if (swapOutput < minOutput) {
            revert SwapOutputInsufficient();
        }

        CurrencyLib.transfer(outputToken, receiver, swapOutput);
        emit Fulfilled(quoteId, outputToken, swapOutput, receiver);
    }

    /// @notice Execute arbitrary signed actions unrelated to quote fulfilment.
    function performActions(
        uint256 nonce,
        Action[] calldata actions,
        bytes calldata signature
    ) external {
        bytes32 messageHash = keccak256(
            abi.encode(block.chainid, address(this), nonce, actions)
        );
        _verifyAndMarkNonce(messageHash, signature, nonce);

        uint256 actionsLength = actions.length;
        for (uint256 i = 0; i < actionsLength; ) {
            if (!_performAction(actions[i])) {
                revert ActionFailed(i);
            }
            unchecked {
                ++i;
            }
        }
    }

    /// @notice Refund vault funds to a receiver for a signed quote.
    function refund(
        bytes32 quoteId,
        uint256 nonce,
        address token,
        uint256 amount,
        address receiver,
        bytes calldata signature
    ) external {
        bytes32 messageHash = _hashFulfilOrRefund(
            REFUND_DISCRIMINATOR,
            quoteId,
            nonce,
            token,
            amount,
            receiver
        );
        _verifyAndMarkQuoteId(messageHash, signature, quoteId);

        CurrencyLib.transfer(token, receiver, amount);
        emit Refunded(quoteId, token, amount, receiver);
    }

    function setSolverSigner(address _solverSigner) external onlyOwner {
        if (_solverSigner == address(0)) revert InvalidSolverSigner();
        SOLVER_SIGNER = _solverSigner;
    }

    function solverSigner() external view returns (address) {
        return SOLVER_SIGNER;
    }

    /// @notice Mark a quoteId as used to block any further fulfil for this quote.
    /// @dev Called with a solver signature on the destination chain before
    ///      initiating an origin-chain refund, preventing race conditions where
    ///      an in-flight fulfil relay lands after the refund is already processed.
    ///      Reverts with InvalidQuoteId if the quoteId was already used.
    function markForRefund(
        bytes32 quoteId,
        uint256 nonce,
        bytes calldata signature
    ) external {
        bytes32 messageHash = _hashMarkForRefund(quoteId, nonce);
        _verifyAndMarkQuoteId(messageHash, signature, quoteId);
        isMarkedForRefund[quoteId] = 1;
    }

    function rescueFunds(
        address token_,
        address rescueTo_,
        uint256 amount_
    ) external onlyOwner {
        RescueFundsLib.rescueFunds(token_, rescueTo_, amount_);
    }

    receive() external payable {}

    /// @dev Fixed-size fulfil/refund hash without abi.encode overhead.
    function _hashFulfilOrRefund(
        uint256 discriminator,
        bytes32 quoteId,
        uint256 nonce,
        address token,
        uint256 amount,
        address receiver
    ) internal view returns (bytes32 hash) {
        assembly {
            let ptr := mload(0x40)
            mstore(ptr, chainid())
            mstore(add(ptr, 0x20), address())
            mstore(add(ptr, 0x40), discriminator)
            mstore(add(ptr, 0x60), quoteId)
            mstore(add(ptr, 0x80), nonce)
            mstore(add(ptr, 0xa0), token)
            mstore(add(ptr, 0xc0), amount)
            mstore(add(ptr, 0xe0), receiver)
            hash := keccak256(ptr, 0x100)
        }
    }

    function _hashSwapAndFulfil(
        bytes32 quoteId,
        uint256 nonce,
        address approvalToken,
        address approvalSpender,
        uint256 approvalAmount,
        address outputToken,
        uint256 minOutput,
        Action calldata swapAction,
        address receiver
    ) internal view returns (bytes32 hash) {
        assembly {
            let ptr := mload(0x40)
            let dataLength := calldataload(add(swapAction, 0x60))
            calldatacopy(ptr, add(swapAction, 0x80), dataLength)
            let dataHash := keccak256(ptr, dataLength)

            mstore(ptr, chainid())
            mstore(add(ptr, 0x20), address())
            mstore(add(ptr, 0x40), SWAP_AND_FULFIL_DISCRIMINATOR)
            mstore(add(ptr, 0x60), quoteId)
            mstore(add(ptr, 0x80), nonce)
            mstore(add(ptr, 0xa0), approvalToken)
            mstore(add(ptr, 0xc0), approvalSpender)
            mstore(add(ptr, 0xe0), approvalAmount)
            mstore(add(ptr, 0x100), outputToken)
            mstore(add(ptr, 0x120), minOutput)
            mstore(add(ptr, 0x140), calldataload(swapAction))
            mstore(add(ptr, 0x160), calldataload(add(swapAction, 0x20)))
            mstore(add(ptr, 0x180), dataHash)
            mstore(add(ptr, 0x1a0), receiver)
            hash := keccak256(ptr, 0x1c0)
        }
    }

    /// @dev Fixed-size mark-for-refund hash without abi.encode overhead.
    function _hashMarkForRefund(
        bytes32 quoteId,
        uint256 nonce
    ) internal view returns (bytes32 hash) {
        assembly {
            let ptr := mload(0x40)
            mstore(ptr, chainid())
            mstore(add(ptr, 0x20), address())
            mstore(add(ptr, 0x40), MARK_FOR_REFUND_DISCRIMINATOR)
            mstore(add(ptr, 0x60), quoteId)
            mstore(add(ptr, 0x80), nonce)
            hash := keccak256(ptr, 0xa0)
        }
    }

    function _verifyAndMarkQuoteId(
        bytes32 messageHash,
        bytes calldata signature,
        bytes32 quoteId
    ) internal {
        address signer = SOLVER_SIGNER;
        if (signer != _authenticate(messageHash, signature)) {
            assembly {
                mstore(0x00, 0x815e1d64)
                revert(0x1c, 0x04)
            }
        }
        _markQuoteId(quoteId);
    }

    function _verifyAndMarkNonce(
        bytes32 messageHash,
        bytes calldata signature,
        uint256 nonce
    ) internal {
        address signer = SOLVER_SIGNER;
        if (signer != _authenticate(messageHash, signature)) {
            assembly {
                mstore(0x00, 0x815e1d64)
                revert(0x1c, 0x04)
            }
        }
        _markNonce(nonce);
    }

    function _authenticate(
        bytes32 messageHash,
        bytes calldata signature
    ) internal view returns (address signer) {
        assembly {
            if iszero(eq(signature.length, 0x41)) {
                mstore(0x00, 0x815e1d64)
                revert(0x1c, 0x04)
            }

            let ptr := mload(0x40)
            mstore(
                ptr,
                0x19457468657265756d205369676e6564204d6573736167653a0a333200000000
            )
            mstore(add(ptr, 0x1c), messageHash)
            let digest := keccak256(ptr, 0x3c)

            mstore(ptr, digest)
            mstore(add(ptr, 0x20), byte(0, calldataload(add(signature.offset, 0x40))))
            mstore(add(ptr, 0x40), calldataload(signature.offset))
            mstore(add(ptr, 0x60), calldataload(add(signature.offset, 0x20)))

            signer := mul(
                mload(ptr),
                staticcall(gas(), 0x01, ptr, 0x80, ptr, 0x20)
            )
        }
    }

    function _markQuoteId(bytes32 quoteId) internal {
        assembly {
            mstore(0, quoteId)
            mstore(0x20, quoteIdUsed.slot)
            let dataSlot := keccak256(0, 0x40)

            if sload(dataSlot) {
                mstore(0x00, 0x140dcdb5)
                revert(0x1c, 0x04)
            }

            sstore(dataSlot, 0x01)
        }
    }

    function _markNonce(uint256 nonce) internal {
        assembly {
            mstore(0, nonce)
            mstore(0x20, nonceUsed.slot)
            let dataSlot := keccak256(0, 0x40)

            if sload(dataSlot) {
                mstore(0x00, 0x756688fe)
                revert(0x1c, 0x04)
            }

            sstore(dataSlot, 0x01)
        }
    }

    /// @dev Does not revert on failure. Caller should check the return value.
    function _performAction(
        Action calldata action
    ) internal returns (bool success) {
        assembly {
            let actionDataLength := calldataload(add(action, 0x60))

            let freeMemPtr := mload(0x40)
            calldatacopy(
                freeMemPtr,
                add(action, 0x80),
                actionDataLength
            )

            success := call(
                gas(),
                calldataload(action),
                calldataload(add(action, 0x20)),
                freeMemPtr,
                actionDataLength,
                0,
                0
            )
        }
    }
}
Chain explorer5821msChain node73ms