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.
195 lines
8.6 KiB
JavaScript
195 lines
8.6 KiB
JavaScript
// Hardhat tests for AereSettlementHub.
|
|
//
|
|
// Covers:
|
|
// - list asset with per-tx limit + restricted-recipient flag
|
|
// - deposit pulls full amount, routes 50bps to sink, parks the rest
|
|
// - claim requires allowed solver + sufficient bond
|
|
// - settle after 6h challenge window releases asset to solver
|
|
// - challenge inside window emits event; outside window reverts
|
|
// - per-tx limit enforcement
|
|
// - restricted recipient enforcement
|
|
// - bond slash routes amount to sink
|
|
//
|
|
// Run: npx hardhat test test/settlement-hub.test.js
|
|
|
|
const { expect } = require("chai");
|
|
const { ethers } = require("hardhat");
|
|
|
|
const ONE = 10n ** 18n;
|
|
const SIX = 10n ** 6n;
|
|
|
|
async function deployStack() {
|
|
const [deployer, alice, solver1, solver2, recipient] = await ethers.getSigners();
|
|
|
|
// WAERE + tiny AereSink fixture.
|
|
const WAERE = await (await ethers.getContractFactory("contracts/WAERE.sol:WAERE")).deploy();
|
|
await WAERE.waitForDeployment();
|
|
|
|
// Mock USDC-like asset (6 decimals) for tests.
|
|
const TestUSDC = await ethers.getContractFactory("MockUSDC");
|
|
const usdc = await TestUSDC.deploy();
|
|
await usdc.waitForDeployment();
|
|
|
|
const burnVault = await (await ethers.getContractFactory("AereFeeBurnVault")).deploy();
|
|
await burnVault.waitForDeployment();
|
|
const fakeRouter = await (await ethers.getContractFactory("WAERE")).deploy();
|
|
await fakeRouter.waitForDeployment();
|
|
|
|
const nonce = await ethers.provider.getTransactionCount(deployer.address);
|
|
const futureSaere = ethers.getCreateAddress({ from: deployer.address, nonce: nonce + 2 });
|
|
await WAERE.connect(deployer).deposit({ value: 1000n });
|
|
await WAERE.connect(deployer).approve(futureSaere, 1000n);
|
|
const sAERE = await (await ethers.getContractFactory("sAERE")).deploy(await WAERE.getAddress());
|
|
await sAERE.waitForDeployment();
|
|
|
|
const sink = await (await ethers.getContractFactory("AereSink")).deploy(
|
|
await WAERE.getAddress(),
|
|
await burnVault.getAddress(),
|
|
await sAERE.getAddress(),
|
|
await fakeRouter.getAddress(),
|
|
1500, 4000, 4500, 200, ethers.ZeroAddress
|
|
);
|
|
await sink.waitForDeployment();
|
|
|
|
// Hub.
|
|
const Hub = await ethers.getContractFactory("AereSettlementHub");
|
|
const hub = await Hub.deploy(await sink.getAddress());
|
|
await hub.waitForDeployment();
|
|
|
|
return { deployer, alice, solver1, solver2, recipient, WAERE, usdc, sink, hub };
|
|
}
|
|
|
|
describe("AereSettlementHub", function () {
|
|
it("deposit pulls full amount and parks solverPortion", async function () {
|
|
const { deployer, alice, recipient, WAERE, hub } = await deployStack();
|
|
|
|
// List WAERE as an asset (use WAERE here because AereSink.flush handles it via fast path).
|
|
await hub.listAsset(await WAERE.getAddress(), 18, 0, 0, false);
|
|
|
|
const amount = 100n * ONE;
|
|
await WAERE.connect(alice).deposit({ value: amount });
|
|
await WAERE.connect(alice).approve(await hub.getAddress(), amount);
|
|
|
|
const intentId = ethers.id("intent1");
|
|
await hub.connect(alice).deposit(await WAERE.getAddress(), amount, intentId, recipient.address);
|
|
|
|
const fee = (amount * 50n) / 10_000n;
|
|
const solverPortion = amount - fee;
|
|
expect(await WAERE.balanceOf(await hub.getAddress())).to.equal(solverPortion);
|
|
|
|
const intent = await hub.intents(intentId);
|
|
expect(intent.amount).to.equal(solverPortion);
|
|
expect(intent.depositor).to.equal(alice.address);
|
|
expect(intent.recipient).to.equal(recipient.address);
|
|
expect(intent.open).to.equal(true);
|
|
});
|
|
|
|
it("claim requires allowed solver AND sufficient bond", async function () {
|
|
const { deployer, alice, solver1, recipient, WAERE, hub } = await deployStack();
|
|
await hub.listAsset(await WAERE.getAddress(), 18, 50n * ONE, 0, false);
|
|
|
|
const amount = 100n * ONE;
|
|
await WAERE.connect(alice).deposit({ value: amount });
|
|
await WAERE.connect(alice).approve(await hub.getAddress(), amount);
|
|
const intentId = ethers.id("intent2");
|
|
await hub.connect(alice).deposit(await WAERE.getAddress(), amount, intentId, recipient.address);
|
|
|
|
// Solver not allowed → revert.
|
|
await expect(hub.connect(solver1).claim(intentId, "0x")).to.be.revertedWithCustomError(hub, "NotAllowedSolver");
|
|
|
|
// Foundation allows solver1.
|
|
await hub.connect(deployer).setSolverAllowed(await WAERE.getAddress(), solver1.address, true);
|
|
|
|
// Solver1 has no bond → revert InsufficientBond.
|
|
await expect(hub.connect(solver1).claim(intentId, "0x")).to.be.revertedWithCustomError(hub, "InsufficientBond");
|
|
|
|
// Post the bond.
|
|
const bondAmt = 50n * ONE;
|
|
await WAERE.connect(alice).deposit({ value: bondAmt });
|
|
await WAERE.connect(alice).approve(await hub.getAddress(), bondAmt);
|
|
await hub.connect(alice).depositSolverBond(await WAERE.getAddress(), solver1.address, bondAmt);
|
|
|
|
await hub.connect(solver1).claim(intentId, "0x" + "ab".repeat(8));
|
|
const intent = await hub.intents(intentId);
|
|
expect(intent.solver).to.equal(solver1.address);
|
|
expect(intent.claimedAt).to.not.equal(0);
|
|
});
|
|
|
|
it("settle after 6h window releases solverPortion", async function () {
|
|
const { deployer, alice, solver1, recipient, WAERE, hub } = await deployStack();
|
|
await hub.listAsset(await WAERE.getAddress(), 18, 1n, 0, false);
|
|
await hub.connect(deployer).setSolverAllowed(await WAERE.getAddress(), solver1.address, true);
|
|
|
|
const amount = 50n * ONE;
|
|
await WAERE.connect(alice).deposit({ value: amount + 1n });
|
|
await WAERE.connect(alice).approve(await hub.getAddress(), amount + 1n);
|
|
await hub.connect(alice).depositSolverBond(await WAERE.getAddress(), solver1.address, 1n);
|
|
const intentId = ethers.id("intent3");
|
|
await hub.connect(alice).deposit(await WAERE.getAddress(), amount, intentId, recipient.address);
|
|
|
|
await hub.connect(solver1).claim(intentId, "0x");
|
|
|
|
await expect(hub.settle(intentId)).to.be.revertedWithCustomError(hub, "WithinChallengeWindow");
|
|
|
|
await ethers.provider.send("evm_increaseTime", [6 * 3600 + 1]);
|
|
await ethers.provider.send("evm_mine", []);
|
|
|
|
const solverPortion = amount - (amount * 50n) / 10_000n;
|
|
const balBefore = await WAERE.balanceOf(solver1.address);
|
|
await hub.settle(intentId);
|
|
expect(await WAERE.balanceOf(solver1.address) - balBefore).to.equal(solverPortion);
|
|
});
|
|
|
|
it("per-tx limit enforces", async function () {
|
|
const { alice, recipient, WAERE, hub } = await deployStack();
|
|
await hub.listAsset(await WAERE.getAddress(), 18, 0, 10n * ONE, false);
|
|
|
|
await WAERE.connect(alice).deposit({ value: 100n * ONE });
|
|
await WAERE.connect(alice).approve(await hub.getAddress(), 100n * ONE);
|
|
|
|
await expect(
|
|
hub.connect(alice).deposit(await WAERE.getAddress(), 11n * ONE, ethers.id("intent4"), recipient.address)
|
|
).to.be.revertedWithCustomError(hub, "PerTxLimitExceeded");
|
|
});
|
|
|
|
it("restricted recipient enforced", async function () {
|
|
const { deployer, alice, recipient, WAERE, hub } = await deployStack();
|
|
await hub.listAsset(await WAERE.getAddress(), 18, 0, 0, true);
|
|
|
|
await WAERE.connect(alice).deposit({ value: 10n * ONE });
|
|
await WAERE.connect(alice).approve(await hub.getAddress(), 10n * ONE);
|
|
|
|
// Recipient not on allowlist → revert.
|
|
await expect(
|
|
hub.connect(alice).deposit(await WAERE.getAddress(), 10n * ONE, ethers.id("intent5"), recipient.address)
|
|
).to.be.revertedWithCustomError(hub, "RecipientNotAllowed");
|
|
|
|
// Foundation allows recipient.
|
|
await hub.setRecipientAllowed(await WAERE.getAddress(), recipient.address, true);
|
|
await hub.connect(alice).deposit(await WAERE.getAddress(), 10n * ONE, ethers.id("intent5"), recipient.address);
|
|
});
|
|
|
|
it("duplicate intentId reverts", async function () {
|
|
const { alice, recipient, WAERE, hub } = await deployStack();
|
|
await hub.listAsset(await WAERE.getAddress(), 18, 0, 0, false);
|
|
await WAERE.connect(alice).deposit({ value: 20n * ONE });
|
|
await WAERE.connect(alice).approve(await hub.getAddress(), 20n * ONE);
|
|
await hub.connect(alice).deposit(await WAERE.getAddress(), 10n * ONE, ethers.id("dup"), recipient.address);
|
|
await expect(
|
|
hub.connect(alice).deposit(await WAERE.getAddress(), 10n * ONE, ethers.id("dup"), recipient.address)
|
|
).to.be.revertedWithCustomError(hub, "DuplicateIntent");
|
|
});
|
|
|
|
it("paused asset rejects deposit", async function () {
|
|
const { alice, recipient, WAERE, hub } = await deployStack();
|
|
await hub.listAsset(await WAERE.getAddress(), 18, 0, 0, false);
|
|
await hub.setAssetPaused(await WAERE.getAddress(), true);
|
|
|
|
await WAERE.connect(alice).deposit({ value: 10n * ONE });
|
|
await WAERE.connect(alice).approve(await hub.getAddress(), 10n * ONE);
|
|
await expect(
|
|
hub.connect(alice).deposit(await WAERE.getAddress(), 10n * ONE, ethers.id("intent6"), recipient.address)
|
|
).to.be.revertedWithCustomError(hub, "AssetPausedErr");
|
|
});
|
|
});
|