// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import {Test} from "forge-std/Test.sol"; import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import {Chain138MainnetCheckpoint} from "../../contracts/mainnet-checkpoint/Chain138MainnetCheckpoint.sol"; import {BlockHeaderOracleExtension} from "../../contracts/mainnet-checkpoint/extensions/BlockHeaderOracleExtension.sol"; import {ExtensionIds} from "../../contracts/mainnet-checkpoint/libraries/ExtensionIds.sol"; import {CheckpointStorage} from "../../contracts/mainnet-checkpoint/storage/CheckpointStorage.sol"; import {CheckpointLeaf} from "../../contracts/mainnet-checkpoint/libraries/CheckpointLeaf.sol"; import {CheckpointFlags} from "../../contracts/mainnet-checkpoint/libraries/CheckpointFlags.sol"; import {CheckpointHubConfig} from "../../contracts/mainnet-checkpoint/libraries/CheckpointHubConfig.sol"; contract BlockHeaderOracleTest is Test { Chain138MainnetCheckpoint hub; BlockHeaderOracleExtension oracle; address admin = address(0xA11CE); address submitter = address(0xB0B); function setUp() public { Chain138MainnetCheckpoint impl = new Chain138MainnetCheckpoint(); bytes memory initData = abi.encodeCall( Chain138MainnetCheckpoint.initialize, (admin, address(1), uint64(1), address(0)) ); hub = Chain138MainnetCheckpoint(address(new ERC1967Proxy(address(impl), initData))); oracle = new BlockHeaderOracleExtension(admin); vm.startPrank(admin); hub.grantRole(hub.SUBMITTER_ROLE(), submitter); CheckpointHubConfig.HubConfig memory cfg = CheckpointHubConfig.mainnetDefaults(); cfg.enforcePreviousBatchId = false; cfg.requireValidatorSigs = false; cfg.allowCCIPIngress = false; hub.applyConfig(cfg); hub.registerExtension( ExtensionIds.BLOCK_ORACLE, address(oracle), oracle.HOOK_BEFORE_SUBMIT() ); oracle.setRequireOracleRecord(true); vm.stopPrank(); } function testRevertsWithoutOracleRecord() public { bytes32 bh = keccak256("bh"); bytes32 sr = keccak256("sr"); bytes32 root = _root(); CheckpointStorage.CheckpointHeader memory header = _header(1, bh, sr, root); vm.prank(submitter); vm.expectRevert("oracle missing"); hub.submitCheckpoint(header, hex"01", new bytes32[](0), ""); } function testSubmitAfterOracleSet() public { bytes32 bh = keccak256("bh"); bytes32 sr = keccak256("sr"); vm.prank(admin); oracle.setBlockHeader(100, bh, sr); bytes32 root = _root(); CheckpointStorage.CheckpointHeader memory header = _header(1, bh, sr, root); vm.prank(submitter); hub.submitCheckpoint(header, hex"01", new bytes32[](0), ""); assertEq(hub.getLatestBatchId(), 1); } function _root() internal pure returns (bytes32) { CheckpointLeaf.PaymentLeafV1 memory leaf = CheckpointLeaf.PaymentLeafV1({ txHash: keccak256("tx"), from: address(1), to: address(2), value: 1, blockNumber: 100, blockTimestamp: 1, gasUsed: 1, success: true }); return CheckpointLeaf.paymentLeafV1(138, leaf); } function _header(uint64 batchId, bytes32 bh, bytes32 sr, bytes32 paymentsRoot) internal pure returns (CheckpointStorage.CheckpointHeader memory) { return CheckpointStorage.CheckpointHeader({ batchId: batchId, previousBatchId: 0, chainId: 138, checkpointBlock: 100, startBlock: 99, endBlock: 100, blockHash: bh, stateRoot: sr, paymentsRoot: paymentsRoot, receiptsRoot: bytes32(0), txCount: 1, flags: CheckpointFlags.PARTIAL_BATCH, submittedAt: 0, submitter: address(0), contentURI: bytes32(0) }); } }