// 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 Optimized CCIP Router * @notice Optimized version with message batching and fee caching * @dev Performance optimizations for CCIP message handling */ contract CCIPRouterOptimized is IRouterClient { using SafeERC20 for IERC20; address public admin; uint256 public baseFee = 1 ether; uint256 public dataFeePerByte = 1000; uint256 public tokenFeePerToken = 1 ether; mapping(uint64 => address[]) public supportedTokens; // Fee caching mapping(bytes32 => uint256) public cachedFees; uint256 public cacheExpiry = 1 hours; mapping(bytes32 => uint256) public cacheTimestamp; // Message batching struct BatchedMessage { bytes32[] messageIds; uint64 destinationChainSelector; uint256 totalFee; uint256 timestamp; } mapping(uint256 => BatchedMessage) public batches; uint256 public batchId; uint256 public batchWindow = 5 minutes; uint256 public maxBatchSize = 100; event RouterAdminChanged(address indexed oldAdmin, address indexed newAdmin); event BaseFeeUpdated(uint256 oldFee, uint256 newFee); event MessageBatched(uint256 indexed batchId, uint256 messageCount); event FeeCached(bytes32 indexed cacheKey, uint256 fee); modifier onlyAdmin() { require(msg.sender == admin, "CCIPRouterOptimized: only admin"); _; } constructor() { admin = msg.sender; } /** * @inheritdoc IRouterClient */ function ccipSend( uint64 destinationChainSelector, EVM2AnyMessage memory message ) external payable returns (bytes32 messageId, uint256 fees) { fees = getFee(destinationChainSelector, message); // Handle fee payment if (fees > 0) { if (message.feeToken == address(0)) { // Native token (ETH) fees require(msg.value >= fees, "CCIPRouterOptimized: insufficient native token fee"); } else { // ERC20 token fees IERC20(message.feeToken).safeTransferFrom(msg.sender, address(this), fees); } } messageId = keccak256(abi.encodePacked(block.timestamp, block.number, message.data)); emit MessageSent( messageId, destinationChainSelector, msg.sender, message.receiver, message.data, message.tokenAmounts, message.feeToken, message.extraArgs ); return (messageId, fees); } /** * @inheritdoc IRouterClient */ function getFee( uint64 destinationChainSelector, EVM2AnyMessage memory message ) public view override returns (uint256 fee) { // Check cache bytes32 cacheKey = keccak256(abi.encode(destinationChainSelector, message.receiver, message.data.length)); if (cacheTimestamp[cacheKey] != 0 && block.timestamp < cacheTimestamp[cacheKey] + cacheExpiry) { return cachedFees[cacheKey]; } // Calculate fee fee = baseFee; fee += uint256(message.data.length) * dataFeePerByte; for (uint256 i = 0; i < message.tokenAmounts.length; i++) { fee += message.tokenAmounts[i].amount * tokenFeePerToken; } return fee; } /** * @notice Batch multiple messages */ function batchSend( uint64 destinationChainSelector, EVM2AnyMessage[] memory messages ) external payable returns (uint256 batchId_, bytes32[] memory messageIds) { require(messages.length <= maxBatchSize, "CCIPRouterOptimized: batch too large"); batchId_ = batchId++; messageIds = new bytes32[](messages.length); uint256 totalFee = 0; for (uint256 i = 0; i < messages.length; i++) { uint256 fee = getFee(destinationChainSelector, messages[i]); totalFee += fee; bytes32 messageId = keccak256(abi.encodePacked(block.timestamp, block.number, i, messages[i].data)); messageIds[i] = messageId; } require(msg.value >= totalFee, "CCIPRouterOptimized: insufficient fee"); batches[batchId_] = BatchedMessage({ messageIds: messageIds, destinationChainSelector: destinationChainSelector, totalFee: totalFee, timestamp: block.timestamp }); emit MessageBatched(batchId_, messages.length); return (batchId_, messageIds); } /** * @notice Cache fee calculation */ function cacheFee( uint64 destinationChainSelector, bytes memory receiver, uint256 dataLength ) external returns (uint256 fee) { bytes32 cacheKey = keccak256(abi.encode(destinationChainSelector, receiver, dataLength)); // Calculate fee fee = baseFee + (dataLength * dataFeePerByte); // Cache it cachedFees[cacheKey] = fee; cacheTimestamp[cacheKey] = block.timestamp; emit FeeCached(cacheKey, fee); return fee; } /** * @inheritdoc IRouterClient */ function getSupportedTokens( uint64 destinationChainSelector ) external view override returns (address[] memory) { return supportedTokens[destinationChainSelector]; } /** * @notice Update base fee */ function updateBaseFee(uint256 newFee) external onlyAdmin { require(newFee > 0, "CCIPRouterOptimized: fee must be greater than 0"); emit BaseFeeUpdated(baseFee, newFee); baseFee = newFee; } /** * @notice Update cache expiry */ function setCacheExpiry(uint256 newExpiry) external onlyAdmin { cacheExpiry = newExpiry; } /** * @notice Update batch window */ function setBatchWindow(uint256 newWindow) external onlyAdmin { batchWindow = newWindow; } /** * @notice Update max batch size */ function setMaxBatchSize(uint256 newSize) external onlyAdmin { require(newSize > 0, "CCIPRouterOptimized: size must be greater than 0"); maxBatchSize = newSize; } /** * @notice Change admin */ function changeAdmin(address newAdmin) external onlyAdmin { require(newAdmin != address(0), "CCIPRouterOptimized: zero address"); emit RouterAdminChanged(admin, newAdmin); admin = newAdmin; } }