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