// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import "./IRouterClient.sol"; import "./CCIPMessageValidator.sol"; import "../oracle/IAggregator.sol"; // Note: This contract must be added as a transmitter to the oracle aggregator // to be able to update oracle answers. The aggregator's updateAnswer function // requires the caller to be a transmitter. /** * @title CCIP Receiver with Oracle Integration * @notice Receives CCIP messages and updates oracle aggregator * @dev Implements CCIP message receiving and oracle update logic with validation */ contract CCIPReceiver { using CCIPMessageValidator for IRouterClient.Any2EVMMessage; IRouterClient public immutable router; address public oracleAggregator; address public admin; mapping(bytes32 => bool) public processedMessages; mapping(uint64 => uint256) public lastNonce; // Track nonces per source chain event MessageReceived( bytes32 indexed messageId, uint64 indexed sourceChainSelector, address sender, bytes data ); event OracleUpdated(uint256 answer, uint256 roundId); event OracleAggregatorUpdated(address oldAggregator, address newAggregator); modifier onlyAdmin() { require(msg.sender == admin, "CCIPReceiver: only admin"); _; } modifier onlyRouter() { require(msg.sender == address(router), "CCIPReceiver: only router"); _; } constructor(address _router, address _oracleAggregator) { require(_router != address(0), "CCIPReceiver: zero router address"); require(_oracleAggregator != address(0), "CCIPReceiver: zero aggregator address"); router = IRouterClient(_router); oracleAggregator = _oracleAggregator; admin = msg.sender; } /** * @notice Handle CCIP message (called by CCIP Router) * @param message The received CCIP message */ function ccipReceive( IRouterClient.Any2EVMMessage calldata message ) external onlyRouter { // Replay protection: check if message already processed require(!processedMessages[message.messageId], "CCIPReceiver: message already processed"); // Validate message format require( CCIPMessageValidator.validateMessageFormat(message), "CCIPReceiver: invalid message format" ); // Validate oracle data format ( bool valid, uint256 answer, uint256 roundId, uint256 timestamp ) = CCIPMessageValidator.validateOracleData(message.data); require(valid, "CCIPReceiver: invalid oracle data"); // Mark message as processed (replay protection) processedMessages[message.messageId] = true; // Update last nonce for source chain (additional replay protection) lastNonce[message.sourceChainSelector] = roundId; // Update oracle aggregator // Note: The aggregator's updateAnswer function can be called directly // The aggregator will handle access control (onlyTransmitter) // We need to ensure this receiver is added as a transmitter try IAggregator(oracleAggregator).updateAnswer(answer) { address sender = abi.decode(message.sender, (address)); emit MessageReceived(message.messageId, message.sourceChainSelector, sender, message.data); emit OracleUpdated(answer, roundId); } catch { // If update fails, emit error event // In production, consider adding error tracking address sender = abi.decode(message.sender, (address)); emit MessageReceived(message.messageId, message.sourceChainSelector, sender, message.data); // Don't emit OracleUpdated if update failed } } /** * @notice Update oracle aggregator address */ function updateOracleAggregator(address newAggregator) external onlyAdmin { require(newAggregator != address(0), "CCIPReceiver: zero address"); address oldAggregator = oracleAggregator; oracleAggregator = newAggregator; emit OracleAggregatorUpdated(oldAggregator, newAggregator); } /** * @notice Change admin */ function changeAdmin(address newAdmin) external onlyAdmin { require(newAdmin != address(0), "CCIPReceiver: zero address"); admin = newAdmin; } /** * @notice Check if message has been processed */ function isMessageProcessed(bytes32 messageId) external view returns (bool) { return processedMessages[messageId]; } }