LZMultiCall

0xd5a0f386a9f3b705d21cfd41a6ef6a4dcdba2692

Verification
Verified
v0.8.26+commit.8a97fa7a
Type
Contract
5,577 bytes
ABI entries
23
4 read · 3 write
License
mit

Contract information

Address
0xd5a0f386a9f3b705d21cfd41a6ef6a4dcdba2692
Chain
Robinhood Chain (4663)
Compiler
v0.8.26+commit.8a97fa7a
Optimization
Enabled
Creation tx
0x2d26abbaab…919c7bc75d

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (4)

TRANSFER_DELEGATE()address
eip712Domain()bytes1, string, string, uint256, address, bytes32, uint256[]
getDigestToSign(tuple[], bytes32, uint256, address)bytes32
nonces(address)uint256

Events (4)

EIP712DomainChangedExecutedExecutedWithSignatureNativeTransfer

ABI

[
  {
    "inputs": [],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "blockTimestamp",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "expiration",
        "type": "uint256"
      }
    ],
    "name": "Expired",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "received",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "expected",
        "type": "uint256"
      }
    ],
    "name": "InvalidCalldataLength",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "received",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "signer",
        "type": "address"
      }
    ],
    "name": "InvalidFromAddress",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "bytes4",
        "name": "selector",
        "type": "bytes4"
      }
    ],
    "name": "InvalidSelector",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidShortString",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidSignature",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "OnlySelf",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ReentrancyGuardReentrantCall",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "SafeERC20FailedOperation",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "string",
        "name": "str",
        "type": "string"
      }
    ],
    "name": "StringTooLong",
    "type": "error"
  },
  {
    "anonymous": false,
    "inputs": [],
    "name": "EIP712DomainChanged",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "signer",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "quoteId",
        "type": "bytes32"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "nonce",
        "type": "uint256"
      }
    ],
    "name": "Executed",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "signer",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "quoteId",
        "type": "bytes32"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "nonce",
        "type": "uint256"
      }
    ],
    "name": "ExecutedWithSignature",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "to",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "value",
        "type": "uint256"
      }
    ],
    "name": "NativeTransfer",
    "type": "event"
  },
  {
    "inputs": [],
    "name": "TRANSFER_DELEGATE",
    "outputs": [
      {
        "internalType": "contract ITransferDelegate",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "eip712Domain",
    "outputs": [
      {
        "internalType": "bytes1",
        "name": "fields",
        "type": "bytes1"
      },
      {
        "internalType": "string",
        "name": "name",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "version",
        "type": "string"
      },
      {
        "internalType": "uint256",
        "name": "chainId",
        "type": "uint256"
      },
      {
        "internalType": "address",
        "name": "verifyingContract",
        "type": "address"
      },
      {
        "internalType": "bytes32",
        "name": "salt",
        "type": "bytes32"
      },
      {
        "internalType": "uint256[]",
        "name": "extensions",
        "type": "uint256[]"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "components": [
          {
            "internalType": "address",
            "name": "target",
            "type": "address"
          },
          {
            "internalType": "uint256",
            "name": "value",
            "type": "uint256"
          },
          {
            "internalType": "bytes",
            "name": "data",
            "type": "bytes"
          }
        ],
        "internalType": "struct ILZMultiCall.Call[]",
        "name": "_calls",
        "type": "tuple[]"
      },
      {
        "internalType": "bytes32",
        "name": "_quoteId",
        "type": "bytes32"
      }
    ],
    "name": "execute",
    "outputs": [],
    "stateMutability": "payable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "components": [
          {
            "internalType": "address",
            "name": "target",
            "type": "address"
          },
          {
            "internalType": "uint256",
            "name": "value",
            "type": "uint256"
          },
          {
            "internalType": "bytes",
            "name": "data",
            "type": "bytes"
          }
        ],
        "internalType": "struct ILZMultiCall.Call[]",
        "name": "_calls",
        "type": "tuple[]"
      },
      {
        "internalType": "bytes32",
        "name": "_quoteId",
        "type": "bytes32"
      },
      {
        "internalType": "uint256",
        "name": "_expiration",
        "type": "uint256"
      },
      {
        "internalType": "address",
        "name": "_signer",
        "type": "address"
      },
      {
        "internalType": "bytes",
        "name": "_signature",
        "type": "bytes"
      }
    ],
    "name": "execute",
    "outputs": [],
    "stateMutability": "payable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "components": [
          {
            "internalType": "address",
            "name": "target",
            "type": "address"
          },
          {
            "internalType": "uint256",
            "name": "value",
            "type": "uint256"
          },
          {
            "internalType": "bytes",
            "name": "data",
            "type": "bytes"
          }
        ],
        "internalType": "struct ILZMultiCall.Call[]",
        "name": "_calls",
        "type": "tuple[]"
      },
      {
        "internalType": "bytes32",
        "name": "_quoteId",
        "type": "bytes32"
      },
      {
        "internalType": "uint256",
        "name": "_expiration",
        "type": "uint256"
      },
      {
        "internalType": "address",
        "name": "_signer",
        "type": "address"
      }
    ],
    "name": "getDigestToSign",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "digest",
        "type": "bytes32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "signer",
        "type": "address"
      }
    ],
    "name": "nonces",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "nonce",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address[]",
        "name": "_tokens",
        "type": "address[]"
      },
      {
        "internalType": "address",
        "name": "_recipient",
        "type": "address"
      }
    ],
    "name": "sweep",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "stateMutability": "payable",
    "type": "receive"
  }
]

Source code

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol";
import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
import {SafeERC20} from "@openzeppelin-v5.5.0/contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC20} from "@openzeppelin-v5.5.0/contracts/token/ERC20/IERC20.sol";
import {LowLevelCall} from "@openzeppelin-v5.5.0/contracts/utils/LowLevelCall.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

import {TransferDelegate} from "./TransferDelegate.sol";
import {ITransferDelegate} from "./interfaces/ITransferDelegate.sol";
import {ILZMultiCall} from "./interfaces/ILZMultiCall.sol";

/**
 * @title LZMultiCall
 * @author LayerZero Labs (@TRileySchwarz, tinom.eth)
 * @custom:version 1.0.0
 * @notice Contract to execute multiple calls for users, including approved ERC20 transfers.
 *         Users approve the transfer delegate contract to spend their ERC20 tokens, and this
 *         contract ensures that the `from` address in the transfer call matches the signer.
 * @dev Any ETH, token, or authorization left in this contract can be permissionlessly claimed by
 *      anyone.
 * @dev No allowance should ever be set on this contract for any token, as it'd allow anyone to
 *      permissionlessly claim the tokens.
 */
contract LZMultiCall is ILZMultiCall, EIP712, ReentrancyGuard {
    using SafeERC20 for IERC20;

    /// @notice Transfer delegate that holds allowances for signers.
    ITransferDelegate public immutable TRANSFER_DELEGATE;

    /**
     * @dev Byte layout of `TransferDelegate.delegateTransferFrom(token, from, to, amount)`:
     *      [4:selector][32:token][32:from][32:to][32:amount]
     */
    uint256 internal constant TRANSFER_DATA_LENGTH = 132;
    uint256 internal constant TRANSFER_FROM_OFFSET = 48;

    bytes32 internal constant CALL_TYPE_HASH = keccak256("Call(address target,uint256 value,bytes data)");
    bytes32 internal constant EXECUTE_TYPE_HASH = keccak256(
        /// @dev Primary type and dependencies (alphabetical, deduped).
        "Execute(Call[] calls,bytes32 quoteId,uint256 expiration,uint256 nonce)"
        "Call(address target,uint256 value,bytes data)"
    );

    /// @notice Nonce for replay protection (per signer).
    mapping(address signer => uint256 nonce) public nonces;

    /// @dev Allow the contract to receive ETH mid-execution.
    receive() external payable virtual {}

    /// @dev Deploys and links a transfer delegate contract.
    constructor() EIP712("LZMultiCall", "1.0.0") {
        TRANSFER_DELEGATE = new TransferDelegate(address(this));
    }

    /**
     * @inheritdoc ILZMultiCall
     */
    function execute(
        Call[] calldata _calls,
        bytes32 _quoteId,
        uint256 _expiration,
        address _signer,
        bytes calldata _signature
    ) public payable virtual nonReentrant {
        if (block.timestamp > _expiration) revert Expired(block.timestamp, _expiration);

        uint256 nonce;
        unchecked {
            nonce = nonces[_signer]++;
        }

        if (!SignatureChecker.isValidSignatureNow(
                _signer, _getDigestToSign(_calls, _quoteId, _expiration, nonce), _signature
            )) {
            revert InvalidSignature();
        }

        _executeCalls(_calls, _signer);

        emit ExecutedWithSignature(_signer, _quoteId, nonce);
    }

    /**
     * @inheritdoc ILZMultiCall
     */
    function execute(Call[] calldata _calls, bytes32 _quoteId) public payable virtual nonReentrant {
        uint256 nonce;
        unchecked {
            nonce = nonces[msg.sender]++;
        }

        _executeCalls(_calls, msg.sender);

        emit Executed(msg.sender, _quoteId, nonce);
    }

    /**
     * @inheritdoc ILZMultiCall
     */
    function sweep(address[] calldata _tokens, address _recipient) public virtual {
        if (msg.sender != address(this)) revert OnlySelf();

        for (uint256 i = 0; i < _tokens.length; ++i) {
            address token = _tokens[i];
            if (token == address(0)) {
                uint256 balance = address(this).balance;
                if (balance > 0) _handleCall(payable(_recipient), bytes(""), balance);
            } else {
                uint256 balance = IERC20(token).balanceOf(address(this));
                if (balance > 0) IERC20(token).safeTransfer(_recipient, balance);
            }
        }
    }

    /**
     * @inheritdoc ILZMultiCall
     */
    function getDigestToSign(Call[] calldata _calls, bytes32 _quoteId, uint256 _expiration, address _signer)
        public
        view
        virtual
        returns (bytes32 digest)
    {
        return _getDigestToSign(_calls, _quoteId, _expiration, nonces[_signer]);
    }

    /**
     * @notice Internal function to execute calls.
     * @param _calls Array of calls to execute
     * @param _signer Address that signed the calls, to validate transfer delegate calls
     */
    function _executeCalls(Call[] calldata _calls, address _signer) internal virtual {
        for (uint256 i = 0; i < _calls.length; ++i) {
            Call calldata call = _calls[i];

            if (call.target == address(TRANSFER_DELEGATE)) {
                _handleTransfer(call.data, _signer, call.value);
            } else {
                _handleCall(call.target, call.data, call.value);
            }
        }
    }

    /**
     * @notice Internal function to handle transfer delegate calls.
     * @dev Validates that the `from` address in the call matches the signer address.
     * @param _data Calldata of the transfer delegate call
     * @param _signer Address that signed the calls, matching call's `from` address
     * @param _value `msg.value` for the transfer delegate call, it'll revert if non-zero
     */
    function _handleTransfer(bytes calldata _data, address _signer, uint256 _value) internal virtual {
        if (_data.length != TRANSFER_DATA_LENGTH) revert InvalidCalldataLength(_data.length, TRANSFER_DATA_LENGTH);

        (bytes4 selector, address from) = _extractSelectorAndFrom(_data);

        if (selector != TransferDelegate.delegateTransferFrom.selector) revert InvalidSelector(selector);
        if (from != _signer) revert InvalidFromAddress(from, _signer);

        _handleCall(address(TRANSFER_DELEGATE), _data, _value);
    }

    /**
     * @notice Internal function to forward arbitrary calls.
     * @param _target Address of the target contract to call
     * @param _data Calldata
     * @param _value Value to send with the call
     */
    function _handleCall(address _target, bytes memory _data, uint256 _value) internal virtual {
        bool success = LowLevelCall.callNoReturn(_target, _value, _data);
        if (!success) {
            LowLevelCall.bubbleRevert();
        }

        if (_data.length == 0 && _value > 0) {
            emit NativeTransfer(_target, _value);
        }
    }

    /**
     * @notice Internal function to efficiently extract selector and `from` address from calldata.
     * @param _data `Call.data` calldata
     * @return selector 4-byte selector
     * @return from 20-byte address
     */
    function _extractSelectorAndFrom(bytes calldata _data)
        internal
        pure
        virtual
        returns (bytes4 selector, address from)
    {
        assembly {
            selector := calldataload(_data.offset)
            from := shr(96, calldataload(add(_data.offset, TRANSFER_FROM_OFFSET)))
        }
    }

    /**
     * @notice Internal function to get the digest to sign for a given set of calls.
     * @param _calls Array of calls to execute
     * @param _quoteId Unique identifier for this execution
     * @param _expiration Expiration block timestamp after which the calls will revert
     * @param _nonce Nonce for replay protection
     * @return digest Digest that should be signed
     */
    function _getDigestToSign(Call[] calldata _calls, bytes32 _quoteId, uint256 _expiration, uint256 _nonce)
        internal
        view
        virtual
        returns (bytes32 digest)
    {
        bytes32[] memory callHashes = new bytes32[](_calls.length);

        for (uint256 i = 0; i < _calls.length; ++i) {
            Call calldata call = _calls[i];
            callHashes[i] = _hashCall(CALL_TYPE_HASH, call.target, call.value, keccak256(call.data));
        }

        bytes32 structHash =
            _hashStruct(EXECUTE_TYPE_HASH, _hashBytes32Array(callHashes), _quoteId, _expiration, _nonce);
        return _hashTypedDataV4(structHash);
    }

    /**
     * @notice Internal function to efficiently hash a call.
     * @dev Equivalent to `keccak256(abi.encode(_typeHash, _target, _value, _dataHash))`.
     * @param _typeHash Type hash of the EIP-712 call struct
     * @param _target Address of the target contract to call
     * @param _value Value to send with the call
     * @param _dataHash Hash of the calldata
     * @return callHash Hash of the call
     */
    function _hashCall(bytes32 _typeHash, address _target, uint256 _value, bytes32 _dataHash)
        internal
        pure
        virtual
        returns (bytes32 callHash)
    {
        assembly {
            let ptr := mload(0x40)
            mstore(ptr, _typeHash)
            mstore(add(ptr, 0x20), _target)
            mstore(add(ptr, 0x40), _value)
            mstore(add(ptr, 0x60), _dataHash)
            callHash := keccak256(ptr, 0x80)
        }
    }

    /**
     * @notice Internal function to efficiently hash the EIP-712 struct.
     * @dev Equivalent to `keccak256(abi.encode(_typeHash, _arrHash, _quoteId, _expiration, _nonce))`.
     * @param _typeHash Type hash of the EIP-712 struct
     * @param _arrHash Hash of the array of `bytes32`
     * @param _quoteId Unique identifier for this execution
     * @param _expiration Expiration block timestamp after which the calls will revert
     * @param _nonce Nonce for replay protection
     * @return hashedStruct Hash of the EIP-712 struct
     */
    function _hashStruct(bytes32 _typeHash, bytes32 _arrHash, bytes32 _quoteId, uint256 _expiration, uint256 _nonce)
        internal
        pure
        virtual
        returns (bytes32 hashedStruct)
    {
        assembly {
            let ptr := mload(0x40)
            mstore(ptr, _typeHash)
            mstore(add(ptr, 0x20), _arrHash)
            mstore(add(ptr, 0x40), _quoteId)
            mstore(add(ptr, 0x60), _expiration)
            mstore(add(ptr, 0x80), _nonce)
            hashedStruct := keccak256(ptr, 0xa0)
        }
    }

    /**
     * @notice Internal function to efficiently hash an array of `bytes32`.
     * @dev Equivalent to `keccak256(abi.encodePacked(_arr))`.
     * @param _arr Array of `bytes32` to hash
     * @return hashedArr Hash of the array
     */
    function _hashBytes32Array(bytes32[] memory _arr) internal pure virtual returns (bytes32 hashedArr) {
        assembly {
            hashedArr := keccak256(add(_arr, 0x20), shl(5, mload(_arr)))
        }
    }
}
Chain explorer4438msChain node91ms