aere-contracts/test/aere-warp-route-v3.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

242 lines
11 KiB
JavaScript

// AereWarpRouteV3 — USDC.e Hyperlane v3 route on the AERE side (chain 2800),
// bound to a faithful AereMailboxV3-like mock. Covers both token modes:
// MintBurn (synthetic USDC.e, the canonical bridged representation) and
// Lock (release an existing token the router does not mint).
//
// Run: npx hardhat test test/aere-warp-route-v3.test.js
const { expect } = require("chai");
const { ethers } = require("hardhat");
const ETH_DOMAIN = 1;
const AERE_DOMAIN = 2800;
const SIX = 10n ** 6n;
function addrToBytes32(a) {
return ethers.zeroPadValue(a, 32);
}
function packBody(recipientB32, amount) {
return ethers.solidityPacked(["bytes32", "uint256"], [recipientB32, amount]);
}
describe("AereWarpRouteV3 — USDC.e Hyperlane v3 route (AERE side)", function () {
let deployer, alice, bob, carol;
let mailbox, igp;
beforeEach(async function () {
[deployer, alice, bob, carol] = await ethers.getSigners();
mailbox = await (await ethers.getContractFactory("MockWarpMailboxV3")).deploy(AERE_DOMAIN);
await mailbox.waitForDeployment();
igp = await (await ethers.getContractFactory("MockWarpIGPV3")).deploy();
await igp.waitForDeployment();
});
// ─────────────────────── MintBurn (synthetic USDC.e) ───────────────────────
describe("MintBurn mode (synthetic USDC.e)", function () {
let usdce, router;
beforeEach(async function () {
// USDC.e's BRIDGE is immutable, so predict the router address (deployer
// nonce + 1) and deploy USDC.e pointing at it first — same pattern the
// multi-stable-warp deploy script uses.
const nonce = await ethers.provider.getTransactionCount(deployer.address);
const futureRouter = ethers.getCreateAddress({ from: deployer.address, nonce: nonce + 1 });
usdce = await (await ethers.getContractFactory("USDCe")).deploy(futureRouter);
await usdce.waitForDeployment();
router = await (await ethers.getContractFactory("AereWarpRouteV3")).deploy(
await mailbox.getAddress(),
await igp.getAddress(),
await usdce.getAddress(),
1 // Mode.MintBurn
);
await router.waitForDeployment();
expect((await router.getAddress()).toLowerCase()).to.equal(futureRouter.toLowerCase());
expect(await usdce.BRIDGE()).to.equal(await router.getAddress());
});
it("wires immutables (mailbox / igp / token / mode)", async function () {
expect(await router.MAILBOX()).to.equal(await mailbox.getAddress());
expect(await router.IGP()).to.equal(await igp.getAddress());
expect(await router.TOKEN()).to.equal(await usdce.getAddress());
expect(await router.MODE()).to.equal(1n);
});
it("enrollRemoteRouter is owner-only", async function () {
const r = addrToBytes32(carol.address);
await expect(router.connect(alice).enrollRemoteRouter(ETH_DOMAIN, r))
.to.be.revertedWith("Ownable: caller is not the owner");
await expect(router.enrollRemoteRouter(ETH_DOMAIN, r))
.to.emit(router, "RemoteRouterEnrolled").withArgs(ETH_DOMAIN, r);
expect(await router.routers(ETH_DOMAIN)).to.equal(r);
});
it("handle from a non-mailbox caller reverts", async function () {
await expect(
router.connect(alice).handle(ETH_DOMAIN, addrToBytes32(carol.address), "0x" + "00".repeat(64))
).to.be.revertedWithCustomError(router, "NotMailbox");
});
it("handle from an unenrolled remote router reverts", async function () {
const body = packBody(addrToBytes32(bob.address), 5n * SIX);
await expect(
mailbox.mockDeliver(await router.getAddress(), ETH_DOMAIN, addrToBytes32(carol.address), body)
).to.be.revertedWithCustomError(router, "UnknownRouter");
});
it("enrolled inbound mints exactly the amount to the recipient", async function () {
const remote = addrToBytes32(carol.address);
await router.enrollRemoteRouter(ETH_DOMAIN, remote);
const amount = 123n * SIX;
const body = packBody(addrToBytes32(bob.address), amount);
await expect(mailbox.mockDeliver(await router.getAddress(), ETH_DOMAIN, remote, body))
.to.emit(router, "ReceivedTransferRemote").withArgs(ETH_DOMAIN, bob.address, amount);
expect(await usdce.balanceOf(bob.address)).to.equal(amount);
expect(await usdce.totalSupply()).to.equal(amount);
expect(await router.totalReceivedIn()).to.equal(amount);
});
it("transferRemote burns caller tokens, quotes, dispatches with msg.value==quote, emits", async function () {
const remote = addrToBytes32(carol.address);
await router.enrollRemoteRouter(ETH_DOMAIN, remote);
// Seed bob with USDC.e via a verified inbound.
const seed = 100n * SIX;
await mailbox.mockDeliver(
await router.getAddress(), ETH_DOMAIN, remote, packBody(addrToBytes32(bob.address), seed)
);
const amount = 40n * SIX;
const recipient = addrToBytes32(alice.address);
const fee = await router.quoteTransferRemote(ETH_DOMAIN, recipient, amount);
expect(fee).to.equal(await mailbox.feeAmount());
await expect(router.connect(bob).transferRemote(ETH_DOMAIN, recipient, amount, { value: fee }))
.to.emit(router, "SentTransferRemote");
// Caller's tokens burned.
expect(await usdce.balanceOf(bob.address)).to.equal(seed - amount);
// Dispatch received exactly the quoted fee, with the right payload.
expect(await mailbox.lastFeePaid()).to.equal(fee);
expect(await mailbox.lastDestination()).to.equal(ETH_DOMAIN);
expect(await mailbox.lastRecipient()).to.equal(remote);
expect(await mailbox.lastBody()).to.equal(packBody(recipient, amount));
// No dust left in the router; mailbox holds exactly the fee.
expect(await ethers.provider.getBalance(await router.getAddress())).to.equal(0n);
expect(await ethers.provider.getBalance(await mailbox.getAddress())).to.equal(fee);
expect(await router.totalSentOut()).to.equal(amount);
});
it("transferRemote refunds overpayment above the quote", async function () {
const remote = addrToBytes32(carol.address);
await router.enrollRemoteRouter(ETH_DOMAIN, remote);
await mailbox.mockDeliver(
await router.getAddress(), ETH_DOMAIN, remote, packBody(addrToBytes32(bob.address), 10n * SIX)
);
const amount = 3n * SIX;
const recipient = addrToBytes32(alice.address);
const fee = await router.quoteTransferRemote(ETH_DOMAIN, recipient, amount);
const overpay = fee + ethers.parseEther("0.05");
const balBefore = await ethers.provider.getBalance(bob.address);
const tx = await router.connect(bob).transferRemote(ETH_DOMAIN, recipient, amount, { value: overpay });
const rc = await tx.wait();
const gas = rc.gasUsed * rc.gasPrice;
const balAfter = await ethers.provider.getBalance(bob.address);
expect(balBefore - balAfter - gas).to.equal(fee);
expect(await ethers.provider.getBalance(await router.getAddress())).to.equal(0n);
});
it("transferRemote reverts when msg.value < quote", async function () {
const remote = addrToBytes32(carol.address);
await router.enrollRemoteRouter(ETH_DOMAIN, remote);
await mailbox.mockDeliver(
await router.getAddress(), ETH_DOMAIN, remote, packBody(addrToBytes32(bob.address), 5n * SIX)
);
const recipient = addrToBytes32(alice.address);
const fee = await router.quoteTransferRemote(ETH_DOMAIN, recipient, 1n * SIX);
await expect(
router.connect(bob).transferRemote(ETH_DOMAIN, recipient, 1n * SIX, { value: fee - 1n })
).to.be.revertedWithCustomError(router, "InsufficientGasPayment");
});
it("transferRemote input guards: no router / zero amount / zero recipient", async function () {
const recipient = addrToBytes32(alice.address);
const fee = ethers.parseEther("0.001");
await expect(router.connect(bob).transferRemote(999, recipient, 1n, { value: fee }))
.to.be.revertedWithCustomError(router, "NoRouterEnrolled");
await router.enrollRemoteRouter(ETH_DOMAIN, addrToBytes32(carol.address));
await expect(router.connect(bob).transferRemote(ETH_DOMAIN, recipient, 0n, { value: fee }))
.to.be.revertedWithCustomError(router, "ZeroAmount");
await expect(router.connect(bob).transferRemote(ETH_DOMAIN, ethers.ZeroHash, 1n, { value: fee }))
.to.be.revertedWithCustomError(router, "ZeroRecipient");
});
it("quoteGasPayment view reads the IGP", async function () {
expect(await router.quoteGasPayment(ETH_DOMAIN, 50000n)).to.equal(50000n * 10n ** 9n);
});
});
// ─────────────────────── Lock (release existing token) ───────────────────────
describe("Lock mode (release existing token)", function () {
let token, router;
beforeEach(async function () {
token = await (await ethers.getContractFactory("MockUSDC")).deploy();
await token.waitForDeployment();
router = await (await ethers.getContractFactory("AereWarpRouteV3")).deploy(
await mailbox.getAddress(),
await igp.getAddress(),
await token.getAddress(),
0 // Mode.Lock
);
await router.waitForDeployment();
});
it("transferRemote locks tokens into the router and dispatches", async function () {
const remote = addrToBytes32(carol.address);
await router.enrollRemoteRouter(ETH_DOMAIN, remote);
const amount = 250n * SIX;
await token.mint(bob.address, amount);
await token.connect(bob).approve(await router.getAddress(), amount);
const recipient = addrToBytes32(alice.address);
const fee = await router.quoteTransferRemote(ETH_DOMAIN, recipient, amount);
await expect(router.connect(bob).transferRemote(ETH_DOMAIN, recipient, amount, { value: fee }))
.to.emit(router, "SentTransferRemote");
expect(await token.balanceOf(await router.getAddress())).to.equal(amount);
expect(await token.balanceOf(bob.address)).to.equal(0n);
expect(await router.lockedSupply()).to.equal(amount);
});
it("enrolled inbound releases exactly the amount to the recipient", async function () {
const remote = addrToBytes32(carol.address);
await router.enrollRemoteRouter(ETH_DOMAIN, remote);
await token.mint(await router.getAddress(), 500n * SIX);
const amount = 175n * SIX;
const body = packBody(addrToBytes32(bob.address), amount);
await expect(mailbox.mockDeliver(await router.getAddress(), ETH_DOMAIN, remote, body))
.to.emit(router, "ReceivedTransferRemote").withArgs(ETH_DOMAIN, bob.address, amount);
expect(await token.balanceOf(bob.address)).to.equal(amount);
expect(await router.lockedSupply()).to.equal(500n * SIX - amount);
});
it("handle from an unenrolled remote router reverts", async function () {
await token.mint(await router.getAddress(), 100n * SIX);
const body = packBody(addrToBytes32(bob.address), 1n * SIX);
await expect(
mailbox.mockDeliver(await router.getAddress(), ETH_DOMAIN, addrToBytes32(carol.address), body)
).to.be.revertedWithCustomError(router, "UnknownRouter");
});
});
});