Compare commits
52 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
727371f251 | ||
|
|
9cb1a2041d | ||
|
|
b6d96d7f62 | ||
|
|
dc06ee8ceb | ||
|
|
8c2f58b06f | ||
|
|
3ad62d6a7f | ||
|
|
9f9638970a | ||
|
|
7054ff26a0 | ||
|
|
afb601b7f5 | ||
|
|
1736fd615d | ||
|
|
ef0a734650 | ||
|
|
6e2238fc9b | ||
|
|
0f3bea5a41 | ||
|
|
0829c95561 | ||
|
|
5bb99a7e95 | ||
|
|
3cd53f7bda | ||
|
|
0eeae74ffa | ||
|
|
8fa715089b | ||
|
|
ab2c0ea120 | ||
|
|
5583ea8b6b | ||
|
|
a4eb446f7b | ||
|
|
2d526a1454 | ||
|
|
9811c13a04 | ||
|
|
406ede9352 | ||
|
|
1360c79e69 | ||
|
|
12229e5e0b | ||
|
|
588b289bb9 | ||
|
|
b3419ccca6 | ||
|
|
ecd20890c8 | ||
|
|
b6588ff3c5 | ||
|
|
ed2de112a2 | ||
|
|
c19f48ef3f | ||
|
|
eb8de323ee | ||
|
|
c42b2f03b7 | ||
|
|
303b02f3ca | ||
|
|
98e0f8e998 | ||
|
|
ecf613954a | ||
|
|
f2a6a64637 | ||
|
|
8d4eb86a19 | ||
|
|
cc6afb3736 | ||
|
|
84ecfc30d9 | ||
|
|
5d770e8607 | ||
|
|
db89d1c12e | ||
|
|
4fd4ac3d73 | ||
|
|
cbd9d607ce | ||
|
|
346fa1e732 | ||
|
|
f6fa83d7ea | ||
|
|
1564ccc580 | ||
|
|
7a54e584d5 | ||
|
|
1d79cf82f3 | ||
|
|
9884b4b424 | ||
|
|
d577a71096 |
@@ -14,7 +14,7 @@ orbs:
|
||||
- run:
|
||||
name: Install Chrome
|
||||
command: |
|
||||
wget -O chrome.deb https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
|
||||
wget -O chrome.deb https://dl.google.com/linux/chrome/deb/pool/main/g/google-chrome-stable/google-chrome-stable_77.0.3865.120-1_amd64.deb
|
||||
sudo dpkg -i chrome.deb
|
||||
install-node:
|
||||
steps:
|
||||
|
||||
2
.gitmodules
vendored
2
.gitmodules
vendored
@@ -1,3 +1,3 @@
|
||||
[submodule "contracts"]
|
||||
path = contracts
|
||||
url = https://github.com/poanetwork/poa-bridge-contracts.git
|
||||
url = https://github.com/poanetwork/tokenbridge-contracts.git
|
||||
|
||||
@@ -30,7 +30,7 @@ Sub-repositories maintained within this monorepo are listed below.
|
||||
| [Commons](commons/README.md) | Interfaces, constants and utilities shared between the sub-repositories |
|
||||
| [E2E-Commons](e2e-commons/README.md) | Common utilities and configuration used in end to end tests |
|
||||
|
||||
Additionally there are [Smart Contracts](https://github.com/poanetwork/poa-bridge-contracts) used to manage bridge validators, collect signatures, and confirm asset relay and disposal.
|
||||
Additionally there are [Smart Contracts](https://github.com/poanetwork/tokenbridge-contracts) used to manage bridge validators, collect signatures, and confirm asset relay and disposal.
|
||||
|
||||
## Available deployments
|
||||
|
||||
@@ -52,11 +52,12 @@ Additionally there are [Smart Contracts](https://github.com/poanetwork/poa-bridg
|
||||
|
||||
## Operational Modes
|
||||
|
||||
The POA TokenBridge provides three operational modes:
|
||||
The POA TokenBridge provides four operational modes:
|
||||
|
||||
- [x] `Native-to-ERC20` **Coins** on a Home network can be converted to ERC20-compatible **tokens** on a Foreign network. Coins are locked on the Home side and the corresponding amount of ERC20 tokens are minted on the Foreign side. When the operation is reversed, tokens are burnt on the Foreign side and unlocked in the Home network. **More Information: [POA-to-POA20 Bridge](https://medium.com/poa-network/introducing-poa-bridge-and-poa20-55d8b78058ac)**
|
||||
- [x] `ERC20-to-ERC20` ERC20-compatible tokens on the Foreign network are locked and minted as ERC20-compatible tokens (ERC677 tokens) on the Home network. When transferred from Home to Foreign, they are burnt on the Home side and unlocked in the Foreign network. This can be considered a form of atomic swap when a user swaps the token "X" in network "A" to the token "Y" in network "B". **More Information: [ERC20-to-ERC20](https://medium.com/poa-network/introducing-the-erc20-to-erc20-tokenbridge-ce266cc1a2d0)**
|
||||
- [x] `ERC20-to-Native`: Pre-existing **tokens** in the Foreign network are locked and **coins** are minted in the `Home` network. In this mode, the Home network consensus engine invokes [Parity's Block Reward contract](https://wiki.parity.io/Block-Reward-Contract.html) to mint coins per the bridge contract request. **More Information: [xDai Chain](https://medium.com/poa-network/poa-network-partners-with-makerdao-on-xdai-chain-the-first-ever-usd-stable-blockchain-65a078c41e6a)**
|
||||
- [x] `Arbitrary-Message`: Transfer arbitrary data between two networks as so the data could be interpreted as an arbitrary contract method invocation.
|
||||
|
||||
## Initializing the monorepository
|
||||
|
||||
@@ -105,5 +106,4 @@ This project is licensed under the GNU Lesser General Public License v3.0. See t
|
||||
|
||||
## References
|
||||
|
||||
* [Additional Documentation](https://forum.poa.network/c/tokenbridge)
|
||||
* [POA20 Bridge FAQ](https://forum.poa.network/c/tokenbridge/poa20-bridge)
|
||||
* [TokenBridge Documentation](http://www.tokenbridge.net/)
|
||||
|
||||
@@ -10,6 +10,10 @@ 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 SAI_TOP = require('../contracts/build/contracts/SaiTopMock').abi
|
||||
|
||||
const { HOME_V1_ABI, FOREIGN_V1_ABI } = require('./v1Abis')
|
||||
const { BRIDGE_MODES } = require('./constants')
|
||||
@@ -60,6 +64,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 +90,9 @@ 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,
|
||||
SAI_TOP
|
||||
}
|
||||
|
||||
@@ -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,11 @@ const FEE_MANAGER_MODE = {
|
||||
UNDEFINED: 'UNDEFINED'
|
||||
}
|
||||
|
||||
module.exports = { BRIDGE_MODES, ERC_TYPES, FEE_MANAGER_MODE }
|
||||
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'
|
||||
|
||||
module.exports = {
|
||||
BRIDGE_MODES,
|
||||
ERC_TYPES,
|
||||
FEE_MANAGER_MODE,
|
||||
ZERO_ADDRESS
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
27
commons/message.js
Normal file
27
commons/message.js
Normal file
@@ -0,0 +1,27 @@
|
||||
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)}`
|
||||
|
||||
return {
|
||||
sender,
|
||||
executor,
|
||||
txHash
|
||||
}
|
||||
}
|
||||
|
||||
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', () => {
|
||||
69
commons/test/message.test.js
Normal file
69
commons/test/message.test.js
Normal file
@@ -0,0 +1,69 @@
|
||||
const { BN } = require('web3-utils')
|
||||
const { expect } = require('chai').use(require('bn-chai')(BN))
|
||||
const { parseAMBMessage, strip0x, addTxHashToData } = require('../message')
|
||||
|
||||
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 } = parseAMBMessage(message)
|
||||
|
||||
// then
|
||||
expect(sender).to.be.equal(msgSender)
|
||||
expect(executor).to.be.equal(msgExecutor)
|
||||
expect(txHash).to.be.equal(msgTxHash)
|
||||
})
|
||||
})
|
||||
@@ -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}'`)
|
||||
}
|
||||
|
||||
Submodule contracts updated: 86b35f8382...c85aeb737b
@@ -0,0 +1,26 @@
|
||||
---
|
||||
- name: Slurp docker compose file
|
||||
slurp:
|
||||
src: "/home/poadocker/bridge/oracle/{{ item }}.yml"
|
||||
register: docker_compose_slurp
|
||||
- name: Parse docker compose file
|
||||
set_fact:
|
||||
docker_compose_parsed: "{{ docker_compose_slurp['content'] | b64decode | from_yaml }}"
|
||||
|
||||
- name: Add the external network used to connect to Parity nodes
|
||||
set_fact:
|
||||
docker_compose_parsed: "{{ docker_compose_parsed |combine({'networks': {'ultimate': {'external': 'true'}}}, recursive=True) }}"
|
||||
|
||||
- name: Add all Oracle containers to the network
|
||||
set_fact:
|
||||
docker_compose_parsed: "{{ docker_compose_parsed | combine({'services': {item: {'networks': docker_compose_parsed.services[item].networks | union(['ultimate'])}}}, recursive=True) }}"
|
||||
with_items: "{{ docker_compose_parsed.services }}"
|
||||
|
||||
- name: Expose Redis port to allow connecting from redis-cli
|
||||
set_fact:
|
||||
docker_compose_parsed: "{{ docker_compose_parsed | combine({'services': {'redis': {'ports': ['6379:6379']}}}, recursive=True) }}"
|
||||
|
||||
- name: Write updated docker file
|
||||
copy:
|
||||
content: "{{ docker_compose_parsed | to_yaml }}"
|
||||
dest: "/home/poadocker/bridge/oracle/{{ item }}.yml"
|
||||
@@ -5,32 +5,12 @@
|
||||
tasks:
|
||||
- name: stop the service
|
||||
shell: service poabridge stop
|
||||
|
||||
- name: Slurp docker compose file
|
||||
slurp:
|
||||
src: "/home/poadocker/bridge/oracle/docker-compose.yml"
|
||||
register: docker_compose_slurp
|
||||
- name: Parse docker compose file
|
||||
set_fact:
|
||||
docker_compose_parsed: "{{ docker_compose_slurp['content'] | b64decode | from_yaml }}"
|
||||
|
||||
- name: Add the external network used to connect to Parity nodes
|
||||
set_fact:
|
||||
docker_compose_parsed: "{{ docker_compose_parsed |combine({'networks': {'ultimate': {'external': 'true'}}}, recursive=True) }}"
|
||||
|
||||
- name: Add all Oracle containers to the network
|
||||
set_fact:
|
||||
docker_compose_parsed: "{{ docker_compose_parsed | combine({'services': {item: {'networks': docker_compose_parsed.services[item].networks | union(['ultimate'])}}}, recursive=True) }}"
|
||||
with_items: "{{ docker_compose_parsed.services }}"
|
||||
|
||||
- name: Expose Redis port to allow connecting from redis-cli
|
||||
set_fact:
|
||||
docker_compose_parsed: "{{ docker_compose_parsed | combine({'services': {'redis': {'ports': ['6379:6379']}}}, recursive=True) }}"
|
||||
|
||||
- name: Write new docker-compose file
|
||||
copy:
|
||||
content: "{{ docker_compose_parsed | to_yaml }}"
|
||||
dest: "/home/poadocker/bridge/oracle/docker-compose.yml"
|
||||
- include_tasks: oracle-add-docker-external-network.yml
|
||||
with_items:
|
||||
- docker-compose
|
||||
- docker-compose-transfer
|
||||
- docker-compose-erc-native
|
||||
|
||||
- name: start the service
|
||||
shell: service poabridge start
|
||||
|
||||
@@ -36,6 +36,8 @@ provisioner:
|
||||
COMMON_FOREIGN_RPC_URL: "http://parity2:8545"
|
||||
ORACLE_VALIDATOR_ADDRESS: "0xaaB52d66283F7A1D5978bcFcB55721ACB467384b"
|
||||
ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY: "8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9"
|
||||
ORACLE_HOME_START_BLOCK: 1
|
||||
ORACLE_FOREIGN_START_BLOCK: 1
|
||||
ui-erc-to-native-host:
|
||||
COMMON_HOME_RPC_URL: "http://localhost:8541"
|
||||
COMMON_FOREIGN_RPC_URL: "http://localhost:8542"
|
||||
|
||||
@@ -1,22 +1,9 @@
|
||||
---
|
||||
- name: Slurp docker compose file
|
||||
slurp:
|
||||
src: "{{ bridge_path }}/oracle/docker-compose.yml"
|
||||
register: docker_compose_slurp
|
||||
|
||||
- name: Parse docker compose file
|
||||
set_fact:
|
||||
docker_compose_parsed: "{{ docker_compose_slurp['content'] | b64decode | from_yaml }}"
|
||||
|
||||
- name: Set logger to remote server
|
||||
set_fact:
|
||||
docker_compose_parsed: "{{ docker_compose_parsed |combine({'services': {item: {'logging': {'driver': 'syslog','options': {'tag': '{{.Name}}/{{.ID}}'}}}}}, recursive=True) }}"
|
||||
with_items: "{{ docker_compose_parsed.services }}"
|
||||
|
||||
- name: Write new docker-compose file
|
||||
copy:
|
||||
content: "{{ docker_compose_parsed | to_yaml }}"
|
||||
dest: "{{ bridge_path }}/oracle/docker-compose.yml"
|
||||
- include_tasks: logging_by_syslog.yml
|
||||
with_items:
|
||||
- docker-compose
|
||||
- docker-compose-transfer
|
||||
- docker-compose-erc-native
|
||||
|
||||
- name: Set the local container logs configuration file
|
||||
template:
|
||||
|
||||
19
deployment/roles/oracle/tasks/logging_by_syslog.yml
Normal file
19
deployment/roles/oracle/tasks/logging_by_syslog.yml
Normal file
@@ -0,0 +1,19 @@
|
||||
---
|
||||
- name: Slurp docker compose file
|
||||
slurp:
|
||||
src: "{{ bridge_path }}/oracle/{{ item }}.yml"
|
||||
register: docker_compose_slurp
|
||||
|
||||
- name: Parse docker compose file
|
||||
set_fact:
|
||||
docker_compose_parsed: "{{ docker_compose_slurp['content'] | b64decode | from_yaml }}"
|
||||
|
||||
- name: Set logger to remote server
|
||||
set_fact:
|
||||
docker_compose_parsed: "{{ docker_compose_parsed |combine({'services': {item: {'logging': {'driver': 'syslog','options': {'tag': '{{.Name}}/{{.ID}}'}}}}}, recursive=True) }}"
|
||||
with_items: "{{ docker_compose_parsed.services }}"
|
||||
|
||||
- name: Write updated docker file
|
||||
copy:
|
||||
content: "{{ docker_compose_parsed | to_yaml }}"
|
||||
dest: "{{ bridge_path }}/oracle/{{ item }}.yml"
|
||||
@@ -5,14 +5,16 @@
|
||||
args:
|
||||
chdir: "{{ bridge_path }}/oracle"
|
||||
register: BLOCKS
|
||||
when: (ORACLE_HOME_START_BLOCK is not defined) or (ORACLE_FOREIGN_START_BLOCK is not defined)
|
||||
|
||||
- name: Write blocks
|
||||
blockinfile:
|
||||
path: "{{ bridge_path }}/oracle/.env"
|
||||
marker: "## {mark} Calculated by scripts/getValidatorStartBlocks.js"
|
||||
block: |
|
||||
HOME_START_BLOCK={{ (BLOCKS.stdout | from_json).homeStartBlock }}
|
||||
FOREIGN_START_BLOCK={{ (BLOCKS.stdout | from_json).foreignStartBlock }}
|
||||
ORACLE_HOME_START_BLOCK={{ (BLOCKS.stdout | from_json).homeStartBlock }}
|
||||
ORACLE_FOREIGN_START_BLOCK={{ (BLOCKS.stdout | from_json).foreignStartBlock }}
|
||||
when: (ORACLE_HOME_START_BLOCK is not defined) or (ORACLE_FOREIGN_START_BLOCK is not defined)
|
||||
|
||||
- name: Get validator address
|
||||
become_user: "{{ compose_service_user }}"
|
||||
@@ -25,6 +27,25 @@
|
||||
set_fact:
|
||||
ORACLE_VALIDATOR_ADDRESS: "{{ VADDRESS.stdout }}"
|
||||
|
||||
- name: Get foreign erc type
|
||||
become_user: "{{ compose_service_user }}"
|
||||
shell: docker-compose run --entrypoint "node scripts/initialChecks.js" bridge_affirmation
|
||||
args:
|
||||
chdir: "{{ bridge_path }}/oracle"
|
||||
register: ERCTYPE
|
||||
|
||||
- name: Set FOREIGN_ERC_TYPE variable
|
||||
set_fact:
|
||||
FOREIGN_ERC_TYPE: "{{ (ERCTYPE.stdout).foreignERC | default('') }}"
|
||||
|
||||
- name: Extend docker compose file
|
||||
set_fact: composefileoverride="-f docker-compose-transfer.yml"
|
||||
when: ORACLE_BRIDGE_MODE == "ERC_TO_ERC" and FOREIGN_ERC_TYPE != "ERC677"
|
||||
|
||||
- name: Extend docker compose file for erc to native
|
||||
set_fact: composefileoverride="-f docker-compose-erc-native.yml"
|
||||
when: ORACLE_BRIDGE_MODE == "ERC_TO_NATIVE"
|
||||
|
||||
- name: Install .key config
|
||||
template:
|
||||
src: key.j2
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
## General settings
|
||||
ORACLE_BRIDGE_MODE={{ ORACLE_BRIDGE_MODE }}
|
||||
{% if ORACLE_LOG_LEVEL | default('') != '' %}
|
||||
ORACLE_LOG_LEVEL={{ ORACLE_LOG_LEVEL }}
|
||||
{% endif %}
|
||||
|
||||
## Home contract
|
||||
COMMON_HOME_RPC_URL={{ COMMON_HOME_RPC_URL }}
|
||||
@@ -45,3 +47,10 @@ COMMON_FOREIGN_GAS_PRICE_FACTOR={{ COMMON_FOREIGN_GAS_PRICE_FACTOR }}
|
||||
ORACLE_ALLOW_HTTP_FOR_RPC={{ "yes" if ORACLE_ALLOW_HTTP_FOR_RPC else "no" }}
|
||||
ORACLE_QUEUE_URL={{ ORACLE_QUEUE_URL }}
|
||||
ORACLE_REDIS_URL={{ ORACLE_REDIS_URL }}
|
||||
|
||||
{% if ORACLE_HOME_START_BLOCK | default('') != '' %}
|
||||
ORACLE_HOME_START_BLOCK={{ ORACLE_HOME_START_BLOCK }}
|
||||
{% endif %}
|
||||
{% if ORACLE_FOREIGN_START_BLOCK | default('') != '' %}
|
||||
ORACLE_FOREIGN_START_BLOCK={{ ORACLE_FOREIGN_START_BLOCK }}
|
||||
{% endif %}
|
||||
|
||||
@@ -16,6 +16,7 @@ WORKDIR="{{ '/home/' + compose_service_user | default('poadocker') + '/' + bridg
|
||||
keyfile="{{ keyfile_path }}"
|
||||
vaddr="ORACLE_VALIDATOR_ADDRESS="
|
||||
vkey="ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY="
|
||||
composefileoverride="{{ composefileoverride | default('') }}"
|
||||
|
||||
#Parsing file content and add key to variable
|
||||
while read -r line
|
||||
@@ -33,30 +34,30 @@ done < $keyfile
|
||||
start(){
|
||||
echo "Starting bridge.."
|
||||
cd $WORKDIR
|
||||
sudo -u "{{ compose_service_user }}" /usr/local/bin/docker-compose down -v
|
||||
sudo -u "{{ compose_service_user }}" /usr/local/bin/docker-compose rm -fv
|
||||
sudo -u "{{ compose_service_user }}" ORACLE_VALIDATOR_ADDRESS=$vaddr ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY=$vkey /usr/local/bin/docker-compose up --detach
|
||||
sudo -u "{{ compose_service_user }}" /usr/local/bin/docker-compose $composefileoverride down -v
|
||||
sudo -u "{{ compose_service_user }}" /usr/local/bin/docker-compose $composefileoverride rm -fv
|
||||
sudo -u "{{ compose_service_user }}" ORACLE_VALIDATOR_ADDRESS=$vaddr ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY=$vkey /usr/local/bin/docker-compose $composefileoverride up --detach
|
||||
}
|
||||
|
||||
stop(){
|
||||
echo "Stopping bridge.."
|
||||
cd $WORKDIR
|
||||
sudo -u "{{ compose_service_user }}" /usr/local/bin/docker-compose down -v
|
||||
sudo -u "{{ compose_service_user }}" /usr/local/bin/docker-compose $composefileoverride down -v
|
||||
sleep 2
|
||||
}
|
||||
|
||||
status(){
|
||||
echo "Bridge status:"
|
||||
cd $WORKDIR
|
||||
sudo -u "{{ compose_service_user }}" /usr/local/bin/docker-compose ps
|
||||
sudo -u "{{ compose_service_user }}" /usr/local/bin/docker-compose $composefileoverride ps
|
||||
}
|
||||
|
||||
rebuild(){
|
||||
echo "Rebuild bridge.."
|
||||
cd $WORKDIR
|
||||
sudo -u "{{ compose_service_user }}" /usr/local/bin/docker-compose down -v
|
||||
sudo -u "{{ compose_service_user }}" /usr/local/bin/docker-compose rm -fv
|
||||
sudo -u "{{ compose_service_user }}" ORACLE_VALIDATOR_ADDRESS=$vaddr ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY=$vkey /usr/local/bin/docker-compose up --detach --force-recreate --no-deps --build
|
||||
sudo -u "{{ compose_service_user }}" /usr/local/bin/docker-compose $composefileoverride down -v
|
||||
sudo -u "{{ compose_service_user }}" /usr/local/bin/docker-compose $composefileoverride rm -fv
|
||||
sudo -u "{{ compose_service_user }}" ORACLE_VALIDATOR_ADDRESS=$vaddr ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY=$vkey /usr/local/bin/docker-compose $composefileoverride up --detach --force-recreate --no-deps --build
|
||||
}
|
||||
|
||||
|
||||
|
||||
18
e2e-commons/components-envs/monitor-amb.env
Normal file
18
e2e-commons/components-envs/monitor-amb.env
Normal file
@@ -0,0 +1,18 @@
|
||||
COMMON_HOME_RPC_URL=http://parity1:8545
|
||||
COMMON_FOREIGN_RPC_URL=http://parity2:8545
|
||||
COMMON_HOME_BRIDGE_ADDRESS=0x0AEe1FCD12dDFab6265F7f8956e6E012A9Fe4Aa0
|
||||
COMMON_FOREIGN_BRIDGE_ADDRESS=0x0AEe1FCD12dDFab6265F7f8956e6E012A9Fe4Aa0
|
||||
MONITOR_HOME_START_BLOCK=0
|
||||
MONITOR_FOREIGN_START_BLOCK=0
|
||||
MONITOR_VALIDATOR_HOME_TX_LIMIT=300000
|
||||
COMMON_HOME_GAS_PRICE_SUPPLIER_URL=https://gasprice.poa.network/
|
||||
COMMON_HOME_GAS_PRICE_SPEED_TYPE=standard
|
||||
COMMON_HOME_GAS_PRICE_FALLBACK=1000000000
|
||||
COMMON_HOME_GAS_PRICE_FACTOR=1
|
||||
MONITOR_VALIDATOR_FOREIGN_TX_LIMIT=300000
|
||||
COMMON_FOREIGN_GAS_PRICE_SUPPLIER_URL=https://gasprice.poa.network/
|
||||
COMMON_FOREIGN_GAS_PRICE_SPEED_TYPE=standard
|
||||
COMMON_FOREIGN_GAS_PRICE_FALLBACK=1000000000
|
||||
COMMON_FOREIGN_GAS_PRICE_FACTOR=1
|
||||
MONITOR_TX_NUMBER_THRESHOLD=100
|
||||
MONITOR_PORT=3013
|
||||
23
e2e-commons/components-envs/oracle-amb.env
Normal file
23
e2e-commons/components-envs/oracle-amb.env
Normal file
@@ -0,0 +1,23 @@
|
||||
|
||||
ORACLE_BRIDGE_MODE=ARBITRARY_MESSAGE
|
||||
ORACLE_QUEUE_URL=amqp://rabbit
|
||||
ORACLE_REDIS_URL=redis://redis
|
||||
COMMON_HOME_RPC_URL=http://parity1:8545
|
||||
COMMON_FOREIGN_RPC_URL=http://parity2:8545
|
||||
COMMON_HOME_BRIDGE_ADDRESS=0x0AEe1FCD12dDFab6265F7f8956e6E012A9Fe4Aa0
|
||||
COMMON_FOREIGN_BRIDGE_ADDRESS=0x0AEe1FCD12dDFab6265F7f8956e6E012A9Fe4Aa0
|
||||
ORACLE_VALIDATOR_ADDRESS=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b
|
||||
ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY=8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9
|
||||
COMMON_HOME_GAS_PRICE_SUPPLIER_URL=https://gasprice.poa.network/
|
||||
COMMON_HOME_GAS_PRICE_SPEED_TYPE=standard
|
||||
COMMON_HOME_GAS_PRICE_FALLBACK=1000000000
|
||||
ORACLE_HOME_GAS_PRICE_UPDATE_INTERVAL=600000
|
||||
COMMON_HOME_GAS_PRICE_FACTOR=1
|
||||
COMMON_FOREIGN_GAS_PRICE_SUPPLIER_URL=https://gasprice.poa.network/
|
||||
COMMON_FOREIGN_GAS_PRICE_SPEED_TYPE=standard
|
||||
COMMON_FOREIGN_GAS_PRICE_FALLBACK=10000000000
|
||||
ORACLE_FOREIGN_GAS_PRICE_UPDATE_INTERVAL=600000
|
||||
COMMON_FOREIGN_GAS_PRICE_FACTOR=1
|
||||
ORACLE_HOME_RPC_POLLING_INTERVAL=500
|
||||
ORACLE_FOREIGN_RPC_POLLING_INTERVAL=500
|
||||
ORACLE_ALLOW_HTTP_FOR_RPC=yes
|
||||
@@ -21,3 +21,5 @@ COMMON_FOREIGN_GAS_PRICE_FACTOR=0.1
|
||||
ORACLE_HOME_RPC_POLLING_INTERVAL=500
|
||||
ORACLE_FOREIGN_RPC_POLLING_INTERVAL=500
|
||||
ORACLE_ALLOW_HTTP_FOR_RPC=yes
|
||||
ORACLE_HOME_START_BLOCK=1
|
||||
ORACLE_FOREIGN_START_BLOCK=1
|
||||
|
||||
@@ -33,10 +33,19 @@
|
||||
"ercToNativeBridge": {
|
||||
"home": "0x488Af810997eD1730cB3a3918cD83b3216E6eAda",
|
||||
"foreign": "0x488Af810997eD1730cB3a3918cD83b3216E6eAda",
|
||||
"foreignToken": "0x3C665A31199694Bf723fD08844AD290207B5797f",
|
||||
"foreignToken": "0x7cc4b1851c35959d34e635a470f6b5c43ba3c9c9",
|
||||
"halfDuplexToken": "0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359",
|
||||
"saiTop": "0x9b0ccf7C8994E19F39b2B4CF708e0A7DF65fA8a3",
|
||||
"ui": "http://localhost:3002",
|
||||
"monitor": "http://monitor-erc20-native:3012"
|
||||
},
|
||||
"amb": {
|
||||
"home": "0x0AEe1FCD12dDFab6265F7f8956e6E012A9Fe4Aa0",
|
||||
"foreign": "0x0AEe1FCD12dDFab6265F7f8956e6E012A9Fe4Aa0",
|
||||
"homeBox": "0x6C4EaAb8756d53Bf599FFe2347FAFF1123D6C8A1",
|
||||
"foreignBox": "0x6C4EaAb8756d53Bf599FFe2347FAFF1123D6C8A1",
|
||||
"monitor": "http://monitor-amb:3013"
|
||||
},
|
||||
"homeRPC": {
|
||||
"URL": "http://parity1:8545",
|
||||
"ID": "77"
|
||||
|
||||
25
e2e-commons/contracts-envs/amb.env
Normal file
25
e2e-commons/contracts-envs/amb.env
Normal file
@@ -0,0 +1,25 @@
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
REQUIRED_NUMBER_OF_VALIDATORS=1
|
||||
VALIDATORS=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b
|
||||
@@ -32,7 +32,7 @@ FOREIGN_GAS_PRICE=10000000000
|
||||
FOREIGN_REWARDABLE=false
|
||||
|
||||
BLOCK_REWARD_ADDRESS=0xF9698Eb93702dfdd0e2d802088d4c21822a8A977
|
||||
ERC20_TOKEN_ADDRESS=0x3C665A31199694Bf723fD08844AD290207B5797f
|
||||
ERC20_TOKEN_ADDRESS=0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359
|
||||
|
||||
REQUIRED_NUMBER_OF_VALIDATORS=1
|
||||
VALIDATORS="0xaaB52d66283F7A1D5978bcFcB55721ACB467384b"
|
||||
|
||||
@@ -61,6 +61,17 @@ services:
|
||||
command: "true"
|
||||
networks:
|
||||
- ultimate
|
||||
oracle-amb:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: oracle/Dockerfile
|
||||
args:
|
||||
DOT_ENV_PATH: e2e-commons/components-envs/oracle-amb.env
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
command: "true"
|
||||
networks:
|
||||
- ultimate
|
||||
ui:
|
||||
build:
|
||||
context: ..
|
||||
@@ -121,6 +132,17 @@ services:
|
||||
- "3012:3012"
|
||||
networks:
|
||||
- ultimate
|
||||
monitor-amb:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: monitor/Dockerfile
|
||||
args:
|
||||
DOT_ENV_PATH: e2e-commons/components-envs/monitor-amb.env
|
||||
entrypoint: yarn check-and-start
|
||||
ports:
|
||||
- "3013:3013"
|
||||
networks:
|
||||
- ultimate
|
||||
e2e:
|
||||
build:
|
||||
context: ..
|
||||
|
||||
@@ -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
|
||||
@@ -17,9 +17,16 @@ while [ "$1" != "" ]; do
|
||||
docker-compose run -d oracle-erc20 yarn watcher:signature-request
|
||||
docker-compose run -d oracle-erc20 yarn watcher:collected-signatures
|
||||
docker-compose run -d oracle-erc20 yarn watcher:affirmation-request
|
||||
docker-compose run -d oracle-erc20 yarn watcher:transfer
|
||||
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-erc20-native yarn watcher:transfer
|
||||
docker-compose run -d oracle-erc20-native yarn watcher:half-duplex-transfer
|
||||
docker-compose run -d oracle-erc20-native yarn worker:swap-tokens
|
||||
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
|
||||
@@ -41,7 +48,7 @@ while [ "$1" != "" ]; do
|
||||
fi
|
||||
|
||||
if [ "$1" == "monitor" ]; then
|
||||
docker-compose up -d monitor monitor-erc20 monitor-erc20-native
|
||||
docker-compose up -d monitor monitor-erc20 monitor-erc20-native monitor-amb
|
||||
fi
|
||||
|
||||
if [ "$1" == "native-to-erc" ]; then
|
||||
|
||||
@@ -3,4 +3,5 @@ while true; do
|
||||
docker-compose -f ../e2e-commons/docker-compose.yml exec monitor yarn check-all
|
||||
docker-compose -f ../e2e-commons/docker-compose.yml exec monitor-erc20 yarn check-all
|
||||
docker-compose -f ../e2e-commons/docker-compose.yml exec monitor-erc20-native yarn check-all
|
||||
docker-compose -f ../e2e-commons/docker-compose.yml exec monitor-amb yarn check-all
|
||||
done
|
||||
|
||||
128
monitor-e2e/test/amb.js
Normal file
128
monitor-e2e/test/amb.js
Normal file
@@ -0,0 +1,128 @@
|
||||
const assert = require('assert')
|
||||
const axios = require('axios')
|
||||
const { amb, user, foreignRPC, homeRPC, validator } = require('../../e2e-commons/constants.json')
|
||||
const { waitUntil, sendAMBMessage, addValidator } = require('../utils')
|
||||
|
||||
const baseUrl = amb.monitor
|
||||
|
||||
describe('AMB', () => {
|
||||
describe('balances', async () => {
|
||||
let data
|
||||
|
||||
before(async () => {
|
||||
;({ data } = await axios.get(`${baseUrl}`))
|
||||
})
|
||||
|
||||
describe('home', async () => {
|
||||
it('should contain toForeign:', () => assert(data.home.toForeign === 0))
|
||||
it('should contain fromForeign', () => assert(data.home.fromForeign === 0))
|
||||
})
|
||||
|
||||
describe('foreign', async () => {
|
||||
it('should contain fromHome:', () => assert(data.foreign.fromHome === 0))
|
||||
it('should contain toHome', () => assert(data.foreign.toHome === 0))
|
||||
})
|
||||
|
||||
describe('general', async () => {
|
||||
it('should contain fromHomeToForeignDiff', () => assert(data.fromHomeToForeignDiff === 0))
|
||||
it('should contain fromForeignToHomeDiff', () => assert(data.fromForeignToHomeDiff === 0))
|
||||
it('should contain lastChecked', () => assert(data.lastChecked >= 0))
|
||||
it('should contain timeDiff', () => assert(data.timeDiff >= 0))
|
||||
it('should contain lastChecked', () => assert(data.lastChecked >= 0))
|
||||
})
|
||||
})
|
||||
describe('validators', async () => {
|
||||
let data
|
||||
|
||||
before(async () => {
|
||||
;({ data } = await axios.get(`${baseUrl}/validators`))
|
||||
})
|
||||
|
||||
it('home', () => {
|
||||
assert(typeof data.home.validators === 'object')
|
||||
assert(data.home.validators[validator.address].balance > 0)
|
||||
assert(data.home.validators[validator.address].leftTx > 0)
|
||||
assert(data.home.validators[validator.address].gasPrice > 0)
|
||||
})
|
||||
|
||||
it('foreign', () => {
|
||||
assert(typeof data.foreign.validators === 'object')
|
||||
assert(data.foreign.validators[validator.address].balance > 0)
|
||||
assert(data.foreign.validators[validator.address].leftTx > 0)
|
||||
assert(data.foreign.validators[validator.address].gasPrice > 0)
|
||||
})
|
||||
|
||||
it('requiredSignaturesMatch', () => assert(data.requiredSignaturesMatch, 1))
|
||||
it('validatorsMatch', () => assert(data.validatorsMatch))
|
||||
it('lastChecked', () => assert(data.lastChecked >= 0))
|
||||
it('timeDiff', () => assert(data.timeDiff >= 0))
|
||||
it('homeOk', () => assert(data.homeOk))
|
||||
it('foreignOk', () => assert(data.foreignOk))
|
||||
it('ok', () => assert(data.ok))
|
||||
})
|
||||
describe('eventsStats', async () => {
|
||||
let data
|
||||
|
||||
before(async () => {
|
||||
;({ data } = await axios.get(`${baseUrl}/eventsStats`))
|
||||
})
|
||||
|
||||
it('ok', () => assert(data.ok))
|
||||
it('lastChecked', () => assert(data.lastChecked >= 0))
|
||||
it('timeDiff', () => assert(data.timeDiff >= 0))
|
||||
it('home-deliveredMsgNotProcessedInForeign', () =>
|
||||
assert(typeof data.home.deliveredMsgNotProcessedInForeign === 'object'))
|
||||
it('home-processedMsgNotDeliveredInForeign', () =>
|
||||
assert(typeof data.home.processedMsgNotDeliveredInForeign === 'object'))
|
||||
it('foreign-deliveredMsgNotProcessedInHome', () =>
|
||||
assert(typeof data.foreign.deliveredMsgNotProcessedInHome === 'object'))
|
||||
it('foreign-processedMsgNotDeliveredInHome', () =>
|
||||
assert(typeof data.foreign.processedMsgNotDeliveredInHome === 'object'))
|
||||
})
|
||||
describe('alerts', async () => {
|
||||
let data
|
||||
|
||||
before(async () => {
|
||||
;({ data } = await axios.get(`${baseUrl}/alerts`))
|
||||
})
|
||||
|
||||
it('ok', () => assert(data.ok))
|
||||
it('lastChecked', () => assert(data.lastChecked >= 0))
|
||||
it('timeDiff', () => assert(data.timeDiff >= 0))
|
||||
it('executeSignatures', () => assert(typeof data.executeSignatures === 'object'))
|
||||
it('executeAffirmations', () => assert(typeof data.executeAffirmations === 'object'))
|
||||
})
|
||||
describe('changing state of contracts', () => {
|
||||
let data
|
||||
|
||||
before(async () => {
|
||||
assert((await axios.get(`${baseUrl}/validators`)).data.validatorsMatch === true)
|
||||
})
|
||||
|
||||
it('should change fromForeignToHomeDiff', async () => {
|
||||
// send message
|
||||
await sendAMBMessage(foreignRPC.URL, user, amb.foreignBox, amb.foreign, amb.homeBox)
|
||||
|
||||
await waitUntil(async () => {
|
||||
;({ data } = await axios.get(`${baseUrl}`))
|
||||
return data.fromForeignToHomeDiff !== 0
|
||||
})
|
||||
})
|
||||
it('should change fromHomeToForeignDiff', async () => {
|
||||
// send message
|
||||
await sendAMBMessage(homeRPC.URL, user, amb.homeBox, amb.home, amb.foreignBox)
|
||||
|
||||
await waitUntil(async () => {
|
||||
;({ data } = await axios.get(`${baseUrl}`))
|
||||
return data.fromHomeToForeignDiff !== 0
|
||||
})
|
||||
})
|
||||
it('should change validatorsMatch', async () => {
|
||||
await addValidator(foreignRPC.URL, validator, amb.foreign)
|
||||
await waitUntil(async () => {
|
||||
;({ data } = await axios.get(`${baseUrl}/validators`))
|
||||
return data.validatorsMatch === false
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -25,7 +25,7 @@ describe('ERC TO NATIVE with changing state of contracts', () => {
|
||||
})
|
||||
|
||||
it('should change balanceDiff', async () => {
|
||||
await sendTokens(foreignRPC.URL, user, ercToNativeBridge.foreignToken, ercToNativeBridge.foreign)
|
||||
await sendTokens(foreignRPC.URL, user, ercToNativeBridge.halfDuplexToken, ercToNativeBridge.foreign)
|
||||
|
||||
await waitUntil(async () => {
|
||||
;({ data } = await axios.get(`${baseUrl}`))
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const Web3 = require('web3')
|
||||
const { ERC677_BRIDGE_TOKEN_ABI, BRIDGE_VALIDATORS_ABI, FOREIGN_NATIVE_TO_ERC_ABI } = require('../commons')
|
||||
const { ERC677_BRIDGE_TOKEN_ABI, BRIDGE_VALIDATORS_ABI, FOREIGN_NATIVE_TO_ERC_ABI, BOX_ABI } = require('../commons')
|
||||
|
||||
const waitUntil = async (predicate, step = 100, timeout = 10000) => {
|
||||
const stopTime = Date.now() + timeout
|
||||
@@ -37,6 +37,17 @@ const sendTokens = async (rpcUrl, account, tokenAddress, recipientAddress) => {
|
||||
})
|
||||
}
|
||||
|
||||
const sendAMBMessage = async (rpcUrl, account, boxAddress, bridgeAddress, boxOtherSideAddress) => {
|
||||
const web3 = new Web3(new Web3.providers.HttpProvider(rpcUrl))
|
||||
web3.eth.accounts.wallet.add(account.privateKey)
|
||||
const homeBox = new web3.eth.Contract(BOX_ABI, boxAddress)
|
||||
|
||||
await homeBox.methods.setValueOnOtherNetwork(3, bridgeAddress, boxOtherSideAddress).send({
|
||||
from: account.address,
|
||||
gas: '400000'
|
||||
})
|
||||
}
|
||||
|
||||
const addValidator = async (rpcUrl, account, bridgeAddress) => {
|
||||
const web3 = new Web3(new Web3.providers.HttpProvider(rpcUrl))
|
||||
web3.eth.accounts.wallet.add(account.privateKey)
|
||||
@@ -53,5 +64,6 @@ module.exports = {
|
||||
waitUntil,
|
||||
sendEther,
|
||||
sendTokens,
|
||||
addValidator
|
||||
addValidator,
|
||||
sendAMBMessage
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ check_files_exist() {
|
||||
(docker-compose -f ../e2e-commons/docker-compose.yml exec monitor /bin/bash -c "$command") || rc=1
|
||||
(docker-compose -f ../e2e-commons/docker-compose.yml exec monitor-erc20 /bin/bash -c "$command") || rc=1
|
||||
(docker-compose -f ../e2e-commons/docker-compose.yml exec monitor-erc20-native /bin/bash -c "$command") || rc=1
|
||||
(docker-compose -f ../e2e-commons/docker-compose.yml exec monitor-amb /bin/bash -c "$command") || rc=1
|
||||
done
|
||||
return $rc
|
||||
}
|
||||
|
||||
@@ -8,5 +8,8 @@
|
||||
"rules": {
|
||||
"no-use-before-define": "off",
|
||||
"node/no-unpublished-require": "off"
|
||||
},
|
||||
"env": {
|
||||
"mocha": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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, eventWithoutReference } = require('./utils/message')
|
||||
const { BRIDGE_MODES } = require('../commons')
|
||||
|
||||
const { COMMON_HOME_RPC_URL, COMMON_FOREIGN_RPC_URL } = process.env
|
||||
|
||||
@@ -13,11 +15,23 @@ const foreignProvider = new Web3.providers.HttpProvider(COMMON_FOREIGN_RPC_URL)
|
||||
const web3Foreign = new Web3(foreignProvider)
|
||||
|
||||
async function main() {
|
||||
const { foreignDeposits, homeDeposits, homeWithdrawals, foreignWithdrawals } = await eventsInfo()
|
||||
|
||||
const xSignatures = foreignDeposits.filter(findDifferences(homeDeposits))
|
||||
const xAffirmations = homeWithdrawals.filter(findDifferences(foreignWithdrawals))
|
||||
const {
|
||||
homeToForeignRequests,
|
||||
homeToForeignConfirmations,
|
||||
foreignToHomeConfirmations,
|
||||
foreignToHomeRequests,
|
||||
bridgeMode
|
||||
} = await eventsInfo()
|
||||
|
||||
let xSignatures
|
||||
let xAffirmations
|
||||
if (bridgeMode === BRIDGE_MODES.ARBITRARY_MESSAGE) {
|
||||
xSignatures = homeToForeignConfirmations.filter(processedMsgNotDelivered(homeToForeignRequests))
|
||||
xAffirmations = foreignToHomeConfirmations.filter(processedMsgNotDelivered(foreignToHomeRequests))
|
||||
} else {
|
||||
xSignatures = homeToForeignConfirmations.filter(eventWithoutReference(homeToForeignRequests))
|
||||
xAffirmations = foreignToHomeConfirmations.filter(eventWithoutReference(foreignToHomeRequests))
|
||||
}
|
||||
logger.debug('building misbehavior blocks')
|
||||
const [homeBlockNumber, foreignBlockNumber] = await getBlockNumber(web3Home, web3Foreign)
|
||||
|
||||
@@ -37,8 +51,8 @@ async function main() {
|
||||
const foreignValidators = await Promise.all(xSignatures.map(event => findTxSender(web3Foreign)(event)))
|
||||
const homeValidators = await Promise.all(xAffirmations.map(event => findTxSender(web3Home)(event)))
|
||||
|
||||
const xSignaturesTxs = xSignatures.map(normalizeEventInformation).reduce(buildTxList(foreignValidators), {})
|
||||
const xAffirmationsTxs = xAffirmations.map(normalizeEventInformation).reduce(buildTxList(homeValidators), {})
|
||||
const xSignaturesTxs = xSignatures.reduce(buildTxList(foreignValidators), {})
|
||||
const xAffirmationsTxs = xAffirmations.reduce(buildTxList(homeValidators), {})
|
||||
|
||||
logger.debug('Done')
|
||||
|
||||
@@ -135,7 +149,7 @@ const findTxSender = web3 => async ({ transactionHash }) => {
|
||||
* }}}
|
||||
*/
|
||||
const buildTxList = validatorsList => (acc, event, index) => {
|
||||
acc[event.txHash] = {
|
||||
acc[event.transactionHash] = {
|
||||
value: event.value,
|
||||
block: event.blockNumber,
|
||||
referenceTx: event.referenceTx,
|
||||
@@ -145,38 +159,4 @@ const buildTxList = validatorsList => (acc, event, index) => {
|
||||
return acc
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a missing destDeposit in src list if there's any
|
||||
* @param {Array} src
|
||||
* @returns {function(*=): boolean}
|
||||
*/
|
||||
const findDifferences = src => dest => {
|
||||
const b = normalizeEventInformation(dest)
|
||||
|
||||
return (
|
||||
src
|
||||
.map(normalizeEventInformation)
|
||||
.filter(a => a.referenceTx === b.referenceTx && a.recipient === b.recipient && a.value === b.value).length === 0
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes the different event objects to facilitate data processing
|
||||
* @param {Object} event
|
||||
* @returns {{
|
||||
* txHash: string,
|
||||
* blockNumber: number,
|
||||
* referenceTx: string,
|
||||
* recipient: string | *,
|
||||
* value: *
|
||||
* }}
|
||||
*/
|
||||
const normalizeEventInformation = event => ({
|
||||
txHash: event.transactionHash,
|
||||
blockNumber: event.blockNumber,
|
||||
referenceTx: event.returnValues.transactionHash || event.transactionHash,
|
||||
recipient: event.returnValues.recipient || event.returnValues.from,
|
||||
value: event.returnValues.value
|
||||
})
|
||||
|
||||
module.exports = main
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Yarn:
|
||||
*/4 * * * * cd $HOME/bridge-monitor; yarn check-all >>cronWorker.out 2>>cronWorker.err
|
||||
*/4 * * * * cd $HOME/tokenbridge/monitor; yarn check-all >>cronWorker.out 2>>cronWorker.err
|
||||
|
||||
# Docker:
|
||||
*/4 * * * * cd $HOME/bridge-monitor; docker-compose exec monitor yarn check-all >>cronWorker.out 2>>cronWorker.err
|
||||
*/4 * * * * cd $HOME/tokenbridge/monitor; docker-compose exec monitor yarn check-all >>cronWorker.out 2>>cronWorker.err
|
||||
|
||||
@@ -1,81 +1,51 @@
|
||||
require('dotenv').config()
|
||||
const logger = require('./logger')('eventsStats')
|
||||
const eventsInfo = require('./utils/events')
|
||||
|
||||
function compareDepositsHome(foreign) {
|
||||
return homeDeposit => {
|
||||
return (
|
||||
foreign.filter(foreignDeposit => {
|
||||
return (
|
||||
foreignDeposit.returnValues.transactionHash === homeDeposit.transactionHash &&
|
||||
foreignDeposit.returnValues.recipient === homeDeposit.returnValues.recipient &&
|
||||
foreignDeposit.returnValues.value === homeDeposit.returnValues.value
|
||||
)
|
||||
}).length === 0
|
||||
)
|
||||
}
|
||||
}
|
||||
function compareDepositsForeign(home) {
|
||||
return foreignDeposit => {
|
||||
return (
|
||||
home.filter(homeDeposit => {
|
||||
return (
|
||||
homeDeposit.transactionHash === foreignDeposit.returnValues.transactionHash &&
|
||||
homeDeposit.returnValues.recipient === foreignDeposit.returnValues.recipient &&
|
||||
homeDeposit.returnValues.value === foreignDeposit.returnValues.value
|
||||
)
|
||||
}).length === 0
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function compareTransferHome(foreign) {
|
||||
return homeDeposit => {
|
||||
return (
|
||||
foreign.filter(foreignDeposit => {
|
||||
return (
|
||||
homeDeposit.returnValues.transactionHash === foreignDeposit.transactionHash &&
|
||||
homeDeposit.returnValues.recipient === foreignDeposit.returnValues.from &&
|
||||
homeDeposit.returnValues.value === foreignDeposit.returnValues.value
|
||||
)
|
||||
}).length === 0
|
||||
)
|
||||
}
|
||||
}
|
||||
function compareTransferForeign(home) {
|
||||
return foreignDeposit => {
|
||||
return (
|
||||
home.filter(homeDeposit => {
|
||||
return (
|
||||
foreignDeposit.transactionHash === homeDeposit.returnValues.transactionHash &&
|
||||
foreignDeposit.returnValues.from === homeDeposit.returnValues.recipient &&
|
||||
foreignDeposit.returnValues.value === homeDeposit.returnValues.value
|
||||
)
|
||||
}).length === 0
|
||||
)
|
||||
}
|
||||
}
|
||||
const { processedMsgNotDelivered, deliveredMsgNotProcessed, eventWithoutReference } = require('./utils/message')
|
||||
const { BRIDGE_MODES } = require('../commons')
|
||||
|
||||
async function main() {
|
||||
const { foreignDeposits, homeDeposits, homeWithdrawals, foreignWithdrawals, isExternalErc20 } = await eventsInfo()
|
||||
const {
|
||||
homeToForeignRequests,
|
||||
homeToForeignConfirmations,
|
||||
foreignToHomeConfirmations,
|
||||
foreignToHomeRequests,
|
||||
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: homeToForeignRequests.filter(
|
||||
deliveredMsgNotProcessed(homeToForeignConfirmations)
|
||||
),
|
||||
processedMsgNotDeliveredInForeign: foreignToHomeConfirmations.filter(
|
||||
processedMsgNotDelivered(foreignToHomeRequests)
|
||||
)
|
||||
},
|
||||
foreign: {
|
||||
deliveredMsgNotProcessedInHome: foreignToHomeRequests.filter(
|
||||
deliveredMsgNotProcessed(foreignToHomeConfirmations)
|
||||
),
|
||||
processedMsgNotDeliveredInHome: homeToForeignConfirmations.filter(
|
||||
processedMsgNotDelivered(homeToForeignRequests)
|
||||
)
|
||||
},
|
||||
lastChecked: Math.floor(Date.now() / 1000)
|
||||
}
|
||||
} else {
|
||||
const onlyInHomeDeposits = homeToForeignRequests.filter(eventWithoutReference(homeToForeignConfirmations))
|
||||
const onlyInForeignDeposits = homeToForeignConfirmations.filter(eventWithoutReference(homeToForeignRequests))
|
||||
|
||||
const onlyInHomeWithdrawals = isExternalErc20
|
||||
? homeWithdrawals.filter(compareTransferHome(foreignWithdrawals))
|
||||
: homeWithdrawals.filter(compareDepositsForeign(foreignWithdrawals))
|
||||
const onlyInForeignWithdrawals = isExternalErc20
|
||||
? foreignWithdrawals.filter(compareTransferForeign(homeWithdrawals))
|
||||
: foreignWithdrawals.filter(compareDepositsHome(homeWithdrawals))
|
||||
const onlyInHomeWithdrawals = foreignToHomeConfirmations.filter(eventWithoutReference(foreignToHomeRequests))
|
||||
const onlyInForeignWithdrawals = foreignToHomeRequests.filter(eventWithoutReference(foreignToHomeConfirmations))
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -83,6 +83,26 @@ async function main(bridgeMode) {
|
||||
const foreignBridge = new web3Foreign.eth.Contract(FOREIGN_ERC_TO_NATIVE_ABI, COMMON_FOREIGN_BRIDGE_ADDRESS)
|
||||
const erc20Address = await foreignBridge.methods.erc20token().call()
|
||||
const erc20Contract = new web3Foreign.eth.Contract(ERC20_ABI, erc20Address)
|
||||
let foreignHalfDuplexErc20Balance = 0
|
||||
let displayHalfDuplexToken = false
|
||||
let tokenSwapAllowed = false
|
||||
try {
|
||||
const halfDuplexTokenAddress = await foreignBridge.methods.halfDuplexErc20token().call()
|
||||
if (halfDuplexTokenAddress !== erc20Address) {
|
||||
const halfDuplexToken = new web3Foreign.eth.Contract(ERC20_ABI, halfDuplexTokenAddress)
|
||||
logger.debug('calling halfDuplexToken.methods.balanceOf')
|
||||
foreignHalfDuplexErc20Balance = await halfDuplexToken.methods.balanceOf(COMMON_FOREIGN_BRIDGE_ADDRESS).call()
|
||||
logger.debug('getting last block numbers')
|
||||
const block = await web3Foreign.eth.getBlock('latest')
|
||||
|
||||
logger.debug(`Checking if SCD Emergency Shutdown has happened`)
|
||||
tokenSwapAllowed = await foreignBridge.methods.isTokenSwapAllowed(block.timestamp).call()
|
||||
displayHalfDuplexToken = true
|
||||
}
|
||||
} catch (e) {
|
||||
logger.debug('Methods for half duplex token are not present')
|
||||
}
|
||||
|
||||
logger.debug('calling erc20Contract.methods.balanceOf')
|
||||
const foreignErc20Balance = await erc20Contract.methods.balanceOf(COMMON_FOREIGN_BRIDGE_ADDRESS).call()
|
||||
|
||||
@@ -91,7 +111,7 @@ async function main(bridgeMode) {
|
||||
const blockRewardAddress = await homeBridge.methods.blockRewardContract().call()
|
||||
const blockRewardContract = new web3Home.eth.Contract(BLOCK_REWARD_ABI, blockRewardAddress)
|
||||
logger.debug('calling blockReward.methods.mintedTotally')
|
||||
const mintedCoins = await blockRewardContract.methods.mintedTotally().call()
|
||||
const mintedCoins = await blockRewardContract.methods.mintedTotallyByBridge(COMMON_HOME_BRIDGE_ADDRESS).call()
|
||||
logger.debug('calling homeBridge.methods.totalBurntCoins')
|
||||
const burntCoins = await homeBridge.methods.totalBurntCoins().call()
|
||||
|
||||
@@ -99,19 +119,45 @@ async function main(bridgeMode) {
|
||||
const burntCoinsBN = new BN(burntCoins)
|
||||
const totalSupplyBN = mintedCoinsBN.minus(burntCoinsBN)
|
||||
const foreignErc20BalanceBN = new BN(foreignErc20Balance)
|
||||
const halfDuplexErc20BalanceBN =
|
||||
displayHalfDuplexToken && tokenSwapAllowed ? new BN(foreignHalfDuplexErc20Balance) : new BN(0)
|
||||
|
||||
const diff = foreignErc20BalanceBN
|
||||
.plus(halfDuplexErc20BalanceBN)
|
||||
.minus(totalSupplyBN)
|
||||
.toFixed()
|
||||
|
||||
let foreign = {
|
||||
erc20Balance: Web3Utils.fromWei(foreignErc20Balance)
|
||||
}
|
||||
|
||||
if (displayHalfDuplexToken && tokenSwapAllowed) {
|
||||
foreign = {
|
||||
...foreign,
|
||||
halfDuplexErc20Balance: Web3Utils.fromWei(foreignHalfDuplexErc20Balance)
|
||||
}
|
||||
} else if (displayHalfDuplexToken && !tokenSwapAllowed) {
|
||||
foreign = {
|
||||
...foreign,
|
||||
halfDuplexErc20BalanceAfterES: Web3Utils.fromWei(foreignHalfDuplexErc20Balance)
|
||||
}
|
||||
}
|
||||
|
||||
const diff = foreignErc20BalanceBN.minus(totalSupplyBN).toFixed()
|
||||
logger.debug('Done')
|
||||
return {
|
||||
home: {
|
||||
totalSupply: Web3Utils.fromWei(totalSupplyBN.toFixed())
|
||||
},
|
||||
foreign: {
|
||||
erc20Balance: Web3Utils.fromWei(foreignErc20Balance)
|
||||
},
|
||||
foreign,
|
||||
balanceDiff: Number(Web3Utils.fromWei(diff)),
|
||||
lastChecked: Math.floor(Date.now() / 1000)
|
||||
}
|
||||
} else if (bridgeMode === BRIDGE_MODES.ARBITRARY_MESSAGE) {
|
||||
return {
|
||||
home: {},
|
||||
foreign: {},
|
||||
lastChecked: Math.floor(Date.now() / 1000)
|
||||
}
|
||||
} else {
|
||||
throw new Error(`Unrecognized bridge mode: '${bridgeMode}'`)
|
||||
}
|
||||
|
||||
@@ -1,19 +1,40 @@
|
||||
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)
|
||||
const {
|
||||
homeToForeignConfirmations,
|
||||
homeToForeignRequests,
|
||||
foreignToHomeConfirmations,
|
||||
foreignToHomeRequests
|
||||
} = 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 {
|
||||
fromHomeToForeignDiff: homeToForeignRequests.length - homeToForeignConfirmations.length,
|
||||
fromForeignToHomeDiff: foreignToHomeConfirmations.length - foreignToHomeRequests.length,
|
||||
home: {
|
||||
toForeign: homeToForeignRequests.length,
|
||||
fromForeign: foreignToHomeConfirmations.length
|
||||
},
|
||||
foreign: {
|
||||
fromHome: homeToForeignConfirmations.length,
|
||||
toHome: foreignToHomeRequests.length
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
depositsDiff: homeToForeignRequests.length - homeToForeignConfirmations.length,
|
||||
withdrawalDiff: foreignToHomeConfirmations.length - foreignToHomeRequests.length,
|
||||
home: {
|
||||
deposits: homeToForeignRequests.length,
|
||||
withdrawals: foreignToHomeConfirmations.length
|
||||
},
|
||||
foreign: {
|
||||
deposits: homeToForeignConfirmations.length,
|
||||
withdrawals: foreignToHomeRequests.length
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ const { isV1Bridge } = require('./utils/serverUtils')
|
||||
|
||||
const app = express()
|
||||
|
||||
const MONITOR_VALIDATOR_HOME_TX_LIMIT = Number(process.env.MONITOR_VALIDATOR_HOME_TX_LIMIT) || 0
|
||||
const MONITOR_VALIDATOR_FOREIGN_TX_LIMIT = Number(process.env.MONITOR_VALIDATOR_FOREIGN_TX_LIMIT) || 0
|
||||
const MONITOR_TX_NUMBER_THRESHOLD = Number(process.env.MONITOR_TX_NUMBER_THRESHOLD) || 100
|
||||
console.log('MONITOR_TX_NUMBER_THRESHOLD = ' + MONITOR_TX_NUMBER_THRESHOLD)
|
||||
|
||||
@@ -52,18 +54,25 @@ app.get('/validators', async (req, res, next) => {
|
||||
const results = await readFile('./responses/validators.json')
|
||||
results.homeOk = true
|
||||
results.foreignOk = true
|
||||
for (const hv in results.home.validators) {
|
||||
if (results.home.validators[hv].leftTx < MONITOR_TX_NUMBER_THRESHOLD) {
|
||||
results.homeOk = false
|
||||
break
|
||||
|
||||
if (MONITOR_VALIDATOR_HOME_TX_LIMIT) {
|
||||
for (const hv in results.home.validators) {
|
||||
if (results.home.validators[hv].leftTx < MONITOR_TX_NUMBER_THRESHOLD) {
|
||||
results.homeOk = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const hv in results.foreign.validators) {
|
||||
if (results.foreign.validators[hv].leftTx < MONITOR_TX_NUMBER_THRESHOLD) {
|
||||
results.foreignOk = false
|
||||
break
|
||||
|
||||
if (MONITOR_VALIDATOR_FOREIGN_TX_LIMIT) {
|
||||
for (const hv in results.foreign.validators) {
|
||||
if (results.foreign.validators[hv].leftTx < MONITOR_TX_NUMBER_THRESHOLD) {
|
||||
results.foreignOk = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
results.ok = results.homeOk && results.foreignOk
|
||||
res.json(results)
|
||||
} catch (e) {
|
||||
@@ -77,10 +86,10 @@ app.get('/eventsStats', async (req, res, next) => {
|
||||
try {
|
||||
const results = await readFile('./responses/eventsStats.json')
|
||||
results.ok =
|
||||
results.onlyInHomeDeposits.length === 0 &&
|
||||
results.onlyInForeignDeposits.length === 0 &&
|
||||
results.onlyInHomeWithdrawals.length === 0 &&
|
||||
results.onlyInForeignWithdrawals.length === 0
|
||||
(results.onlyInHomeDeposits || results.home.deliveredMsgNotProcessedInForeign).length === 0 &&
|
||||
(results.onlyInForeignDeposits || results.home.processedMsgNotDeliveredInForeign).length === 0 &&
|
||||
(results.onlyInHomeWithdrawals || results.foreign.deliveredMsgNotProcessedInHome).length === 0 &&
|
||||
(results.onlyInForeignWithdrawals || results.foreign.processedMsgNotDeliveredInHome).length === 0
|
||||
res.json(results)
|
||||
} catch (e) {
|
||||
// this will eventually be handled by your error handling middleware
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
"start": "node index.js",
|
||||
"check-and-start": "yarn check-all && yarn start",
|
||||
"lint": "eslint . --ignore-path ../.eslintignore",
|
||||
"lint:fix": "eslint . --fix"
|
||||
"lint:fix": "eslint . --fix",
|
||||
"test": "NODE_ENV=test mocha"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
@@ -23,5 +24,6 @@
|
||||
"node": ">=8.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"chai": "^4.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
206
monitor/test/message.test.js
Normal file
206
monitor/test/message.test.js
Normal file
@@ -0,0 +1,206 @@
|
||||
const { expect } = require('chai')
|
||||
const { normalizeEventInformation, eventWithoutReference } = require('../utils/message')
|
||||
|
||||
describe('normalizeEventInformation', () => {
|
||||
it('should return normalized object for UserRequestForSignature event', () => {
|
||||
// Given
|
||||
const event = {
|
||||
address: '0x7301CFA0e1756B71869E93d4e4Dca5c7d0eb0AA6',
|
||||
blockNumber: 324231,
|
||||
transactionHash: '0x17be1e0745136b9e2857124542f8218812db8fe4458236d5ae045c1ceeb79978',
|
||||
returnValues: {
|
||||
recipient: '0xA84944735B66e957Fe385567dcc85975022Fe68A',
|
||||
value: '100000000000000000000'
|
||||
},
|
||||
event: 'UserRequestForSignature'
|
||||
}
|
||||
|
||||
// When
|
||||
const result = normalizeEventInformation(event)
|
||||
|
||||
// Then
|
||||
expect(result.transactionHash).to.equal(event.transactionHash)
|
||||
expect(result.blockNumber).to.equal(event.blockNumber)
|
||||
expect(result.referenceTx).to.equal(event.transactionHash)
|
||||
expect(result.recipient).to.equal(event.returnValues.recipient)
|
||||
expect(result.value).to.equal(event.returnValues.value)
|
||||
})
|
||||
it('should return normalized object for UserRequestForAffirmation event', () => {
|
||||
// Given
|
||||
const event = {
|
||||
address: '0x7301CFA0e1756B71869E93d4e4Dca5c7d0eb0AA6',
|
||||
blockNumber: 324231,
|
||||
transactionHash: '0x17be1e0745136b9e2857124542f8218812db8fe4458236d5ae045c1ceeb79978',
|
||||
returnValues: {
|
||||
recipient: '0xA84944735B66e957Fe385567dcc85975022Fe68A',
|
||||
value: '100000000000000000000'
|
||||
},
|
||||
event: 'UserRequestForAffirmation'
|
||||
}
|
||||
|
||||
// When
|
||||
const result = normalizeEventInformation(event)
|
||||
|
||||
// Then
|
||||
expect(result.transactionHash).to.equal(event.transactionHash)
|
||||
expect(result.blockNumber).to.equal(event.blockNumber)
|
||||
expect(result.referenceTx).to.equal(event.transactionHash)
|
||||
expect(result.recipient).to.equal(event.returnValues.recipient)
|
||||
expect(result.value).to.equal(event.returnValues.value)
|
||||
})
|
||||
it('should return normalized object for transfer event', () => {
|
||||
// Given
|
||||
const event = {
|
||||
address: '0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359',
|
||||
blockNumber: 6593953,
|
||||
transactionHash: '0x05afb402e27946d3600b100020dc23419ffd10cb61d3b241cee7b4a84909b48a',
|
||||
returnValues: {
|
||||
from: '0x13C0a8009A578837fB7A80Aa252F6A3ba4aD6B79',
|
||||
to: '0x4aa42145Aa6Ebf72e164C9bBC74fbD3788045016',
|
||||
value: '4000000000000000000'
|
||||
},
|
||||
event: 'Transfer'
|
||||
}
|
||||
|
||||
// When
|
||||
const result = normalizeEventInformation(event)
|
||||
|
||||
// Then
|
||||
expect(result.transactionHash).to.equal(event.transactionHash)
|
||||
expect(result.blockNumber).to.equal(event.blockNumber)
|
||||
expect(result.referenceTx).to.equal(event.transactionHash)
|
||||
expect(result.recipient).to.equal(event.returnValues.from)
|
||||
expect(result.value).to.equal(event.returnValues.value)
|
||||
})
|
||||
it('should return normalized object for RelayedMessage event', () => {
|
||||
// Given
|
||||
const event = {
|
||||
address: '0x4aa42145Aa6Ebf72e164C9bBC74fbD3788045016',
|
||||
blockNumber: 7025826,
|
||||
transactionHash: '0x6ee5969973da763d6d9f162d2dd1b1ec34c2dd977dc39e6b25030b4f04471567',
|
||||
returnValues: {
|
||||
recipient: '0x38BC00Ea43EbB5ef5150593A0BA6C381803717e2',
|
||||
value: '4900000000000000000',
|
||||
transactionHash: '0x5c5c2ab5e333bda4acd035a6a30ea29c7370351891d85373b2d06c7cc6cbb210'
|
||||
},
|
||||
event: 'RelayedMessage'
|
||||
}
|
||||
|
||||
// When
|
||||
const result = normalizeEventInformation(event)
|
||||
|
||||
// Then
|
||||
expect(result.transactionHash).to.equal(event.transactionHash)
|
||||
expect(result.blockNumber).to.equal(event.blockNumber)
|
||||
expect(result.referenceTx).to.equal(event.returnValues.transactionHash)
|
||||
expect(result.recipient).to.equal(event.returnValues.recipient)
|
||||
expect(result.value).to.equal(event.returnValues.value)
|
||||
})
|
||||
it('should return normalized object for AffirmationCompleted event', () => {
|
||||
// Given
|
||||
const event = {
|
||||
address: '0x7301CFA0e1756B71869E93d4e4Dca5c7d0eb0AA6',
|
||||
blockNumber: 474439,
|
||||
transactionHash: '0x654004b372ba32754cef34f403153bbdf43f0fbb3191d5e4683bba7f32e0dc4a',
|
||||
|
||||
returnValues: {
|
||||
recipient: '0x9b7b2B4f7a391b6F14A81221AE0920A9735B67Fb',
|
||||
value: '5000000000000000000',
|
||||
transactionHash: '0xe96da94bbda2cfc865acd3f98040f5c79a627ee9de839d86885d34acd8ecd10d'
|
||||
},
|
||||
event: 'AffirmationCompleted'
|
||||
}
|
||||
|
||||
// When
|
||||
const result = normalizeEventInformation(event)
|
||||
|
||||
// Then
|
||||
expect(result.transactionHash).to.equal(event.transactionHash)
|
||||
expect(result.blockNumber).to.equal(event.blockNumber)
|
||||
expect(result.referenceTx).to.equal(event.returnValues.transactionHash)
|
||||
expect(result.recipient).to.equal(event.returnValues.recipient)
|
||||
expect(result.value).to.equal(event.returnValues.value)
|
||||
})
|
||||
})
|
||||
describe('eventWithoutReference', () => {
|
||||
it('should return false if event is present', () => {
|
||||
// Given
|
||||
const event = {
|
||||
txHash: '0x17be1e0745136b9e2857124542f8218812db8fe4458236d5ae045c1ceeb79978',
|
||||
blockNumber: 474439,
|
||||
referenceTx: '0x17be1e0745136b9e2857124542f8218812db8fe4458236d5ae045c1ceeb79978',
|
||||
recipient: '0x9b7b2B4f7a391b6F14A81221AE0920A9735B67Fb',
|
||||
value: '5000000000000000000'
|
||||
}
|
||||
|
||||
const otherSideEvents = [
|
||||
{
|
||||
txHash: '0x17be1e0745136b9e2857124542f8218812db8fe4458236d5ae045c1ceeb79978',
|
||||
blockNumber: 474439,
|
||||
referenceTx: '0x17be1e0745136b9e2857124542f8218812db8fe4458236d5ae045c1ceeb79978',
|
||||
recipient: '0x9b7b2B4f7a391b6F14A81221AE0920A9735B67Fb',
|
||||
value: '5000000000000000000'
|
||||
},
|
||||
{
|
||||
txHash: '0x17be1e0745136b9e2857124542f8218812db8fe4458236d5ae045c1ceeb79978',
|
||||
blockNumber: 474439,
|
||||
referenceTx: '0x17be1e0745136b9e2857124542f8218812db8fe4458236d5ae045c1ceeb79978',
|
||||
recipient: '0x38BC00Ea43EbB5ef5150593A0BA6C381803717e2',
|
||||
value: '6000000000000000000'
|
||||
},
|
||||
{
|
||||
txHash: '0x17be1e0745136b9e2857124542f8218812db8fe4458236d5ae045c1ceeb79978',
|
||||
blockNumber: 474439,
|
||||
referenceTx: '0x17be1e0745136b9e2857124542f8218812db8fe4458236d5ae045c1ceeb79978',
|
||||
recipient: '0x38BC00Ea43EbB5ef5150593A0BA6C381803717e2',
|
||||
value: '8000000000000000000'
|
||||
}
|
||||
]
|
||||
|
||||
// When
|
||||
const result = eventWithoutReference(otherSideEvents)(event)
|
||||
|
||||
// Then
|
||||
expect(result).to.equal(false)
|
||||
})
|
||||
it('should return true if event is not present', () => {
|
||||
// Given
|
||||
const event = {
|
||||
txHash: '0xe96da94bbda2cfc865acd3f98040f5c79a627ee9de839d86885d34acd8ecd10d',
|
||||
blockNumber: 474439,
|
||||
referenceTx: '0xe96da94bbda2cfc865acd3f98040f5c79a627ee9de839d86885d34acd8ecd10d',
|
||||
recipient: '0x9b7b2B4f7a391b6F14A81221AE0920A9735B67Fb',
|
||||
value: '2000000000000000000'
|
||||
}
|
||||
|
||||
const otherSideEvents = [
|
||||
{
|
||||
txHash: '0x17be1e0745136b9e2857124542f8218812db8fe4458236d5ae045c1ceeb79978',
|
||||
blockNumber: 474439,
|
||||
referenceTx: '0x17be1e0745136b9e2857124542f8218812db8fe4458236d5ae045c1ceeb79978',
|
||||
recipient: '0x9b7b2B4f7a391b6F14A81221AE0920A9735B67Fb',
|
||||
value: '5000000000000000000'
|
||||
},
|
||||
{
|
||||
txHash: '0x05afb402e27946d3600b100020dc23419ffd10cb61d3b241cee7b4a84909b48a',
|
||||
blockNumber: 474439,
|
||||
referenceTx: '0x05afb402e27946d3600b100020dc23419ffd10cb61d3b241cee7b4a84909b48a',
|
||||
recipient: '0x38BC00Ea43EbB5ef5150593A0BA6C381803717e2',
|
||||
value: '6000000000000000000'
|
||||
},
|
||||
{
|
||||
txHash: '0x17be1e0745136b9e2857124542f8218812db8fe4458236d5ae045c1ceeb79978',
|
||||
blockNumber: 474439,
|
||||
referenceTx: '0x17be1e0745136b9e2857124542f8218812db8fe4458236d5ae045c1ceeb79978',
|
||||
recipient: '0x38BC00Ea43EbB5ef5150593A0BA6C381803717e2',
|
||||
value: '8000000000000000000'
|
||||
}
|
||||
]
|
||||
|
||||
// When
|
||||
const result = eventWithoutReference(otherSideEvents)(event)
|
||||
|
||||
// Then
|
||||
expect(result).to.equal(true)
|
||||
})
|
||||
})
|
||||
@@ -11,8 +11,11 @@ const {
|
||||
ERC20_ABI,
|
||||
ERC677_BRIDGE_TOKEN_ABI,
|
||||
getTokenType,
|
||||
getPastEvents
|
||||
getPastEvents,
|
||||
ZERO_ADDRESS
|
||||
} = require('../../commons')
|
||||
const { normalizeEventInformation } = require('./message')
|
||||
const { filterTransferBeforeES } = require('./tokenUtils')
|
||||
|
||||
const {
|
||||
COMMON_HOME_RPC_URL,
|
||||
@@ -38,61 +41,127 @@ async function main(mode) {
|
||||
const homeBridge = new web3Home.eth.Contract(HOME_ABI, COMMON_HOME_BRIDGE_ADDRESS)
|
||||
const foreignBridge = new web3Foreign.eth.Contract(FOREIGN_ABI, COMMON_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),
|
||||
COMMON_FOREIGN_BRIDGE_ADDRESS
|
||||
)
|
||||
const isExternalErc20 = tokenType === ERC_TYPES.ERC20
|
||||
const erc20Contract = new web3Foreign.eth.Contract(ERC20_ABI, erc20Address)
|
||||
let isExternalErc20
|
||||
let erc20Contract
|
||||
let erc20Address
|
||||
let normalizeEvent = normalizeEventInformation
|
||||
if (bridgeMode !== BRIDGE_MODES.ARBITRARY_MESSAGE) {
|
||||
const erc20MethodName = bridgeMode === BRIDGE_MODES.NATIVE_TO_ERC || v1Bridge ? 'erc677token' : 'erc20token'
|
||||
erc20Address = await foreignBridge.methods[erc20MethodName]().call()
|
||||
const tokenType = await getTokenType(
|
||||
new web3Foreign.eth.Contract(ERC677_BRIDGE_TOKEN_ABI, erc20Address),
|
||||
COMMON_FOREIGN_BRIDGE_ADDRESS
|
||||
)
|
||||
isExternalErc20 = tokenType === ERC_TYPES.ERC20
|
||||
erc20Contract = new web3Foreign.eth.Contract(ERC20_ABI, erc20Address)
|
||||
} else {
|
||||
normalizeEvent = e => e
|
||||
}
|
||||
|
||||
logger.debug('getting last block numbers')
|
||||
const [homeBlockNumber, foreignBlockNumber] = await getBlockNumber(web3Home, web3Foreign)
|
||||
|
||||
logger.debug("calling homeBridge.getPastEvents('UserRequestForSignature')")
|
||||
const homeDeposits = await getPastEvents(homeBridge, {
|
||||
const homeToForeignRequests = (await getPastEvents(homeBridge, {
|
||||
event: v1Bridge ? 'Deposit' : 'UserRequestForSignature',
|
||||
fromBlock: MONITOR_HOME_START_BLOCK,
|
||||
toBlock: homeBlockNumber
|
||||
})
|
||||
})).map(normalizeEvent)
|
||||
|
||||
logger.debug("calling foreignBridge.getPastEvents('RelayedMessage')")
|
||||
const foreignDeposits = await getPastEvents(foreignBridge, {
|
||||
const homeToForeignConfirmations = (await getPastEvents(foreignBridge, {
|
||||
event: v1Bridge ? 'Deposit' : 'RelayedMessage',
|
||||
fromBlock: MONITOR_FOREIGN_START_BLOCK,
|
||||
toBlock: foreignBlockNumber
|
||||
})
|
||||
})).map(normalizeEvent)
|
||||
|
||||
logger.debug("calling homeBridge.getPastEvents('AffirmationCompleted')")
|
||||
const homeWithdrawals = await getPastEvents(homeBridge, {
|
||||
const foreignToHomeConfirmations = (await getPastEvents(homeBridge, {
|
||||
event: v1Bridge ? 'Withdraw' : 'AffirmationCompleted',
|
||||
fromBlock: MONITOR_HOME_START_BLOCK,
|
||||
toBlock: homeBlockNumber
|
||||
})
|
||||
})).map(normalizeEvent)
|
||||
|
||||
logger.debug("calling foreignBridge.getPastEvents('UserRequestForAffirmation')")
|
||||
const foreignWithdrawals = isExternalErc20
|
||||
? await getPastEvents(erc20Contract, {
|
||||
event: 'Transfer',
|
||||
fromBlock: MONITOR_FOREIGN_START_BLOCK,
|
||||
toBlock: foreignBlockNumber,
|
||||
options: {
|
||||
filter: { to: COMMON_FOREIGN_BRIDGE_ADDRESS }
|
||||
}
|
||||
})
|
||||
: await getPastEvents(foreignBridge, {
|
||||
event: v1Bridge ? 'Withdraw' : 'UserRequestForAffirmation',
|
||||
let foreignToHomeRequests = (await getPastEvents(foreignBridge, {
|
||||
event: v1Bridge ? 'Withdraw' : 'UserRequestForAffirmation',
|
||||
fromBlock: MONITOR_FOREIGN_START_BLOCK,
|
||||
toBlock: foreignBlockNumber
|
||||
})).map(normalizeEvent)
|
||||
if (isExternalErc20) {
|
||||
logger.debug("calling erc20Contract.getPastEvents('Transfer')")
|
||||
let transferEvents = (await getPastEvents(erc20Contract, {
|
||||
event: 'Transfer',
|
||||
fromBlock: MONITOR_FOREIGN_START_BLOCK,
|
||||
toBlock: foreignBlockNumber,
|
||||
options: {
|
||||
filter: { to: COMMON_FOREIGN_BRIDGE_ADDRESS }
|
||||
}
|
||||
})).map(normalizeEvent)
|
||||
|
||||
let directTransfers = transferEvents
|
||||
const tokensSwappedAbiExists = FOREIGN_ABI.filter(e => e.type === 'event' && e.name === 'TokensSwapped')[0]
|
||||
if (tokensSwappedAbiExists) {
|
||||
const tokensSwappedEvents = await getPastEvents(foreignBridge, {
|
||||
event: 'TokensSwapped',
|
||||
fromBlock: MONITOR_FOREIGN_START_BLOCK,
|
||||
toBlock: foreignBlockNumber
|
||||
})
|
||||
|
||||
// Get token swap events emitted by foreign bridge
|
||||
const bridgeTokensSwappedEvents = tokensSwappedEvents.filter(e => e.address === COMMON_FOREIGN_BRIDGE_ADDRESS)
|
||||
|
||||
// Get transfer events for each previous erc20
|
||||
const uniqueTokenAddresses = [...new Set(bridgeTokensSwappedEvents.map(e => e.returnValues.from))]
|
||||
await Promise.all(
|
||||
uniqueTokenAddresses.map(async tokenAddress => {
|
||||
const halfDuplexTokenContract = new web3Foreign.eth.Contract(ERC20_ABI, tokenAddress)
|
||||
|
||||
const halfDuplexTransferEvents = (await getPastEvents(halfDuplexTokenContract, {
|
||||
event: 'Transfer',
|
||||
fromBlock: MONITOR_FOREIGN_START_BLOCK,
|
||||
toBlock: foreignBlockNumber,
|
||||
options: {
|
||||
filter: { to: COMMON_FOREIGN_BRIDGE_ADDRESS }
|
||||
}
|
||||
})).map(normalizeEvent)
|
||||
|
||||
// Remove events after the ES
|
||||
const validHalfDuplexTransfers = await filterTransferBeforeES(
|
||||
halfDuplexTransferEvents,
|
||||
web3Foreign,
|
||||
foreignBridge
|
||||
)
|
||||
|
||||
transferEvents = [...validHalfDuplexTransfers, ...transferEvents]
|
||||
})
|
||||
)
|
||||
|
||||
// filter transfer that is part of a token swap
|
||||
directTransfers = transferEvents.filter(
|
||||
e =>
|
||||
bridgeTokensSwappedEvents.findIndex(
|
||||
t => t.transactionHash === e.referenceTx && e.recipient === ZERO_ADDRESS
|
||||
) === -1
|
||||
)
|
||||
}
|
||||
|
||||
// Get transfer events that didn't have a UserRequestForAffirmation event in the same transaction
|
||||
directTransfers = directTransfers.filter(
|
||||
e => foreignToHomeRequests.findIndex(t => t.referenceTx === e.referenceTx) === -1
|
||||
)
|
||||
|
||||
foreignToHomeRequests = [...foreignToHomeRequests, ...directTransfers]
|
||||
}
|
||||
|
||||
logger.debug('Done')
|
||||
return {
|
||||
homeDeposits,
|
||||
foreignDeposits,
|
||||
homeWithdrawals,
|
||||
foreignWithdrawals,
|
||||
isExternalErc20
|
||||
homeToForeignRequests,
|
||||
homeToForeignConfirmations,
|
||||
foreignToHomeConfirmations,
|
||||
foreignToHomeRequests,
|
||||
isExternalErc20,
|
||||
bridgeMode
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
72
monitor/utils/message.js
Normal file
72
monitor/utils/message.js
Normal file
@@ -0,0 +1,72 @@
|
||||
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
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes the different event objects to facilitate data processing
|
||||
* @param {Object} event
|
||||
* @returns {{
|
||||
* transactionHash: string,
|
||||
* blockNumber: number,
|
||||
* referenceTx: string,
|
||||
* recipient: string | *,
|
||||
* value: *
|
||||
* }}
|
||||
*/
|
||||
const normalizeEventInformation = event => ({
|
||||
transactionHash: event.transactionHash,
|
||||
blockNumber: event.blockNumber,
|
||||
referenceTx: event.returnValues.transactionHash || event.transactionHash,
|
||||
recipient: event.returnValues.recipient || event.returnValues.from,
|
||||
value: event.returnValues.value
|
||||
})
|
||||
|
||||
const eventWithoutReference = otherSideEvents => e =>
|
||||
otherSideEvents.filter(a => a.referenceTx === e.referenceTx && a.recipient === e.recipient && a.value === e.value)
|
||||
.length === 0
|
||||
|
||||
module.exports = {
|
||||
deliveredMsgNotProcessed,
|
||||
processedMsgNotDelivered,
|
||||
normalizeEventInformation,
|
||||
eventWithoutReference
|
||||
}
|
||||
40
monitor/utils/tokenUtils.js
Normal file
40
monitor/utils/tokenUtils.js
Normal file
@@ -0,0 +1,40 @@
|
||||
let beforeESBiggestBlockNumber = 0
|
||||
|
||||
/**
|
||||
*
|
||||
* Returns true if the event was before the Emergency Shutdown.
|
||||
* The method has an optimization to avoid making request if a bigger block number is confirmed
|
||||
* to be before the ES. Events should be iterated from newer to older order to use the optimization.
|
||||
*/
|
||||
async function transferBeforeES(event, web3Foreign, foreignBridge) {
|
||||
const { blockNumber } = event
|
||||
|
||||
if (blockNumber < beforeESBiggestBlockNumber) {
|
||||
return true
|
||||
}
|
||||
|
||||
const block = await web3Foreign.eth.getBlock(blockNumber)
|
||||
|
||||
const tokenSwapAllowed = await foreignBridge.methods.isTokenSwapAllowed(block.timestamp).call()
|
||||
if (tokenSwapAllowed) {
|
||||
beforeESBiggestBlockNumber = blockNumber
|
||||
}
|
||||
return tokenSwapAllowed
|
||||
}
|
||||
|
||||
async function filterTransferBeforeES(array, web3Foreign, foreignBridge) {
|
||||
const newArray = []
|
||||
// Iterate events from newer to older
|
||||
for (let i = array.length - 1; i >= 0; i--) {
|
||||
const beforeES = await transferBeforeES(array[i], web3Foreign, foreignBridge)
|
||||
if (beforeES) {
|
||||
// add element to first position so the new array will have the same order
|
||||
newArray.unshift(array[i])
|
||||
}
|
||||
}
|
||||
return newArray
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
filterTransferBeforeES
|
||||
}
|
||||
@@ -10,12 +10,10 @@ const {
|
||||
COMMON_FOREIGN_RPC_URL,
|
||||
COMMON_HOME_BRIDGE_ADDRESS,
|
||||
COMMON_FOREIGN_BRIDGE_ADDRESS,
|
||||
MONITOR_VALIDATOR_HOME_TX_LIMIT,
|
||||
COMMON_HOME_GAS_PRICE_SUPPLIER_URL,
|
||||
COMMON_HOME_GAS_PRICE_SPEED_TYPE,
|
||||
COMMON_HOME_GAS_PRICE_FALLBACK,
|
||||
COMMON_HOME_GAS_PRICE_FACTOR,
|
||||
MONITOR_VALIDATOR_FOREIGN_TX_LIMIT,
|
||||
COMMON_FOREIGN_GAS_PRICE_SUPPLIER_URL,
|
||||
COMMON_FOREIGN_GAS_PRICE_SPEED_TYPE,
|
||||
COMMON_FOREIGN_GAS_PRICE_FALLBACK,
|
||||
@@ -23,6 +21,8 @@ const {
|
||||
} = process.env
|
||||
const MONITOR_HOME_START_BLOCK = Number(process.env.MONITOR_HOME_START_BLOCK) || 0
|
||||
const MONITOR_FOREIGN_START_BLOCK = Number(process.env.MONITOR_FOREIGN_START_BLOCK) || 0
|
||||
const MONITOR_VALIDATOR_HOME_TX_LIMIT = Number(process.env.MONITOR_VALIDATOR_HOME_TX_LIMIT) || 0
|
||||
const MONITOR_VALIDATOR_FOREIGN_TX_LIMIT = Number(process.env.MONITOR_VALIDATOR_FOREIGN_TX_LIMIT) || 0
|
||||
|
||||
const Web3Utils = Web3.utils
|
||||
|
||||
@@ -65,76 +65,97 @@ async function main(bridgeMode) {
|
||||
const foreignBridgeValidators = new web3Foreign.eth.Contract(BRIDGE_VALIDATORS_ABI, foreignValidatorsAddress)
|
||||
|
||||
logger.debug('calling foreignBridgeValidators getValidatorList()')
|
||||
const foreignValidators = await getValidatorList(foreignValidatorsAddress, web3Foreign.eth, {
|
||||
const foreignValidators = (await getValidatorList(foreignValidatorsAddress, web3Foreign.eth, {
|
||||
from: MONITOR_FOREIGN_START_BLOCK,
|
||||
to: foreignBlockNumber,
|
||||
logger
|
||||
})
|
||||
})).map(web3Foreign.utils.toChecksumAddress)
|
||||
|
||||
logger.debug('calling homeBridgeValidators getValidatorList()')
|
||||
const homeValidators = await getValidatorList(homeValidatorsAddress, web3Home.eth, {
|
||||
const homeValidators = (await getValidatorList(homeValidatorsAddress, web3Home.eth, {
|
||||
from: MONITOR_HOME_START_BLOCK,
|
||||
to: homeBlockNumber,
|
||||
logger
|
||||
})
|
||||
})).map(web3Home.utils.toChecksumAddress)
|
||||
|
||||
const homeBalances = {}
|
||||
logger.debug('calling asyncForEach homeValidators homeBalances')
|
||||
await asyncForEach(homeValidators, async v => {
|
||||
homeBalances[v] = Web3Utils.fromWei(await web3Home.eth.getBalance(v))
|
||||
})
|
||||
const foreignVBalances = {}
|
||||
const homeVBalances = {}
|
||||
|
||||
logger.debug('calling home getGasPrices')
|
||||
const homeGasPrice =
|
||||
(await gasPriceFromSupplier(() => fetch(COMMON_HOME_GAS_PRICE_SUPPLIER_URL), homeGasPriceSupplierOpts)) ||
|
||||
Web3Utils.toBN(COMMON_HOME_GAS_PRICE_FALLBACK)
|
||||
const homeGasPriceGwei = Web3Utils.fromWei(homeGasPrice.toString(), 'gwei')
|
||||
const homeTxCost = homeGasPrice.mul(Web3Utils.toBN(MONITOR_VALIDATOR_HOME_TX_LIMIT))
|
||||
let homeGasPrice
|
||||
let homeGasPriceGwei
|
||||
let homeTxCost
|
||||
|
||||
logger.debug('calling foreign getGasPrices')
|
||||
const foreignGasPrice =
|
||||
(await gasPriceFromSupplier(() => fetch(COMMON_FOREIGN_GAS_PRICE_SUPPLIER_URL), foreignGasPriceSupplierOpts)) ||
|
||||
Web3Utils.toBN(COMMON_FOREIGN_GAS_PRICE_FALLBACK)
|
||||
const foreignGasPriceGwei = Web3Utils.fromWei(foreignGasPrice.toString(), 'gwei')
|
||||
const foreignTxCost = foreignGasPrice.mul(Web3Utils.toBN(MONITOR_VALIDATOR_FOREIGN_TX_LIMIT))
|
||||
if (MONITOR_VALIDATOR_HOME_TX_LIMIT) {
|
||||
logger.debug('calling home getGasPrices')
|
||||
homeGasPrice =
|
||||
(await gasPriceFromSupplier(() => fetch(COMMON_HOME_GAS_PRICE_SUPPLIER_URL), homeGasPriceSupplierOpts)) ||
|
||||
Web3Utils.toBN(COMMON_HOME_GAS_PRICE_FALLBACK)
|
||||
homeGasPriceGwei = Web3Utils.fromWei(homeGasPrice.toString(), 'gwei')
|
||||
homeTxCost = homeGasPrice.mul(Web3Utils.toBN(MONITOR_VALIDATOR_HOME_TX_LIMIT))
|
||||
}
|
||||
|
||||
let foreignGasPrice
|
||||
let foreignGasPriceGwei
|
||||
let foreignTxCost
|
||||
|
||||
if (MONITOR_VALIDATOR_FOREIGN_TX_LIMIT) {
|
||||
logger.debug('calling foreign getGasPrices')
|
||||
foreignGasPrice =
|
||||
(await gasPriceFromSupplier(() => fetch(COMMON_FOREIGN_GAS_PRICE_SUPPLIER_URL), foreignGasPriceSupplierOpts)) ||
|
||||
Web3Utils.toBN(COMMON_FOREIGN_GAS_PRICE_FALLBACK)
|
||||
foreignGasPriceGwei = Web3Utils.fromWei(foreignGasPrice.toString(), 'gwei')
|
||||
foreignTxCost = foreignGasPrice.mul(Web3Utils.toBN(MONITOR_VALIDATOR_FOREIGN_TX_LIMIT))
|
||||
}
|
||||
|
||||
let validatorsMatch = true
|
||||
logger.debug('calling asyncForEach foreignValidators foreignVBalances')
|
||||
await asyncForEach(foreignValidators, async v => {
|
||||
const balance = await web3Foreign.eth.getBalance(v)
|
||||
const leftTx = Web3Utils.toBN(balance)
|
||||
.div(foreignTxCost)
|
||||
.toString(10)
|
||||
foreignVBalances[v] = {
|
||||
balance: Web3Utils.fromWei(balance),
|
||||
leftTx: Number(leftTx),
|
||||
gasPrice: Number(foreignGasPriceGwei)
|
||||
if (MONITOR_VALIDATOR_FOREIGN_TX_LIMIT) {
|
||||
const leftTx = Web3Utils.toBN(balance)
|
||||
.div(foreignTxCost)
|
||||
.toString(10)
|
||||
foreignVBalances[v] = {
|
||||
balance: Web3Utils.fromWei(balance),
|
||||
leftTx: Number(leftTx),
|
||||
gasPrice: Number(foreignGasPriceGwei)
|
||||
}
|
||||
} else {
|
||||
foreignVBalances[v] = {
|
||||
balance: Web3Utils.fromWei(balance)
|
||||
}
|
||||
}
|
||||
|
||||
if (!homeValidators.includes(v)) {
|
||||
validatorsMatch = false
|
||||
foreignVBalances[v].onlyOnForeign = true
|
||||
}
|
||||
})
|
||||
|
||||
logger.debug('calling asyncForEach homeValidators homeVBalances')
|
||||
await asyncForEach(homeValidators, async v => {
|
||||
const balance = await web3Home.eth.getBalance(v)
|
||||
const leftTx = homeTxCost.isZero()
|
||||
? 999999
|
||||
: Web3Utils.toBN(balance)
|
||||
.div(homeTxCost)
|
||||
.toString(10)
|
||||
homeVBalances[v] = {
|
||||
balance: Web3Utils.fromWei(balance),
|
||||
leftTx: Number(leftTx),
|
||||
gasPrice: Number(homeGasPriceGwei)
|
||||
if (MONITOR_VALIDATOR_HOME_TX_LIMIT) {
|
||||
const leftTx = Web3Utils.toBN(balance)
|
||||
.div(homeTxCost)
|
||||
.toString(10)
|
||||
homeVBalances[v] = {
|
||||
balance: Web3Utils.fromWei(balance),
|
||||
leftTx: Number(leftTx),
|
||||
gasPrice: Number(homeGasPriceGwei)
|
||||
}
|
||||
} else {
|
||||
homeVBalances[v] = {
|
||||
balance: Web3Utils.fromWei(balance)
|
||||
}
|
||||
}
|
||||
|
||||
if (!foreignValidators.includes(v)) {
|
||||
validatorsMatch = false
|
||||
homeVBalances[v].onlyOnHome = true
|
||||
}
|
||||
})
|
||||
|
||||
logger.debug('calling homeBridgeValidators.methods.requiredSignatures().call()')
|
||||
const reqSigHome = await homeBridgeValidators.methods.requiredSignatures().call()
|
||||
logger.debug('calling foreignBridgeValidators.methods.requiredSignatures().call()')
|
||||
|
||||
102
oracle-e2e/test/amb.js
Normal file
102
oracle-e2e/test/amb.js
Normal file
@@ -0,0 +1,102 @@
|
||||
const Web3 = require('web3')
|
||||
const assert = require('assert')
|
||||
const promiseRetry = require('promise-retry')
|
||||
const { user, homeRPC, foreignRPC, amb } = require('../../e2e-commons/constants.json')
|
||||
const { generateNewBlock } = require('../../e2e-commons/utils')
|
||||
const { 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)
|
||||
|
||||
const homeBox = new homeWeb3.eth.Contract(BOX_ABI, amb.homeBox)
|
||||
const foreignBox = new foreignWeb3.eth.Contract(BOX_ABI, amb.foreignBox)
|
||||
|
||||
describe('arbitrary message bridging', () => {
|
||||
describe('Home to Foreign', () => {
|
||||
describe('Subsidized Mode', () => {
|
||||
it('should bridge message', async () => {
|
||||
const newValue = 3
|
||||
|
||||
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
|
||||
.setValueOnOtherNetwork(newValue, amb.home, amb.foreignBox)
|
||||
.send({
|
||||
from: user.address,
|
||||
gas: '400000'
|
||||
})
|
||||
.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('Subsidized Mode', () => {
|
||||
it('should bridge message', async () => {
|
||||
const newValue = 7
|
||||
|
||||
const initialValue = await homeBox.methods.value().call()
|
||||
assert(!toBN(initialValue).eq(toBN(newValue)), 'initial value should be different from new value')
|
||||
|
||||
await foreignBox.methods
|
||||
.setValueOnOtherNetwork(newValue, amb.foreign, amb.homeBox)
|
||||
.send({
|
||||
from: user.address,
|
||||
gas: '400000'
|
||||
})
|
||||
.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()
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,8 +1,8 @@
|
||||
const Web3 = require('web3')
|
||||
const assert = require('assert')
|
||||
const promiseRetry = require('promise-retry')
|
||||
const { user, ercToErcBridge, homeRPC, foreignRPC } = require('../../e2e-commons/constants.json')
|
||||
const { ERC677_BRIDGE_TOKEN_ABI } = require('../../commons')
|
||||
const { user, secondUser, ercToErcBridge, homeRPC, foreignRPC } = require('../../e2e-commons/constants.json')
|
||||
const { ERC677_BRIDGE_TOKEN_ABI, FOREIGN_ERC_TO_NATIVE_ABI } = require('../../commons')
|
||||
const { generateNewBlock } = require('../../e2e-commons/utils')
|
||||
|
||||
const homeWeb3 = new Web3(new Web3.providers.HttpProvider(homeRPC.URL))
|
||||
@@ -17,6 +17,7 @@ homeWeb3.eth.accounts.wallet.add(user.privateKey)
|
||||
foreignWeb3.eth.accounts.wallet.add(user.privateKey)
|
||||
|
||||
const erc20Token = new foreignWeb3.eth.Contract(ERC677_BRIDGE_TOKEN_ABI, ercToErcBridge.foreignToken)
|
||||
const foreignBridge = new foreignWeb3.eth.Contract(FOREIGN_ERC_TO_NATIVE_ABI, COMMON_FOREIGN_BRIDGE_ADDRESS)
|
||||
const erc677Token = new homeWeb3.eth.Contract(ERC677_BRIDGE_TOKEN_ABI, ercToErcBridge.homeToken)
|
||||
|
||||
describe('erc to erc', () => {
|
||||
@@ -24,9 +25,49 @@ describe('erc to erc', () => {
|
||||
const balance = await erc20Token.methods.balanceOf(user.address).call()
|
||||
assert(!toBN(balance).isZero(), 'Account should have tokens')
|
||||
|
||||
const firstTransferValue = homeWeb3.utils.toWei('0.01')
|
||||
|
||||
// approve tokens to foreign bridge
|
||||
await erc20Token.methods
|
||||
.approve(COMMON_FOREIGN_BRIDGE_ADDRESS, firstTransferValue)
|
||||
.send({
|
||||
from: user.address,
|
||||
gas: '1000000'
|
||||
})
|
||||
.catch(e => {
|
||||
console.error(e)
|
||||
})
|
||||
|
||||
// call bridge method to transfer tokens to a different recipient
|
||||
await foreignBridge.methods
|
||||
.relayTokens(secondUser.address, firstTransferValue)
|
||||
.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 balance increases
|
||||
await promiseRetry(async retry => {
|
||||
const balance = await erc677Token.methods.balanceOf(user.address).call()
|
||||
const recipientBalance = await erc677Token.methods.balanceOf(secondUser.address).call()
|
||||
assert(toBN(balance).isZero(), 'User balance should be the same')
|
||||
if (toBN(recipientBalance).isZero()) {
|
||||
retry()
|
||||
}
|
||||
})
|
||||
|
||||
const secondTransferValue = homeWeb3.utils.toWei('0.05')
|
||||
|
||||
// send tokens to foreign bridge
|
||||
await erc20Token.methods
|
||||
.transfer(COMMON_FOREIGN_BRIDGE_ADDRESS, homeWeb3.utils.toWei('0.01'))
|
||||
.transfer(COMMON_FOREIGN_BRIDGE_ADDRESS, secondTransferValue)
|
||||
.send({
|
||||
from: user.address,
|
||||
gas: '1000000'
|
||||
@@ -44,6 +85,8 @@ describe('erc to erc', () => {
|
||||
const balance = await erc677Token.methods.balanceOf(user.address).call()
|
||||
if (toBN(balance).isZero()) {
|
||||
retry()
|
||||
} else {
|
||||
assert(toBN(balance).eq(toBN(secondTransferValue)), 'User balance should be increased only by second transfer')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
const Web3 = require('web3')
|
||||
const assert = require('assert')
|
||||
const promiseRetry = require('promise-retry')
|
||||
const { user, ercToNativeBridge, homeRPC, foreignRPC } = require('../../e2e-commons/constants.json')
|
||||
const { ERC677_BRIDGE_TOKEN_ABI } = require('../../commons')
|
||||
const {
|
||||
user,
|
||||
secondUser,
|
||||
validator,
|
||||
ercToNativeBridge,
|
||||
homeRPC,
|
||||
foreignRPC
|
||||
} = require('../../e2e-commons/constants.json')
|
||||
const { ERC677_BRIDGE_TOKEN_ABI, FOREIGN_ERC_TO_NATIVE_ABI, SAI_TOP } = require('../../commons')
|
||||
const { generateNewBlock } = require('../../e2e-commons/utils')
|
||||
|
||||
const homeWeb3 = new Web3(new Web3.providers.HttpProvider(homeRPC.URL))
|
||||
@@ -15,18 +22,181 @@ const { toBN } = foreignWeb3.utils
|
||||
|
||||
homeWeb3.eth.accounts.wallet.add(user.privateKey)
|
||||
foreignWeb3.eth.accounts.wallet.add(user.privateKey)
|
||||
foreignWeb3.eth.accounts.wallet.add(validator.privateKey)
|
||||
|
||||
const erc20Token = new foreignWeb3.eth.Contract(ERC677_BRIDGE_TOKEN_ABI, ercToNativeBridge.foreignToken)
|
||||
const foreignBridge = new foreignWeb3.eth.Contract(FOREIGN_ERC_TO_NATIVE_ABI, COMMON_FOREIGN_BRIDGE_ADDRESS)
|
||||
|
||||
describe('erc to native', () => {
|
||||
it('should convert tokens in foreign to coins in home', async () => {
|
||||
const balance = await erc20Token.methods.balanceOf(user.address).call()
|
||||
let halfDuplexTokenAddress
|
||||
let halfDuplexToken
|
||||
before(async () => {
|
||||
halfDuplexTokenAddress = await foreignBridge.methods.halfDuplexErc20token().call()
|
||||
halfDuplexToken = new foreignWeb3.eth.Contract(ERC677_BRIDGE_TOKEN_ABI, halfDuplexTokenAddress)
|
||||
})
|
||||
it('should continue working after migration', async () => {
|
||||
const originalBalanceOnHome = await homeWeb3.eth.getBalance(user.address)
|
||||
assert(!toBN(balance).isZero(), 'Account should have tokens')
|
||||
|
||||
const transferValue = homeWeb3.utils.toWei('0.01')
|
||||
|
||||
// erc20 token address and half duplex address are the same before migration
|
||||
const tokenAddress = await foreignBridge.methods.erc20token().call()
|
||||
|
||||
const erc20AndhalfDuplexToken = new foreignWeb3.eth.Contract(ERC677_BRIDGE_TOKEN_ABI, tokenAddress)
|
||||
|
||||
// send tokens to foreign bridge
|
||||
await erc20AndhalfDuplexToken.methods
|
||||
.transfer(COMMON_FOREIGN_BRIDGE_ADDRESS, transferValue)
|
||||
.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 balance increases
|
||||
await promiseRetry(async (retry, number) => {
|
||||
const balance = await homeWeb3.eth.getBalance(user.address)
|
||||
await generateNewBlock(foreignWeb3, user.address)
|
||||
// retry at least 4 times to check transfer is not double processed by the two watchers
|
||||
if (toBN(balance).lte(toBN(originalBalanceOnHome)) || number < 4) {
|
||||
retry()
|
||||
} else {
|
||||
assert(
|
||||
toBN(balance).eq(toBN(originalBalanceOnHome).add(toBN(transferValue))),
|
||||
'User balance should be increased only by second transfer'
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
// call migration
|
||||
await foreignBridge.methods.migrateToMCD().send({
|
||||
from: validator.address,
|
||||
gas: '4000000'
|
||||
})
|
||||
|
||||
// update min threshold for swap
|
||||
await foreignBridge.methods.setMinHDTokenBalance(foreignWeb3.utils.toWei('1', 'ether')).send({
|
||||
from: validator.address,
|
||||
gas: '1000000'
|
||||
})
|
||||
|
||||
const AfterMigrateBalance = await homeWeb3.eth.getBalance(user.address)
|
||||
|
||||
// send tokens to foreign bridge
|
||||
await erc20Token.methods
|
||||
.transfer(COMMON_FOREIGN_BRIDGE_ADDRESS, homeWeb3.utils.toWei('0.01'))
|
||||
.transfer(COMMON_FOREIGN_BRIDGE_ADDRESS, transferValue)
|
||||
.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 balance increases
|
||||
await promiseRetry(async (retry, number) => {
|
||||
const balance = await homeWeb3.eth.getBalance(user.address)
|
||||
await generateNewBlock(foreignWeb3, user.address)
|
||||
// retry at least 4 times to check transfer is not double processed by the two watchers
|
||||
if (toBN(balance).lte(toBN(AfterMigrateBalance)) || number < 4) {
|
||||
retry()
|
||||
} else {
|
||||
assert(
|
||||
toBN(balance).eq(toBN(AfterMigrateBalance).add(toBN(transferValue))),
|
||||
'User balance should be increased only by second transfer'
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
const afterMigrateAndTransferBalance = await homeWeb3.eth.getBalance(user.address)
|
||||
|
||||
// send tokens to foreign bridge
|
||||
await halfDuplexToken.methods
|
||||
.transfer(COMMON_FOREIGN_BRIDGE_ADDRESS, transferValue)
|
||||
.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 balance increases
|
||||
await promiseRetry(async (retry, number) => {
|
||||
const balance = await homeWeb3.eth.getBalance(user.address)
|
||||
await generateNewBlock(foreignWeb3, user.address)
|
||||
// retry at least 4 times to check transfer is not double processed by the two watchers
|
||||
if (toBN(balance).lte(toBN(afterMigrateAndTransferBalance)) || number < 4) {
|
||||
retry()
|
||||
} else {
|
||||
assert(
|
||||
toBN(balance).eq(toBN(afterMigrateAndTransferBalance).add(toBN(transferValue))),
|
||||
'User balance should be increased only by second transfer'
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
it('should convert tokens in foreign to coins in home', async () => {
|
||||
const balance = await erc20Token.methods.balanceOf(user.address).call()
|
||||
const originalBalanceOnHome = await homeWeb3.eth.getBalance(user.address)
|
||||
const initialBalanceSecondUser = await homeWeb3.eth.getBalance(secondUser.address)
|
||||
assert(!toBN(balance).isZero(), 'Account should have tokens')
|
||||
|
||||
// approve tokens to foreign bridge
|
||||
await erc20Token.methods
|
||||
.approve(COMMON_FOREIGN_BRIDGE_ADDRESS, homeWeb3.utils.toWei('0.01'))
|
||||
.send({
|
||||
from: user.address,
|
||||
gas: '1000000'
|
||||
})
|
||||
.catch(e => {
|
||||
console.error(e)
|
||||
})
|
||||
|
||||
// call bridge method to transfer tokens to a different recipient
|
||||
await foreignBridge.methods
|
||||
.relayTokens(secondUser.address, homeWeb3.utils.toWei('0.01'))
|
||||
.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 balance increases
|
||||
await promiseRetry(async retry => {
|
||||
const balance = await homeWeb3.eth.getBalance(user.address)
|
||||
const secondUserbalance = await homeWeb3.eth.getBalance(secondUser.address)
|
||||
assert(toBN(balance).lte(toBN(originalBalanceOnHome)), 'User balance should be the same')
|
||||
if (toBN(secondUserbalance).lte(toBN(initialBalanceSecondUser))) {
|
||||
retry()
|
||||
}
|
||||
})
|
||||
|
||||
const transferValue = homeWeb3.utils.toWei('0.05')
|
||||
|
||||
// send tokens to foreign bridge
|
||||
await erc20Token.methods
|
||||
.transfer(COMMON_FOREIGN_BRIDGE_ADDRESS, transferValue)
|
||||
.send({
|
||||
from: user.address,
|
||||
gas: '1000000'
|
||||
@@ -44,6 +214,243 @@ describe('erc to native', () => {
|
||||
const balance = await homeWeb3.eth.getBalance(user.address)
|
||||
if (toBN(balance).lte(toBN(originalBalanceOnHome))) {
|
||||
retry()
|
||||
} else {
|
||||
assert(
|
||||
toBN(balance).eq(toBN(originalBalanceOnHome).add(toBN(transferValue))),
|
||||
'User balance should be increased only by second transfer'
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
it('should convert half duplex token in foreign to native token in home', async () => {
|
||||
const originalBalanceOnHome = await homeWeb3.eth.getBalance(user.address)
|
||||
const bridgeErc20TokenBalance = await erc20Token.methods.balanceOf(COMMON_FOREIGN_BRIDGE_ADDRESS).call()
|
||||
const bridgeHalfDuplexBalance = await halfDuplexToken.methods.balanceOf(COMMON_FOREIGN_BRIDGE_ADDRESS).call()
|
||||
|
||||
const valueToTransfer = foreignWeb3.utils.toWei('1', 'ether')
|
||||
|
||||
// this transfer won't trigger a call to swap tokens
|
||||
await halfDuplexToken.methods.transfer(COMMON_FOREIGN_BRIDGE_ADDRESS, valueToTransfer).send({
|
||||
from: user.address,
|
||||
gas: '1000000'
|
||||
})
|
||||
|
||||
// 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 balance increases
|
||||
await promiseRetry(async retry => {
|
||||
const balance = await homeWeb3.eth.getBalance(user.address)
|
||||
if (toBN(balance).lte(toBN(originalBalanceOnHome))) {
|
||||
retry()
|
||||
} else {
|
||||
assert(
|
||||
toBN(balance).eq(toBN(originalBalanceOnHome).add(toBN(valueToTransfer))),
|
||||
'User balance should be increased by the half duplex token transfer'
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
const updatedBalanceOnHome = await homeWeb3.eth.getBalance(user.address)
|
||||
const updatedBridgeHalfDuplexBalance = await halfDuplexToken.methods.balanceOf(COMMON_FOREIGN_BRIDGE_ADDRESS).call()
|
||||
assert(
|
||||
toBN(updatedBridgeHalfDuplexBalance).eq(toBN(bridgeHalfDuplexBalance).add(toBN(valueToTransfer))),
|
||||
'Bridge balance should reflect the transfer value'
|
||||
)
|
||||
|
||||
// this transfer will trigger call to swap tokens
|
||||
await halfDuplexToken.methods.transfer(COMMON_FOREIGN_BRIDGE_ADDRESS, valueToTransfer).send({
|
||||
from: user.address,
|
||||
gas: '1000000'
|
||||
})
|
||||
|
||||
await generateNewBlock(foreignWeb3, user.address)
|
||||
|
||||
await promiseRetry(async retry => {
|
||||
const userBalance = await homeWeb3.eth.getBalance(user.address)
|
||||
const updatedBridgeErc20TokenBalance = await erc20Token.methods.balanceOf(COMMON_FOREIGN_BRIDGE_ADDRESS).call()
|
||||
|
||||
if (
|
||||
toBN(userBalance).lte(toBN(updatedBalanceOnHome)) ||
|
||||
toBN(updatedBridgeErc20TokenBalance).lte(toBN(bridgeErc20TokenBalance))
|
||||
) {
|
||||
retry()
|
||||
} else {
|
||||
assert(
|
||||
toBN(userBalance).eq(toBN(updatedBalanceOnHome).add(toBN(valueToTransfer))),
|
||||
'User balance should be increased by the half duplex token transfer'
|
||||
)
|
||||
const updatedBalance = await halfDuplexToken.methods.balanceOf(COMMON_FOREIGN_BRIDGE_ADDRESS).call()
|
||||
assert(toBN(updatedBalance).isZero(), 'Half duplex bridge balance should be zero')
|
||||
assert(
|
||||
toBN(updatedBridgeErc20TokenBalance).eq(
|
||||
toBN(bridgeErc20TokenBalance)
|
||||
.add(toBN(bridgeHalfDuplexBalance))
|
||||
.add(toBN(foreignWeb3.utils.toWei('2', 'ether')))
|
||||
),
|
||||
'Erc20 token balance should be correctly increased by the token swap'
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
it('should convert half duplex token in foreign to native token in home for alternative receiver ', async () => {
|
||||
const originalBalanceOnHome = await homeWeb3.eth.getBalance(user.address)
|
||||
const initialBalanceSecondUser = await homeWeb3.eth.getBalance(secondUser.address)
|
||||
const bridgeErc20TokenBalance = await erc20Token.methods.balanceOf(COMMON_FOREIGN_BRIDGE_ADDRESS).call()
|
||||
|
||||
const valueToTransfer = foreignWeb3.utils.toWei('1', 'ether')
|
||||
|
||||
// approve tokens to foreign bridge
|
||||
await halfDuplexToken.methods
|
||||
.approve(COMMON_FOREIGN_BRIDGE_ADDRESS, valueToTransfer)
|
||||
.send({
|
||||
from: user.address,
|
||||
gas: '1000000'
|
||||
})
|
||||
.catch(e => {
|
||||
console.error(e)
|
||||
})
|
||||
|
||||
// call bridge method to transfer tokens to a different recipient
|
||||
await foreignBridge.methods['relayTokens(address,uint256,address)'](
|
||||
secondUser.address,
|
||||
valueToTransfer,
|
||||
halfDuplexTokenAddress
|
||||
)
|
||||
.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 balance increases
|
||||
await promiseRetry(async retry => {
|
||||
const secondUserbalance = await homeWeb3.eth.getBalance(secondUser.address)
|
||||
const updatedBridgeErc20TokenBalance = await erc20Token.methods.balanceOf(COMMON_FOREIGN_BRIDGE_ADDRESS).call()
|
||||
const userbalance = await homeWeb3.eth.getBalance(user.address)
|
||||
assert(toBN(userbalance).lte(toBN(originalBalanceOnHome)), 'User balance should be the same')
|
||||
if (
|
||||
toBN(secondUserbalance).lte(toBN(initialBalanceSecondUser)) ||
|
||||
toBN(updatedBridgeErc20TokenBalance).lte(toBN(bridgeErc20TokenBalance))
|
||||
) {
|
||||
retry()
|
||||
} else {
|
||||
assert(
|
||||
toBN(secondUserbalance).eq(toBN(initialBalanceSecondUser).add(toBN(valueToTransfer))),
|
||||
'User balance should be increased by the half duplex token transfer'
|
||||
)
|
||||
const updatedHDBalance = await halfDuplexToken.methods.balanceOf(COMMON_FOREIGN_BRIDGE_ADDRESS).call()
|
||||
assert(toBN(updatedHDBalance).isZero(), 'Half duplex bridge balance should be zero')
|
||||
assert(
|
||||
toBN(updatedBridgeErc20TokenBalance).eq(toBN(bridgeErc20TokenBalance).add(toBN(valueToTransfer))),
|
||||
'Erc20 token balance should be correctly increased by the token swap'
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
it('should not relay half duplex token transfer after Emergency Shutdown', async () => {
|
||||
const originalBalanceOnHome = await homeWeb3.eth.getBalance(user.address)
|
||||
const bridgeErc20TokenBalance = await erc20Token.methods.balanceOf(COMMON_FOREIGN_BRIDGE_ADDRESS).call()
|
||||
const bridgeHalfDuplexBalance = await halfDuplexToken.methods.balanceOf(COMMON_FOREIGN_BRIDGE_ADDRESS).call()
|
||||
|
||||
const block = await foreignWeb3.eth.getBlock('latest')
|
||||
const saiTop = new foreignWeb3.eth.Contract(SAI_TOP, ercToNativeBridge.saiTop)
|
||||
|
||||
// Trigger Emergency Shutdown
|
||||
await saiTop.methods
|
||||
.setCaged(block.timestamp)
|
||||
.send({
|
||||
from: user.address,
|
||||
gas: '1000000'
|
||||
})
|
||||
.catch(e => {
|
||||
console.error(e)
|
||||
})
|
||||
|
||||
const valueToTransfer = foreignWeb3.utils.toWei('1', 'ether')
|
||||
|
||||
await generateNewBlock(foreignWeb3, user.address)
|
||||
|
||||
// this transfer won't trigger a call to swap tokens
|
||||
await halfDuplexToken.methods.transfer(COMMON_FOREIGN_BRIDGE_ADDRESS, valueToTransfer).send({
|
||||
from: user.address,
|
||||
gas: '1000000'
|
||||
})
|
||||
|
||||
// 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 transfer and swap are not processed in the next blocks.
|
||||
await promiseRetry(async (retry, number) => {
|
||||
const balanceOnHome = await homeWeb3.eth.getBalance(user.address)
|
||||
const currentBridgeErc20TokenBalance = await erc20Token.methods.balanceOf(COMMON_FOREIGN_BRIDGE_ADDRESS).call()
|
||||
const currentBridgeHalfDuplexBalance = await halfDuplexToken.methods
|
||||
.balanceOf(COMMON_FOREIGN_BRIDGE_ADDRESS)
|
||||
.call()
|
||||
|
||||
assert(toBN(balanceOnHome).eq(toBN(originalBalanceOnHome)), 'User balance should be the same')
|
||||
assert(
|
||||
toBN(currentBridgeHalfDuplexBalance).eq(toBN(bridgeHalfDuplexBalance).add(toBN(valueToTransfer))),
|
||||
'Half duplex balance should be the value of transfer'
|
||||
)
|
||||
assert(toBN(currentBridgeErc20TokenBalance).eq(toBN(bridgeErc20TokenBalance)), 'erc20 balance should not change')
|
||||
|
||||
// generate new blocks
|
||||
await generateNewBlock(foreignWeb3, user.address)
|
||||
|
||||
// after several retries, the state is corrects
|
||||
if (number < 4) {
|
||||
retry()
|
||||
}
|
||||
})
|
||||
|
||||
// let's undo the Emergency Shutdown to check that the oracle is still working
|
||||
await saiTop.methods.setCaged('0').send({
|
||||
from: user.address,
|
||||
gas: '1000000'
|
||||
})
|
||||
|
||||
const newValueToTransfer = foreignWeb3.utils.toWei('2', 'ether')
|
||||
|
||||
await halfDuplexToken.methods.transfer(COMMON_FOREIGN_BRIDGE_ADDRESS, newValueToTransfer).send({
|
||||
from: user.address,
|
||||
gas: '1000000'
|
||||
})
|
||||
|
||||
await generateNewBlock(foreignWeb3, user.address)
|
||||
|
||||
await promiseRetry(async retry => {
|
||||
const userBalance = await homeWeb3.eth.getBalance(user.address)
|
||||
const updatedBridgeErc20TokenBalance = await erc20Token.methods.balanceOf(COMMON_FOREIGN_BRIDGE_ADDRESS).call()
|
||||
|
||||
if (
|
||||
toBN(userBalance).lte(toBN(originalBalanceOnHome)) ||
|
||||
toBN(updatedBridgeErc20TokenBalance).lte(toBN(bridgeErc20TokenBalance))
|
||||
) {
|
||||
retry()
|
||||
} else {
|
||||
assert(
|
||||
toBN(userBalance).eq(toBN(originalBalanceOnHome).add(toBN(newValueToTransfer))),
|
||||
'User balance should be increased by the half duplex token transfer'
|
||||
)
|
||||
const updatedHDBalance = await halfDuplexToken.methods.balanceOf(COMMON_FOREIGN_BRIDGE_ADDRESS).call()
|
||||
assert(toBN(updatedHDBalance).isZero(), 'Half duplex bridge balance should be zero')
|
||||
assert(
|
||||
toBN(updatedBridgeErc20TokenBalance).eq(
|
||||
toBN(bridgeErc20TokenBalance)
|
||||
.add(toBN(valueToTransfer))
|
||||
.add(toBN(newValueToTransfer))
|
||||
),
|
||||
'Erc20 token balance should be correctly increased by the token swap'
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -10,7 +10,7 @@ The Oracle is deployed on specified validator nodes (only nodes whose private ke
|
||||
|
||||
## Architecture
|
||||
|
||||
### Native-to-ERC20
|
||||
### Native-to-ERC20 and Arbitrary-Message
|
||||
|
||||

|
||||
|
||||
@@ -27,7 +27,7 @@ There are three Watchers:
|
||||
- **Signature Request Watcher**: Listens to `UserRequestForSignature` events on the Home network.
|
||||
- **Collected Signatures Watcher**: Listens to `CollectedSignatures` events on the Home network.
|
||||
- **Affirmation Request Watcher**: Depends on the bridge mode.
|
||||
- `Native-to-ERC20`: Listens to `UserRequestForAffirmation` raised by the bridge contract.
|
||||
- `Native-to-ERC20` and `Arbitrary-Message`: Listens to `UserRequestForAffirmation` raised by the bridge contract.
|
||||
- `ERC20-to-ERC20` and `ERC20-to-Native`: Listens to `Transfer` events raised by the token contract.
|
||||
|
||||
### Sender
|
||||
@@ -53,7 +53,7 @@ For more information on the Redis/RabbitMQ requirements, see [#90](/../../issues
|
||||
|
||||
**Note:** The following steps detail the bridge deployment process for development and testing. For deployment in a production environment we recommend using the [Bridge Deployment Playbooks](../deployment/README.md).
|
||||
|
||||
1. [Deploy the bridge contracts](https://github.com/poanetwork/poa-bridge-contracts/blob/master/deploy/README.md)
|
||||
1. [Deploy the bridge contracts](https://github.com/poanetwork/tokenbridge-contracts/blob/master/deploy/README.md)
|
||||
|
||||
2. Open `bridgeDeploymentResults.json` or copy the JSON output generated by the bridge contract deployment process.
|
||||
|
||||
@@ -232,10 +232,10 @@ When running the processes, the following commands can be used to test functiona
|
||||
| `USER_ADDRESS` | An account - the current owner of coins/tokens. |
|
||||
| `USER_ADDRESS_PRIVATE_KEY` | A private key belonging to the account. |
|
||||
| `COMMON_HOME_BRIDGE_ADDRESS` | Address of the bridge in the Home network to send transactions. |
|
||||
| `HOME_MIN_AMOUNT_PER_TX` | Value (in _eth_ or tokens) to be sent in one transaction for the Home network. This should be greater than or equal to the value specified in the `poa-bridge-contracts/deploy/.env` file. The default value in that file is 500000000000000000, which is equivalent to 0.5. |
|
||||
| `HOME_MIN_AMOUNT_PER_TX` | Value (in _eth_ or tokens) to be sent in one transaction for the Home network. This should be greater than or equal to the value specified in the `tokenbridge-contracts/deploy/.env` file. The default value in that file is 500000000000000000, which is equivalent to 0.5. |
|
||||
| `HOME_TEST_TX_GAS_PRICE` | The gas price (in Wei) that is used to send transactions in the Home network . |
|
||||
| `COMMON_FOREIGN_BRIDGE_ADDRESS` | Address of the bridge in the Foreign network to send transactions. |
|
||||
| `FOREIGN_MIN_AMOUNT_PER_TX` | Value (in _eth_ or tokens) to be sent in one transaction for the Foreign network. This should be greater than or equal to the value specified in the `poa-bridge-contracts/deploy/.env` file. The default value in that file is 500000000000000000, which is equivalent to 0.5. |
|
||||
| `FOREIGN_MIN_AMOUNT_PER_TX` | Value (in _eth_ or tokens) to be sent in one transaction for the Foreign network. This should be greater than or equal to the value specified in the `tokenbridge-contracts/deploy/.env` file. The default value in that file is 500000000000000000, which is equivalent to 0.5. |
|
||||
| `FOREIGN_TEST_TX_GAS_PRICE` | The gas price (in Wei) that is used to send transactions in the Foreign network . |
|
||||
|
||||
## Contributing
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const baseConfig = require('./base.config')
|
||||
const { ERC20_ABI, ERC_TYPES } = require('../../commons')
|
||||
const { ERC_TYPES } = require('../../commons')
|
||||
|
||||
const initialChecksJson = process.argv[3]
|
||||
|
||||
@@ -20,24 +20,11 @@ if (baseConfig.id === 'erc-erc' && initialChecks.foreignERC === ERC_TYPES.ERC677
|
||||
|
||||
const id = `${baseConfig.id}-affirmation-request`
|
||||
|
||||
module.exports =
|
||||
(baseConfig.id === 'erc-erc' && initialChecks.foreignERC === ERC_TYPES.ERC20) || baseConfig.id === 'erc-native'
|
||||
? {
|
||||
...baseConfig.bridgeConfig,
|
||||
...baseConfig.foreignConfig,
|
||||
event: 'Transfer',
|
||||
eventContractAddress: initialChecks.bridgeableTokenAddress,
|
||||
eventAbi: ERC20_ABI,
|
||||
eventFilter: { to: process.env.COMMON_FOREIGN_BRIDGE_ADDRESS },
|
||||
queue: 'home',
|
||||
name: `watcher-${id}`,
|
||||
id
|
||||
}
|
||||
: {
|
||||
...baseConfig.bridgeConfig,
|
||||
...baseConfig.foreignConfig,
|
||||
event: 'UserRequestForAffirmation',
|
||||
queue: 'home',
|
||||
name: `watcher-${id}`,
|
||||
id
|
||||
}
|
||||
module.exports = {
|
||||
...baseConfig.bridgeConfig,
|
||||
...baseConfig.foreignConfig,
|
||||
event: 'UserRequestForAffirmation',
|
||||
queue: 'home',
|
||||
name: `watcher-${id}`,
|
||||
id
|
||||
}
|
||||
|
||||
@@ -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.ORACLE_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.ORACLE_BRIDGE_MODE} not supported.`)
|
||||
|
||||
39
oracle/config/half-duplex-transfer-watcher.config.js
Normal file
39
oracle/config/half-duplex-transfer-watcher.config.js
Normal file
@@ -0,0 +1,39 @@
|
||||
const baseConfig = require('./base.config')
|
||||
const { ERC20_ABI } = require('../../commons')
|
||||
const { EXIT_CODES } = require('../src/utils/constants')
|
||||
|
||||
const initialChecksJson = process.argv[3]
|
||||
|
||||
if (!initialChecksJson) {
|
||||
throw new Error('initial check parameter was not provided.')
|
||||
}
|
||||
|
||||
let initialChecks
|
||||
try {
|
||||
initialChecks = JSON.parse(initialChecksJson)
|
||||
} catch (e) {
|
||||
throw new Error('Error on decoding values from initial checks.')
|
||||
}
|
||||
|
||||
const id = `${baseConfig.id}-half-duplex-transfer`
|
||||
|
||||
const transferWatcherRequired = baseConfig.id === 'erc-native'
|
||||
|
||||
if (!transferWatcherRequired) {
|
||||
console.error(`Transfer watcher not required for bridge mode ${process.env.ORACLE_BRIDGE_MODE}`)
|
||||
process.exit(EXIT_CODES.WATCHER_NOT_REQUIRED)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
...baseConfig.bridgeConfig,
|
||||
...baseConfig.foreignConfig,
|
||||
event: 'Transfer',
|
||||
eventContractAddress: initialChecks.halfDuplexTokenAddress,
|
||||
eventAbi: ERC20_ABI,
|
||||
eventFilter: { to: process.env.COMMON_FOREIGN_BRIDGE_ADDRESS },
|
||||
queue: 'home',
|
||||
workerQueue: 'swap-tokens',
|
||||
name: `watcher-${id}`,
|
||||
id,
|
||||
idle: initialChecks.idle
|
||||
}
|
||||
20
oracle/config/swap-tokens-worker.config.js
Normal file
20
oracle/config/swap-tokens-worker.config.js
Normal file
@@ -0,0 +1,20 @@
|
||||
const baseConfig = require('./base.config')
|
||||
const { EXIT_CODES } = require('../src/utils/constants')
|
||||
|
||||
const id = `${baseConfig.id}-swap-tokens`
|
||||
|
||||
const workerRequired = baseConfig.id === 'erc-native'
|
||||
|
||||
if (!workerRequired) {
|
||||
console.error(`Swap tokens worker not required for bridge mode ${process.env.ORACLE_BRIDGE_MODE}`)
|
||||
process.exit(EXIT_CODES.WATCHER_NOT_REQUIRED)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
...baseConfig.bridgeConfig,
|
||||
...baseConfig.foreignConfig,
|
||||
workerQueue: 'swap-tokens',
|
||||
senderQueue: 'foreign',
|
||||
name: `worker-${id}`,
|
||||
id
|
||||
}
|
||||
42
oracle/config/transfer-watcher.config.js
Normal file
42
oracle/config/transfer-watcher.config.js
Normal file
@@ -0,0 +1,42 @@
|
||||
const baseConfig = require('./base.config')
|
||||
const { ERC20_ABI, ERC_TYPES } = require('../../commons')
|
||||
const { EXIT_CODES } = require('../src/utils/constants')
|
||||
|
||||
const initialChecksJson = process.argv[3]
|
||||
|
||||
if (!initialChecksJson) {
|
||||
throw new Error('initial check parameter was not provided.')
|
||||
}
|
||||
|
||||
let initialChecks
|
||||
try {
|
||||
initialChecks = JSON.parse(initialChecksJson)
|
||||
} catch (e) {
|
||||
throw new Error('Error on decoding values from initial checks.')
|
||||
}
|
||||
|
||||
if (baseConfig.id === 'erc-erc' && initialChecks.foreignERC === ERC_TYPES.ERC677) {
|
||||
baseConfig.id = 'erc677-erc677'
|
||||
}
|
||||
|
||||
const id = `${baseConfig.id}-transfer`
|
||||
|
||||
const transferWatcherRequired =
|
||||
(baseConfig.id === 'erc-erc' && initialChecks.foreignERC === ERC_TYPES.ERC20) || baseConfig.id === 'erc-native'
|
||||
|
||||
if (!transferWatcherRequired) {
|
||||
console.error(`Transfer watcher not required for bridge mode ${process.env.ORACLE_BRIDGE_MODE}`)
|
||||
process.exit(EXIT_CODES.WATCHER_NOT_REQUIRED)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
...baseConfig.bridgeConfig,
|
||||
...baseConfig.foreignConfig,
|
||||
event: 'Transfer',
|
||||
eventContractAddress: initialChecks.bridgeableTokenAddress,
|
||||
eventAbi: ERC20_ABI,
|
||||
eventFilter: { to: process.env.COMMON_FOREIGN_BRIDGE_ADDRESS },
|
||||
queue: 'home',
|
||||
name: `watcher-${id}`,
|
||||
id
|
||||
}
|
||||
129
oracle/docker-compose-erc-native.yml
Normal file
129
oracle/docker-compose-erc-native.yml
Normal file
@@ -0,0 +1,129 @@
|
||||
---
|
||||
version: '2.4'
|
||||
services:
|
||||
rabbit:
|
||||
extends:
|
||||
file: docker-compose.yml
|
||||
service: rabbit
|
||||
networks:
|
||||
- net_rabbit_bridge_transfer
|
||||
- net_rabbit_bridge_half_duplex_transfer
|
||||
- net_rabbit_bridge_swap_tokens_worker
|
||||
redis:
|
||||
extends:
|
||||
file: docker-compose.yml
|
||||
service: redis
|
||||
networks:
|
||||
- net_db_bridge_transfer
|
||||
- net_db_bridge_half_duplex_transfer
|
||||
bridge_request:
|
||||
extends:
|
||||
file: docker-compose.yml
|
||||
service: bridge_request
|
||||
networks:
|
||||
- net_db_bridge_request
|
||||
- net_rabbit_bridge_request
|
||||
bridge_collected:
|
||||
extends:
|
||||
file: docker-compose.yml
|
||||
service: bridge_collected
|
||||
networks:
|
||||
- net_db_bridge_request
|
||||
- net_rabbit_bridge_request
|
||||
bridge_affirmation:
|
||||
extends:
|
||||
file: docker-compose.yml
|
||||
service: bridge_affirmation
|
||||
networks:
|
||||
- net_db_bridge_request
|
||||
- net_rabbit_bridge_request
|
||||
bridge_transfer:
|
||||
cpus: 0.1
|
||||
mem_limit: 500m
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: oracle/Dockerfile
|
||||
env_file: ./.env
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- ORACLE_VALIDATOR_ADDRESS=${ORACLE_VALIDATOR_ADDRESS}
|
||||
restart: unless-stopped
|
||||
entrypoint: yarn watcher:transfer
|
||||
networks:
|
||||
- net_db_bridge_transfer
|
||||
- net_rabbit_bridge_transfer
|
||||
bridge_half_duplex_transfer:
|
||||
cpus: 0.1
|
||||
mem_limit: 500m
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: oracle/Dockerfile
|
||||
env_file: ./.env
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- ORACLE_VALIDATOR_ADDRESS=${ORACLE_VALIDATOR_ADDRESS}
|
||||
restart: unless-stopped
|
||||
entrypoint: yarn watcher:half-duplex-transfer
|
||||
networks:
|
||||
- net_db_bridge_half_duplex_transfer
|
||||
- net_rabbit_bridge_half_duplex_transfer
|
||||
bridge_swap_tokens_worker:
|
||||
cpus: 0.1
|
||||
mem_limit: 500m
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: oracle/Dockerfile
|
||||
env_file: ./.env
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- ORACLE_VALIDATOR_ADDRESS=${ORACLE_VALIDATOR_ADDRESS}
|
||||
restart: unless-stopped
|
||||
entrypoint: yarn worker:swap-tokens
|
||||
networks:
|
||||
- net_rabbit_bridge_swap_tokens_worker
|
||||
bridge_senderhome:
|
||||
extends:
|
||||
file: docker-compose.yml
|
||||
service: bridge_senderhome
|
||||
networks:
|
||||
- net_db_bridge_request
|
||||
- net_rabbit_bridge_request
|
||||
bridge_senderforeign:
|
||||
extends:
|
||||
file: docker-compose.yml
|
||||
service: bridge_senderforeign
|
||||
networks:
|
||||
- net_db_bridge_request
|
||||
- net_rabbit_bridge_request
|
||||
|
||||
networks:
|
||||
net_db_bridge_request:
|
||||
driver: bridge
|
||||
net_db_bridge_collected:
|
||||
driver: bridge
|
||||
net_db_bridge_affirmation:
|
||||
driver: bridge
|
||||
net_db_bridge_transfer:
|
||||
driver: bridge
|
||||
net_db_bridge_half_duplex_transfer:
|
||||
driver: bridge
|
||||
net_db_bridge_senderhome:
|
||||
driver: bridge
|
||||
net_db_bridge_senderforeign:
|
||||
driver: bridge
|
||||
net_rabbit_bridge_request:
|
||||
driver: bridge
|
||||
net_rabbit_bridge_collected:
|
||||
driver: bridge
|
||||
net_rabbit_bridge_affirmation:
|
||||
driver: bridge
|
||||
net_rabbit_bridge_transfer:
|
||||
driver: bridge
|
||||
net_rabbit_bridge_half_duplex_transfer:
|
||||
driver: bridge
|
||||
net_rabbit_bridge_swap_tokens_worker:
|
||||
driver: bridge
|
||||
net_rabbit_bridge_senderhome:
|
||||
driver: bridge
|
||||
net_rabbit_bridge_senderforeign:
|
||||
driver: bridge
|
||||
91
oracle/docker-compose-transfer.yml
Normal file
91
oracle/docker-compose-transfer.yml
Normal file
@@ -0,0 +1,91 @@
|
||||
---
|
||||
version: '2.4'
|
||||
services:
|
||||
rabbit:
|
||||
extends:
|
||||
file: docker-compose.yml
|
||||
service: rabbit
|
||||
networks:
|
||||
- net_rabbit_bridge_transfer
|
||||
redis:
|
||||
extends:
|
||||
file: docker-compose.yml
|
||||
service: redis
|
||||
networks:
|
||||
- net_db_bridge_transfer
|
||||
bridge_request:
|
||||
extends:
|
||||
file: docker-compose.yml
|
||||
service: bridge_request
|
||||
networks:
|
||||
- net_db_bridge_request
|
||||
- net_rabbit_bridge_request
|
||||
bridge_collected:
|
||||
extends:
|
||||
file: docker-compose.yml
|
||||
service: bridge_collected
|
||||
networks:
|
||||
- net_db_bridge_request
|
||||
- net_rabbit_bridge_request
|
||||
bridge_affirmation:
|
||||
extends:
|
||||
file: docker-compose.yml
|
||||
service: bridge_affirmation
|
||||
networks:
|
||||
- net_db_bridge_request
|
||||
- net_rabbit_bridge_request
|
||||
bridge_transfer:
|
||||
cpus: 0.1
|
||||
mem_limit: 500m
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: oracle/Dockerfile
|
||||
env_file: ./.env
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- ORACLE_VALIDATOR_ADDRESS=${ORACLE_VALIDATOR_ADDRESS}
|
||||
restart: unless-stopped
|
||||
entrypoint: yarn watcher:transfer
|
||||
networks:
|
||||
- net_db_bridge_transfer
|
||||
- net_rabbit_bridge_transfer
|
||||
bridge_senderhome:
|
||||
extends:
|
||||
file: docker-compose.yml
|
||||
service: bridge_senderhome
|
||||
networks:
|
||||
- net_db_bridge_request
|
||||
- net_rabbit_bridge_request
|
||||
bridge_senderforeign:
|
||||
extends:
|
||||
file: docker-compose.yml
|
||||
service: bridge_senderforeign
|
||||
networks:
|
||||
- net_db_bridge_request
|
||||
- net_rabbit_bridge_request
|
||||
|
||||
networks:
|
||||
net_db_bridge_request:
|
||||
driver: bridge
|
||||
net_db_bridge_collected:
|
||||
driver: bridge
|
||||
net_db_bridge_affirmation:
|
||||
driver: bridge
|
||||
net_db_bridge_transfer:
|
||||
driver: bridge
|
||||
net_db_bridge_senderhome:
|
||||
driver: bridge
|
||||
net_db_bridge_senderforeign:
|
||||
driver: bridge
|
||||
net_rabbit_bridge_request:
|
||||
driver: bridge
|
||||
net_rabbit_bridge_collected:
|
||||
driver: bridge
|
||||
net_rabbit_bridge_affirmation:
|
||||
driver: bridge
|
||||
net_rabbit_bridge_transfer:
|
||||
driver: bridge
|
||||
net_rabbit_bridge_senderhome:
|
||||
driver: bridge
|
||||
net_rabbit_bridge_senderforeign:
|
||||
driver: bridge
|
||||
@@ -8,9 +8,12 @@
|
||||
"watcher:signature-request": "./scripts/start-worker.sh watcher signature-request-watcher",
|
||||
"watcher:collected-signatures": "./scripts/start-worker.sh watcher collected-signatures-watcher",
|
||||
"watcher:affirmation-request": "./scripts/start-worker.sh watcher affirmation-request-watcher",
|
||||
"watcher:transfer": "./scripts/start-worker.sh watcher transfer-watcher",
|
||||
"watcher:half-duplex-transfer": "./scripts/start-worker.sh watcher half-duplex-transfer-watcher",
|
||||
"worker:swap-tokens": "./scripts/start-worker.sh worker swap-tokens-worker",
|
||||
"sender:home": "./scripts/start-worker.sh sender home-sender",
|
||||
"sender:foreign": "./scripts/start-worker.sh sender foreign-sender",
|
||||
"dev": "concurrently -n 'watcher:signature-request,watcher:collected-signatures,watcher:affirmation-request,sender:home,sender:foreign' -c 'red,green,yellow,blue,magenta' 'yarn watcher:signature-request' 'yarn watcher:collected-signatures' 'yarn watcher:affirmation-request' 'yarn sender:home' 'yarn sender:foreign'",
|
||||
"dev": "concurrently -n 'watcher:signature-request,watcher:collected-signatures,watcher:affirmation-request,watcher:transfer,watcher:half-duplex-transfer, worker:swap-tokens, sender:home,sender:foreign' -c 'red,green,yellow,blue,white,gray,magenta,cyan' 'yarn watcher:signature-request' 'yarn watcher:collected-signatures' 'yarn watcher:affirmation-request' 'yarn watcher:transfer' 'yarn watcher:half-duplex-transfer' 'yarn worker:swap-tokens' 'yarn sender:home' 'yarn sender:foreign'",
|
||||
"test": "NODE_ENV=test mocha",
|
||||
"test:watch": "NODE_ENV=test mocha --watch --reporter=min",
|
||||
"coverage": "NODE_ENV=test nyc --reporter=text --reporter=html mocha",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
require('../env')
|
||||
const Web3 = require('web3')
|
||||
const { getTokensState } = require('../src/utils/tokenState')
|
||||
const {
|
||||
ERC677_BRIDGE_TOKEN_ABI,
|
||||
FOREIGN_ERC_TO_ERC_ABI,
|
||||
@@ -7,9 +8,14 @@ const {
|
||||
getTokenType
|
||||
} = require('../../commons')
|
||||
|
||||
const emptyLogger = {
|
||||
debug: () => {},
|
||||
info: () => {}
|
||||
}
|
||||
|
||||
async function initialChecks() {
|
||||
const { ORACLE_BRIDGE_MODE, COMMON_FOREIGN_RPC_URL, COMMON_FOREIGN_BRIDGE_ADDRESS } = process.env
|
||||
const result = {}
|
||||
let result = {}
|
||||
const foreignWeb3 = new Web3(new Web3.providers.HttpProvider(COMMON_FOREIGN_RPC_URL))
|
||||
|
||||
if (ORACLE_BRIDGE_MODE === 'ERC_TO_ERC') {
|
||||
@@ -17,7 +23,7 @@ async function initialChecks() {
|
||||
result.bridgeableTokenAddress = await bridge.methods.erc20token().call()
|
||||
} else if (ORACLE_BRIDGE_MODE === 'ERC_TO_NATIVE') {
|
||||
const bridge = new foreignWeb3.eth.Contract(FOREIGN_ERC_TO_NATIVE_ABI, COMMON_FOREIGN_BRIDGE_ADDRESS)
|
||||
result.bridgeableTokenAddress = await bridge.methods.erc20token().call()
|
||||
result = await getTokensState(bridge, emptyLogger)
|
||||
}
|
||||
|
||||
if (ORACLE_BRIDGE_MODE === 'ERC_TO_ERC') {
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
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 }) {
|
||||
try {
|
||||
const gasEstimate = await homeBridge.methods.executeAffirmation(message).estimateGas({
|
||||
from: address
|
||||
})
|
||||
|
||||
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
|
||||
95
oracle/src/events/processAMBAffirmationRequests/index.js
Normal file
95
oracle/src/events/processAMBAffirmationRequests/index.js
Normal file
@@ -0,0 +1,95 @@
|
||||
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 { 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 => async () => {
|
||||
const { encodedData } = affirmationRequest.returnValues
|
||||
|
||||
const logger = rootLogger.child({
|
||||
eventTransactionHash: affirmationRequest.transactionHash
|
||||
})
|
||||
|
||||
const message = addTxHashToData({
|
||||
encodedData,
|
||||
transactionHash: affirmationRequest.transactionHash
|
||||
})
|
||||
|
||||
const { sender, executor } = 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
|
||||
})
|
||||
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()
|
||||
|
||||
txToSend.push({
|
||||
data,
|
||||
gasEstimate,
|
||||
transactionReference: affirmationRequest.transactionHash,
|
||||
to: config.homeBridgeAddress
|
||||
})
|
||||
})
|
||||
.map(promise => limit(promise))
|
||||
|
||||
await Promise.all(callbacks)
|
||||
return txToSend
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = processAffirmationRequestsBuilder
|
||||
@@ -0,0 +1,62 @@
|
||||
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
|
||||
}) {
|
||||
try {
|
||||
const gasEstimate = await foreignBridge.methods.executeSignatures(message, signatures).estimateGas({
|
||||
from: address
|
||||
})
|
||||
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 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 => async () => {
|
||||
const { authorityResponsibleForRelay, messageHash, NumberOfCollectedSignatures } = colSignature.returnValues
|
||||
|
||||
const logger = rootLogger.child({
|
||||
eventTransactionHash: colSignature.transactionHash
|
||||
})
|
||||
|
||||
if (authorityResponsibleForRelay !== web3Home.utils.toChecksumAddress(config.validatorAddress)) {
|
||||
logger.info(`Validator not responsible for relaying CollectedSignatures ${colSignature.transactionHash}`)
|
||||
return
|
||||
}
|
||||
|
||||
logger.info(`Processing CollectedSignatures ${colSignature.transactionHash}`)
|
||||
const message = await homeBridge.methods.message(messageHash).call()
|
||||
|
||||
logger.debug({ NumberOfCollectedSignatures }, 'Number of signatures to get')
|
||||
|
||||
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 { 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
|
||||
})
|
||||
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()
|
||||
|
||||
txToSend.push({
|
||||
data,
|
||||
gasEstimate,
|
||||
transactionReference: colSignature.transactionHash,
|
||||
to: config.foreignBridgeAddress
|
||||
})
|
||||
})
|
||||
.map(promise => limit(promise))
|
||||
|
||||
await Promise.all(callbacks)
|
||||
|
||||
return txToSend
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = processCollectedSignaturesBuilder
|
||||
99
oracle/src/events/processAMBSignatureRequests/index.js
Normal file
99
oracle/src/events/processAMBSignatureRequests/index.js
Normal file
@@ -0,0 +1,99 @@
|
||||
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 { ORACLE_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 => 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${ORACLE_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()
|
||||
|
||||
txToSend.push({
|
||||
data,
|
||||
gasEstimate,
|
||||
transactionReference: signatureRequest.transactionHash,
|
||||
to: config.homeBridgeAddress
|
||||
})
|
||||
})
|
||||
.map(promise => limit(promise))
|
||||
|
||||
await Promise.all(callbacks)
|
||||
return txToSend
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = processSignatureRequestsBuilder
|
||||
@@ -28,8 +28,8 @@ function processAffirmationRequestsBuilder(config) {
|
||||
}
|
||||
|
||||
rootLogger.debug(`Processing ${affirmationRequests.length} AffirmationRequest events`)
|
||||
const callbacks = affirmationRequests.map(affirmationRequest =>
|
||||
limit(async () => {
|
||||
const callbacks = affirmationRequests
|
||||
.map(affirmationRequest => async () => {
|
||||
const { recipient, value } = affirmationRequest.returnValues
|
||||
|
||||
const logger = rootLogger.child({
|
||||
@@ -82,7 +82,7 @@ function processAffirmationRequestsBuilder(config) {
|
||||
to: config.homeBridgeAddress
|
||||
})
|
||||
})
|
||||
)
|
||||
.map(promise => limit(promise))
|
||||
|
||||
await Promise.all(callbacks)
|
||||
return txToSend
|
||||
|
||||
@@ -30,74 +30,77 @@ function processCollectedSignaturesBuilder(config) {
|
||||
}
|
||||
|
||||
rootLogger.debug(`Processing ${signatures.length} CollectedSignatures events`)
|
||||
const callbacks = signatures.map(colSignature =>
|
||||
limit(async () => {
|
||||
const callbacks = signatures
|
||||
.map(colSignature => 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 [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 recover = signatureToVRS(signature)
|
||||
v.push(recover.v)
|
||||
r.push(recover.r)
|
||||
s.push(recover.s)
|
||||
})
|
||||
|
||||
await Promise.all(signaturePromises)
|
||||
|
||||
let gasEstimate
|
||||
try {
|
||||
logger.debug('Estimate gas')
|
||||
gasEstimate = await estimateGas({
|
||||
foreignBridge,
|
||||
validatorContract,
|
||||
v,
|
||||
r,
|
||||
s,
|
||||
message,
|
||||
numberOfCollectedSignatures: NumberOfCollectedSignatures
|
||||
})
|
||||
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(v, r, s, message).encodeABI()
|
||||
txToSend.push({
|
||||
data,
|
||||
gasEstimate,
|
||||
transactionReference: colSignature.transactionHash,
|
||||
to: config.foreignBridgeAddress
|
||||
})
|
||||
} else {
|
||||
if (authorityResponsibleForRelay !== web3Home.utils.toChecksumAddress(config.validatorAddress)) {
|
||||
logger.info(`Validator not responsible for relaying CollectedSignatures ${colSignature.transactionHash}`)
|
||||
return
|
||||
}
|
||||
|
||||
logger.info(`Processing CollectedSignatures ${colSignature.transactionHash}`)
|
||||
const message = await homeBridge.methods.message(messageHash).call()
|
||||
|
||||
logger.debug({ NumberOfCollectedSignatures }, 'Number of signatures to get')
|
||||
|
||||
const requiredSignatures = []
|
||||
requiredSignatures.length = NumberOfCollectedSignatures
|
||||
requiredSignatures.fill(0)
|
||||
|
||||
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 recover = signatureToVRS(signature)
|
||||
v.push(recover.v)
|
||||
r.push(recover.r)
|
||||
s.push(recover.s)
|
||||
})
|
||||
|
||||
await Promise.all(signaturePromises)
|
||||
|
||||
let gasEstimate
|
||||
try {
|
||||
logger.debug('Estimate gas')
|
||||
gasEstimate = await estimateGas({
|
||||
foreignBridge,
|
||||
validatorContract,
|
||||
v,
|
||||
r,
|
||||
s,
|
||||
message,
|
||||
numberOfCollectedSignatures: NumberOfCollectedSignatures
|
||||
})
|
||||
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(v, r, s, message).encodeABI()
|
||||
txToSend.push({
|
||||
data,
|
||||
gasEstimate,
|
||||
transactionReference: colSignature.transactionHash,
|
||||
to: config.foreignBridgeAddress
|
||||
})
|
||||
})
|
||||
)
|
||||
.map(promise => limit(promise))
|
||||
|
||||
await Promise.all(callbacks)
|
||||
|
||||
|
||||
134
oracle/src/events/processHalfDuplexTransfers/index.js
Normal file
134
oracle/src/events/processHalfDuplexTransfers/index.js
Normal file
@@ -0,0 +1,134 @@
|
||||
require('../../../env')
|
||||
const promiseLimit = require('promise-limit')
|
||||
const { HttpListProviderError } = require('http-list-provider')
|
||||
const { BRIDGE_VALIDATORS_ABI, ZERO_ADDRESS } = require('../../../../commons')
|
||||
const rootLogger = require('../../services/logger')
|
||||
const { web3Home, web3Foreign } = require('../../services/web3')
|
||||
const { AlreadyProcessedError, AlreadySignedError, InvalidValidatorError } = require('../../utils/errors')
|
||||
const { EXIT_CODES, MAX_CONCURRENT_EVENTS } = require('../../utils/constants')
|
||||
const estimateGas = require('../processAffirmationRequests/estimateGas')
|
||||
|
||||
const limit = promiseLimit(MAX_CONCURRENT_EVENTS)
|
||||
|
||||
let validatorContract = null
|
||||
|
||||
function processTransfersBuilder(config) {
|
||||
const homeBridge = new web3Home.eth.Contract(config.homeBridgeAbi, config.homeBridgeAddress)
|
||||
const foreignBridge = new web3Foreign.eth.Contract(config.foreignBridgeAbi, config.foreignBridgeAddress)
|
||||
const userRequestForAffirmationAbi = config.foreignBridgeAbi.filter(
|
||||
e => e.type === 'event' && e.name === 'UserRequestForAffirmation'
|
||||
)[0]
|
||||
const tokensSwappedAbi = config.foreignBridgeAbi.filter(e => e.type === 'event' && e.name === 'TokensSwapped')[0]
|
||||
const userRequestForAffirmationHash = web3Home.eth.abi.encodeEventSignature(userRequestForAffirmationAbi)
|
||||
const tokensSwappedHash = tokensSwappedAbi ? web3Home.eth.abi.encodeEventSignature(tokensSwappedAbi) : '0x'
|
||||
|
||||
return async function processTransfers(transfers, blockNumber) {
|
||||
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(BRIDGE_VALIDATORS_ABI, validatorContractAddress)
|
||||
}
|
||||
|
||||
rootLogger.debug(`Processing ${transfers.length} Transfer events`)
|
||||
const callbacks = transfers
|
||||
.map(transfer => async () => {
|
||||
const { from, value } = transfer.returnValues
|
||||
|
||||
const logger = rootLogger.child({
|
||||
eventTransactionHash: transfer.transactionHash
|
||||
})
|
||||
|
||||
logger.info({ from, value }, `Processing transfer ${transfer.transactionHash}`)
|
||||
|
||||
const block = await web3Foreign.eth.getBlock(blockNumber)
|
||||
logger.debug({ blockNumber, timestamp: block.timestamp }, `Block obtained`)
|
||||
|
||||
const tokenSwapAllowed = await foreignBridge.methods.isTokenSwapAllowed(block.timestamp).call()
|
||||
|
||||
if (!tokenSwapAllowed) {
|
||||
logger.info(
|
||||
`Transfer event discarded because SCD Emergency Shutdown has happened ${transfer.transactionHash}`
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
const receipt = await web3Foreign.eth.getTransactionReceipt(transfer.transactionHash)
|
||||
|
||||
const existsAffirmationEvent = receipt.logs.some(
|
||||
e => e.address === config.foreignBridgeAddress && e.topics[0] === userRequestForAffirmationHash
|
||||
)
|
||||
|
||||
if (existsAffirmationEvent) {
|
||||
logger.info(
|
||||
`Transfer event discarded because a transaction with alternative receiver detected in transaction ${
|
||||
transfer.transactionHash
|
||||
}`
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
const existsTokensSwappedEvent = tokensSwappedAbi
|
||||
? receipt.logs.some(e => e.address === config.foreignBridgeAddress && e.topics[0] === tokensSwappedHash)
|
||||
: false
|
||||
|
||||
if (from === ZERO_ADDRESS && existsTokensSwappedEvent) {
|
||||
logger.info(
|
||||
`Transfer event discarded because token swap is detected in transaction ${transfer.transactionHash}`
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
let gasEstimate
|
||||
try {
|
||||
logger.debug('Estimate gas')
|
||||
gasEstimate = await estimateGas({
|
||||
web3: web3Home,
|
||||
homeBridge,
|
||||
validatorContract,
|
||||
recipient: from,
|
||||
value,
|
||||
txHash: transfer.transactionHash,
|
||||
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 transfer ${transfer.transactionHash}`)
|
||||
return
|
||||
} else if (e instanceof AlreadyProcessedError) {
|
||||
logger.info(`transfer ${transfer.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(from, value, transfer.transactionHash)
|
||||
.encodeABI({ from: config.validatorAddress })
|
||||
|
||||
txToSend.push({
|
||||
data,
|
||||
gasEstimate,
|
||||
transactionReference: transfer.transactionHash,
|
||||
to: config.homeBridgeAddress
|
||||
})
|
||||
})
|
||||
.map(promise => limit(promise))
|
||||
|
||||
await Promise.all(callbacks)
|
||||
return txToSend
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = processTransfersBuilder
|
||||
@@ -35,8 +35,8 @@ function processSignatureRequestsBuilder(config) {
|
||||
}
|
||||
|
||||
rootLogger.debug(`Processing ${signatureRequests.length} SignatureRequest events`)
|
||||
const callbacks = signatureRequests.map(signatureRequest =>
|
||||
limit(async () => {
|
||||
const callbacks = signatureRequests
|
||||
.map(signatureRequest => async () => {
|
||||
const { recipient, value } = signatureRequest.returnValues
|
||||
|
||||
const logger = rootLogger.child({
|
||||
@@ -98,7 +98,7 @@ function processSignatureRequestsBuilder(config) {
|
||||
to: config.homeBridgeAddress
|
||||
})
|
||||
})
|
||||
)
|
||||
.map(promise => limit(promise))
|
||||
|
||||
await Promise.all(callbacks)
|
||||
return txToSend
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
require('../../../env')
|
||||
const promiseLimit = require('promise-limit')
|
||||
const { HttpListProviderError } = require('http-list-provider')
|
||||
const { BRIDGE_VALIDATORS_ABI } = require('../../../../commons')
|
||||
const { BRIDGE_VALIDATORS_ABI, ZERO_ADDRESS } = require('../../../../commons')
|
||||
const rootLogger = require('../../services/logger')
|
||||
const { web3Home } = require('../../services/web3')
|
||||
const { web3Home, web3Foreign } = require('../../services/web3')
|
||||
const { AlreadyProcessedError, AlreadySignedError, InvalidValidatorError } = require('../../utils/errors')
|
||||
const { EXIT_CODES, MAX_CONCURRENT_EVENTS } = require('../../utils/constants')
|
||||
const estimateGas = require('../processAffirmationRequests/estimateGas')
|
||||
@@ -14,6 +14,12 @@ let validatorContract = null
|
||||
|
||||
function processTransfersBuilder(config) {
|
||||
const homeBridge = new web3Home.eth.Contract(config.homeBridgeAbi, config.homeBridgeAddress)
|
||||
const userRequestForAffirmationAbi = config.foreignBridgeAbi.filter(
|
||||
e => e.type === 'event' && e.name === 'UserRequestForAffirmation'
|
||||
)[0]
|
||||
const tokensSwappedAbi = config.foreignBridgeAbi.filter(e => e.type === 'event' && e.name === 'TokensSwapped')[0]
|
||||
const userRequestForAffirmationHash = web3Home.eth.abi.encodeEventSignature(userRequestForAffirmationAbi)
|
||||
const tokensSwappedHash = tokensSwappedAbi ? web3Home.eth.abi.encodeEventSignature(tokensSwappedAbi) : '0x'
|
||||
|
||||
return async function processTransfers(transfers) {
|
||||
const txToSend = []
|
||||
@@ -27,8 +33,8 @@ function processTransfersBuilder(config) {
|
||||
}
|
||||
|
||||
rootLogger.debug(`Processing ${transfers.length} Transfer events`)
|
||||
const callbacks = transfers.map(transfer =>
|
||||
limit(async () => {
|
||||
const callbacks = transfers
|
||||
.map(transfer => async () => {
|
||||
const { from, value } = transfer.returnValues
|
||||
|
||||
const logger = rootLogger.child({
|
||||
@@ -37,6 +43,32 @@ function processTransfersBuilder(config) {
|
||||
|
||||
logger.info({ from, value }, `Processing transfer ${transfer.transactionHash}`)
|
||||
|
||||
const receipt = await web3Foreign.eth.getTransactionReceipt(transfer.transactionHash)
|
||||
|
||||
const existsAffirmationEvent = receipt.logs.some(
|
||||
e => e.address === config.foreignBridgeAddress && e.topics[0] === userRequestForAffirmationHash
|
||||
)
|
||||
|
||||
if (existsAffirmationEvent) {
|
||||
logger.info(
|
||||
`Transfer event discarded because a transaction with alternative receiver detected in transaction ${
|
||||
transfer.transactionHash
|
||||
}`
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
const existsTokensSwappedEvent = tokensSwappedAbi
|
||||
? receipt.logs.some(e => e.address === config.foreignBridgeAddress && e.topics[0] === tokensSwappedHash)
|
||||
: false
|
||||
|
||||
if (from === ZERO_ADDRESS && existsTokensSwappedEvent) {
|
||||
logger.info(
|
||||
`Transfer event discarded because token swap is detected in transaction ${transfer.transactionHash}`
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
let gasEstimate
|
||||
try {
|
||||
logger.debug('Estimate gas')
|
||||
@@ -79,7 +111,7 @@ function processTransfersBuilder(config) {
|
||||
to: config.homeBridgeAddress
|
||||
})
|
||||
})
|
||||
)
|
||||
.map(promise => limit(promise))
|
||||
|
||||
await Promise.all(callbacks)
|
||||
return txToSend
|
||||
|
||||
@@ -11,17 +11,23 @@ connection.on('disconnect', () => {
|
||||
logger.error('Disconnected from amqp Broker')
|
||||
})
|
||||
|
||||
function connectWatcherToQueue({ queueName, cb }) {
|
||||
function connectWatcherToQueue({ queueName, workerQueue, cb }) {
|
||||
const queueList = workerQueue ? [queueName, workerQueue] : [queueName]
|
||||
|
||||
const channelWrapper = connection.createChannel({
|
||||
json: true,
|
||||
setup(channel) {
|
||||
return Promise.all([channel.assertQueue(queueName, { durable: true })])
|
||||
return Promise.all(queueList.map(queue => channel.assertQueue(queue, { durable: true })))
|
||||
}
|
||||
})
|
||||
|
||||
const sendToQueue = data => channelWrapper.sendToQueue(queueName, data, { persistent: true })
|
||||
let sendToWorker
|
||||
if (workerQueue) {
|
||||
sendToWorker = data => channelWrapper.sendToQueue(workerQueue, data, { persistent: true })
|
||||
}
|
||||
|
||||
cb({ sendToQueue, channel: channelWrapper })
|
||||
cb({ sendToQueue, sendToWorker, channel: channelWrapper })
|
||||
}
|
||||
|
||||
function connectSenderToQueue({ queueName, cb }) {
|
||||
@@ -59,6 +65,43 @@ function connectSenderToQueue({ queueName, cb }) {
|
||||
})
|
||||
}
|
||||
|
||||
function connectWorkerToQueue({ queueName, senderQueue, cb }) {
|
||||
const deadLetterExchange = `${queueName}-retry`
|
||||
|
||||
const channelWrapper = connection.createChannel({
|
||||
json: true
|
||||
})
|
||||
|
||||
channelWrapper.addSetup(channel => {
|
||||
return Promise.all([
|
||||
channel.assertExchange(deadLetterExchange, 'fanout', { durable: true }),
|
||||
channel.assertQueue(queueName, { durable: true }),
|
||||
channel.assertQueue(senderQueue, { durable: true }),
|
||||
channel.bindQueue(queueName, deadLetterExchange),
|
||||
channel.prefetch(1),
|
||||
channel.consume(queueName, msg =>
|
||||
cb({
|
||||
msg,
|
||||
channel: channelWrapper,
|
||||
ackMsg: job => channelWrapper.ack(job),
|
||||
nackMsg: job => channelWrapper.nack(job, false, true),
|
||||
sendToSenderQueue: data => channelWrapper.sendToQueue(senderQueue, data, { persistent: true }),
|
||||
scheduleForRetry: async (data, msgRetries = 0) => {
|
||||
await generateRetry({
|
||||
data,
|
||||
msgRetries,
|
||||
channelWrapper,
|
||||
channel,
|
||||
queueName,
|
||||
deadLetterExchange
|
||||
})
|
||||
}
|
||||
})
|
||||
)
|
||||
])
|
||||
})
|
||||
}
|
||||
|
||||
async function generateRetry({ data, msgRetries, channelWrapper, channel, queueName, deadLetterExchange }) {
|
||||
const retries = msgRetries + 1
|
||||
const delay = getRetrySequence(retries) * 1000
|
||||
@@ -78,6 +121,7 @@ async function generateRetry({ data, msgRetries, channelWrapper, channel, queueN
|
||||
module.exports = {
|
||||
connectWatcherToQueue,
|
||||
connectSenderToQueue,
|
||||
connectWorkerToQueue,
|
||||
connection,
|
||||
generateRetry
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ module.exports = {
|
||||
DEFAULT_GAS_PRICE_FACTOR: 1,
|
||||
EXIT_CODES: {
|
||||
GENERAL_ERROR: 1,
|
||||
WATCHER_NOT_REQUIRED: 0,
|
||||
INCOMPATIBILITY: 10,
|
||||
MAX_TIME_REACHED: 11
|
||||
},
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
31
oracle/src/utils/tokenState.js
Normal file
31
oracle/src/utils/tokenState.js
Normal file
@@ -0,0 +1,31 @@
|
||||
async function getTokensState(bridgeContract, logger) {
|
||||
const context = {}
|
||||
try {
|
||||
logger.debug('Getting bridgeable token address')
|
||||
context.bridgeableTokenAddress = await bridgeContract.methods.erc20token().call()
|
||||
logger.debug({ address: context.bridgeableTokenAddress }, 'Token address obtained')
|
||||
} catch (e) {
|
||||
throw new Error(`Bridgeable token address cannot be obtained`)
|
||||
}
|
||||
|
||||
try {
|
||||
logger.debug('Getting Half Duplex token address')
|
||||
const halfDuplexErc20tokenAddress = await bridgeContract.methods.halfDuplexErc20token().call()
|
||||
logger.debug({ address: halfDuplexErc20tokenAddress }, 'Half Duplex token address obtained')
|
||||
if (halfDuplexErc20tokenAddress !== context.bridgeableTokenAddress) {
|
||||
context.halfDuplexTokenAddress = halfDuplexErc20tokenAddress
|
||||
} else {
|
||||
logger.info('Migration to support two tokens was not applied')
|
||||
context.idle = true
|
||||
}
|
||||
} catch (e) {
|
||||
logger.info('Old version of contracts is used')
|
||||
context.idle = true
|
||||
}
|
||||
|
||||
return context
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getTokensState
|
||||
}
|
||||
@@ -21,13 +21,21 @@ 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 processHalfDuplexTransfers = require('./events/processHalfDuplexTransfers')(config)
|
||||
const processAMBSignatureRequests = require('./events/processAMBSignatureRequests')(config)
|
||||
const processAMBCollectedSignatures = require('./events/processAMBCollectedSignatures')(config)
|
||||
const processAMBAffirmationRequests = require('./events/processAMBAffirmationRequests')(config)
|
||||
|
||||
const { getTokensState } = require('./utils/tokenState')
|
||||
|
||||
const ZERO = toBN(0)
|
||||
const ONE = toBN(1)
|
||||
|
||||
const web3Instance = config.web3
|
||||
const bridgeContract = new web3Instance.eth.Contract(config.bridgeAbi, config.bridgeContractAddress)
|
||||
const eventContract = new web3Instance.eth.Contract(config.eventAbi, config.eventContractAddress)
|
||||
let { eventContractAddress } = config
|
||||
let eventContract = new web3Instance.eth.Contract(config.eventAbi, eventContractAddress)
|
||||
let skipEvents = config.idle
|
||||
const lastBlockRedisKey = `${config.id}:lastProcessedBlock`
|
||||
let lastProcessedBlock = BN.max(config.startBlock.sub(ONE), ZERO)
|
||||
|
||||
@@ -41,6 +49,7 @@ async function initialize() {
|
||||
await getLastProcessedBlock()
|
||||
connectWatcherToQueue({
|
||||
queueName: config.queue,
|
||||
workerQueue: config.workerQueue,
|
||||
cb: runMain
|
||||
})
|
||||
} catch (e) {
|
||||
@@ -49,16 +58,16 @@ async function initialize() {
|
||||
}
|
||||
}
|
||||
|
||||
async function runMain({ sendToQueue }) {
|
||||
async function runMain({ sendToQueue, sendToWorker }) {
|
||||
try {
|
||||
if (connection.isConnected() && redis.status === 'ready') {
|
||||
if (config.maxProcessingTime) {
|
||||
await watchdog(() => main({ sendToQueue }), config.maxProcessingTime, () => {
|
||||
await watchdog(() => main({ sendToQueue, sendToWorker }), config.maxProcessingTime, () => {
|
||||
logger.fatal('Max processing time reached')
|
||||
process.exit(EXIT_CODES.MAX_TIME_REACHED)
|
||||
})
|
||||
} else {
|
||||
await main({ sendToQueue })
|
||||
await main({ sendToQueue, sendToWorker })
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -66,7 +75,7 @@ async function runMain({ sendToQueue }) {
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
runMain({ sendToQueue })
|
||||
runMain({ sendToQueue, sendToWorker })
|
||||
}, config.pollingInterval)
|
||||
}
|
||||
|
||||
@@ -81,7 +90,7 @@ function updateLastProcessedBlock(lastBlockNumber) {
|
||||
return redis.set(lastBlockRedisKey, lastProcessedBlock.toString())
|
||||
}
|
||||
|
||||
function processEvents(events) {
|
||||
function processEvents(events, blockNumber) {
|
||||
switch (config.id) {
|
||||
case 'native-erc-signature-request':
|
||||
case 'erc-erc-signature-request':
|
||||
@@ -93,15 +102,50 @@ function processEvents(events) {
|
||||
return processCollectedSignatures(events)
|
||||
case 'native-erc-affirmation-request':
|
||||
case 'erc677-erc677-affirmation-request':
|
||||
return processAffirmationRequests(events)
|
||||
case 'erc-erc-affirmation-request':
|
||||
case 'erc-native-affirmation-request':
|
||||
case 'erc-erc-affirmation-request':
|
||||
return processAffirmationRequests(events)
|
||||
case 'erc-erc-transfer':
|
||||
case 'erc-native-transfer':
|
||||
return processTransfers(events)
|
||||
case 'erc-native-half-duplex-transfer':
|
||||
return processHalfDuplexTransfers(events, blockNumber)
|
||||
case 'amb-signature-request':
|
||||
return processAMBSignatureRequests(events)
|
||||
case 'amb-collected-signatures':
|
||||
return processAMBCollectedSignatures(events)
|
||||
case 'amb-affirmation-request':
|
||||
return processAMBAffirmationRequests(events)
|
||||
default:
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
async function checkConditions() {
|
||||
let state
|
||||
switch (config.id) {
|
||||
case 'erc-native-transfer':
|
||||
logger.debug('Getting token address to listen Transfer events')
|
||||
state = await getTokensState(bridgeContract, logger)
|
||||
updateEventContract(state.bridgeableTokenAddress)
|
||||
break
|
||||
case 'erc-native-half-duplex-transfer':
|
||||
logger.debug('Getting Half Duplex token address to listen Transfer events')
|
||||
state = await getTokensState(bridgeContract, logger)
|
||||
skipEvents = state.idle
|
||||
updateEventContract(state.halfDuplexTokenAddress)
|
||||
break
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
function updateEventContract(address) {
|
||||
if (eventContractAddress !== address) {
|
||||
eventContractAddress = address
|
||||
eventContract = new web3Instance.eth.Contract(config.eventAbi, eventContractAddress)
|
||||
}
|
||||
}
|
||||
|
||||
async function getLastBlockToProcess() {
|
||||
const lastBlockNumberPromise = getBlockNumber(web3Instance).then(toBN)
|
||||
const requiredBlockConfirmationsPromise = getRequiredBlockConfirmations(bridgeContract).then(toBN)
|
||||
@@ -113,8 +157,15 @@ async function getLastBlockToProcess() {
|
||||
return lastBlockNumber.sub(requiredBlockConfirmations)
|
||||
}
|
||||
|
||||
async function main({ sendToQueue }) {
|
||||
async function main({ sendToQueue, sendToWorker }) {
|
||||
try {
|
||||
await checkConditions()
|
||||
|
||||
if (skipEvents) {
|
||||
logger.debug('Watcher in idle mode, skipping getting events')
|
||||
return
|
||||
}
|
||||
|
||||
const lastBlockToProcess = await getLastBlockToProcess()
|
||||
|
||||
if (lastBlockToProcess.lte(lastProcessedBlock)) {
|
||||
@@ -135,7 +186,11 @@ async function main({ sendToQueue }) {
|
||||
logger.info(`Found ${events.length} ${config.event} events`)
|
||||
|
||||
if (events.length) {
|
||||
const job = await processEvents(events)
|
||||
if (sendToWorker) {
|
||||
await sendToWorker({ blockNumber: toBlock.toString() })
|
||||
}
|
||||
|
||||
const job = await processEvents(events, toBlock.toString())
|
||||
logger.info('Transactions to send:', job.length)
|
||||
|
||||
if (job.length) {
|
||||
|
||||
73
oracle/src/worker.js
Normal file
73
oracle/src/worker.js
Normal file
@@ -0,0 +1,73 @@
|
||||
const path = require('path')
|
||||
const logger = require('./services/logger')
|
||||
const rpcUrlsManager = require('./services/getRpcUrlsManager')
|
||||
const { checkHTTPS, watchdog } = require('./utils/utils')
|
||||
const { EXIT_CODES } = require('./utils/constants')
|
||||
const { connectWorkerToQueue } = require('./services/amqpClient')
|
||||
|
||||
const config = require(path.join('../config/', process.argv[2]))
|
||||
|
||||
const swapTokens = require('./workers/swapTokens')(config)
|
||||
|
||||
async function initialize() {
|
||||
try {
|
||||
const checkHttps = checkHTTPS(process.env.ORACLE_ALLOW_HTTP_FOR_RPC, logger)
|
||||
|
||||
rpcUrlsManager.homeUrls.forEach(checkHttps('home'))
|
||||
rpcUrlsManager.foreignUrls.forEach(checkHttps('foreign'))
|
||||
|
||||
connectWorkerToQueue({
|
||||
queueName: config.workerQueue,
|
||||
senderQueue: config.senderQueue,
|
||||
cb: options => {
|
||||
if (config.maxProcessingTime) {
|
||||
return watchdog(() => main(options), config.maxProcessingTime, () => {
|
||||
logger.fatal('Max processing time reached')
|
||||
process.exit(EXIT_CODES.MAX_TIME_REACHED)
|
||||
})
|
||||
}
|
||||
|
||||
return main(options)
|
||||
}
|
||||
})
|
||||
} catch (e) {
|
||||
logger.error(e.message)
|
||||
process.exit(EXIT_CODES.GENERAL_ERROR)
|
||||
}
|
||||
}
|
||||
|
||||
async function run(blockNumber) {
|
||||
if (config.id === 'erc-native-swap-tokens') {
|
||||
return swapTokens(blockNumber)
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
async function main({ msg, ackMsg, nackMsg, sendToSenderQueue, scheduleForRetry }) {
|
||||
try {
|
||||
const { blockNumber } = JSON.parse(msg.content)
|
||||
logger.info(`Msg received with block number ${blockNumber}`)
|
||||
|
||||
try {
|
||||
const job = await run(blockNumber)
|
||||
|
||||
logger.info('Transactions to send:', job.length)
|
||||
|
||||
if (job.length) {
|
||||
await sendToSenderQueue(job)
|
||||
}
|
||||
} catch (e) {
|
||||
logger.info(`Sending failed msg to retry`)
|
||||
await scheduleForRetry({ blockNumber }, msg.properties.headers['x-retries'])
|
||||
}
|
||||
|
||||
ackMsg(msg)
|
||||
} catch (e) {
|
||||
logger.error(e)
|
||||
nackMsg(msg)
|
||||
}
|
||||
logger.debug(`Finished worker operation`)
|
||||
}
|
||||
|
||||
initialize()
|
||||
111
oracle/src/workers/swapTokens.js
Normal file
111
oracle/src/workers/swapTokens.js
Normal file
@@ -0,0 +1,111 @@
|
||||
require('../../env')
|
||||
const { HttpListProviderError } = require('http-list-provider')
|
||||
const rootLogger = require('../services/logger')
|
||||
const { web3Foreign } = require('../services/web3')
|
||||
|
||||
const { BRIDGE_VALIDATORS_ABI, ERC20_ABI } = require('../../../commons')
|
||||
|
||||
let validatorContract = null
|
||||
let halfDuplexTokenContract = null
|
||||
|
||||
function swapTokensBuilder(config) {
|
||||
const foreignBridge = new web3Foreign.eth.Contract(config.foreignBridgeAbi, config.foreignBridgeAddress)
|
||||
|
||||
return async function swapTokens(blockNumber) {
|
||||
const txToSend = []
|
||||
|
||||
const logger = rootLogger.child({
|
||||
blockNumber: blockNumber.toString()
|
||||
})
|
||||
|
||||
logger.debug(`Starting swap tokens operation`)
|
||||
|
||||
if (validatorContract === null) {
|
||||
logger.debug('Getting validator contract address')
|
||||
const validatorContractAddress = await foreignBridge.methods.validatorContract().call()
|
||||
logger.debug({ validatorContractAddress }, 'Validator contract address obtained')
|
||||
|
||||
validatorContract = new web3Foreign.eth.Contract(BRIDGE_VALIDATORS_ABI, validatorContractAddress)
|
||||
}
|
||||
|
||||
logger.debug(`Checking if is validator duty`)
|
||||
const validatorDuty = await validatorContract.methods.isValidatorDuty(config.validatorAddress).call()
|
||||
|
||||
if (!validatorDuty) {
|
||||
logger.info(`Token swap discarded because is not validator duty`)
|
||||
return txToSend
|
||||
}
|
||||
|
||||
logger.debug(`Checking if half duplex token balance is above the threshold`)
|
||||
const hdTokenBalanceAboveMinBalance = await foreignBridge.methods.isHDTokenBalanceAboveMinBalance().call()
|
||||
|
||||
if (!hdTokenBalanceAboveMinBalance) {
|
||||
logger.info(`Token swap discarded because half duplex balance is below the threshold`)
|
||||
return txToSend
|
||||
}
|
||||
|
||||
const block = await web3Foreign.eth.getBlock(blockNumber)
|
||||
logger.debug({ timestamp: block.timestamp }, `Block obtained`)
|
||||
|
||||
logger.debug(`Checking if SCD Emergency Shutdown has happened`)
|
||||
const tokenSwapAllowed = await foreignBridge.methods.isTokenSwapAllowed(block.timestamp).call()
|
||||
|
||||
if (!tokenSwapAllowed) {
|
||||
logger.info(`Token swap discarded because SCD Emergency Shutdown has happened`)
|
||||
return txToSend
|
||||
}
|
||||
|
||||
let gasEstimate
|
||||
|
||||
try {
|
||||
logger.debug(`Estimate gas`)
|
||||
gasEstimate = await foreignBridge.methods.swapTokens().estimateGas({
|
||||
from: config.validatorAddress
|
||||
})
|
||||
|
||||
logger.debug({ gasEstimate }, 'Gas estimated')
|
||||
} catch (e) {
|
||||
if (e instanceof HttpListProviderError) {
|
||||
const errorMsg = 'RPC Connection Error: swapTokens Gas Estimate cannot be obtained.'
|
||||
logger.error(e, errorMsg)
|
||||
throw new Error(errorMsg)
|
||||
} else {
|
||||
if (halfDuplexTokenContract === null) {
|
||||
logger.debug('Getting half duplex token contract address')
|
||||
const halfDuplexErc20Token = await foreignBridge.methods.halfDuplexErc20token().call()
|
||||
logger.debug({ halfDuplexErc20Token }, 'Half duplex token contract address obtained')
|
||||
|
||||
halfDuplexTokenContract = new web3Foreign.eth.Contract(ERC20_ABI, halfDuplexErc20Token)
|
||||
}
|
||||
|
||||
const balance = web3Foreign.utils.toBN(
|
||||
await halfDuplexTokenContract.methods.balanceOf(config.foreignBridgeAddress).call()
|
||||
)
|
||||
logger.debug({ balance: balance.toString() }, 'Half duplex token bridge balance obtained')
|
||||
|
||||
if (balance.isZero()) {
|
||||
logger.info(`Gas estimate failed because half duplex token balance is zero. Tokens swap is discarded.`)
|
||||
return txToSend
|
||||
}
|
||||
|
||||
logger.error(e, 'Unknown error while processing transaction')
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
// generate data
|
||||
const data = await foreignBridge.methods.swapTokens().encodeABI()
|
||||
|
||||
// push to job
|
||||
txToSend.push({
|
||||
data,
|
||||
gasEstimate,
|
||||
transactionReference: `swap tokens operation for block number ${blockNumber.toString()}`,
|
||||
to: config.foreignBridgeAddress
|
||||
})
|
||||
|
||||
return txToSend
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = swapTokensBuilder
|
||||
204
oracle/upgrade/oracle-upgrade-1.1.1-to-1.2.0-rc0.sh
Normal file
204
oracle/upgrade/oracle-upgrade-1.1.1-to-1.2.0-rc0.sh
Normal file
@@ -0,0 +1,204 @@
|
||||
#!/bin/bash
|
||||
|
||||
orig_service="/etc/init.d/poabridge"
|
||||
new_service=${orig_service}
|
||||
|
||||
dockercompose_file="docker-compose-erc-native.yml"
|
||||
ref_dockercompose_file="docker-compose.yml"
|
||||
|
||||
source_dockerimage="poanetwork/tokenbridge-oracle:1.2.0-rc0"
|
||||
|
||||
old_oracle_services="bridge_affirmation bridge_collected bridge_request bridge_senderforeign bridge_senderhome bridge_transfer"
|
||||
new_oracle_services="bridge_affirmation bridge_collected bridge_half_duplex_transfer bridge_request bridge_senderforeign bridge_senderhome bridge_swap_tokens_worker bridge_transfer"
|
||||
|
||||
echo "Looking for the service file"
|
||||
if [ ! -f ${orig_service} ]; then
|
||||
echo "Service file ${orig_service} not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "Indentifying the directory with docker-compose files"
|
||||
oracle_dir=`cat ${orig_service} | grep 'WORKDIR=' | sed 's/WORKDIR="//' | tr -d '"'`
|
||||
if [ -z "${oracle_dir}" ]; then
|
||||
echo "Cannot identify the directory"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "Updating the service file"
|
||||
if [ ! -f ${new_service}".bak" ]; then
|
||||
cp --preserve=all -f ${new_service} ${new_service}".bak"
|
||||
fi
|
||||
|
||||
if [ ! "${orig_service}" == "${new_service}" ]; then
|
||||
cp --preserve=all ${orig_service} ${new_service}
|
||||
fi
|
||||
sed -i 's/-f docker-compose-transfer.yml/-f docker-compose-erc-native.yml/' ${new_service}
|
||||
sed -i 's/rebuild)/isnotsupported)/' ${new_service}
|
||||
if [ -z "`grep 'f docker-compose-erc-native.yml' ${new_service}`" ]; then
|
||||
echo "The service file was not updated properly"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "Re-enable service file"
|
||||
systemctl daemon-reload
|
||||
if [ ! "$?" == "0" ]; then
|
||||
echo "An error during the service file reload"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "Generate a new docker-compose file"
|
||||
cat <<tEOF > ${oracle_dir}/${dockercompose_file}
|
||||
networks:
|
||||
net_db_bridge_affirmation: {driver: bridge}
|
||||
net_db_bridge_collected: {driver: bridge}
|
||||
net_db_bridge_half_duplex_transfer: {driver: bridge}
|
||||
net_db_bridge_request: {driver: bridge}
|
||||
net_db_bridge_senderforeign: {driver: bridge}
|
||||
net_db_bridge_senderhome: {driver: bridge}
|
||||
net_db_bridge_transfer: {driver: bridge}
|
||||
net_rabbit_bridge_affirmation: {driver: bridge}
|
||||
net_rabbit_bridge_collected: {driver: bridge}
|
||||
net_rabbit_bridge_half_duplex_transfer: {driver: bridge}
|
||||
net_rabbit_bridge_request: {driver: bridge}
|
||||
net_rabbit_bridge_senderforeign: {driver: bridge}
|
||||
net_rabbit_bridge_senderhome: {driver: bridge}
|
||||
net_rabbit_bridge_swap_tokens_worker: {driver: bridge}
|
||||
net_rabbit_bridge_transfer: {driver: bridge}
|
||||
services:
|
||||
bridge_affirmation:
|
||||
extends: {file: docker-compose.yml, service: bridge_affirmation}
|
||||
logging:
|
||||
driver: syslog
|
||||
options: {tag: '{{.Name}}/{{.ID}}'}
|
||||
networks: [net_db_bridge_request, net_rabbit_bridge_request]
|
||||
depends_on:
|
||||
- redis
|
||||
- rabbit
|
||||
bridge_collected:
|
||||
extends: {file: docker-compose.yml, service: bridge_collected}
|
||||
logging:
|
||||
driver: syslog
|
||||
options: {tag: '{{.Name}}/{{.ID}}'}
|
||||
networks: [net_db_bridge_request, net_rabbit_bridge_request]
|
||||
depends_on:
|
||||
- redis
|
||||
- rabbit
|
||||
bridge_half_duplex_transfer:
|
||||
build: {context: .., dockerfile: oracle/Dockerfile}
|
||||
cpus: 0.1
|
||||
entrypoint: yarn watcher:half-duplex-transfer
|
||||
env_file: ./.env
|
||||
environment: [NODE_ENV=production, 'ORACLE_VALIDATOR_ADDRESS=\${ORACLE_VALIDATOR_ADDRESS}']
|
||||
logging:
|
||||
driver: syslog
|
||||
options: {tag: '{{.Name}}/{{.ID}}'}
|
||||
mem_limit: 500m
|
||||
networks: [net_db_bridge_half_duplex_transfer, net_rabbit_bridge_half_duplex_transfer]
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- redis
|
||||
- rabbit
|
||||
bridge_request:
|
||||
extends: {file: docker-compose.yml, service: bridge_request}
|
||||
logging:
|
||||
driver: syslog
|
||||
options: {tag: '{{.Name}}/{{.ID}}'}
|
||||
networks: [net_db_bridge_request, net_rabbit_bridge_request]
|
||||
depends_on:
|
||||
- redis
|
||||
- rabbit
|
||||
bridge_senderforeign:
|
||||
extends: {file: docker-compose.yml, service: bridge_senderforeign}
|
||||
logging:
|
||||
driver: syslog
|
||||
options: {tag: '{{.Name}}/{{.ID}}'}
|
||||
networks: [net_db_bridge_request, net_rabbit_bridge_request]
|
||||
depends_on:
|
||||
- redis
|
||||
- rabbit
|
||||
bridge_senderhome:
|
||||
extends: {file: docker-compose.yml, service: bridge_senderhome}
|
||||
logging:
|
||||
driver: syslog
|
||||
options: {tag: '{{.Name}}/{{.ID}}'}
|
||||
networks: [net_db_bridge_request, net_rabbit_bridge_request]
|
||||
depends_on:
|
||||
- redis
|
||||
- rabbit
|
||||
bridge_swap_tokens_worker:
|
||||
build: {context: .., dockerfile: oracle/Dockerfile}
|
||||
cpus: 0.1
|
||||
entrypoint: yarn worker:swap-tokens
|
||||
env_file: ./.env
|
||||
environment: [NODE_ENV=production, 'ORACLE_VALIDATOR_ADDRESS=\${ORACLE_VALIDATOR_ADDRESS}']
|
||||
logging:
|
||||
driver: syslog
|
||||
options: {tag: '{{.Name}}/{{.ID}}'}
|
||||
mem_limit: 500m
|
||||
networks: [net_rabbit_bridge_swap_tokens_worker]
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- rabbit
|
||||
bridge_transfer:
|
||||
build: {context: .., dockerfile: oracle/Dockerfile}
|
||||
cpus: 0.1
|
||||
entrypoint: yarn watcher:transfer
|
||||
env_file: ./.env
|
||||
environment: [NODE_ENV=production, 'ORACLE_VALIDATOR_ADDRESS=\${ORACLE_VALIDATOR_ADDRESS}']
|
||||
logging:
|
||||
driver: syslog
|
||||
options: {tag: '{{.Name}}/{{.ID}}'}
|
||||
mem_limit: 500m
|
||||
networks: [net_db_bridge_transfer, net_rabbit_bridge_transfer]
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- redis
|
||||
- rabbit
|
||||
rabbit:
|
||||
extends: {file: docker-compose.yml, service: rabbit}
|
||||
logging:
|
||||
driver: syslog
|
||||
options: {tag: '{{.Name}}/{{.ID}}'}
|
||||
networks: [net_rabbit_bridge_transfer, net_rabbit_bridge_swap_tokens_worker, net_rabbit_bridge_half_duplex_transfer]
|
||||
redis:
|
||||
extends: {file: docker-compose.yml, service: redis}
|
||||
logging:
|
||||
driver: syslog
|
||||
options: {tag: '{{.Name}}/{{.ID}}'}
|
||||
networks: [net_db_bridge_transfer, net_db_bridge_half_duplex_transfer]
|
||||
version: '2.4'
|
||||
tEOF
|
||||
|
||||
if [ ! -f ${oracle_dir}/${dockercompose_file} ]; then
|
||||
echo "New compose file was not generated"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
chmod --reference=${oracle_dir}/${ref_dockercompose_file} ${oracle_dir}/${dockercompose_file}
|
||||
chown --reference=${oracle_dir}/${ref_dockercompose_file} ${oracle_dir}/${dockercompose_file}
|
||||
|
||||
echo
|
||||
echo "Delete previous docker images"
|
||||
for i in ${old_oracle_services}; do
|
||||
docker rmi "oracle_"${i}
|
||||
done
|
||||
|
||||
echo
|
||||
echo "Pull the docker image for 1.2.0-rc0"
|
||||
docker pull ${source_dockerimage}
|
||||
|
||||
docker inspect ${source_dockerimage} >/dev/null
|
||||
if [ ! "$?" == "0" ]; then
|
||||
echo "An error with pulling of the docker image"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "Create new set of docker images"
|
||||
for i in ${new_oracle_services}; do
|
||||
docker tag ${source_dockerimage} "oracle_"${i}
|
||||
done
|
||||
@@ -36,7 +36,7 @@
|
||||
"initialize": "yarn clean && git submodule update --init && yarn install --unsafe-perm --frozen-lockfile && yarn install:deploy && yarn compile:contracts",
|
||||
"build": "yarn workspace ui run build",
|
||||
"lint": "yarn wsrun --exclude token-bridge-contracts lint",
|
||||
"test": "yarn wsrun --exclude monitor --exclude oracle-e2e --exclude ui-e2e --exclude monitor-e2e test",
|
||||
"test": "yarn wsrun --exclude oracle-e2e --exclude ui-e2e --exclude monitor-e2e test",
|
||||
"oracle-e2e": "./oracle-e2e/run-tests.sh",
|
||||
"ui-e2e": "./ui-e2e/run-tests.sh",
|
||||
"clean": "rm -rf ./node_modules ./**/node_modules ./**/**/node_modules ./**/build",
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -4,9 +4,9 @@
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"selenium-webdriver": "3.6.0",
|
||||
"chromedriver": "^77.0.0",
|
||||
"mocha": "^5.2.0",
|
||||
"chromedriver": "76.0.0"
|
||||
"selenium-webdriver": "3.6.0"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "eslint . --ignore-path ../.eslintignore"
|
||||
|
||||
10
ui/README.md
10
ui/README.md
@@ -52,7 +52,7 @@ The following is an example setup using the POA Sokol testnet as the Home networ
|
||||
|
||||
### Dependencies
|
||||
|
||||
- [Smart Contracts](https://github.com/poanetwork/poa-bridge-contracts)
|
||||
- [Smart Contracts](https://github.com/poanetwork/tokenbridge-contracts)
|
||||
- [Oracle](../oracle/README.md)
|
||||
- [Node.js](https://nodejs.org/en/download/)
|
||||
- [AlphaWallet](https://alphawallet.com/) or [Nifty Wallet](https://github.com/poanetwork/nifty-wallet) or [MetaMask](https://metamask.io/)
|
||||
@@ -72,8 +72,8 @@ The following is an example setup using the POA Sokol testnet as the Home networ
|
||||
* Get free Kovan Coins from the [gitter channel](https://gitter.im/kovan-testnet/faucet) or [Iracus faucet](https://github.com/kovan-testnet/faucet) for Foreign account(s). Get 5 Keth to 1 acc, and transfer from there to all other wallets if more than one account is used.
|
||||
|
||||
4. Deploy the Sokol <-> Kovan Bridge contracts.
|
||||
* Go to the the `sokol-kovan-bridge` folder created in step 1 and `git clone https://github.com/poanetwork/poa-bridge-contracts`
|
||||
* Follow instructions in the [POA Bridge contracts repo](https://github.com/poanetwork/poa-bridge-contracts).
|
||||
* Go to the the `sokol-kovan-bridge` folder created in step 1 and `git clone https://github.com/poanetwork/tokenbridge-contracts`
|
||||
* Follow instructions in the [POA Bridge contracts repo](https://github.com/poanetwork/tokenbridge-contracts).
|
||||
* Set the parameters in the .env file.
|
||||
* `DEPLOYMENT_ACCOUNT_PRIVATE_KEY`: Export the private key from step 2
|
||||
* `HOME_RPC_URL`=https://sokol.poa.network
|
||||
@@ -86,7 +86,7 @@ The following is an example setup using the POA Sokol testnet as the Home networ
|
||||
* `FOREIGN_UPGRADEABLE_ADMIN_BRIDGE`
|
||||
* `VALIDATORS` _Note: Wallet address(es) for validator(s) are separated by a space. For testing, you can use the same address that was used as the bridge contracts management account._
|
||||
* `FOREIGN_RPC_URL`=https://kovan.infura.io/mew
|
||||
* When deployment is finished, check that the `bridgeDeploymentResults.json` file exists in the `poa-bridge-contracts/deploy` directory and includes the bridge contract addresses.
|
||||
* When deployment is finished, check that the `bridgeDeploymentResults.json` file exists in the `tokenbridge-contracts/deploy` directory and includes the bridge contract addresses.
|
||||
|
||||
5. Install and run the TokenBridge Oracle.
|
||||
* Go to the `sokol-kovan-bridge` folder
|
||||
@@ -125,7 +125,7 @@ cp .env.example .env
|
||||
````
|
||||
* Insert the addresses from the bridgeDeploymentResults.json file (from step 4) into the .env file. No other changes are needed, see [Env Parameter Details](#env-parameter-details) for information about each parameter.
|
||||
```
|
||||
cat ../poa-bridge-contracts/deploy/bridgeDeploymentResults.json
|
||||
cat ../tokenbridge-contracts/deploy/bridgeDeploymentResults.json
|
||||
```
|
||||
|
||||
```bash
|
||||
|
||||
@@ -60,7 +60,7 @@ class GasPriceStore {
|
||||
|
||||
@computed
|
||||
get gasPriceInHex() {
|
||||
return toHex(this.gasPrice)
|
||||
return toHex(this.gasPrice.toString())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ class TxStore {
|
||||
try {
|
||||
return this.web3Store.getWeb3Promise.then(async () => {
|
||||
if (this.web3Store.defaultAccount.address) {
|
||||
const data = await contract.methods.transferAndCall(to, value, '0x00').encodeABI()
|
||||
const data = await contract.methods.transferAndCall(to, value, '0x').encodeABI()
|
||||
return this.doSend({ to: tokenAddress, from, value: '0x00', data, sentValue: value })
|
||||
} else {
|
||||
this.alertStore.pushError('Please unlock wallet')
|
||||
|
||||
Reference in New Issue
Block a user