From 6f2931a5ffc75ea692edf9a29d68993015f03a9e Mon Sep 17 00:00:00 2001 From: evandrosaturnino Date: Thu, 16 May 2024 03:52:16 -0300 Subject: [PATCH] refactor: update price feed tests to reflect redstone changes --- .../contracts/test/BOB/PriceFeedBaseTest.js | 110 + packages/contracts/test/PriceFeedTest.js | 2350 +++++++++++++++++ 2 files changed, 2460 insertions(+) create mode 100644 packages/contracts/test/BOB/PriceFeedBaseTest.js diff --git a/packages/contracts/test/BOB/PriceFeedBaseTest.js b/packages/contracts/test/BOB/PriceFeedBaseTest.js new file mode 100644 index 000000000..f5cab2dde --- /dev/null +++ b/packages/contracts/test/BOB/PriceFeedBaseTest.js @@ -0,0 +1,110 @@ +const PriceFeedBase = artifacts.require("./PriceFeedBase.sol") +const MockRedstoneAdapterBase = artifacts.require("./MockRedstoneAdapterBase.sol") + +const testHelpers = require("../../utils/testHelpers.js") +const th = testHelpers.TestHelper + +const { dec } = th +const priceFeedBaseDataFeedId = ethers.utils.formatBytes32String("BTC"); + +contract('PriceFeedBase', async () => { + + let priceFeedBase + let mockRedstoneAdapterBase + let now + let previousUpdate + + beforeEach(async () => { + mockRedstoneAdapterBase = await MockRedstoneAdapterBase.new() + MockRedstoneAdapterBase.setAsDeployed(mockRedstoneAdapterBase) + + priceFeedBase = await PriceFeedBase.new(mockRedstoneAdapterBase.address, priceFeedBaseDataFeedId) + PriceFeedBase.setAsDeployed(priceFeedBase) + + // Set Chainlink latest and prev round Id's to non-zero + await mockRedstoneAdapterBase.setLatestRoundId(3) + + //Set current and prev prices in both oracles + await mockRedstoneAdapterBase.setPrice(dec(10, 8)) + await mockRedstoneAdapterBase.setPrevPrice(dec(5, 8)) + + // Set mock price updateTimes in both oracles to very recent + now = await th.getLatestBlockTimestamp(web3) + previousUpdate = now - 1 + + await mockRedstoneAdapterBase.setUpdateTime(now) + await mockRedstoneAdapterBase.setPrevTime(previousUpdate) + }) + + describe('Tests for getting the price feed details', async accounts => { + it("should properly get the price feed adapter", async () => { + const redstoneAdapter = await priceFeedBase.redstoneAdapter() + assert.equal(redstoneAdapter, mockRedstoneAdapterBase.address) + }) + + it("should properly get data feed id", async () => { + const dataFeedId = await priceFeedBase.dataFeedId() + assert.equal(dataFeedId, ethers.utils.formatBytes32String("BTC")) + }) + + it("should properly get the decimals", async () => { + const decimals = await priceFeedBase.decimals() + assert.equal(decimals, 8) + }) + + it("should properly get the description", async () => { + const description = await priceFeedBase.description() + assert.equal(description, "Redstone Price Feed") + }) + + it("should properly get the version", async () => { + const version = await priceFeedBase.version() + assert.equal(version, 1) + }) + }) + + describe('Tests for getting the price feed values', async () => { + it("should properly get latest round data", async () => { + let latestRoundData = await priceFeedBase.latestRoundData() + assert.equal(latestRoundData.roundId, 3) + assert.equal(latestRoundData.answer, dec(10, 8)) + assert.equal(latestRoundData.startedAt, now) + assert.equal(latestRoundData.updatedAt, now) + assert.equal(latestRoundData.answeredInRound, 3) + + let updatedTime = await th.getLatestBlockTimestamp(web3) + + await mockRedstoneAdapterBase.setLatestRoundId(4) + await mockRedstoneAdapterBase.setPrice(dec(12, 8)) + await mockRedstoneAdapterBase.setUpdateTime(updatedTime) + + latestRoundData = await priceFeedBase.latestRoundData() + assert.equal(latestRoundData.roundId, 4) + assert.equal(latestRoundData.answer, dec(12, 8)) + assert.equal(latestRoundData.startedAt, updatedTime) + assert.equal(latestRoundData.updatedAt, updatedTime) + assert.equal(latestRoundData.answeredInRound, 4) + }) + + it("should properly get a previous round data", async () => { + let previousRoundData = await priceFeedBase.getRoundData(2) + assert.equal(previousRoundData.roundId, 2) + assert.equal(previousRoundData.answer, dec(5, 8)) + assert.equal(previousRoundData.startedAt, previousUpdate) + assert.equal(previousRoundData.updatedAt, previousUpdate) + assert.equal(previousRoundData.answeredInRound, 2) + + let updatedPreviousTime = now + + await mockRedstoneAdapterBase.setPrevPrice(dec(10, 8)) + await mockRedstoneAdapterBase.setPrevTime(updatedPreviousTime) + + previousRoundData = await priceFeedBase.getRoundData(3) + assert.equal(previousRoundData.roundId, 3) + assert.equal(previousRoundData.answer, dec(10, 8)) + assert.equal(previousRoundData.startedAt, updatedPreviousTime) + assert.equal(previousRoundData.updatedAt, updatedPreviousTime) + assert.equal(previousRoundData.answeredInRound, 3) + }) + }) +}) diff --git a/packages/contracts/test/PriceFeedTest.js b/packages/contracts/test/PriceFeedTest.js index fe341c612..9df9cc993 100644 --- a/packages/contracts/test/PriceFeedTest.js +++ b/packages/contracts/test/PriceFeedTest.js @@ -5,6 +5,8 @@ const MockChainlink = artifacts.require("./MockAggregator.sol") const MockTellor = artifacts.require("./MockTellor.sol") const BrokenMockTellor = artifacts.require("./MockTellor.sol") const TellorCaller = artifacts.require("./TellorCaller.sol") +const PriceFeedBase = artifacts.require("./PriceFeedBase.sol") +const MockRedstoneAdapterBase = artifacts.require("./MockRedstoneAdapterBase.sol") const testHelpers = require("../utils/testHelpers.js") const th = testHelpers.TestHelper @@ -16,6 +18,8 @@ const queryDataArgs = abiCoder.encode(["string", "string"], ["erc20", "usd"]); const queryData = abiCoder.encode(["string", "bytes"], ["SpotPrice", queryDataArgs]); const queryId = ethers.utils.keccak256(queryData); +const priceFeedBaseDataFeedId = ethers.utils.formatBytes32String("BTC"); + contract('PriceFeed', async accounts => { const [owner, alice] = accounts; @@ -2470,6 +2474,2352 @@ contract('PriceFeed', async accounts => { }) } + context("when Price feed base is being used as price aggregator", () => { + let priceFeed + let priceFeedBase + let mockRedstoneAdapterBase + let mockTellor + let tellorCaller + + const tellorDigits = 18 + + const setAddresses = async () => { + await priceFeed.setAddresses(priceFeedBase.address, tellorCaller.address, { from: owner }) + } + + async function setTellorPrice(price) { + // Tests set price with 6 digits, adjust it if tellorDigits bigger than that + adjustedPrice = dec(price, tellorDigits - 6) + let valueBytes = abiCoder.encode(["uint256"], [adjustedPrice]); + await mockTellor.submitValue(queryId, valueBytes, 0, queryData) + await th.fastForwardTime(15 * 60 + 1, web3.currentProvider) // 15 minutes + } + + beforeEach(async () => { + priceFeed = await PriceFeed.new(tellorDigits) + PriceFeed.setAsDeployed(priceFeed) + + mockRedstoneAdapterBase = await MockRedstoneAdapterBase.new() + MockRedstoneAdapterBase.setAsDeployed(mockRedstoneAdapterBase) + + priceFeedBase = await PriceFeedBase.new(mockRedstoneAdapterBase.address, priceFeedBaseDataFeedId) + PriceFeedBase.setAsDeployed(priceFeedBase) + + mockTellor = await MockTellor.new(queryData) + MockTellor.setAsDeployed(mockTellor) + + tellorCaller = await TellorCaller.new(mockTellor.address, queryId) + TellorCaller.setAsDeployed(tellorCaller) + + // Set Chainlink latest and prev round Id's to non-zero + await mockRedstoneAdapterBase.setLatestRoundId(3) + + //Set current and prev prices in both oracles + await mockRedstoneAdapterBase.setPrice(dec(10, 8)) + await mockRedstoneAdapterBase.setPrevPrice(dec(5, 8)) + + // Set mock price updateTimes in both oracles to very recent + now = await th.getLatestBlockTimestamp(web3) + previousUpdate = now - 1 + + await mockRedstoneAdapterBase.setUpdateTime(now) + await mockRedstoneAdapterBase.setPrevTime(previousUpdate) + await setTellorPrice(dec(100, 18)) + }) + + describe('Mainnet PriceFeed setup', async accounts => { + it("setAddresses should fail whe called by nonOwner", async () => { + await assertRevert( + priceFeed.setAddresses(priceFeedBase.address, mockTellor.address, { from: alice }), + "Ownable: caller is not the owner" + ) + }) + + it("setAddresses should fail after address has already been set", async () => { + // Owner can successfully set any address + const txOwner = await priceFeed.setAddresses(priceFeedBase.address, mockTellor.address, { from: owner }) + assert.isTrue(txOwner.receipt.status) + + await assertRevert( + priceFeed.setAddresses(priceFeedBase.address, mockTellor.address, { from: owner }), + "PriceFeed: contacts already set" + ) + + await assertRevert( + priceFeed.setAddresses(priceFeedBase.address, mockTellor.address, { from: alice }), + "Ownable: caller is not the owner" + ) + }) + }) + + it("C1 Chainlink working: fetchPrice should return the correct price, taking into account the number of decimal digits on the aggregator", async () => { + await setAddresses() + + // Oracle price price is 10.00000000 + await mockRedstoneAdapterBase.setPrevPrice(dec(1, 9)) + await mockRedstoneAdapterBase.setPrice(dec(1, 9)) + await priceFeed.fetchPrice() + let price = await priceFeed.lastGoodPrice() + // Check Liquity PriceFeed gives 10, with 18 digit precision + assert.equal(price, dec(10, 18)) + + // Oracle price is 1e9 + await mockRedstoneAdapterBase.setPrevPrice(dec(1, 18)) + await mockRedstoneAdapterBase.setPrice(dec(1, 18)) + await priceFeed.fetchPrice() + price = await priceFeed.lastGoodPrice() + // Check Liquity PriceFeed gives 1e9, with 18 digit precision + assert.isTrue(price.eq(toBN(dec(1, 28)))) + + // Oracle price is 0.0001 + await mockRedstoneAdapterBase.setPrevPrice(dec(1, 4)) + await mockRedstoneAdapterBase.setPrice(dec(1, 4)) + await priceFeed.fetchPrice() + price = await priceFeed.lastGoodPrice() + // Check Liquity PriceFeed gives 0.0001 with 18 digit precision + assert.isTrue(price.eq(toBN(dec(1, 14)))) + + // Oracle price is 1.23456789 + await mockRedstoneAdapterBase.setPrevPrice(dec(123456789)) + await mockRedstoneAdapterBase.setPrice(dec(123456789)) + await priceFeed.fetchPrice() + price = await priceFeed.lastGoodPrice() + // Check Liquity PriceFeed gives the value with 18 digit precision + assert.equal(price, '1234567890000000000') + }) + + // --- Chainlink breaks --- + it("C1 Chainlink breaks, Tellor working: fetchPrice should return the correct Tellor price, taking into account Tellor's 6-digit granularity", async () => { + await setAddresses() + // --- Chainlink fails, system switches to Tellor --- + const statusBefore = await priceFeed.status() + assert.equal(statusBefore, '0') // status 0: Chainlink working + + // Chainlink breaks with negative price + await mockRedstoneAdapterBase.setPrevPrice(dec(1, 8)) + await mockRedstoneAdapterBase.setPrice(dec(1, 4)) + + await setTellorPrice(dec(123, 6)) + await mockRedstoneAdapterBase.setUpdateTime(0) + + const priceFetchTx = await priceFeed.fetchPrice() + const statusAfter = await priceFeed.status() + assert.equal(statusAfter, '1') // status 1: using Tellor, Chainlink untrusted + + let price = await priceFeed.lastGoodPrice() + assert.equal(price, dec(123, 18)) + + // Tellor price is 10 at 6-digit precision + await setTellorPrice(dec(10, 6)) + await priceFeed.fetchPrice() + price = await priceFeed.lastGoodPrice() + // Check Liquity PriceFeed gives 10, with 18 digit precision + assert.equal(price, dec(10, 18)) + + // Tellor price is 1e9 at 6-digit precision + await setTellorPrice(dec(1, 15)) + await priceFeed.fetchPrice() + price = await priceFeed.lastGoodPrice() + // Check Liquity PriceFeed gives 1e9, with 18 digit precision + assert.equal(price, dec(1, 27)) + + // Tellor price is 0.0001 at 6-digit precision + await setTellorPrice(100) + await priceFeed.fetchPrice() + price = await priceFeed.lastGoodPrice() + // Check Liquity PriceFeed gives 0.0001 with 18 digit precision + + assert.equal(price, dec(1, 14)) + + // Tellor price is 1234.56789 at 6-digit precision + await setTellorPrice(dec(1234567890)) + await priceFeed.fetchPrice() + price = await priceFeed.lastGoodPrice() + // Check Liquity PriceFeed gives 0.0001 with 18 digit precision + assert.equal(price, '1234567890000000000000') + }) + + it("C1 chainlinkWorking: Chainlink broken by zero latest roundId, Tellor working: switch to usingChainlinkTellorUntrusted", async () => { + await setAddresses() + const statusBefore = await priceFeed.status() + assert.equal(statusBefore, '0') // status 0: Chainlink working + + await mockRedstoneAdapterBase.setPrevPrice(dec(999, 8)) + await mockRedstoneAdapterBase.setPrice(dec(999, 8)) + await priceFeed.setLastGoodPrice(dec(999, 18)) + + await setTellorPrice(dec(123, 6)) + await mockRedstoneAdapterBase.setLatestRoundId(0) + + const priceFetchTx = await priceFeed.fetchPrice() + const statusAfter = await priceFeed.status() + assert.equal(statusAfter, '1') // status 1: using Tellor, Chainlink untrusted + }) + + it("C1 chainlinkWorking: Chainlink broken by zero timestamp, Tellor working, switch to usingChainlinkTellorUntrusted", async () => { + await setAddresses() + const statusBefore = await priceFeed.status() + assert.equal(statusBefore, '0') // status 0: Chainlink working + + await mockRedstoneAdapterBase.setPrevPrice(dec(999, 8)) + await mockRedstoneAdapterBase.setPrice(dec(999, 8)) + await priceFeed.setLastGoodPrice(dec(999, 18)) + + await setTellorPrice(dec(123, 6)) + await mockRedstoneAdapterBase.setUpdateTime(0) + + const priceFetchTx = await priceFeed.fetchPrice() + const statusAfter = await priceFeed.status() + assert.equal(statusAfter, '1') // status 1: using Tellor, Chainlink untrusted + }) + + it("C1 chainlinkWorking: Chainlink broken by zero timestamp, Tellor working, return Tellor price", async () => { + await setAddresses() + const statusBefore = await priceFeed.status() + assert.equal(statusBefore, '0') // status 0: Chainlink working + + await mockRedstoneAdapterBase.setPrevPrice(dec(999, 8)) + await mockRedstoneAdapterBase.setPrice(dec(999, 8)) + await priceFeed.setLastGoodPrice(dec(999, 18)) + + await setTellorPrice(dec(123, 6)) + await mockRedstoneAdapterBase.setUpdateTime(0) + + const priceFetchTx = await priceFeed.fetchPrice() + + let price = await priceFeed.lastGoodPrice() + assert.equal(price, dec(123, 18)) + }) + + it("C1 chainlinkWorking: Chainlink broken by future timestamp, Tellor working, switch to usingChainlinkTellorUntrusted", async () => { + await setAddresses() + const statusBefore = await priceFeed.status() + assert.equal(statusBefore, '0') // status 0: Chainlink working + + await mockRedstoneAdapterBase.setPrevPrice(dec(999, 8)) + await mockRedstoneAdapterBase.setPrice(dec(999, 8)) + await priceFeed.setLastGoodPrice(dec(999, 18)) + + const now = await th.getLatestBlockTimestamp(web3) + const future = toBN(now).add(toBN('1000')) + + await setTellorPrice(dec(123, 6)) + await mockRedstoneAdapterBase.setUpdateTime(future) + + const priceFetchTx = await priceFeed.fetchPrice() + const statusAfter = await priceFeed.status() + assert.equal(statusAfter, '1') // status 1: using Tellor, Chainlink untrusted + }) + + it("C1 chainlinkWorking: Chainlink broken by future timestamp, Tellor working, return Tellor price", async () => { + await setAddresses() + const statusBefore = await priceFeed.status() + assert.equal(statusBefore, '0') // status 0: Chainlink working + + await mockRedstoneAdapterBase.setPrevPrice(dec(999, 8)) + await mockRedstoneAdapterBase.setPrice(dec(999, 8)) + await priceFeed.setLastGoodPrice(dec(999, 18)) + + const now = await th.getLatestBlockTimestamp(web3) + const future = toBN(now).add(toBN('1000')) + + await setTellorPrice(dec(123, 6)) + await mockRedstoneAdapterBase.setUpdateTime(future) + + const priceFetchTx = await priceFeed.fetchPrice() + + let price = await priceFeed.lastGoodPrice() + assert.equal(price, dec(123, 18)) + }) + + it("C1 chainlinkWorking: Chainlink broken by negative price, Tellor working, switch to usingChainlinkTellorUntrusted", async () => { + await setAddresses() + const statusBefore = await priceFeed.status() + assert.equal(statusBefore, '0') // status 0: Chainlink working + + await mockRedstoneAdapterBase.setPrevPrice(dec(999, 8)) + await priceFeed.setLastGoodPrice(dec(999, 18)) + + await setTellorPrice(dec(123, 6)) + await mockRedstoneAdapterBase.setPrice(dec(1, 8)) + + const priceFetchTx = await priceFeed.fetchPrice() + const statusAfter = await priceFeed.status() + assert.equal(statusAfter, '1') // status 1: using Tellor, Chainlink untrusted + }) + + it("C1 chainlinkWorking: Chainlink broken by negative price, Tellor working, return Tellor price", async () => { + await setAddresses() + const statusBefore = await priceFeed.status() + assert.equal(statusBefore, '0') // status 0: Chainlink working + + await mockRedstoneAdapterBase.setPrevPrice(dec(999, 8)) + await priceFeed.setLastGoodPrice(dec(999, 18)) + + await setTellorPrice(dec(123, 6)) + await mockRedstoneAdapterBase.setPrice(dec(1, 8)) + + const priceFetchTx = await priceFeed.fetchPrice() + + let price = await priceFeed.lastGoodPrice() + assert.equal(price, dec(123, 18)) + }) + + it("C1 chainlinkWorking: Chainlink broken - latest round call reverted, Tellor working, switch to usingChainlinkTellorUntrusted", async () => { + await setAddresses() + const statusBefore = await priceFeed.status() + assert.equal(statusBefore, '0') // status 0: Chainlink working + + await mockRedstoneAdapterBase.setPrevPrice(dec(999, 8)) + await mockRedstoneAdapterBase.setPrice(dec(999, 8)) + await priceFeed.setLastGoodPrice(dec(999, 18)) + + await setTellorPrice(dec(123, 6)) + await mockRedstoneAdapterBase.setLatestRevert() + + const priceFetchTx = await priceFeed.fetchPrice() + const statusAfter = await priceFeed.status() + assert.equal(statusAfter, '1') // status 1: using Tellor, Chainlink untrusted + }) + + it("C1 chainlinkWorking: latest round call reverted, Tellor working, return the Tellor price", async () => { + await setAddresses() + const statusBefore = await priceFeed.status() + assert.equal(statusBefore, '0') // status 0: Chainlink working + + await mockRedstoneAdapterBase.setPrevPrice(dec(999, 8)) + await mockRedstoneAdapterBase.setPrice(dec(999, 8)) + await priceFeed.setLastGoodPrice(dec(999, 18)) + + await setTellorPrice(dec(123, 6)) + await mockRedstoneAdapterBase.setLatestRevert() + + const priceFetchTx = await priceFeed.fetchPrice() + + let price = await priceFeed.lastGoodPrice() + assert.equal(price, dec(123, 18)) + }) + + it("C1 chainlinkWorking: previous round call reverted, Tellor working, switch to usingChainlinkTellorUntrusted", async () => { + await setAddresses() + const statusBefore = await priceFeed.status() + assert.equal(statusBefore, '0') // status 0: Chainlink working + + await mockRedstoneAdapterBase.setPrevPrice(dec(999, 8)) + await mockRedstoneAdapterBase.setPrice(dec(999, 8)) + await priceFeed.setLastGoodPrice(dec(999, 18)) + + await setTellorPrice(dec(123, 6)) + await mockRedstoneAdapterBase.setPrevRevert() + + const priceFetchTx = await priceFeed.fetchPrice() + const statusAfter = await priceFeed.status() + assert.equal(statusAfter, '1') // status 1: using Tellor, Chainlink untrusted + }) + + it("C1 chainlinkWorking: previous round call reverted, Tellor working, return Tellor Price", async () => { + await setAddresses() + const statusBefore = await priceFeed.status() + assert.equal(statusBefore, '0') // status 0: Chainlink working + + await mockRedstoneAdapterBase.setPrevPrice(dec(999, 8)) + await mockRedstoneAdapterBase.setPrice(dec(999, 8)) + await priceFeed.setLastGoodPrice(dec(999, 18)) + + await setTellorPrice(dec(123, 6)) + await mockRedstoneAdapterBase.setPrevRevert() + + const priceFetchTx = await priceFeed.fetchPrice() + + let price = await priceFeed.lastGoodPrice() + assert.equal(price, dec(123, 18)) + }) + + // --- Chainlink timeout --- + + it("C1 chainlinkWorking: Chainlink frozen, Tellor working: switch to usingTellorChainlinkFrozen", async () => { + await setAddresses() + const statusBefore = await priceFeed.status() + assert.equal(statusBefore, '0') // status 0: Chainlink working + + await mockRedstoneAdapterBase.setPrevPrice(dec(999, 8)) + await mockRedstoneAdapterBase.setPrice(dec(999, 8)) + await priceFeed.setLastGoodPrice(dec(999, 18)) + + await th.fastForwardTime(14400, web3.currentProvider) // fast forward 4 hours + const now = await th.getLatestBlockTimestamp(web3) + + // Tellor price is recent + await setTellorPrice(dec(123, 6)) + + const priceFetchTx = await priceFeed.fetchPrice() + const statusAfter = await priceFeed.status() + assert.equal(statusAfter, '3') // status 3: using Tellor, Chainlink frozen + }) + + it("C1 chainlinkWorking: Chainlink frozen, Tellor working: return Tellor price", async () => { + await setAddresses() + const statusBefore = await priceFeed.status() + assert.equal(statusBefore, '0') // status 0: Chainlink working + + await mockRedstoneAdapterBase.setPrevPrice(dec(999, 8)) + await mockRedstoneAdapterBase.setPrice(dec(999, 8)) + await priceFeed.setLastGoodPrice(dec(999, 18)) + + await th.fastForwardTime(14400, web3.currentProvider) // Fast forward 4 hours + const now = await th.getLatestBlockTimestamp(web3) + // Tellor price is recent + await setTellorPrice(dec(123, 6)) + + const priceFetchTx = await priceFeed.fetchPrice() + + let price = await priceFeed.lastGoodPrice() + assert.equal(price, dec(123, 18)) + }) + + it("C1 chainlinkWorking: Chainlink frozen, Tellor frozen: switch to usingTellorChainlinkFrozen", async () => { + await setAddresses() + const statusBefore = await priceFeed.status() + assert.equal(statusBefore, '0') // status 0: Chainlink working + + await mockRedstoneAdapterBase.setPrevPrice(dec(999, 8)) + await mockRedstoneAdapterBase.setPrice(dec(999, 8)) + await priceFeed.setLastGoodPrice(dec(999, 18)) + + await setTellorPrice(dec(123, 6)) + + await th.fastForwardTime(14400, web3.currentProvider) // fast forward 4 hours + + // check Tellor price timestamp is out of date by > 4 hours + const now = await th.getLatestBlockTimestamp(web3) + const tellorUpdateTime = (await tellorCaller.getTellorCurrentValue.call())[2] + assert.isTrue(tellorUpdateTime.lt(toBN(now).sub(toBN(14400)))) + + const priceFetchTx = await priceFeed.fetchPrice() + const statusAfter = await priceFeed.status() + assert.equal(statusAfter, '3') // status 3: using Tellor, Chainlink frozen + }) + + it("C1 chainlinkWorking: Chainlink frozen, Tellor frozen: return last good price", async () => { + await setAddresses() + const statusBefore = await priceFeed.status() + assert.equal(statusBefore, '0') // status 0: Chainlink working + + await mockRedstoneAdapterBase.setPrevPrice(dec(999, 8)) + await mockRedstoneAdapterBase.setPrice(dec(999, 8)) + await priceFeed.setLastGoodPrice(dec(999, 18)) + + await setTellorPrice(dec(123, 6)) + + await th.fastForwardTime(14400, web3.currentProvider) // Fast forward 4 hours + + // check Tellor price timestamp is out of date by > 4 hours + const now = await th.getLatestBlockTimestamp(web3) + const tellorUpdateTime = (await tellorCaller.getTellorCurrentValue.call())[2] + assert.isTrue(tellorUpdateTime.lt(toBN(now).sub(toBN(14400)))) + + const priceFetchTx = await priceFeed.fetchPrice() + let price = await priceFeed.lastGoodPrice() + // Expect lastGoodPrice has not updated + assert.equal(price, dec(999, 18)) + }) + + it("C1 chainlinkWorking: Chainlink times out, Tellor broken by 0 price: switch to usingChainlinkTellorUntrusted", async () => { + await setAddresses() + const statusBefore = await priceFeed.status() + assert.equal(statusBefore, '0') // status 0: Chainlink working + + await mockRedstoneAdapterBase.setPrevPrice(dec(999, 8)) + await mockRedstoneAdapterBase.setPrice(dec(999, 8)) + await priceFeed.setLastGoodPrice(dec(999, 18)) + + await th.fastForwardTime(14400, web3.currentProvider) // Fast forward 4 hours + + // Tellor breaks by 0 price + await setTellorPrice(0) + + const priceFetchTx = await priceFeed.fetchPrice() + const statusAfter = await priceFeed.status() + assert.equal(statusAfter, '4') // status 4: using Chainlink, Tellor untrusted + }) + + it("C1 chainlinkWorking: Chainlink times out, Tellor broken by 0 price: return last good price", async () => { + await setAddresses() + const statusBefore = await priceFeed.status() + assert.equal(statusBefore, '0') // status 0: Chainlink working + + await mockRedstoneAdapterBase.setPrevPrice(dec(999, 8)) + await mockRedstoneAdapterBase.setPrice(dec(999, 8)) + await priceFeed.setLastGoodPrice(dec(999, 18)) + + await th.fastForwardTime(14400, web3.currentProvider) // Fast forward 4 hours + + await setTellorPrice(0) + + const priceFetchTx = await priceFeed.fetchPrice() + let price = await priceFeed.lastGoodPrice() + + // Expect lastGoodPrice has not updated + assert.equal(price, dec(999, 18)) + }) + + it("C1 chainlinkWorking: Chainlink is out of date by <3hrs: remain chainlinkWorking", async () => { + await setAddresses() + const statusBefore = await priceFeed.status() + assert.equal(statusBefore, '0') // status 0: Chainlink working + + await mockRedstoneAdapterBase.setPrevPrice(dec(1234, 8)) + await mockRedstoneAdapterBase.setPrice(dec(1234, 8)) + await th.fastForwardTime(10740, web3.currentProvider) // fast forward 2hrs 59 minutes + + const priceFetchTx = await priceFeed.fetchPrice() + const statusAfter = await priceFeed.status() + assert.equal(statusAfter, '0') // status 0: Chainlink working + }) + + it("C1 chainlinkWorking: Chainlink is out of date by <3hrs: return Chainklink price", async () => { + await setAddresses() + const statusBefore = await priceFeed.status() + assert.equal(statusBefore, '0') // status 0: Chainlink working + + await mockRedstoneAdapterBase.setPrevPrice(dec(1234, 8)) + await mockRedstoneAdapterBase.setPrice(dec(1234, 8)) + await th.fastForwardTime(10740, web3.currentProvider) // fast forward 2hrs 59 minutes + + const priceFetchTx = await priceFeed.fetchPrice() + const price = await priceFeed.lastGoodPrice() + assert.equal(price, dec(1234, 18)) + }) + + // --- Chainlink price deviation --- + + it("C1 chainlinkWorking: Chainlink price drop of >50%, switch to usingChainlinkTellorUntrusted", async () => { + await setAddresses() + priceFeed.setLastGoodPrice(dec(2, 18)) + + const statusBefore = await priceFeed.status() + assert.equal(statusBefore, '0') // status 0: Chainlink working + + await setTellorPrice(dec(203, 4)) + await mockRedstoneAdapterBase.setPrevPrice(dec(2, 8)) // price = 2 + await mockRedstoneAdapterBase.setPrice(99999999) // price drops to 0.99999999: a drop of > 50% from previous + + const priceFetchTx = await priceFeed.fetchPrice() + const statusAfter = await priceFeed.status() + assert.equal(statusAfter, '1') // status 1: using Tellor, Chainlink untrusted + }) + + it("C1 chainlinkWorking: Chainlink price drop of >50%, return the Tellor price", async () => { + await setAddresses() + priceFeed.setLastGoodPrice(dec(2, 18)) + + const statusBefore = await priceFeed.status() + assert.equal(statusBefore, '0') // status 0: Chainlink working + + await setTellorPrice(dec(203,4)) + await mockRedstoneAdapterBase.setPrevPrice(dec(2, 8)) // price = 2 + await mockRedstoneAdapterBase.setPrice(99999999) // price drops to 0.99999999: a drop of > 50% from previous + + const priceFetchTx = await priceFeed.fetchPrice() + + let price = await priceFeed.lastGoodPrice() + assert.equal(price, dec(203, 16)) + }) + + it("C1 chainlinkWorking: Chainlink price drop of 50%, remain chainlinkWorking", async () => { + await setAddresses() + priceFeed.setLastGoodPrice(dec(2, 18)) + + const statusBefore = await priceFeed.status() + assert.equal(statusBefore, '0') // status 0: Chainlink working + + await setTellorPrice(dec(203, 4)) + await mockRedstoneAdapterBase.setPrevPrice(dec(2, 8)) // price = 2 + await mockRedstoneAdapterBase.setPrice(dec(1, 8)) // price drops to 1 + + const priceFetchTx = await priceFeed.fetchPrice() + const statusAfter = await priceFeed.status() + assert.equal(statusAfter, '0') // status 0: Chainlink working + }) + + it("C1 chainlinkWorking: Chainlink price drop of 50%, return the Chainlink price", async () => { + await setAddresses() + priceFeed.setLastGoodPrice(dec(2, 18)) + + const statusBefore = await priceFeed.status() + assert.equal(statusBefore, '0') // status 0: Chainlink working + + await setTellorPrice(dec(203, 4)) + await mockRedstoneAdapterBase.setPrevPrice(dec(2, 8)) // price = 2 + await mockRedstoneAdapterBase.setPrice(dec(1, 8)) // price drops to 1 + + const priceFetchTx = await priceFeed.fetchPrice() + + let price = await priceFeed.lastGoodPrice() + assert.equal(price, dec(1, 18)) + }) + + it("C1 chainlinkWorking: Chainlink price drop of <50%, remain chainlinkWorking", async () => { + await setAddresses() + priceFeed.setLastGoodPrice(dec(2, 18)) + + const statusBefore = await priceFeed.status() + assert.equal(statusBefore, '0') // status 0: Chainlink working + + await setTellorPrice(dec(203, 4)) + await mockRedstoneAdapterBase.setPrevPrice(dec(2, 8)) // price = 2 + await mockRedstoneAdapterBase.setPrice(dec(100000001)) // price drops to 1.00000001: a drop of < 50% from previous + + const priceFetchTx = await priceFeed.fetchPrice() + const statusAfter = await priceFeed.status() + assert.equal(statusAfter, '0') // status 0: Chainlink working + }) + + it("C1 chainlinkWorking: Chainlink price drop of <50%, return Chainlink price", async () => { + await setAddresses() + priceFeed.setLastGoodPrice(dec(2, 18)) + + const statusBefore = await priceFeed.status() + assert.equal(statusBefore, '0') // status 0: Chainlink working + + await setTellorPrice(dec(203, 4)) + await mockRedstoneAdapterBase.setPrevPrice(dec(2, 8)) // price = 2 + await mockRedstoneAdapterBase.setPrice(100000001) // price drops to 1.00000001: a drop of < 50% from previous + + const priceFetchTx = await priceFeed.fetchPrice() + + let price = await priceFeed.lastGoodPrice() + assert.equal(price, dec(100000001, 10)) + }) + + // Price increase + it("C1 chainlinkWorking: Chainlink price increase of >100%, switch to usingChainlinkTellorUntrusted", async () => { + await setAddresses() + priceFeed.setLastGoodPrice(dec(2, 18)) + + const statusBefore = await priceFeed.status() + assert.equal(statusBefore, '0') // status 0: Chainlink working + + await setTellorPrice(dec(203, 4)) + await mockRedstoneAdapterBase.setPrevPrice(dec(2, 8)) // price = 2 + await mockRedstoneAdapterBase.setPrice(400000001) // price increases to 4.000000001: an increase of > 100% from previous + + const priceFetchTx = await priceFeed.fetchPrice() + const statusAfter = await priceFeed.status() + assert.equal(statusAfter, '1') // status 1: using Tellor, Chainlink untrusted + }) + + it("C1 chainlinkWorking: Chainlink price increase of >100%, return Tellor price", async () => { + await setAddresses() + priceFeed.setLastGoodPrice(dec(2, 18)) + + const statusBefore = await priceFeed.status() + assert.equal(statusBefore, '0') // status 0: Chainlink working + + await setTellorPrice(dec(203, 4)) + await mockRedstoneAdapterBase.setPrevPrice(dec(2, 8)) // price = 2 + await mockRedstoneAdapterBase.setPrice(400000001) // price increases to 4.000000001: an increase of > 100% from previous + + const priceFetchTx = await priceFeed.fetchPrice() + let price = await priceFeed.lastGoodPrice() + assert.equal(price, dec(203, 16)) + }) + + it("C1 chainlinkWorking: Chainlink price increase of 100%, remain chainlinkWorking", async () => { + await setAddresses() + priceFeed.setLastGoodPrice(dec(2, 18)) + + const statusBefore = await priceFeed.status() + assert.equal(statusBefore, '0') // status 0: Chainlink working + + await setTellorPrice(dec(203, 4)) + await mockRedstoneAdapterBase.setPrevPrice(dec(2, 8)) // price = 2 + await mockRedstoneAdapterBase.setPrice(dec(4, 8)) // price increases to 4: an increase of 100% from previous + + const priceFetchTx = await priceFeed.fetchPrice() + const statusAfter = await priceFeed.status() + assert.equal(statusAfter, '0') // status 0: Chainlink working + }) + + it("C1 chainlinkWorking: Chainlink price increase of 100%, return Chainlink price", async () => { + await setAddresses() + priceFeed.setLastGoodPrice(dec(2, 18)) + + const statusBefore = await priceFeed.status() + assert.equal(statusBefore, '0') // status 0: Chainlink working + + await setTellorPrice(dec(203, 4)) + await mockRedstoneAdapterBase.setPrevPrice(dec(2, 8)) // price = 2 + await mockRedstoneAdapterBase.setPrice(dec(4, 8)) // price increases to 4: an increase of 100% from previous + + const priceFetchTx = await priceFeed.fetchPrice() + let price = await priceFeed.lastGoodPrice() + assert.equal(price, dec(4, 18)) + }) + + it("C1 chainlinkWorking: Chainlink price increase of <100%, remain chainlinkWorking", async () => { + await setAddresses() + priceFeed.setLastGoodPrice(dec(2, 18)) + + const statusBefore = await priceFeed.status() + assert.equal(statusBefore, '0') // status 0: Chainlink working + + await setTellorPrice(dec(203, 4)) + await mockRedstoneAdapterBase.setPrevPrice(dec(2, 8)) // price = 2 + await mockRedstoneAdapterBase.setPrice(399999999) // price increases to 3.99999999: an increase of < 100% from previous + + const priceFetchTx = await priceFeed.fetchPrice() + const statusAfter = await priceFeed.status() + assert.equal(statusAfter, '0') // status 0: Chainlink working + }) + + it("C1 chainlinkWorking: Chainlink price increase of <100%, return Chainlink price", async () => { + await setAddresses() + priceFeed.setLastGoodPrice(dec(2, 18)) + + const statusBefore = await priceFeed.status() + assert.equal(statusBefore, '0') // status 0: Chainlink working + + await setTellorPrice(dec(203, 4)) + await mockRedstoneAdapterBase.setPrevPrice(dec(2, 8)) // price = 2 + await mockRedstoneAdapterBase.setPrice(399999999) // price increases to 3.99999999: an increase of < 100% from previous + + const priceFetchTx = await priceFeed.fetchPrice() + let price = await priceFeed.lastGoodPrice() + assert.equal(price, dec(399999999, 10)) + }) + + it("C1 chainlinkWorking: Chainlink price drop of >50% and Tellor price matches: remain chainlinkWorking", async () => { + await setAddresses() + priceFeed.setLastGoodPrice(dec(2, 18)) + + const statusBefore = await priceFeed.status() + assert.equal(statusBefore, '0') // status 0: Chainlink working + + await mockRedstoneAdapterBase.setPrevPrice(dec(2, 8)) // price = 2 + await mockRedstoneAdapterBase.setPrice(99999999) // price drops to 0.99999999: a drop of > 50% from previous + await setTellorPrice(999999) // Tellor price drops to same value (6 ecimals) + + const priceFetchTx = await priceFeed.fetchPrice() + const statusAfter = await priceFeed.status() + assert.equal(statusAfter, '0') // status 0: Chainlink working + }) + + it("C1 chainlinkWorking: Chainlink price drop of >50% and Tellor price matches: return Chainlink price", async () => { + await setAddresses() + priceFeed.setLastGoodPrice(dec(2, 18)) + + const statusBefore = await priceFeed.status() + assert.equal(statusBefore, '0') // status 0: Chainlink working + + await mockRedstoneAdapterBase.setPrevPrice(dec(2, 8)) // price = 2 + await mockRedstoneAdapterBase.setPrice(99999999) // price drops to 0.99999999: a drop of > 50% from previous + await setTellorPrice(999999) // Tellor price drops to same value (at 6 decimals) + + const priceFetchTx = await priceFeed.fetchPrice() + let price = await priceFeed.lastGoodPrice() + assert.equal(price, dec(99999999, 10)) + }) + + it("C1 chainlinkWorking: Chainlink price drop of >50% and Tellor price within 5% of Chainlink: remain chainlinkWorking", async () => { + await setAddresses() + priceFeed.setLastGoodPrice(dec(2, 18)) + + const statusBefore = await priceFeed.status() + assert.equal(statusBefore, '0') // status 0: Chainlink working + + await mockRedstoneAdapterBase.setPrevPrice(dec(1000, 8)) // prev price = 1000 + await mockRedstoneAdapterBase.setPrice(dec(100, 8)) // price drops to 100: a drop of > 50% from previous + await setTellorPrice(104999999) // Tellor price drops to 104.99: price difference with new Chainlink price is now just under 5% + + const priceFetchTx = await priceFeed.fetchPrice() + const statusAfter = await priceFeed.status() + assert.equal(statusAfter, '0') // status 0: Chainlink working + }) + + it("C1 chainlinkWorking: Chainlink price drop of >50% and Tellor price within 5% of Chainlink: return Chainlink price", async () => { + await setAddresses() + priceFeed.setLastGoodPrice(dec(2, 18)) + + const statusBefore = await priceFeed.status() + assert.equal(statusBefore, '0') // status 0: Chainlink working + + await mockRedstoneAdapterBase.setPrevPrice(dec(1000, 8)) // prev price = 1000 + await mockRedstoneAdapterBase.setPrice(dec(100, 8)) // price drops to 100: a drop of > 50% from previous + await setTellorPrice(104999999) // Tellor price drops to 104.99: price difference with new Chainlink price is now just under 5% + + const priceFetchTx = await priceFeed.fetchPrice() + let price = await priceFeed.lastGoodPrice() + assert.equal(price, dec(100, 18)) + }) + + it("C1 chainlinkWorking: Chainlink price drop of >50% and Tellor live but not within 5% of Chainlink: switch to usingChainlinkTellorUntrusted", async () => { + await setAddresses() + priceFeed.setLastGoodPrice(dec(2, 18)) + + const statusBefore = await priceFeed.status() + assert.equal(statusBefore, '0') // status 0: Chainlink working + + await mockRedstoneAdapterBase.setPrevPrice(dec(1000, 8)) // prev price = 1000 + await mockRedstoneAdapterBase.setPrice(dec(100, 8)) // price drops to 100: a drop of > 50% from previous + await setTellorPrice(105000001) // Tellor price drops to 105.000001: price difference with new Chainlink price is now > 5% + + const priceFetchTx = await priceFeed.fetchPrice() + const statusAfter = await priceFeed.status() + assert.equal(statusAfter, '1') // status 1: using Tellor, Chainlink untrusted + }) + + it("C1 chainlinkWorking: Chainlink price drop of >50% and Tellor live but not within 5% of Chainlink: return Tellor price", async () => { + await setAddresses() + priceFeed.setLastGoodPrice(dec(2, 18)) + + const statusBefore = await priceFeed.status() + assert.equal(statusBefore, '0') // status 0: Chainlink working + + await mockRedstoneAdapterBase.setPrevPrice(dec(1000, 8)) // prev price = 1000 + await mockRedstoneAdapterBase.setPrice(dec(100, 8)) // price drops to 100: a drop of > 50% from previous + await setTellorPrice(105000001) // Tellor price drops to 105.000001: price difference with new Chainlink price is now > 5% + + const priceFetchTx = await priceFeed.fetchPrice() + let price = await priceFeed.lastGoodPrice() + + assert.equal(price, dec(105000001, 12)) // return Tellor price + }) + + it("C1 chainlinkWorking: Chainlink price drop of >50% and Tellor frozen: switch to usingChainlinkTellorUntrusted", async () => { + await setAddresses() + const statusBefore = await priceFeed.status() + assert.equal(statusBefore, '0') // status 0: Chainlink working + + await mockRedstoneAdapterBase.setPrevPrice(dec(1000, 8)) // prev price = 1000 + await mockRedstoneAdapterBase.setPrice(dec(100, 8)) // price drops to 100: a drop of > 50% from previous + await setTellorPrice(dec(100, 8)) + + // 4 hours pass with no Tellor updates + await th.fastForwardTime(14400, web3.currentProvider) + + // check Tellor price timestamp is out of date by > 4 hours + const now = await th.getLatestBlockTimestamp(web3) + const tellorUpdateTime = (await tellorCaller.getTellorCurrentValue.call())[2] + assert.isTrue(tellorUpdateTime.lt(toBN(now).sub(toBN(14400)))) + + await mockRedstoneAdapterBase.setUpdateTime(now) + + const priceFetchTx = await priceFeed.fetchPrice() + + const statusAfter = await priceFeed.status() + assert.equal(statusAfter, '1') // status 1: using Tellor, Chainlink untrusted + }) + + it("C1 chainlinkWorking: Chainlink price drop of >50% and Tellor frozen: return last good price", async () => { + await setAddresses() + priceFeed.setLastGoodPrice(dec(1200, 18)) // establish a "last good price" from the previous price fetch + + const statusBefore = await priceFeed.status() + assert.equal(statusBefore, '0') // status 0: Chainlink working + + await mockRedstoneAdapterBase.setPrevPrice(dec(1000, 8)) // prev price = 1000 + await mockRedstoneAdapterBase.setPrice(dec(100, 8)) // price drops to 100: a drop of > 50% from previous + await setTellorPrice(dec(100, 8)) + + // 4 hours pass with no Tellor updates + await th.fastForwardTime(14400, web3.currentProvider) + + // check Tellor price timestamp is out of date by > 4 hours + const now = await th.getLatestBlockTimestamp(web3) + const tellorUpdateTime = (await tellorCaller.getTellorCurrentValue.call())[2] + assert.isTrue(tellorUpdateTime.lt(toBN(now).sub(toBN(14400)))) + + await mockRedstoneAdapterBase.setUpdateTime(now) + + const priceFetchTx = await priceFeed.fetchPrice() + let price = await priceFeed.lastGoodPrice() + + // Check that the returned price is the last good price + assert.equal(price, dec(1200, 18)) + }) + + //--- Chainlink fails and Tellor is broken --- + + it("C1 chainlinkWorking: Chainlink price drop of >50% and Tellor is broken by 0 price: switch to bothOracleSuspect", async () => { + await setAddresses() + const statusBefore = await priceFeed.status() + assert.equal(statusBefore, '0') // status 0: Chainlink working + + await mockRedstoneAdapterBase.setPrevPrice(dec(2, 8)) // price = 2 + await mockRedstoneAdapterBase.setPrice(99999999) // price drops to 0.99999999: a drop of > 50% from previous + + // Make mock Tellor return 0 price + await setTellorPrice(0) + + const priceFetchTx = await priceFeed.fetchPrice() + + const statusAfter = await priceFeed.status() + assert.equal(statusAfter, '2') // status 2: both oracles untrusted + }) + + it("C1 chainlinkWorking: Chainlink price drop of >50% and Tellor is broken by 0 price: return last good price", async () => { + await setAddresses() + priceFeed.setLastGoodPrice(dec(1200, 18)) // establish a "last good price" from the previous price fetch + + const statusBefore = await priceFeed.status() + assert.equal(statusBefore, '0') // status 0: Chainlink working + + await setTellorPrice(dec(1300, 6)) + + // Make mock Chainlink price deviate too much + await mockRedstoneAdapterBase.setPrevPrice(dec(2, 8)) // price = 2 + await mockRedstoneAdapterBase.setPrice(99999999) // price drops to 0.99999999: a drop of > 50% from previous + + // Make mock Tellor return 0 price + await setTellorPrice(0) + + const priceFetchTx = await priceFeed.fetchPrice() + let price = await priceFeed.lastGoodPrice() + + // Check that the returned price is in fact the previous price + assert.equal(price, dec(1200, 18)) + }) + + it("C1 chainlinkWorking: Chainlink price drop of >50% and Tellor is broken by 0 timestamp: switch to bothOracleSuspect", async () => { + await setAddresses() + const statusBefore = await priceFeed.status() + assert.equal(statusBefore, '0') // status 0: Chainlink working + + // Make mock Chainlink price deviate too much + await mockRedstoneAdapterBase.setPrevPrice(dec(2, 8)) // price = 2 + await mockRedstoneAdapterBase.setPrice(99999999) // price drops to 0.99999999: a drop of > 50% from previous + + // Make mock Tellor return 0 timestamp + await mockTellor.setUpdateTime(0) + const priceFetchTx = await priceFeed.fetchPrice() + + const statusAfter = await priceFeed.status() + assert.equal(statusAfter, '2') // status 2: both oracles untrusted + }) + + it("C1 chainlinkWorking: Chainlink price drop of >50% and Tellor is broken by 0 timestamp: return last good price", async () => { + await setAddresses() + priceFeed.setLastGoodPrice(dec(1200, 18)) // establish a "last good price" from the previous price fetch + + const statusBefore = await priceFeed.status() + assert.equal(statusBefore, '0') // status 0: Chainlink working + + await setTellorPrice(dec(1300, 6)) + + // Make mock Chainlink price deviate too much + await mockRedstoneAdapterBase.setPrevPrice(dec(2, 8)) // price = 2 + await mockRedstoneAdapterBase.setPrice(99999999) // price drops to 0.99999999: a drop of > 50% from previous + + // Make mock Tellor return 0 timestamp + await mockTellor.setUpdateTime(0) + + const priceFetchTx = await priceFeed.fetchPrice() + let price = await priceFeed.lastGoodPrice() + + // Check that the returned price is in fact the previous price + assert.equal(price, dec(1200, 18)) + }) + + it("C1 chainlinkWorking: Chainlink price drop of >50% and Tellor is broken by stale timestamp: Pricefeed switches to bothOracleSuspect", async () => { + await setAddresses() + const statusBefore = await priceFeed.status() + assert.equal(statusBefore, '0') // status 0: Chainlink working + + // Make mock Chainlink price deviate too much + await mockRedstoneAdapterBase.setPrevPrice(dec(2, 8)) // price = 2 + await mockRedstoneAdapterBase.setPrice(99999999) // price drops to 0.99999999: a drop of > 50% from previous + + // Make mock Tellor return 0 timestamp + await mockTellor.setUpdateTime(0) + + const priceFetchTx = await priceFeed.fetchPrice() + + const statusAfter = await priceFeed.status() + assert.equal(statusAfter, '2') // status 2: both oracles untrusted + }) + + it("C1 chainlinkWorking: Chainlink price drop of >50% and Tellor is broken by stale timestamp: return last good price", async () => { + await setAddresses() + priceFeed.setLastGoodPrice(dec(1200, 18)) // establish a "last good price" from the previous price fetch + + const statusBefore = await priceFeed.status() + assert.equal(statusBefore, '0') // status 0: Chainlink working + + await setTellorPrice(dec(1300, 6)) + + // Make mock Chainlink price deviate too much + await mockRedstoneAdapterBase.setPrevPrice(dec(2, 8)) // price = 2 + await mockRedstoneAdapterBase.setPrice(99999999) // price drops to 0.99999999: a drop of > 50% from previous + + // Make mock stale Tellor + await th.fastForwardTime(24 * 60 * 60 + 1, web3.currentProvider) // 24 hours + + const priceFetchTx = await priceFeed.fetchPrice() + let price = await priceFeed.lastGoodPrice() + + // Check that the returned price is in fact the previous price + assert.equal(price, dec(1200, 18)) + }) + + // -- Chainlink is working + it("C1 chainlinkWorking: Chainlink is working and Tellor is working - remain on chainlinkWorking", async () => { + await setAddresses() + priceFeed.setLastGoodPrice(dec(1200, 18)) + + const statusBefore = await priceFeed.status() + assert.equal(statusBefore, '0') // status 0: Chainlink working + + await mockRedstoneAdapterBase.setPrevPrice(dec(101, 8)) + await mockRedstoneAdapterBase.setPrice(dec(102, 8)) + + await setTellorPrice(dec(103, 18)) + + const priceFetchTx = await priceFeed.fetchPrice() + + const statusAfter = await priceFeed.status() + assert.equal(statusAfter, '0') // status 0: Chainlink working + }) + + it("C1 chainlinkWorking: Chainlink is working and Tellor is working - return Chainlink price", async () => { + await setAddresses() + priceFeed.setLastGoodPrice(dec(1200, 18)) + + const statusBefore = await priceFeed.status() + assert.equal(statusBefore, '0') // status 0: Chainlink working + + await mockRedstoneAdapterBase.setPrevPrice(dec(101, 8)) + await mockRedstoneAdapterBase.setPrice(dec(102, 8)) + + await setTellorPrice(dec(103, 18)) + + const priceFetchTx = await priceFeed.fetchPrice() + let price = await priceFeed.lastGoodPrice() + + // Check that the returned price is current Chainlink price + assert.equal(price, dec(102, 18)) + }) + + it("C1 chainlinkWorking: Chainlink is working and Tellor freezes - remain on chainlinkWorking", async () => { + await setAddresses() + priceFeed.setLastGoodPrice(dec(1200, 18)) + + const statusBefore = await priceFeed.status() + assert.equal(statusBefore, '0') // status 0: Chainlink working + + await mockRedstoneAdapterBase.setPrevPrice(dec(101, 8)) + await mockRedstoneAdapterBase.setPrice(dec(102, 8)) + + await setTellorPrice(dec(103, 18)) + + // 4 hours pass with no Tellor updates + await th.fastForwardTime(14400, web3.currentProvider) + + // check Tellor price timestamp is out of date by > 4 hours + const now = await th.getLatestBlockTimestamp(web3) + const tellorUpdateTime = (await tellorCaller.getTellorCurrentValue.call())[2] + assert.isTrue(tellorUpdateTime.lt(toBN(now).sub(toBN(14400)))) + + await mockRedstoneAdapterBase.setUpdateTime(now) // Chainlink's price is current + + const priceFetchTx = await priceFeed.fetchPrice() + + const statusAfter = await priceFeed.status() + assert.equal(statusAfter, '0') // status 0: Chainlink working + }) + + it("C1 chainlinkWorking: Chainlink is working and Tellor freezes - return Chainlink price", async () => { + await setAddresses() + priceFeed.setLastGoodPrice(dec(1200, 18)) + + const statusBefore = await priceFeed.status() + assert.equal(statusBefore, '0') // status 0: Chainlink working + + await mockRedstoneAdapterBase.setPrevPrice(dec(101, 8)) + await mockRedstoneAdapterBase.setPrice(dec(102, 8)) + + await setTellorPrice(dec(103, 18)) + + // 4 hours pass with no Tellor updates + await th.fastForwardTime(14400, web3.currentProvider) + + // check Tellor price timestamp is out of date by > 4 hours + const now = await th.getLatestBlockTimestamp(web3) + const tellorUpdateTime = (await tellorCaller.getTellorCurrentValue.call())[2] + assert.isTrue(tellorUpdateTime.lt(toBN(now).sub(toBN(14400)))) + + await mockRedstoneAdapterBase.setUpdateTime(now) // Chainlink's price is current + + const priceFetchTx = await priceFeed.fetchPrice() + let price = await priceFeed.lastGoodPrice() + + // Check that the returned price is current Chainlink price + assert.equal(price, dec(102, 18)) + }) + + it("C1 chainlinkWorking: Chainlink is working and Tellor breaks: switch to usingChainlinkTellorUntrusted", async () => { + await setAddresses() + priceFeed.setLastGoodPrice(dec(1200, 18)) // establish a "last good price" from the previous price fetch + + const statusBefore = await priceFeed.status() + assert.equal(statusBefore, '0') // status 0: Chainlink working + + await mockRedstoneAdapterBase.setPrevPrice(dec(101, 8)) + await mockRedstoneAdapterBase.setPrice(dec(102, 8)) + + await setTellorPrice(0) + + const priceFetchTx = await priceFeed.fetchPrice() + + const statusAfter = await priceFeed.status() + assert.equal(statusAfter, '4') // status 4: Using Tellor, Chainlink untrusted + }) + + it("C1 chainlinkWorking: Chainlink is working and Tellor breaks: return Chainlink price", async () => { + await setAddresses() + priceFeed.setLastGoodPrice(dec(1200, 18)) // establish a "last good price" from the previous price fetch + + const statusBefore = await priceFeed.status() + assert.equal(statusBefore, '0') // status 0: Chainlink working + + await mockRedstoneAdapterBase.setPrevPrice(dec(101, 8)) + await mockRedstoneAdapterBase.setPrice(dec(102, 8)) + + await setTellorPrice(0) + + const priceFetchTx = await priceFeed.fetchPrice() + let price = await priceFeed.lastGoodPrice() + + // Check that the returned price is current Chainlink price + assert.equal(price, dec(102, 18)) + }) + + it("C1 chainlinkWorking: Chainlink is working, revert transaction", async () => { + await setAddresses() + const statusBefore = await priceFeed.status() + assert.equal(statusBefore, '0') // status 0: Chainlink working + + await assertRevert( + priceFeed.forceExitBothUntrustedStatus(false, { from: owner }), + "PriceFeed: at least one oracle is working" + ) + await assertRevert( + priceFeed.forceExitBothUntrustedStatus(true, { from: owner }), + "PriceFeed: at least one oracle is working" + ) + }) + + // --- Case 2: Using Tellor --- + + // Using Tellor, Tellor breaks + it("C2 usingTellorChainlinkUntrusted: Tellor breaks by zero price: switch to bothOraclesSuspect", async () => { + await setAddresses() + priceFeed.setStatus(1) // status 1: using Tellor, Chainlink untrusted + + await mockRedstoneAdapterBase.setPrevPrice(dec(999, 8)) + await mockRedstoneAdapterBase.setPrice(dec(999, 8)) + + await priceFeed.setLastGoodPrice(dec(123, 18)) + + const now = await th.getLatestBlockTimestamp(web3) + await mockTellor.setUpdateTime(now) + await setTellorPrice(0) + + await priceFeed.fetchPrice() + + const status = await priceFeed.status() + assert.equal(status, 2) // status 2: both oracles untrusted + }) + + it("C2 usingTellorChainlinkUntrusted: Tellor breaks by zero price: return last good price", async () => { + await setAddresses() + priceFeed.setStatus(1) // status 1: using Tellor, Chainlink untrusted + + await mockRedstoneAdapterBase.setPrevPrice(dec(999, 8)) + await mockRedstoneAdapterBase.setPrice(dec(999, 8)) + + await priceFeed.setLastGoodPrice(dec(123, 18)) + + const now = await th.getLatestBlockTimestamp(web3) + await mockTellor.setUpdateTime(now) + await setTellorPrice(0) + + await priceFeed.fetchPrice() + const price = await priceFeed.lastGoodPrice() + + assert.equal(price, dec(123, 18)) + }) + + // Using Tellor, Tellor breaks + it("C2 usingTellorChainlinkUntrusted: Tellor breaks by call reverted: switch to bothOraclesSuspect", async () => { + // deploy broken mock + mockTellor = await BrokenMockTellor.new(queryData) + BrokenMockTellor.setAsDeployed(mockTellor) + tellorCaller = await TellorCaller.new(mockTellor.address, queryId) + await setAddresses() + + priceFeed.setStatus(1) // status 1: using Tellor, Chainlink untrusted + + await priceFeed.setLastGoodPrice(dec(123, 18)) + + await mockRedstoneAdapterBase.setPrevPrice(dec(999, 8)) + await mockRedstoneAdapterBase.setPrice(dec(999, 8)) + + await priceFeed.fetchPrice() + + const status = await priceFeed.status() + assert.equal(status, 2) // status 2: both oracles untrusted + }) + + it("C2 usingTellorChainlinkUntrusted: Tellor breaks by call reverted: return last good price", async () => { + // deploy broken mock + mockTellor = await BrokenMockTellor.new(queryData) + BrokenMockTellor.setAsDeployed(mockTellor) + tellorCaller = await TellorCaller.new(mockTellor.address, queryId) + await setAddresses() + + priceFeed.setStatus(1) // status 1: using Tellor, Chainlink untrusted + + await priceFeed.setLastGoodPrice(dec(123, 18)) + + await mockRedstoneAdapterBase.setPrevPrice(dec(999, 8)) + await mockRedstoneAdapterBase.setPrice(dec(999, 8)) + + await priceFeed.fetchPrice() + const price = await priceFeed.lastGoodPrice() + + assert.equal(price, dec(123, 18)) + }) + + // Using Tellor, Tellor breaks + it("C2 usingTellorChainlinkUntrusted: Tellor breaks by zero timestamp: switch to bothOraclesSuspect", async () => { + await setAddresses() + priceFeed.setStatus(1) // status 1: using Tellor, Chainlink untrusted + + await priceFeed.setLastGoodPrice(dec(123, 18)) + + await mockRedstoneAdapterBase.setPrevPrice(dec(999, 8)) + await mockRedstoneAdapterBase.setPrice(dec(999, 8)) + await setTellorPrice(dec(999, 6)) + + await mockTellor.setUpdateTime(0) + + await priceFeed.fetchPrice() + + const status = await priceFeed.status() + assert.equal(status, 2) // status 2: both oracles untrusted + }) + + it("C2 usingTellorChainlinkUntrusted: Tellor breaks by zero timestamp: return last good price", async () => { + await setAddresses() + priceFeed.setStatus(1) // status 1: using Tellor, Chainlink untrusted + + await priceFeed.setLastGoodPrice(dec(123, 18)) + + await mockRedstoneAdapterBase.setPrevPrice(dec(999, 8)) + await mockRedstoneAdapterBase.setPrice(dec(999, 8)) + await setTellorPrice(dec(999, 6)) + + await mockTellor.setUpdateTime(0) + + await priceFeed.fetchPrice() + const price = await priceFeed.lastGoodPrice() + + assert.equal(price, dec(123, 18)) + }) + + // Using Tellor, Tellor freezes + it("C2 usingTellorChainlinkUntrusted: Tellor freezes - remain usingChainlinkTellorUntrusted", async () => { + await setAddresses() + priceFeed.setStatus(1) // status 1: using Tellor, Chainlink untrusted + + await mockRedstoneAdapterBase.setPrevPrice(dec(999, 8)) + await mockRedstoneAdapterBase.setPrice(dec(999, 8)) + + await priceFeed.setLastGoodPrice(dec(246, 18)) + + await setTellorPrice(dec(123, 6)) + + await th.fastForwardTime(14400, web3.currentProvider) // Fast forward 4 hours + + // check Tellor price timestamp is out of date by > 4 hours + const now = await th.getLatestBlockTimestamp(web3) + const tellorUpdateTime = (await tellorCaller.getTellorCurrentValue.call())[2] + assert.isTrue(tellorUpdateTime.lt(toBN(now).sub(toBN(14400)))) + + await mockRedstoneAdapterBase.setUpdateTime(now) + + await priceFeed.fetchPrice() + + const status = await priceFeed.status() + assert.equal(status, 1) // status 1: using Tellor, Chainlink untrusted + }) + + it("C2 usingTellorChainlinkUntrusted: Tellor freezes - return last good price", async () => { + await setAddresses() + priceFeed.setStatus(1) // status 1: using Tellor, Chainlink untrusted + + await mockRedstoneAdapterBase.setPrevPrice(dec(999, 8)) + await mockRedstoneAdapterBase.setPrice(dec(999, 8)) + + await priceFeed.setLastGoodPrice(dec(246, 18)) + + await setTellorPrice(dec(123, 6)) + + await th.fastForwardTime(14400, web3.currentProvider) // Fast forward 4 hours + + // check Tellor price timestamp is out of date by > 4 hours + const now = await th.getLatestBlockTimestamp(web3) + const tellorUpdateTime = (await tellorCaller.getTellorCurrentValue.call())[2] + assert.isTrue(tellorUpdateTime.lt(toBN(now).sub(toBN(14400)))) + + await mockRedstoneAdapterBase.setUpdateTime(now) + + await priceFeed.fetchPrice() + const price = await priceFeed.lastGoodPrice() + + assert.equal(price, dec(246, 18)) + }) + + // Using Tellor, both Chainlink & Tellor go live + + it("C2 usingTellorChainlinkUntrusted: both Tellor and Chainlink are live and <= 5% price difference - switch to chainlinkWorking", async () => { + await setAddresses() + priceFeed.setStatus(1) // status 1: using Tellor, Chainlink untrusted + + await setTellorPrice(dec(100, 6)) // price = 100 + await mockRedstoneAdapterBase.setPrice(dec(105, 8)) // price = 105: 5% difference from Chainlink + + await priceFeed.fetchPrice() + + const status = await priceFeed.status() + assert.equal(status, 0) // status 0: Chainlink working + }) + + it("C2 usingTellorChainlinkUntrusted: both Tellor and Chainlink are live and <= 5% price difference - return Chainlink price", async () => { + await setAddresses() + priceFeed.setStatus(1) // status 1: using Tellor, Chainlink untrusted + + await setTellorPrice(dec(100, 6)) // price = 100 + await mockRedstoneAdapterBase.setPrice(dec(105, 8)) // price = 105: 5% difference from Chainlink + + await priceFeed.fetchPrice() + + const price = await priceFeed.lastGoodPrice() + assert.equal(price, dec(105, 18)) + }) + + it("C2 usingTellorChainlinkUntrusted: both Tellor and Chainlink are live and > 5% price difference - remain usingChainlinkTellorUntrusted", async () => { + await setAddresses() + priceFeed.setStatus(1) // status 1: using Tellor, Chainlink untrusted + + await setTellorPrice(dec(100, 6)) // price = 100 + await mockRedstoneAdapterBase.setPrice('10500000001') // price = 105.00000001: > 5% difference from Tellor + + await priceFeed.fetchPrice() + + const status = await priceFeed.status() + assert.equal(status, 1) // status 1: using Tellor, Chainlink untrusted + }) + + it("C2 usingTellorChainlinkUntrusted: both Tellor and Chainlink are live and > 5% price difference - return Tellor price", async () => { + await setAddresses() + priceFeed.setStatus(1) // status 1: using Tellor, Chainlink untrusted + + await setTellorPrice(dec(100, 6)) // price = 100 + await mockRedstoneAdapterBase.setPrice('10500000001') // price = 105.00000001: > 5% difference from Tellor + + await priceFeed.fetchPrice() + + const price = await priceFeed.lastGoodPrice() + assert.equal(price, dec(100, 18)) + }) + + it("C2 usingTellorChainlinkUntrusted: Tellor is working, revert transaction", async () => { + await setAddresses() + priceFeed.setStatus(1) // status 1: using Tellor, Chainlink untrusted + + await assertRevert( + priceFeed.forceExitBothUntrustedStatus(false, { from: owner }), + "PriceFeed: at least one oracle is working" + ) + await assertRevert( + priceFeed.forceExitBothUntrustedStatus(true, { from: owner }), + "PriceFeed: at least one oracle is working" + ) + }) + + + // --- Case 3: Both Oracles suspect + + it("C3 bothOraclesUntrusted: both Tellor and Chainlink are live and > 5% price difference remain bothOraclesSuspect", async () => { + await setAddresses() + priceFeed.setStatus(2) // status 2: both oracles untrusted + + await priceFeed.setLastGoodPrice(dec(50, 18)) + + await setTellorPrice(dec(100, 6)) // price = 100 + await mockRedstoneAdapterBase.setPrice('10500000001') // price = 105.00000001: > 5% difference from Tellor + + await priceFeed.fetchPrice() + + const status = await priceFeed.status() + assert.equal(status, 2) // status 2: both oracles untrusted + }) + + it("C3 bothOraclesUntrusted: both Tellor and Chainlink are live and > 5% price difference, return last good price", async () => { + await setAddresses() + priceFeed.setStatus(2) // status 2: both oracles untrusted + + await priceFeed.setLastGoodPrice(dec(50, 18)) + + await setTellorPrice(dec(100, 6)) // price = 100 + await mockRedstoneAdapterBase.setPrice('10500000001') // price = 105.00000001: > 5% difference from Tellor + + await priceFeed.fetchPrice() + const price = await priceFeed.lastGoodPrice() + + assert.equal(price, dec(50, 18)) + }) + + it("C3 bothOraclesUntrusted: both Tellor and Chainlink are live and <= 5% price difference, switch to chainlinkWorking", async () => { + await setAddresses() + priceFeed.setStatus(2) // status 2: both oracles untrusted + + await setTellorPrice(dec(100, 6)) // price = 100 + await mockRedstoneAdapterBase.setPrice(dec(105, 8)) // price = 105: 5% difference from Tellor + + await priceFeed.fetchPrice() + + const status = await priceFeed.status() + assert.equal(status, 0) // status 0: Chainlink working + }) + + it("C3 bothOraclesUntrusted: both Tellor and Chainlink are live and <= 5% price difference, return Chainlink price", async () => { + await setAddresses() + priceFeed.setStatus(2) // status 2: both oracles untrusted + + await setTellorPrice(dec(100, 6)) // price = 100 + await mockRedstoneAdapterBase.setPrice(dec(105, 8)) // price = 105: 5% difference from Tellor + + await priceFeed.fetchPrice() + + const price = await priceFeed.lastGoodPrice() + assert.equal(price, dec(105, 18)) + }) + + it("C3 bothOraclesUntrusted: both Tellor and Chainlink are broken, reverts transaction", async () => { + await setAddresses() + priceFeed.setStatus(2) // status 2: both oracles untrusted + + await setTellorPrice(0) // Tellor is broken + await mockRedstoneAdapterBase.setUpdateTime(0) // ChainLink is broken + + await assertRevert( + priceFeed.forceExitBothUntrustedStatus(false, { from: owner }), + "PriceFeed: both oracles are still untrusted" + ) + await assertRevert( + priceFeed.forceExitBothUntrustedStatus(true, { from: owner }), + "PriceFeed: both oracles are still untrusted" + ) + }) + + it("C3 bothOraclesUntrusted: Chainlink back online, check ChainLink first, switch to usingChainlinkTellorUntrusted", async () => { + await setAddresses() + priceFeed.setStatus(2) // status 2: both oracles untrusted + + await setTellorPrice(0) // Tellor is broken + await mockRedstoneAdapterBase.setPrevPrice(dec(100, 8)) + await mockRedstoneAdapterBase.setPrice(dec(105, 8)) // price = 105: 5% difference from previous + + await priceFeed.forceExitBothUntrustedStatus(false, { from: owner }) + + const status = await priceFeed.status() + assert.equal(status, 4) // status 4: using chainlink, tellor broken + + const price = await priceFeed.lastGoodPrice() + assert.equal(price, dec(105, 18)) + }) + + it("C3 bothOraclesUntrusted: Chainlink back online, check Tellor first, switch to usingChainlinkTellorUntrusted", async () => { + await setAddresses() + priceFeed.setStatus(2) // status 2: both oracles untrusted + + await setTellorPrice(dec(123, 6)) // Tellor frozen + await th.fastForwardTime(14400, web3.currentProvider) // Fast forward 4 hours + + await mockRedstoneAdapterBase.setPrevPrice(dec(199, 8)) + await mockRedstoneAdapterBase.setPrice(dec(100, 8)) // price = 100: 5% difference from previous + const now = await th.getLatestBlockTimestamp(web3) + await mockRedstoneAdapterBase.setUpdateTime(now) + + await priceFeed.forceExitBothUntrustedStatus(true, { from: owner }) + + const status = await priceFeed.status() + assert.equal(status, 4) // status 4: using chainlink, tellor broken + + const price = await priceFeed.lastGoodPrice() + assert.equal(price, dec(100, 18)) + }) + + it("C3 bothOraclesUntrusted: Tellor back online, check Chainlink first, switch to usingTellorChainlinkUntrusted", async () => { + await setAddresses() + priceFeed.setStatus(2) // status 2: both oracles untrusted + + await setTellorPrice(dec(123, 6)) // Tellor is online + await mockRedstoneAdapterBase.setPrevPrice(dec(201, 8)) + await mockRedstoneAdapterBase.setPrice(dec(100, 8)) // ChainLink: difference from previous more than 50% + + await priceFeed.forceExitBothUntrustedStatus(false, { from: owner }) + + const status = await priceFeed.status() + assert.equal(status, 1) // status 1: using tellor, chainlink broken + + const price = await priceFeed.lastGoodPrice() + assert.equal(price.toString(), dec(123, 18)) + }) + + it("C3 bothOraclesUntrusted: Tellor back online, check Tellor first, switch to usingTellorChainlinkUntrusted", async () => { + await setAddresses() + priceFeed.setStatus(2) // status 2: both oracles untrusted + + await mockRedstoneAdapterBase.setPrevPrice(dec(100, 8)) + await mockRedstoneAdapterBase.setPrice(dec(100, 8)) // ChainLink frozen + + await th.fastForwardTime(14400, web3.currentProvider) // Fast forward 4 hours + + // check Chainlink price timestamp is out of date by > 4 hours + const now = await th.getLatestBlockTimestamp(web3) + const chainlinkUpdateTime = (await priceFeedBase.latestRoundData())[3] + assert.isTrue(chainlinkUpdateTime.lt(toBN(now).sub(toBN(14400)))) + + // set Tellor to current time + await setTellorPrice(dec(123, 6)) // Tellor is online + + await priceFeed.forceExitBothUntrustedStatus(true, { from: owner }) + + const status = await priceFeed.status() + assert.equal(status, 1) // status 1: using tellor, chainlink broken + + const price = await priceFeed.lastGoodPrice() + assert.equal(price.toString(), dec(123, 18)) + }) + + it("C3 bothOraclesUntrusted: both oracles online with different prices, check ChainLink first, switch to usingChainlinkTellorUntrusted", async () => { + await setAddresses() + priceFeed.setStatus(2) // status 2: both oracles untrusted + + await mockRedstoneAdapterBase.setPrevPrice(dec(100, 8)) + await mockRedstoneAdapterBase.setPrice(dec(100, 8)) // ChainLink is online + await setTellorPrice(dec(110, 6)) // Tellor is online, differenece > 5% + + await priceFeed.forceExitBothUntrustedStatus(false, { from: owner }) + + const status = await priceFeed.status() + assert.equal(status, 4) // status 4: using chainlink, tellor broken + + const price = await priceFeed.lastGoodPrice() + assert.equal(price.toString(), dec(100, 18)) + }) + + it("C3 bothOraclesUntrusted: both oracles online with different prices, check Tellor first, switch to usingTellorChainlinkUntrusted", async () => { + await setAddresses() + priceFeed.setStatus(2) // status 2: both oracles untrusted + + await mockRedstoneAdapterBase.setPrevPrice(dec(100, 8)) + await mockRedstoneAdapterBase.setPrice(dec(100, 8)) // ChainLink is online + await setTellorPrice(dec(110, 6)) // Tellor is online, differenece > 5% + + await priceFeed.forceExitBothUntrustedStatus(true, { from: owner }) + + const status = await priceFeed.status() + assert.equal(status, 1) // status 1: using tellor, chainlink broken + + const price = await priceFeed.lastGoodPrice() + assert.equal(price.toString(), dec(110, 18)) + }) + + // --- Case 4 --- + it("C4 usingTellorChainlinkFrozen: when both Chainlink and Tellor break, switch to bothOraclesSuspect", async () => { + await setAddresses() + priceFeed.setStatus(3) // status 3: using Tellor, Chainlink frozen + + await mockRedstoneAdapterBase.setPrevPrice(dec(999, 8)) + + // Both Chainlink and Tellor break with 0 price + await mockRedstoneAdapterBase.setPrice(0) + await setTellorPrice(0) + + await priceFeed.fetchPrice() + + const status = await priceFeed.status() + assert.equal(status, 2) // status 2: both oracles untrusted + }) + + it("C4 usingTellorChainlinkFrozen: when both Chainlink and Tellor break, return last good price", async () => { + await setAddresses() + priceFeed.setStatus(2) // status 2: using tellor, chainlink frozen + + await priceFeed.setLastGoodPrice(dec(50, 18)) + + await mockRedstoneAdapterBase.setPrevPrice(dec(999, 8)) + + // Both Chainlink and Tellor break with 0 price + await mockRedstoneAdapterBase.setPrice(dec(0)) + await setTellorPrice(dec(0)) + + await priceFeed.fetchPrice() + + const price = await priceFeed.lastGoodPrice() + assert.equal(price, dec(50, 18)) + }) + + it("C4 usingTellorChainlinkFrozen: when Chainlink breaks and Tellor freezes, switch to usingChainlinkTellorUntrusted", async () => { + await setAddresses() + priceFeed.setStatus(3) // status 3: using Tellor, Chainlink frozen + + await priceFeed.setLastGoodPrice(dec(50, 18)) + + await mockRedstoneAdapterBase.setPrevPrice(dec(999, 8)) + + // Chainlink breaks + await mockRedstoneAdapterBase.setPrice(dec(0)) + + await setTellorPrice(dec(123, 6)) + + await th.fastForwardTime(14400, web3.currentProvider) // Fast forward 4 hours + + // check Tellor price timestamp is out of date by > 4 hours + const now = await th.getLatestBlockTimestamp(web3) + const tellorUpdateTime = (await tellorCaller.getTellorCurrentValue.call())[2] + assert.isTrue(tellorUpdateTime.lt(toBN(now).sub(toBN(14400)))) + + await priceFeed.fetchPrice() + + const status = await priceFeed.status() + assert.equal(status, 1) // status 1: using Tellor, Chainlink untrusted + }) + + it("C4 usingTellorChainlinkFrozen: when Chainlink breaks and Tellor freezes, return last good price", async () => { + await setAddresses() + priceFeed.setStatus(3) // status 3: using Tellor, Chainlink frozen + + await priceFeed.setLastGoodPrice(dec(50, 18)) + + await mockRedstoneAdapterBase.setPrevPrice(dec(999, 8)) + + // Chainlink breaks + await mockRedstoneAdapterBase.setPrice(dec(0)) + + await setTellorPrice(dec(123, 6)) + + await th.fastForwardTime(14400, web3.currentProvider) // Fast forward 4 hours + + // check Tellor price timestamp is out of date by > 4 hours + const now = await th.getLatestBlockTimestamp(web3) + const tellorUpdateTime = (await tellorCaller.getTellorCurrentValue.call())[2] + assert.isTrue(tellorUpdateTime.lt(toBN(now).sub(toBN(14400)))) + + await priceFeed.fetchPrice() + + const price = await priceFeed.lastGoodPrice() + assert.equal(price, dec(50, 18)) + }) + + it("C4 usingTellorChainlinkFrozen: when Chainlink breaks and Tellor live, switch to usingChainlinkTellorUntrusted", async () => { + await setAddresses() + priceFeed.setStatus(3) // status 3: using Tellor, Chainlink frozen + + await priceFeed.setLastGoodPrice(dec(50, 18)) + + await mockRedstoneAdapterBase.setPrevPrice(dec(999, 8)) + + // Chainlink breaks + await mockRedstoneAdapterBase.setPrice(dec(0)) + + await setTellorPrice(dec(123, 6)) + + await th.fastForwardTime(14400, web3.currentProvider) // Fast forward 4 hours + + await priceFeed.fetchPrice() + + const status = await priceFeed.status() + assert.equal(status, 1) // status 1: using Tellor, Chainlink untrusted + }) + + it("C4 usingTellorChainlinkFrozen: when Chainlink breaks and Tellor live, return Tellor price", async () => { + await setAddresses() + priceFeed.setStatus(3) // status 3: using Tellor, Chainlink frozen + + await priceFeed.setLastGoodPrice(dec(50, 18)) + + await mockRedstoneAdapterBase.setPrevPrice(dec(999, 8)) + + // Chainlink breaks + await mockRedstoneAdapterBase.setPrice(dec(0)) + + await setTellorPrice(dec(123, 6)) + + await priceFeed.fetchPrice() + + const price = await priceFeed.lastGoodPrice() + assert.equal(price, dec(123, 18)) + }) + + it("C4 usingTellorChainlinkFrozen: when Chainlink is live and Tellor is live with <5% price difference, switch back to chainlinkWorking", async () => { + await setAddresses() + priceFeed.setStatus(3) // status 3: using Tellor, Chainlink frozen + + await priceFeed.setLastGoodPrice(dec(50, 18)) + + await mockRedstoneAdapterBase.setPrevPrice(dec(999, 8)) + await mockRedstoneAdapterBase.setPrice(dec(999, 8)) + + await setTellorPrice(dec(998, 6)) + + await priceFeed.fetchPrice() + + const status = await priceFeed.status() + assert.equal(status, 0) // status 0: Chainlink working + }) + + it("C4 usingTellorChainlinkFrozen: when Chainlink is live and Tellor is live with <5% price difference, return Chainlink current price", async () => { + await setAddresses() + priceFeed.setStatus(3) // status 3: using Tellor, Chainlink frozen + + await priceFeed.setLastGoodPrice(dec(50, 18)) + + await mockRedstoneAdapterBase.setPrevPrice(dec(999, 8)) + await mockRedstoneAdapterBase.setPrice(dec(999, 8)) + + await setTellorPrice(dec(998, 6)) + + await priceFeed.fetchPrice() + + const price = await priceFeed.lastGoodPrice() + assert.equal(price, dec(999, 18)) // Chainlink price + }) + + it("C4 usingTellorChainlinkFrozen: when Chainlink is live and Tellor is live with >5% price difference, switch back to usingChainlinkTellorUntrusted", async () => { + await setAddresses() + priceFeed.setStatus(3) // status 3: using Tellor, Chainlink frozen + + await priceFeed.setLastGoodPrice(dec(50, 18)) + + await mockRedstoneAdapterBase.setPrevPrice(dec(999, 8)) + await mockRedstoneAdapterBase.setPrice(dec(999, 8)) + + await setTellorPrice(dec(123, 6)) + + await priceFeed.fetchPrice() + + const status = await priceFeed.status() + assert.equal(status, 1) // status 1: Using Tellor, Chainlink untrusted + }) + + it("C4 usingTellorChainlinkFrozen: when Chainlink is live and Tellor is live with >5% price difference, return Chainlink current price", async () => { + await setAddresses() + priceFeed.setStatus(3) // status 3: using Tellor, Chainlink frozen + + await priceFeed.setLastGoodPrice(dec(50, 18)) + + await mockRedstoneAdapterBase.setPrevPrice(dec(999, 8)) + await mockRedstoneAdapterBase.setPrice(dec(999, 8)) + + await setTellorPrice(dec(123, 6)) + + await priceFeed.fetchPrice() + + const price = await priceFeed.lastGoodPrice() + assert.equal(price, dec(123, 18)) // Tellor price + }) + + it("C4 usingTellorChainlinkFrozen: when Chainlink is live and Tellor is live with similar price, switch back to chainlinkWorking", async () => { + await setAddresses() + priceFeed.setStatus(3) // status 3: using Tellor, Chainlink frozen + + await priceFeed.setLastGoodPrice(dec(50, 18)) + + await mockRedstoneAdapterBase.setPrevPrice(dec(999, 8)) + await mockRedstoneAdapterBase.setPrice(dec(999, 8)) + + await setTellorPrice(dec(998, 6)) + + await priceFeed.fetchPrice() + + const status = await priceFeed.status() + assert.equal(status, 0) // status 0: Chainlink working + }) + + it("C4 usingTellorChainlinkFrozen: when Chainlink is live and Tellor is live with similar price, return Chainlink current price", async () => { + await setAddresses() + priceFeed.setStatus(3) // status 3: using Tellor, Chainlink frozen + + await priceFeed.setLastGoodPrice(dec(50, 18)) + + await mockRedstoneAdapterBase.setPrevPrice(dec(999, 8)) + await mockRedstoneAdapterBase.setPrice(dec(999, 8)) + + await setTellorPrice(dec(998, 6)) + + await priceFeed.fetchPrice() + + const price = await priceFeed.lastGoodPrice() + assert.equal(price, dec(999, 18)) // Chainlink price + }) + + it("C4 usingTellorChainlinkFrozen: when Chainlink is live and Tellor breaks, switch to usingChainlinkTellorUntrusted", async () => { + await setAddresses() + priceFeed.setStatus(3) // status 3: using Tellor, Chainlink frozen + + await priceFeed.setLastGoodPrice(dec(50, 18)) + + await mockRedstoneAdapterBase.setPrevPrice(dec(999, 8)) + await mockRedstoneAdapterBase.setPrice(dec(999, 8)) + + await setTellorPrice(0) + + await priceFeed.fetchPrice() + + const status = await priceFeed.status() + assert.equal(status, 4) // status 4: Using Chainlink, Tellor untrusted + }) + + it("C4 usingTellorChainlinkFrozen: when Chainlink is live and Tellor breaks, return Chainlink current price", async () => { + await setAddresses() + priceFeed.setStatus(3) // status 3: using Tellor, Chainlink frozen + + await priceFeed.setLastGoodPrice(dec(50, 18)) + + await mockRedstoneAdapterBase.setPrevPrice(dec(999, 8)) + await mockRedstoneAdapterBase.setPrice(dec(999, 8)) + + await setTellorPrice(0) + + await priceFeed.fetchPrice() + + const price = await priceFeed.lastGoodPrice() + assert.equal(price, dec(999, 18)) + }) + + it("C4 usingTellorChainlinkFrozen: when Chainlink still frozen and Tellor breaks, switch to usingChainlinkTellorUntrusted", async () => { + await setAddresses() + priceFeed.setStatus(3) // status 3: using Tellor, Chainlink frozen + + await priceFeed.setLastGoodPrice(dec(50, 18)) + + await mockRedstoneAdapterBase.setPrevPrice(dec(999, 8)) + await mockRedstoneAdapterBase.setPrice(dec(999, 8)) + + await th.fastForwardTime(14400, web3.currentProvider) // Fast forward 4 hours + + // check Chainlink price timestamp is out of date by > 4 hours + const now = await th.getLatestBlockTimestamp(web3) + const chainlinkUpdateTime = (await priceFeedBase.latestRoundData())[3] + assert.isTrue(chainlinkUpdateTime.lt(toBN(now).sub(toBN(14400)))) + + // set tellor broken + await setTellorPrice(0) + await mockTellor.set + + await priceFeed.fetchPrice() + + const status = await priceFeed.status() + assert.equal(status, 4) // status 4: using Chainlink, Tellor untrusted + }) + + it("C4 usingTellorChainlinkFrozen: when Chainlink still frozen and Tellor broken, return last good price", async () => { + await setAddresses() + priceFeed.setStatus(3) // status 3: using Tellor, Chainlink frozen + + await priceFeed.setLastGoodPrice(dec(50, 18)) + + await mockRedstoneAdapterBase.setPrevPrice(dec(999, 8)) + await mockRedstoneAdapterBase.setPrice(dec(999, 8)) + + await th.fastForwardTime(14400, web3.currentProvider) // Fast forward 4 hours + + // check Chainlink price timestamp is out of date by > 4 hours + const now = await th.getLatestBlockTimestamp(web3) + const chainlinkUpdateTime = (await priceFeedBase.latestRoundData())[3] + assert.isTrue(chainlinkUpdateTime.lt(toBN(now).sub(toBN(14400)))) + + // set tellor broken + await setTellorPrice(0) + + await priceFeed.fetchPrice() + + const price = await priceFeed.lastGoodPrice() + assert.equal(price, dec(50, 18)) + }) + + it("C4 usingTellorChainlinkFrozen: when Chainlink still frozen and Tellor live, remain usingTellorChainlinkFrozen", async () => { + await setAddresses() + priceFeed.setStatus(3) // status 3: using Tellor, Chainlink frozen + + await priceFeed.setLastGoodPrice(dec(50, 18)) + + await mockRedstoneAdapterBase.setPrevPrice(dec(999, 8)) + await mockRedstoneAdapterBase.setPrice(dec(999, 8)) + + await setTellorPrice(dec(123, 6)) + + await th.fastForwardTime(14400, web3.currentProvider) // Fast forward 4 hours + + // check Chainlink price timestamp is out of date by > 4 hours + const now = await th.getLatestBlockTimestamp(web3) + const chainlinkUpdateTime = (await priceFeedBase.latestRoundData())[3] + assert.isTrue(chainlinkUpdateTime.lt(toBN(now).sub(toBN(14400)))) + + // set Tellor to current time + await mockTellor.setUpdateTime(now) + + await priceFeed.fetchPrice() + + const status = await priceFeed.status() + assert.equal(status, 3) // status 3: using Tellor, Chainlink frozen + }) + + it("C4 usingTellorChainlinkFrozen: when Chainlink still frozen and Tellor live, return Tellor price", async () => { + await setAddresses() + priceFeed.setStatus(3) // status 3: using Tellor, Chainlink frozen + + await priceFeed.setLastGoodPrice(dec(50, 18)) + + await mockRedstoneAdapterBase.setPrevPrice(dec(999, 8)) + await mockRedstoneAdapterBase.setPrice(dec(999, 8)) + + await th.fastForwardTime(14400, web3.currentProvider) // Fast forward 4 hours + + // check Chainlink price timestamp is out of date by > 4 hours + const now = await th.getLatestBlockTimestamp(web3) + const chainlinkUpdateTime = (await priceFeedBase.latestRoundData())[3] + assert.isTrue(chainlinkUpdateTime.lt(toBN(now).sub(toBN(14400)))) + + // set Tellor price + await setTellorPrice(dec(123, 6)) + + await priceFeed.fetchPrice() + + const price = await priceFeed.lastGoodPrice() + assert.equal(price, dec(123, 18)) + }) + + it("C4 usingTellorChainlinkFrozen: when Chainlink still frozen and Tellor freezes, remain usingTellorChainlinkFrozen", async () => { + await setAddresses() + priceFeed.setStatus(3) // status 3: using Tellor, Chainlink frozen + + await priceFeed.setLastGoodPrice(dec(50, 18)) + + await mockRedstoneAdapterBase.setPrevPrice(dec(999, 8)) + await mockRedstoneAdapterBase.setPrice(dec(999, 8)) + + await setTellorPrice(dec(123, 6)) + + await th.fastForwardTime(14400, web3.currentProvider) // Fast forward 4 hours + + // check Chainlink price timestamp is out of date by > 4 hours + const now = await th.getLatestBlockTimestamp(web3) + const chainlinkUpdateTime = (await priceFeedBase.latestRoundData())[3] + assert.isTrue(chainlinkUpdateTime.lt(toBN(now).sub(toBN(14400)))) + + // check Tellor price timestamp is out of date by > 4 hours + const tellorUpdateTime = (await tellorCaller.getTellorCurrentValue.call())[2] + assert.isTrue(tellorUpdateTime.lt(toBN(now).sub(toBN(14400)))) + + await priceFeed.fetchPrice() + + const status = await priceFeed.status() + assert.equal(status, 3) // status 3: using Tellor, Chainlink frozen + }) + + it("C4 usingTellorChainlinkFrozen: when Chainlink still frozen and Tellor freezes, return last good price", async () => { + await setAddresses() + priceFeed.setStatus(3) // status 3: using Tellor, Chainlink frozen + + await priceFeed.setLastGoodPrice(dec(50, 18)) + + await mockRedstoneAdapterBase.setPrevPrice(dec(999, 8)) + await mockRedstoneAdapterBase.setPrice(dec(999, 8)) + + await setTellorPrice(dec(123, 6)) + + await th.fastForwardTime(14400, web3.currentProvider) // Fast forward 4 hours + + // check Chainlink price timestamp is out of date by > 4 hours + const now = await th.getLatestBlockTimestamp(web3) + const chainlinkUpdateTime = (await priceFeedBase.latestRoundData())[3] + assert.isTrue(chainlinkUpdateTime.lt(toBN(now).sub(toBN(14400)))) + + // check Tellor price timestamp is out of date by > 4 hours + const tellorUpdateTime = (await tellorCaller.getTellorCurrentValue.call())[2] + assert.isTrue(tellorUpdateTime.lt(toBN(now).sub(toBN(14400)))) + + await priceFeed.fetchPrice() + + const price = await priceFeed.lastGoodPrice() + assert.equal(price, dec(50, 18)) + }) + + it("C4 usingTellorChainlinkFrozen: Tellor is working, revert transaction", async () => { + await setAddresses() + priceFeed.setStatus(3) // status 3: using Tellor, Chainlink frozen + + await assertRevert( + priceFeed.forceExitBothUntrustedStatus(false, { from: owner }), + "PriceFeed: at least one oracle is working" + ) + await assertRevert( + priceFeed.forceExitBothUntrustedStatus(true, { from: owner }), + "PriceFeed: at least one oracle is working" + ) + }) + + // --- Case 5 --- + it("C5 usingChainlinkTellorUntrusted: when Chainlink is live and Tellor price >5% - no status change", async () => { + await setAddresses() + priceFeed.setStatus(4) // status 4: using chainlink, Tellor untrusted + + await priceFeed.setLastGoodPrice(dec(246, 18)) + + await mockRedstoneAdapterBase.setPrevPrice(dec(999, 8)) + await mockRedstoneAdapterBase.setPrice(dec(999, 8)) + + await setTellorPrice(dec(123, 6)) // Greater than 5% difference with chainlink + + await priceFeed.fetchPrice() + + const status = await priceFeed.status() + assert.equal(status, 4) // status 4: using Chainlink, Tellor untrusted + }) + + it("C5 usingChainlinkTellorUntrusted: when Chainlink is live and Tellor price >5% - return Chainlink price", async () => { + await setAddresses() + priceFeed.setStatus(4) // status 4: using chainlink, Tellor untrusted + + await priceFeed.setLastGoodPrice(dec(246, 18)) + + await mockRedstoneAdapterBase.setPrevPrice(dec(999, 8)) + await mockRedstoneAdapterBase.setPrice(dec(999, 8)) + + await setTellorPrice(dec(123, 6)) // Greater than 5% difference with chainlink + + await priceFeed.fetchPrice() + + const price = await priceFeed.lastGoodPrice() + assert.equal(price, dec(999, 18)) + }) + + it("C5 usingChainlinkTellorUntrusted: when Chainlink is live and Tellor price within <5%, switch to chainlinkWorking", async () => { + await setAddresses() + priceFeed.setStatus(4) // status 4: using chainlink, Tellor untrusted + + await priceFeed.setLastGoodPrice(dec(246, 18)) + + await mockRedstoneAdapterBase.setPrevPrice(dec(999, 8)) + await mockRedstoneAdapterBase.setPrice(dec(999, 8)) + + await setTellorPrice(dec(998, 6)) // within 5% of Chainlink + + await priceFeed.fetchPrice() + + const status = await priceFeed.status() + assert.equal(status, 0) // status 0: Chainlink working + }) + + it("C5 usingChainlinkTellorUntrusted: when Chainlink is live, Tellor price not within 5%, return Chainlink price", async () => { + await setAddresses() + priceFeed.setStatus(4) // status 4: using chainlink, Tellor untrusted + + await priceFeed.setLastGoodPrice(dec(246, 18)) + + await mockRedstoneAdapterBase.setPrevPrice(dec(999, 8)) + await mockRedstoneAdapterBase.setPrice(dec(999, 8)) + + await setTellorPrice(dec(998, 6)) // within 5% of Chainlink + + await priceFeed.fetchPrice() + + const price = await priceFeed.lastGoodPrice() + assert.equal(price, dec(999, 18)) + }) + + // --------- + + it("C5 usingChainlinkTellorUntrusted: when Chainlink is live, <50% price deviation from previous, Tellor price not within 5%, remain on usingChainlinkTellorUntrusted", async () => { + await setAddresses() + priceFeed.setStatus(4) // status 4: using chainlink, Tellor untrusted + + await priceFeed.setLastGoodPrice(dec(246, 18)) + + await mockRedstoneAdapterBase.setPrevPrice(dec(999, 8)) + await mockRedstoneAdapterBase.setPrice(dec(998, 8)) + await setTellorPrice(dec(123, 6)) // Tellor not close to current Chainlink + + await priceFeed.fetchPrice() + + const status = await priceFeed.status() + assert.equal(status, 4) // status 4: using Chainlink, Tellor untrusted + }) + + it("C5 usingChainlinkTellorUntrusted: when Chainlink is live, <50% price deviation from previous, Tellor price not within 5%, return Chainlink price", async () => { + await setAddresses() + priceFeed.setStatus(4) // status 4: using chainlink, Tellor untrusted + + await priceFeed.setLastGoodPrice(dec(246, 18)) + + await mockRedstoneAdapterBase.setPrevPrice(dec(999, 8)) + await mockRedstoneAdapterBase.setPrice(dec(998, 8)) + await setTellorPrice(dec(123, 6)) // Tellor not close to current Chainlink + + await priceFeed.fetchPrice() + + const price = await priceFeed.lastGoodPrice() + assert.equal(price, dec(998, 18)) + }) + + it("C5 usingChainlinkTellorUntrusted: when Chainlink is live, >50% price deviation from previous, Tellor price not within 5%, remain on usingChainlinkTellorUntrusted", async () => { + await setAddresses() + priceFeed.setStatus(4) // status 4: using chainlink, Tellor untrusted + + await priceFeed.setLastGoodPrice(dec(246, 18)) + + await mockRedstoneAdapterBase.setPrevPrice(dec(200, 8)) + await mockRedstoneAdapterBase.setPrice(dec(99, 8)) // >50% price drop from previous Chainlink price + await setTellorPrice(dec(123, 6)) // Tellor not close to current Chainlink + + await priceFeed.fetchPrice() + + const status = await priceFeed.status() + assert.equal(status, 2) // status 2: both Oracles untrusted + }) + + it("C5 usingChainlinkTellorUntrusted: when Chainlink is live, >50% price deviation from previous, Tellor price not within 5%, return Chainlink price", async () => { + await setAddresses() + priceFeed.setStatus(4) // status 4: using chainlink, Tellor untrusted + + await priceFeed.setLastGoodPrice(dec(246, 18)) + + await mockRedstoneAdapterBase.setPrevPrice(dec(200, 8)) + await mockRedstoneAdapterBase.setPrice(dec(99, 8)) // >50% price drop from previous Chainlink price + await setTellorPrice(dec(123, 6)) // Tellor not close to current Chainlink + + await priceFeed.fetchPrice() + + const price = await priceFeed.lastGoodPrice() + assert.equal(price, dec(246, 18)) // last good price + }) + + // ------- + + it("C5 usingChainlinkTellorUntrusted: when Chainlink is live, <50% price deviation from previous, and Tellor is frozen, remain on usingChainlinkTellorUntrusted", async () => { + await setAddresses() + priceFeed.setStatus(4) // status 4: using chainlink, Tellor untrusted + + await priceFeed.setLastGoodPrice(dec(246, 18)) + + await mockRedstoneAdapterBase.setPrevPrice(dec(999, 8)) + await mockRedstoneAdapterBase.setPrice(dec(999, 8)) + + await setTellorPrice(dec(123, 6)) + + await th.fastForwardTime(14400, web3.currentProvider) // fast forward 4 hours + + // check Tellor price timestamp is out of date by > 4 hours + const now = await th.getLatestBlockTimestamp(web3) + const tellorUpdateTime = (await tellorCaller.getTellorCurrentValue.call())[2] + assert.isTrue(tellorUpdateTime.lt(toBN(now).sub(toBN(14400)))) + + await mockRedstoneAdapterBase.setPrice(dec(998, 8)) + await mockRedstoneAdapterBase.setUpdateTime(now) // Chainlink is current + + await priceFeed.fetchPrice() + + const status = await priceFeed.status() + assert.equal(status, 4) // status 4: using Chainlink, Tellor untrusted + }) + + it("C5 usingChainlinkTellorUntrusted: when Chainlink is live, <50% price deviation from previous, Tellor is frozen, return Chainlink price", async () => { + await setAddresses() + priceFeed.setStatus(4) // status 4: using chainlink, Tellor untrusted + + await priceFeed.setLastGoodPrice(dec(246, 18)) + + await mockRedstoneAdapterBase.setPrevPrice(dec(999, 8)) + await mockRedstoneAdapterBase.setPrice(dec(999, 8)) + + await setTellorPrice(dec(123, 6)) + + await th.fastForwardTime(14400, web3.currentProvider) // fast forward 4 hours + + // check Tellor price timestamp is out of date by > 4 hours + const now = await th.getLatestBlockTimestamp(web3) + const tellorUpdateTime = (await tellorCaller.getTellorCurrentValue.call())[2] + assert.isTrue(tellorUpdateTime.lt(toBN(now).sub(toBN(14400)))) + + await mockRedstoneAdapterBase.setPrice(dec(998, 8)) + await mockRedstoneAdapterBase.setUpdateTime(now) // Chainlink is current + + await priceFeed.fetchPrice() + + const price = await priceFeed.lastGoodPrice() + assert.equal(price, dec(998, 18)) + }) + + it("C5 usingChainlinkTellorUntrusted: when Chainlink is live, >50% price deviation from previous, Tellor is frozen, remain on usingChainlinkTellorUntrusted", async () => { + await setAddresses() + priceFeed.setStatus(4) // status 4: using chainlink, Tellor untrusted + + await priceFeed.setLastGoodPrice(dec(246, 18)) + + await mockRedstoneAdapterBase.setPrevPrice(dec(200, 8)) + await mockRedstoneAdapterBase.setPrice(dec(200, 8)) + + await setTellorPrice(dec(123, 6)) + + await th.fastForwardTime(14400, web3.currentProvider) // fast forward 4 hours + + // check Tellor price timestamp is out of date by > 4 hours + const now = await th.getLatestBlockTimestamp(web3) + const tellorUpdateTime = (await tellorCaller.getTellorCurrentValue.call())[2] + assert.isTrue(tellorUpdateTime.lt(toBN(now).sub(toBN(14400)))) + + await mockRedstoneAdapterBase.setPrice(dec(99, 8)) // >50% price drop from previous Chainlink price + await mockRedstoneAdapterBase.setUpdateTime(now) // Chainlink is current + + await priceFeed.fetchPrice() + + const status = await priceFeed.status() + assert.equal(status, 2) // status 2: both Oracles untrusted + }) + + it("C5 usingChainlinkTellorUntrusted: when Chainlink is live, >50% price deviation from previous, Tellor is frozen, return Chainlink price", async () => { + await setAddresses() + priceFeed.setStatus(4) // status 4: using chainlink, Tellor untrusted + + await priceFeed.setLastGoodPrice(dec(246, 18)) + + await mockRedstoneAdapterBase.setPrevPrice(dec(200, 8)) + await mockRedstoneAdapterBase.setPrice(dec(200, 8)) + + await setTellorPrice(dec(123, 6)) + + await th.fastForwardTime(14400, web3.currentProvider) // fast forward 4 hours + + // check Tellor price timestamp is out of date by > 4 hours + const now = await th.getLatestBlockTimestamp(web3) + const tellorUpdateTime = (await tellorCaller.getTellorCurrentValue.call())[2] + assert.isTrue(tellorUpdateTime.lt(toBN(now).sub(toBN(14400)))) + + await mockRedstoneAdapterBase.setPrice(dec(99, 8)) // > 50% price drop from previous Chainlink price + await mockRedstoneAdapterBase.setUpdateTime(now) // Chainlink is current + + await priceFeed.fetchPrice() + + const price = await priceFeed.lastGoodPrice() + assert.equal(price, dec(246, 18)) // last good price + }) + + it("C5 usingChainlinkTellorUntrusted: when Chainlink frozen, remain on usingChainlinkTellorUntrusted", async () => { + await setAddresses() + priceFeed.setStatus(4) // status 4: using chainlink, Tellor untrusted + + await priceFeed.setLastGoodPrice(dec(246, 18)) + + await mockRedstoneAdapterBase.setPrevPrice(dec(999, 8)) + await mockRedstoneAdapterBase.setPrice(dec(999, 8)) + + await setTellorPrice(dec(123, 6)) + + await th.fastForwardTime(14400, web3.currentProvider) // Fast forward 4 hours + + // check Chainlink price timestamp is out of date by > 4 hours + const now = await th.getLatestBlockTimestamp(web3) + const chainlinkUpdateTime = (await priceFeedBase.latestRoundData())[3] + assert.isTrue(chainlinkUpdateTime.lt(toBN(now).sub(toBN(14400)))) + + await priceFeed.fetchPrice() + + const status = await priceFeed.status() + assert.equal(status, 4) // status 4: using Chainlink, Tellor untrusted + }) + + it("C5 usingChainlinkTellorUntrusted: when Chainlink frozen, return last good price", async () => { + await setAddresses() + priceFeed.setStatus(4) // status 4: using Chainlink, Tellor untrusted + + await priceFeed.setLastGoodPrice(dec(246, 18)) + + await mockRedstoneAdapterBase.setPrevPrice(dec(999, 8)) + await mockRedstoneAdapterBase.setPrice(dec(999, 8)) + + await setTellorPrice(dec(123, 6)) + + await th.fastForwardTime(14400, web3.currentProvider) // Fast forward 4 hours + + // check Chainlink price timestamp is out of date by > 4 hours + const now = await th.getLatestBlockTimestamp(web3) + const chainlinkUpdateTime = (await priceFeedBase.latestRoundData())[3] + assert.isTrue(chainlinkUpdateTime.lt(toBN(now).sub(toBN(14400)))) + + await priceFeed.fetchPrice() + + const price = await priceFeed.lastGoodPrice() + assert.equal(price, dec(246, 18)) + }) + + it("C5 usingChainlinkTellorUntrusted: when Chainlink breaks too, switch to bothOraclesSuspect", async () => { + await setAddresses() + priceFeed.setStatus(4) // status 4: using chainlink, Tellor untrusted + + await priceFeed.setLastGoodPrice(dec(246, 18)) + + await mockRedstoneAdapterBase.setPrevPrice(dec(999, 8)) + await mockRedstoneAdapterBase.setPrice(dec(999, 8)) + await mockRedstoneAdapterBase.setUpdateTime(0) // Chainlink breaks by 0 timestamp + + await setTellorPrice(dec(123, 6)) + + await priceFeed.fetchPrice() + + const status = await priceFeed.status() + assert.equal(status, 2) // status 2: both oracles untrusted + }) + + it("C5 usingChainlinkTellorUntrusted: Chainlink breaks too, return last good price", async () => { + await setAddresses() + priceFeed.setStatus(4) // status 4: using chainlink, Tellor untrusted + + await priceFeed.setLastGoodPrice(dec(246, 18)) + + await mockRedstoneAdapterBase.setPrevPrice(dec(999, 8)) + await mockRedstoneAdapterBase.setPrice(dec(999, 8)) + await mockRedstoneAdapterBase.setUpdateTime(0) // Chainlink breaks by 0 timestamp + + await setTellorPrice(dec(123, 6)) + + await priceFeed.fetchPrice() + + const price = await priceFeed.lastGoodPrice() + assert.equal(price, dec(246, 18)) + }) + + it("C5 usingChainlinkTellorUntrusted: Chainlink is working, revert transaction", async () => { + await setAddresses() + priceFeed.setStatus(4) // status 4: using chainlink, Tellor untrusted + + await assertRevert( + priceFeed.forceExitBothUntrustedStatus(false, { from: owner }), + "PriceFeed: at least one oracle is working" + ) + await assertRevert( + priceFeed.forceExitBothUntrustedStatus(true, { from: owner }), + "PriceFeed: at least one oracle is working" + ) + }) + }) + context("when Tellor feed has 6 decimals", () => { contextTestPriceFeed( 6 ) })