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