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.
115 lines
5.4 KiB
JavaScript
115 lines
5.4 KiB
JavaScript
// Round 3 fix: AereSink oracle-based amountOutMin computation.
|
|
//
|
|
// Sandwich-resistance: amountOutMin must be derived from an EXTERNAL oracle
|
|
// (price feed independent of DEX pool state), not from DEX reserves.
|
|
|
|
const { expect } = require("chai");
|
|
const { ethers } = require("hardhat");
|
|
|
|
const ONE = 10n ** 18n;
|
|
|
|
describe("AereSink — oracle floor (Round 3 H1 fix)", function () {
|
|
|
|
async function deployWithOracle(oracleAddr) {
|
|
const [deployer] = await ethers.getSigners();
|
|
const WAERE = await (await ethers.getContractFactory("contracts/WAERE.sol:WAERE")).deploy();
|
|
await WAERE.waitForDeployment();
|
|
const FeeToken = await (await ethers.getContractFactory("contracts/WAERE.sol:WAERE")).deploy();
|
|
await FeeToken.waitForDeployment();
|
|
|
|
const burnVault = await (await ethers.getContractFactory("AereFeeBurnVault")).deploy();
|
|
await burnVault.waitForDeployment();
|
|
|
|
const router = await (await ethers.getContractFactory("MockSinkRouter")).deploy();
|
|
await router.waitForDeployment();
|
|
// Router will return AERE (the first WAERE).
|
|
await router.setOutToken(await WAERE.getAddress());
|
|
|
|
const sink = await (await ethers.getContractFactory("AereSink")).deploy(
|
|
await WAERE.getAddress(), // AERE
|
|
await burnVault.getAddress(), // BURN_VAULT
|
|
await WAERE.getAddress(), // sAERE stub (just receives)
|
|
await router.getAddress(), // DEX_ROUTER
|
|
1500, 4000, 4500, // burn/buyback/staker bps
|
|
500, // 5% slippage
|
|
oracleAddr, // ORACLE
|
|
);
|
|
await sink.waitForDeployment();
|
|
|
|
return { deployer, WAERE, FeeToken, burnVault, router, sink };
|
|
}
|
|
|
|
it("ORACLE = address(0) → amountOutMin = 1 (legacy behavior, sandwich-vulnerable)", async function () {
|
|
const { router, sink, FeeToken, deployer, WAERE } = await deployWithOracle(ethers.ZeroAddress);
|
|
// Fund router with AERE so it can pay the swap output.
|
|
await deployer.sendTransaction({ to: await WAERE.getAddress(), value: 100n * ONE });
|
|
await WAERE.transfer(await router.getAddress(), 100n * ONE);
|
|
|
|
// Get fee tokens via WAERE wrap, then approve sink.
|
|
await deployer.sendTransaction({ to: await FeeToken.getAddress(), value: 10n * ONE });
|
|
await FeeToken.approve(await sink.getAddress(), 10n * ONE);
|
|
|
|
await sink.flush(await FeeToken.getAddress(), 10n * ONE);
|
|
expect(await router.lastAmountOutMin()).to.equal(1n);
|
|
});
|
|
|
|
it("ORACLE set with equal prices → amountOutMin reflects 5% slippage tolerance", async function () {
|
|
const oracle = await (await ethers.getContractFactory("MockPriceOracle")).deploy();
|
|
await oracle.waitForDeployment();
|
|
const { router, sink, FeeToken, deployer, WAERE } = await deployWithOracle(await oracle.getAddress());
|
|
|
|
// Set both at $1 (1e18).
|
|
await oracle.setPrice(await FeeToken.getAddress(), ONE);
|
|
await oracle.setPrice(await WAERE.getAddress(), ONE);
|
|
|
|
// Fund router with AERE.
|
|
await deployer.sendTransaction({ to: await WAERE.getAddress(), value: 100n * ONE });
|
|
await WAERE.transfer(await router.getAddress(), 100n * ONE);
|
|
|
|
await deployer.sendTransaction({ to: await FeeToken.getAddress(), value: 10n * ONE });
|
|
await FeeToken.approve(await sink.getAddress(), 10n * ONE);
|
|
|
|
await sink.flush(await FeeToken.getAddress(), 10n * ONE);
|
|
// Two swaps inside flush: burn+buyback bundle (55% of 10 = 5.5 AERE)
|
|
// and staker (45% = 4.5 AERE). Last call captured by mock:
|
|
// Expected on staker swap (4.5 AERE): floor = 4.5 * 95% = 4.275 AERE.
|
|
const lastFloor = await router.lastAmountOutMin();
|
|
const expectedStaker = (45n * 10n * ONE) / 100n; // 4.5e18
|
|
const minStaker = expectedStaker * 9500n / 10_000n; // 4.275e18
|
|
expect(lastFloor).to.equal(minStaker);
|
|
});
|
|
|
|
it("ORACLE returns zero for one asset → fallback amountOutMin = 1", async function () {
|
|
const oracle = await (await ethers.getContractFactory("MockPriceOracle")).deploy();
|
|
await oracle.waitForDeployment();
|
|
const { router, sink, FeeToken, deployer, WAERE } = await deployWithOracle(await oracle.getAddress());
|
|
|
|
// FeeToken priced; AERE not (returns 0).
|
|
await oracle.setPrice(await FeeToken.getAddress(), ONE);
|
|
// AERE intentionally not set.
|
|
|
|
await deployer.sendTransaction({ to: await WAERE.getAddress(), value: 100n * ONE });
|
|
await WAERE.transfer(await router.getAddress(), 100n * ONE);
|
|
await deployer.sendTransaction({ to: await FeeToken.getAddress(), value: 10n * ONE });
|
|
await FeeToken.approve(await sink.getAddress(), 10n * ONE);
|
|
|
|
await sink.flush(await FeeToken.getAddress(), 10n * ONE);
|
|
expect(await router.lastAmountOutMin()).to.equal(1n);
|
|
});
|
|
|
|
it("ORACLE reverts → fallback amountOutMin = 1", async function () {
|
|
const oracle = await (await ethers.getContractFactory("MockPriceOracle")).deploy();
|
|
await oracle.waitForDeployment();
|
|
const { router, sink, FeeToken, deployer, WAERE } = await deployWithOracle(await oracle.getAddress());
|
|
await oracle.setRevert(true);
|
|
|
|
await deployer.sendTransaction({ to: await WAERE.getAddress(), value: 100n * ONE });
|
|
await WAERE.transfer(await router.getAddress(), 100n * ONE);
|
|
await deployer.sendTransaction({ to: await FeeToken.getAddress(), value: 10n * ONE });
|
|
await FeeToken.approve(await sink.getAddress(), 10n * ONE);
|
|
|
|
await sink.flush(await FeeToken.getAddress(), 10n * ONE);
|
|
expect(await router.lastAmountOutMin()).to.equal(1n);
|
|
});
|
|
});
|