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