aere-contracts/test/aere-fee-burn-vault.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

211 lines
9.2 KiB
JavaScript

// =============================================================================
// REAL Hardhat coverage tests for contracts/AereFeeBurnVault.sol.
//
// AereFeeBurnVault is an IN-USE contract: live on chain 2800 at
// 0x696afDF4f814e6Fd6aa45CE14C498ed9375fB2c6 (deployments/fee-burn-vault.json),
// referenced in aerenew/sdk-js/src/addresses.ts, and the destination of 37.5%
// of protocol fees routed by AereCoinbaseSplitter. The scoped coverage run in
// aerenew/docs/AERE-TEST-COVERAGE-REPORT.md measured it at 0/0/0/0 because none
// of the six economic-core tests call its functions.
//
// These tests exercise EVERY function and EVERY reachable branch, prioritising
// branch coverage (revert paths + boundary conditions), as required by the task.
//
// The one genuinely unreachable branch is the `require(ok, "zero-send failed")`
// FALSE path in sweepToZero(): a low-level call to address(0) with sufficient
// balance always succeeds on the EVM (it is a value transfer to a bare account),
// so that failure branch cannot be driven from a test. It is documented here
// rather than faked.
//
// No em-dashes anywhere in this file.
// =============================================================================
const { expect } = require("chai");
const { ethers } = require("hardhat");
const ONE = 10n ** 18n;
const ZERO = ethers.ZeroAddress;
const ADDR_ZERO_BURN = "0x0000000000000000000000000000000000000000";
describe("AereFeeBurnVault (in-use fee burn endpoint) — full branch battery", function () {
let deployer, alice, bob, vault, vaultAddr, waere, waereAddr;
beforeEach(async function () {
[deployer, alice, bob] = await ethers.getSigners();
vault = await (await ethers.getContractFactory("AereFeeBurnVault")).deploy();
await vault.waitForDeployment();
vaultAddr = await vault.getAddress();
// A real, compliant ERC20 whose transferFrom returns true: WAERE.
waere = await (await ethers.getContractFactory("contracts/WAERE.sol:WAERE")).deploy();
await waere.waitForDeployment();
waereAddr = await waere.getAddress();
});
/* ------------------------------- native burn ------------------------------ */
describe("receive() — plain native AERE transfer is a burn", function () {
it("credits totalBurnedAERE and emits AereBurned", async function () {
const amt = 3n * ONE;
await expect(alice.sendTransaction({ to: vaultAddr, value: amt }))
.to.emit(vault, "AereBurned")
.withArgs(alice.address, amt, amt);
expect(await vault.totalBurnedAERE()).to.equal(amt);
expect(await ethers.provider.getBalance(vaultAddr)).to.equal(amt);
expect(await vault.currentAEREBalance()).to.equal(amt);
});
it("accumulates across multiple senders (running total in the event)", async function () {
await alice.sendTransaction({ to: vaultAddr, value: ONE });
await bob.sendTransaction({ to: vaultAddr, value: 2n * ONE });
expect(await vault.totalBurnedAERE()).to.equal(3n * ONE);
expect(await vault.currentAEREBalance()).to.equal(3n * ONE);
});
});
describe("burn() — explicit native burn entrypoint", function () {
it("credits totalBurnedAERE and emits AereBurned with the running total", async function () {
const amt = 5n * ONE;
await expect(vault.connect(alice).burn({ value: amt }))
.to.emit(vault, "AereBurned")
.withArgs(alice.address, amt, amt);
expect(await vault.totalBurnedAERE()).to.equal(amt);
});
it("burn() and receive() share the same counter", async function () {
await vault.connect(alice).burn({ value: ONE });
await alice.sendTransaction({ to: vaultAddr, value: ONE });
expect(await vault.totalBurnedAERE()).to.equal(2n * ONE);
});
it("burn() with zero value is a no-op success that still emits (amount 0)", async function () {
await expect(vault.connect(alice).burn({ value: 0 }))
.to.emit(vault, "AereBurned")
.withArgs(alice.address, 0, 0);
expect(await vault.totalBurnedAERE()).to.equal(0);
});
});
/* -------------------------------- burnToken ------------------------------- */
describe("burnToken() — ERC20 burn pull path (both sides of all 3 requires)", function () {
it("SUCCESS: pulls the token, bumps per-token counter, emits TokenBurned", async function () {
const amt = 7n * ONE;
// Fund alice with WAERE and approve the vault.
await alice.sendTransaction({ to: waereAddr, value: amt });
await waere.connect(alice).approve(vaultAddr, amt);
await expect(vault.connect(alice).burnToken(waereAddr, amt))
.to.emit(vault, "TokenBurned")
.withArgs(waereAddr, alice.address, amt, amt);
expect(await vault.totalBurnedToken(waereAddr)).to.equal(amt);
expect(await waere.balanceOf(vaultAddr)).to.equal(amt);
// Native counter untouched by an ERC20 burn.
expect(await vault.totalBurnedAERE()).to.equal(0);
});
it("SUCCESS: per-token counter accumulates across calls", async function () {
await alice.sendTransaction({ to: waereAddr, value: 10n * ONE });
await waere.connect(alice).approve(vaultAddr, 10n * ONE);
await vault.connect(alice).burnToken(waereAddr, 4n * ONE);
await vault.connect(alice).burnToken(waereAddr, 6n * ONE);
expect(await vault.totalBurnedToken(waereAddr)).to.equal(10n * ONE);
});
it("REVERT: zero token address (require token != address(0))", async function () {
await expect(vault.connect(alice).burnToken(ZERO, ONE)).to.be.revertedWith(
"BurnVault: zero token"
);
});
it("REVERT: zero amount (require amount > 0)", async function () {
await expect(vault.connect(alice).burnToken(waereAddr, 0)).to.be.revertedWith(
"BurnVault: zero amount"
);
});
it("REVERT: transferFrom returns false (require ok) via a non-compliant token", async function () {
const bad = await (await ethers.getContractFactory("MockReturnFalseERC20")).deploy();
await bad.waitForDeployment();
const badAddr = await bad.getAddress();
await bad.mint(alice.address, ONE);
await bad.connect(alice).approve(vaultAddr, ONE);
await expect(vault.connect(alice).burnToken(badAddr, ONE)).to.be.revertedWith(
"BurnVault: transferFrom failed"
);
// Counter never moved on the failed pull.
expect(await vault.totalBurnedToken(badAddr)).to.equal(0);
});
it("REVERT: transferFrom reverts (no approval) surfaces the token's own revert", async function () {
await alice.sendTransaction({ to: waereAddr, value: ONE });
// No approve() -> WAERE.transferFrom reverts on insufficient allowance.
await expect(vault.connect(alice).burnToken(waereAddr, ONE)).to.be.reverted;
});
});
/* ------------------------------- sweepToZero ------------------------------ */
describe("sweepToZero() — forward accumulated native AERE to address(0)", function () {
it("SUCCESS: sends the whole balance to 0x0, bumps totalSentToZero, emits", async function () {
const amt = 8n * ONE;
await vault.connect(alice).burn({ value: amt });
expect(await vault.currentAEREBalance()).to.equal(amt);
const zeroBefore = await ethers.provider.getBalance(ADDR_ZERO_BURN);
await expect(vault.connect(bob).sweepToZero())
.to.emit(vault, "AereSentToZero")
.withArgs(amt, amt);
// Vault emptied, sent-to-zero counter reflects the sweep.
expect(await vault.currentAEREBalance()).to.equal(0);
expect(await vault.totalSentToZero()).to.equal(amt);
// address(0) actually received the value.
expect(await ethers.provider.getBalance(ADDR_ZERO_BURN)).to.equal(zeroBefore + amt);
// The gross-burned counter is a lifetime figure and is NOT reset by a sweep.
expect(await vault.totalBurnedAERE()).to.equal(amt);
});
it("SUCCESS: two sweeps accumulate totalSentToZero", async function () {
await vault.connect(alice).burn({ value: 2n * ONE });
await vault.connect(bob).sweepToZero();
await vault.connect(alice).burn({ value: 3n * ONE });
await vault.connect(bob).sweepToZero();
expect(await vault.totalSentToZero()).to.equal(5n * ONE);
expect(await vault.currentAEREBalance()).to.equal(0);
});
it("REVERT: nothing to sweep when balance is zero (require bal > 0)", async function () {
await expect(vault.connect(alice).sweepToZero()).to.be.revertedWith(
"BurnVault: nothing to sweep"
);
});
it("REVERT: nothing to sweep AFTER a full sweep empties the vault", async function () {
await vault.connect(alice).burn({ value: ONE });
await vault.connect(bob).sweepToZero();
// Second sweep with a zero balance reverts.
await expect(vault.connect(bob).sweepToZero()).to.be.revertedWith(
"BurnVault: nothing to sweep"
);
});
});
/* -------------------------------- read views ------------------------------ */
describe("currentAEREBalance() view", function () {
it("tracks the live native balance across burn and sweep", async function () {
expect(await vault.currentAEREBalance()).to.equal(0);
await vault.connect(alice).burn({ value: 4n * ONE });
expect(await vault.currentAEREBalance()).to.equal(4n * ONE);
await vault.connect(bob).sweepToZero();
expect(await vault.currentAEREBalance()).to.equal(0);
});
});
});