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
- Creator
- 0x4e59b44847…26c0B4956C
- 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