// AereStateRootAnchor tests. // // What is REAL here: // - The AERE genesis block header (chain 2800) is real data; encodeShanghaiHeader // reconstructs its canonical hash 0xd86d57a8... exactly. The anchor's keccak // match + state-root decode is tested against it (genesis is outside the live // 256-block BLOCKHASH window, so the mock injects its real canonical hash). // - The real BLOCKHASH opcode path is tested end-to-end on the local chain: // a freshly mined block is reconstructed, keccak-verified against blockhash, // then anchored through the PRODUCTION contract (no mock). // - proveAgainstAnchor is tested end-to-end against a faithful stand-in for the // live AereStorageProofVerifier's verify/submitProofWithExpectedRoot ABI. const { expect } = require("chai"); const { ethers } = require("hardhat"); const { anyUint } = require("@nomicfoundation/hardhat-chai-matchers/withArgs"); const { encodeHeader } = require("./header-rlp"); const genesis = require("./fixtures/aere-genesis-header.json"); const ZERO32 = "0x" + "00".repeat(32); // abi.encode(chainId, blockNumber, stateRoot, account, slot, value) -> 192 bytes function publicValues({ chainId = 2800n, blockNumber, stateRoot, account, slot, value }) { return ethers.AbiCoder.defaultAbiCoder().encode( ["uint256", "uint256", "bytes32", "address", "bytes32", "bytes32"], [chainId, blockNumber, stateRoot, account, slot, value] ); } describe("AereStateRootAnchor", function () { let verifier, mockAnchor, prodAnchor, user; beforeEach(async function () { [user] = await ethers.getSigners(); const V = await ethers.getContractFactory("MockStorageProofVerifier"); verifier = await V.deploy(); await verifier.waitForDeployment(); const vAddr = await verifier.getAddress(); const M = await ethers.getContractFactory("MockBlockhashAnchor"); mockAnchor = await M.deploy(vAddr); await mockAnchor.waitForDeployment(); const P = await ethers.getContractFactory("AereStateRootAnchor"); prodAnchor = await P.deploy(vAddr); await prodAnchor.waitForDeployment(); }); describe("constructor", function () { it("stores the verifier and rejects the zero address", async function () { expect(await prodAnchor.VERIFIER()).to.equal(await verifier.getAddress()); expect(await prodAnchor.BLOCKHASH_WINDOW()).to.equal(256n); const P = await ethers.getContractFactory("AereStateRootAnchor"); await expect(P.deploy(ethers.ZeroAddress)).to.be.revertedWithCustomError( prodAnchor, "ZeroVerifier" ); }); }); describe("encoder / fixture sanity (REAL AERE genesis)", function () { it("reconstructs the real canonical AERE genesis block hash", function () { const header = encodeHeader(genesis); expect(ethers.keccak256(header)).to.equal(genesis.hash); }); it("previewStateRoot decodes the real genesis state root", async function () { const header = encodeHeader(genesis); expect(await prodAnchor.previewStateRoot(header)).to.equal(genesis.stateRoot); }); }); describe("anchorRecentHeader (against REAL AERE genesis via injected canonical hash)", function () { it("anchors the real genesis header and records the real state root", async function () { const header = encodeHeader(genesis); await mockAnchor.setBlockHash(0, genesis.hash); await expect(mockAnchor.anchorRecentHeader(0, header)) .to.emit(mockAnchor, "HeaderAnchored") .withArgs(0, genesis.hash, genesis.stateRoot, anyUint); expect(await mockAnchor.isAnchored(0)).to.equal(true); expect(await mockAnchor.anchoredStateRoot(0)).to.equal(genesis.stateRoot); const rec = await mockAnchor.getAnchoredRoot(0); expect(rec.exists).to.equal(true); expect(rec.stateRoot).to.equal(genesis.stateRoot); expect(rec.blockHash).to.equal(genesis.hash); expect(rec.blockNum).to.equal(0n); expect(rec.verifiedAt).to.be.gt(0n); }); it("reverts HeaderHashMismatch when the header is tampered", async function () { const header = encodeHeader(genesis); await mockAnchor.setBlockHash(0, genesis.hash); // Flip one byte inside the state-root region of the header. const bytesArr = ethers.getBytes(header); bytesArr[60] ^= 0x01; const tampered = ethers.hexlify(bytesArr); await expect(mockAnchor.anchorRecentHeader(0, tampered)).to.be.revertedWithCustomError( mockAnchor, "HeaderHashMismatch" ); }); it("reverts MalformedHeader when a hash-matching input is not a valid header", async function () { // A short RLP list whose keccak we inject: passes the hash check, then // fails structural decode. const bogus = "0xc0"; // empty RLP list await mockAnchor.setBlockHash(7, ethers.keccak256(bogus)); await expect(mockAnchor.anchorRecentHeader(7, bogus)).to.be.revertedWithCustomError( mockAnchor, "MalformedHeader" ); }); it("reverts OutOfWindow when no canonical hash is available", async function () { const header = encodeHeader(genesis); // No injected hash for block 0 -> _blockHashOf returns 0. await expect(mockAnchor.anchorRecentHeader(0, header)).to.be.revertedWithCustomError( mockAnchor, "OutOfWindow" ); }); it("decodes an arbitrary (real) state root value carried in a valid header", async function () { // Same real genesis layout but carrying AERE recent-block state root // 0x9592f7...; proves the decode reads whatever root the header commits. const recentRoot = "0x9592f7acd3ec0a4694b5fa23cd9b8b814d49a19c5c9fcf6c47a523611e5b0245"; const header = encodeHeader({ ...genesis, stateRoot: recentRoot }); await mockAnchor.setBlockHash(123, ethers.keccak256(header)); await mockAnchor.anchorRecentHeader(123, header); expect(await mockAnchor.anchoredStateRoot(123)).to.equal(recentRoot); }); }); describe("real BLOCKHASH opcode path (PRODUCTION contract, no mock)", function () { it("anchors a freshly mined local block via the real blockhash() opcode", async function () { // Produce a few normal blocks. for (let i = 0; i < 3; i++) await ethers.provider.send("evm_mine", []); const head = await ethers.provider.getBlockNumber(); const target = head - 1; // inside the 256 window, not the current block const raw = await ethers.provider.send("eth_getBlockByNumber", [ "0x" + target.toString(16), false, ]); const header = encodeHeader(raw); // Hard gate: our reconstruction must equal the chain's block hash AND the // value the contract sees from the real BLOCKHASH opcode. expect(ethers.keccak256(header)).to.equal(raw.hash); expect(await prodAnchor.canonicalBlockHash(target)).to.equal(raw.hash); expect(await prodAnchor.isWithinWindow(target)).to.equal(true); await prodAnchor.anchorRecentHeader(target, header); expect(await prodAnchor.anchoredStateRoot(target)).to.equal(raw.stateRoot); }); it("reverts OutOfWindow for the current/future block (blockhash == 0)", async function () { const head = await ethers.provider.getBlockNumber(); const header = encodeHeader(genesis); // content irrelevant; window check is first // A future block number: BLOCKHASH returns 0 for the current and any // future block, so the window check reverts before the hash check. await expect( prodAnchor.anchorRecentHeader(head + 100, header) ).to.be.revertedWithCustomError(prodAnchor, "OutOfWindow"); }); it("reverts OutOfWindow for a block older than 256 (past the BLOCKHASH window)", async function () { await ethers.provider.send("hardhat_mine", ["0x160"]); // mine 352 blocks const header = encodeHeader(genesis); // Block 1 is now far outside the last-256 window. await expect(prodAnchor.anchorRecentHeader(1, header)).to.be.revertedWithCustomError( prodAnchor, "OutOfWindow" ); }); }); describe("proveAgainstAnchor (end-to-end with faithful verifier stand-in)", function () { const account = "0x00000000000000000000000000000000000000A1"; const slot = "0x" + "00".repeat(31) + "05"; const value = "0x" + "00".repeat(31) + "2a"; beforeEach(async function () { // Anchor the real genesis root at block 0 on the mock anchor. const header = encodeHeader(genesis); await mockAnchor.setBlockHash(0, genesis.hash); await mockAnchor.anchorRecentHeader(0, header); }); it("records a proof whose committed root equals the anchored root", async function () { const pv = publicValues({ blockNumber: 0n, stateRoot: genesis.stateRoot, account, slot, value, }); await expect(mockAnchor.proveAgainstAnchor(pv, "0x")) .to.emit(mockAnchor, "ProvenAgainstAnchor") .withArgs(0, genesis.stateRoot, account, slot, value); expect(await verifier.recorded(ethers.keccak256(pv))).to.equal(true); }); it("reverts RootNotCanonical when the proof's root is not the anchored root", async function () { const pv = publicValues({ blockNumber: 0n, stateRoot: ZERO32, // not the anchored genesis root account, slot, value, }); await expect(mockAnchor.proveAgainstAnchor(pv, "0x")).to.be.revertedWithCustomError( mockAnchor, "RootNotCanonical" ); }); it("reverts NotAnchored when the proof's block is not anchored", async function () { const pv = publicValues({ blockNumber: 999n, stateRoot: genesis.stateRoot, account, slot, value, }); await expect(mockAnchor.proveAgainstAnchor(pv, "0x")).to.be.revertedWithCustomError( mockAnchor, "NotAnchored" ); }); it("reverts when the underlying SP1 proof is invalid", async function () { await verifier.setValidate(false); const pv = publicValues({ blockNumber: 0n, stateRoot: genesis.stateRoot, account, slot, value, }); await expect(mockAnchor.proveAgainstAnchor(pv, "0x")).to.be.reverted; }); }); });