mirror of
https://github.com/luxfi/oracle.git
synced 2026-11-02 14:40:47 +00:00
207 lines
6.8 KiB
JavaScript
207 lines
6.8 KiB
JavaScript
const { web3 } = require("hardhat");
|
|
const { assert } = require("chai");
|
|
const { ForexDailyPriceFeed } = require("../../dist/price-feed/ForexDailyPriceFeed");
|
|
const { NetworkerMock } = require("../../dist/price-feed/NetworkerMock");
|
|
const { spyLogIncludes, SpyTransport } = require("../../dist/logger/SpyTransport");
|
|
const winston = require("winston");
|
|
const moment = require("moment-timezone");
|
|
const { parseFixed } = require("@uma/common");
|
|
const sinon = require("sinon");
|
|
|
|
describe("ForexDailyPriceFeed.js", function () {
|
|
let forexPriceFeed;
|
|
// Keep test timezone consistent with price feed's. The API uses data published daily by the
|
|
// ECB at 16:00 CET. Therefore, to convert from datestring to unix,
|
|
// first convert to CET, and then add 16 hours, since the API "begins" days at 16:00.
|
|
let mockTime = moment.tz("2021-03-12", "YYYY-MM-DD", "Europe/Berlin").endOf("day").add(16, "hours").unix();
|
|
let networker;
|
|
let spy;
|
|
|
|
const base = "EUR";
|
|
const symbol = "USD";
|
|
const lookback = 0; // Lookback doesn't matter in this test since we populate the Networker
|
|
const minTimeBetweenUpdates = 43200; // 12 hours.
|
|
// with fake data.
|
|
const getTime = () => mockTime;
|
|
|
|
const { toBN } = web3.utils;
|
|
const pricePrecision = 6;
|
|
|
|
const convertPriceFeedDecimals = (number) => {
|
|
return toBN(parseFixed(number.toString().substring(0, pricePrecision), pricePrecision).toString());
|
|
};
|
|
// Fake data to inject. Stress test with prices with lots of decimals
|
|
// to ensure pricefeed correctly handles the edge case where API price # decimals > priceFeedDecimals
|
|
const validResponses = [
|
|
{
|
|
rates: {
|
|
"2021-03-09": { USD: 1.1933333333333333 },
|
|
"2021-03-10": { USD: 1.1923333333333333 },
|
|
"2021-03-11": { USD: 1.19263333333333333 },
|
|
"2021-03-12": { USD: 1.19163333333333333 },
|
|
},
|
|
start_at: "2021-03-10",
|
|
base: "EUR",
|
|
end_at: "2021-03-12",
|
|
},
|
|
];
|
|
|
|
beforeEach(async function () {
|
|
spy = sinon.spy();
|
|
networker = new NetworkerMock();
|
|
forexPriceFeed = new ForexDailyPriceFeed(
|
|
winston.createLogger({ level: "info", transports: [new SpyTransport({ level: "debug" }, { spy: spy })] }),
|
|
web3,
|
|
base,
|
|
symbol,
|
|
lookback,
|
|
networker,
|
|
getTime,
|
|
pricePrecision // Add arbitrary decimal conversion.
|
|
);
|
|
});
|
|
|
|
it("No update", async function () {
|
|
assert.equal(forexPriceFeed.getCurrentPrice(), undefined);
|
|
assert.isTrue(await forexPriceFeed.getHistoricalPrice(1000).catch(() => true));
|
|
assert.equal(forexPriceFeed.getLastUpdateTime(), undefined);
|
|
assert.equal(forexPriceFeed.getLookback(), lookback);
|
|
});
|
|
|
|
it("Invalid base or symbol", async function () {
|
|
let errorThrown = false;
|
|
try {
|
|
forexPriceFeed = new ForexDailyPriceFeed(
|
|
winston.createLogger({ level: "info", transports: [new SpyTransport({ level: "debug" }, { spy: spy })] }),
|
|
web3,
|
|
"invalid",
|
|
symbol,
|
|
lookback,
|
|
networker,
|
|
getTime,
|
|
6 // Add arbitrary decimal conversion.
|
|
);
|
|
} catch (err) {
|
|
errorThrown = true;
|
|
}
|
|
assert.isTrue(errorThrown);
|
|
|
|
errorThrown = false;
|
|
try {
|
|
forexPriceFeed = new ForexDailyPriceFeed(
|
|
winston.createLogger({ level: "info", transports: [new SpyTransport({ level: "debug" }, { spy: spy })] }),
|
|
web3,
|
|
base,
|
|
"invalid",
|
|
lookback,
|
|
networker,
|
|
getTime,
|
|
6 // Add arbitrary decimal conversion.
|
|
);
|
|
} catch (err) {
|
|
errorThrown = true;
|
|
}
|
|
assert.isTrue(errorThrown);
|
|
});
|
|
|
|
it("Basic historical price", async function () {
|
|
// Inject data.
|
|
networker.getJsonReturns = [...validResponses];
|
|
|
|
await forexPriceFeed.update();
|
|
|
|
// Historical prices will be available from the oldest price's open time to the newest price's
|
|
// close time.
|
|
const validLookback =
|
|
forexPriceFeed.historicalPricePeriods[forexPriceFeed.historicalPricePeriods.length - 1].closeTime -
|
|
forexPriceFeed.historicalPricePeriods[0].openTime;
|
|
|
|
// Before period 1 should fail.
|
|
assert.isTrue(await forexPriceFeed.getHistoricalPrice(mockTime - validLookback - 1).catch(() => true));
|
|
|
|
// During period 1.
|
|
assert.equal(
|
|
(await forexPriceFeed.getHistoricalPrice(mockTime - validLookback + 1)).toString(),
|
|
convertPriceFeedDecimals("1.193333")
|
|
);
|
|
|
|
// During period 2.
|
|
assert.equal(
|
|
(await forexPriceFeed.getHistoricalPrice(mockTime - validLookback + 24 * 3600 + 1)).toString(),
|
|
convertPriceFeedDecimals("1.192333")
|
|
);
|
|
|
|
// During period 3.
|
|
assert.equal(
|
|
(await forexPriceFeed.getHistoricalPrice(mockTime - validLookback + 2 * 24 * 3600 + 1)).toString(),
|
|
convertPriceFeedDecimals("1.192633")
|
|
);
|
|
|
|
// After period 3 should return the most recent price.
|
|
assert.equal(
|
|
(await forexPriceFeed.getHistoricalPrice(mockTime + 1)).toString(),
|
|
convertPriceFeedDecimals("1.191633")
|
|
);
|
|
});
|
|
|
|
it("Basic current price", async function () {
|
|
// Inject data.
|
|
networker.getJsonReturns = [...validResponses];
|
|
|
|
await forexPriceFeed.update();
|
|
|
|
// Should return the current price in the data.
|
|
assert.equal(forexPriceFeed.getCurrentPrice().toString(), convertPriceFeedDecimals("1.191633"));
|
|
});
|
|
|
|
it("Last update time", async function () {
|
|
// Inject data.
|
|
networker.getJsonReturns = [...validResponses];
|
|
|
|
await forexPriceFeed.update();
|
|
|
|
// Should return the mock time.
|
|
assert.equal(forexPriceFeed.getLastUpdateTime(), mockTime);
|
|
});
|
|
|
|
it("No or bad response", async function () {
|
|
// Bad price response.
|
|
networker.getJsonReturns = [
|
|
{
|
|
rates: {
|
|
// Valid response, just missing some data points.
|
|
"2021-03-09": {},
|
|
},
|
|
},
|
|
{ rates: {} },
|
|
{ error: "test" },
|
|
];
|
|
|
|
// Update should throw errors in all cases.
|
|
assert.isTrue(await forexPriceFeed.update().catch(() => true), "Update didn't throw");
|
|
assert.isTrue(await forexPriceFeed.update().catch(() => true), "Update didn't throw");
|
|
assert.isTrue(await forexPriceFeed.update().catch(() => true), "Update didn't throw");
|
|
|
|
assert.equal(forexPriceFeed.getCurrentPrice(), undefined);
|
|
assert.isTrue(await forexPriceFeed.getHistoricalPrice(mockTime).catch(() => true));
|
|
});
|
|
|
|
it("Update frequency", async function () {
|
|
networker.getJsonReturns = [...validResponses];
|
|
|
|
await forexPriceFeed.update();
|
|
|
|
networker.getJsonReturns = [...validResponses];
|
|
|
|
// Advancing the pricefeed's time forward by anything less than `minTimeBetweenUpdates`
|
|
// should make it skip an update.
|
|
const originalMockTime = mockTime;
|
|
mockTime += minTimeBetweenUpdates - 1;
|
|
|
|
await forexPriceFeed.update();
|
|
|
|
assert.isTrue(spyLogIncludes(spy, -1, "Update skipped because the last one was too recent"));
|
|
assert.equal(forexPriceFeed.getLastUpdateTime(), originalMockTime);
|
|
});
|
|
});
|