aere-contracts/test/compliance-pool.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

181 lines
8.0 KiB
JavaScript

const { expect } = require("chai");
const { ethers } = require("hardhat");
describe("AereCompliancePool", () => {
let foundation, casp, alice, bob, relayer;
let token, verifier, pool;
const DENOM = ethers.parseUnits("1000", 18);
beforeEach(async () => {
[foundation, casp, alice, bob, relayer] = await ethers.getSigners();
const T = await ethers.getContractFactory("MockERC20Lending");
token = await T.deploy("USDC.e", "USDCe", 18);
await token.waitForDeployment();
const V = await ethers.getContractFactory("MockPrivacyPoolVerifier");
verifier = await V.deploy();
await verifier.waitForDeployment();
const P = await ethers.getContractFactory("AereCompliancePool");
pool = await P.deploy(
await token.getAddress(), DENOM, foundation.address, await verifier.getAddress()
);
await pool.waitForDeployment();
await token.mint(alice.address, DENOM * 10n);
await token.connect(alice).approve(await pool.getAddress(), ethers.MaxUint256);
});
function rh(s) { return ethers.keccak256(ethers.toUtf8Bytes(s)); }
async function depositOnce(commitTag = "commit-1") {
await pool.connect(foundation).setComplianceProvider(casp.address, true);
const commit = rh(commitTag);
const trh = rh("travel-rule");
await pool.connect(alice).deposit(commit, trh, casp.address, "ipfs://tr");
return await pool.latestDepositRoot();
}
async function publishAssoc(tag = "assoc") {
const ar = rh(tag);
await pool.connect(foundation).proposeAssociationRoot(ar);
await ethers.provider.send("evm_increaseTime", [24 * 3600 + 1]);
await ethers.provider.send("evm_mine", []);
await pool.publishAssociationRoot(ar);
return ar;
}
it("only Foundation registers compliance providers", async () => {
await expect(
pool.connect(alice).setComplianceProvider(casp.address, true)
).to.be.revertedWithCustomError(pool, "NotFoundation");
await pool.connect(foundation).setComplianceProvider(casp.address, true);
expect(await pool.isComplianceProvider(casp.address)).to.equal(true);
expect((await pool.complianceProviders()).length).to.equal(1);
});
it("deposit pulls DENOM + emits Deposited + advances on-chain Merkle root (audit fix HIGH #28)", async () => {
const commit = rh("commit-1");
const trh = rh("travel-rule-hash-1");
await expect(
pool.connect(alice).deposit(commit, trh, casp.address, "ipfs://tr")
).to.be.revertedWithCustomError(pool, "NotAComplianceProvider");
await pool.connect(foundation).setComplianceProvider(casp.address, true);
const balBefore = await token.balanceOf(await pool.getAddress());
const rootBefore = await pool.latestDepositRoot();
await pool.connect(alice).deposit(commit, trh, casp.address, "ipfs://tr");
expect(await token.balanceOf(await pool.getAddress())).to.equal(balBefore + DENOM);
expect(await pool.depositCount()).to.equal(1n);
const rootAfter = await pool.latestDepositRoot();
expect(rootAfter).to.not.equal(rootBefore);
expect(await pool.isKnownRoot(rootAfter)).to.equal(true);
});
it("association root 24h challenge window (audit fix MED #28)", async () => {
const a1 = rh("assoc-root-1");
await pool.connect(foundation).proposeAssociationRoot(a1);
await expect(pool.publishAssociationRoot(a1)).to.be.revertedWithCustomError(pool, "UnknownAssociationRoot");
await ethers.provider.send("evm_increaseTime", [24 * 3600 + 1]);
await ethers.provider.send("evm_mine", []);
await expect(pool.publishAssociationRoot(a1))
.to.emit(pool, "AssociationRootPublished").withArgs(a1);
expect(await pool.isAssociationRootValid(a1)).to.equal(true);
});
it("association root challenge blocks finalisation", async () => {
const a1 = rh("assoc-root-challenged");
await pool.connect(foundation).proposeAssociationRoot(a1);
await pool.connect(alice).challengeAssociationRoot(a1, "this would let me bypass OFAC");
await ethers.provider.send("evm_increaseTime", [24 * 3600 + 1]);
await ethers.provider.send("evm_mine", []);
await expect(pool.publishAssociationRoot(a1)).to.be.revertedWithCustomError(pool, "UnknownAssociationRoot");
await pool.connect(foundation).dismissAssociationRootChallenge(a1);
await ethers.provider.send("evm_increaseTime", [24 * 3600 + 1]);
await ethers.provider.send("evm_mine", []);
await pool.publishAssociationRoot(a1);
expect(await pool.isAssociationRootValid(a1)).to.equal(true);
});
it("withdraw happy path: valid proof, known on-chain deposit root, fresh nullifier", async () => {
const dr = await depositOnce("c1");
const ar = await publishAssoc();
const nh = rh("nullifier-1");
const balBefore = await token.balanceOf(bob.address);
const relayerFee = ethers.parseUnits("5", 18);
await expect(
pool.connect(relayer).withdraw("0x1234", dr, ar, nh, bob.address, relayer.address, relayerFee, 0)
).to.emit(pool, "Withdrawn");
expect(await token.balanceOf(bob.address)).to.equal(balBefore + DENOM - relayerFee);
expect(await token.balanceOf(relayer.address)).to.equal(relayerFee);
expect(await pool.spentNullifiers(nh)).to.equal(true);
});
it("withdraw double-spend rejected (same nullifier)", async () => {
const dr = await depositOnce("c2");
const ar = await publishAssoc("ar2");
const nh = rh("nullifier-2");
await pool.connect(relayer).withdraw("0x", dr, ar, nh, bob.address, relayer.address, 0, 0);
await expect(
pool.connect(relayer).withdraw("0x", dr, ar, nh, bob.address, relayer.address, 0, 0)
).to.be.revertedWithCustomError(pool, "AlreadySpent");
});
it("withdraw unknown association root rejected", async () => {
const dr = await depositOnce("c3");
await expect(
pool.connect(relayer).withdraw("0x", dr, rh("never-published"), rh("n"), bob.address, relayer.address, 0, 0)
).to.be.revertedWithCustomError(pool, "UnknownAssociationRoot");
});
it("withdraw unknown deposit root rejected (audit fix HIGH #28: only on-chain roots accepted)", async () => {
const ar = await publishAssoc("ar4");
// depositRoot fabricated; not in on-chain ring buffer → reverts.
await expect(
pool.connect(relayer).withdraw("0x", rh("fake-dr"), ar, rh("n"), bob.address, relayer.address, 0, 0)
).to.be.revertedWithCustomError(pool, "UnknownDepositRoot");
});
it("withdraw invalid proof rejected by verifier", async () => {
const dr = await depositOnce("c5");
const ar = await publishAssoc("ar5");
await verifier.setAccept(false);
await expect(
pool.connect(relayer).withdraw("0x", dr, ar, rh("n"), bob.address, relayer.address, 0, 0)
).to.be.revertedWithCustomError(pool, "InvalidProof");
});
it("fee + refund > DENOMINATION rejected", async () => {
const dr = await depositOnce("c6");
const ar = await publishAssoc("ar6");
await expect(
pool.connect(relayer).withdraw("0x", dr, ar, rh("n"), bob.address, relayer.address, DENOM, 1n)
).to.be.revertedWithCustomError(pool, "FeeTooLarge");
});
it("older on-chain root remains valid (root history)", async () => {
await pool.connect(foundation).setComplianceProvider(casp.address, true);
await pool.connect(alice).deposit(rh("a1"), rh("t"), casp.address, "");
const r1 = await pool.latestDepositRoot();
await pool.connect(alice).deposit(rh("a2"), rh("t"), casp.address, "");
const r2 = await pool.latestDepositRoot();
expect(r1).to.not.equal(r2);
expect(await pool.isKnownRoot(r1)).to.equal(true);
expect(await pool.isKnownRoot(r2)).to.equal(true);
const ar = await publishAssoc("ar7");
// Withdraw against the OLDER root still works.
await pool.connect(relayer).withdraw("0x", r1, ar, rh("n"), bob.address, relayer.address, 0, 0);
});
it("duplicate commitment rejected", async () => {
await pool.connect(foundation).setComplianceProvider(casp.address, true);
const c = rh("same");
await pool.connect(alice).deposit(c, rh("t"), casp.address, "");
await expect(
pool.connect(alice).deposit(c, rh("t"), casp.address, "")
).to.be.revertedWithCustomError(pool, "CommitmentReused");
});
});