aere-contracts/test/foundation-aggregator.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

116 lines
4.9 KiB
JavaScript

// AereFoundationAggregator tests.
// Chainlink-compatible single-asset feed with Foundation push updates.
const { expect } = require("chai");
const { ethers } = require("hardhat");
async function deploy(opts = {}) {
const desc = opts.desc ?? "BUIDL.e/USD";
const jumpBps = opts.jumpBps ?? 500; // 5% per-update max
const minHeartbeat = opts.minHeartbeat ?? 300; // 5 min
const initialAnswer = opts.initialAnswer ?? 1_00000000n; // $1, 8 decimals
const Agg = await ethers.getContractFactory("AereFoundationAggregator");
const agg = await Agg.deploy(desc, jumpBps, minHeartbeat, initialAnswer);
await agg.waitForDeployment();
return agg;
}
describe("AereFoundationAggregator", function () {
it("constructor sets immutable params + initial round", async function () {
const agg = await deploy();
expect(await agg.description()).to.equal("BUIDL.e/USD");
expect(await agg.decimals()).to.equal(8);
expect(await agg.maxJumpBps()).to.equal(500);
expect(await agg.minHeartbeat()).to.equal(300);
expect(await agg.latestRoundId()).to.equal(1n);
expect(await agg.latestAnswer()).to.equal(1_00000000n);
});
it("latestRoundData returns Chainlink-compatible tuple", async function () {
const agg = await deploy();
const data = await agg.latestRoundData();
expect(data[0]).to.equal(1n); // roundId
expect(data[1]).to.equal(1_00000000n); // answer
expect(data[4]).to.equal(1n); // answeredInRound == roundId
});
it("Foundation can push update after heartbeat", async function () {
const agg = await deploy();
await ethers.provider.send("evm_increaseTime", [301]);
await ethers.provider.send("evm_mine", []);
await agg.updateAnswer(1_02000000n); // $1.02 — 2% jump, within 5% cap
expect(await agg.latestRoundId()).to.equal(2n);
expect(await agg.latestAnswer()).to.equal(1_02000000n);
});
it("rejects update before heartbeat elapsed", async function () {
const agg = await deploy();
await expect(agg.updateAnswer(1_01000000n))
.to.be.revertedWithCustomError(agg, "HeartbeatTooSoon");
});
it("rejects update that exceeds max jump cap", async function () {
const agg = await deploy({ jumpBps: 200 }); // 2% cap
await ethers.provider.send("evm_increaseTime", [400]);
await ethers.provider.send("evm_mine", []);
await expect(agg.updateAnswer(1_05000000n)) // 5% > 2% cap
.to.be.revertedWithCustomError(agg, "JumpExceeded");
});
it("rejects zero or negative prices", async function () {
const agg = await deploy();
await ethers.provider.send("evm_increaseTime", [400]);
await ethers.provider.send("evm_mine", []);
await expect(agg.updateAnswer(0n)).to.be.revertedWithCustomError(agg, "InvalidPrice");
});
it("non-owner cannot update", async function () {
const agg = await deploy();
const [, alice] = await ethers.getSigners();
await expect(agg.connect(alice).updateAnswer(1_01000000n))
.to.be.revertedWith("Ownable: caller is not the owner");
});
it("pause blocks new updates; freezes price (last good value persists)", async function () {
const agg = await deploy();
await agg.setPaused(true);
await ethers.provider.send("evm_increaseTime", [400]);
await ethers.provider.send("evm_mine", []);
await expect(agg.updateAnswer(1_01000000n))
.to.be.revertedWithCustomError(agg, "AggregatorPaused");
// latestRoundData still returns the last good price.
const d = await agg.latestRoundData();
expect(d[1]).to.equal(1_00000000n);
});
it("getRoundData returns historical values", async function () {
const agg = await deploy();
await ethers.provider.send("evm_increaseTime", [400]);
await ethers.provider.send("evm_mine", []);
await agg.updateAnswer(1_02000000n);
const r1 = await agg.getRoundData(1);
expect(r1[1]).to.equal(1_00000000n);
const r2 = await agg.getRoundData(2);
expect(r2[1]).to.equal(1_02000000n);
await expect(agg.getRoundData(99)).to.be.revertedWithCustomError(agg, "UnknownRound");
});
it("works as Chainlink feed for AereLendingOracle", async function () {
// End-to-end: configure as the source for an asset in the lending oracle.
const agg = await deploy();
const Oracle = await ethers.getContractFactory("AereLendingOracle");
const oracle = await Oracle.deploy();
await oracle.waitForDeployment();
const ERC20 = await ethers.getContractFactory("MockERC20Lending");
const buidl = await ERC20.deploy("BUIDL.e", "BUIDL.e", 18);
await buidl.waitForDeployment();
// FeedType.ChainlinkLike = 0
await oracle.configureFeed(await buidl.getAddress(), 0, await agg.getAddress(), ethers.ZeroHash, 86400, 200, 8);
// configureFeed auto-pokes → lastGoodPrice set.
const feed = await oracle.feeds(await buidl.getAddress());
expect(feed.lastGoodPrice).to.equal(10n ** 18n); // 1e18 normalised from 1e8
expect(await oracle.getPrice(await buidl.getAddress())).to.equal(10n ** 18n);
});
});