aere-contracts/test/state-root-anchor.test.js
Aere Network a13a649b77
Some checks are pending
contracts-ci / Install (lockfile) → compile → full test suite (push) Waiting to run
contracts-ci / PQC known-answer tests (NIST vectors) (push) Waiting to run
contracts-ci / Coverage (scoped, with artifacts) (push) Waiting to run
Initial public release
Aere Network public source. Everything here can be checked against the live
chain (chain id 2800, https://rpc.aere.network).

Scope note, stated up front rather than buried: consensus on chain 2800 is
classical secp256k1 ECDSA QBFT. The post-quantum work in this repository is at
the signature, precompile, account and transport layers. Nothing here makes the
consensus post-quantum, and no document in it should be read as claiming so.
2026-07-20 01:02:37 +03:00

254 lines
10 KiB
JavaScript

// 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;
});
});
});