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:
parent
2be0e9f363
commit
055a444fae
@ -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
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'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// when
|
||||||
|
await gasPrice.fetchGasPrice('standard', 1, null, oracleFetchFn)
|
||||||
|
|
||||||
|
// then
|
||||||
|
expect(gasPrice.getPrice().toString()).to.equal('103000000000')
|
||||||
})
|
})
|
||||||
it('should return max limit if gas price is above max boundary', () => {
|
|
||||||
// Given
|
|
||||||
const initialGasPrice = 260
|
|
||||||
|
|
||||||
// When
|
it('should fetch gas from contract', 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 bridgeContractMock = {
|
||||||
expect(gasPrice).to.equal(GAS_PRICE_BOUNDARIES.MAX)
|
methods: {
|
||||||
|
gasPrice: sinon.stub().returns({
|
||||||
|
call: sinon.stub().returns(Promise.resolve('102000000000'))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// when
|
||||||
|
await gasPrice.fetchGasPrice('standard', 1, bridgeContractMock, null)
|
||||||
|
|
||||||
|
// then
|
||||||
|
expect(gasPrice.getPrice().toString()).to.equal('102000000000')
|
||||||
})
|
})
|
||||||
})
|
|
||||||
describe('normalizeGasPrice', () => {
|
|
||||||
it('should work with oracle gas price in gwei', () => {
|
|
||||||
// Given
|
|
||||||
const oracleGasPrice = 20
|
|
||||||
const factor = 1
|
|
||||||
|
|
||||||
// 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('20000000000')
|
methods: {
|
||||||
|
gasPrice: sinon.stub().returns({
|
||||||
|
call: sinon.stub().returns(Promise.resolve('102000000000'))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const oracleFetchFn = () => ({
|
||||||
|
json: () => ({
|
||||||
|
standard: '103'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// when
|
||||||
|
await gasPrice.fetchGasPrice('standard', 1, bridgeContractMock, oracleFetchFn)
|
||||||
|
|
||||||
|
// then
|
||||||
|
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('log errors using the logger', async () => {
|
||||||
const result = normalizeGasPrice(oracleGasPrice, factor)
|
// given
|
||||||
|
const fakeLogger = { error: sinon.spy() }
|
||||||
|
const gasPrice = proxyquire('../src/services/gasPrice', {
|
||||||
|
'../utils/utils': utils,
|
||||||
|
'../services/logger': { child: () => fakeLogger }
|
||||||
|
})
|
||||||
|
await gasPrice.start('home')
|
||||||
|
|
||||||
// Then
|
// when
|
||||||
expect(result).to.equal('20000000000')
|
await gasPrice.fetchGasPrice('standard', 1, null, null)
|
||||||
})
|
|
||||||
it('should increase gas price value from oracle', () => {
|
|
||||||
// Given
|
|
||||||
const oracleGasPrice = 20
|
|
||||||
const factor = 1.5
|
|
||||||
|
|
||||||
// When
|
// then
|
||||||
const result = normalizeGasPrice(oracleGasPrice, factor)
|
expect(fakeLogger.error.calledTwice).to.equal(true) // two errors
|
||||||
|
|
||||||
// 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)
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
|
|
||||||
// Then
|
|
||||||
expect(result).to.equal(minInWei)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -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')
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user