import { expect } from "chai"; import { ethers } from "hardhat"; import type { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers"; import type { WhitelistOracle, SimpleOracle } from "../typechain-types"; describe("Checkpoint1", function () { before(async () => { await ethers.provider.send("evm_setAutomine", [true]); await ethers.provider.send("evm_setIntervalMining", [0]); }); let whitelistOracle: WhitelistOracle; let owner: HardhatEthersSigner, addr1: HardhatEthersSigner, addr2: HardhatEthersSigner, addr3: HardhatEthersSigner, addr4: HardhatEthersSigner; const contractAddress = process.env.CONTRACT_ADDRESS; if (contractAddress) { // If env variable is set then skip this test file (for the auto-grader) return true; } beforeEach(async function () { [owner, addr1, addr2, addr3, addr4] = await ethers.getSigners(); const WhitelistOracleFactory = await ethers.getContractFactory("WhitelistOracle"); whitelistOracle = await WhitelistOracleFactory.deploy(); }); it("Should deploy and set owner", async function () { expect(await whitelistOracle.owner()).to.equal(owner.address); }); it("Should allow adding oracles and deploy SimpleOracle contracts", async function () { await whitelistOracle.addOracle(addr1.address); const oracleAddress = await whitelistOracle.oracles(0); expect(oracleAddress).to.not.equal(ethers.ZeroAddress); // Check that the oracle is a SimpleOracle contract const SimpleOracleFactory = await ethers.getContractFactory("SimpleOracle"); const oracle = SimpleOracleFactory.attach(oracleAddress) as SimpleOracle; expect(await oracle.owner()).to.equal(addr1.address); }); it("Should allow removing oracles by index", async function () { await whitelistOracle.addOracle(addr1.address); await whitelistOracle.addOracle(addr2.address); const oracle1Address = await whitelistOracle.oracles(0); await whitelistOracle.removeOracle(0); // After removal, the oracle at index 0 should be different (swapped from end) const newOracle0Address = await whitelistOracle.oracles(0); expect(newOracle0Address).to.not.equal(oracle1Address); // Should only have one oracle left await expect(whitelistOracle.oracles(1)).to.be.reverted; }); it("Should emit OracleAdded event when an oracle is added", async function () { const tx = await whitelistOracle.addOracle(addr1.address); await tx.wait(); const oracleAddress = await whitelistOracle.oracles(0); expect(tx).to.emit(whitelistOracle, "OracleAdded").withArgs(oracleAddress, addr1.address); }); it("Should emit OracleRemoved event when an oracle is removed", async function () { await whitelistOracle.addOracle(addr1.address); const oracleAddress = await whitelistOracle.oracles(0); await expect(whitelistOracle.removeOracle(0)).to.emit(whitelistOracle, "OracleRemoved").withArgs(oracleAddress); }); it("Should revert with IndexOutOfBounds when trying to remove non-existent oracle", async function () { await expect(whitelistOracle.removeOracle(0)).to.be.revertedWithCustomError(whitelistOracle, "IndexOutOfBounds"); await whitelistOracle.addOracle(addr1.address); await expect(whitelistOracle.removeOracle(1)).to.be.revertedWithCustomError(whitelistOracle, "IndexOutOfBounds"); await whitelistOracle.removeOracle(0); await expect(whitelistOracle.removeOracle(0)).to.be.revertedWithCustomError(whitelistOracle, "IndexOutOfBounds"); }); it("Should revert with NoOraclesAvailable when getPrice is called with no oracles", async function () { await expect(whitelistOracle.getPrice()).to.be.revertedWithCustomError(whitelistOracle, "NoOraclesAvailable"); }); it("Should return correct price with one oracle", async function () { await whitelistOracle.addOracle(addr1.address); const oracleAddress = await whitelistOracle.oracles(0); const SimpleOracleFactory = await ethers.getContractFactory("SimpleOracle"); const oracle = SimpleOracleFactory.attach(oracleAddress) as SimpleOracle; await oracle.setPrice(1000n); const price = await whitelistOracle.getPrice(); expect(price).to.equal(1000n); }); it("Should return correct median price with odd number of oracles", async function () { await whitelistOracle.addOracle(addr1.address); await whitelistOracle.addOracle(addr2.address); await whitelistOracle.addOracle(addr3.address); const SimpleOracleFactory = await ethers.getContractFactory("SimpleOracle"); const oracle1 = SimpleOracleFactory.attach(await whitelistOracle.oracles(0)) as SimpleOracle; const oracle2 = SimpleOracleFactory.attach(await whitelistOracle.oracles(1)) as SimpleOracle; const oracle3 = SimpleOracleFactory.attach(await whitelistOracle.oracles(2)) as SimpleOracle; await oracle1.setPrice(1000n); await oracle2.setPrice(3000n); await oracle3.setPrice(2000n); const medianPrice = await whitelistOracle.getPrice(); expect(medianPrice).to.equal(2000n); }); it("Should return correct median price with even number of oracles", async function () { await whitelistOracle.addOracle(addr1.address); await whitelistOracle.addOracle(addr2.address); await whitelistOracle.addOracle(addr3.address); await whitelistOracle.addOracle(addr4.address); const SimpleOracleFactory = await ethers.getContractFactory("SimpleOracle"); const oracle1 = SimpleOracleFactory.attach(await whitelistOracle.oracles(0)) as SimpleOracle; const oracle2 = SimpleOracleFactory.attach(await whitelistOracle.oracles(1)) as SimpleOracle; const oracle3 = SimpleOracleFactory.attach(await whitelistOracle.oracles(2)) as SimpleOracle; const oracle4 = SimpleOracleFactory.attach(await whitelistOracle.oracles(3)) as SimpleOracle; await oracle1.setPrice(1000n); await oracle2.setPrice(3000n); await oracle3.setPrice(2000n); await oracle4.setPrice(4000n); const medianPrice = await whitelistOracle.getPrice(); expect(medianPrice).to.equal(2500n); }); it("Should exclude price reports older than 24 seconds from median calculation", async function () { await whitelistOracle.addOracle(addr1.address); await whitelistOracle.addOracle(addr2.address); await whitelistOracle.addOracle(addr3.address); const SimpleOracleFactory = await ethers.getContractFactory("SimpleOracle"); const oracle1 = SimpleOracleFactory.attach(await whitelistOracle.oracles(0)) as SimpleOracle; const oracle2 = SimpleOracleFactory.attach(await whitelistOracle.oracles(1)) as SimpleOracle; const oracle3 = SimpleOracleFactory.attach(await whitelistOracle.oracles(2)) as SimpleOracle; await oracle1.setPrice(1000n); await oracle2.setPrice(2000n); await oracle3.setPrice(3000n); let medianPrice = await whitelistOracle.getPrice(); expect(medianPrice).to.equal(2000n); // Advance time by 25 seconds (more than STALE_DATA_WINDOW of 24 seconds) await ethers.provider.send("evm_increaseTime", [25]); await ethers.provider.send("evm_mine"); // Set new prices for only two oracles (the old prices should be stale) await oracle1.setPrice(5000n); await oracle2.setPrice(3000n); // Should only use the two fresh prices: median of [5000, 3000] = 4000 medianPrice = await whitelistOracle.getPrice(); expect(medianPrice).to.equal(4000n); }); it("Should return empty array when no oracles are active", async function () { const activeNodes = await whitelistOracle.getActiveOracleNodes(); expect(activeNodes.length).to.equal(0); }); it("Should return correct active oracle nodes", async function () { await whitelistOracle.addOracle(addr1.address); await whitelistOracle.addOracle(addr2.address); const oracle1Address = await whitelistOracle.oracles(0); const oracle2Address = await whitelistOracle.oracles(1); const SimpleOracleFactory = await ethers.getContractFactory("SimpleOracle"); const oracle1 = SimpleOracleFactory.attach(oracle1Address) as SimpleOracle; const oracle2 = SimpleOracleFactory.attach(oracle2Address) as SimpleOracle; await oracle1.setPrice(1000n); await oracle2.setPrice(2000n); let activeNodes = await whitelistOracle.getActiveOracleNodes(); expect(activeNodes.length).to.equal(2); expect(activeNodes).to.include(oracle1Address); expect(activeNodes).to.include(oracle2Address); // Make oracle1's price stale await ethers.provider.send("evm_increaseTime", [25]); await ethers.provider.send("evm_mine"); // Update only oracle2 await oracle2.setPrice(3000n); activeNodes = await whitelistOracle.getActiveOracleNodes(); expect(activeNodes.length).to.equal(1); expect(activeNodes[0]).to.equal(oracle2Address); }); it("Should handle edge case when all prices are stale but array is not empty", async function () { await whitelistOracle.addOracle(addr1.address); await whitelistOracle.addOracle(addr2.address); const SimpleOracleFactory = await ethers.getContractFactory("SimpleOracle"); const oracle1 = SimpleOracleFactory.attach(await whitelistOracle.oracles(0)) as SimpleOracle; const oracle2 = SimpleOracleFactory.attach(await whitelistOracle.oracles(1)) as SimpleOracle; await oracle1.setPrice(1000n); await oracle2.setPrice(2000n); // Verify median works initially const medianPrice = await whitelistOracle.getPrice(); expect(medianPrice).to.equal(1500n); // Make all prices stale await ethers.provider.send("evm_increaseTime", [25]); await ethers.provider.send("evm_mine"); const activeNodes = await whitelistOracle.getActiveOracleNodes(); expect(activeNodes.length).to.equal(0); }); });