Common gasprice (#179)

* Common gas price normalization.

* only e2e jobs

* One func

* More extraction.

* Fixed the tests

* skip gasPriceWithinLimits

* test fix

* tos tring

* boundaries

* Extracted fetching gas price from contract

* Refactored oracle gas price

* lint

* lint

* Commentary

* Using common gas price from oracle in ui

* Fix lint

* lint

* Log

* Using common gas price in monitor

* cosmetics

* all jobs

* lint

* lint

* tests

* more tests

* incljdes

* Tests in oracle

* Tests in commons

* Lint

* moved tests from ui to commons

* chai

* Changed order of fetching gas price
This commit is contained in:
Przemyslaw Rzad 2019-08-05 17:22:57 +02:00 committed by GitHub
parent 2be0e9f363
commit 055a444fae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 355 additions and 321 deletions

@ -4,7 +4,8 @@
"../.eslintrc" "../.eslintrc"
], ],
"rules": { "rules": {
"no-unused-expressions": "off" "no-unused-expressions": "off",
"import/no-extraneous-dependencies": "off"
}, },
"env": { "env": {
"mocha": true "mocha": true

@ -6,5 +6,8 @@
"scripts": { "scripts": {
"lint": "eslint . --ignore-path ../.eslintignore", "lint": "eslint . --ignore-path ../.eslintignore",
"test": "NODE_ENV=test mocha" "test": "NODE_ENV=test mocha"
},
"dependencies": {
"web3-utils": "1.0.0-beta.30"
} }
} }

162
commons/test/gas.js Normal file

@ -0,0 +1,162 @@
const { expect } = require('chai')
const Web3Utils = require('web3-utils')
const { gasPriceWithinLimits, normalizeGasPrice } = require('..')
const GAS_PRICE_BOUNDARIES = {
MIN: 1,
MAX: 250
}
describe('gas', () => {
describe('normalizeGasPrice', () => {
it('should work with oracle gas price in gwei', () => {
// Given
const oracleGasPrice = 30
const factor = 1
// When
const result = normalizeGasPrice(oracleGasPrice, factor).toString()
// Then
expect(result).to.equal('30000000000')
})
it('should work with oracle gas price not in gwei', () => {
// Given
const oracleGasPrice = 300
const factor = 0.1
// When
const result = normalizeGasPrice(oracleGasPrice, factor).toString()
// Then
expect(result).to.equal('30000000000')
})
it('should increase gas price value from oracle', () => {
// Given
const oracleGasPrice = 20
const factor = 1.5
// When
const result = normalizeGasPrice(oracleGasPrice, factor).toString()
// Then
expect(result).to.equal('30000000000')
})
})
describe('gasPriceWithinLimits', () => {
it('should return gas price if gas price is between boundaries', () => {
// given
const minGasPrice = 1
const middleGasPrice = 10
const maxGasPrice = 250
// when
const minGasPriceWithinLimits = gasPriceWithinLimits(minGasPrice, GAS_PRICE_BOUNDARIES)
const middleGasPriceWithinLimits = gasPriceWithinLimits(middleGasPrice, GAS_PRICE_BOUNDARIES)
const maxGasPriceWithinLimits = gasPriceWithinLimits(maxGasPrice, GAS_PRICE_BOUNDARIES)
// then
expect(minGasPriceWithinLimits).to.equal(minGasPrice)
expect(middleGasPriceWithinLimits).to.equal(middleGasPrice)
expect(maxGasPriceWithinLimits).to.equal(maxGasPrice)
})
it('should return min limit if gas price is below min boundary', () => {
// Given
const initialGasPrice = 0.5
// When
const gasPrice = gasPriceWithinLimits(initialGasPrice, GAS_PRICE_BOUNDARIES)
// Then
expect(gasPrice).to.equal(GAS_PRICE_BOUNDARIES.MIN)
})
it('should return max limit if gas price is above max boundary', () => {
// Given
const initialGasPrice = 260
// When
const gasPrice = gasPriceWithinLimits(initialGasPrice, GAS_PRICE_BOUNDARIES)
// Then
expect(gasPrice).to.equal(GAS_PRICE_BOUNDARIES.MAX)
})
it('should return gas price if boundaries not provided', () => {
// Given
const initialGasPrice = 260
// When
const gasPrice = gasPriceWithinLimits(initialGasPrice)
// Then
expect(gasPrice).to.equal(initialGasPrice)
})
})
describe('normalizeGasPrice', () => {
it('should work with oracle gas price in gwei', () => {
// Given
const oracleGasPrice = 20
const factor = 1
// When
const result = normalizeGasPrice(oracleGasPrice, factor).toString()
// Then
expect(result).to.equal('20000000000')
})
it('should work with oracle gas price not in gwei', () => {
// Given
const oracleGasPrice = 200
const factor = 0.1
// When
const result = normalizeGasPrice(oracleGasPrice, factor).toString()
// Then
expect(result).to.equal('20000000000')
})
it('should increase gas price value from oracle', () => {
// Given
const oracleGasPrice = 20
const factor = 1.5
// When
const result = normalizeGasPrice(oracleGasPrice, factor).toString()
// Then
expect(result).to.equal('30000000000')
})
it('should respect gas price max limit', () => {
// Given
const oracleGasPrice = 200
const factor = 4
const maxInWei = Web3Utils.toWei(GAS_PRICE_BOUNDARIES.MAX.toString(), 'gwei')
// When
const result = normalizeGasPrice(oracleGasPrice, factor, GAS_PRICE_BOUNDARIES).toString()
// Then
expect(result).to.equal(maxInWei)
})
it('should respect gas price min limit', () => {
// Given
const oracleGasPrice = 1
const factor = 0.01
const minInWei = Web3Utils.toWei(GAS_PRICE_BOUNDARIES.MIN.toString(), 'gwei')
// When
const result = normalizeGasPrice(oracleGasPrice, factor, GAS_PRICE_BOUNDARIES).toString()
// Then
expect(result).to.equal(minInWei)
})
})
})

@ -1,3 +1,4 @@
const { toWei, toBN } = require('web3-utils')
const { BRIDGE_MODES, FEE_MANAGER_MODE, ERC_TYPES } = require('./constants') const { BRIDGE_MODES, FEE_MANAGER_MODE, ERC_TYPES } = require('./constants')
function decodeBridgeMode(bridgeModeHash) { function decodeBridgeMode(bridgeModeHash) {
@ -65,10 +66,77 @@ const getUnit = bridgeMode => {
return { unitHome, unitForeign } return { unitHome, unitForeign }
} }
const gasPriceWithinLimits = (gasPrice, limits) => {
if (!limits) {
return gasPrice
}
if (gasPrice < limits.MIN) {
return limits.MIN
} else if (gasPrice > limits.MAX) {
return limits.MAX
} else {
return gasPrice
}
}
const normalizeGasPrice = (oracleGasPrice, factor, limits = null) => {
let gasPrice = oracleGasPrice * factor
gasPrice = gasPriceWithinLimits(gasPrice, limits)
return toBN(toWei(gasPrice.toFixed(2).toString(), 'gwei'))
}
// fetchFn has to be supplied (instead of just url to oracle),
// because this utility function is shared between Browser and Node,
// we use built-in 'fetch' on browser side, and `node-fetch` package in Node.
const gasPriceFromOracle = async (fetchFn, options = {}) => {
try {
const response = await fetchFn()
const json = await response.json()
const oracleGasPrice = json[options.speedType]
if (!oracleGasPrice) {
options.logger &&
options.logger.error &&
options.logger.error(`Response from Oracle didn't include gas price for ${options.speedType} type.`)
return null
}
const normalizedGasPrice = normalizeGasPrice(oracleGasPrice, options.factor, options.limits)
options.logger &&
options.logger.debug &&
options.logger.debug({ oracleGasPrice, normalizedGasPrice }, 'Gas price updated using the API')
return normalizedGasPrice
} catch (e) {
options.logger && options.logger.error && options.logger.error(`Gas Price API is not available. ${e.message}`)
}
return null
}
const gasPriceFromContract = async (bridgeContract, options = {}) => {
try {
const gasPrice = await bridgeContract.methods.gasPrice().call()
options.logger &&
options.logger.debug &&
options.logger.debug({ gasPrice }, 'Gas price updated using the contracts')
return gasPrice
} catch (e) {
options.logger &&
options.logger.error &&
options.logger.error(`There was a problem getting the gas price from the contract. ${e.message}`)
}
return null
}
module.exports = { module.exports = {
decodeBridgeMode, decodeBridgeMode,
decodeFeeManagerMode, decodeFeeManagerMode,
getBridgeMode, getBridgeMode,
getTokenType, getTokenType,
getUnit getUnit,
normalizeGasPrice,
gasPriceFromOracle,
gasPriceFromContract,
gasPriceWithinLimits
} }

@ -2,7 +2,7 @@ require('dotenv').config()
const Web3 = require('web3') const Web3 = require('web3')
const fetch = require('node-fetch') const fetch = require('node-fetch')
const logger = require('./logger')('validators') const logger = require('./logger')('validators')
const { getBridgeABIs, BRIDGE_VALIDATORS_ABI } = require('../commons') const { getBridgeABIs, BRIDGE_VALIDATORS_ABI, gasPriceFromOracle } = require('../commons')
const { getValidatorList } = require('./utils/validatorUtils') const { getValidatorList } = require('./utils/validatorUtils')
const { getBlockNumber } = require('./utils/contract') const { getBlockNumber } = require('./utils/contract')
@ -33,40 +33,24 @@ const web3Home = new Web3(homeProvider)
const foreignProvider = new Web3.providers.HttpProvider(FOREIGN_RPC_URL) const foreignProvider = new Web3.providers.HttpProvider(FOREIGN_RPC_URL)
const web3Foreign = new Web3(foreignProvider) const web3Foreign = new Web3(foreignProvider)
const homeGasOracleOpts = {
speedType: HOME_GAS_PRICE_SPEED_TYPE,
factor: HOME_GAS_PRICE_FACTOR,
logger
}
const foreignGasOracleOpts = {
speedType: FOREIGN_GAS_PRICE_SPEED_TYPE,
factor: FOREIGN_GAS_PRICE_FACTOR,
logger
}
const asyncForEach = async (array, callback) => { const asyncForEach = async (array, callback) => {
for (let index = 0; index < array.length; index++) { for (let index = 0; index < array.length; index++) {
await callback(array[index], index, array) await callback(array[index], index, array)
} }
} }
function getGasPrices(url, type, factor, fallback) {
if (!url) {
return Web3Utils.toBN(fallback)
}
return fetchGasPrices(url, type, factor, fallback)
}
async function fetchGasPrices(url, type, factor, fallback) {
try {
const response = await fetch(url)
const json = await response.json()
logger.log('Fetched gasprice: ' + json[type])
const gasPrice = json[type]
if (!gasPrice) {
throw new Error(`Response from Oracle didn't include gas price for ${type} type.`)
}
return normalizeGasPrice(gasPrice, factor)
} catch (e) {
logger.error('Gas Price API is not available', e)
return Web3Utils.toBN(fallback)
}
}
function normalizeGasPrice(oracleGasPrice, factor) {
const gasPrice = oracleGasPrice * factor
return Web3Utils.toBN(Web3Utils.toWei(gasPrice.toFixed(2).toString(), 'gwei'))
}
async function main(bridgeMode) { async function main(bridgeMode) {
const { HOME_ABI, FOREIGN_ABI } = getBridgeABIs(bridgeMode) const { HOME_ABI, FOREIGN_ABI } = getBridgeABIs(bridgeMode)
const homeBridge = new web3Home.eth.Contract(HOME_ABI, HOME_BRIDGE_ADDRESS) const homeBridge = new web3Home.eth.Contract(HOME_ABI, HOME_BRIDGE_ADDRESS)
@ -106,22 +90,16 @@ async function main(bridgeMode) {
const homeVBalances = {} const homeVBalances = {}
logger.debug('calling home getGasPrices') logger.debug('calling home getGasPrices')
const homeGasPrice = await getGasPrices( const homeGasPrice =
HOME_GAS_PRICE_ORACLE_URL, (await gasPriceFromOracle(() => fetch(HOME_GAS_PRICE_ORACLE_URL), homeGasOracleOpts)) ||
HOME_GAS_PRICE_SPEED_TYPE, Web3Utils.toBN(HOME_GAS_PRICE_FALLBACK)
HOME_GAS_PRICE_FACTOR,
HOME_GAS_PRICE_FALLBACK
)
const homeGasPriceGwei = Web3Utils.fromWei(homeGasPrice.toString(), 'gwei') const homeGasPriceGwei = Web3Utils.fromWei(homeGasPrice.toString(), 'gwei')
const homeTxCost = homeGasPrice.mul(Web3Utils.toBN(HOME_GAS_LIMIT)) const homeTxCost = homeGasPrice.mul(Web3Utils.toBN(HOME_GAS_LIMIT))
logger.debug('calling foreign getGasPrices') logger.debug('calling foreign getGasPrices')
const foreignGasPrice = await getGasPrices( const foreignGasPrice =
FOREIGN_GAS_PRICE_ORACLE_URL, (await gasPriceFromOracle(() => fetch(FOREIGN_GAS_PRICE_ORACLE_URL), foreignGasOracleOpts)) ||
FOREIGN_GAS_PRICE_SPEED_TYPE, Web3Utils.toBN(FOREIGN_GAS_PRICE_FALLBACK)
FOREIGN_GAS_PRICE_FACTOR,
FOREIGN_GAS_PRICE_FALLBACK
)
const foreignGasPriceGwei = Web3Utils.fromWei(foreignGasPrice.toString(), 'gwei') const foreignGasPriceGwei = Web3Utils.fromWei(foreignGasPrice.toString(), 'gwei')
const foreignTxCost = foreignGasPrice.mul(Web3Utils.toBN(FOREIGN_GAS_LIMIT)) const foreignTxCost = foreignGasPrice.mul(Web3Utils.toBN(FOREIGN_GAS_LIMIT))

@ -1,6 +1,5 @@
require('../../env') require('../../env')
const fetch = require('node-fetch') const fetch = require('node-fetch')
const Web3Utils = require('web3-utils')
const { web3Home, web3Foreign } = require('../services/web3') const { web3Home, web3Foreign } = require('../services/web3')
const { bridgeConfig } = require('../../config/base.config') const { bridgeConfig } = require('../../config/base.config')
const logger = require('../services/logger').child({ const logger = require('../services/logger').child({
@ -8,6 +7,7 @@ const logger = require('../services/logger').child({
}) })
const { setIntervalAndRun } = require('../utils/utils') const { setIntervalAndRun } = require('../utils/utils')
const { DEFAULT_UPDATE_INTERVAL, GAS_PRICE_BOUNDARIES, DEFAULT_GAS_PRICE_FACTOR } = require('../utils/constants') const { DEFAULT_UPDATE_INTERVAL, GAS_PRICE_BOUNDARIES, DEFAULT_GAS_PRICE_FACTOR } = require('../utils/constants')
const { gasPriceFromOracle, gasPriceFromContract } = require('../../../commons')
const HomeABI = bridgeConfig.homeBridgeAbi const HomeABI = bridgeConfig.homeBridgeAbi
const ForeignABI = bridgeConfig.foreignBridgeAbi const ForeignABI = bridgeConfig.foreignBridgeAbi
@ -33,53 +33,18 @@ const foreignBridge = new web3Foreign.eth.Contract(ForeignABI, FOREIGN_BRIDGE_AD
let cachedGasPrice = null let cachedGasPrice = null
function gasPriceWithinLimits(gasPrice) {
if (gasPrice < GAS_PRICE_BOUNDARIES.MIN) {
return GAS_PRICE_BOUNDARIES.MIN
} else if (gasPrice > GAS_PRICE_BOUNDARIES.MAX) {
return GAS_PRICE_BOUNDARIES.MAX
} else {
return gasPrice
}
}
function normalizeGasPrice(oracleGasPrice, factor) {
const gasPriceGwei = oracleGasPrice * factor
const gasPrice = gasPriceWithinLimits(gasPriceGwei)
return Web3Utils.toWei(gasPrice.toFixed(2).toString(), 'gwei')
}
async function fetchGasPriceFromOracle(oracleUrl, speedType, factor) {
const response = await fetch(oracleUrl)
const json = await response.json()
const oracleGasPrice = json[speedType]
if (!oracleGasPrice) {
throw new Error(`Response from Oracle didn't include gas price for ${speedType} type.`)
}
return normalizeGasPrice(oracleGasPrice, factor)
}
async function fetchGasPrice({ bridgeContract, oracleFn }) {
let gasPrice = null
try {
gasPrice = await oracleFn()
logger.debug({ gasPrice }, 'Gas price updated using the oracle')
} catch (e) {
logger.error(`Gas Price API is not available. ${e.message}`)
try {
gasPrice = await bridgeContract.methods.gasPrice().call()
logger.debug({ gasPrice }, 'Gas price updated using the contracts')
} catch (e) {
logger.error(`There was a problem getting the gas price from the contract. ${e.message}`)
}
}
return gasPrice
}
let fetchGasPriceInterval = null let fetchGasPriceInterval = null
const fetchGasPrice = async (speedType, factor, bridgeContract, oracleFetchFn) => {
const contractOptions = { logger }
const oracleOptions = { speedType, factor, limits: GAS_PRICE_BOUNDARIES, logger }
cachedGasPrice =
(await gasPriceFromOracle(oracleFetchFn, oracleOptions)) ||
(await gasPriceFromContract(bridgeContract, contractOptions)) ||
cachedGasPrice
return cachedGasPrice
}
async function start(chainId) { async function start(chainId) {
clearInterval(fetchGasPriceInterval) clearInterval(fetchGasPriceInterval)
@ -108,13 +73,10 @@ async function start(chainId) {
throw new Error(`Unrecognized chainId '${chainId}'`) throw new Error(`Unrecognized chainId '${chainId}'`)
} }
fetchGasPriceInterval = setIntervalAndRun(async () => { fetchGasPriceInterval = setIntervalAndRun(
const gasPrice = await fetchGasPrice({ () => fetchGasPrice(speedType, factor, bridgeContract, () => fetch(oracleUrl)),
bridgeContract, updateInterval
oracleFn: () => fetchGasPriceFromOracle(oracleUrl, speedType, factor) )
})
cachedGasPrice = gasPrice || cachedGasPrice
}, updateInterval)
} }
function getPrice() { function getPrice() {
@ -123,8 +85,6 @@ function getPrice() {
module.exports = { module.exports = {
start, start,
fetchGasPrice,
getPrice, getPrice,
gasPriceWithinLimits, fetchGasPrice
normalizeGasPrice
} }

@ -1,80 +1,9 @@
const sinon = require('sinon') const sinon = require('sinon')
const { expect } = require('chai') const { expect } = require('chai')
const proxyquire = require('proxyquire').noPreserveCache() const proxyquire = require('proxyquire').noPreserveCache()
const Web3Utils = require('web3-utils') const { DEFAULT_UPDATE_INTERVAL } = require('../src/utils/constants')
const { fetchGasPrice, gasPriceWithinLimits, normalizeGasPrice } = require('../src/services/gasPrice')
const { DEFAULT_UPDATE_INTERVAL, GAS_PRICE_BOUNDARIES } = require('../src/utils/constants')
describe('gasPrice', () => { describe('gasPrice', () => {
describe('fetchGasPrice', () => {
beforeEach(() => {
sinon.stub(console, 'error')
})
afterEach(() => {
console.error.restore()
})
it('should fetch the gas price from the oracle by default', async () => {
// given
const oracleFnMock = () => Promise.resolve('1')
const bridgeContractMock = {
methods: {
gasPrice: {
call: sinon.stub().returns(Promise.resolve('2'))
}
}
}
// when
const gasPrice = await fetchGasPrice({
bridgeContract: bridgeContractMock,
oracleFn: oracleFnMock
})
// then
expect(gasPrice).to.equal('1')
})
it('should fetch the gas price from the contract if the oracle fails', async () => {
// given
const oracleFnMock = () => Promise.reject(new Error('oracle failed'))
const bridgeContractMock = {
methods: {
gasPrice: sinon.stub().returns({
call: sinon.stub().returns(Promise.resolve('2'))
})
}
}
// when
const gasPrice = await fetchGasPrice({
bridgeContract: bridgeContractMock,
oracleFn: oracleFnMock
})
// then
expect(gasPrice).to.equal('2')
})
it('should return null if both the oracle and the contract fail', async () => {
// given
const oracleFnMock = () => Promise.reject(new Error('oracle failed'))
const bridgeContractMock = {
methods: {
gasPrice: sinon.stub().returns({
call: sinon.stub().returns(Promise.reject(new Error('contract failed')))
})
}
}
// when
const gasPrice = await fetchGasPrice({
bridgeContract: bridgeContractMock,
oracleFn: oracleFnMock
})
// then
expect(gasPrice).to.equal(null)
})
})
describe('start', () => { describe('start', () => {
const utils = { setIntervalAndRun: sinon.spy() } const utils = { setIntervalAndRun: sinon.spy() }
beforeEach(() => { beforeEach(() => {
@ -131,101 +60,104 @@ describe('gasPrice', () => {
expect(utils.setIntervalAndRun.args[0][1]).to.equal(DEFAULT_UPDATE_INTERVAL) expect(utils.setIntervalAndRun.args[0][1]).to.equal(DEFAULT_UPDATE_INTERVAL)
}) })
}) })
describe('gasPriceWithinLimits', () => {
it('should return gas price if gas price is between boundaries', () => { describe('fetching gas price', () => {
const utils = { setIntervalAndRun: () => {} }
it('should fall back to default if contract and oracle/supplier are not working', async () => {
// given // given
const minGasPrice = 1 process.env.HOME_GAS_PRICE_FALLBACK = '101000000000'
const middleGasPrice = 10 const gasPrice = proxyquire('../src/services/gasPrice', { '../utils/utils': utils })
const maxGasPrice = 250 await gasPrice.start('home')
// when // when
const minGasPriceWithinLimits = gasPriceWithinLimits(minGasPrice) await gasPrice.fetchGasPrice('standard', 1, null, null)
const middleGasPriceWithinLimits = gasPriceWithinLimits(middleGasPrice)
const maxGasPriceWithinLimits = gasPriceWithinLimits(maxGasPrice)
// then // then
expect(minGasPriceWithinLimits).to.equal(minGasPrice) expect(gasPrice.getPrice()).to.equal('101000000000')
expect(middleGasPriceWithinLimits).to.equal(middleGasPrice)
expect(maxGasPriceWithinLimits).to.equal(maxGasPrice)
}) })
it('should return min limit if gas price is below min boundary', () => {
// Given
const initialGasPrice = 0.5
// When it('should fetch gas from oracle/supplier', async () => {
const gasPrice = gasPriceWithinLimits(initialGasPrice) // given
process.env.HOME_GAS_PRICE_FALLBACK = '101000000000'
const gasPrice = proxyquire('../src/services/gasPrice', { '../utils/utils': utils })
await gasPrice.start('home')
// Then const oracleFetchFn = () => ({
expect(gasPrice).to.equal(GAS_PRICE_BOUNDARIES.MIN) json: () => ({
}) standard: '103'
it('should return max limit if gas price is above max boundary', () => {
// Given
const initialGasPrice = 260
// When
const gasPrice = gasPriceWithinLimits(initialGasPrice)
// Then
expect(gasPrice).to.equal(GAS_PRICE_BOUNDARIES.MAX)
}) })
}) })
describe('normalizeGasPrice', () => {
it('should work with oracle gas price in gwei', () => {
// Given
const oracleGasPrice = 20
const factor = 1
// When // when
const result = normalizeGasPrice(oracleGasPrice, factor) await gasPrice.fetchGasPrice('standard', 1, null, oracleFetchFn)
// Then // then
expect(result).to.equal('20000000000') expect(gasPrice.getPrice().toString()).to.equal('103000000000')
}) })
it('should work with oracle gas price not in gwei', () => {
// Given
const oracleGasPrice = 200
const factor = 0.1
// When it('should fetch gas from contract', async () => {
const result = normalizeGasPrice(oracleGasPrice, factor) // given
process.env.HOME_GAS_PRICE_FALLBACK = '101000000000'
const gasPrice = proxyquire('../src/services/gasPrice', { '../utils/utils': utils })
await gasPrice.start('home')
// Then const bridgeContractMock = {
expect(result).to.equal('20000000000') methods: {
gasPrice: sinon.stub().returns({
call: sinon.stub().returns(Promise.resolve('102000000000'))
}) })
it('should increase gas price value from oracle', () => { }
// Given }
const oracleGasPrice = 20
const factor = 1.5
// When // when
const result = normalizeGasPrice(oracleGasPrice, factor) await gasPrice.fetchGasPrice('standard', 1, bridgeContractMock, null)
// Then // then
expect(result).to.equal('30000000000') expect(gasPrice.getPrice().toString()).to.equal('102000000000')
}) })
it('should respect gas price max limit', () => {
// Given
const oracleGasPrice = 200
const factor = 4
const maxInWei = Web3Utils.toWei(GAS_PRICE_BOUNDARIES.MAX.toString(), 'gwei')
// When it('should fetch the gas price from the oracle first', async () => {
const result = normalizeGasPrice(oracleGasPrice, factor) // given
process.env.HOME_GAS_PRICE_FALLBACK = '101000000000'
const gasPrice = proxyquire('../src/services/gasPrice', { '../utils/utils': utils })
await gasPrice.start('home')
// Then const bridgeContractMock = {
expect(result).to.equal(maxInWei) methods: {
gasPrice: sinon.stub().returns({
call: sinon.stub().returns(Promise.resolve('102000000000'))
}) })
it('should respect gas price min limit', () => { }
// Given }
const oracleGasPrice = 1
const factor = 0.01
const minInWei = Web3Utils.toWei(GAS_PRICE_BOUNDARIES.MIN.toString(), 'gwei')
// When const oracleFetchFn = () => ({
const result = normalizeGasPrice(oracleGasPrice, factor) json: () => ({
standard: '103'
})
})
// Then // when
expect(result).to.equal(minInWei) await gasPrice.fetchGasPrice('standard', 1, bridgeContractMock, oracleFetchFn)
// then
expect(gasPrice.getPrice().toString()).to.equal('103000000000')
})
it('log errors using the logger', async () => {
// given
const fakeLogger = { error: sinon.spy() }
const gasPrice = proxyquire('../src/services/gasPrice', {
'../utils/utils': utils,
'../services/logger': { child: () => fakeLogger }
})
await gasPrice.start('home')
// when
await gasPrice.fetchGasPrice('standard', 1, null, null)
// then
expect(fakeLogger.error.calledTwice).to.equal(true) // two errors
}) })
}) })
}) })

@ -1,6 +1,6 @@
import { observable, computed } from 'mobx' import { observable, computed } from 'mobx'
import { toHex } from 'web3-utils' import { toHex } from 'web3-utils'
import { fetchGasPrice, fetchGasPriceFromOracle } from './utils/gas' import { gasPriceFromOracle } from '../../../commons'
const HOME_GAS_PRICE_FALLBACK = process.env.REACT_APP_HOME_GAS_PRICE_FALLBACK const HOME_GAS_PRICE_FALLBACK = process.env.REACT_APP_HOME_GAS_PRICE_FALLBACK
const HOME_GAS_PRICE_ORACLE_URL = process.env.REACT_APP_HOME_GAS_PRICE_ORACLE_URL const HOME_GAS_PRICE_ORACLE_URL = process.env.REACT_APP_HOME_GAS_PRICE_ORACLE_URL
@ -50,11 +50,9 @@ class GasPriceStore {
this.factor = Number(FOREIGN_GAS_PRICE_FACTOR) || DEFAULT_GAS_PRICE_FACTOR this.factor = Number(FOREIGN_GAS_PRICE_FACTOR) || DEFAULT_GAS_PRICE_FACTOR
} }
const newGasPrice = await fetchGasPrice({ const oracleOptions = { speedType: this.speedType, factor: this.factor, logger: console }
oracleFn: () => fetchGasPriceFromOracle(this.oracleUrl, this.speedType, this.factor) this.gasPrice = (await gasPriceFromOracle(() => fetch(this.oracleUrl), oracleOptions)) || this.gasPrice
})
this.gasPrice = newGasPrice || this.gasPrice
setTimeout(() => this.updateGasPrice(), this.updateInterval) setTimeout(() => this.updateGasPrice(), this.updateInterval)
} }

@ -1,37 +0,0 @@
import { normalizeGasPrice } from '../gas'
describe('normalizeGasPrice', () => {
it('should work with oracle gas price in gwei', () => {
// Given
const oracleGasPrice = 30
const factor = 1
// When
const result = normalizeGasPrice(oracleGasPrice, factor)
// Then
expect(result).toEqual('30000000000')
})
it('should work with oracle gas price not in gwei', () => {
// Given
const oracleGasPrice = 300
const factor = 0.1
// When
const result = normalizeGasPrice(oracleGasPrice, factor)
// Then
expect(result).toEqual('30000000000')
})
it('should increase gas price value from oracle', () => {
// Given
const oracleGasPrice = 20
const factor = 1.5
// When
const result = normalizeGasPrice(oracleGasPrice, factor)
// Then
expect(result).toEqual('30000000000')
})
})

@ -1,31 +0,0 @@
const { toWei } = require('web3-utils')
export async function fetchGasPrice({ oracleFn }) {
let gasPrice = null
try {
gasPrice = await oracleFn()
} catch (e) {
if (!e.message.includes('Gas Price Oracle url not defined')) {
console.error(`Gas Price API is not available. ${e.message}`)
}
}
return gasPrice
}
export async function fetchGasPriceFromOracle(oracleUrl, speedType, factor) {
if (!oracleUrl) {
throw new Error(`Gas Price Oracle url not defined`)
}
const response = await fetch(oracleUrl)
const json = await response.json()
const gasPrice = json[speedType]
if (!gasPrice) {
throw new Error(`Response from Oracle didn't include gas price for ${speedType} type.`)
}
return normalizeGasPrice(gasPrice, factor)
}
export function normalizeGasPrice(oracleGasPrice, factor) {
const gasPrice = oracleGasPrice * factor
return toWei(gasPrice.toFixed(2).toString(), 'gwei')
}