// 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); }); });