- 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.
3.2 KiB
3.2 KiB
CCIP Message Format
Overview
This document describes the message format used for CCIP cross-chain oracle updates.
Message Structure
CCIP messages contain encoded oracle data in the following format:
struct OracleMessage {
uint256 answer; // Oracle price/answer (scaled by 10^8)
uint256 roundId; // Round ID for this update
uint256 timestamp; // Unix timestamp of the update
}
Encoding
Messages are encoded using ABI encoding:
bytes memory messageData = abi.encode(answer, roundId, timestamp);
Decoding
On the receiving chain, messages are decoded:
(uint256 answer, uint256 roundId, uint256 timestamp) = abi.decode(message.data, (uint256, uint256, uint256));
Example
Sending Message
uint256 price = 25000000000; // $250.00 (scaled by 10^8)
uint256 roundId = 12345;
uint256 timestamp = block.timestamp;
bytes memory messageData = abi.encode(price, roundId, timestamp);
CCIPSender sender = CCIPSender(senderAddress);
uint256 fee = sender.calculateFee(targetChainSelector, messageData);
sender.sendOracleUpdate{value: fee}(targetChainSelector, receiverAddress, messageData);
Receiving Message
function ccipReceive(
IRouterClient.Any2EVMMessage calldata message
) external onlyRouter {
(uint256 answer, uint256 roundId, uint256 timestamp) = abi.decode(
message.data,
(uint256, uint256, uint256)
);
// Update oracle
updateOracle(answer, roundId, timestamp);
}
Data Types
Answer (uint256)
- Oracle price/value
- Scaled by 10^8 (8 decimal places)
- Example: $250.00 = 25000000000
Round ID (uint256)
- Sequential round identifier
- Increments with each update
- Used for ordering and deduplication
Timestamp (uint256)
- Unix timestamp (seconds since epoch)
- When the price was observed
- Used for staleness checks
Message Size
Typical message size: ~96 bytes (3 * 32 bytes)
Maximum recommended size: 256 bytes
Validation
Before processing, validate:
- Message ID: Check for replay attacks
- Source Chain: Verify source chain selector
- Sender: Verify sender address is authorized
- Timestamp: Check timestamp is recent
- Round ID: Ensure round ID is sequential
Error Handling
Invalid Format
If message cannot be decoded:
try abi.decode(message.data, (uint256, uint256, uint256)) returns (uint256, uint256, uint256) {
// Process message
} catch {
// Log error and reject message
emit InvalidMessageFormat(message.messageId);
return;
}
Stale Data
Check timestamp is recent:
require(block.timestamp - timestamp < MAX_STALENESS, "Data too stale");
Invalid Round ID
Ensure round ID is sequential:
require(roundId > lastRoundId, "Invalid round ID");
Security Considerations
- Replay Protection: Track processed message IDs
- Source Validation: Verify source chain and sender
- Data Validation: Validate all fields before processing
- Access Control: Only authorized contracts can receive messages