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.
116 lines
4.9 KiB
JavaScript
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);
|
|
});
|
|
});
|