2021-04-13 17:05:01 +03:00
|
|
|
const { toWei, toBN, BN } = require('web3-utils')
|
2020-08-23 22:56:59 +03:00
|
|
|
const { GasPriceOracle } = require('gas-price-oracle')
|
2021-05-08 18:50:46 +03:00
|
|
|
const { BRIDGE_MODES } = require('./constants')
|
2019-08-08 16:27:09 +03:00
|
|
|
const { REWARDABLE_VALIDATORS_ABI } = require('./abis')
|
2019-07-11 16:46:52 +03:00
|
|
|
|
2020-08-23 22:56:59 +03:00
|
|
|
const gasPriceOracle = new GasPriceOracle()
|
|
|
|
|
2019-07-11 16:46:52 +03:00
|
|
|
function decodeBridgeMode(bridgeModeHash) {
|
|
|
|
switch (bridgeModeHash) {
|
|
|
|
case '0x18762d46':
|
|
|
|
return BRIDGE_MODES.ERC_TO_NATIVE
|
2019-10-21 15:57:28 +03:00
|
|
|
case '0x2544fbb9':
|
|
|
|
return BRIDGE_MODES.ARBITRARY_MESSAGE
|
2020-05-18 23:36:52 +03:00
|
|
|
case '0x76595b56':
|
|
|
|
return BRIDGE_MODES.AMB_ERC_TO_ERC
|
2019-07-11 16:46:52 +03:00
|
|
|
default:
|
|
|
|
throw new Error(`Unrecognized bridge mode hash: '${bridgeModeHash}'`)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async function getBridgeMode(contract) {
|
2021-05-08 18:50:46 +03:00
|
|
|
const bridgeModeHash = await contract.methods.getBridgeMode().call()
|
|
|
|
return decodeBridgeMode(bridgeModeHash)
|
2019-07-11 16:46:52 +03:00
|
|
|
}
|
|
|
|
|
2019-08-08 16:27:09 +03:00
|
|
|
const parseValidatorEvent = event => {
|
|
|
|
if (
|
|
|
|
event.event === undefined &&
|
|
|
|
event.raw &&
|
|
|
|
event.raw.topics &&
|
|
|
|
(event.raw.topics[0] === '0xe366c1c0452ed8eec96861e9e54141ebff23c9ec89fe27b996b45f5ec3884987' ||
|
|
|
|
event.raw.topics[0] === '0x8064a302796c89446a96d63470b5b036212da26bd2debe5bec73e0170a9a5e83')
|
|
|
|
) {
|
|
|
|
const rawAddress = event.raw.topics.length > 1 ? event.raw.topics[1] : event.raw.data
|
|
|
|
const address = '0x' + rawAddress.slice(26)
|
|
|
|
event.event = 'ValidatorAdded'
|
|
|
|
event.returnValues.validator = address
|
|
|
|
} else if (
|
|
|
|
event.event === undefined &&
|
|
|
|
event.raw &&
|
|
|
|
event.raw.topics &&
|
|
|
|
event.raw.topics[0] === '0xe1434e25d6611e0db941968fdc97811c982ac1602e951637d206f5fdda9dd8f1'
|
|
|
|
) {
|
|
|
|
const rawAddress = event.raw.data === '0x' ? event.raw.topics[1] : event.raw.data
|
|
|
|
const address = '0x' + rawAddress.slice(26)
|
|
|
|
event.event = 'ValidatorRemoved'
|
|
|
|
event.returnValues.validator = address
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const processValidatorsEvents = events => {
|
|
|
|
const validatorList = new Set()
|
|
|
|
events.forEach(event => {
|
|
|
|
parseValidatorEvent(event)
|
|
|
|
|
|
|
|
if (event.event === 'ValidatorAdded') {
|
|
|
|
validatorList.add(event.returnValues.validator)
|
|
|
|
} else if (event.event === 'ValidatorRemoved') {
|
|
|
|
validatorList.delete(event.returnValues.validator)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
return Array.from(validatorList)
|
|
|
|
}
|
|
|
|
|
|
|
|
const tryCall = async (method, fallbackValue) => {
|
|
|
|
try {
|
|
|
|
return await method.call()
|
|
|
|
} catch (e) {
|
|
|
|
return fallbackValue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const getDeployedAtBlock = async contract => tryCall(contract.methods.deployedAtBlock(), 0)
|
|
|
|
|
2021-04-13 17:05:01 +03:00
|
|
|
const getPastEventsOrSplit = async (contract, { event, fromBlock, toBlock, options }) => {
|
|
|
|
let events = []
|
2019-08-08 16:27:09 +03:00
|
|
|
try {
|
|
|
|
events = await contract.getPastEvents(event, {
|
|
|
|
...options,
|
|
|
|
fromBlock,
|
|
|
|
toBlock
|
|
|
|
})
|
|
|
|
} catch (e) {
|
2021-04-13 17:05:01 +03:00
|
|
|
if (e.message.includes('query returned more than') || e.message.toLowerCase().includes('timeout')) {
|
2019-08-08 16:27:09 +03:00
|
|
|
const middle = toBN(fromBlock)
|
2020-11-04 14:24:42 +03:00
|
|
|
.add(toBN(toBlock))
|
2019-08-08 16:27:09 +03:00
|
|
|
.divRound(toBN(2))
|
|
|
|
const middlePlusOne = middle.add(toBN(1))
|
|
|
|
|
2021-04-13 17:05:01 +03:00
|
|
|
const firstHalfEvents = await getPastEventsOrSplit(contract, {
|
2020-10-05 15:48:36 +03:00
|
|
|
options,
|
2019-08-08 16:27:09 +03:00
|
|
|
event,
|
|
|
|
fromBlock,
|
|
|
|
toBlock: middle
|
|
|
|
})
|
2021-04-13 17:05:01 +03:00
|
|
|
const secondHalfEvents = await getPastEventsOrSplit(contract, {
|
2020-10-05 15:48:36 +03:00
|
|
|
options,
|
2019-08-08 16:27:09 +03:00
|
|
|
event,
|
|
|
|
fromBlock: middlePlusOne,
|
|
|
|
toBlock
|
|
|
|
})
|
|
|
|
events = [...firstHalfEvents, ...secondHalfEvents]
|
|
|
|
} else {
|
|
|
|
throw new Error(e)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return events
|
|
|
|
}
|
|
|
|
|
2021-04-13 17:05:01 +03:00
|
|
|
const getPastEvents = async (
|
|
|
|
contract,
|
|
|
|
{ event = 'allEvents', fromBlock = toBN(0), toBlock = 'latest', options = {} }
|
|
|
|
) => {
|
|
|
|
if (toBlock === 'latest') {
|
|
|
|
return contract.getPastEvents(event, {
|
|
|
|
...options,
|
|
|
|
fromBlock,
|
|
|
|
toBlock
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
const batchSize = 1000000
|
|
|
|
const to = toBN(toBlock)
|
|
|
|
const events = []
|
|
|
|
|
|
|
|
for (let from = toBN(fromBlock); from.lte(to); from = from.addn(batchSize + 1)) {
|
|
|
|
const opts = { event, fromBlock: from, toBlock: BN.min(to, from.addn(batchSize)), options }
|
|
|
|
const batch = await getPastEventsOrSplit(contract, opts)
|
|
|
|
events.push(batch)
|
|
|
|
}
|
|
|
|
|
|
|
|
return [].concat(...events)
|
|
|
|
}
|
|
|
|
|
2019-08-08 16:27:09 +03:00
|
|
|
const getValidatorList = async (address, eth, options) => {
|
|
|
|
options.logger && options.logger.debug && options.logger.debug('getting validatorList')
|
|
|
|
|
|
|
|
const validatorsContract = new eth.Contract(REWARDABLE_VALIDATORS_ABI, address) // in monitor, BRIDGE_VALIDATORS_ABI was used
|
|
|
|
const validators = await tryCall(validatorsContract.methods.validatorList(), [])
|
|
|
|
|
|
|
|
if (validators.length) {
|
|
|
|
return validators
|
|
|
|
}
|
|
|
|
|
|
|
|
options.logger && options.logger.debug && options.logger.debug('getting validatorsEvents')
|
|
|
|
|
|
|
|
const deployedAtBlock = await tryCall(validatorsContract.methods.deployedAtBlock(), 0)
|
|
|
|
const fromBlock = options.fromBlock || Number(deployedAtBlock) || 0
|
|
|
|
const toBlock = options.toBlock || 'latest'
|
|
|
|
|
|
|
|
const validatorsEvents = await getPastEvents(new eth.Contract([], address), {
|
|
|
|
event: 'allEvents',
|
|
|
|
fromBlock,
|
|
|
|
toBlock,
|
|
|
|
options: {}
|
|
|
|
})
|
|
|
|
|
|
|
|
return processValidatorsEvents(validatorsEvents)
|
|
|
|
}
|
|
|
|
|
2019-08-05 18:22:57 +03:00
|
|
|
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.
|
2019-09-13 15:54:51 +03:00
|
|
|
const gasPriceFromSupplier = async (fetchFn, options = {}) => {
|
2019-08-05 18:22:57 +03:00
|
|
|
try {
|
2020-08-23 22:56:59 +03:00
|
|
|
let json
|
|
|
|
if (fetchFn) {
|
|
|
|
const response = await fetchFn()
|
|
|
|
json = await response.json()
|
|
|
|
} else {
|
|
|
|
json = await gasPriceOracle.fetchGasPricesOffChain()
|
|
|
|
}
|
2019-08-05 18:22:57 +03:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2019-07-11 16:46:52 +03:00
|
|
|
module.exports = {
|
|
|
|
decodeBridgeMode,
|
|
|
|
getBridgeMode,
|
2019-08-08 16:27:09 +03:00
|
|
|
parseValidatorEvent,
|
|
|
|
processValidatorsEvents,
|
|
|
|
getValidatorList,
|
|
|
|
getPastEvents,
|
|
|
|
getDeployedAtBlock,
|
2019-08-05 18:22:57 +03:00
|
|
|
normalizeGasPrice,
|
2019-09-13 15:54:51 +03:00
|
|
|
gasPriceFromSupplier,
|
2019-08-05 18:22:57 +03:00
|
|
|
gasPriceFromContract,
|
2021-05-08 18:50:46 +03:00
|
|
|
gasPriceWithinLimits
|
2019-07-11 16:46:52 +03:00
|
|
|
}
|