234 lines
9.6 KiB
TypeScript
234 lines
9.6 KiB
TypeScript
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);
|
|
});
|
|
});
|