aere-contracts/test/destination-settler.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

178 lines
7.7 KiB
JavaScript

// Hardhat tests for AereDestinationSettler (ERC-7683 IDestinationSettler).
//
// The DESTINATION / filling side of AERE's cross-chain intents. A solver calls
// fill(orderId, originData, fillerData) to deliver the order's promised output to
// the recipient on AERE; the settler records the fill and emits Filled so the origin
// chain can repay the solver behind a QBFT-finality proof.
//
// Cases:
// - valid fill: delivers output to recipient, records the fill, emits Filled,
// exposes the record + fillCommitment
// - duplicate fill reverts (AlreadyFilled)
// - mismatched-output fill reverts (OutputMismatch)
// - zero-recipient reverts (ZeroRecipient)
//
// Run: npx hardhat test test/destination-settler.test.js
const { expect } = require("chai");
const { ethers } = require("hardhat");
const ONE = 10n ** 18n;
const AERE_CHAIN_ID = 2800n;
function b32(addr) {
return ethers.zeroPadValue(addr, 32);
}
// originData: the origin settler's declared output leg (matches AereSpokePool).
function encodeOriginData(orderId, outputToken, outputAmount, recipient32) {
return ethers.AbiCoder.defaultAbiCoder().encode(
["bytes32", "address", "uint256", "bytes32"],
[orderId, outputToken, outputAmount, recipient32]
);
}
// fillerData: the solver's actual delivery + origin-chain repayment address.
function encodeFillerData(deliveredToken, deliveredAmount, deliveredRecipient, repaymentAddress32) {
return ethers.AbiCoder.defaultAbiCoder().encode(
["address", "uint256", "address", "bytes32"],
[deliveredToken, deliveredAmount, deliveredRecipient, repaymentAddress32]
);
}
async function deployFixture() {
const [deployer, solver, recipient, other] = await ethers.getSigners();
// Output token the solver delivers on AERE.
const Token = await ethers.getContractFactory("MockERC20Lending");
const token = await Token.deploy("USD Coin (bridged)", "USDC.e", 6);
await token.waitForDeployment();
const Settler = await ethers.getContractFactory(
"contracts/intents/AereDestinationSettler.sol:AereDestinationSettler"
);
const settler = await Settler.deploy();
await settler.waitForDeployment();
// Fund the solver with output tokens and approve the settler to pull them.
const funded = 1_000_000n * ONE;
await token.mint(solver.address, funded);
await token.connect(solver).approve(await settler.getAddress(), funded);
return { deployer, solver, recipient, other, token, settler };
}
describe("AereDestinationSettler - ERC-7683 IDestinationSettler", function () {
it("valid fill: delivers output to recipient, records the fill, emits Filled, exposes commitment", async function () {
const { solver, recipient, token, settler } = await deployFixture();
const orderId = ethers.id("order-1");
const outputAmount = 250n * ONE;
const tokenAddr = await token.getAddress();
const recipient32 = b32(recipient.address);
const repayment32 = b32(solver.address);
const originData = encodeOriginData(orderId, tokenAddr, outputAmount, recipient32);
const fillerData = encodeFillerData(tokenAddr, outputAmount, recipient.address, repayment32);
const recipBefore = await token.balanceOf(recipient.address);
await expect(settler.connect(solver).fill(orderId, originData, fillerData))
.to.emit(settler, "Filled");
// Recipient received exactly the delivered output; settler custodies nothing.
expect(await token.balanceOf(recipient.address) - recipBefore).to.equal(outputAmount);
expect(await token.balanceOf(await settler.getAddress())).to.equal(0n);
// Fill recorded and readable.
expect(await settler.isFilled(orderId)).to.equal(true);
const rec = await settler.getFill(orderId);
expect(rec.filler).to.equal(solver.address);
expect(rec.outputToken).to.equal(tokenAddr);
expect(rec.outputAmount).to.equal(outputAmount);
expect(rec.recipient).to.equal(recipient.address);
expect(rec.repaymentAddress).to.equal(repayment32);
expect(await settler.fillCount()).to.equal(1n);
// fillCommitment binds the exact fields the origin-side verifier re-derives.
const expectedCommitment = ethers.keccak256(
ethers.AbiCoder.defaultAbiCoder().encode(
["uint256", "address", "bytes32", "address", "address", "uint256", "address", "bytes32"],
[
AERE_CHAIN_ID,
await settler.getAddress(),
orderId,
solver.address,
tokenAddr,
outputAmount,
recipient.address,
repayment32,
]
)
);
expect(await settler.fillCommitment(orderId)).to.equal(expectedCommitment);
});
it("duplicate fill reverts (AlreadyFilled)", async function () {
const { solver, recipient, token, settler } = await deployFixture();
const orderId = ethers.id("order-dup");
const outputAmount = 100n * ONE;
const tokenAddr = await token.getAddress();
const originData = encodeOriginData(orderId, tokenAddr, outputAmount, b32(recipient.address));
const fillerData = encodeFillerData(tokenAddr, outputAmount, recipient.address, b32(solver.address));
await settler.connect(solver).fill(orderId, originData, fillerData);
await expect(settler.connect(solver).fill(orderId, originData, fillerData))
.to.be.revertedWithCustomError(settler, "AlreadyFilled")
.withArgs(orderId);
});
it("mismatched-output fill reverts (OutputMismatch)", async function () {
const { solver, recipient, other, token, settler } = await deployFixture();
const orderId = ethers.id("order-mismatch");
const outputAmount = 100n * ONE;
const tokenAddr = await token.getAddress();
const originData = encodeOriginData(orderId, tokenAddr, outputAmount, b32(recipient.address));
// Short delivery (less than the declared output) must revert.
const shortFiller = encodeFillerData(tokenAddr, outputAmount - 1n, recipient.address, b32(solver.address));
await expect(settler.connect(solver).fill(orderId, originData, shortFiller))
.to.be.revertedWithCustomError(settler, "OutputMismatch");
// Wrong recipient (delivering to someone other than the declared recipient) must revert.
const wrongRecipFiller = encodeFillerData(tokenAddr, outputAmount, other.address, b32(solver.address));
await expect(settler.connect(solver).fill(orderId, originData, wrongRecipFiller))
.to.be.revertedWithCustomError(settler, "OutputMismatch");
// Wrong token (delivering a different token than declared) must revert.
const OtherToken = await ethers.getContractFactory("MockERC20Lending");
const otherToken = await OtherToken.deploy("Wrapped Ether", "WETH", 18);
await otherToken.waitForDeployment();
await otherToken.mint(solver.address, outputAmount);
await otherToken.connect(solver).approve(await settler.getAddress(), outputAmount);
const wrongTokenFiller = encodeFillerData(await otherToken.getAddress(), outputAmount, recipient.address, b32(solver.address));
await expect(settler.connect(solver).fill(orderId, originData, wrongTokenFiller))
.to.be.revertedWithCustomError(settler, "OutputMismatch");
// Order stayed unfilled through all three rejected attempts.
expect(await settler.isFilled(orderId)).to.equal(false);
});
it("zero-recipient reverts (ZeroRecipient)", async function () {
const { solver, token, settler } = await deployFixture();
const orderId = ethers.id("order-zero-recip");
const outputAmount = 100n * ONE;
const tokenAddr = await token.getAddress();
// Declared recipient is zero.
const originData = encodeOriginData(orderId, tokenAddr, outputAmount, ethers.ZeroHash);
const fillerData = encodeFillerData(tokenAddr, outputAmount, ethers.ZeroAddress, b32(solver.address));
await expect(settler.connect(solver).fill(orderId, originData, fillerData))
.to.be.revertedWithCustomError(settler, "ZeroRecipient");
});
});