median gas price init

This commit is contained in:
Alexey 2020-10-16 19:34:59 +03:00
parent b39073aa53
commit 8d86bfc0fc
4 changed files with 126 additions and 8 deletions

View File

@ -49,9 +49,9 @@ const chainlink: OnChainOracle = {
export const offChainOracles: { [key: string]: OffChainOracle } = {
ethgasstation,
zoltu,
poa,
etherchain,
zoltu,
};
export const onChainOracles: { [key: string]: OnChainOracle } = {

View File

@ -1,6 +1,6 @@
import axios from 'axios';
import config from './config';
import { GasPrice, OffChainOracle, OnChainOracle, ConstructorArgs } from './types';
import { GasPrice, OffChainOracle, OnChainOracle, ConstructorArgs, GasPriceKey } from './types';
import BigNumber from 'bignumber.js';
export class GasPriceOracle {
@ -50,6 +50,78 @@ export class GasPriceOracle {
throw new Error('All oracles are down. Probably a network error.');
}
async fetchMedianGasPriceOffChain(): Promise<GasPrice> {
const allGasPrices: GasPrice[] = [];
for (let oracle of Object.values(this.offChainOracles)) {
const {
name,
url,
instantPropertyName,
fastPropertyName,
standardPropertyName,
lowPropertyName,
denominator,
} = oracle;
try {
const response = await axios.get(url, { timeout: 10000 });
// todo parallel requests
if (response.status === 200) {
const gas = response.data;
if (Number(gas[fastPropertyName]) === 0) {
throw new Error(`${name} oracle provides corrupted values`);
}
const gasPrices: GasPrice = {
instant: parseFloat(gas[instantPropertyName]) / denominator,
fast: parseFloat(gas[fastPropertyName]) / denominator,
standard: parseFloat(gas[standardPropertyName]) / denominator,
low: parseFloat(gas[lowPropertyName]) / denominator,
};
allGasPrices.push(gasPrices);
} else {
throw new Error(`Fetch gasPrice from ${name} oracle failed. Trying another one...`);
}
} catch (e) {
console.error(e.message);
}
}
if (allGasPrices.length === 0) {
throw new Error('All oracles are down. Probably a network error.');
}
return this.median(allGasPrices);
}
median(gasPrices: GasPrice[]): GasPrice {
const medianGasPrice: GasPrice = { instant: 0, fast: 0, standard: 0, low: 0 };
const results: { [key in GasPriceKey]: number[] } = {
instant: [],
fast: [],
standard: [],
low: [],
};
for (const gasPrice of gasPrices) {
results.instant.push(gasPrice.instant);
results.fast.push(gasPrice.fast);
results.standard.push(gasPrice.standard);
results.low.push(gasPrice.low);
}
for (const type of Object.keys(medianGasPrice) as Array<keyof GasPrice>) {
const allPrices = results[type].sort((a, b) => a - b);
if (allPrices.length === 1) {
medianGasPrice[type] = allPrices[0];
continue;
} else if (allPrices.length === 0) {
continue;
}
const isEven = allPrices.length % 2 === 0;
const middle = Math.floor(allPrices.length / 2);
medianGasPrice[type] = isEven ? (allPrices[middle - 1] + allPrices[middle]) / 2.0 : allPrices[middle];
}
return medianGasPrice;
}
async fetchGasPricesOnChain(): Promise<number> {
for (let oracle of Object.values(this.onChainOracles)) {
const { name, callData, contract, denominator } = oracle;
@ -81,7 +153,7 @@ export class GasPriceOracle {
throw new Error('All oracles are down. Probably a network error.');
}
async gasPrices(fallbackGasPrices?: GasPrice): Promise<GasPrice> {
async gasPrices(fallbackGasPrices?: GasPrice, median = true): Promise<GasPrice> {
const defaultFastGas = 22;
const defaultFallbackGasPrices = {
instant: defaultFastGas * 1.3,
@ -91,7 +163,9 @@ export class GasPriceOracle {
};
this.lastGasPrice = this.lastGasPrice || fallbackGasPrices || defaultFallbackGasPrices;
try {
this.lastGasPrice = await this.fetchGasPricesOffChain();
this.lastGasPrice = median
? await this.fetchMedianGasPriceOffChain()
: await this.fetchGasPricesOffChain();
return this.lastGasPrice;
} catch (e) {
console.log('Failed to fetch gas prices from offchain oracles. Trying onchain ones...');

View File

@ -17,12 +17,11 @@ export type OnChainOracle = {
};
export type GasPrice = {
instant: number;
fast: number;
standard: number;
low: number;
[key in GasPriceKey]: number;
};
export type GasPriceKey = 'instant' | 'fast' | 'standard' | 'low';
export interface ConstructorArgs {
defaultRpc?: string;
}

View File

@ -144,6 +144,51 @@ describe('gasPrice', function () {
});
});
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 () {
let 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);
});
});
after('after', function () {
after(function () {
mockery.disable();