Compare commits

...

37 Commits

Author SHA1 Message Date
Gerardo Nardelli
9d4dea9823 Fix amb subsidized mode e2e test 2019-08-22 17:35:53 -03:00
Gerardo Nardelli
2357d787a6 Fix amb subsidized mode tests in oracle-e2e 2019-08-22 17:22:49 -03:00
Gerardo Nardelli
065956e94e Fix amb subsidized mode hash in oracle-e2e 2019-08-22 17:08:49 -03:00
Gerardo Nardelli
7e651a86c4 Update oracle-e2e tests box contract addresses 2019-08-22 16:53:14 -03:00
Gerardo Nardelli
411c35d873 Fix amb packSignatures method in Oracle 2019-08-22 16:16:54 -03:00
Gerardo Nardelli
3ff096ef55 Lint fixes 2019-08-22 13:37:38 -03:00
Gerardo Nardelli
7f15733ad3 Merge fix 2019-08-22 13:30:07 -03:00
Gerardo Nardelli
d22a3e8bae Merge branch 'master' into amb-oracle
# Conflicts:
#	.prettierrc
#	commons/abis.js
#	commons/package.json
#	e2e-commons/docker-compose.yml
#	monitor/eventsStats.js
#	monitor/utils/events.js
#	oracle/src/services/gasPrice.js
#	oracle/test/gasPrice.test.js
#	yarn.lock
2019-08-22 12:10:49 -03:00
Gerardo Nardelli
43609d739d Pack signatures when processing amb collectedSignatures event in Oracle 2019-08-22 11:06:44 -03:00
Gerardo Nardelli
ab5166b895 Update submodule 2019-08-22 10:39:47 -03:00
Gerardo Nardelli
383e22cb58 Update amb message processing 2019-08-20 15:19:49 -03:00
Gerardo Nardelli
e1f20b8686 Update contract submodule 2019-08-20 15:19:09 -03:00
Gerardo Nardelli
4b59f18d29 Fix amb monitor 2019-07-30 10:05:44 -03:00
Gerardo Nardelli
2d42c236d7 Fix prettier 2019-07-25 15:28:36 -03:00
Gerardo Nardelli
ee4a6c8f5c Fix amb events in monitor 2019-07-25 14:44:05 -03:00
Gerardo Nardelli
55f8443c55 Update contract submodule 2019-07-25 14:43:14 -03:00
Gerardo Nardelli
e5ce665656 Move amb message utils to commons 2019-07-25 12:36:10 -03:00
Gerardo Nardelli
751b885f73 Support amb events info in monitor 2019-07-25 11:30:59 -03:00
Gerardo Nardelli
3d0f68fad9 Support amb balances info in monitor 2019-07-24 11:50:35 -03:00
Gerardo Nardelli
08d2c46a03 Update deployment oracle template 2019-07-23 11:35:54 -03:00
Gerardo Nardelli
48620a8d01 Update submodule to latest amb contract changes 2019-07-18 15:07:29 -03:00
Gerardo Nardelli
35c2e642ee Update amb contract submodule 2019-07-18 10:23:03 -03:00
Gerardo Nardelli
a056a75932 Update amb contract submodule 2019-07-18 09:30:40 -03:00
Gerardo Nardelli
e8a179bc19 Update contract module name 2019-07-18 09:04:10 -03:00
Gerardo Nardelli
aa32f2cc0c Update contract submodule 2019-07-18 08:39:51 -03:00
Gerardo Nardelli
473b6a3e46 Fix commons unit test 2019-07-17 12:09:55 -03:00
Gerardo Nardelli
8cf97aea3c Fix e2e-commons up script 2019-07-17 12:03:29 -03:00
Gerardo Nardelli
ac6775bd6e Lint fix 2019-07-17 11:53:43 -03:00
Gerardo Nardelli
478b30b2ed Add ARBITRARY_MESSAGE bridge mode 2019-07-17 11:49:29 -03:00
Gerardo Nardelli
70d03c1a93 Oracle amb merge fix 2019-07-17 11:47:25 -03:00
Gerardo Nardelli
0694fc24e9 Merge branch 'master' into amb-oracle
# Conflicts:
#	oracle-e2e/deploy.js
#	oracle-e2e/docker-compose.yml
#	oracle-e2e/run-tests.sh
#	oracle/config/base.config.js
#	oracle/src/services/gasPrice.js
#	oracle/src/utils/utils.js
#	oracle/test/gasPrice.test.js
2019-07-17 10:00:10 -03:00
Gerardo Nardelli
80940a0a82 Use compiled amb contracts on oracle 2019-06-04 11:25:53 -03:00
Gerardo Nardelli
a2603701b6 Merge branch 'master' into amb-oracle
# Conflicts:
#	oracle-e2e/run-tests.sh
2019-06-04 10:26:31 -03:00
Gerardo Nardelli
e006443f50 add amb oracle e2e tests 2019-06-03 16:56:53 -03:00
Gerardo Nardelli
0326745684 Update submodule to amb branch 2019-06-03 16:56:13 -03:00
Gerardo Nardelli
95f4a0acc6 use gas price from message on gas estimate for amb events 2019-05-27 16:28:29 -03:00
Gerardo Nardelli
7ca3b0079c add amb base implementation on oracle 2019-05-24 15:42:26 -03:00
35 changed files with 1463 additions and 64 deletions

@ -10,6 +10,9 @@ const ERC677_BRIDGE_TOKEN_ABI = require('../contracts/build/contracts/ERC677Brid
const BLOCK_REWARD_ABI = require('../contracts/build/contracts/IBlockReward').abi
const BRIDGE_VALIDATORS_ABI = require('../contracts/build/contracts/BridgeValidators').abi
const REWARDABLE_VALIDATORS_ABI = require('../contracts/build/contracts/RewardableValidators').abi
const HOME_AMB_ABI = require('../contracts/build/contracts/HomeAMB').abi
const FOREIGN_AMB_ABI = require('../contracts/build/contracts/ForeignAMB').abi
const BOX_ABI = require('../contracts/build/contracts/Box').abi
const { HOME_V1_ABI, FOREIGN_V1_ABI } = require('./v1Abis')
const { BRIDGE_MODES } = require('./constants')
@ -60,6 +63,9 @@ function getBridgeABIs(bridgeMode) {
} else if (bridgeMode === BRIDGE_MODES.NATIVE_TO_ERC_V1) {
HOME_ABI = HOME_V1_ABI
FOREIGN_ABI = FOREIGN_V1_ABI
} else if (bridgeMode === BRIDGE_MODES.ARBITRARY_MESSAGE) {
HOME_ABI = HOME_AMB_ABI
FOREIGN_ABI = FOREIGN_AMB_ABI
} else {
throw new Error(`Unrecognized bridge mode: ${bridgeMode}`)
}
@ -83,5 +89,8 @@ module.exports = {
REWARDABLE_VALIDATORS_ABI,
HOME_V1_ABI,
FOREIGN_V1_ABI,
ERC20_BYTES32_ABI
ERC20_BYTES32_ABI,
HOME_AMB_ABI,
FOREIGN_AMB_ABI,
BOX_ABI
}

@ -2,7 +2,8 @@ const BRIDGE_MODES = {
NATIVE_TO_ERC: 'NATIVE_TO_ERC',
ERC_TO_ERC: 'ERC_TO_ERC',
ERC_TO_NATIVE: 'ERC_TO_NATIVE',
NATIVE_TO_ERC_V1: 'NATIVE_TO_ERC_V1'
NATIVE_TO_ERC_V1: 'NATIVE_TO_ERC_V1',
ARBITRARY_MESSAGE: 'ARBITRARY_MESSAGE'
}
const ERC_TYPES = {
@ -16,4 +17,23 @@ const FEE_MANAGER_MODE = {
UNDEFINED: 'UNDEFINED'
}
module.exports = { BRIDGE_MODES, ERC_TYPES, FEE_MANAGER_MODE }
const GAS_PRICE_OPTIONS = {
UNDEFINED: '00',
GAS_PRICE: '01',
SPEED: '02'
}
const ORACLE_GAS_PRICE_SPEEDS = {
SLOW: 'slow',
STANDARD: 'standard',
FAST: 'fast',
INSTANT: 'instant'
}
module.exports = {
BRIDGE_MODES,
ERC_TYPES,
FEE_MANAGER_MODE,
GAS_PRICE_OPTIONS,
ORACLE_GAS_PRICE_SPEEDS
}

@ -1,9 +1,11 @@
const constants = require('./constants')
const abis = require('./abis')
const utils = require('./utils')
const message = require('./message')
module.exports = {
...constants,
...abis,
...utils
...utils,
...message
}

63
commons/message.js Normal file

@ -0,0 +1,63 @@
const web3Utils = require('web3-utils')
const { GAS_PRICE_OPTIONS, ORACLE_GAS_PRICE_SPEEDS } = require('./constants')
const gasPriceSpeedMapper = {
'01': ORACLE_GAS_PRICE_SPEEDS.INSTANT,
'02': ORACLE_GAS_PRICE_SPEEDS.FAST,
'03': ORACLE_GAS_PRICE_SPEEDS.STANDARD,
'04': ORACLE_GAS_PRICE_SPEEDS.SLOW
}
function strip0x(input) {
return input.replace(/^0x/, '')
}
function addTxHashToData({ encodedData, transactionHash }) {
return encodedData.slice(0, 2) + strip0x(transactionHash) + encodedData.slice(2)
}
function parseAMBMessage(message) {
message = strip0x(message)
const txHash = `0x${message.slice(0, 64)}`
const sender = `0x${message.slice(64, 104)}`
const executor = `0x${message.slice(104, 144)}`
const gasLimit = web3Utils.toBN(message.slice(144, 208))
const dataType = message.slice(208, 210)
let gasPrice = null
let gasPriceSpeed = null
let dataStart = 210
switch (dataType) {
case GAS_PRICE_OPTIONS.GAS_PRICE:
gasPrice = web3Utils.toBN(message.slice(210, 274))
dataStart += 64
break
case GAS_PRICE_OPTIONS.SPEED:
gasPriceSpeed = gasPriceSpeedMapper[message.slice(210, 212)]
dataStart += 2
break
case GAS_PRICE_OPTIONS.UNDEFINED:
default:
break
}
const data = `0x${message.slice(dataStart, message.length)}`
return {
sender,
executor,
txHash,
gasLimit,
dataType,
gasPrice,
gasPriceSpeed,
data
}
}
module.exports = {
addTxHashToData,
parseAMBMessage,
strip0x
}

@ -8,6 +8,10 @@
"test": "NODE_ENV=test mocha"
},
"dependencies": {
"web3-utils": "1.0.0-beta.30"
"web3-utils": "1.0.0-beta.34"
},
"devDependencies": {
"bn-chai": "^1.0.1",
"chai": "^4.2.0"
}
}

@ -3,7 +3,7 @@ const { BRIDGE_MODES, ERC_TYPES } = require('../constants')
describe('constants', () => {
it('should contain correct number of bridge types', () => {
expect(Object.keys(BRIDGE_MODES).length).to.be.equal(4)
expect(Object.keys(BRIDGE_MODES).length).to.be.equal(5)
})
it('should contain correct number of erc types', () => {

@ -0,0 +1,125 @@
const { BN, toBN } = require('web3-utils')
const { expect } = require('chai').use(require('bn-chai')(BN))
const { parseAMBMessage, strip0x, addTxHashToData } = require('../message')
const { ORACLE_GAS_PRICE_SPEEDS } = require('../constants')
describe('strip0x', () => {
it('should remove 0x from input', () => {
// Given
const input = '0x12345'
// When
const result = strip0x(input)
// Then
expect(result).to.be.equal('12345')
})
it('should not modify input if 0x is not present', () => {
// Given
const input = '12345'
// When
const result = strip0x(input)
// Then
expect(result).to.be.equal(input)
})
})
describe('addTxHashToData', () => {
it('should add txHash to encoded data at position 2', () => {
// Given
const msgSender = '0x003667154bb32e42bb9e1e6532f19d187fa0082e'
const msgExecutor = '0xf4bef13f9f4f2b203faf0c3cbbaabe1afe056955'
const msgGasLimit = '000000000000000000000000000000000000000000000000000000005b877705'
const msgDataType = '00'
const msgData = '0xb1591967aed668a4b27645ff40c444892d91bf5951b382995d4d4f6ee3a2ce03'
const encodedData = `0x${strip0x(msgSender)}${strip0x(msgExecutor)}${msgGasLimit}${msgDataType}${strip0x(msgData)}`
const transactionHash = '0xbdceda9d8c94838aca10c687da1411a07b1390e88239c0638cb9cc264219cc10'
const message = `0x${strip0x(transactionHash)}${strip0x(msgSender)}${strip0x(
msgExecutor
)}${msgGasLimit}${msgDataType}${strip0x(msgData)}`
// When
const result = addTxHashToData({ encodedData, transactionHash })
// Then
expect(result).to.be.equal(message)
})
})
describe('parseAMBMessage', () => {
it('should parse data type 00', () => {
const msgSender = '0x003667154bb32e42bb9e1e6532f19d187fa0082e'
const msgExecutor = '0xf4bef13f9f4f2b203faf0c3cbbaabe1afe056955'
const msgTxHash = '0xbdceda9d8c94838aca10c687da1411a07b1390e88239c0638cb9cc264219cc10'
const msgGasLimit = '000000000000000000000000000000000000000000000000000000005b877705'
const msgDataType = '00'
const msgData = '0xb1591967aed668a4b27645ff40c444892d91bf5951b382995d4d4f6ee3a2ce03'
const message = `0x${strip0x(msgTxHash)}${strip0x(msgSender)}${strip0x(
msgExecutor
)}${msgGasLimit}${msgDataType}${strip0x(msgData)}`
// when
const { sender, executor, txHash, gasLimit, dataType, gasPrice, gasPriceSpeed, data } = parseAMBMessage(message)
// then
expect(sender).to.be.equal(msgSender)
expect(executor).to.be.equal(msgExecutor)
expect(txHash).to.be.equal(msgTxHash)
expect(gasLimit).to.eq.BN(toBN(msgGasLimit))
expect(dataType).to.be.equal(msgDataType)
expect(gasPrice).to.be.equal(null)
expect(gasPriceSpeed).to.be.equal(null)
expect(data).to.be.equal(msgData)
})
it('should parse data type 01', () => {
const msgSender = '0x003667154bb32e42bb9e1e6532f19d187fa0082e'
const msgExecutor = '0xf4bef13f9f4f2b203faf0c3cbbaabe1afe056955'
const msgTxHash = '0xbdceda9d8c94838aca10c687da1411a07b1390e88239c0638cb9cc264219cc10'
const msgGasLimit = '000000000000000000000000000000000000000000000000000000005b877705'
const msgDataType = '01'
const msgGasPrice = '0000000000000000000000000000000000000000000000000000000165a0bc00'
const msgData = '0xb1591967aed668a4b27645ff40c444892d91bf5951b382995d4d4f6ee3a2ce03'
const message = `0x${strip0x(msgTxHash)}${strip0x(msgSender)}${strip0x(
msgExecutor
)}${msgGasLimit}${msgDataType}${msgGasPrice}${strip0x(msgData)}`
// when
const { sender, executor, txHash, gasLimit, dataType, gasPrice, gasPriceSpeed, data } = parseAMBMessage(message)
// then
expect(sender).to.be.equal(msgSender)
expect(executor).to.be.equal(msgExecutor)
expect(txHash).to.be.equal(msgTxHash)
expect(gasLimit).to.eq.BN(toBN(msgGasLimit))
expect(dataType).to.be.equal(msgDataType)
expect(gasPrice).to.eq.BN(toBN(msgGasPrice))
expect(gasPriceSpeed).to.be.equal(null)
expect(data).to.be.equal(msgData)
})
it('should parse data type 02', () => {
const msgSender = '0x003667154bb32e42bb9e1e6532f19d187fa0082e'
const msgExecutor = '0xf4bef13f9f4f2b203faf0c3cbbaabe1afe056955'
const msgTxHash = '0xbdceda9d8c94838aca10c687da1411a07b1390e88239c0638cb9cc264219cc10'
const msgGasLimit = '000000000000000000000000000000000000000000000000000000005b877705'
const msgDataType = '02'
const msgGasPriceSpeed = '0x03'
const msgData = '0xb1591967aed668a4b27645ff40c444892d91bf5951b382995d4d4f6ee3a2ce03'
const message = `0x${strip0x(msgTxHash)}${strip0x(msgSender)}${strip0x(
msgExecutor
)}${msgGasLimit}${msgDataType}${strip0x(msgGasPriceSpeed)}${strip0x(msgData)}`
// when
const { sender, executor, txHash, gasLimit, dataType, gasPrice, gasPriceSpeed, data } = parseAMBMessage(message)
// then
expect(sender).to.be.equal(msgSender)
expect(executor).to.be.equal(msgExecutor)
expect(txHash).to.be.equal(msgTxHash)
expect(gasLimit).to.eq.BN(toBN(msgGasLimit))
expect(dataType).to.be.equal(msgDataType)
expect(gasPrice).to.be.equal(null)
expect(gasPriceSpeed).to.be.equal(ORACLE_GAS_PRICE_SPEEDS.STANDARD)
expect(data).to.be.equal(msgData)
})
})

@ -10,6 +10,8 @@ function decodeBridgeMode(bridgeModeHash) {
return BRIDGE_MODES.ERC_TO_ERC
case '0x18762d46':
return BRIDGE_MODES.ERC_TO_NATIVE
case '0x2544fbb9':
return BRIDGE_MODES.ARBITRARY_MESSAGE
default:
throw new Error(`Unrecognized bridge mode hash: '${bridgeModeHash}'`)
}
@ -222,7 +224,7 @@ const gasPriceFromOracle = async (fetchFn, options = {}) => {
options.logger.debug &&
options.logger.debug({ oracleGasPrice, normalizedGasPrice }, 'Gas price updated using the API')
return normalizedGasPrice
return options.returnAllSpeeds ? { gasPrice: normalizedGasPrice, oracleGasPriceSpeeds: json } : normalizedGasPrice
} catch (e) {
options.logger && options.logger.error && options.logger.error(`Gas Price API is not available. ${e.message}`)
}

@ -1 +1 @@
Subproject commit 048f51e0e8e7ce4213b727c0982aadab6c994db9
Subproject commit bab9d9199d13a2c6595f8c2fc4ed6e396143d985

@ -9,7 +9,9 @@ HOME_POLLING_INTERVAL={{ HOME_POLLING_INTERVAL }}
## Foreign contract
FOREIGN_RPC_URL={{ FOREIGN_RPC_URL }}
FOREIGN_BRIDGE_ADDRESS={{ FOREIGN_BRIDGE_ADDRESS }}
{% if ERC20_TOKEN_ADDRESS | default('') != '' %}
ERC20_TOKEN_ADDRESS={{ ERC20_TOKEN_ADDRESS }}
{% endif %}
FOREIGN_POLLING_INTERVAL={{ FOREIGN_POLLING_INTERVAL }}
## Gasprice

@ -34,6 +34,12 @@
"foreignToken": "0x3C665A31199694Bf723fD08844AD290207B5797f",
"ui": "http://localhost:3002"
},
"amb": {
"home": "0x0AEe1FCD12dDFab6265F7f8956e6E012A9Fe4Aa0",
"foreign": "0x0AEe1FCD12dDFab6265F7f8956e6E012A9Fe4Aa0",
"homeBox": "0x60a6ECE19a26476353E1Ea50fc5Db1bdBEaa74Cf",
"foreignBox": "0x60a6ECE19a26476353E1Ea50fc5Db1bdBEaa74Cf"
},
"homeRPC": {
"URL": "http://parity1:8545",
"ID": "77"

@ -126,6 +126,36 @@ services:
command: "true"
networks:
- ultimate
oracle-amb:
build:
context: ..
dockerfile: oracle/Dockerfile
environment:
- NODE_ENV=production
- BRIDGE_MODE=ARBITRARY_MESSAGE
- QUEUE_URL=amqp://rabbit
- REDIS_URL=redis://redis
- HOME_RPC_URL=http://parity1:8545
- FOREIGN_RPC_URL=http://parity2:8545
- HOME_BRIDGE_ADDRESS=0x0AEe1FCD12dDFab6265F7f8956e6E012A9Fe4Aa0
- FOREIGN_BRIDGE_ADDRESS=0x0AEe1FCD12dDFab6265F7f8956e6E012A9Fe4Aa0
- VALIDATOR_ADDRESS_PRIVATE_KEY=8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9
- HOME_GAS_PRICE_ORACLE_URL=https://gasprice.poa.network/
- HOME_GAS_PRICE_SPEED_TYPE=standard
- HOME_GAS_PRICE_FALLBACK=1000000000
- HOME_GAS_PRICE_UPDATE_INTERVAL=600000
- HOME_GAS_PRICE_FACTOR=1
- FOREIGN_GAS_PRICE_ORACLE_URL=https://gasprice.poa.network/
- FOREIGN_GAS_PRICE_SPEED_TYPE=standard
- FOREIGN_GAS_PRICE_FALLBACK=10000000000
- FOREIGN_GAS_PRICE_UPDATE_INTERVAL=600000
- FOREIGN_GAS_PRICE_FACTOR=1
- HOME_POLLING_INTERVAL=500
- FOREIGN_POLLING_INTERVAL=500
- ALLOW_HTTP=yes
command: "true"
networks:
- ultimate
ui:
build:
context: ..

27
e2e-commons/envs/amb.env Normal file

@ -0,0 +1,27 @@
BRIDGE_MODE=ARBITRARY_MESSAGE
DEPLOYMENT_ACCOUNT_PRIVATE_KEY=8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9
HOME_DEPLOYMENT_GAS_PRICE=10000000000
FOREIGN_DEPLOYMENT_GAS_PRICE=10000000000
GET_RECEIPT_INTERVAL_IN_MILLISECONDS=50
DEPLOYMENT_GAS_LIMIT_EXTRA=0.2
HOME_RPC_URL=http://parity1:8545
HOME_BRIDGE_OWNER=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b
HOME_VALIDATORS_OWNER=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b
HOME_UPGRADEABLE_ADMIN=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b
HOME_MAX_AMOUNT_PER_TX=8000000
HOME_REQUIRED_BLOCK_CONFIRMATIONS=1
HOME_GAS_PRICE=1000000000
HOME_AMB_SUBSIDIZED_MODE=false
FOREIGN_RPC_URL=http://parity2:8545
FOREIGN_BRIDGE_OWNER=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b
FOREIGN_VALIDATORS_OWNER=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b
FOREIGN_UPGRADEABLE_ADMIN=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b
FOREIGN_MAX_AMOUNT_PER_TX=8000000
FOREIGN_REQUIRED_BLOCK_CONFIRMATIONS=1
FOREIGN_GAS_PRICE=10000000000
FOREIGN_AMB_SUBSIDIZED_MODE=false
REQUIRED_NUMBER_OF_VALIDATORS=1
VALIDATORS=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b

@ -29,3 +29,14 @@ cp "$ENVS_PATH/erc-to-native.env" "$DEPLOY_PATH/.env"
cd "$DEPLOY_PATH"
node deploy.js
cd - > /dev/null
echo -e "\n\n############ Deploying amb ############\n"
cp "$ENVS_PATH/amb.env" "$DEPLOY_PATH/.env"
cd "$DEPLOY_PATH"
node deploy.js
cd - > /dev/null
echo -e "\n\n############ Deploying test contract for amb ############\n"
cd "$DEPLOY_PATH"
node src/utils/deployTestBox.js
cd - > /dev/null

@ -9,7 +9,7 @@ docker-compose up -d parity1 parity2 e2e
while [ "$1" != "" ]; do
if [ "$1" == "oracle" ]; then
docker-compose up -d redis rabbit oracle oracle-erc20 oracle-erc20-native
docker-compose up -d redis rabbit oracle oracle-erc20 oracle-erc20-native oracle-amb
docker-compose run -d oracle yarn watcher:signature-request
docker-compose run -d oracle yarn watcher:collected-signatures
@ -20,6 +20,9 @@ while [ "$1" != "" ]; do
docker-compose run -d oracle-erc20-native yarn watcher:signature-request
docker-compose run -d oracle-erc20-native yarn watcher:collected-signatures
docker-compose run -d oracle-erc20-native yarn watcher:affirmation-request
docker-compose run -d oracle-amb yarn watcher:signature-request
docker-compose run -d oracle-amb yarn watcher:collected-signatures
docker-compose run -d oracle-amb yarn watcher:affirmation-request
docker-compose run -d oracle yarn sender:home
docker-compose run -d oracle yarn sender:foreign
fi

@ -3,6 +3,8 @@ const Web3 = require('web3')
const logger = require('./logger')('alerts')
const eventsInfo = require('./utils/events')
const { getBlockNumber } = require('./utils/contract')
const { processedMsgNotDelivered } = require('./utils/message')
const { BRIDGE_MODES } = require('../commons')
const { HOME_RPC_URL, FOREIGN_RPC_URL } = process.env
@ -13,10 +15,17 @@ const foreignProvider = new Web3.providers.HttpProvider(FOREIGN_RPC_URL)
const web3Foreign = new Web3(foreignProvider)
async function main() {
const { foreignDeposits, homeDeposits, homeWithdrawals, foreignWithdrawals } = await eventsInfo()
const { foreignDeposits, homeDeposits, homeWithdrawals, foreignWithdrawals, bridgeMode } = await eventsInfo()
const xSignatures = foreignDeposits.filter(findDifferences(homeDeposits))
const xAffirmations = homeWithdrawals.filter(findDifferences(foreignWithdrawals))
let xSignatures
let xAffirmations
if (bridgeMode === BRIDGE_MODES.ARBITRARY_MESSAGE) {
xSignatures = foreignDeposits.filter(processedMsgNotDelivered(homeDeposits))
xAffirmations = homeWithdrawals.filter(processedMsgNotDelivered(foreignWithdrawals))
} else {
xSignatures = foreignDeposits.filter(findDifferences(homeDeposits))
xAffirmations = homeWithdrawals.filter(findDifferences(foreignWithdrawals))
}
logger.debug('building misbehavior blocks')
const [homeBlockNumber, foreignBlockNumber] = await getBlockNumber(web3Home, web3Foreign)

@ -1,6 +1,7 @@
require('dotenv').config()
const logger = require('./logger')('eventsStats')
const eventsInfo = require('./utils/events')
const { processedMsgNotDelivered, deliveredMsgNotProcessed } = require('./utils/message')
const { BRIDGE_MODES } = require('../commons')
function compareDepositsHome(foreign) {
return homeDeposit => {
@ -57,25 +58,45 @@ function compareTransferForeign(home) {
}
async function main() {
const { foreignDeposits, homeDeposits, homeWithdrawals, foreignWithdrawals, isExternalErc20 } = await eventsInfo()
const {
foreignDeposits,
homeDeposits,
homeWithdrawals,
foreignWithdrawals,
isExternalErc20,
bridgeMode
} = await eventsInfo()
const onlyInHomeDeposits = homeDeposits.filter(compareDepositsHome(foreignDeposits))
const onlyInForeignDeposits = foreignDeposits.concat([]).filter(compareDepositsForeign(homeDeposits))
if (bridgeMode === BRIDGE_MODES.ARBITRARY_MESSAGE) {
return {
home: {
deliveredMsgNotProcessedInForeign: homeDeposits.filter(deliveredMsgNotProcessed(foreignDeposits)),
processedMsgNotDeliveredInForeign: homeWithdrawals.filter(processedMsgNotDelivered(foreignWithdrawals))
},
foreign: {
deliveredMsgNotProcessedInHome: foreignWithdrawals.filter(deliveredMsgNotProcessed(homeWithdrawals)),
processedMsgNotDeliveredInHome: foreignDeposits.filter(processedMsgNotDelivered(homeDeposits))
},
lastChecked: Math.floor(Date.now() / 1000)
}
} else {
const onlyInHomeDeposits = homeDeposits.filter(compareDepositsHome(foreignDeposits))
const onlyInForeignDeposits = foreignDeposits.concat([]).filter(compareDepositsForeign(homeDeposits))
const onlyInHomeWithdrawals = isExternalErc20
? homeWithdrawals.filter(compareTransferHome(foreignWithdrawals))
: homeWithdrawals.filter(compareDepositsForeign(foreignWithdrawals))
const onlyInForeignWithdrawals = isExternalErc20
? foreignWithdrawals.filter(compareTransferForeign(homeWithdrawals))
: foreignWithdrawals.filter(compareDepositsHome(homeWithdrawals))
const onlyInHomeWithdrawals = isExternalErc20
? homeWithdrawals.filter(compareTransferHome(foreignWithdrawals))
: homeWithdrawals.filter(compareDepositsForeign(foreignWithdrawals))
const onlyInForeignWithdrawals = isExternalErc20
? foreignWithdrawals.filter(compareTransferForeign(homeWithdrawals))
: foreignWithdrawals.filter(compareDepositsHome(homeWithdrawals))
logger.debug('Done')
return {
onlyInHomeDeposits,
onlyInForeignDeposits,
onlyInHomeWithdrawals,
onlyInForeignWithdrawals,
lastChecked: Math.floor(Date.now() / 1000)
return {
onlyInHomeDeposits,
onlyInForeignDeposits,
onlyInHomeWithdrawals,
onlyInForeignWithdrawals,
lastChecked: Math.floor(Date.now() / 1000)
}
}
}

@ -107,6 +107,22 @@ async function main(bridgeMode) {
balanceDiff: Number(Web3Utils.fromWei(diff)),
lastChecked: Math.floor(Date.now() / 1000)
}
} else if (bridgeMode === BRIDGE_MODES.ARBITRARY_MESSAGE) {
const homeBalance = await web3Home.eth.getBalance(HOME_BRIDGE_ADDRESS)
const foreignBalance = await web3Foreign.eth.getBalance(FOREIGN_BRIDGE_ADDRESS)
const diff = new BN(homeBalance).minus(new BN(foreignBalance)).toString()
return {
home: {
balance: Web3Utils.fromWei(homeBalance, 'ether')
},
foreign: {
balance: Web3Utils.fromWei(foreignBalance, 'ether')
},
balanceDiff: Number(Web3Utils.fromWei(diff, 'ether')),
lastChecked: Math.floor(Date.now() / 1000)
}
} else {
throw new Error(`Unrecognized bridge mode: '${bridgeMode}'`)
}

@ -1,19 +1,35 @@
require('dotenv').config()
const eventsInfo = require('./utils/events')
const { BRIDGE_MODES } = require('../commons')
async function main(bridgeMode) {
const { foreignDeposits, homeDeposits, homeWithdrawals, foreignWithdrawals } = await eventsInfo(bridgeMode)
return {
depositsDiff: homeDeposits.length - foreignDeposits.length,
withdrawalDiff: homeWithdrawals.length - foreignWithdrawals.length,
home: {
deposits: homeDeposits.length,
withdrawals: homeWithdrawals.length
},
foreign: {
deposits: foreignDeposits.length,
withdrawals: foreignWithdrawals.length
if (bridgeMode === BRIDGE_MODES.ARBITRARY_MESSAGE) {
return {
deliveryDiff: homeDeposits.length - foreignDeposits.length,
processedDiff: homeWithdrawals.length - foreignWithdrawals.length,
home: {
delivered: homeDeposits.length,
processed: homeWithdrawals.length
},
foreign: {
delivered: foreignWithdrawals.length,
processed: foreignDeposits.length
}
}
} else {
return {
depositsDiff: homeDeposits.length - foreignDeposits.length,
withdrawalDiff: homeWithdrawals.length - foreignWithdrawals.length,
home: {
deposits: homeDeposits.length,
withdrawals: homeWithdrawals.length
},
foreign: {
deposits: foreignDeposits.length,
withdrawals: foreignWithdrawals.length
}
}
}
}

@ -33,14 +33,18 @@ async function main(mode) {
const homeBridge = new web3Home.eth.Contract(HOME_ABI, HOME_BRIDGE_ADDRESS)
const foreignBridge = new web3Foreign.eth.Contract(FOREIGN_ABI, FOREIGN_BRIDGE_ADDRESS)
const v1Bridge = bridgeMode === BRIDGE_MODES.NATIVE_TO_ERC_V1
const erc20MethodName = bridgeMode === BRIDGE_MODES.NATIVE_TO_ERC || v1Bridge ? 'erc677token' : 'erc20token'
const erc20Address = await foreignBridge.methods[erc20MethodName]().call()
const tokenType = await getTokenType(
new web3Foreign.eth.Contract(ERC677_BRIDGE_TOKEN_ABI, erc20Address),
FOREIGN_BRIDGE_ADDRESS
)
const isExternalErc20 = tokenType === ERC_TYPES.ERC20
const erc20Contract = new web3Foreign.eth.Contract(ERC20_ABI, erc20Address)
let isExternalErc20
let erc20Contract
if (bridgeMode !== BRIDGE_MODES.ARBITRARY_MESSAGE) {
const erc20MethodName = bridgeMode === BRIDGE_MODES.NATIVE_TO_ERC || v1Bridge ? 'erc677token' : 'erc20token'
const erc20Address = await foreignBridge.methods[erc20MethodName]().call()
const tokenType = await getTokenType(
new web3Foreign.eth.Contract(ERC677_BRIDGE_TOKEN_ABI, erc20Address),
FOREIGN_BRIDGE_ADDRESS
)
isExternalErc20 = tokenType === ERC_TYPES.ERC20
erc20Contract = new web3Foreign.eth.Contract(ERC20_ABI, erc20Address)
}
logger.debug('getting last block numbers')
const [homeBlockNumber, foreignBlockNumber] = await getBlockNumber(web3Home, web3Foreign)
@ -87,7 +91,8 @@ async function main(mode) {
foreignDeposits,
homeWithdrawals,
foreignWithdrawals,
isExternalErc20
isExternalErc20,
bridgeMode
}
}

47
monitor/utils/message.js Normal file

@ -0,0 +1,47 @@
const web3Utils = require('web3').utils
const { addTxHashToData, parseAMBMessage } = require('../../commons')
function deliveredMsgNotProcessed(processedList) {
return deliveredMsg => {
const msg = parseAMBMessage(
addTxHashToData({
encodedData: deliveredMsg.returnValues.encodedData,
transactionHash: deliveredMsg.transactionHash
})
)
return (
processedList.filter(processedMsg => {
return messageEqualsEvent(msg, processedMsg.returnValues)
}).length === 0
)
}
}
function processedMsgNotDelivered(deliveredList) {
return processedMsg => {
return (
deliveredList.filter(deliveredMsg => {
const msg = parseAMBMessage(
addTxHashToData({
encodedData: deliveredMsg.returnValues.encodedData,
transactionHash: deliveredMsg.transactionHash
})
)
return messageEqualsEvent(msg, processedMsg.returnValues)
}).length === 0
)
}
}
function messageEqualsEvent(parsedMsg, event) {
return (
web3Utils.toChecksumAddress(parsedMsg.sender) === event.sender &&
web3Utils.toChecksumAddress(parsedMsg.executor) === event.executor &&
parsedMsg.txHash === event.transactionHash
)
}
module.exports = {
deliveredMsgNotProcessed,
processedMsgNotDelivered
}

331
oracle-e2e/test/amb.js Normal file

@ -0,0 +1,331 @@
const Web3 = require('web3')
const assert = require('assert')
const promiseRetry = require('promise-retry')
const { user, validator, homeRPC, foreignRPC, amb } = require('../../e2e-commons/constants.json')
const { generateNewBlock } = require('../../e2e-commons/utils')
const { HOME_AMB_ABI, FOREIGN_AMB_ABI, BOX_ABI } = require('../../commons')
const { toBN } = Web3.utils
const homeWeb3 = new Web3(new Web3.providers.HttpProvider(homeRPC.URL))
const foreignWeb3 = new Web3(new Web3.providers.HttpProvider(foreignRPC.URL))
homeWeb3.eth.accounts.wallet.add(user.privateKey)
foreignWeb3.eth.accounts.wallet.add(user.privateKey)
homeWeb3.eth.accounts.wallet.add(validator.privateKey)
foreignWeb3.eth.accounts.wallet.add(validator.privateKey)
const homeAMB = new homeWeb3.eth.Contract(HOME_AMB_ABI, amb.home)
const homeBox = new homeWeb3.eth.Contract(BOX_ABI, amb.homeBox)
const foreignAMB = new foreignWeb3.eth.Contract(FOREIGN_AMB_ABI, amb.foreign)
const foreignBox = new foreignWeb3.eth.Contract(BOX_ABI, amb.foreignBox)
const oneEther = foreignWeb3.utils.toWei('1', 'ether')
const subsidizedMode = toBN(0)
describe('arbitrary message bridging', () => {
describe('Home to Foreign', () => {
describe('Defrayal Mode', () => {
it('should be able to deposit funds for home sender', async () => {
const initialBalance = await foreignAMB.methods.balanceOf(amb.homeBox).call()
assert(toBN(initialBalance).isZero(), 'Balance should be zero')
await foreignAMB.methods.depositForContractSender(amb.homeBox).send({
from: user.address,
gas: '1000000',
value: oneEther
})
const balance = await foreignAMB.methods.balanceOf(amb.homeBox).call()
assert(toBN(balance).eq(toBN(oneEther)), 'Balance should be one ether')
})
it('should bridge message and take fees', async () => {
const newValue = 9
const initialValue = await foreignBox.methods.value().call()
assert(toBN(initialValue).isZero(), 'Value should be zero')
const initialBalance = await foreignAMB.methods.balanceOf(amb.homeBox).call()
assert(!toBN(initialBalance).isZero(), 'Balance should not be zero')
const setValueTx = await homeBox.methods
.setValueOnOtherNetworkGasPrice(newValue, amb.home, amb.foreignBox, '1000000000')
.send({
from: user.address,
gas: '1000000'
})
.catch(e => {
console.error(e)
})
// Send a trivial transaction to generate a new block since the watcher
// is configured to wait 1 confirmation block
await generateNewBlock(homeWeb3, user.address)
// The bridge should create a new transaction with a CollectedSignatures
// event so we generate another trivial transaction
await promiseRetry(
async retry => {
const lastBlockNumber = await homeWeb3.eth.getBlockNumber()
if (lastBlockNumber >= setValueTx.blockNumber + 2) {
await generateNewBlock(homeWeb3, user.address)
} else {
retry()
}
},
{
forever: true,
factor: 1,
minTimeout: 500
}
)
// check that value changed and balance decreased
await promiseRetry(async retry => {
const value = await foreignBox.methods.value().call()
const balance = await foreignAMB.methods.balanceOf(amb.homeBox).call()
if (!toBN(value).eq(toBN(newValue)) || toBN(balance).gte(toBN(oneEther))) {
retry()
}
})
})
it('should be able to withdraw from deposit', async () => {
const initialBalance = await foreignAMB.methods.balanceOf(amb.homeBox).call()
assert(!toBN(initialBalance).isZero(), 'Balance should not be zero')
const initialUserBalance = toBN(await foreignWeb3.eth.getBalance(user.address))
const tx = await homeBox.methods
.withdrawFromDepositOnOtherNetworkGasPrice(user.address, amb.home, amb.foreign, '1000000000')
.send({
from: user.address,
gas: '1000000'
})
.catch(e => {
console.error(e)
})
// Send a trivial transaction to generate a new block since the watcher
// is configured to wait 1 confirmation block
await generateNewBlock(homeWeb3, user.address)
// The bridge should create a new transaction with a CollectedSignatures
// event so we generate another trivial transaction
await promiseRetry(
async retry => {
const lastBlockNumber = await homeWeb3.eth.getBlockNumber()
if (lastBlockNumber >= tx.blockNumber + 2) {
await generateNewBlock(homeWeb3, user.address)
} else {
retry()
}
},
{
forever: true,
factor: 1,
minTimeout: 500
}
)
// check that value changed and balance decreased
await promiseRetry(async retry => {
const userBalance = toBN(await foreignWeb3.eth.getBalance(user.address))
const boxBalance = toBN(await foreignAMB.methods.balanceOf(amb.homeBox).call())
if (!boxBalance.isZero() || userBalance.lte(initialUserBalance)) {
retry()
}
})
})
})
describe('Subsidized Mode', () => {
it('should bridge message without taking fees', async () => {
const newValue = 3
await homeAMB.methods.setSubsidizedModeForHomeToForeign().send({
from: validator.address,
gas: '1000000'
})
const homeMode = await homeAMB.methods.homeToForeignMode().call()
await foreignAMB.methods.setSubsidizedModeForHomeToForeign().send({
from: validator.address,
gas: '1000000'
})
const foreignMode = await foreignAMB.methods.homeToForeignMode().call()
assert(toBN(homeMode).eq(subsidizedMode), 'home mode incorrect')
assert(toBN(foreignMode).eq(subsidizedMode), 'foreign mode incorrect')
const initialValue = await foreignBox.methods.value().call()
assert(!toBN(initialValue).eq(toBN(newValue)), 'initial value should be different from new value')
const setValueTx = await homeBox.methods
.setValueOnOtherNetworkGasPrice(newValue, amb.home, amb.foreignBox, '1000000000')
.send({
from: user.address,
gas: '1000000'
})
.catch(e => {
console.error(e)
})
// Send a trivial transaction to generate a new block since the watcher
// is configured to wait 1 confirmation block
await generateNewBlock(homeWeb3, user.address)
// The bridge should create a new transaction with a CollectedSignatures
// event so we generate another trivial transaction
await promiseRetry(
async retry => {
const lastBlockNumber = await homeWeb3.eth.getBlockNumber()
if (lastBlockNumber >= setValueTx.blockNumber + 2) {
await generateNewBlock(homeWeb3, user.address)
} else {
retry()
}
},
{
forever: true,
factor: 1,
minTimeout: 500
}
)
// check that value changed and balance decreased
await promiseRetry(async retry => {
const value = await foreignBox.methods.value().call()
if (!toBN(value).eq(toBN(newValue))) {
retry()
}
})
})
})
})
describe('Foreign to Home', () => {
describe('Defrayal Mode', () => {
it('should be able to deposit funds for foreign sender', async () => {
const initialBalance = await homeAMB.methods.balanceOf(amb.foreignBox).call()
assert(toBN(initialBalance).isZero(), 'Balance should be zero')
await homeAMB.methods.depositForContractSender(amb.homeBox).send({
from: user.address,
gas: '1000000',
value: oneEther
})
const balance = await homeAMB.methods.balanceOf(amb.foreignBox).call()
assert(toBN(balance).eq(toBN(oneEther)), 'Balance should be one ether')
})
it('should bridge message and take fees', async () => {
const newValue = 6
const initialValue = await homeBox.methods.value().call()
assert(toBN(initialValue).isZero(), 'Value should be zero')
const initialBalance = await homeAMB.methods.balanceOf(amb.foreignBox).call()
assert(!toBN(initialBalance).isZero(), 'Balance should not be zero')
await foreignBox.methods
.setValueOnOtherNetworkGasPrice(newValue, amb.foreign, amb.homeBox, '1000000000')
.send({
from: user.address,
gas: '1000000'
})
.catch(e => {
console.error(e)
})
// Send a trivial transaction to generate a new block since the watcher
// is configured to wait 1 confirmation block
await generateNewBlock(foreignWeb3, user.address)
// check that value changed and balance decreased
await promiseRetry(async retry => {
const value = await homeBox.methods.value().call()
const balance = await homeAMB.methods.balanceOf(amb.foreignBox).call()
if (!toBN(value).eq(toBN(newValue)) || toBN(balance).gte(toBN(oneEther))) {
retry()
}
})
})
it('should be able to withdraw from deposit', async () => {
const initialBalance = await homeAMB.methods.balanceOf(amb.foreignBox).call()
assert(!toBN(initialBalance).isZero(), 'Balance should not be zero')
const initialUserBalance = toBN(await homeWeb3.eth.getBalance(user.address))
await foreignBox.methods
.withdrawFromDepositOnOtherNetworkGasPrice(user.address, amb.foreign, amb.home, '1000000000')
.send({
from: user.address,
gas: '1000000'
})
.catch(e => {
console.error(e)
})
// Send a trivial transaction to generate a new block since the watcher
// is configured to wait 1 confirmation block
await generateNewBlock(foreignWeb3, user.address)
// check that value changed and balance decreased
await promiseRetry(async retry => {
const userBalance = toBN(await homeWeb3.eth.getBalance(user.address))
const boxBalance = toBN(await homeAMB.methods.balanceOf(amb.foreignBox).call())
if (!boxBalance.isZero() || userBalance.lte(initialUserBalance)) {
retry()
}
})
})
})
describe('Subsidized Mode', () => {
it('should bridge message without taking fees', async () => {
const newValue = 7
await homeAMB.methods.setSubsidizedModeForForeignToHome().send({
from: validator.address,
gas: '1000000'
})
const homeMode = await homeAMB.methods.foreignToHomeMode().call()
await foreignAMB.methods.setSubsidizedModeForForeignToHome().send({
from: validator.address,
gas: '1000000'
})
const foreignMode = await foreignAMB.methods.foreignToHomeMode().call()
assert(toBN(homeMode).eq(subsidizedMode), 'home mode incorrect')
assert(toBN(foreignMode).eq(subsidizedMode), 'foreign mode incorrect')
const initialValue = await homeBox.methods.value().call()
assert(!toBN(initialValue).eq(toBN(newValue)), 'initial value should be different from new value')
await foreignBox.methods
.setValueOnOtherNetworkGasPrice(newValue, amb.home, amb.foreignBox, '1000000000')
.send({
from: user.address,
gas: '1000000'
})
.catch(e => {
console.error(e)
})
// Send a trivial transaction to generate a new block since the watcher
// is configured to wait 1 confirmation block
await generateNewBlock(foreignWeb3, user.address)
// check that value changed and balance decreased
await promiseRetry(async retry => {
const value = await homeBox.methods.value().call()
if (!toBN(value).eq(toBN(newValue))) {
retry()
}
})
})
})
})
})

@ -8,7 +8,9 @@ const {
HOME_ERC_TO_ERC_ABI,
FOREIGN_ERC_TO_ERC_ABI,
HOME_ERC_TO_NATIVE_ABI,
FOREIGN_ERC_TO_NATIVE_ABI
FOREIGN_ERC_TO_NATIVE_ABI,
HOME_AMB_ABI,
FOREIGN_AMB_ABI
} = require('../../commons')
const { web3Home, web3Foreign } = require('../src/services/web3')
const { privateKeyToAddress } = require('../src/utils/utils')
@ -35,6 +37,11 @@ switch (process.env.BRIDGE_MODE) {
foreignAbi = FOREIGN_ERC_TO_NATIVE_ABI
id = 'erc-native'
break
case BRIDGE_MODES.ARBITRARY_MESSAGE:
homeAbi = HOME_AMB_ABI
foreignAbi = FOREIGN_AMB_ABI
id = 'amb'
break
default:
if (process.env.NODE_ENV !== 'test') {
throw new Error(`Bridge Mode: ${process.env.BRIDGE_MODE} not supported.`)

@ -0,0 +1,52 @@
const { HttpListProviderError } = require('http-list-provider')
const { AlreadyProcessedError, AlreadySignedError, InvalidValidatorError } = require('../../utils/errors')
const logger = require('../../services/logger').child({
module: 'processAffirmationRequests:estimateGas'
})
async function estimateGas({ web3, homeBridge, validatorContract, message, address, gasPrice }) {
try {
const gasEstimate = await homeBridge.methods.executeAffirmation(message).estimateGas({
from: address,
gasPrice
})
return gasEstimate
} catch (e) {
if (e instanceof HttpListProviderError) {
throw e
}
const messageHash = web3.utils.soliditySha3(message)
const senderHash = web3.utils.soliditySha3(address, messageHash)
// Check if minimum number of validations was already reached
logger.debug('Check if minimum number of validations was already reached')
const numAffirmationsSigned = await homeBridge.methods.numAffirmationsSigned(messageHash).call()
const alreadyProcessed = await homeBridge.methods.isAlreadyProcessed(numAffirmationsSigned).call()
if (alreadyProcessed) {
throw new AlreadyProcessedError(e.message)
}
// Check if the message was already signed by this validator
logger.debug('Check if the message was already signed')
const alreadySigned = await homeBridge.methods.affirmationsSigned(senderHash).call()
if (alreadySigned) {
throw new AlreadySignedError(e.message)
}
// Check if address is validator
logger.debug('Check if address is a validator')
const isValidator = await validatorContract.methods.isValidator(address).call()
if (!isValidator) {
throw new InvalidValidatorError(`${address} is not a validator`)
}
throw new Error('Unknown error while processing message')
}
}
module.exports = estimateGas

@ -0,0 +1,100 @@
require('dotenv').config()
const { HttpListProviderError } = require('http-list-provider')
const promiseLimit = require('promise-limit')
const rootLogger = require('../../services/logger')
const { web3Home } = require('../../services/web3')
const bridgeValidatorsABI = require('../../../../contracts/build/contracts/BridgeValidators').abi
const { EXIT_CODES, MAX_CONCURRENT_EVENTS } = require('../../utils/constants')
const estimateGas = require('./estimateGas')
const { addTxHashToData, parseAMBMessage } = require('../../../../commons')
const { generateGasPriceOptions } = require('../../utils/utils')
const { AlreadyProcessedError, AlreadySignedError, InvalidValidatorError } = require('../../utils/errors')
const limit = promiseLimit(MAX_CONCURRENT_EVENTS)
let validatorContract = null
function processAffirmationRequestsBuilder(config) {
const homeBridge = new web3Home.eth.Contract(config.homeBridgeAbi, config.homeBridgeAddress)
return async function processAffirmationRequests(affirmationRequests) {
const txToSend = []
if (validatorContract === null) {
rootLogger.debug('Getting validator contract address')
const validatorContractAddress = await homeBridge.methods.validatorContract().call()
rootLogger.debug({ validatorContractAddress }, 'Validator contract address obtained')
validatorContract = new web3Home.eth.Contract(bridgeValidatorsABI, validatorContractAddress)
}
rootLogger.debug(`Processing ${affirmationRequests.length} AffirmationRequest events`)
const callbacks = affirmationRequests.map(affirmationRequest =>
limit(async () => {
const { encodedData } = affirmationRequest.returnValues
const logger = rootLogger.child({
eventTransactionHash: affirmationRequest.transactionHash
})
const message = addTxHashToData({
encodedData,
transactionHash: affirmationRequest.transactionHash
})
const { sender, executor, dataType, gasPrice, gasPriceSpeed } = parseAMBMessage(message)
logger.info({ sender, executor }, `Processing affirmationRequest ${affirmationRequest.transactionHash}`)
let gasEstimate
try {
logger.debug('Estimate gas')
gasEstimate = await estimateGas({
web3: web3Home,
homeBridge,
validatorContract,
message,
address: config.validatorAddress,
gasPrice
})
logger.debug({ gasEstimate }, 'Gas estimated')
} catch (e) {
if (e instanceof HttpListProviderError) {
throw new Error('RPC Connection Error: submitSignature Gas Estimate cannot be obtained.')
} else if (e instanceof InvalidValidatorError) {
logger.fatal({ address: config.validatorAddress }, 'Invalid validator')
process.exit(EXIT_CODES.INCOMPATIBILITY)
} else if (e instanceof AlreadySignedError) {
logger.info(`Already signed affirmationRequest ${affirmationRequest.transactionHash}`)
return
} else if (e instanceof AlreadyProcessedError) {
logger.info(
`affirmationRequest ${affirmationRequest.transactionHash} was already processed by other validators`
)
return
} else {
logger.error(e, 'Unknown error while processing transaction')
throw e
}
}
const data = await homeBridge.methods.executeAffirmation(message).encodeABI()
const gasPriceOptions = generateGasPriceOptions({ dataType, gasPrice, gasPriceSpeed })
txToSend.push({
data,
gasEstimate,
transactionReference: affirmationRequest.transactionHash,
to: config.homeBridgeAddress,
gasPriceOptions
})
})
)
await Promise.all(callbacks)
return txToSend
}
}
module.exports = processAffirmationRequestsBuilder

@ -0,0 +1,64 @@
const Web3 = require('web3')
const { HttpListProviderError } = require('http-list-provider')
const { AlreadyProcessedError, IncompatibleContractError, InvalidValidatorError } = require('../../utils/errors')
const logger = require('../../services/logger').child({
module: 'processCollectedSignatures:estimateGas'
})
const web3 = new Web3()
const { toBN } = Web3.utils
async function estimateGas({
foreignBridge,
validatorContract,
message,
numberOfCollectedSignatures,
v,
r,
s,
signatures,
txHash,
address,
gasPrice
}) {
try {
const gasEstimate = await foreignBridge.methods.executeSignatures(message, signatures).estimateGas({
from: address,
gasPrice
})
return gasEstimate
} catch (e) {
if (e instanceof HttpListProviderError) {
throw e
}
// check if the message was already processed
logger.debug('Check if the message was already processed')
const alreadyProcessed = await foreignBridge.methods.relayedMessages(txHash).call()
if (alreadyProcessed) {
throw new AlreadyProcessedError()
}
// check if the number of signatures is enough
logger.debug('Check if number of signatures is enough')
const requiredSignatures = await validatorContract.methods.requiredSignatures().call()
if (toBN(requiredSignatures).gt(toBN(numberOfCollectedSignatures))) {
throw new IncompatibleContractError('The number of collected signatures does not match')
}
// check if all the signatures were made by validators
for (let i = 0; i < v.length; i++) {
const address = web3.eth.accounts.recover(message, web3.utils.toHex(v[i]), r[i], s[i])
logger.debug({ address }, 'Check that signature is from a validator')
const isValidator = await validatorContract.methods.isValidator(address).call()
if (!isValidator) {
throw new InvalidValidatorError(`Message signed by ${address} that is not a validator`)
}
}
throw new Error('Unknown error while processing message')
}
}
module.exports = estimateGas

@ -0,0 +1,122 @@
require('dotenv').config()
const promiseLimit = require('promise-limit')
const { HttpListProviderError } = require('http-list-provider')
const bridgeValidatorsABI = require('../../../../contracts/build/contracts/BridgeValidators').abi
const rootLogger = require('../../services/logger')
const { web3Home, web3Foreign } = require('../../services/web3')
const { signatureToVRS, signatureToVRSAMB, packSignatures } = require('../../utils/message')
const { parseAMBMessage } = require('../../../../commons')
const { generateGasPriceOptions } = require('../../utils/utils')
const estimateGas = require('./estimateGas')
const { AlreadyProcessedError, IncompatibleContractError, InvalidValidatorError } = require('../../utils/errors')
const { MAX_CONCURRENT_EVENTS } = require('../../utils/constants')
const limit = promiseLimit(MAX_CONCURRENT_EVENTS)
let validatorContract = null
function processCollectedSignaturesBuilder(config) {
const homeBridge = new web3Home.eth.Contract(config.homeBridgeAbi, config.homeBridgeAddress)
const foreignBridge = new web3Foreign.eth.Contract(config.foreignBridgeAbi, config.foreignBridgeAddress)
return async function processCollectedSignatures(signatures) {
const txToSend = []
if (validatorContract === null) {
rootLogger.debug('Getting validator contract address')
const validatorContractAddress = await foreignBridge.methods.validatorContract().call()
rootLogger.debug({ validatorContractAddress }, 'Validator contract address obtained')
validatorContract = new web3Foreign.eth.Contract(bridgeValidatorsABI, validatorContractAddress)
}
rootLogger.debug(`Processing ${signatures.length} CollectedSignatures events`)
const callbacks = signatures.map(colSignature =>
limit(async () => {
const { authorityResponsibleForRelay, messageHash, NumberOfCollectedSignatures } = colSignature.returnValues
const logger = rootLogger.child({
eventTransactionHash: colSignature.transactionHash
})
if (authorityResponsibleForRelay === web3Home.utils.toChecksumAddress(config.validatorAddress)) {
logger.info(`Processing CollectedSignatures ${colSignature.transactionHash}`)
const message = await homeBridge.methods.message(messageHash).call()
const requiredSignatures = []
requiredSignatures.length = NumberOfCollectedSignatures
requiredSignatures.fill(0)
const signaturesArray = []
const [v, r, s] = [[], [], []]
logger.debug('Getting message signatures')
const signaturePromises = requiredSignatures.map(async (el, index) => {
logger.debug({ index }, 'Getting message signature')
const signature = await homeBridge.methods.signature(messageHash, index).call()
const vrs = signatureToVRS(signature)
v.push(vrs.v)
r.push(vrs.r)
s.push(vrs.s)
const recover = signatureToVRSAMB(signature)
signaturesArray.push(recover)
})
await Promise.all(signaturePromises)
const signatures = packSignatures(signaturesArray)
const { dataType, gasPrice, gasPriceSpeed, txHash } = parseAMBMessage(message)
let gasEstimate
try {
logger.debug('Estimate gas')
gasEstimate = await estimateGas({
foreignBridge,
validatorContract,
v,
r,
s,
signatures,
message,
numberOfCollectedSignatures: NumberOfCollectedSignatures,
txHash,
address: config.validatorAddress,
gasPrice
})
logger.debug({ gasEstimate }, 'Gas estimated')
} catch (e) {
if (e instanceof HttpListProviderError) {
throw new Error('RPC Connection Error: submitSignature Gas Estimate cannot be obtained.')
} else if (e instanceof AlreadyProcessedError) {
logger.info(`Already processed CollectedSignatures ${colSignature.transactionHash}`)
return
} else if (e instanceof IncompatibleContractError || e instanceof InvalidValidatorError) {
logger.error(`The message couldn't be processed; skipping: ${e.message}`)
return
} else {
logger.error(e, 'Unknown error while processing transaction')
throw e
}
}
const data = await foreignBridge.methods.executeSignatures(message, signatures).encodeABI()
const gasPriceOptions = generateGasPriceOptions({ dataType, gasPrice, gasPriceSpeed })
txToSend.push({
data,
gasEstimate,
transactionReference: colSignature.transactionHash,
to: config.foreignBridgeAddress,
gasPriceOptions
})
} else {
logger.info(`Validator not responsible for relaying CollectedSignatures ${colSignature.transactionHash}`)
}
})
)
await Promise.all(callbacks)
return txToSend
}
}
module.exports = processCollectedSignaturesBuilder

@ -0,0 +1,101 @@
require('dotenv').config()
const promiseLimit = require('promise-limit')
const { HttpListProviderError } = require('http-list-provider')
const bridgeValidatorsABI = require('../../../../contracts/build/contracts/BridgeValidators').abi
const rootLogger = require('../../services/logger')
const { web3Home } = require('../../services/web3')
const { addTxHashToData, parseAMBMessage } = require('../../../../commons')
const estimateGas = require('../processSignatureRequests/estimateGas')
const { AlreadyProcessedError, AlreadySignedError, InvalidValidatorError } = require('../../utils/errors')
const { EXIT_CODES, MAX_CONCURRENT_EVENTS } = require('../../utils/constants')
const { VALIDATOR_ADDRESS_PRIVATE_KEY } = process.env
const limit = promiseLimit(MAX_CONCURRENT_EVENTS)
let validatorContract = null
function processSignatureRequestsBuilder(config) {
const homeBridge = new web3Home.eth.Contract(config.homeBridgeAbi, config.homeBridgeAddress)
return async function processSignatureRequests(signatureRequests) {
const txToSend = []
if (validatorContract === null) {
rootLogger.debug('Getting validator contract address')
const validatorContractAddress = await homeBridge.methods.validatorContract().call()
rootLogger.debug({ validatorContractAddress }, 'Validator contract address obtained')
validatorContract = new web3Home.eth.Contract(bridgeValidatorsABI, validatorContractAddress)
}
rootLogger.debug(`Processing ${signatureRequests.length} SignatureRequest events`)
const callbacks = signatureRequests.map(signatureRequest =>
limit(async () => {
const { encodedData } = signatureRequest.returnValues
const logger = rootLogger.child({
eventTransactionHash: signatureRequest.transactionHash
})
const message = addTxHashToData({
encodedData,
transactionHash: signatureRequest.transactionHash
})
const { sender, executor } = parseAMBMessage(message)
logger.info({ sender, executor }, `Processing signatureRequest ${signatureRequest.transactionHash}`)
const signature = web3Home.eth.accounts.sign(message, `0x${VALIDATOR_ADDRESS_PRIVATE_KEY}`)
let gasEstimate
try {
logger.debug('Estimate gas')
gasEstimate = await estimateGas({
web3: web3Home,
homeBridge,
validatorContract,
signature: signature.signature,
message,
address: config.validatorAddress
})
logger.debug({ gasEstimate }, 'Gas estimated')
} catch (e) {
if (e instanceof HttpListProviderError) {
throw new Error('RPC Connection Error: submitSignature Gas Estimate cannot be obtained.')
} else if (e instanceof InvalidValidatorError) {
logger.fatal({ address: config.validatorAddress }, 'Invalid validator')
process.exit(EXIT_CODES.INCOMPATIBILITY)
} else if (e instanceof AlreadySignedError) {
logger.info(`Already signed signatureRequest ${signatureRequest.transactionHash}`)
return
} else if (e instanceof AlreadyProcessedError) {
logger.info(
`signatureRequest ${signatureRequest.transactionHash} was already processed by other validators`
)
return
} else {
logger.error(e, 'Unknown error while processing transaction')
throw e
}
}
const data = await homeBridge.methods
.submitSignature(signature.signature, message)
.encodeABI({ from: config.validatorAddress })
txToSend.push({
data,
gasEstimate,
transactionReference: signatureRequest.transactionHash,
to: config.homeBridgeAddress
})
})
)
await Promise.all(callbacks)
return txToSend
}
}
module.exports = processSignatureRequestsBuilder

@ -97,7 +97,6 @@ async function main({ msg, ackMsg, nackMsg, channel, scheduleForRetry }) {
const txArray = JSON.parse(msg.content)
logger.info(`Msg received with ${txArray.length} Tx to send`)
const gasPrice = GasPrice.getPrice()
let nonce = await readNonce()
let insufficientFunds = false
@ -107,6 +106,7 @@ async function main({ msg, ackMsg, nackMsg, channel, scheduleForRetry }) {
logger.debug(`Sending ${txArray.length} transactions`)
await syncForEach(txArray, async job => {
const gasLimit = addExtraGas(job.gasEstimate, EXTRA_GAS_PERCENTAGE)
const gasPrice = GasPrice.getPrice(job.gasPriceOptions)
try {
logger.info(`Sending transaction with nonce ${nonce}`)
@ -114,7 +114,7 @@ async function main({ msg, ackMsg, nackMsg, channel, scheduleForRetry }) {
chain: config.id,
data: job.data,
nonce,
gasPrice: gasPrice.toString(10),
gasPrice,
amount: '0',
gasLimit,
privateKey: VALIDATOR_ADDRESS_PRIVATE_KEY,

@ -1,5 +1,6 @@
require('../../env')
const fetch = require('node-fetch')
const Web3Utils = require('web3-utils')
const { web3Home, web3Foreign } = require('../services/web3')
const { bridgeConfig } = require('../../config/base.config')
const logger = require('../services/logger').child({
@ -7,7 +8,7 @@ const logger = require('../services/logger').child({
})
const { setIntervalAndRun } = require('../utils/utils')
const { DEFAULT_UPDATE_INTERVAL, GAS_PRICE_BOUNDARIES, DEFAULT_GAS_PRICE_FACTOR } = require('../utils/constants')
const { gasPriceFromOracle, gasPriceFromContract } = require('../../../commons')
const { gasPriceFromOracle, gasPriceFromContract, GAS_PRICE_OPTIONS } = require('../../../commons')
const HomeABI = bridgeConfig.homeBridgeAbi
const ForeignABI = bridgeConfig.foreignBridgeAbi
@ -32,16 +33,20 @@ const homeBridge = new web3Home.eth.Contract(HomeABI, HOME_BRIDGE_ADDRESS)
const foreignBridge = new web3Foreign.eth.Contract(ForeignABI, FOREIGN_BRIDGE_ADDRESS)
let cachedGasPrice = null
let cachedGasPriceOracleSpeeds = null
let fetchGasPriceInterval = null
const fetchGasPrice = async (speedType, factor, bridgeContract, oracleFetchFn) => {
const contractOptions = { logger }
const oracleOptions = { speedType, factor, limits: GAS_PRICE_BOUNDARIES, logger }
const oracleOptions = { speedType, factor, limits: GAS_PRICE_BOUNDARIES, logger, returnAllSpeeds: true }
const oracleGasPriceData = await gasPriceFromOracle(oracleFetchFn, oracleOptions)
cachedGasPrice =
(await gasPriceFromOracle(oracleFetchFn, oracleOptions)) ||
(oracleGasPriceData && oracleGasPriceData.gasPrice) ||
(await gasPriceFromContract(bridgeContract, contractOptions)) ||
cachedGasPrice
cachedGasPriceOracleSpeeds =
(oracleGasPriceData && oracleGasPriceData.oracleGasPriceSpeeds) || cachedGasPriceOracleSpeeds
return cachedGasPrice
}
@ -79,12 +84,26 @@ async function start(chainId) {
)
}
function getPrice() {
return cachedGasPrice
function getPrice(options) {
return processGasPriceOptions({ options, cachedGasPrice, cachedGasPriceOracleSpeeds })
}
function processGasPriceOptions({ options, cachedGasPrice, cachedGasPriceOracleSpeeds }) {
let gasPrice = cachedGasPrice
if (options && options.type && options.value) {
if (options.type === GAS_PRICE_OPTIONS.GAS_PRICE) {
return options.value
} else if (options.type === GAS_PRICE_OPTIONS.SPEED) {
const speedOption = cachedGasPriceOracleSpeeds[options.value]
gasPrice = speedOption ? Web3Utils.toWei(speedOption.toString(), 'gwei') : cachedGasPrice
}
}
return gasPrice
}
module.exports = {
start,
getPrice,
processGasPriceOptions,
fetchGasPrice
}

@ -1,10 +1,6 @@
const assert = require('assert')
const Web3Utils = require('web3-utils')
// strips leading "0x" if present
function strip0x(input) {
return input.replace(/^0x/, '')
}
const { strip0x } = require('../../../commons')
function createMessage({ recipient, value, transactionHash, bridgeAddress, expectedMessageLength }) {
recipient = strip0x(recipient)
@ -63,8 +59,32 @@ function signatureToVRS(signature) {
return { v, r, s }
}
function signatureToVRSAMB(rawSignature) {
const signature = strip0x(rawSignature)
const v = signature.substr(64 * 2)
const r = signature.substr(0, 32 * 2)
const s = signature.substr(32 * 2, 32 * 2)
return { v, r, s }
}
function packSignatures(array) {
const length = strip0x(Web3Utils.toHex(array.length))
const msgLength = length.length === 1 ? `0${length}` : length
let v = ''
let r = ''
let s = ''
array.forEach(e => {
v = v.concat(e.v)
r = r.concat(e.r)
s = s.concat(e.s)
})
return `0x${msgLength}${v}${r}${s}`
}
module.exports = {
createMessage,
parseMessage,
signatureToVRS
signatureToVRS,
signatureToVRSAMB,
packSignatures
}

@ -1,6 +1,8 @@
const BigNumber = require('bignumber.js')
const promiseRetry = require('promise-retry')
const Web3 = require('web3')
const Web3Utils = require('web3-utils')
const { GAS_PRICE_OPTIONS } = require('../../../commons')
const retrySequence = [1, 2, 3, 5, 8, 13, 21, 34, 55, 60]
@ -100,6 +102,22 @@ function nonceError(e) {
)
}
function generateGasPriceOptions({ dataType, gasPrice, gasPriceSpeed }) {
let gasPriceOptions = null
if (dataType === GAS_PRICE_OPTIONS.GAS_PRICE) {
gasPriceOptions = {
type: dataType,
value: Web3Utils.hexToNumberString(gasPrice)
}
} else if (dataType === GAS_PRICE_OPTIONS.SPEED) {
gasPriceOptions = {
type: dataType,
value: gasPriceSpeed
}
}
return gasPriceOptions
}
module.exports = {
syncForEach,
checkHTTPS,
@ -109,5 +127,6 @@ module.exports = {
watchdog,
privateKeyToAddress,
nonceError,
getRetrySequence
getRetrySequence,
generateGasPriceOptions
}

@ -21,6 +21,9 @@ const processSignatureRequests = require('./events/processSignatureRequests')(co
const processCollectedSignatures = require('./events/processCollectedSignatures')(config)
const processAffirmationRequests = require('./events/processAffirmationRequests')(config)
const processTransfers = require('./events/processTransfers')(config)
const processAMBSignatureRequests = require('./events/processAMBSignatureRequests')(config)
const processAMBCollectedSignatures = require('./events/processAMBCollectedSignatures')(config)
const processAMBAffirmationRequests = require('./events/processAMBAffirmationRequests')(config)
const ZERO = toBN(0)
const ONE = toBN(1)
@ -97,6 +100,12 @@ function processEvents(events) {
case 'erc-erc-affirmation-request':
case 'erc-native-affirmation-request':
return processTransfers(events)
case 'amb-signature-request':
return processAMBSignatureRequests(events)
case 'amb-collected-signatures':
return processAMBCollectedSignatures(events)
case 'amb-affirmation-request':
return processAMBAffirmationRequests(events)
default:
return []
}

@ -1,7 +1,10 @@
const sinon = require('sinon')
const { expect } = require('chai')
const proxyquire = require('proxyquire').noPreserveCache()
const Web3Utils = require('web3-utils')
const { processGasPriceOptions } = require('../src/services/gasPrice')
const { DEFAULT_UPDATE_INTERVAL } = require('../src/utils/constants')
const { GAS_PRICE_OPTIONS, ORACLE_GAS_PRICE_SPEEDS } = require('../../commons')
describe('gasPrice', () => {
describe('start', () => {
@ -160,4 +163,86 @@ describe('gasPrice', () => {
expect(fakeLogger.error.calledTwice).to.equal(true) // two errors
})
})
describe('processGasPriceOptions', () => {
const oracleMockResponse = {
fast: 17.64,
block_time: 13.548,
health: true,
standard: 10.64,
block_number: 6704240,
instant: 51.9,
slow: 4.4
}
it('should return cached gas price if no options provided', async () => {
// given
const options = {}
const cachedGasPrice = '1000000000'
// when
const gasPrice = await processGasPriceOptions({
options,
cachedGasPrice,
cachedGasPriceOracleSpeeds: oracleMockResponse
})
// then
expect(gasPrice).to.equal(cachedGasPrice)
})
it('should return gas price provided by options', async () => {
// given
const options = {
type: GAS_PRICE_OPTIONS.GAS_PRICE,
value: '3000000000'
}
const cachedGasPrice = '1000000000'
// when
const gasPrice = await processGasPriceOptions({
options,
cachedGasPrice,
cachedGasPriceOracleSpeeds: oracleMockResponse
})
// then
expect(gasPrice).to.equal(options.value)
})
it('should return gas price provided by oracle speed option', async () => {
// given
const options = {
type: GAS_PRICE_OPTIONS.SPEED,
value: ORACLE_GAS_PRICE_SPEEDS.STANDARD
}
const cachedGasPrice = '1000000000'
const oracleGasPriceGwei = oracleMockResponse[ORACLE_GAS_PRICE_SPEEDS.STANDARD]
const oracleGasPrice = Web3Utils.toWei(oracleGasPriceGwei.toString(), 'gwei')
// when
const gasPrice = await processGasPriceOptions({
options,
cachedGasPrice,
cachedGasPriceOracleSpeeds: oracleMockResponse
})
// then
expect(gasPrice).to.equal(oracleGasPrice)
})
it('should return cached gas price if invalid speed option', async () => {
// given
const options = {
type: GAS_PRICE_OPTIONS.SPEED,
value: 'unknown'
}
const cachedGasPrice = '1000000000'
// when
const gasPrice = await processGasPriceOptions({
options,
cachedGasPrice,
cachedGasPriceOracleSpeeds: oracleMockResponse
})
// then
expect(gasPrice).to.equal(cachedGasPrice)
})
})
})

@ -3,7 +3,9 @@ const chai = require('chai')
const chaiAsPromised = require('chai-as-promised')
const BigNumber = require('bignumber.js')
const proxyquire = require('proxyquire')
const { addExtraGas, syncForEach } = require('../src/utils/utils')
const Web3Utils = require('web3-utils')
const { addExtraGas, syncForEach, generateGasPriceOptions } = require('../src/utils/utils')
const { GAS_PRICE_OPTIONS, ORACLE_GAS_PRICE_SPEEDS } = require('../../commons')
chai.use(chaiAsPromised)
const { expect } = chai
@ -134,4 +136,54 @@ describe('utils', () => {
})
})
})
describe('generateGasPriceOptions', () => {
it('should work for GAS_PRICE option', () => {
// given
const dataType = GAS_PRICE_OPTIONS.GAS_PRICE
const gasPrice = Web3Utils.toBN('0000000000000000000000000000000000000000000000000000000165a0bc00')
const gasPriceSpeed = null
const expectedResult = {
type: dataType,
value: Web3Utils.hexToNumberString(gasPrice)
}
// when
const gasPriceOptions = generateGasPriceOptions({ dataType, gasPrice, gasPriceSpeed })
// then
expect(gasPriceOptions.type).to.be.equal(expectedResult.type)
expect(gasPriceOptions.value).to.be.equal(expectedResult.value)
})
it('should work for SPEED option', () => {
// given
const dataType = GAS_PRICE_OPTIONS.SPEED
const gasPrice = null
const gasPriceSpeed = ORACLE_GAS_PRICE_SPEEDS.STANDARD
const expectedResult = {
type: dataType,
value: gasPriceSpeed
}
// when
const gasPriceOptions = generateGasPriceOptions({ dataType, gasPrice, gasPriceSpeed })
// then
expect(gasPriceOptions.type).to.be.equal(expectedResult.type)
expect(gasPriceOptions.value).to.be.equal(expectedResult.value)
})
it('should return null option for undefined option', () => {
// given
const dataType = GAS_PRICE_OPTIONS.UNDEFINED
const gasPrice = null
const gasPriceSpeed = null
// when
const gasPriceOptions = generateGasPriceOptions({ dataType, gasPrice, gasPriceSpeed })
// then
expect(gasPriceOptions).to.be.equal(null)
})
})
})