aere-contracts/test/insurance-fund-cap-clamp-order-fix.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

114 lines
4.9 KiB
JavaScript

// =============================================================================
// TDD proof for the AereInsuranceFund LIFETIME-CAP CHECK ORDER fix (LOW).
//
// BUG: the lifetime-cap check ran on the REQUESTED amount BEFORE the
// clamp-to-owed step, so a legitimate coverage whose clamped amount is under
// the remaining cap was spuriously blocked with CapExceeded.
//
// FIX: fetch owed + clamp `amount` to it FIRST, then run the cap check on the
// ACTUAL (clamped) amount that will be paid. Cap semantics unchanged: total
// lifetime coverage per market <= cap.
//
// Uses MockDebtMarketForFund so the debt balance is pinned exactly (the real
// AereLendingMarket accrues per-second interest across the 7-day registration
// timelock, which would blur the cap boundary this test targets).
//
// Run in ISOLATION:
// npx hardhat test test/insurance-fund-cap-clamp-order-fix.test.js
//
// No em-dashes in this file.
// =============================================================================
const { expect } = require("chai");
const { ethers } = require("hardhat");
const ONE6 = 10n ** 6n;
describe("AereInsuranceFund - cap check applies to clamped amount (order fix)", function () {
this.timeout(120000);
async function deploy() {
const [, foundation, bob, donor] = await ethers.getSigners();
const ERC20 = await ethers.getContractFactory("MockERC20Lending");
const debt = await ERC20.deploy("USDC.e", "USDC.e", 6);
await debt.waitForDeployment();
const Mock = await ethers.getContractFactory("MockDebtMarketForFund");
const mkt = await Mock.deploy(await debt.getAddress());
await mkt.waitForDeployment();
const Fund = await ethers.getContractFactory("AereInsuranceFund");
const fund = await Fund.deploy(foundation.address, 0); // no cooldown
await fund.waitForDeployment();
// Fund the pool with 1000 USDC.e.
await debt.mint(donor.address, 1000n * ONE6);
await debt.connect(donor).approve(await fund.getAddress(), 1000n * ONE6);
await fund.connect(donor).donate(await debt.getAddress(), 1000n * ONE6);
return { fund, mkt, debt, foundation, bob };
}
async function register(fund, foundation, mkt, cap) {
await fund.connect(foundation).proposeMarket(await mkt.getAddress(), cap);
await ethers.provider.send("evm_increaseTime", [7 * 86400 + 1]);
await ethers.provider.send("evm_mine", []);
await fund.connect(foundation).registerMarket(await mkt.getAddress());
}
it("request > owed but clamped amount < remaining cap now SUCCEEDS (pre-fix reverted CapExceeded)", async () => {
const { fund, mkt, foundation, bob } = await deploy();
const owed = 10n * ONE6;
await mkt.setDebt(bob.address, owed);
// cap (20) > owed (10); request (100) > cap.
const cap = 20n * ONE6;
await register(fund, foundation, mkt, cap);
const request = 100n * ONE6;
// Pre-fix: cap check on the request (100 > 20) -> CapExceeded.
// Post-fix: clamp to owed (10) <= cap (20) -> pays exactly 10.
await expect(
fund.connect(foundation).coverBadDebt(await mkt.getAddress(), bob.address, request)
).to.emit(fund, "BadDebtCovered").withArgs(await mkt.getAddress(), bob.address, owed);
const m = await fund.markets(await mkt.getAddress());
expect(m.covered).to.equal(owed);
expect(await mkt.debtBalance(bob.address)).to.equal(0n);
});
it("cap semantics preserved: clamped amount OVER the cap still reverts CapExceeded", async () => {
const { fund, mkt, foundation, bob } = await deploy();
const owed = 30n * ONE6;
await mkt.setDebt(bob.address, owed);
// cap (20) < owed (30): even after clamping to 30, 30 > 20 -> CapExceeded.
const cap = 20n * ONE6;
await register(fund, foundation, mkt, cap);
await expect(
fund.connect(foundation).coverBadDebt(await mkt.getAddress(), bob.address, 1000n * ONE6)
).to.be.revertedWithCustomError(fund, "CapExceeded");
});
it("cap accumulates across coverages on the clamped amounts", async () => {
const { fund, mkt, foundation, bob } = await deploy();
const [, , , , carol] = await ethers.getSigners();
// cap 25; first cover clamps to 10, second to 12 -> total 22 <= 25 ok;
// a third clamped 10 would push to 32 > 25 -> CapExceeded.
const cap = 25n * ONE6;
await register(fund, foundation, mkt, cap);
await mkt.setDebt(bob.address, 10n * ONE6);
await fund.connect(foundation).coverBadDebt(await mkt.getAddress(), bob.address, 1000n * ONE6);
expect((await fund.markets(await mkt.getAddress())).covered).to.equal(10n * ONE6);
await mkt.setDebt(carol.address, 12n * ONE6);
await fund.connect(foundation).coverBadDebt(await mkt.getAddress(), carol.address, 1000n * ONE6);
expect((await fund.markets(await mkt.getAddress())).covered).to.equal(22n * ONE6);
await mkt.setDebt(bob.address, 10n * ONE6);
await expect(
fund.connect(foundation).coverBadDebt(await mkt.getAddress(), bob.address, 1000n * ONE6)
).to.be.revertedWithCustomError(fund, "CapExceeded");
});
});