339 lines
11 KiB
TypeScript
339 lines
11 KiB
TypeScript
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
|
/* eslint-disable @typescript-eslint/no-var-requires */
|
|
import chai from 'chai';
|
|
import mockery from 'mockery';
|
|
import BigNumber from 'bignumber.js';
|
|
|
|
import { ChainId, NETWORKS } from '../src/config';
|
|
import { GasPriceOracle } from '../src/index';
|
|
|
|
import { GasPrice, OffChainOracle } from '../src/types';
|
|
|
|
chai.use(require('chai-as-promised'));
|
|
chai.should();
|
|
|
|
let oracle = new GasPriceOracle();
|
|
let { onChainOracles, offChainOracles } = oracle;
|
|
|
|
before('before', function () {
|
|
const axiosMock = {
|
|
get: () => {
|
|
throw new Error('axios GET method is mocked for tests');
|
|
},
|
|
post: () => {
|
|
throw new Error('axios POST method is mocked for tests');
|
|
},
|
|
};
|
|
mockery.registerMock('axios', axiosMock);
|
|
});
|
|
|
|
beforeEach('beforeEach', function () {
|
|
oracle = new GasPriceOracle();
|
|
({ onChainOracles, offChainOracles } = oracle);
|
|
});
|
|
|
|
describe('constructor', function () {
|
|
it('should set default values', async function () {
|
|
oracle.configuration.defaultRpc.should.be.equal('https://api.mycryptoapi.com/eth');
|
|
oracle.configuration.timeout.should.be.equal(10000);
|
|
});
|
|
|
|
it('should set passed values', async function () {
|
|
const newOracle = new GasPriceOracle({ timeout: 1337 });
|
|
newOracle.configuration.defaultRpc.should.be.equal('https://api.mycryptoapi.com/eth');
|
|
newOracle.configuration.timeout.should.be.equal(1337);
|
|
});
|
|
});
|
|
|
|
describe('fetchGasPricesOffChain', function () {
|
|
it('should work', async function () {
|
|
const gas: GasPrice = await oracle.fetchGasPricesOffChain();
|
|
|
|
gas.instant.should.be.a('number');
|
|
gas.fast.should.be.a('number');
|
|
gas.standard.should.be.a('number');
|
|
gas.low.should.be.a('number');
|
|
|
|
gas.instant.should.be.at.least(gas.fast); // greater than or equal to the given number.
|
|
gas.fast.should.be.at.least(gas.standard);
|
|
gas.standard.should.be.at.least(gas.low);
|
|
gas.low.should.not.be.equal(0);
|
|
});
|
|
|
|
it('should throw if all offchain oracles are down', async function () {
|
|
mockery.enable({ useCleanCache: true, warnOnUnregistered: false });
|
|
const { GasPriceOracle } = require('../src/index');
|
|
oracle = new GasPriceOracle();
|
|
await oracle
|
|
.fetchGasPricesOffChain()
|
|
.should.be.rejectedWith('All oracles are down. Probably a network error.');
|
|
mockery.disable();
|
|
});
|
|
});
|
|
|
|
describe('fetchGasPricesOnChain', function () {
|
|
it('should work', async function () {
|
|
const gas: number = await oracle.fetchGasPricesOnChain();
|
|
gas.should.be.a('number');
|
|
gas.should.be.above(1);
|
|
gas.should.not.be.equal(0);
|
|
});
|
|
|
|
it('should work with custom rpc', async function () {
|
|
const rpc = 'https://ethereum-rpc.trustwalletapp.com';
|
|
const oracle = new GasPriceOracle({ defaultRpc: rpc });
|
|
oracle.configuration.defaultRpc.should.be.equal(rpc);
|
|
const gas: number = await oracle.fetchGasPricesOnChain();
|
|
|
|
gas.should.be.a('number');
|
|
|
|
gas.should.be.above(1);
|
|
gas.should.not.be.equal(0);
|
|
});
|
|
|
|
it('should remove oracle', async function () {
|
|
await oracle.fetchGasPricesOnChain();
|
|
oracle.removeOnChainOracle('chainlink');
|
|
await oracle
|
|
.fetchGasPricesOnChain()
|
|
.should.be.rejectedWith('All oracles are down. Probably a network error.');
|
|
});
|
|
|
|
it('should add oracle', async function () {
|
|
const { chainlink } = onChainOracles;
|
|
await oracle.fetchGasPricesOnChain();
|
|
oracle.removeOnChainOracle('chainlink');
|
|
await oracle
|
|
.fetchGasPricesOnChain()
|
|
.should.be.rejectedWith('All oracles are down. Probably a network error.');
|
|
oracle.addOnChainOracle(chainlink);
|
|
const gas: number = await oracle.fetchGasPricesOnChain();
|
|
|
|
gas.should.be.a('number');
|
|
gas.should.not.be.equal(0);
|
|
});
|
|
|
|
it('should throw if all onchain oracles are down', async function () {
|
|
mockery.enable({ useCleanCache: true, warnOnUnregistered: false });
|
|
const { GasPriceOracle } = require('../src/index');
|
|
oracle = new GasPriceOracle();
|
|
await oracle
|
|
.fetchGasPricesOnChain()
|
|
.should.be.rejectedWith('All oracles are down. Probably a network error.');
|
|
mockery.disable();
|
|
});
|
|
});
|
|
|
|
describe('fetchGasPriceFromRpc', function () {
|
|
it('should work', async function () {
|
|
const gas: number = await oracle.fetchGasPriceFromRpc();
|
|
gas.should.be.a('number');
|
|
gas.should.be.above(1);
|
|
gas.should.not.be.equal(0);
|
|
});
|
|
|
|
it('should work with custom rpc', async function () {
|
|
const rpc = 'https://ethereum-rpc.trustwalletapp.com';
|
|
const oracle = new GasPriceOracle({ defaultRpc: rpc });
|
|
oracle.configuration.defaultRpc.should.be.equal(rpc);
|
|
const gas: number = await oracle.fetchGasPriceFromRpc();
|
|
|
|
gas.should.be.a('number');
|
|
|
|
gas.should.be.above(1);
|
|
gas.should.not.be.equal(0);
|
|
});
|
|
|
|
it('should throw if default rpc is down', async function () {
|
|
mockery.enable({ useCleanCache: true, warnOnUnregistered: false });
|
|
const { GasPriceOracle } = require('../src/index');
|
|
oracle = new GasPriceOracle();
|
|
await oracle
|
|
.fetchGasPriceFromRpc()
|
|
.should.be.rejectedWith('Default RPC is down. Probably a network error.');
|
|
mockery.disable();
|
|
});
|
|
});
|
|
|
|
describe('gasPrice', function () {
|
|
it('should work', async function () {
|
|
const gas: GasPrice = await oracle.gasPrices();
|
|
|
|
gas.instant.should.be.a('number');
|
|
gas.fast.should.be.a('number');
|
|
gas.standard.should.be.a('number');
|
|
gas.low.should.be.a('number');
|
|
|
|
gas.instant.should.be.at.least(gas.fast);
|
|
gas.fast.should.be.at.least(gas.standard);
|
|
gas.standard.should.be.at.least(gas.low);
|
|
gas.low.should.not.be.equal(0);
|
|
});
|
|
it('should fallback', async function () {
|
|
mockery.enable({ useCleanCache: true, warnOnUnregistered: false });
|
|
const { GasPriceOracle } = require('../src/index');
|
|
oracle = new GasPriceOracle();
|
|
const gas: GasPrice = await oracle.gasPrices();
|
|
|
|
gas.instant.should.be.equal(28.6);
|
|
gas.fast.should.be.equal(22);
|
|
gas.standard.should.be.equal(18.7);
|
|
gas.low.should.be.equal(11);
|
|
mockery.disable();
|
|
});
|
|
|
|
it('should fallback to set values', async function () {
|
|
mockery.enable({ useCleanCache: true, warnOnUnregistered: false });
|
|
const { GasPriceOracle } = require('../src/index');
|
|
oracle = new GasPriceOracle();
|
|
const gas: GasPrice = await oracle.gasPrices({ instant: 50, fast: 21, standard: 10, low: 3 });
|
|
|
|
gas.instant.should.be.equal(50);
|
|
gas.fast.should.be.equal(21);
|
|
gas.standard.should.be.equal(10);
|
|
gas.low.should.be.equal(3);
|
|
mockery.disable();
|
|
});
|
|
});
|
|
|
|
describe('median', function () {
|
|
it('should work', async function () {
|
|
const gas1 = { instant: 100, fast: 100, standard: 100, low: 100 };
|
|
const gas2 = { instant: 90, fast: 90, standard: 90, low: 90 };
|
|
const gas3 = { instant: 70, fast: 70, standard: 70, low: 70 };
|
|
const gas4 = { instant: 110.1, fast: 110.1, standard: 110.1, low: 110.1 };
|
|
let gas: GasPrice = await oracle.median([gas1, gas2, gas3]);
|
|
gas.instant.should.be.a('number');
|
|
gas.fast.should.be.a('number');
|
|
gas.standard.should.be.a('number');
|
|
gas.low.should.be.a('number');
|
|
|
|
gas.instant.should.be.eq(90);
|
|
gas.fast.should.be.eq(90);
|
|
gas.standard.should.be.eq(90);
|
|
gas.low.should.be.eq(90);
|
|
|
|
gas = await oracle.median([gas1, gas2, gas3, gas4]);
|
|
gas.instant.should.be.a('number');
|
|
gas.fast.should.be.a('number');
|
|
gas.standard.should.be.a('number');
|
|
gas.low.should.be.a('number');
|
|
|
|
gas.instant.should.be.eq(95);
|
|
gas.fast.should.be.eq(95);
|
|
gas.standard.should.be.eq(95);
|
|
gas.low.should.be.eq(95);
|
|
});
|
|
});
|
|
|
|
describe('fetchMedianGasPriceOffChain', function () {
|
|
it('should work', async function () {
|
|
const gas: GasPrice = await oracle.fetchMedianGasPriceOffChain();
|
|
gas.instant.should.be.a('number');
|
|
gas.fast.should.be.a('number');
|
|
gas.standard.should.be.a('number');
|
|
gas.low.should.be.a('number');
|
|
|
|
gas.instant.should.be.at.least(gas.fast); // greater than or equal to the given number.
|
|
gas.fast.should.be.at.least(gas.standard);
|
|
gas.standard.should.be.at.least(gas.low);
|
|
gas.low.should.not.be.equal(0);
|
|
});
|
|
});
|
|
|
|
describe('normalize result values', function () {
|
|
const wrongDecimalsGas = {
|
|
instant: 1.1,
|
|
fast: 2.12345678901,
|
|
standard: 3.12345678901,
|
|
low: 3.1234567890123456789,
|
|
};
|
|
|
|
const checkDecimals = (gas: GasPrice) => {
|
|
const gasPrices: number[] = Object.values(gas);
|
|
|
|
for (const gas of gasPrices) {
|
|
new BigNumber(gas).dp().should.be.at.most(9);
|
|
}
|
|
};
|
|
|
|
it('default fallback should be normalized', function () {
|
|
mockery.enable({ useCleanCache: true, warnOnUnregistered: false });
|
|
|
|
const { GasPriceOracle } = require('../src/index');
|
|
oracle = new GasPriceOracle({
|
|
defaultFallbackGasPrices: wrongDecimalsGas,
|
|
});
|
|
const { configuration } = oracle;
|
|
|
|
checkDecimals(configuration.defaultFallbackGasPrices);
|
|
|
|
mockery.disable();
|
|
});
|
|
|
|
it('fallback should be normalized', async function () {
|
|
mockery.enable({ useCleanCache: true, warnOnUnregistered: false });
|
|
|
|
const { GasPriceOracle } = require('../src/index');
|
|
oracle = new GasPriceOracle();
|
|
|
|
const gas = await oracle.gasPrices(wrongDecimalsGas);
|
|
|
|
checkDecimals(gas);
|
|
mockery.disable();
|
|
});
|
|
|
|
it('rpc fallback should be normalized', async function () {
|
|
const { GasPriceOracle } = require('../src/index');
|
|
oracle = new GasPriceOracle({ chainId: ChainId.ARBITRUM, defaultRpc: 'https://arb1.arbitrum.io/rpc' });
|
|
|
|
const { onChainOracles, offChainOracles } = oracle;
|
|
|
|
Object.keys(onChainOracles).forEach(chainOracle => oracle.removeOnChainOracle(chainOracle));
|
|
Object.keys(offChainOracles).forEach(chainOracle => oracle.removeOffChainOracle(chainOracle));
|
|
|
|
const gas = await oracle.gasPrices();
|
|
|
|
checkDecimals(gas);
|
|
});
|
|
});
|
|
|
|
describe('askOracle', function () {
|
|
const chains = Object.keys(NETWORKS).map(id => Number(id));
|
|
|
|
chains.forEach(chainId => {
|
|
describe(`all ${ChainId[chainId]} oracles should answer`, function () {
|
|
oracle = new GasPriceOracle({ chainId });
|
|
({ offChainOracles } = oracle);
|
|
|
|
for (const o of Object.values(offChainOracles) as Array<OffChainOracle>) {
|
|
it(`check ${o.name}`, async function () {
|
|
try {
|
|
const gas: GasPrice = await oracle.askOracle(o);
|
|
|
|
gas.instant.should.be.a('number');
|
|
gas.fast.should.be.a('number');
|
|
gas.standard.should.be.a('number');
|
|
gas.low.should.be.a('number');
|
|
|
|
gas.instant.should.be.at.least(gas.fast); // greater than or equal to the given number.
|
|
gas.fast.should.be.at.least(gas.standard);
|
|
gas.standard.should.be.at.least(gas.low);
|
|
gas.low.should.not.be.equal(0);
|
|
} catch (e) {
|
|
console.error(`Failed to get data from ${o.name} oracle`);
|
|
throw new Error(e);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
});
|
|
});
|
|
|
|
after('after', function () {
|
|
after(function () {
|
|
mockery.disable();
|
|
mockery.deregisterMock('node-fetch');
|
|
});
|
|
});
|