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

113 lines
5.2 KiB
JavaScript

const { expect } = require("chai");
const { ethers } = require("hardhat");
const DAY = 24 * 60 * 60;
describe("AereSlashingInsurance", function () {
let fund, foundation, delegator, donor, attacker;
const COVERAGE_BPS = 5000; // 50% of a slash
const PER_EVENT_CAP = ethers.parseEther("100"); // max 100 AERE per coverage
const COOLDOWN = 3 * DAY;
const CLAIM_DELAY = 2 * DAY;
beforeEach(async function () {
[foundation, delegator, donor, attacker] = await ethers.getSigners();
const F = await ethers.getContractFactory("AereSlashingInsurance");
fund = await F.connect(foundation).deploy(
foundation.address, COVERAGE_BPS, PER_EVENT_CAP, COOLDOWN, CLAIM_DELAY
);
await fund.waitForDeployment();
// seed the vault with native AERE
await fund.connect(donor).donate({ value: ethers.parseEther("500") });
});
it("records immutables and accepts donations (donate + receive)", async function () {
expect(await fund.FOUNDATION()).to.equal(foundation.address);
expect(await fund.COVERAGE_BPS()).to.equal(COVERAGE_BPS);
expect(await fund.PER_EVENT_CAP()).to.equal(PER_EVENT_CAP);
expect(await fund.available()).to.equal(ethers.parseEther("500"));
// plain transfer hits receive()
await donor.sendTransaction({ to: await fund.getAddress(), value: ethers.parseEther("10") });
expect(await fund.available()).to.equal(ethers.parseEther("510"));
expect(await fund.totalDonated()).to.equal(ethers.parseEther("510"));
});
it("coverageFor applies bps then caps per event", async function () {
// 50 AERE slash -> 25 AERE (under cap)
expect(await fund.coverageFor(ethers.parseEther("50"))).to.equal(ethers.parseEther("25"));
// 400 AERE slash -> 200 -> capped to 100
expect(await fund.coverageFor(ethers.parseEther("400"))).to.equal(PER_EVENT_CAP);
});
it("only Foundation can propose", async function () {
await expect(
fund.connect(attacker).proposeCoverage(delegator.address, ethers.parseEther("50"), ethers.ZeroHash)
).to.be.revertedWithCustomError(fund, "NotFoundation");
});
it("full happy path: propose, wait, execute pays frozen payout to delegator", async function () {
await fund.connect(foundation).proposeCoverage(
delegator.address, ethers.parseEther("50"), ethers.id("slash-evidence")
);
const c = await fund.claims(0);
expect(c.delegator).to.equal(delegator.address);
expect(c.payout).to.equal(ethers.parseEther("25"));
// cannot execute before the timelock
await expect(fund.connect(delegator).executeCoverage(0))
.to.be.revertedWithCustomError(fund, "TimelockNotElapsed");
await ethers.provider.send("evm_increaseTime", [CLAIM_DELAY + 1]);
await ethers.provider.send("evm_mine", []);
const before = await ethers.provider.getBalance(delegator.address);
// anyone can trigger; recipient/amount are frozen. Use donor to avoid gas accounting.
await fund.connect(donor).executeCoverage(0);
const after = await ethers.provider.getBalance(delegator.address);
expect(after - before).to.equal(ethers.parseEther("25"));
expect(await fund.totalCovered()).to.equal(ethers.parseEther("25"));
expect((await fund.claims(0)).exists).to.equal(false);
});
it("permissionless cancel blocks a fraudulent claim during the delay", async function () {
// compromised-key scenario: propose a payout to the attacker
await fund.connect(foundation).proposeCoverage(
attacker.address, ethers.parseEther("200"), ethers.ZeroHash
);
// community observer cancels it
await expect(fund.connect(donor).cancelCoverage(0, "payout to attacker"))
.to.emit(fund, "CoverageCancelled");
await ethers.provider.send("evm_increaseTime", [CLAIM_DELAY + 1]);
await ethers.provider.send("evm_mine", []);
await expect(fund.connect(attacker).executeCoverage(0))
.to.be.revertedWithCustomError(fund, "NoClaim");
});
it("enforces the global cooldown between executed coverages", async function () {
// claim 0
await fund.connect(foundation).proposeCoverage(delegator.address, ethers.parseEther("50"), ethers.ZeroHash);
// claim 1
await fund.connect(foundation).proposeCoverage(delegator.address, ethers.parseEther("50"), ethers.ZeroHash);
await ethers.provider.send("evm_increaseTime", [CLAIM_DELAY + 1]);
await ethers.provider.send("evm_mine", []);
await fund.connect(donor).executeCoverage(0);
// second execution blocked by cooldown
await expect(fund.connect(donor).executeCoverage(1))
.to.be.revertedWithCustomError(fund, "CooldownActive");
// after cooldown it succeeds
await ethers.provider.send("evm_increaseTime", [COOLDOWN + 1]);
await ethers.provider.send("evm_mine", []);
await fund.connect(donor).executeCoverage(1);
expect(await fund.totalCovered()).to.equal(ethers.parseEther("50"));
});
it("has no admin-withdrawal path (only executeCoverage moves funds)", async function () {
// there is no withdraw()/sweep() function in the ABI
const names = fund.interface.fragments.filter((f) => f.type === "function").map((f) => f.name);
for (const n of names) {
expect(n.toLowerCase()).to.not.match(/withdraw|sweep|rescue|drain|skim/);
}
});
});