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

149 lines
5.4 KiB
JavaScript

// Hardhat tests for AereNavOracle.
//
// Covers:
// - propose snapshot + 24h challenge window
// - attest after window with no challenges (permissionless)
// - challenge + dismiss path; attest after dismissals
// - verifyReserve against attested root with Merkle proof
// - latestAttestedEpoch view
// - monotonic epoch enforcement (cannot overwrite an existing epoch)
//
// Run: npx hardhat test test/nav-oracle.test.js
const { expect } = require("chai");
const { ethers } = require("hardhat");
function leafOf(asset, chainId, reserveAmount, decimals) {
return ethers.keccak256(ethers.AbiCoder.defaultAbiCoder().encode(
["address", "uint256", "uint256", "uint8"],
[asset, chainId, reserveAmount, decimals]
));
}
function buildSimpleTree(leaves) {
let layer = leaves.slice().map(l => l.toLowerCase());
layer.sort();
const layers = [layer];
while (layer.length > 1) {
const next = [];
for (let i = 0; i < layer.length; i += 2) {
const a = layer[i];
const b = layer[i + 1] ?? layer[i];
const [lo, hi] = a < b ? [a, b] : [b, a];
next.push(ethers.keccak256(ethers.concat([lo, hi])));
}
layer = next;
layers.push(layer);
}
return { root: layer[0], layers };
}
function proofFor(tree, leaf) {
const proof = [];
let target = leaf.toLowerCase();
for (let i = 0; i < tree.layers.length - 1; i++) {
const lay = tree.layers[i];
const idx = lay.indexOf(target);
if (idx < 0) throw new Error("leaf not in layer");
const sib = idx ^ 1;
if (sib < lay.length) proof.push(lay[sib]);
const a = lay[idx];
const b = lay[sib] ?? lay[idx];
const [lo, hi] = a < b ? [a, b] : [b, a];
target = ethers.keccak256(ethers.concat([lo, hi]));
}
return proof;
}
describe("AereNavOracle", function () {
let oracle, deployer, alice, bob;
beforeEach(async function () {
[deployer, alice, bob] = await ethers.getSigners();
oracle = await (await ethers.getContractFactory("AereNavOracle")).deploy();
await oracle.waitForDeployment();
});
it("proposes a snapshot and attests after 24h with no challenge", async function () {
const usdce = "0x" + "11".repeat(20);
const usdte = "0x" + "22".repeat(20);
const leaves = [
leafOf(usdce, 2800, 1_000_000n * 10n ** 6n, 6),
leafOf(usdte, 2800, 5_000_000n * 10n ** 6n, 6),
];
const tree = buildSimpleTree(leaves);
await oracle.proposeSnapshot(1, tree.root, "ipfs://QmExampleCid");
// Cannot attest early.
await expect(oracle.attest(1)).to.be.revertedWithCustomError(oracle, "TimelockNotElapsed");
// Advance 24h.
await ethers.provider.send("evm_increaseTime", [24 * 3600 + 1]);
await ethers.provider.send("evm_mine", []);
await oracle.attest(1);
// Verify a reserve via Merkle proof.
const leaf = leaves[0];
const ok = await oracle.verifyReserve(1, usdce, 2800, 1_000_000n * 10n ** 6n, 6, proofFor(tree, leaf));
expect(ok).to.equal(true);
// Wrong amount → false.
const bad = await oracle.verifyReserve(1, usdce, 2800, 999_999n * 10n ** 6n, 6, proofFor(tree, leaf));
expect(bad).to.equal(false);
});
it("monotonic epoch — cannot overwrite an existing one", async function () {
await oracle.proposeSnapshot(1, ethers.ZeroHash, "");
await expect(oracle.proposeSnapshot(1, ethers.id("other"), "")).to.be.revertedWithCustomError(oracle, "EpochAlreadyExists");
});
it("challenge → dismiss → attest path; window does not reset", async function () {
await oracle.proposeSnapshot(2, ethers.id("root2"), "ipfs://r2");
await oracle.connect(bob).challenge(2, "double counting USDC.e");
// Cannot attest while challenged even after window elapsed.
await ethers.provider.send("evm_increaseTime", [24 * 3600 + 1]);
await ethers.provider.send("evm_mine", []);
await expect(oracle.attest(2)).to.be.revertedWithCustomError(oracle, "WrongState");
// Owner dismisses.
await oracle.dismissChallenge(2, 0, "double-count claim is false; see audit log");
// Now attestable.
await oracle.attest(2);
const status = await oracle.snapshotStatus(2);
expect(status.state).to.equal(2); // Attested
});
it("challenge after window elapses reverts", async function () {
await oracle.proposeSnapshot(3, ethers.id("root3"), "");
await ethers.provider.send("evm_increaseTime", [24 * 3600 + 1]);
await ethers.provider.send("evm_mine", []);
await expect(oracle.connect(bob).challenge(3, "too late")).to.be.revertedWithCustomError(oracle, "TimelockNotElapsed");
});
it("latestAttestedEpoch returns the highest attested epoch", async function () {
await oracle.proposeSnapshot(5, ethers.id("a"), "");
await oracle.proposeSnapshot(7, ethers.id("b"), "");
await ethers.provider.send("evm_increaseTime", [24 * 3600 + 1]);
await ethers.provider.send("evm_mine", []);
await oracle.attest(5);
await oracle.attest(7);
const [epoch, root] = await oracle.latestAttestedEpoch();
expect(epoch).to.equal(7n);
expect(root).to.equal(ethers.id("b"));
});
it("verifyReserve returns false for non-attested snapshot", async function () {
const usdce = "0x" + "11".repeat(20);
const leaves = [leafOf(usdce, 2800, 1n, 6)];
const tree = buildSimpleTree(leaves);
await oracle.proposeSnapshot(9, tree.root, "");
// Not yet attested.
const ok = await oracle.verifyReserve(9, usdce, 2800, 1n, 6, proofFor(tree, leaves[0]));
expect(ok).to.equal(false);
});
});