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.
149 lines
5.4 KiB
JavaScript
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);
|
|
});
|
|
});
|