aere-contracts/test/daCommittee.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

99 lines
4.2 KiB
JavaScript

// AereDACommittee — unit tests (pure ECDSA M-of-N, runs fully local).
const { expect } = require("chai");
const { ethers } = require("hardhat");
describe("AereDACommittee", function () {
let dac, admin, m1, m2, m3, outsider;
let members; // sorted ascending by address
const rollupId = ethers.id("rollup:demo");
const epoch = 7n;
const dataCommitment = ethers.id("batch-data-commitment");
beforeEach(async function () {
[admin, m1, m2, m3, outsider] = await ethers.getSigners();
// sort committee members ascending (attest requires ascending recovered addr)
members = [m1, m2, m3].sort((a, b) =>
a.address.toLowerCase() < b.address.toLowerCase() ? -1 : 1
);
const F = await ethers.getContractFactory("AereDACommittee");
dac = await F.deploy(admin.address, members.map((m) => m.address), 2);
await dac.waitForDeployment();
});
async function sign(wallet, rId = rollupId, ep = epoch, dc = dataCommitment) {
// members sign the raw inner hash via personal_sign; the contract wraps with
// the EIP-191 prefix in attestationDigest.
const inner = ethers.keccak256(
ethers.AbiCoder.defaultAbiCoder().encode(
["string", "uint256", "address", "bytes32", "uint256", "bytes32"],
["AereDACommittee:attest", 31337n, await dac.getAddress(), rId, ep, dc]
)
);
return wallet.signMessage(ethers.getBytes(inner));
}
it("constructs with the committee and threshold", async function () {
expect(await dac.memberCount()).to.equal(3n);
expect(await dac.threshold()).to.equal(2n);
expect(await dac.isMember(members[0].address)).to.equal(true);
expect(await dac.isMember(outsider.address)).to.equal(false);
expect((await dac.getMembers()).length).to.equal(3);
});
it("records availability with M distinct member signatures", async function () {
const sigs = [await sign(members[0]), await sign(members[1])]; // ascending
const tx = await dac.attest(rollupId, epoch, dataCommitment, sigs);
await tx.wait();
expect(await dac.isAvailable(rollupId, epoch)).to.equal(true);
const [available, dc, , n] = await dac.getAttestation(rollupId, epoch);
expect(available).to.equal(true);
expect(dc).to.equal(dataCommitment);
expect(n).to.equal(2n);
});
it("rejects M-1 signatures", async function () {
const sigs = [await sign(members[0])];
await expect(dac.attest(rollupId, epoch, dataCommitment, sigs))
.to.be.revertedWithCustomError(dac, "ThresholdNotMet")
.withArgs(1, 2);
expect(await dac.isAvailable(rollupId, epoch)).to.equal(false);
});
it("rejects a non-member signature", async function () {
const sigs = [await sign(members[0]), await sign(outsider)];
// ordering may or may not place outsider second; either way it must revert.
await expect(dac.attest(rollupId, epoch, dataCommitment, sigs)).to.be.reverted;
expect(await dac.isAvailable(rollupId, epoch)).to.equal(false);
});
it("rejects duplicate / unsorted signatures", async function () {
const dup = await sign(members[0]);
await expect(dac.attest(rollupId, epoch, dataCommitment, [dup, dup]))
.to.be.revertedWithCustomError(dac, "UnsortedOrDuplicate");
});
it("rejects re-recording the same epoch", async function () {
const sigs = [await sign(members[0]), await sign(members[1])];
await dac.attest(rollupId, epoch, dataCommitment, sigs);
await expect(dac.attest(rollupId, epoch, dataCommitment, sigs))
.to.be.revertedWithCustomError(dac, "AlreadyRecorded");
});
it("admin governance cannot forge availability, and freeze locks it", async function () {
// no admin function can set availability; only attest can.
await dac.connect(admin).freeze();
expect(await dac.frozen()).to.equal(true);
await expect(dac.connect(admin).addMember(outsider.address)).to.be.revertedWithCustomError(dac, "IsFrozen");
// attest still works post-freeze
const sigs = [await sign(members[0]), await sign(members[1])];
await dac.attest(rollupId, epoch, dataCommitment, sigs);
expect(await dac.isAvailable(rollupId, epoch)).to.equal(true);
});
it("only admin can manage the committee", async function () {
await expect(dac.connect(outsider).setThreshold(1)).to.be.revertedWithCustomError(dac, "NotAdmin");
});
});