// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import "./Aggregator.sol"; import "../ccip/CCIPSender.sol"; /** * @title Oracle Aggregator with CCIP Integration * @notice Extends Aggregator with CCIP cross-chain messaging capabilities * @dev Automatically sends oracle updates to other chains via CCIP when updates occur */ contract OracleWithCCIP is Aggregator { CCIPSender public ccipSender; bool public ccipEnabled; // Destination chain configurations (using CCIPSender's destinations) uint64[] public ccipDestinationChains; event CCIPUpdateSent( bytes32 indexed messageId, uint64 indexed destinationChainSelector, uint256 answer, uint256 roundId ); event CCIPEnabled(bool enabled); event CCIPSenderUpdated(address oldSender, address newSender); constructor( string memory _description, address _admin, uint256 _heartbeat, uint256 _deviationThreshold, address _ccipSender ) Aggregator(_description, _admin, _heartbeat, _deviationThreshold) { require(_ccipSender != address(0), "OracleWithCCIP: zero sender address"); ccipSender = CCIPSender(_ccipSender); ccipEnabled = true; } /** * @notice Update the answer and send to CCIP destinations * @param answer New answer value */ function updateAnswer(uint256 answer) external override onlyTransmitter whenNotPaused { uint256 currentRound = latestRound; Round storage round = rounds[currentRound]; // Check if we need to start a new round if (round.updatedAt == 0 || block.timestamp >= round.startedAt + heartbeat || shouldUpdate(answer, round.answer)) { currentRound = latestRound + 1; latestRound = currentRound; rounds[currentRound] = Round({ answer: answer, startedAt: block.timestamp, updatedAt: block.timestamp, answeredInRound: currentRound, transmitter: msg.sender }); emit NewRound(currentRound, msg.sender, block.timestamp); // Send to CCIP destinations if enabled if (ccipEnabled) { _sendToCCIP(currentRound, answer); } } else { // Update existing round round.updatedAt = block.timestamp; round.transmitter = msg.sender; } emit AnswerUpdated(int256(answer), currentRound, block.timestamp); } /** * @notice Send oracle update to all CCIP destinations */ function _sendToCCIP(uint256 roundId, uint256 answer) internal { uint64[] memory destinations = ccipSender.getDestinationChains(); for (uint256 i = 0; i < destinations.length; i++) { uint64 chainSelector = destinations[i]; try ccipSender.sendOracleUpdate( chainSelector, answer, roundId, block.timestamp ) returns (bytes32 messageId) { emit CCIPUpdateSent(messageId, chainSelector, answer, roundId); } catch { // Log error but don't revert // In production, consider adding error tracking } } } /** * @notice Enable/disable CCIP */ function setCCIPEnabled(bool enabled) external { require(msg.sender == admin, "OracleWithCCIP: only admin"); ccipEnabled = enabled; emit CCIPEnabled(enabled); } /** * @notice Update CCIP sender */ function updateCCIPSender(address newSender) external { require(msg.sender == admin, "OracleWithCCIP: only admin"); require(newSender != address(0), "OracleWithCCIP: zero address"); address oldSender = address(ccipSender); ccipSender = CCIPSender(newSender); emit CCIPSenderUpdated(oldSender, newSender); } /** * @notice Get CCIP destinations */ function getCCIPDestinations() external view returns (uint64[] memory) { return ccipSender.getDestinationChains(); } /** * @notice Get fee for sending to CCIP destination */ function getCCIPFee(uint64 chainSelector) external view returns (uint256) { bytes memory data = abi.encode(uint256(0), uint256(0), uint256(0)); return ccipSender.calculateFee(chainSelector, data); } }