0.1.0 version

This commit is contained in:
Alexey 2020-06-02 15:46:03 +03:00
parent 2bcfa0003d
commit d34559deff
4 changed files with 168 additions and 57 deletions

View File

@ -1,6 +1,6 @@
{
"name": "gas-price-oracle",
"version": "1.0.0",
"version": "0.1.0",
"description": "Gas Price Oracle library for Ethereum dApps.",
"main": "lib/index.js",
"types": "lib/index.d.ts",

View File

@ -21,18 +21,42 @@ const zoltu: OffChainOracle = {
denominator: 1
};
const chainLink: OnChainOracle = {
name: 'chainLink',
const etherchain: OffChainOracle = {
name: 'etherchain',
url: 'https://www.etherchain.org/api/gasPriceOracle',
instantPropertyName: 'fastest',
fastPropertyName: 'fast',
standardPropertyName: 'standard',
lowPropertyName: 'safeLow',
denominator: 1
};
const poa: OffChainOracle = {
name: 'poa',
url: 'https://gasprice.poa.network/',
instantPropertyName: 'instant',
fastPropertyName: 'fast',
standardPropertyName: 'standard',
lowPropertyName: 'slow',
denominator: 1
};
const chainlink: OnChainOracle = {
name: 'chainlink',
callData: '0x50d25bcd',
contract: '0xA417221ef64b1549575C977764E651c9FAB50141',
denominator: '1000000000'
};
export default {
offChainOracles: [
ethgasstation, zoltu
],
onChainOracles: [
chainLink
]
export const offChainOracles: { [key: string]: OffChainOracle } = {
ethgasstation, zoltu, poa, etherchain
};
export const onChainOracles: { [key: string]: OnChainOracle } = {
chainlink
};
export default {
offChainOracles,
onChainOracles
};

View File

@ -11,6 +11,8 @@ export class GasPriceOracle {
low: 1
};
defaultRpc = 'https://api.mycryptoapi.com/eth';
offChainOracles = { ...config.offChainOracles };
onChainOracles = { ...config.onChainOracles };
constructor(defaultRpc?: string) {
if (defaultRpc) {
@ -19,7 +21,7 @@ export class GasPriceOracle {
}
async fetchGasPricesOffChain(throwIfFailsToFetch = true): Promise<GasPrice> {
for (let oracle of config.offChainOracles) {
for (let oracle of Object.values(this.offChainOracles)) {
const { name, url, instantPropertyName, fastPropertyName, standardPropertyName, lowPropertyName, denominator } = oracle;
try {
const response = await fetch(url);
@ -50,7 +52,7 @@ export class GasPriceOracle {
}
async fetchGasPricesOnChain(throwIfFailsToFetch = true): Promise<GasPrice> {
for (let oracle of config.onChainOracles) {
for (let oracle of Object.values(this.onChainOracles)) {
const { name, callData, contract, denominator } = oracle;
let { rpc } = oracle;
rpc = rpc ? rpc : this.defaultRpc;
@ -95,11 +97,38 @@ export class GasPriceOracle {
return this.lastGasPrice;
}
async gasPrices(): Promise<GasPrice> {
let gas = this.lastGasPrice;
try {
gas = await this.fetchGasPricesOffChain();
return gas;
} catch (e) {
console.log('Failed to fetch gas prices from offchain oracles. Trying onchain ones...');
}
try {
gas = await this.fetchGasPricesOnChain();
return gas;
} catch (e) {
console.log('Failed to fetch gas prices from onchain oracles. Last known gas will be returned');
}
return gas;
}
addOffChainOracle(oracle: OffChainOracle) {
config.offChainOracles.push(oracle);
this.offChainOracles[oracle.name] = oracle;
}
addOnChainOracle(oracle: OnChainOracle) {
config.onChainOracles.push(oracle);
this.onChainOracles[oracle.name] = oracle;
}
removeOnChainOracle(name: string) {
delete this.onChainOracles[name];
}
removeOffChainOracle(name: string) {
delete this.offChainOracles[name];
}
}

View File

@ -1,14 +1,26 @@
import { GasPriceOracle } from '../src/index';
import { GasPrice } from '../src/types';
import mockery from 'mockery';
import chai from 'chai';
import { onChainOracles } from '../src/config';
const { GasPriceOracle } = require('../src/index');
chai.use(require('chai-as-promised'));
chai.should();
let oracle = new GasPriceOracle();
before('before', function () {
let fetchMock = () => {
throw new Error('Mocked for tests');
};
mockery.registerMock('node-fetch', fetchMock);
});
beforeEach('beforeEach', function () {
oracle = new GasPriceOracle();
});
describe('fetchGasPricesOffChain', function () {
it('should work', async function () {
const oracle = new GasPriceOracle();
const gas: GasPrice = await oracle.fetchGasPricesOffChain();
gas.instant.should.be.a('number');
@ -21,61 +33,26 @@ describe('fetchGasPricesOffChain', function () {
gas.standard.should.be.above(gas.low);
gas.low.should.not.be.equal(0);
});
});
describe('throw checks', function () {
before('before', function () {
// Mocking the mod1 module
let fetchMock = () => {
throw new Error('Mocked for tests');
};
// replace the module with mock for any `require`
mockery.registerMock('node-fetch', fetchMock);
// set additional parameters
mockery.enable({
useCleanCache: true,
// warnOnReplace: false,
warnOnUnregistered: false
});
});
it('should throw if all offchain oracles are down', async function () {
mockery.enable({ useCleanCache: true, warnOnUnregistered: false });
const { GasPriceOracle } = require('../src/index');
const oracle = new GasPriceOracle();
oracle = new GasPriceOracle();
await oracle.fetchGasPricesOffChain().should.be.rejectedWith('All oracles are down. Probaly network error.');
});
it('should throw if all onchain oracles are down', async function () {
const { GasPriceOracle } = require('../src/index');
const oracle = new GasPriceOracle();
await oracle.fetchGasPricesOnChain().should.be.rejectedWith('All oracles are down. Probaly network error.');
mockery.disable();
});
it('should not throw if throwIfFailsToFetch is false', async function () {
mockery.enable({ useCleanCache: true, warnOnUnregistered: false });
const { GasPriceOracle } = require('../src/index');
const oracle = new GasPriceOracle();
await oracle.fetchGasPricesOnChain(false);
});
it('should not throw if throwIfFailsToFetch is false', async function () {
const { GasPriceOracle } = require('../src/index');
const oracle = new GasPriceOracle();
oracle = new GasPriceOracle();
await oracle.fetchGasPricesOffChain(false);
});
after('after', function () {
after(function () {
mockery.disable();
mockery.deregisterMock('node-fetch');
});
mockery.disable();
});
});
describe('fetchGasPricesOnChain', function () {
it('should work', async function () {
const oracle = new GasPriceOracle();
const gas: GasPrice = await oracle.fetchGasPricesOnChain();
gas.instant.should.be.a('number');
@ -88,4 +65,85 @@ describe('fetchGasPricesOnChain', function () {
gas.standard.should.be.above(gas.low);
gas.low.should.not.be.equal(0);
});
it('should work with custom rpc', async function () {
const rpc = 'https://ethereum-rpc.trustwalletapp.com';
const oracle = new GasPriceOracle(rpc);
oracle.defaultRpc.should.be.equal(rpc);
const gas: GasPrice = await oracle.fetchGasPricesOnChain();
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.above(gas.fast);
gas.fast.should.be.above(gas.standard);
gas.standard.should.be.above(gas.low);
gas.low.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. Probaly 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. Probaly network error.');
oracle.addOnChainOracle(chainlink);
const gas: GasPrice = await oracle.fetchGasPricesOnChain();
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.above(gas.fast);
gas.fast.should.be.above(gas.standard);
gas.standard.should.be.above(gas.low);
gas.low.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. Probaly network error.');
mockery.disable();
});
it('should not throw if throwIfFailsToFetch is false', async function () {
mockery.enable({ useCleanCache: true, warnOnUnregistered: false });
const { GasPriceOracle } = require('../src/index');
oracle = new GasPriceOracle();
await oracle.fetchGasPricesOnChain(false);
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.above(gas.fast);
gas.fast.should.be.above(gas.standard);
gas.standard.should.be.above(gas.low);
gas.low.should.not.be.equal(0);
});
});
after('after', function () {
after(function () {
mockery.disable();
mockery.deregisterMock('node-fetch');
});
});