Files

131 lines
4.6 KiB
Solidity
Raw Permalink Normal View History

// 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];
}
}