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.
This commit is contained in:
209
contracts/ccip/CCIPRouter.sol
Normal file
209
contracts/ccip/CCIPRouter.sol
Normal file
@@ -0,0 +1,209 @@
|
||||
// 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 {}
|
||||
}
|
||||
Reference in New Issue
Block a user