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

153 lines
7.0 KiB
JavaScript

const { expect } = require("chai");
const { ethers } = require("hardhat");
describe("AereDeathSwitch", () => {
let foundation, alice, bob;
let bridge, dex, ds;
const BUCKET_BRIDGE = ethers.keccak256(ethers.toUtf8Bytes("bridge"));
const BUCKET_DEX = ethers.keccak256(ethers.toUtf8Bytes("dex"));
beforeEach(async () => {
[foundation, alice, bob] = await ethers.getSigners();
const M = await ethers.getContractFactory("MockDeathSwitchCondition");
bridge = await M.deploy(BUCKET_BRIDGE, "bridge collateral ratio breached");
await bridge.waitForDeployment();
dex = await M.deploy(BUCKET_DEX, "dex pool drained > 50%");
await dex.waitForDeployment();
const D = await ethers.getContractFactory("AereDeathSwitch");
ds = await D.deploy(foundation.address, [await bridge.getAddress(), await dex.getAddress()]);
await ds.waitForDeployment();
});
it("constructor registers conditions + buckets", async () => {
expect(await ds.conditionsLength()).to.equal(2n);
const [addr0, b0] = await ds.conditionAt(0);
expect(addr0).to.equal(await bridge.getAddress());
expect(b0).to.equal(BUCKET_BRIDGE);
expect(await ds.isTripped()).to.equal(false);
});
it("only Foundation can declare emergency; declares + lock", async () => {
await expect(
ds.connect(alice).armForDeclaredEmergency("rogue Foundation key?")
).to.be.revertedWithCustomError(ds, "NotFoundation");
await ds.connect(foundation).armForDeclaredEmergency("test incident");
expect(await ds.isTripped()).to.equal(true);
expect(await ds.lastTripLabel()).to.equal("test incident");
expect(await ds.lastTripReason()).to.equal(ethers.keccak256(ethers.toUtf8Bytes("declared")));
});
it("permissionless trip succeeds when condition breached", async () => {
await bridge.setBreached(true);
await expect(ds.connect(alice).permissionlessTrip(0))
.to.emit(ds, "Tripped")
.withArgs(BUCKET_BRIDGE, alice.address, 0n, "bridge collateral ratio breached");
expect(await ds.isTripped()).to.equal(true);
expect(await ds.isBucketTripped(BUCKET_BRIDGE)).to.equal(true);
});
it("permissionless trip rejects when condition not breached / unknown id", async () => {
await expect(ds.connect(alice).permissionlessTrip(0))
.to.be.revertedWithCustomError(ds, "ConditionNotBreached");
await expect(ds.connect(alice).permissionlessTrip(99))
.to.be.revertedWithCustomError(ds, "UnknownCondition");
});
it("cannot trip twice; rearm flow gated by 7-day timelock", async () => {
await ds.connect(foundation).armForDeclaredEmergency("incident");
await expect(ds.connect(foundation).armForDeclaredEmergency("twice"))
.to.be.revertedWithCustomError(ds, "AlreadyTripped");
// Only Foundation can request rearm.
await expect(ds.connect(alice).requestRearm(ethers.ZeroHash))
.to.be.revertedWithCustomError(ds, "NotFoundation");
const reason = ethers.keccak256(ethers.toUtf8Bytes("rotated keys"));
await ds.connect(foundation).requestRearm(reason);
// Double request prevented.
await expect(ds.connect(foundation).requestRearm(reason))
.to.be.revertedWithCustomError(ds, "RearmAlreadyRequested");
// Cannot finalize before timelock.
await expect(ds.connect(foundation).finalizeRearm())
.to.be.revertedWithCustomError(ds, "RearmTimelockOpen");
// Fast-forward 7d.
await ethers.provider.send("evm_increaseTime", [7 * 24 * 3600 + 1]);
await ethers.provider.send("evm_mine", []);
await expect(ds.connect(foundation).finalizeRearm()).to.emit(ds, "Rearmed");
expect(await ds.isTripped()).to.equal(false);
expect(await ds.rearmReadyAt()).to.equal(0n);
});
it("finalizeRearm reverts when no request pending", async () => {
await ds.connect(foundation).armForDeclaredEmergency("incident");
await expect(ds.connect(foundation).finalizeRearm())
.to.be.revertedWithCustomError(ds, "NoRearmRequest");
});
it("cancelRearmRequest drops a pending request + emits (audit fix MED #37)", async () => {
await ds.connect(foundation).armForDeclaredEmergency("incident");
const reason = ethers.keccak256(ethers.toUtf8Bytes("reason"));
await ds.connect(foundation).requestRearm(reason);
await expect(ds.connect(foundation).cancelRearmRequest()).to.emit(ds, "RearmCancelled").withArgs(reason);
expect(await ds.rearmReadyAt()).to.equal(0n);
await ds.connect(foundation).requestRearm(ethers.ZeroHash);
expect(await ds.rearmReadyAt()).to.be.gt(0n);
});
it("permissionlessUnarmDeclared after 24h if no condition breached (audit fix HIGH #19)", async () => {
await ds.connect(foundation).armForDeclaredEmergency("Foundation pager test");
// Too early.
await expect(ds.connect(alice).permissionlessUnarmDeclared()).to.be.revertedWithCustomError(ds, "RearmTimelockOpen");
await ethers.provider.send("evm_increaseTime", [24 * 3600 + 1]);
await ethers.provider.send("evm_mine", []);
// If a condition is breached now, unarm still rejected.
await bridge.setBreached(true);
await expect(ds.connect(alice).permissionlessUnarmDeclared()).to.be.revertedWithCustomError(ds, "NoConditionsBreached");
await bridge.setBreached(false);
// Now succeeds — any address can call.
await ds.connect(alice).permissionlessUnarmDeclared();
expect(await ds.isTripped()).to.equal(false);
});
it("permissionlessUnarmDeclared not applicable for condition-tripped emergencies", async () => {
await bridge.setBreached(true);
await ds.connect(alice).permissionlessTrip(0);
await ethers.provider.send("evm_increaseTime", [24 * 3600 + 1]);
await ethers.provider.send("evm_mine", []);
await expect(ds.connect(alice).permissionlessUnarmDeclared()).to.be.revertedWithCustomError(ds, "ConditionNotBreached");
});
it("bucketTripped flags clear after rearm finalize", async () => {
await bridge.setBreached(true);
await ds.connect(alice).permissionlessTrip(0);
expect(await ds.bucketTripped(BUCKET_BRIDGE)).to.equal(true);
await bridge.setBreached(false);
await ds.connect(foundation).requestRearm(ethers.ZeroHash);
await ethers.provider.send("evm_increaseTime", [7 * 24 * 3600 + 1]);
await ethers.provider.send("evm_mine", []);
await ds.connect(foundation).finalizeRearm();
expect(await ds.bucketTripped(BUCKET_BRIDGE)).to.equal(false);
});
it("isBucketTripped is true while master tripped, even for unregistered buckets", async () => {
const FOO = ethers.keccak256(ethers.toUtf8Bytes("foo"));
await ds.connect(foundation).armForDeclaredEmergency("incident");
expect(await ds.isBucketTripped(FOO)).to.equal(true);
});
it("constructor with empty conditions list works (manual-only mode)", async () => {
const D = await ethers.getContractFactory("AereDeathSwitch");
const manual = await D.deploy(foundation.address, []);
expect(await manual.conditionsLength()).to.equal(0n);
await manual.connect(foundation).armForDeclaredEmergency("manual-only");
expect(await manual.isTripped()).to.equal(true);
});
});