2019-08-05 18:22:57 +03:00
|
|
|
const { toWei, toBN } = require('web3-utils')
|
2019-07-26 15:26:14 +03:00
|
|
|
const { BRIDGE_MODES, FEE_MANAGER_MODE, ERC_TYPES } = require('./constants')
|
2019-08-08 16:27:09 +03:00
|
|
|
const { REWARDABLE_VALIDATORS_ABI } = require('./abis')
|
2019-07-11 16:46:52 +03:00
|
|
|
|
|
|
|
function decodeBridgeMode(bridgeModeHash) {
|
|
|
|
switch (bridgeModeHash) {
|
|
|
|
case '0x92a8d7fe':
|
|
|
|
return BRIDGE_MODES.NATIVE_TO_ERC
|
|
|
|
case '0xba4690f5':
|
|
|
|
return BRIDGE_MODES.ERC_TO_ERC
|
|
|
|
case '0x18762d46':
|
|
|
|
return BRIDGE_MODES.ERC_TO_NATIVE
|
2019-09-18 22:45:13 +03:00
|
|
|
case '0x2544fbb9':
|
|
|
|
return BRIDGE_MODES.ARBITRARY_MESSAGE
|
2019-07-11 16:46:52 +03:00
|
|
|
default:
|
|
|
|
throw new Error(`Unrecognized bridge mode hash: '${bridgeModeHash}'`)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const decodeFeeManagerMode = managerModeHash => {
|
|
|
|
switch (managerModeHash) {
|
|
|
|
case '0xf2aed8f7':
|
|
|
|
return FEE_MANAGER_MODE.ONE_DIRECTION
|
|
|
|
case '0xd7de965f':
|
|
|
|
return FEE_MANAGER_MODE.BOTH_DIRECTIONS
|
|
|
|
default:
|
|
|
|
throw new Error(`Unrecognized fee manager mode hash: '${managerModeHash}'`)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async function getBridgeMode(contract) {
|
|
|
|
try {
|
|
|
|
const bridgeModeHash = await contract.methods.getBridgeMode().call()
|
|
|
|
return decodeBridgeMode(bridgeModeHash)
|
|
|
|
} catch (e) {
|
|
|
|
return BRIDGE_MODES.NATIVE_TO_ERC_V1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-26 15:26:14 +03:00
|
|
|
const getTokenType = async (bridgeTokenContract, bridgeAddress) => {
|
|
|
|
try {
|
|
|
|
const resultBridgeAddress = await bridgeTokenContract.methods.bridgeContract().call()
|
|
|
|
if (resultBridgeAddress === bridgeAddress) {
|
|
|
|
return ERC_TYPES.ERC677
|
|
|
|
} else {
|
|
|
|
return ERC_TYPES.ERC20
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
return ERC_TYPES.ERC20
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-11 16:46:52 +03:00
|
|
|
const getUnit = bridgeMode => {
|
|
|
|
let unitHome = null
|
|
|
|
let unitForeign = null
|
|
|
|
if (bridgeMode === BRIDGE_MODES.NATIVE_TO_ERC) {
|
|
|
|
unitHome = 'Native coins'
|
|
|
|
unitForeign = 'Tokens'
|
|
|
|
} else if (bridgeMode === BRIDGE_MODES.ERC_TO_ERC) {
|
|
|
|
unitHome = 'Tokens'
|
|
|
|
unitForeign = 'Tokens'
|
|
|
|
} else if (bridgeMode === BRIDGE_MODES.ERC_TO_NATIVE) {
|
|
|
|
unitHome = 'Native coins'
|
|
|
|
unitForeign = 'Tokens'
|
|
|
|
} else {
|
|
|
|
throw new Error(`Unrecognized bridge mode: ${bridgeMode}`)
|
|
|
|
}
|
|
|
|
|
|
|
|
return { unitHome, unitForeign }
|
|
|
|
}
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
const getPastEvents = async (
|
|
|
|
contract,
|
|
|
|
{ event = 'allEvents', fromBlock = toBN(0), toBlock = 'latest', options = {} }
|
|
|
|
) => {
|
|
|
|
let events
|
|
|
|
try {
|
|
|
|
events = await contract.getPastEvents(event, {
|
|
|
|
...options,
|
|
|
|
fromBlock,
|
|
|
|
toBlock
|
|
|
|
})
|
|
|
|
} catch (e) {
|
|
|
|
if (e.message.includes('query returned more than') && toBlock !== 'latest') {
|
|
|
|
const middle = toBN(fromBlock)
|
|
|
|
.add(toBlock)
|
|
|
|
.divRound(toBN(2))
|
|
|
|
const middlePlusOne = middle.add(toBN(1))
|
|
|
|
|
|
|
|
const firstHalfEvents = await getPastEvents(contract, {
|
|
|
|
...options,
|
|
|
|
event,
|
|
|
|
fromBlock,
|
|
|
|
toBlock: middle
|
|
|
|
})
|
|
|
|
const secondHalfEvents = await getPastEvents(contract, {
|
|
|
|
...options,
|
|
|
|
event,
|
|
|
|
fromBlock: middlePlusOne,
|
|
|
|
toBlock
|
|
|
|
})
|
|
|
|
events = [...firstHalfEvents, ...secondHalfEvents]
|
|
|
|
} else {
|
|
|
|
throw new Error(e)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return events
|
|
|
|
}
|
|
|
|
|
|
|
|
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 {
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2019-07-11 16:46:52 +03:00
|
|
|
module.exports = {
|
|
|
|
decodeBridgeMode,
|
|
|
|
decodeFeeManagerMode,
|
|
|
|
getBridgeMode,
|
2019-07-26 15:26:14 +03:00
|
|
|
getTokenType,
|
2019-08-05 18:22:57 +03:00
|
|
|
getUnit,
|
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,
|
|
|
|
gasPriceWithinLimits
|
2019-07-11 16:46:52 +03:00
|
|
|
}
|