RelayDepository

0x4cd00e387622c35bddb9b4c962c136462338bc31

Verification
Verified
v0.8.28+commit.7893614a
Type
Contract
8,628 bytes
ABI entries
33
7 read · 10 write
License
none

Contract information

Address
0x4cd00e387622c35bddb9b4c962c136462338bc31
Chain
Robinhood Chain (4663)
Compiler
v0.8.28+commit.7893614a
Optimization
Disabled
Creation tx
0x3dce830a05…334f9cc928

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (7)

_CALL_REQUEST_TYPEHASH()bytes32
_CALL_TYPEHASH()bytes32
allocator()address
callRequests(bytes32)bool
eip712Domain()bytes1, string, string, uint256, address, bytes32, uint256[]
owner()address
ownershipHandoverExpiresAt(address)uint256

Events (6)

OwnershipHandoverCanceledOwnershipHandoverRequestedOwnershipTransferredRelayCallExecutedRelayErc20DepositRelayNativeDeposit

ABI

[
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "_owner",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "_allocator",
        "type": "address"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [],
    "name": "AddressCannotBeZero",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "AlreadyInitialized",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "bytes",
        "name": "returnData",
        "type": "bytes"
      }
    ],
    "name": "CallFailed",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "CallRequestAlreadyUsed",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "CallRequestExpired",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidSignature",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NewOwnerIsZeroAddress",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NoHandoverRequest",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "Unauthorized",
    "type": "error"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "pendingOwner",
        "type": "address"
      }
    ],
    "name": "OwnershipHandoverCanceled",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "pendingOwner",
        "type": "address"
      }
    ],
    "name": "OwnershipHandoverRequested",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "oldOwner",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "OwnershipTransferred",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "bytes32",
        "name": "id",
        "type": "bytes32"
      },
      {
        "components": [
          {
            "internalType": "address",
            "name": "to",
            "type": "address"
          },
          {
            "internalType": "bytes",
            "name": "data",
            "type": "bytes"
          },
          {
            "internalType": "uint256",
            "name": "value",
            "type": "uint256"
          },
          {
            "internalType": "bool",
            "name": "allowFailure",
            "type": "bool"
          }
        ],
        "indexed": false,
        "internalType": "struct Call",
        "name": "call",
        "type": "tuple"
      }
    ],
    "name": "RelayCallExecuted",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "address",
        "name": "from",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "bytes32",
        "name": "id",
        "type": "bytes32"
      }
    ],
    "name": "RelayErc20Deposit",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "address",
        "name": "from",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "bytes32",
        "name": "id",
        "type": "bytes32"
      }
    ],
    "name": "RelayNativeDeposit",
    "type": "event"
  },
  {
    "inputs": [],
    "name": "_CALL_REQUEST_TYPEHASH",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "_CALL_TYPEHASH",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "allocator",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }
    ],
    "name": "callRequests",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "cancelOwnershipHandover",
    "outputs": [],
    "stateMutability": "payable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "pendingOwner",
        "type": "address"
      }
    ],
    "name": "completeOwnershipHandover",
    "outputs": [],
    "stateMutability": "payable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "depositor",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "bytes32",
        "name": "id",
        "type": "bytes32"
      }
    ],
    "name": "depositErc20",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "depositor",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      },
      {
        "internalType": "bytes32",
        "name": "id",
        "type": "bytes32"
      }
    ],
    "name": "depositErc20",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "depositor",
        "type": "address"
      },
      {
        "internalType": "bytes32",
        "name": "id",
        "type": "bytes32"
      }
    ],
    "name": "depositNative",
    "outputs": [],
    "stateMutability": "payable",
    "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": [
          {
            "components": [
              {
                "internalType": "address",
                "name": "to",
                "type": "address"
              },
              {
                "internalType": "bytes",
                "name": "data",
                "type": "bytes"
              },
              {
                "internalType": "uint256",
                "name": "value",
                "type": "uint256"
              },
              {
                "internalType": "bool",
                "name": "allowFailure",
                "type": "bool"
              }
            ],
            "internalType": "struct Call[]",
            "name": "calls",
            "type": "tuple[]"
          },
          {
            "internalType": "uint256",
            "name": "nonce",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "expiration",
            "type": "uint256"
          }
        ],
        "internalType": "struct CallRequest",
        "name": "request",
        "type": "tuple"
      },
      {
        "internalType": "bytes",
        "name": "signature",
        "type": "bytes"
      }
    ],
    "name": "execute",
    "outputs": [
      {
        "components": [
          {
            "internalType": "bool",
            "name": "success",
            "type": "bool"
          },
          {
            "internalType": "bytes",
            "name": "returnData",
            "type": "bytes"
          }
        ],
        "internalType": "struct CallResult[]",
        "name": "results",
        "type": "tuple[]"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "owner",
    "outputs": [
      {
        "internalType": "address",
        "name": "result",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "pendingOwner",
        "type": "address"
      }
    ],
    "name": "ownershipHandoverExpiresAt",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "result",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "renounceOwnership",
    "outputs": [],
    "stateMutability": "payable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "requestOwnershipHandover",
    "outputs": [],
    "stateMutability": "payable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "_allocator",
        "type": "address"
      }
    ],
    "name": "setAllocator",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "transferOwnership",
    "outputs": [],
    "stateMutability": "payable",
    "type": "function"
  }
]

Source code

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

import {Ownable} from "solady/auth/Ownable.sol";
import {ERC20} from "solady/tokens/ERC20.sol";
import {EIP712} from "solady/utils/EIP712.sol";
import {SafeTransferLib} from "solady/utils/SafeTransferLib.sol";
import {SignatureCheckerLib} from "solady/utils/SignatureCheckerLib.sol";

import {Call, CallRequest, CallResult} from "./utils/RelayDepositoryStructs.sol";

/// @title RelayDepository
/// @author Relay
contract RelayDepository is Ownable, EIP712 {
    using SafeTransferLib for address;
    using SignatureCheckerLib for address;

    /// @notice Revert if the address is zero
    error AddressCannotBeZero();

    /// @notice Revert if the signature is invalid
    error InvalidSignature();

    /// @notice Revert if the call request is expired
    error CallRequestExpired();

    /// @notice Revert if the call request has already been used
    error CallRequestAlreadyUsed();

    /// @notice Revert if a call fails
    error CallFailed(bytes returnData);

    /// @notice Emit event when a native deposit is made
    event RelayNativeDeposit(address from, uint256 amount, bytes32 id);

    /// @notice Emit event when an erc20 deposit is made
    event RelayErc20Deposit(
        address from,
        address token,
        uint256 amount,
        bytes32 id
    );

    /// @notice Emit event when a call is executed
    event RelayCallExecuted(bytes32 id, Call call);

    /// @notice The EIP-712 typehash for the Call struct
    bytes32 public constant _CALL_TYPEHASH =
        keccak256(
            "Call(address to,bytes data,uint256 value,bool allowFailure)"
        );

    /// @notice The EIP-712 typehash for the CallRequest struct
    bytes32 public constant _CALL_REQUEST_TYPEHASH =
        keccak256(
            "CallRequest(Call[] calls,uint256 nonce,uint256 expiration)Call(address to,bytes data,uint256 value,bool allowFailure)"
        );

    /// @notice Set of executed call requests
    mapping(bytes32 => bool) public callRequests;

    /// @notice The allocator address
    address public allocator;

    constructor(address _owner, address _allocator) {
        _initializeOwner(_owner);
        allocator = _allocator;
    }

    /// @notice Set the allocator address
    /// @param _allocator The new allocator address
    function setAllocator(address _allocator) external onlyOwner {
        if (_allocator == address(0)) {
            revert AddressCannotBeZero();
        }
        allocator = _allocator;
    }

    /// @notice Deposit native tokens and emit a `RelayNativeDeposit` event
    /// @param depositor The address of the depositor - set to `address(0)` to credit `msg.sender`
    /// @param id The id associated with the deposit
    function depositNative(address depositor, bytes32 id) external payable {
        address depositorAddress = depositor == address(0)
            ? msg.sender
            : depositor;

        // Emit the `RelayNativeDeposit` event
        emit RelayNativeDeposit(depositorAddress, msg.value, id);
    }

    /// @notice Deposit erc20 tokens and emit an `RelayErc20Deposit` event
    /// @param depositor The address of the depositor - set to `address(0)` to credit `msg.sender`
    /// @param token The erc20 token to deposit
    /// @param amount The amount to deposit
    /// @param id The id associated with the deposit
    function depositErc20(
        address depositor,
        address token,
        uint256 amount,
        bytes32 id
    ) public {
        // Transfer the tokens to the contract
        token.safeTransferFrom(msg.sender, address(this), amount);

        address depositorAddress = depositor == address(0)
            ? msg.sender
            : depositor;

        // Emit the `RelayErc20Deposit` event
        emit RelayErc20Deposit(depositorAddress, token, amount, id);
    }

    /// @notice Deposit erc20 tokens and emit an `RelayErc20Deposit` event
    /// @param depositor The address of the depositor - set to `address(0)` to credit `msg.sender`
    /// @param token The erc20 token to deposit
    /// @param id The id associated with the deposit
    function depositErc20(
        address depositor,
        address token,
        bytes32 id
    ) external {
        uint256 amount = ERC20(token).allowance(msg.sender, address(this));

        depositErc20(depositor, token, amount, id);
    }

    /// @notice Execute a `CallRequest` signed by the allocator
    /// @param request The `CallRequest` to execute
    /// @param signature The signature from the allocator
    /// @return results The results of the calls
    function execute(
        CallRequest calldata request,
        bytes memory signature
    ) external returns (CallResult[] memory results) {
        (bytes32 structHash, bytes32 eip712Hash) = _hashCallRequest(request);

        // Validate the call request expiration
        if (request.expiration < block.timestamp) {
            revert CallRequestExpired();
        }

        // Validate the allocator signature
        if (!allocator.isValidSignatureNow(eip712Hash, signature)) {
            revert InvalidSignature();
        }

        // Revert if the call request has already been used
        if (callRequests[structHash]) {
            revert CallRequestAlreadyUsed();
        }

        // Mark the call request as used
        callRequests[structHash] = true;

        // Execute the calls
        results = _executeCalls(structHash, request.calls);
    }

    /// @notice Execute a list of calls
    /// @param id The id of the call request
    /// @param calls The calls to execute
    /// @return returnData The results of the calls
    function _executeCalls(
        bytes32 id,
        Call[] calldata calls
    ) internal returns (CallResult[] memory returnData) {
        unchecked {
            uint256 length = calls.length;

            // Initialize the return data array
            returnData = new CallResult[](length);

            // Iterate over the calls
            for (uint256 i; i < length; i++) {
                Call memory c = calls[i];

                // Execute the call
                (bool success, bytes memory data) = c.to.call{value: c.value}(
                    c.data
                );

                // Revert if the call failed and failure is not allowed
                if (!success && !c.allowFailure) {
                    revert CallFailed(data);
                }

                // Store the success status and return data
                returnData[i] = CallResult({
                    success: success,
                    returnData: data
                });

                // Emit the `RelayCallExecuted` event if the call was successful
                if (success) {
                    emit RelayCallExecuted(id, c);
                }
            }
        }
    }

    /// @notice Helper function to hash a `CallRequest` and return the EIP-712 digest
    /// @param request The `CallRequest` to hash
    /// @return structHash The struct hash
    /// @return eip712Hash The EIP712 hash
    function _hashCallRequest(
        CallRequest calldata request
    ) internal view returns (bytes32 structHash, bytes32 eip712Hash) {
        // Initialize the array of call hashes
        bytes32[] memory callHashes = new bytes32[](request.calls.length);

        // Iterate over the underlying calls
        for (uint256 i = 0; i < request.calls.length; i++) {
            // Hash the call
            bytes32 callHash = keccak256(
                abi.encode(
                    _CALL_TYPEHASH,
                    request.calls[i].to,
                    keccak256(request.calls[i].data),
                    request.calls[i].value,
                    request.calls[i].allowFailure
                )
            );

            // Store the hash in the array
            callHashes[i] = callHash;
        }

        // Get the struct hash
        structHash = keccak256(
            abi.encode(
                _CALL_REQUEST_TYPEHASH,
                keccak256(abi.encodePacked(callHashes)),
                request.nonce,
                request.expiration
            )
        );

        // Get the EIP-712 hash
        eip712Hash = _hashTypedData(structHash);
    }

    /// @notice Returns the domain name and version of the contract to be used in the domain separator
    /// @return name The domain name
    /// @return version The version
    function _domainNameAndVersion()
        internal
        pure
        override
        returns (string memory name, string memory version)
    {
        name = "RelayDepository";
        version = "1";
    }
}
Chain explorer2310msChain node71ms