// 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 {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"; import {CheckpointErrors} from "../../contracts/mainnet-checkpoint/libraries/CheckpointErrors.sol"; contract SubmitWithLeavesTest is Test { Chain138MainnetCheckpoint hub; 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))); vm.startPrank(admin); hub.grantRole(hub.SUBMITTER_ROLE(), submitter); CheckpointHubConfig.HubConfig memory cfg = CheckpointHubConfig.mainnetDefaults(); cfg.enforcePreviousBatchId = false; cfg.requireValidatorSigs = false; hub.applyConfig(cfg); vm.stopPrank(); } function testSubmitWithLeavesVerifiesRoot() public { CheckpointLeaf.PaymentLeafV1[] memory leaves = new CheckpointLeaf.PaymentLeafV1[](2); leaves[0] = _leaf(keccak256("a")); leaves[1] = _leaf(keccak256("b")); bytes32[] memory hashes = new bytes32[](2); hashes[0] = CheckpointLeaf.paymentLeafV1(138, leaves[0]); hashes[1] = CheckpointLeaf.paymentLeafV1(138, leaves[1]); bytes32 root = CheckpointLeaf.buildMerkleRoot(hashes); CheckpointStorage.CheckpointHeader memory header = CheckpointStorage.CheckpointHeader({ batchId: 1, previousBatchId: 0, chainId: 138, checkpointBlock: 10, startBlock: 9, endBlock: 10, blockHash: keccak256("bh"), stateRoot: keccak256("sr"), paymentsRoot: root, receiptsRoot: bytes32(0), txCount: 2, flags: CheckpointFlags.PARTIAL_BATCH, submittedAt: 0, submitter: address(0), contentURI: bytes32(0) }); bytes32[] memory txHashes = new bytes32[](2); txHashes[0] = leaves[0].txHash; txHashes[1] = leaves[1].txHash; vm.prank(submitter); hub.submitCheckpointWithLeaves(header, hex"01", txHashes, leaves); assertEq(hub.getLatestBatchId(), 1); } function testSubmitWithLeavesRevertsOnBadRoot() public { CheckpointLeaf.PaymentLeafV1[] memory leaves = new CheckpointLeaf.PaymentLeafV1[](1); leaves[0] = _leaf(keccak256("x")); CheckpointStorage.CheckpointHeader memory header = CheckpointStorage.CheckpointHeader({ batchId: 1, previousBatchId: 0, chainId: 138, checkpointBlock: 1, startBlock: 1, endBlock: 1, blockHash: keccak256("h"), stateRoot: keccak256("s"), paymentsRoot: keccak256("wrong"), receiptsRoot: bytes32(0), txCount: 1, flags: CheckpointFlags.PARTIAL_BATCH, submittedAt: 0, submitter: address(0), contentURI: bytes32(0) }); vm.prank(submitter); vm.expectRevert(CheckpointErrors.RootMismatch.selector); hub.submitCheckpointWithLeaves(header, hex"01", new bytes32[](0), leaves); } function _leaf(bytes32 txHash) private pure returns (CheckpointLeaf.PaymentLeafV1 memory) { return CheckpointLeaf.PaymentLeafV1({ txHash: txHash, from: address(3), to: address(4), value: 1, blockNumber: 1, blockTimestamp: 1, gasUsed: 1, success: true }); } }