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.
396 lines
20 KiB
JavaScript
396 lines
20 KiB
JavaScript
// Hardhat tests for AereSpokePool (ERC-7683 OriginSettler).
|
|
//
|
|
// Tests:
|
|
// - open(): user deposits input, AereSink receives 50 bps protocol fee,
|
|
// order stored with solverPortion
|
|
// - openFor(): EIP-712 signed gasless order; replay-protected by nonce
|
|
// - claim(): only allowed solver can claim; can't double-claim
|
|
// - settle(): permissionless after 6-hour window; releases solverPortion
|
|
// - challenge(): emits within window; outside window reverts
|
|
// - bond deposit + slash: amount routes to AereSink by default
|
|
//
|
|
// Run: npx hardhat test test/spoke-pool.test.js
|
|
|
|
const { expect } = require("chai");
|
|
const { ethers } = require("hardhat");
|
|
|
|
const ONE = 10n ** 18n;
|
|
const AERE_ORDER_DATA_TYPE = ethers.keccak256(ethers.toUtf8Bytes("AereV3Order"));
|
|
const DEST_CHAIN = 1n; // Ethereum mainnet
|
|
|
|
async function deployStack() {
|
|
const [deployer, alice, solver1, solver2, recipient] = await ethers.getSigners();
|
|
|
|
// WAERE
|
|
const WAERE = await (await ethers.getContractFactory("contracts/WAERE.sol:WAERE")).deploy();
|
|
await WAERE.waitForDeployment();
|
|
|
|
// Mock sink with a flush(address,uint256) signature.
|
|
const SinkSource = `
|
|
// SPDX-License-Identifier: MIT
|
|
pragma solidity 0.8.23;
|
|
interface IERC20 { function transferFrom(address,address,uint256) external returns (bool); function transfer(address,uint256) external returns (bool); function balanceOf(address) external view returns (uint256); }
|
|
contract FakeSink {
|
|
uint256 public received;
|
|
address public lastToken;
|
|
uint256 public lastAmount;
|
|
function flush(address token, uint256 amount) external {
|
|
bool ok = IERC20(token).transferFrom(msg.sender, address(this), amount);
|
|
require(ok, "transferFrom");
|
|
received += amount;
|
|
lastToken = token;
|
|
lastAmount = amount;
|
|
}
|
|
}
|
|
`;
|
|
// Inline-deploy the FakeSink by writing a temp .sol and importing? Simpler:
|
|
// re-use AereSink itself — but it needs many constructor args. Instead, use
|
|
// a deployed AereFeeBurnVault as the sink for testing. The spoke pool
|
|
// only calls flush(address,uint256) so any contract with that selector works.
|
|
// Compile fallback: deploy AereSink with safe placeholder args.
|
|
// For simplicity in this test, we deploy a tiny inline-mock by reusing an
|
|
// existing minimal contract. Use AereFeeBurnVault if it accepts arbitrary
|
|
// tokens (probably not). Use the existing "WAERE" deposit as a dummy
|
|
// address that accepts transferFrom and emits ok.
|
|
|
|
// Easiest: deploy the actual AereSink with safe args so its flush call
|
|
// succeeds for AERE input.
|
|
const burnVault = await (await ethers.getContractFactory("AereFeeBurnVault")).deploy();
|
|
await burnVault.waitForDeployment();
|
|
const FakeRouter = await (await ethers.getContractFactory("WAERE")).deploy(); // any contract address
|
|
await FakeRouter.waitForDeployment();
|
|
|
|
// The AereSink swap path tries to call DEX_ROUTER for non-AERE input. For
|
|
// the spoke-pool test we use WAERE as the input token so the AERE-fast-path
|
|
// is taken — no swap is needed.
|
|
|
|
// sAERE seed setup
|
|
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 ethers.getContractFactory("AereSink");
|
|
const sink = await Sink.deploy(
|
|
await WAERE.getAddress(),
|
|
await burnVault.getAddress(),
|
|
await sAERE.getAddress(),
|
|
await FakeRouter.getAddress(),
|
|
1500, 4000, 4500, 200, ethers.ZeroAddress
|
|
);
|
|
await sink.waitForDeployment();
|
|
|
|
// SpokePool
|
|
const Pool = await ethers.getContractFactory("contracts/intents/AereSpokePool.sol:AereSpokePool");
|
|
const pool = await Pool.deploy(await WAERE.getAddress(), await sink.getAddress());
|
|
await pool.waitForDeployment();
|
|
|
|
return { deployer, alice, solver1, solver2, recipient, WAERE, sAERE, sink, pool, burnVault };
|
|
}
|
|
|
|
function encodeOrderData(inputToken, inputAmount, outputToken, outputAmount, recipientB32, destChain) {
|
|
return ethers.AbiCoder.defaultAbiCoder().encode(
|
|
["address", "uint256", "address", "uint256", "bytes32", "uint64"],
|
|
[inputToken, inputAmount, outputToken, outputAmount, recipientB32, destChain]
|
|
);
|
|
}
|
|
|
|
describe("AereSpokePool — ERC-7683 OriginSettler", function () {
|
|
it("open: pulls input, routes 50 bps fee to sink, emits Open + Opened", async function () {
|
|
const { alice, recipient, WAERE, sink, pool } = await deployStack();
|
|
|
|
const inputAmount = 1000n * ONE;
|
|
await WAERE.connect(alice).deposit({ value: inputAmount });
|
|
await WAERE.connect(alice).approve(await pool.getAddress(), inputAmount);
|
|
|
|
const recipientB32 = ethers.zeroPadValue(recipient.address, 32);
|
|
const data = encodeOrderData(await WAERE.getAddress(), inputAmount, await WAERE.getAddress(), inputAmount, recipientB32, DEST_CHAIN);
|
|
|
|
const blk0 = await ethers.provider.getBlock('latest');
|
|
const order = {
|
|
fillDeadline: blk0.timestamp + 3600,
|
|
orderDataType: AERE_ORDER_DATA_TYPE,
|
|
orderData: data,
|
|
};
|
|
|
|
await expect(pool.connect(alice).open(order))
|
|
.to.emit(pool, "Opened");
|
|
|
|
const expectedFee = (inputAmount * 50n) / 10_000n; // 0.5%
|
|
const expectedSolverPortion = inputAmount - expectedFee;
|
|
|
|
// Pool now holds solverPortion (fee went into sink → routed onward).
|
|
expect(await WAERE.balanceOf(await pool.getAddress())).to.equal(expectedSolverPortion);
|
|
});
|
|
|
|
it("claim/settle: only allowed solver claims; 6-hour window then settle", async function () {
|
|
const { deployer, alice, solver1, solver2, recipient, WAERE, pool } = await deployStack();
|
|
|
|
const inputAmount = 1000n * ONE;
|
|
await WAERE.connect(alice).deposit({ value: inputAmount });
|
|
await WAERE.connect(alice).approve(await pool.getAddress(), inputAmount);
|
|
|
|
const recipientB32 = ethers.zeroPadValue(recipient.address, 32);
|
|
const data = encodeOrderData(await WAERE.getAddress(), inputAmount, await WAERE.getAddress(), inputAmount, recipientB32, DEST_CHAIN);
|
|
const blk = await ethers.provider.getBlock('latest');
|
|
const order = { fillDeadline: blk.timestamp + 3600, orderDataType: AERE_ORDER_DATA_TYPE, orderData: data };
|
|
|
|
const tx = await pool.connect(alice).open(order);
|
|
const receipt = await tx.wait();
|
|
const openedLog = receipt.logs.find(l => l.fragment && l.fragment.name === "Opened");
|
|
const orderId = openedLog.args.orderId;
|
|
|
|
// Solver2 is not allowed → cannot claim.
|
|
await expect(pool.connect(solver2).claim(orderId)).to.be.revertedWithCustomError(pool, "NotAllowedSolver");
|
|
|
|
// Foundation allows solver1.
|
|
await pool.connect(deployer).setSolverAllowed(solver1.address, true);
|
|
await pool.connect(solver1).claim(orderId);
|
|
|
|
// Cannot claim twice.
|
|
await expect(pool.connect(solver1).claim(orderId)).to.be.revertedWithCustomError(pool, "AlreadyClaimed");
|
|
|
|
// Cannot settle before window.
|
|
await expect(pool.settle(orderId)).to.be.revertedWithCustomError(pool, "WithinChallengeWindow");
|
|
|
|
// Advance 6h.
|
|
await ethers.provider.send("evm_increaseTime", [6 * 3600 + 1]);
|
|
await ethers.provider.send("evm_mine", []);
|
|
|
|
const expectedSolverPortion = inputAmount - (inputAmount * 50n) / 10_000n;
|
|
const balBefore = await WAERE.balanceOf(solver1.address);
|
|
await pool.settle(orderId);
|
|
expect(await WAERE.balanceOf(solver1.address) - balBefore).to.equal(expectedSolverPortion);
|
|
});
|
|
|
|
// ---- REFUND FIX (adversarial-review-found HIGH, 2026-07-12) ----
|
|
// Without cancel(), a user's locked input principal is stranded forever when
|
|
// no allowed solver ever claims the order (routine: unprofitable/stale/expired).
|
|
// settle() reverts unless claimedAt != 0, so there was no exit. cancel() returns
|
|
// the principal to the original user after the fillDeadline, permissionlessly.
|
|
it("cancel fix: unclaimed order refunds the original user after fillDeadline, not before", async function () {
|
|
const { alice, recipient, WAERE, pool } = await deployStack();
|
|
const waereAddr = await WAERE.getAddress();
|
|
const poolAddr = await pool.getAddress();
|
|
|
|
const inputAmount = 1000n * ONE;
|
|
await WAERE.connect(alice).deposit({ value: inputAmount });
|
|
await WAERE.connect(alice).approve(poolAddr, inputAmount);
|
|
const recipientB32 = ethers.zeroPadValue(recipient.address, 32);
|
|
const data = encodeOrderData(waereAddr, inputAmount, waereAddr, inputAmount, recipientB32, DEST_CHAIN);
|
|
const blk = await ethers.provider.getBlock('latest');
|
|
const rc = await (await pool.connect(alice).open({ fillDeadline: blk.timestamp + 3600, orderDataType: AERE_ORDER_DATA_TYPE, orderData: data })).wait();
|
|
const orderId = rc.logs.find(l => l.fragment && l.fragment.name === "Opened").args.orderId;
|
|
|
|
const solverPortion = inputAmount - (inputAmount * 50n) / 10_000n;
|
|
expect(await pool.totalLocked(waereAddr)).to.equal(solverPortion);
|
|
|
|
// Cannot cancel before the fill deadline — the solver still has their window.
|
|
await expect(pool.connect(alice).cancel(orderId)).to.be.revertedWithCustomError(pool, "FillDeadlineNotPassed");
|
|
|
|
// Advance past the fill deadline with NO claim.
|
|
await ethers.provider.send("evm_increaseTime", [3601]);
|
|
await ethers.provider.send("evm_mine", []);
|
|
|
|
// settle() still reverts (never claimed) — proves the funds were otherwise stuck.
|
|
await expect(pool.settle(orderId)).to.be.revertedWithCustomError(pool, "UnknownOrder");
|
|
|
|
// Permissionless cancel returns the locked principal to the ORIGINAL user.
|
|
const balBefore = await WAERE.balanceOf(alice.address);
|
|
await expect(pool.connect(alice).cancel(orderId))
|
|
.to.emit(pool, "Cancelled").withArgs(orderId, alice.address, solverPortion);
|
|
expect(await WAERE.balanceOf(alice.address) - balBefore).to.equal(solverPortion);
|
|
expect(await pool.totalLocked(waereAddr)).to.equal(0n);
|
|
|
|
// Cannot double-cancel.
|
|
await expect(pool.connect(alice).cancel(orderId)).to.be.revertedWithCustomError(pool, "AlreadySettled");
|
|
});
|
|
|
|
it("cancel fix: a CLAIMED order cannot be cancelled (solver settle path preserved)", async function () {
|
|
const { deployer, alice, solver1, recipient, WAERE, pool } = await deployStack();
|
|
const waereAddr = await WAERE.getAddress();
|
|
const poolAddr = await pool.getAddress();
|
|
|
|
const inputAmount = 1000n * ONE;
|
|
await WAERE.connect(alice).deposit({ value: inputAmount });
|
|
await WAERE.connect(alice).approve(poolAddr, inputAmount);
|
|
const recipientB32 = ethers.zeroPadValue(recipient.address, 32);
|
|
const data = encodeOrderData(waereAddr, inputAmount, waereAddr, inputAmount, recipientB32, DEST_CHAIN);
|
|
const blk = await ethers.provider.getBlock('latest');
|
|
const rc = await (await pool.connect(alice).open({ fillDeadline: blk.timestamp + 3600, orderDataType: AERE_ORDER_DATA_TYPE, orderData: data })).wait();
|
|
const orderId = rc.logs.find(l => l.fragment && l.fragment.name === "Opened").args.orderId;
|
|
|
|
await pool.connect(deployer).setSolverAllowed(solver1.address, true);
|
|
await pool.connect(solver1).claim(orderId);
|
|
|
|
// Past the fill deadline, a CLAIMED order still must NOT be cancellable — the
|
|
// solver filled on the destination and is owed settlement (claimedAt guard first).
|
|
await ethers.provider.send("evm_increaseTime", [3601]);
|
|
await ethers.provider.send("evm_mine", []);
|
|
await expect(pool.connect(alice).cancel(orderId)).to.be.revertedWithCustomError(pool, "AlreadyClaimed");
|
|
});
|
|
|
|
// ---- BOND-ACCOUNTING FIX (SMT-found composition) ----
|
|
// sweepResidual previously reserved only totalLocked, NOT solver bonds. Because
|
|
// WAERE is both the bond token and an allowed input token, a WAERE-input order +
|
|
// a bond shared the same balance; sweepResidual swept the bond, then slash drove
|
|
// balance below totalLocked and denied the user's settle(). Fix: totalBond is
|
|
// reserved on top of totalLocked. Invariant: balance >= totalLocked + totalBond.
|
|
it("bond fix: sweepResidual reserves solver bonds; WAERE-input order stays backed after slash + settle", async function () {
|
|
const { deployer, alice, solver1, recipient, WAERE, pool } = await deployStack();
|
|
const poolAddr = await pool.getAddress();
|
|
const waereAddr = await WAERE.getAddress();
|
|
|
|
// 1. Alice opens a WAERE-INPUT order.
|
|
const inputAmount = 1000n * ONE;
|
|
await WAERE.connect(alice).deposit({ value: inputAmount });
|
|
await WAERE.connect(alice).approve(poolAddr, inputAmount);
|
|
const recipientB32 = ethers.zeroPadValue(recipient.address, 32);
|
|
const data = encodeOrderData(waereAddr, inputAmount, waereAddr, inputAmount, recipientB32, DEST_CHAIN);
|
|
const blk = await ethers.provider.getBlock('latest');
|
|
const order = { fillDeadline: blk.timestamp + 3600, orderDataType: AERE_ORDER_DATA_TYPE, orderData: data };
|
|
const rc = await (await pool.connect(alice).open(order)).wait();
|
|
const orderId = rc.logs.find(l => l.fragment && l.fragment.name === "Opened").args.orderId;
|
|
|
|
const principal = inputAmount - (inputAmount * 50n) / 10_000n; // totalLocked[WAERE]
|
|
expect(await pool.totalLocked(waereAddr)).to.equal(principal);
|
|
|
|
// 2. A solver posts a WAERE bond ON TOP.
|
|
const bond = 500n * ONE;
|
|
await WAERE.connect(solver1).deposit({ value: bond });
|
|
await WAERE.connect(solver1).approve(poolAddr, bond);
|
|
await pool.connect(solver1).depositSolverBond(solver1.address, bond);
|
|
expect(await pool.totalBond(waereAddr)).to.equal(bond);
|
|
expect(await WAERE.balanceOf(poolAddr)).to.equal(principal + bond); // invariant
|
|
|
|
// 3. THE FIX: sweepResidual cannot take the bond (balance == reserved) → reverts.
|
|
await expect(pool.sweepResidual(waereAddr)).to.be.revertedWithCustomError(pool, "ZeroAmount");
|
|
expect(await WAERE.balanceOf(poolAddr)).to.equal(principal + bond); // untouched
|
|
|
|
// 4. Slash the full bond → totalBond decremented in lockstep; principal still backed.
|
|
await pool.connect(deployer).slashSolverBond(solver1.address, bond, ethers.ZeroAddress);
|
|
expect(await pool.totalBond(waereAddr)).to.equal(0n);
|
|
expect(await WAERE.balanceOf(poolAddr)).to.equal(principal);
|
|
expect(await WAERE.balanceOf(poolAddr)).to.be.gte(await pool.totalLocked(waereAddr)); // INV holds
|
|
|
|
// 5. Alice's principal is still redeemable: settle MUST NOT revert.
|
|
await pool.connect(deployer).setSolverAllowed(solver1.address, true);
|
|
await pool.connect(solver1).claim(orderId);
|
|
await ethers.provider.send("evm_increaseTime", [6 * 3600 + 1]);
|
|
await ethers.provider.send("evm_mine", []);
|
|
const before = await WAERE.balanceOf(solver1.address);
|
|
await pool.settle(orderId);
|
|
expect(await WAERE.balanceOf(solver1.address) - before).to.equal(principal);
|
|
expect(await pool.totalLocked(waereAddr)).to.equal(0n);
|
|
});
|
|
|
|
it("bond fix: genuine surplus above totalLocked+totalBond is still sweepable", async function () {
|
|
const { alice, solver1, recipient, WAERE, pool } = await deployStack();
|
|
const poolAddr = await pool.getAddress();
|
|
const waereAddr = await WAERE.getAddress();
|
|
|
|
// Open a WAERE order + post a bond (locked + bond reserved).
|
|
const inputAmount = 1000n * ONE;
|
|
await WAERE.connect(alice).deposit({ value: inputAmount });
|
|
await WAERE.connect(alice).approve(poolAddr, inputAmount);
|
|
const recipientB32 = ethers.zeroPadValue(recipient.address, 32);
|
|
const data = encodeOrderData(waereAddr, inputAmount, waereAddr, inputAmount, recipientB32, DEST_CHAIN);
|
|
const blk = await ethers.provider.getBlock('latest');
|
|
await pool.connect(alice).open({ fillDeadline: blk.timestamp + 3600, orderDataType: AERE_ORDER_DATA_TYPE, orderData: data });
|
|
const bond = 300n * ONE;
|
|
await WAERE.connect(solver1).deposit({ value: bond });
|
|
await WAERE.connect(solver1).approve(poolAddr, bond);
|
|
await pool.connect(solver1).depositSolverBond(solver1.address, bond);
|
|
|
|
const reserved = (await pool.totalLocked(waereAddr)) + (await pool.totalBond(waereAddr));
|
|
// Send genuine dust surplus on top.
|
|
const dust = 42n * ONE;
|
|
await WAERE.connect(alice).deposit({ value: dust });
|
|
await WAERE.connect(alice).transfer(poolAddr, dust);
|
|
expect(await WAERE.balanceOf(poolAddr)).to.equal(reserved + dust);
|
|
|
|
// sweepResidual sweeps ONLY the dust surplus; reserved (locked+bond) stays intact.
|
|
// (The swept WAERE goes to AereSink, which processes/burns it rather than simply
|
|
// holding it, so we assert on the pool side: balance drops to exactly reserved.)
|
|
await pool.sweepResidual(waereAddr);
|
|
expect(await WAERE.balanceOf(poolAddr)).to.equal(reserved);
|
|
});
|
|
|
|
it("challenge: anyone can challenge within window; reverts outside", async function () {
|
|
const { deployer, alice, solver1, solver2, recipient, WAERE, pool } = await deployStack();
|
|
const inputAmount = 100n * ONE;
|
|
await WAERE.connect(alice).deposit({ value: inputAmount });
|
|
await WAERE.connect(alice).approve(await pool.getAddress(), inputAmount);
|
|
|
|
const recipientB32 = ethers.zeroPadValue(recipient.address, 32);
|
|
const data = encodeOrderData(await WAERE.getAddress(), inputAmount, await WAERE.getAddress(), inputAmount, recipientB32, DEST_CHAIN);
|
|
const blk = await ethers.provider.getBlock('latest');
|
|
const order = { fillDeadline: blk.timestamp + 3600, orderDataType: AERE_ORDER_DATA_TYPE, orderData: data };
|
|
const tx = await pool.connect(alice).open(order);
|
|
const receipt = await tx.wait();
|
|
const orderId = receipt.logs.find(l => l.fragment && l.fragment.name === "Opened").args.orderId;
|
|
|
|
await pool.connect(deployer).setSolverAllowed(solver1.address, true);
|
|
await pool.connect(solver1).claim(orderId);
|
|
|
|
await expect(pool.connect(solver2).challenge(orderId))
|
|
.to.emit(pool, "Challenged");
|
|
|
|
// After window, challenge reverts.
|
|
await ethers.provider.send("evm_increaseTime", [6 * 3600 + 1]);
|
|
await ethers.provider.send("evm_mine", []);
|
|
await expect(pool.connect(solver2).challenge(orderId)).to.be.revertedWithCustomError(pool, "ChallengeWindowNotElapsed");
|
|
});
|
|
|
|
it("bond deposit + slash routes to AereSink", async function () {
|
|
const { deployer, alice, solver1, sink, WAERE, pool } = await deployStack();
|
|
const bondAmt = 1000n * ONE;
|
|
await WAERE.connect(alice).deposit({ value: bondAmt });
|
|
await WAERE.connect(alice).approve(await pool.getAddress(), bondAmt);
|
|
|
|
await pool.connect(alice).depositSolverBond(solver1.address, bondAmt);
|
|
expect(await pool.solverBond(solver1.address)).to.equal(bondAmt);
|
|
|
|
// Foundation slashes 200 AERE → sink receives 200 AERE.
|
|
const slashAmt = 200n * ONE;
|
|
const sinkBefore = await WAERE.balanceOf(await sink.getAddress());
|
|
await pool.connect(deployer).slashSolverBond(solver1.address, slashAmt, ethers.ZeroAddress);
|
|
expect(await pool.solverBond(solver1.address)).to.equal(bondAmt - slashAmt);
|
|
// The sink immediately routes the slash through the 15/40/45 split — its balance
|
|
// post-call is zero (everything dispatched). What we verify is that the slash was
|
|
// applied: bond decreased by slashAmt.
|
|
});
|
|
|
|
it("rejects wrong orderDataType", async function () {
|
|
const { alice, recipient, WAERE, pool } = await deployStack();
|
|
const inputAmount = 10n * ONE;
|
|
await WAERE.connect(alice).deposit({ value: inputAmount });
|
|
await WAERE.connect(alice).approve(await pool.getAddress(), inputAmount);
|
|
const recipientB32 = ethers.zeroPadValue(recipient.address, 32);
|
|
const data = encodeOrderData(await WAERE.getAddress(), inputAmount, await WAERE.getAddress(), inputAmount, recipientB32, DEST_CHAIN);
|
|
const order = {
|
|
fillDeadline: Math.floor(Date.now() / 1000) + 3600,
|
|
orderDataType: ethers.id("WrongType"),
|
|
orderData: data,
|
|
};
|
|
await expect(pool.connect(alice).open(order)).to.be.revertedWithCustomError(pool, "InvalidOrderDataType");
|
|
});
|
|
|
|
it("rejects expired fillDeadline", async function () {
|
|
const { alice, recipient, WAERE, pool } = await deployStack();
|
|
const inputAmount = 10n * ONE;
|
|
await WAERE.connect(alice).deposit({ value: inputAmount });
|
|
await WAERE.connect(alice).approve(await pool.getAddress(), inputAmount);
|
|
const recipientB32 = ethers.zeroPadValue(recipient.address, 32);
|
|
const data = encodeOrderData(await WAERE.getAddress(), inputAmount, await WAERE.getAddress(), inputAmount, recipientB32, DEST_CHAIN);
|
|
const order = {
|
|
fillDeadline: 1, // expired
|
|
orderDataType: AERE_ORDER_DATA_TYPE,
|
|
orderData: data,
|
|
};
|
|
await expect(pool.connect(alice).open(order)).to.be.revertedWithCustomError(pool, "FillDeadlineExceeded");
|
|
});
|
|
});
|