// 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 ); } }