Compare commits
37 Commits
master
...
amb-defray
Author | SHA1 | Date | |
---|---|---|---|
|
9d4dea9823 | ||
|
2357d787a6 | ||
|
065956e94e | ||
|
7e651a86c4 | ||
|
411c35d873 | ||
|
3ff096ef55 | ||
|
7f15733ad3 | ||
|
d22a3e8bae | ||
|
43609d739d | ||
|
ab5166b895 | ||
|
383e22cb58 | ||
|
e1f20b8686 | ||
|
4b59f18d29 | ||
|
2d42c236d7 | ||
|
ee4a6c8f5c | ||
|
55f8443c55 | ||
|
e5ce665656 | ||
|
751b885f73 | ||
|
3d0f68fad9 | ||
|
08d2c46a03 | ||
|
48620a8d01 | ||
|
35c2e642ee | ||
|
a056a75932 | ||
|
e8a179bc19 | ||
|
aa32f2cc0c | ||
|
473b6a3e46 | ||
|
8cf97aea3c | ||
|
ac6775bd6e | ||
|
478b30b2ed | ||
|
70d03c1a93 | ||
|
0694fc24e9 | ||
|
80940a0a82 | ||
|
a2603701b6 | ||
|
e006443f50 | ||
|
0326745684 | ||
|
95f4a0acc6 | ||
|
7ca3b0079c |
@ -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
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', () => {
|
125
commons/test/message.test.js
Normal file
125
commons/test/message.test.js
Normal file
@ -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
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
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
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
|
100
oracle/src/events/processAMBAffirmationRequests/index.js
Normal file
100
oracle/src/events/processAMBAffirmationRequests/index.js
Normal file
@ -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
|
122
oracle/src/events/processAMBCollectedSignatures/index.js
Normal file
122
oracle/src/events/processAMBCollectedSignatures/index.js
Normal file
@ -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
|
101
oracle/src/events/processAMBSignatureRequests/index.js
Normal file
101
oracle/src/events/processAMBSignatureRequests/index.js
Normal file
@ -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)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user