// SPDX-License-Identifier: MIT pragma solidity 0.8.24; import { RuntimeCodeGuardV3 } from "./common/RuntimeCodeGuardV3.sol"; interface ICrossChainReceiptVerifierV3 { function verifyReceipt( uint256 sourceChainId, address sourceRegistry, uint256 destinationChainId, address destinationRegistry, bytes32 receiptId, bytes32 payloadHash, bytes calldata proof ) external view returns (bool); } /// @notice Route-specific portable receipt import. It records verified receipts and never moves value. contract CrossChainReceiptRegistryV3 is RuntimeCodeGuardV3 { uint256 public constant MAX_PROOF_BYTES = 65_536; uint256 public immutable sourceChainId; address public immutable sourceRegistry; address public immutable verifier; bytes32 public immutable verifierCodehash; uint256 public immutable destinationChainId; mapping(bytes32 => bytes32) public importedPayloadHash; mapping(bytes32 => bytes32) public importProofHash; error BadConfiguration(); error WrongDestinationChain(); error DuplicateReceipt(); error BadReceipt(); error InvalidVerifier(); error VerificationFailed(); event CrossChainReceiptImportedV3( bytes32 indexed receiptId, bytes32 indexed payloadHash, uint256 indexed sourceChainId, address sourceRegistry, address verifier, bytes32 proofHash ); constructor(uint256 sourceChainId_, address sourceRegistry_, address verifier_, bytes32 verifierCodehash_) { if ( sourceChainId_ == 0 || sourceChainId_ == block.chainid || sourceRegistry_ == address(0) || verifier_.code.length == 0 || verifier_.codehash != verifierCodehash_ ) revert BadConfiguration(); _rejectDelegatingRuntime(verifier_); sourceChainId = sourceChainId_; sourceRegistry = sourceRegistry_; verifier = verifier_; verifierCodehash = verifierCodehash_; destinationChainId = block.chainid; } function importReceipt(bytes32 receiptId, bytes32 payloadHash, bytes calldata proof) external { if (block.chainid != destinationChainId) revert WrongDestinationChain(); if (receiptId == bytes32(0) || payloadHash == bytes32(0) || proof.length == 0 || proof.length > MAX_PROOF_BYTES) { revert BadReceipt(); } if (importedPayloadHash[receiptId] != bytes32(0)) revert DuplicateReceipt(); if (verifier.codehash != verifierCodehash) revert InvalidVerifier(); (bool ok, bytes memory result) = verifier.staticcall( abi.encodeCall( ICrossChainReceiptVerifierV3.verifyReceipt, (sourceChainId, sourceRegistry, destinationChainId, address(this), receiptId, payloadHash, proof) ) ); if (!ok || result.length != 32 || !abi.decode(result, (bool))) revert VerificationFailed(); importedPayloadHash[receiptId] = payloadHash; bytes32 proofHash = keccak256(proof); importProofHash[receiptId] = proofHash; emit CrossChainReceiptImportedV3(receiptId, payloadHash, sourceChainId, sourceRegistry, verifier, proofHash); } function bridgeSemantics() external pure returns (string memory) { return "receipt registry only; no token/value bridge and no verifier correctness claim"; } }