mirror of
https://github.com/luxfi/oracle.git
synced 2026-11-02 14:40:47 +00:00
257 lines
8.7 KiB
JavaScript
257 lines
8.7 KiB
JavaScript
const { web3 } = require("hardhat");
|
|
const { assert } = require("chai");
|
|
|
|
const { CoinGeckoPriceFeed } = require("../../dist/price-feed/CoinGeckoPriceFeed");
|
|
const { NetworkerMock } = require("../../dist/price-feed/NetworkerMock");
|
|
const winston = require("winston");
|
|
const { parseFixed } = require("@uma/common");
|
|
|
|
const Convert = (decimals) => (number) => parseFixed(number.toString().substring(0, decimals), decimals).toString();
|
|
|
|
describe("CoinGeckoPriceFeed.js", function () {
|
|
let coinGeckoPriceFeed;
|
|
let networker;
|
|
let mockTime;
|
|
|
|
const contractAddress = "0x6b175474e89094c44da98b954eedeac495271d0f";
|
|
const currency = "php";
|
|
const lookback = 120; // 2 minutes.
|
|
const getTime = () => mockTime;
|
|
const minTimeBetweenUpdates = 60;
|
|
const priceFeedDecimals = 18;
|
|
|
|
const { toWei, toBN } = web3.utils;
|
|
|
|
const mockPrice = 48.15;
|
|
const validResponse = { [contractAddress]: { [currency]: mockPrice } };
|
|
|
|
beforeEach(async function () {
|
|
networker = new NetworkerMock();
|
|
mockTime = new Date().getTime();
|
|
|
|
const dummyLogger = winston.createLogger({ level: "info", transports: [new winston.transports.Console()] });
|
|
|
|
coinGeckoPriceFeed = new CoinGeckoPriceFeed(
|
|
dummyLogger,
|
|
web3,
|
|
contractAddress,
|
|
currency,
|
|
lookback,
|
|
networker,
|
|
getTime,
|
|
minTimeBetweenUpdates,
|
|
false,
|
|
priceFeedDecimals
|
|
);
|
|
});
|
|
|
|
it("getCurrentPrice() returns the latest price", async function () {
|
|
networker.getJsonReturns = [validResponse];
|
|
|
|
await coinGeckoPriceFeed.update();
|
|
|
|
const price = coinGeckoPriceFeed.getCurrentPrice();
|
|
assert.equal(price.toString(), toWei(`${mockPrice}`));
|
|
});
|
|
|
|
it("getCurrentPrice() returns undefined if update() is never called", async function () {
|
|
const price = coinGeckoPriceFeed.getCurrentPrice();
|
|
assert.equal(price, undefined);
|
|
});
|
|
|
|
it("getHistoricalPrice() returns the price for the specified time", async function () {
|
|
// Run a series of updates()
|
|
networker.getJsonReturns = [
|
|
{ [contractAddress]: { [currency]: mockPrice } },
|
|
{ [contractAddress]: { [currency]: mockPrice + 1 } },
|
|
{ [contractAddress]: { [currency]: mockPrice + 2 } },
|
|
];
|
|
|
|
const originalMockTime = mockTime;
|
|
await coinGeckoPriceFeed.update(); // should produce { mockTime, mockPrice }
|
|
mockTime += 300;
|
|
await coinGeckoPriceFeed.update(); // should produce { mockTime + 300, mockPrice + 1 }
|
|
mockTime += 300;
|
|
await coinGeckoPriceFeed.update(); // should produce { mockTime + 600, mockPrice + 2 }
|
|
|
|
// Do assertions for each period
|
|
const price1 = await coinGeckoPriceFeed.getHistoricalPrice(originalMockTime);
|
|
assert.equal(price1.toString(), toWei(`${mockPrice}`));
|
|
|
|
const price2 = await coinGeckoPriceFeed.getHistoricalPrice(originalMockTime + 300);
|
|
assert.equal(price2.toString(), toWei(`${mockPrice + 1}`));
|
|
|
|
const price3 = await coinGeckoPriceFeed.getHistoricalPrice(originalMockTime + 600);
|
|
assert.equal(price3.toString(), toWei(`${mockPrice + 2}`));
|
|
});
|
|
|
|
it("getHistoricalPrice() throws error if update() is never called", async function () {
|
|
const didThrow = await coinGeckoPriceFeed.getHistoricalPrice(mockTime).catch(() => true);
|
|
assert.isTrue(didThrow, "getHistoricalPrice() didn't throw");
|
|
});
|
|
|
|
it("getHistoricalPrice() returns the price if the time is within the lookout window", async function () {
|
|
networker.getJsonReturns = [validResponse];
|
|
|
|
await coinGeckoPriceFeed.update();
|
|
|
|
const price = await coinGeckoPriceFeed.getHistoricalPrice(mockTime - lookback);
|
|
assert.equal(price.toString(), toWei(`${mockPrice}`));
|
|
});
|
|
|
|
it("getHistoricalPrice() throws error if the time is before the lookout window", async function () {
|
|
networker.getJsonReturns = [validResponse];
|
|
|
|
await coinGeckoPriceFeed.update();
|
|
|
|
const didThrow = await coinGeckoPriceFeed.getHistoricalPrice(mockTime - lookback - 1).catch(() => true);
|
|
assert.isTrue(didThrow, "getHistoricalPrice() didn't throw");
|
|
});
|
|
|
|
it("getHistoricalPrice() throws error if the time is after the lookout window", async function () {
|
|
networker.getJsonReturns = [validResponse];
|
|
|
|
await coinGeckoPriceFeed.update();
|
|
|
|
const didThrow = await coinGeckoPriceFeed.getHistoricalPrice(mockTime + 1).catch(() => true);
|
|
assert.isTrue(didThrow, "getHistoricalPrice() didn't throw");
|
|
});
|
|
|
|
it("getLastUpdateTime() returns the time when update() was last called", async function () {
|
|
networker.getJsonReturns = [validResponse];
|
|
|
|
await coinGeckoPriceFeed.update();
|
|
|
|
assert.equal(coinGeckoPriceFeed.getLastUpdateTime(), mockTime);
|
|
});
|
|
|
|
it("getLastUpdateTime() returns undefined if update() is never called", async function () {
|
|
assert.equal(coinGeckoPriceFeed.getLastUpdateTime(), undefined);
|
|
});
|
|
|
|
it("getPriceFeedDecimals() returns the correct value", async function () {
|
|
assert.equal(coinGeckoPriceFeed.getPriceFeedDecimals(), priceFeedDecimals);
|
|
});
|
|
|
|
it("getLookback() returns the correct value", async function () {
|
|
assert.equal(coinGeckoPriceFeed.getLookback(), lookback);
|
|
});
|
|
|
|
it("Handles bad API response properly", async function () {
|
|
networker.getJsonReturns = [{}];
|
|
|
|
const errorCatched = await coinGeckoPriceFeed.update().catch(() => true);
|
|
assert.isTrue(errorCatched, "Update didn't throw");
|
|
|
|
const price = coinGeckoPriceFeed.getCurrentPrice();
|
|
assert.equal(price, undefined);
|
|
|
|
const didThrow = await coinGeckoPriceFeed.getHistoricalPrice(mockTime).catch(() => true);
|
|
assert.isTrue(didThrow, "getHistoricalPrice() didn't throw");
|
|
});
|
|
|
|
it("Should not call API again if succeeding update() call is within minTimeBetweenUpdates", async function () {
|
|
networker.getJsonReturns = [
|
|
{ [contractAddress]: { [currency]: mockPrice } },
|
|
{ [contractAddress]: { [currency]: mockPrice + 1 } },
|
|
];
|
|
|
|
await coinGeckoPriceFeed.update();
|
|
|
|
const originalMockTime = mockTime;
|
|
mockTime += minTimeBetweenUpdates - 1;
|
|
await coinGeckoPriceFeed.update();
|
|
|
|
// Last update time should remain to be originalMockTime
|
|
const time = coinGeckoPriceFeed.getLastUpdateTime();
|
|
assert.equal(time, originalMockTime);
|
|
|
|
// Current price should remain to be mockPrice
|
|
const price = coinGeckoPriceFeed.getCurrentPrice();
|
|
assert.equal(price.toString(), toWei(`${mockPrice}`));
|
|
});
|
|
|
|
it("Has support for inverted price", async function () {
|
|
// Inverted CMC price feed setup
|
|
networker = new NetworkerMock();
|
|
mockTime = new Date().getTime();
|
|
|
|
const dummyLogger = winston.createLogger({ level: "info", transports: [new winston.transports.Console()] });
|
|
|
|
const cgInvertedPriceFeed = new CoinGeckoPriceFeed(
|
|
dummyLogger,
|
|
web3,
|
|
contractAddress,
|
|
currency,
|
|
lookback,
|
|
networker,
|
|
getTime,
|
|
minTimeBetweenUpdates,
|
|
true,
|
|
priceFeedDecimals
|
|
);
|
|
|
|
// Here comes the actual tests
|
|
networker.getJsonReturns = [validResponse];
|
|
|
|
await cgInvertedPriceFeed.update();
|
|
|
|
const invertedPrice = toBN(toWei("1"))
|
|
.mul(toBN(toWei("1")))
|
|
.div(toBN(toWei(`${mockPrice}`)))
|
|
.toString();
|
|
|
|
const price = cgInvertedPriceFeed.getCurrentPrice();
|
|
assert.equal(price.toString(), invertedPrice);
|
|
|
|
const historicalPrice = await cgInvertedPriceFeed.getHistoricalPrice(mockTime);
|
|
assert.equal(historicalPrice.toString(), invertedPrice);
|
|
});
|
|
|
|
it("Can handle non-18 decimal place precision", async function () {
|
|
// non-18 decimal price feed setup
|
|
const expectedPrice = 48.158716;
|
|
const expectedResponse = { [contractAddress]: { [currency]: expectedPrice } };
|
|
|
|
networker = new NetworkerMock();
|
|
mockTime = new Date().getTime();
|
|
|
|
const dummyLogger = winston.createLogger({ level: "info", transports: [new winston.transports.Console()] });
|
|
|
|
const cgSixDecimalPriceFeed = new CoinGeckoPriceFeed(
|
|
dummyLogger,
|
|
web3,
|
|
contractAddress,
|
|
currency,
|
|
lookback,
|
|
networker,
|
|
getTime,
|
|
minTimeBetweenUpdates,
|
|
false,
|
|
6 // e.g. USDC
|
|
);
|
|
|
|
// Here comes the actual tests
|
|
networker.getJsonReturns = [expectedResponse];
|
|
|
|
await cgSixDecimalPriceFeed.update();
|
|
|
|
const sixDecimalPrice = Convert(6)(expectedPrice);
|
|
|
|
const price = cgSixDecimalPriceFeed.getCurrentPrice();
|
|
assert.equal(price.toString(), sixDecimalPrice);
|
|
|
|
const historicalPrice = await cgSixDecimalPriceFeed.getHistoricalPrice(mockTime);
|
|
assert.equal(historicalPrice.toString(), sixDecimalPrice);
|
|
});
|
|
|
|
it("Produces correct url", async function () {
|
|
networker.getJsonReturns = [validResponse];
|
|
await coinGeckoPriceFeed.update();
|
|
|
|
assert.deepStrictEqual(networker.getJsonInputs, [
|
|
`https://api.coingecko.com/api/v3/simple/token_price/ethereum?contract_addresses=${contractAddress}&vs_currencies=${currency}`,
|
|
]);
|
|
});
|
|
});
|