99 lines
3.2 KiB
Solidity
99 lines
3.2 KiB
Solidity
|
|
// SPDX-License-Identifier: MIT
|
||
|
|
pragma solidity ^0.8.19;
|
||
|
|
|
||
|
|
import "../ccip/IRouterClient.sol";
|
||
|
|
import "@openzeppelin/contracts/access/AccessControl.sol";
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @title CCIP Relay Router
|
||
|
|
* @notice Relay router that forwards CCIP messages from off-chain relay to bridge contracts
|
||
|
|
* @dev This contract acts as a relay endpoint on the destination chain
|
||
|
|
*/
|
||
|
|
contract CCIPRelayRouter is AccessControl {
|
||
|
|
bytes32 public constant RELAYER_ROLE = keccak256("RELAYER_ROLE");
|
||
|
|
|
||
|
|
// Mapping of bridge contracts that can receive messages
|
||
|
|
mapping(address => bool) public authorizedBridges;
|
||
|
|
|
||
|
|
event MessageRelayed(
|
||
|
|
bytes32 indexed messageId,
|
||
|
|
uint64 indexed sourceChainSelector,
|
||
|
|
address indexed bridge,
|
||
|
|
address recipient,
|
||
|
|
uint256 amount
|
||
|
|
);
|
||
|
|
|
||
|
|
event BridgeAuthorized(address indexed bridge);
|
||
|
|
event BridgeRevoked(address indexed bridge);
|
||
|
|
|
||
|
|
constructor() {
|
||
|
|
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @notice Authorize a bridge contract to receive relayed messages
|
||
|
|
*/
|
||
|
|
function authorizeBridge(address bridge) external onlyRole(DEFAULT_ADMIN_ROLE) {
|
||
|
|
authorizedBridges[bridge] = true;
|
||
|
|
emit BridgeAuthorized(bridge);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @notice Revoke authorization for a bridge contract
|
||
|
|
*/
|
||
|
|
function revokeBridge(address bridge) external onlyRole(DEFAULT_ADMIN_ROLE) {
|
||
|
|
authorizedBridges[bridge] = false;
|
||
|
|
emit BridgeRevoked(bridge);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @notice Grant relayer role to an address
|
||
|
|
*/
|
||
|
|
function grantRelayerRole(address relayer) external onlyRole(DEFAULT_ADMIN_ROLE) {
|
||
|
|
_grantRole(RELAYER_ROLE, relayer);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @notice Revoke relayer role from an address
|
||
|
|
*/
|
||
|
|
function revokeRelayerRole(address relayer) external onlyRole(DEFAULT_ADMIN_ROLE) {
|
||
|
|
_revokeRole(RELAYER_ROLE, relayer);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @notice Relay a CCIP message to a bridge contract
|
||
|
|
* @param bridge The bridge contract address to receive the message
|
||
|
|
* @param message The CCIP message to relay
|
||
|
|
*/
|
||
|
|
function relayMessage(
|
||
|
|
address bridge,
|
||
|
|
IRouterClient.Any2EVMMessage calldata message
|
||
|
|
) external onlyRole(RELAYER_ROLE) {
|
||
|
|
require(authorizedBridges[bridge], "CCIPRelayRouter: bridge not authorized");
|
||
|
|
|
||
|
|
// Call bridge's ccipReceive function using low-level call
|
||
|
|
// This ensures proper ABI encoding for the struct parameter
|
||
|
|
// The call will revert with the actual error if it fails
|
||
|
|
(bool success, bytes memory returnData) = bridge.call(
|
||
|
|
abi.encodeWithSignature("ccipReceive((bytes32,uint64,bytes,bytes,(address,uint256,uint8)[]))", message)
|
||
|
|
);
|
||
|
|
require(success, "CCIPRelayRouter: ccipReceive failed");
|
||
|
|
|
||
|
|
// If we get here, the call succeeded
|
||
|
|
// Decode recipient and amount from message data
|
||
|
|
(address recipient, uint256 amount, , ) = abi.decode(
|
||
|
|
message.data,
|
||
|
|
(address, uint256, address, uint256)
|
||
|
|
);
|
||
|
|
|
||
|
|
emit MessageRelayed(
|
||
|
|
message.messageId,
|
||
|
|
message.sourceChainSelector,
|
||
|
|
bridge,
|
||
|
|
recipient,
|
||
|
|
amount
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|