Files
smom-dbis-138/contracts/ccip/CCIPRouter.sol
defiQUG 1fb7266469 Add Oracle Aggregator and CCIP Integration
- Introduced Aggregator.sol for Chainlink-compatible oracle functionality, including round-based updates and access control.
- Added OracleWithCCIP.sol to extend Aggregator with CCIP cross-chain messaging capabilities.
- Created .gitmodules to include OpenZeppelin contracts as a submodule.
- Developed a comprehensive deployment guide in NEXT_STEPS_COMPLETE_GUIDE.md for Phase 2 and smart contract deployment.
- Implemented Vite configuration for the orchestration portal, supporting both Vue and React frameworks.
- Added server-side logic for the Multi-Cloud Orchestration Portal, including API endpoints for environment management and monitoring.
- Created scripts for resource import and usage validation across non-US regions.
- Added tests for CCIP error handling and integration to ensure robust functionality.
- Included various new files and directories for the orchestration portal and deployment scripts.
2025-12-12 14:57:48 -08:00

210 lines
6.5 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "./IRouterClient.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
/**
* @title CCIP Router Implementation
* @notice Full Chainlink CCIP Router interface implementation
* @dev Implements message sending, fee calculation, and message validation
*/
contract CCIPRouter is IRouterClient {
using SafeERC20 for IERC20;
// Fee token (LINK token address)
address public immutable feeToken;
// Message tracking
mapping(bytes32 => bool) public sentMessages;
mapping(bytes32 => bool) public receivedMessages;
// Chain selectors
mapping(uint64 => bool) public supportedChains;
mapping(uint64 => address[]) public supportedTokens;
// Fee configuration
uint256 public baseFee; // Base fee in feeToken units
uint256 public dataFeePerByte; // Fee per byte of data
address public admin;
// Events are inherited from IRouterClient interface
modifier onlyAdmin() {
require(msg.sender == admin, "CCIPRouter: only admin");
_;
}
constructor(address _feeToken, uint256 _baseFee, uint256 _dataFeePerByte) {
// Allow zero address for native token fees (ETH)
// If feeToken is zero, fees are paid in native token (msg.value)
feeToken = _feeToken;
baseFee = _baseFee;
dataFeePerByte = _dataFeePerByte;
admin = msg.sender;
}
/**
* @notice Send a message to a destination chain
* @param destinationChainSelector The chain selector of the destination chain
* @param message The message to send
* @return messageId The ID of the sent message
* @return fees The fees required for the message
*/
function ccipSend(
uint64 destinationChainSelector,
EVM2AnyMessage memory message
) external payable returns (bytes32 messageId, uint256 fees) {
require(supportedChains[destinationChainSelector], "CCIPRouter: chain not supported");
require(message.receiver.length > 0, "CCIPRouter: empty receiver");
// Calculate fee
fees = getFee(destinationChainSelector, message);
// Collect fee
if (fees > 0) {
if (feeToken == address(0)) {
// Native token (ETH) fees
require(msg.value >= fees, "CCIPRouter: insufficient native token fee");
} else {
// ERC20 token fees
IERC20(feeToken).safeTransferFrom(msg.sender, address(this), fees);
}
}
// Generate message ID
messageId = keccak256(abi.encodePacked(
block.chainid,
destinationChainSelector,
msg.sender,
message.receiver,
message.data,
block.timestamp,
block.number
));
require(!sentMessages[messageId], "CCIPRouter: duplicate message");
sentMessages[messageId] = true;
emit MessageSent(
messageId,
destinationChainSelector,
msg.sender,
message.receiver,
message.data,
message.tokenAmounts,
message.feeToken,
message.extraArgs
);
return (messageId, fees);
}
/**
* @notice Get the fee for sending a message
* @param destinationChainSelector The chain selector of the destination chain
* @param message The message to send
* @return fee The fee required for the message
*/
function getFee(
uint64 destinationChainSelector,
EVM2AnyMessage memory message
) public view returns (uint256 fee) {
require(supportedChains[destinationChainSelector], "CCIPRouter: chain not supported");
// Base fee
fee = baseFee;
// Data fee (per byte)
fee += message.data.length * dataFeePerByte;
// Token transfer fees
for (uint256 i = 0; i < message.tokenAmounts.length; i++) {
fee += message.tokenAmounts[i].amount / 1000; // 0.1% of token amount
}
return fee;
}
/**
* @notice Get supported tokens for a destination chain
* @param destinationChainSelector The chain selector of the destination chain
* @return tokens The list of supported tokens
*/
function getSupportedTokens(
uint64 destinationChainSelector
) external view returns (address[] memory tokens) {
return supportedTokens[destinationChainSelector];
}
/**
* @notice Add supported chain
*/
function addSupportedChain(uint64 chainSelector) external onlyAdmin {
supportedChains[chainSelector] = true;
}
/**
* @notice Remove supported chain
*/
function removeSupportedChain(uint64 chainSelector) external onlyAdmin {
supportedChains[chainSelector] = false;
}
/**
* @notice Add supported token for a chain
*/
function addSupportedToken(uint64 chainSelector, address token) external onlyAdmin {
require(token != address(0), "CCIPRouter: zero token");
address[] storage tokens = supportedTokens[chainSelector];
for (uint256 i = 0; i < tokens.length; i++) {
require(tokens[i] != token, "CCIPRouter: token already supported");
}
tokens.push(token);
}
/**
* @notice Update fee configuration
*/
function updateFees(uint256 _baseFee, uint256 _dataFeePerByte) external onlyAdmin {
baseFee = _baseFee;
dataFeePerByte = _dataFeePerByte;
}
/**
* @notice Change admin
*/
function changeAdmin(address newAdmin) external onlyAdmin {
require(newAdmin != address(0), "CCIPRouter: zero address");
admin = newAdmin;
}
/**
* @notice Withdraw collected fees
*/
function withdrawFees(uint256 amount) external onlyAdmin {
if (feeToken == address(0)) {
// Native token (ETH) fees
payable(admin).transfer(amount);
} else {
// ERC20 token fees
IERC20(feeToken).safeTransfer(admin, amount);
}
}
/**
* @notice Withdraw all native token (ETH) fees
*/
function withdrawNativeFees() external onlyAdmin {
require(feeToken == address(0), "CCIPRouter: not native token");
payable(admin).transfer(address(this).balance);
}
/**
* @notice Receive native token (ETH)
*/
receive() external payable {}
}