Prover Return Decoding Guide
This document explains how to interpret the returns from CrossL2ProverV2
Contract with the following example.
The setValueFromSource
function takes in a proof from Prove API and uses it to validate and replicate a state change from a source chain to the destination chain. The process involves several steps of data validation and decoding.
CrossL2Prover Interface
function validateEvent(bytes calldata proof)
returns (
uint32 chainId, // Source chain identifier
address emittingContract, // Emitting contract address
bytes topics, // Concatenated Event topics
bytes unindexedData // Non-indexed event parameters
)
Raw Return Data
[
chainId, // uint32: e.g., 11155420 (Optimism Sepolia)
emittingContract, // address: 0x24B1D355f5B254aF86860bBe4214aEDe2DB1314E
topics, // bytes: topic1 + topic2 + topic3
unindexedData // hexbytes: abi encoded non-indexed parameters
]
Origin Event Structure Reference
event ValueSet(
// → topic[0] = keccak(ValueSet(address,string,bytes,uint256,bytese32,uint256))
address indexed sender, // In topics[1]
string key, // In unindexedData
bytes value, // In unindexedData
uint256 nonce, // In unindexedData
bytes32 indexed hashedKey, // In topics[2]
uint256 version // In unindexedData
);
1. Initial Proof Validation
(
uint32 sourceChainId,
address sourceContract,
bytes memory topics,
bytes memory unindexedData
) = polymerProver.validateEvent(proof);
The Polymer prover returns four key pieces of information:
sourceChainId
: The chain ID where the event originatedsourceContract
: The contract address that emitted the eventtopics
: A concatenated bytes of event topics (1-4 x 32 bytes)unindexedData
: ABI-encoded non-indexed event parameters
2. Topics Decoding
The topics
byte array contains three 32-byte values concatenated together:
bytes32[] memory topicsArray = new bytes32[](3);
require(topics.length >= 96, "Invalid topics length"); // 3 * 32 bytes
assembly {
let topicsPtr := add(topics, 32) // Skip length prefix
for { let i := 0 } lt(i, 3) { i := add(i, 1) } {
mstore(
add(add(topicsArray, 32), mul(i, 32)),
mload(add(topicsPtr, mul(i, 32)))
)
}
}
The topics array contains:
topicsArray[0]
: Event signature hashtopicsArray[1]
: Indexed sender address (padded to 32 bytes)topicsArray[2]
: Indexed hashedKey
3. Event Signature Verification
bytes32 expectedSelector = keccak256("ValueSet(address,string,bytes,uint256,bytes32,uint256)");
require(topicsArray[0] == expectedSelector, "Invalid event signature");
This security check ensures:
- Only ValueSet events are processed
- The parameter types and order match exactly
- Events from different contracts with similar structures are rejected
4. Indexed Parameter Extraction
address sender = address(uint160(uint256(topicsArray[1])));
bytes32 hashedKey = topicsArray[2];
Converting indexed parameters:
sender
: Convert bytes32 → uint256 → uint160 → addresshashedKey
: Direct use (already bytes32)
5. Non-indexed Parameter Decoding
(
, // skip key
bytes memory value,
uint256 nonce,
uint256 version
) = abi.decode(
unindexedData,
(string, bytes, uint256, uint256)
);
The unindexedData
contains ABI-encoded parameters that weren't indexed:
key
(string): Skipped as we use hashedKey from topicsvalue
(bytes): The actual value to storenonce
(uint256): Used for replay protectionversion
(uint256): Used for version control
⚠️ Important Security Considerations
This is a demonstration implementation. For production-ready applications, additional security measures are crucial:
- Source Chain Validation:
-
Safeguards against event duplication from a different chain.
-
Maintain a whitelist of allowed source chains
-
Validate
sourceChainId
against this whitelist -
Example:
require(allowedSourceChains[sourceChainId], "Invalid source chain");
-
- Source Contract Validation:
-Safeguards against event duplication from a different contract address on a given source chain.
-
Maintain a mapping of authorized contracts per chain
-
Verify the
sourceContract
address is authorized -
Example:
require(
authorizedContracts[sourceChainId][sourceContract],
"Unauthorized source contract"
);
-
- Event Signature Validation (topics[0]):
-
Always verify the event signature hash (topics[0])
-
This is crucial as it validates both event name and parameter types
-
Example:
// Validates event name and exact parameter types/order
bytes32 expectedSelector = keccak256("ValueSet(address,string,bytes,uint256,bytes32,uint256)");
require(topicsArray[0] == expectedSelector, "Invalid event signature");
// Even a slight change in parameters would generate a different hash:
// "ValueSet(string,address,bytes,uint256,bytes32,uint256)" -> different hash
// "ValueSet(address,bytes,string,uint256,bytes32,uint256)" -> different hash
-