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

227 lines
11 KiB
JavaScript

// AereRaaSFactory + AereRollupSettlement tests.
const { expect } = require("chai");
const { ethers } = require("hardhat");
async function deployStack(opts = {}) {
const [deployer, foundation, alice, bob, keeper] = await ethers.getSigners();
// BOND_TOKEN + DEBT_TOKEN — reuse MockERC20Lending
const ERC20 = await ethers.getContractFactory("MockERC20Lending");
const bond = await ERC20.deploy("WAERE", "WAERE", 18);
await bond.waitForDeployment();
const debt = await ERC20.deploy("USDC.e", "USDC.e", 6);
await debt.waitForDeployment();
// Sink — reuse the real AereSink.
const WAERE = await (await ethers.getContractFactory("contracts/WAERE.sol:WAERE")).deploy();
await WAERE.waitForDeployment();
const burn = await (await ethers.getContractFactory("AereFeeBurnVault")).deploy();
await burn.waitForDeployment();
const sink = await (await ethers.getContractFactory("AereSink")).deploy(
await WAERE.getAddress(),
await burn.getAddress(),
await WAERE.getAddress(),
await WAERE.getAddress(),
1500, 4000, 4500, 200, ethers.ZeroAddress
);
await sink.waitForDeployment();
// Factory.
const Fac = await ethers.getContractFactory("AereRaaSFactory");
const fac = await Fac.deploy(
await bond.getAddress(),
await sink.getAddress(),
foundation.address,
1000n * 10n ** 18n, // registrationBond = 1000 WAERE
1000, // minSinkBps = 10%
6 * 3600, // defaultChallengeWindow = 6h
);
await fac.waitForDeployment();
return { deployer, foundation, alice, bob, keeper, bond, debt, sink, fac };
}
async function registerRollup(env, opts = {}) {
const id = opts.id ?? ethers.id("rollup-" + Math.floor(Math.random() * 1e9));
await env.bond.mint(env.alice.address, 1000n * 10n ** 18n);
await env.bond.connect(env.alice).approve(await env.fac.getAddress(), 1000n * 10n ** 18n);
const tx = await env.fac.connect(env.alice).registerRollup(
id,
env.alice.address, // rollupOwner
env.bob.address, // sequencer
await env.debt.getAddress(),
opts.sinkBps ?? 1500,
opts.rollupBps ?? 7500,
opts.sequencerBps ?? 1000,
opts.minChallengeBond ?? 100n * 10n ** 6n,
opts.challengeWindow ?? 0,
"ipfs://QmMeta"
);
await tx.wait();
const r = await env.fac.rollups(id);
const settlement = await ethers.getContractAt("AereRollupSettlement", r.settlement);
return { id, settlement };
}
describe("AereRaaSFactory + AereRollupSettlement", function () {
it("registers a rollup, deploys settlement contract", async function () {
const env = await deployStack();
const { id, settlement } = await registerRollup(env);
const r = await env.fac.rollups(id);
expect(r.registered).to.equal(true);
expect(r.bond).to.equal(1000n * 10n ** 18n);
expect(await settlement.ROLLUP_ID()).to.equal(id);
expect(await settlement.SINK_BPS()).to.equal(1500);
});
it("rejects splits that don't sum to 10000", async function () {
const env = await deployStack();
await env.bond.mint(env.alice.address, 1000n * 10n ** 18n);
await env.bond.connect(env.alice).approve(await env.fac.getAddress(), 1000n * 10n ** 18n);
await expect(env.fac.connect(env.alice).registerRollup(
ethers.id("badsplit"), env.alice.address, env.bob.address, await env.debt.getAddress(),
1000, 7500, 1000, 0, 0, ""
)).to.be.revertedWithCustomError(env.fac, "InvalidSplits");
});
it("rejects sinkBps below floor", async function () {
const env = await deployStack();
await env.bond.mint(env.alice.address, 1000n * 10n ** 18n);
await env.bond.connect(env.alice).approve(await env.fac.getAddress(), 1000n * 10n ** 18n);
await expect(env.fac.connect(env.alice).registerRollup(
ethers.id("lowsink"), env.alice.address, env.bob.address, await env.debt.getAddress(),
500, 8500, 1000, 0, 0, "" // 5% < 10% floor
)).to.be.revertedWithCustomError(env.fac, "SinkBpsTooLow");
});
it("sequencer proposes + auto-finalises after window", async function () {
const env = await deployStack();
const { settlement } = await registerRollup(env);
await settlement.connect(env.bob).proposeStateRoot(1, ethers.id("root1"), ethers.id("blk1"));
expect(await settlement.isFinalised(1)).to.equal(false);
// Advance past 6h window.
await ethers.provider.send("evm_increaseTime", [6 * 3600 + 60]);
await ethers.provider.send("evm_mine", []);
expect(await settlement.isFinalised(1)).to.equal(true);
await expect(settlement.advanceFinalisation()).to.emit(settlement, "StateRootFinalised");
});
it("non-sequencer rejected on propose", async function () {
const env = await deployStack();
const { settlement } = await registerRollup(env);
await expect(
settlement.connect(env.alice).proposeStateRoot(1, ethers.id("root1"), ethers.id("blk1"))
).to.be.revertedWithCustomError(settlement, "NotSequencer");
});
it("challenge blocks finalisation; sequencer resolves IN WINDOW; bond routes", async function () {
const env = await deployStack();
const { settlement } = await registerRollup(env);
await settlement.connect(env.bob).proposeStateRoot(1, ethers.id("root1"), ethers.id("blk1"));
// Mint bond and challenge.
await env.debt.mint(env.keeper.address, 200n * 10n ** 6n);
await env.debt.connect(env.keeper).approve(await settlement.getAddress(), 200n * 10n ** 6n);
await settlement.connect(env.keeper).challenge(1, 200n * 10n ** 6n, ethers.id("oh no"));
// While window is still open, sequencer can resolve.
// (Sequencer rejects challenge → keeps bond.)
const seqBefore = await env.debt.balanceOf(env.bob.address);
await settlement.connect(env.bob).resolveChallenge(1, false);
expect(await env.debt.balanceOf(env.bob.address) - seqBefore).to.equal(200n * 10n ** 6n);
// After window passes the root finalises (was never rejected).
await ethers.provider.send("evm_increaseTime", [6 * 3600 + 60]);
await ethers.provider.send("evm_mine", []);
expect(await settlement.isFinalised(1)).to.equal(true);
});
it("revenue settlement splits sink/rollup/sequencer", async function () {
const env = await deployStack();
const { settlement } = await registerRollup(env, { sinkBps: 2000, rollupBps: 6000, sequencerBps: 2000 });
// rollupOwner = alice settles 10,000 USDC.e.
const amount = 10_000n * 10n ** 6n;
await env.debt.mint(env.alice.address, amount);
await env.debt.connect(env.alice).approve(await settlement.getAddress(), amount);
const seqBefore = await env.debt.balanceOf(env.bob.address);
const rolBefore = await env.debt.balanceOf(env.alice.address);
await settlement.connect(env.alice).settleRevenue(amount);
// sequencer (bob) +20%, rollupOwner (alice) +60% net (paid in 100% then receives 60%) — so net = -40%.
expect(await env.debt.balanceOf(env.bob.address) - seqBefore).to.equal(amount * 2000n / 10000n);
expect(rolBefore - (await env.debt.balanceOf(env.alice.address))).to.equal(amount * 4000n / 10000n);
});
it("exit returns bond on valid Foundation attestation", async function () {
const env = await deployStack();
const { id } = await registerRollup(env);
const deadline = (await ethers.provider.getBlock("latest")).timestamp + 3600;
const hash = ethers.keccak256(ethers.AbiCoder.defaultAbiCoder().encode(
["string", "uint256", "address", "bytes32", "address", "uint64"],
["AereRaaSFactory:exit", (await ethers.provider.getNetwork()).chainId, await env.fac.getAddress(), id, env.alice.address, deadline]
));
const sig = await env.foundation.signMessage(ethers.getBytes(hash));
const before = await env.bond.balanceOf(env.alice.address);
await env.fac.connect(env.alice).exitRollup(id, env.alice.address, deadline, sig);
expect(await env.bond.balanceOf(env.alice.address) - before).to.equal(1000n * 10n ** 18n);
});
it("exit rejects bad signature", async function () {
const env = await deployStack();
const { id } = await registerRollup(env);
const deadline = (await ethers.provider.getBlock("latest")).timestamp + 3600;
// signed by alice (NOT foundation)
const hash = ethers.keccak256(ethers.AbiCoder.defaultAbiCoder().encode(
["string", "uint256", "address", "bytes32", "address", "uint64"],
["AereRaaSFactory:exit", (await ethers.provider.getNetwork()).chainId, await env.fac.getAddress(), id, env.alice.address, deadline]
));
const sig = await env.alice.signMessage(ethers.getBytes(hash));
await expect(
env.fac.connect(env.alice).exitRollup(id, env.alice.address, deadline, sig)
).to.be.revertedWithCustomError(env.fac, "InvalidExitSig");
});
it("H6 fix: resolveChallenge after window closed reverts", async function () {
const env = await deployStack();
const { settlement } = await registerRollup(env);
await settlement.connect(env.bob).proposeStateRoot(1, ethers.id("root1"), ethers.id("blk1"));
await env.debt.mint(env.keeper.address, 200n * 10n ** 6n);
await env.debt.connect(env.keeper).approve(await settlement.getAddress(), 200n * 10n ** 6n);
await settlement.connect(env.keeper).challenge(1, 200n * 10n ** 6n, ethers.id("fraud"));
// Wait past window — sequencer is now TOO LATE.
await ethers.provider.send("evm_increaseTime", [6 * 3600 + 60]);
await ethers.provider.send("evm_mine", []);
await expect(
settlement.connect(env.bob).resolveChallenge(1, false)
).to.be.revertedWithCustomError(settlement, "WindowClosed");
});
it("H6 fix: resolveChallengeExpired returns bond + marks root rejected", async function () {
const env = await deployStack();
const { settlement } = await registerRollup(env);
await settlement.connect(env.bob).proposeStateRoot(1, ethers.id("root1"), ethers.id("blk1"));
await env.debt.mint(env.keeper.address, 200n * 10n ** 6n);
await env.debt.connect(env.keeper).approve(await settlement.getAddress(), 200n * 10n ** 6n);
await settlement.connect(env.keeper).challenge(1, 200n * 10n ** 6n, ethers.id("fraud"));
// Cannot expire-resolve while window still open.
await expect(
settlement.connect(env.alice).resolveChallengeExpired(1)
).to.be.revertedWithCustomError(settlement, "WindowOpen");
await ethers.provider.send("evm_increaseTime", [6 * 3600 + 60]);
await ethers.provider.send("evm_mine", []);
const before = await env.debt.balanceOf(env.keeper.address);
await settlement.connect(env.alice).resolveChallengeExpired(1);
expect(await env.debt.balanceOf(env.keeper.address) - before).to.equal(200n * 10n ** 6n);
expect(await settlement.isFinalised(1)).to.equal(false); // rejected
});
it("sequencer rotation by rollup owner", async function () {
const env = await deployStack();
const { settlement } = await registerRollup(env);
await settlement.connect(env.alice).setSequencer(env.keeper.address);
expect(await settlement.sequencer()).to.equal(env.keeper.address);
});
});