Some checks failed
CI/CD Pipeline / Solidity Contracts (push) Failing after 1m11s
CI/CD Pipeline / Security Scanning (push) Has been cancelled
CI/CD Pipeline / Lint and Format (push) Has been cancelled
CI/CD Pipeline / Terraform Validation (push) Has been cancelled
CI/CD Pipeline / Kubernetes Validation (push) Has been cancelled
Validation / validate-genesis (push) Has been cancelled
Validation / validate-terraform (push) Has been cancelled
Validation / validate-kubernetes (push) Has been cancelled
Validation / validate-smart-contracts (push) Has been cancelled
Validation / validate-security (push) Has been cancelled
Validation / validate-documentation (push) Has been cancelled
Deploy ChainID 138 / Deploy ChainID 138 (push) Failing after 1m4s
HYBX OMNL TypeScript & anchor / token-aggregation build + reconcile artifact (push) Failing after 31s
OMNL reconcile anchor / Run omnl:reconcile and upload artifacts (push) Failing after 29s
Verify Deployment / Verify Deployment (push) Failing after 57s
Relay router, reserve system, oracle publisher, token-aggregation compliance middleware, and Monad deployment scripts. Co-authored-by: Cursor <cursoragent@cursor.com>
152 lines
5.3 KiB
Solidity
152 lines
5.3 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.19;
|
|
|
|
import "../ccip/IRouterClient.sol";
|
|
import {Client} from "@chainlink/contracts-ccip/contracts/libraries/Client.sol";
|
|
import {IAny2EVMMessageReceiver} from "@chainlink/contracts-ccip/contracts/interfaces/IAny2EVMMessageReceiver.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");
|
|
|
|
// CWMultiTokenBridgeL2 and official CCIP receivers use Client.Any2EVMMessage
|
|
// (destTokenAmounts: EVMTokenAmount[]). Legacy CCIPRelayBridge uses IRouterClient
|
|
// Any2EVMMessage (tokenAmounts with amountType). Try Client format first, then legacy.
|
|
Client.Any2EVMMessage memory clientMessage = _toClientMessage(message);
|
|
(bool success, bytes memory returnData) = bridge.call(
|
|
abi.encodeCall(IAny2EVMMessageReceiver.ccipReceive, (clientMessage))
|
|
);
|
|
if (!success) {
|
|
(success, returnData) = bridge.call(
|
|
abi.encodeWithSignature(
|
|
"ccipReceive((bytes32,uint64,bytes,bytes,(address,uint256,uint8)[]))",
|
|
message
|
|
)
|
|
);
|
|
}
|
|
if (!success && returnData.length > 0) {
|
|
assembly {
|
|
revert(add(returnData, 32), mload(returnData))
|
|
}
|
|
}
|
|
require(success, "CCIPRelayRouter: ccipReceive failed");
|
|
|
|
// If we get here, the call succeeded. Decode common payload shapes without
|
|
// reverting the full relay transaction on non-WETH bridge payloads.
|
|
(address recipient, uint256 amount) = _decodeRecipientAndAmount(message.data);
|
|
|
|
emit MessageRelayed(
|
|
message.messageId,
|
|
message.sourceChainSelector,
|
|
bridge,
|
|
recipient,
|
|
amount
|
|
);
|
|
}
|
|
|
|
function _decodeRecipientAndAmount(bytes calldata data)
|
|
internal
|
|
pure
|
|
returns (address recipient, uint256 amount)
|
|
{
|
|
if (data.length == 64) {
|
|
return abi.decode(data, (address, uint256));
|
|
}
|
|
|
|
if (data.length == 96) {
|
|
(, recipient, amount) = abi.decode(data, (address, address, uint256));
|
|
return (recipient, amount);
|
|
}
|
|
|
|
if (data.length == 128) {
|
|
(recipient, amount, , ) = abi.decode(data, (address, uint256, address, uint256));
|
|
return (recipient, amount);
|
|
}
|
|
|
|
return (address(0), 0);
|
|
}
|
|
|
|
function _toClientMessage(
|
|
IRouterClient.Any2EVMMessage calldata message
|
|
) internal pure returns (Client.Any2EVMMessage memory clientMessage) {
|
|
Client.EVMTokenAmount[] memory destAmounts = new Client.EVMTokenAmount[](message.tokenAmounts.length);
|
|
for (uint256 i = 0; i < message.tokenAmounts.length; i++) {
|
|
destAmounts[i] = Client.EVMTokenAmount({
|
|
token: message.tokenAmounts[i].token,
|
|
amount: message.tokenAmounts[i].amount
|
|
});
|
|
}
|
|
clientMessage = Client.Any2EVMMessage({
|
|
messageId: message.messageId,
|
|
sourceChainSelector: message.sourceChainSelector,
|
|
sender: message.sender,
|
|
data: message.data,
|
|
destTokenAmounts: destAmounts
|
|
});
|
|
}
|
|
}
|