// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import "./IRouterClient.sol"; /** * @title CCIP Message Validator * @notice Validates CCIP messages for replay protection and format * @dev Provides message validation utilities */ library CCIPMessageValidator { // Message nonce tracking per source chain struct MessageNonce { uint256 nonce; bool used; } /** * @notice Validate message format * @param message The CCIP message to validate * @return valid True if message format is valid */ function validateMessageFormat( IRouterClient.Any2EVMMessage memory message ) internal pure returns (bool valid) { // Check message ID is not zero if (message.messageId == bytes32(0)) { return false; } // Check source chain selector is valid if (message.sourceChainSelector == 0) { return false; } // Check sender is not empty if (message.sender.length == 0) { return false; } // Check data is not empty if (message.data.length == 0) { return false; } return true; } /** * @notice Validate oracle data format * @param data The encoded oracle data * @return valid True if data format is valid * @return answer Decoded answer * @return roundId Decoded round ID * @return timestamp Decoded timestamp */ function validateOracleData( bytes memory data ) internal view returns ( bool valid, uint256 answer, uint256 roundId, uint256 timestamp ) { // Check minimum data length (3 uint256 = 96 bytes) if (data.length < 96) { return (false, 0, 0, 0); } // Decode oracle data directly (no need for try-catch in library) (uint256 _answer, uint256 _roundId, uint256 _timestamp) = abi.decode(data, (uint256, uint256, uint256)); // Validate answer is not zero if (_answer == 0) { return (false, 0, 0, 0); } // Validate timestamp is reasonable (not too far in future/past) // Note: Using uint256 to avoid underflow uint256 currentTime = block.timestamp; if (_timestamp > currentTime + 300) { return (false, 0, 0, 0); } if (currentTime > _timestamp && currentTime - _timestamp > 3600) { return (false, 0, 0, 0); } return (true, _answer, _roundId, _timestamp); } }