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.
181 lines
8.0 KiB
JavaScript
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");
|
|
});
|
|
});
|