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/); } }); });