- CCIP/trustless bridge contracts, GRU tokens, DEX/PMM tests, reserve vault. - Token-aggregation service routes, planner, chain config, relay env templates. - Config snapshots and multi-chain deployment markdown updates. - gitignore services/btc-intake/dist/ (tsc output); do not track dist. Run forge build && forge test before deploy (large solc graph). Made-with: Cursor
117 lines
3.8 KiB
Solidity
117 lines
3.8 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, ) = 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 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);
|
|
}
|
|
}
|