Merge the develop branch to the master branch, preparation to v2.2.0

This commit is contained in:
Alexander Kolotov 2020-06-08 16:48:28 +03:00 committed by GitHub
commit 8e10a5d609
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
192 changed files with 4349 additions and 835 deletions

@ -314,3 +314,8 @@ workflows:
scenario-name: amb
redis-key: amb-collected-signatures:lastProcessedBlock
oracle-e2e-script: "amb"
- ultimate:
name: "ultimate: amb stake erc to erc"
scenario-name: ultimate-amb-stake-erc-to-erc
redis-key: amb-collected-signatures:lastProcessedBlock
ui-e2e-grep: "AMB-STAKE-ERC-TO-ERC"

@ -9,6 +9,8 @@
**/*.md
contracts/test
contracts/build
oracle/test
oracle/**/*.png
oracle/**/*.jpg
audit

1
.gitignore vendored

@ -49,4 +49,5 @@ __pycache__
#monitor
monitor/responses/*
monitor/configs/*.env
!monitor/.gitkeep

@ -43,6 +43,7 @@ ORACLE_VALIDATOR_ADDRESS | The public address of the bridge validator | hexideci
name | description | value
--- | --- | ---
UI_TITLE | The title for the bridge UI page. `%c` will be replaced by the name of the network. | string
UI_OG_TITLE | The meta title for the deployed bridge page. | string
UI_DESCRIPTION | The meta description for the deployed bridge page. | string
UI_NATIVE_TOKEN_DISPLAY_NAME | name of the home native coin | string
UI_HOME_NETWORK_DISPLAY_NAME | name to be displayed for home network | string
@ -56,7 +57,8 @@ UI_FOREIGN_EXPLORER_ADDRESS_TEMPLATE | template link to address on foreign explo
UI_HOME_GAS_PRICE_UPDATE_INTERVAL | An interval in milliseconds used to get the updated gas price value either from the oracle or from the Home Bridge contract. | integer
UI_FOREIGN_GAS_PRICE_UPDATE_INTERVAL | An interval in milliseconds used to get the updated gas price value either from the oracle or from the Foreign Bridge contract. | integer
UI_PORT | The port for the UI app. | integer
UI_STYLES | The set of styles to render the bridge UI page. Currently only `classic` is implemented | classic
UI_STYLES | The set of styles to render the bridge UI page. | core/classic/stake
UI_PUBLIC_URL | The public url for the deployed bridge page | string
## Monitor configuration

@ -29,6 +29,8 @@ Sub-repositories maintained within this monorepo are listed below.
| [Deployment-E2E](deployment-e2e/README.md) | End to end tests for the Deployment |
| [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 |
| [ALM](alm/README.md) | DApp interface tool for AMB Live Monitoring |
| [Burner-wallet-plugin](burner-wallet-plugin/README.md) | TokenBridge Burner Wallet 2 Plugin |
Additionally there are [Smart Contracts](https://github.com/poanetwork/tokenbridge-contracts) used to manage bridge validators, collect signatures, and confirm asset relay and disposal.

5
alm/.env.example Normal file

@ -0,0 +1,5 @@
COMMON_HOME_BRIDGE_ADDRESS=0xFe446bEF1DbF7AFE24E81e05BC8B271C1BA9a560
COMMON_FOREIGN_BRIDGE_ADDRESS=0xFe446bEF1DbF7AFE24E81e05BC8B271C1BA9a560
COMMON_HOME_RPC_URL=https://sokol.poa.network
COMMON_FOREIGN_RPC_URL=https://kovan.infura.io/v3/

6
alm/.eslintrc.js Normal file

@ -0,0 +1,6 @@
module.exports = {
extends: [
"react-app",
"../.eslintrc"
]
}

23
alm/.gitignore vendored Normal file

@ -0,0 +1,23 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

23
alm/Dockerfile Normal file

@ -0,0 +1,23 @@
FROM node:12
WORKDIR /mono
COPY package.json .
COPY contracts/package.json ./contracts/
COPY commons/package.json ./commons/
COPY alm/package.json ./alm/
COPY yarn.lock .
RUN yarn install --production --frozen-lockfile
COPY ./contracts ./contracts
RUN yarn run compile:contracts
RUN mv ./contracts/build ./ && rm -rf ./contracts/* ./contracts/.[!.]* && mv ./build ./contracts/
COPY ./commons ./commons
COPY ./alm ./alm
ARG DOT_ENV_PATH=./alm/.env
COPY ${DOT_ENV_PATH} ./alm/.env
WORKDIR /mono/alm
CMD echo "To start the application run:" \
"yarn start"

46
alm/README.md Normal file

@ -0,0 +1,46 @@
# AMB Live Monitoring
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
## Available Scripts
In the project directory, you can run:
### `yarn start`
Runs the app in the development mode.<br />
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
The page will reload if you make edits.<br />
You will also see any lint errors in the console.
### `yarn test`
Launches the test runner in the interactive watch mode.<br />
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
### `yarn build`
Builds the app for production to the `build` folder.<br />
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.<br />
Your app is ready to be deployed!
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
### `yarn eject`
**Note: this is a one-way operation. Once you `eject`, you cant go back!**
If you arent satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point youre on your own.
You dont have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldnt feel obligated to use this feature. However we understand that this tool wouldnt be useful if you couldnt customize it when you are ready for it.
## Learn More
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
To learn React, check out the [React documentation](https://reactjs.org/).

9
alm/config-overrides.js Normal file

@ -0,0 +1,9 @@
const { override, disableEsLint } = require('customize-cra')
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin')
const disableModuleScopePlugin = () => config => {
config.resolve.plugins = config.resolve.plugins.filter(plugin => !(plugin instanceof ModuleScopePlugin))
return config
}
module.exports = override(disableEsLint(), disableModuleScopePlugin())

12
alm/docker-compose.yml Normal file

@ -0,0 +1,12 @@
---
version: '2.4'
services:
alm:
build:
context: ..
dockerfile: alm/Dockerfile
env_file: ./.env
environment:
- NODE_ENV=production
restart: unless-stopped
entrypoint: yarn start

16
alm/load-env.sh Executable file

@ -0,0 +1,16 @@
#!/bin/bash
while read line; do
if [ "$line" = "" ]; then
: # Skip empty lines
elif [[ "$line" =~ \#.* ]]; then
: # Skip comment lines
elif [[ "$line" =~ "UI_PORT"* ]]; then
eval $line
export PORT="$UI_PORT"
else
export "REACT_APP_$line"
fi
done < '.env'
$*

45
alm/package.json Normal file

@ -0,0 +1,45 @@
{
"name": "alm",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"@types/jest": "^24.0.0",
"@types/node": "^12.0.0",
"@types/react": "^16.9.0",
"@types/react-dom": "^16.9.0",
"customize-cra": "^1.0.0",
"react": "^16.13.1",
"react-app-rewired": "^2.1.6",
"react-dom": "^16.13.1",
"react-scripts": "3.0.1",
"typescript": "^3.5.2"
},
"scripts": {
"start": "./load-env.sh react-app-rewired start",
"build": "./load-env.sh react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-app-rewired eject",
"lint": "eslint '*/**/*.{js,ts,tsx}' --ignore-path ../.eslintignore"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"eslint-plugin-prettier": "^3.1.3"
}
}

BIN
alm/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

43
alm/public/index.html Normal file

@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

BIN
alm/public/logo192.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

BIN
alm/public/logo512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

25
alm/public/manifest.json Normal file

@ -0,0 +1,25 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

3
alm/public/robots.txt Normal file

@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

14
alm/src/App.css Normal file

@ -0,0 +1,14 @@
.App {
text-align: center;
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}

9
alm/src/App.test.tsx Normal file

@ -0,0 +1,9 @@
import React from 'react'
import { render } from '@testing-library/react'
import App from './App'
test('renders learn react link', () => {
const { getByText } = render(<App />)
const linkElement = getByText(/AMB Live Monitoring/i)
expect(linkElement).toBeInTheDocument()
})

14
alm/src/App.tsx Normal file

@ -0,0 +1,14 @@
import React from 'react'
import './App.css'
function App() {
return (
<div className="App">
<header className="App-header">
<p>AMB Live Monitoring</p>
</header>
</div>
)
}
export default App

8
alm/src/index.css Normal file

@ -0,0 +1,8 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

11
alm/src/index.tsx Normal file

@ -0,0 +1,11 @@
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
import App from './App'
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
)

1
alm/src/react-app-env.d.ts vendored Normal file

@ -0,0 +1 @@
/// <reference types="react-scripts" />

5
alm/src/setupTests.ts Normal file

@ -0,0 +1,5 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom/extend-expect'

25
alm/tsconfig.json Normal file

@ -0,0 +1,25 @@
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "preserve"
},
"include": [
"src"
]
}

@ -18,7 +18,10 @@
"lib": [
"es6",
"dom"
]
],
"types" : [
"node"
]
},
"exclude": [
"node_modules",

@ -7,13 +7,17 @@ const FOREIGN_ERC_TO_NATIVE_ABI = require('../contracts/build/contracts/ForeignB
const ERC20_ABI = require('../contracts/build/contracts/ERC20').abi
const ERC677_ABI = require('../contracts/build/contracts/ERC677').abi
const ERC677_BRIDGE_TOKEN_ABI = require('../contracts/build/contracts/ERC677BridgeToken').abi
const BLOCK_REWARD_ABI = require('../contracts/build/contracts/IBlockReward').abi
const BLOCK_REWARD_ABI = require('../contracts/build/contracts/BlockReward').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_AMB_ERC_TO_ERC_ABI = require('../contracts/build/contracts/HomeAMBErc677ToErc677').abi
const FOREIGN_AMB_ERC_TO_ERC_ABI = require('../contracts/build/contracts/ForeignAMBErc677ToErc677').abi
const HOME_STAKE_ERC_TO_ERC_ABI = require('../contracts/build/contracts/HomeStakeTokenMediator').abi
const FOREIGN_STAKE_ERC_TO_ERC_ABI = require('../contracts/build/contracts/ForeignStakeTokenMediator').abi
const { HOME_V1_ABI, FOREIGN_V1_ABI } = require('./v1Abis')
const { BRIDGE_MODES } = require('./constants')
@ -67,6 +71,12 @@ function getBridgeABIs(bridgeMode) {
} else if (bridgeMode === BRIDGE_MODES.ARBITRARY_MESSAGE) {
HOME_ABI = HOME_AMB_ABI
FOREIGN_ABI = FOREIGN_AMB_ABI
} else if (bridgeMode === BRIDGE_MODES.AMB_ERC_TO_ERC) {
HOME_ABI = HOME_AMB_ERC_TO_ERC_ABI
FOREIGN_ABI = FOREIGN_AMB_ERC_TO_ERC_ABI
} else if (bridgeMode === BRIDGE_MODES.STAKE_AMB_ERC_TO_ERC) {
HOME_ABI = HOME_STAKE_ERC_TO_ERC_ABI
FOREIGN_ABI = FOREIGN_STAKE_ERC_TO_ERC_ABI
} else {
throw new Error(`Unrecognized bridge mode: ${bridgeMode}`)
}
@ -94,5 +104,7 @@ module.exports = {
HOME_AMB_ABI,
FOREIGN_AMB_ABI,
BOX_ABI,
SAI_TOP
SAI_TOP,
HOME_STAKE_ERC_TO_ERC_ABI,
FOREIGN_STAKE_ERC_TO_ERC_ABI
}

@ -3,7 +3,9 @@ const BRIDGE_MODES = {
ERC_TO_ERC: 'ERC_TO_ERC',
ERC_TO_NATIVE: 'ERC_TO_NATIVE',
NATIVE_TO_ERC_V1: 'NATIVE_TO_ERC_V1',
ARBITRARY_MESSAGE: 'ARBITRARY_MESSAGE'
ARBITRARY_MESSAGE: 'ARBITRARY_MESSAGE',
AMB_ERC_TO_ERC: 'AMB_ERC_TO_ERC',
STAKE_AMB_ERC_TO_ERC: 'STAKE_AMB_ERC_TO_ERC'
}
const ERC_TYPES = {
@ -14,6 +16,7 @@ const ERC_TYPES = {
const FEE_MANAGER_MODE = {
ONE_DIRECTION: 'ONE_DIRECTION',
BOTH_DIRECTIONS: 'BOTH_DIRECTIONS',
ONE_DIRECTION_STAKE: 'ONE_DIRECTION_STAKE',
UNDEFINED: 'UNDEFINED'
}

@ -9,14 +9,14 @@ function addTxHashToData({ encodedData, transactionHash }) {
function parseAMBMessage(message) {
message = strip0x(message)
const txHash = `0x${message.slice(0, 64)}`
const messageId = `0x${message.slice(0, 64)}`
const sender = `0x${message.slice(64, 104)}`
const executor = `0x${message.slice(104, 144)}`
return {
sender,
executor,
txHash
messageId
}
}

@ -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(5)
expect(Object.keys(BRIDGE_MODES).length).to.be.equal(7)
})
it('should contain correct number of erc types', () => {

@ -61,4 +61,99 @@ describe('getTokenType', () => {
// Then
expect(type).to.equal(ERC_TYPES.ERC20)
})
it('should return ERC20 if bridgeContract and isBridge are not present', async () => {
// Given
const bridgeAddress = '0xCecBE80Ed3548dE11D7d2D922a36576eA40C4c26'
const contract = {
methods: {
bridgeContract: () => {
return {
call: () => Promise.reject()
}
},
isBridge: () => {
return {
call: () => Promise.reject()
}
}
}
}
// When
const type = await getTokenType(contract, bridgeAddress)
// Then
expect(type).to.equal(ERC_TYPES.ERC20)
})
it('should return ERC677 if isBridge returns true', async () => {
// Given
const bridgeAddress = '0xCecBE80Ed3548dE11D7d2D922a36576eA40C4c26'
const contract = {
methods: {
bridgeContract: () => {
return {
call: () => Promise.reject()
}
},
isBridge: () => {
return {
call: () => Promise.resolve(true)
}
}
}
}
// When
const type = await getTokenType(contract, bridgeAddress)
// Then
expect(type).to.equal(ERC_TYPES.ERC677)
})
it('should return ERC677 if isBridge returns true and bridgeContract not present', async () => {
// Given
const bridgeAddress = '0xCecBE80Ed3548dE11D7d2D922a36576eA40C4c26'
const contract = {
methods: {
isBridge: () => {
return {
call: () => Promise.resolve(true)
}
}
}
}
// When
const type = await getTokenType(contract, bridgeAddress)
// Then
expect(type).to.equal(ERC_TYPES.ERC677)
})
it('should return ERC20 if isBridge returns false', async () => {
// Given
const bridgeAddress = '0xCecBE80Ed3548dE11D7d2D922a36576eA40C4c26'
const contract = {
methods: {
bridgeContract: () => {
return {
call: () => Promise.reject()
}
},
isBridge: () => {
return {
call: () => Promise.resolve(false)
}
}
}
}
// When
const type = await getTokenType(contract, bridgeAddress)
// Then
expect(type).to.equal(ERC_TYPES.ERC20)
})
})

@ -50,20 +50,20 @@ describe('parseAMBMessage', () => {
it('should parse data type 00', () => {
const msgSender = '0x003667154bb32e42bb9e1e6532f19d187fa0082e'
const msgExecutor = '0xf4bef13f9f4f2b203faf0c3cbbaabe1afe056955'
const msgTxHash = '0xbdceda9d8c94838aca10c687da1411a07b1390e88239c0638cb9cc264219cc10'
const msgId = '0xbdceda9d8c94838aca10c687da1411a07b1390e88239c0638cb9cc264219cc10'
const msgGasLimit = '000000000000000000000000000000000000000000000000000000005b877705'
const msgDataType = '00'
const msgData = '0xb1591967aed668a4b27645ff40c444892d91bf5951b382995d4d4f6ee3a2ce03'
const message = `0x${strip0x(msgTxHash)}${strip0x(msgSender)}${strip0x(
const message = `0x${strip0x(msgId)}${strip0x(msgSender)}${strip0x(
msgExecutor
)}${msgGasLimit}${msgDataType}${strip0x(msgData)}`
// when
const { sender, executor, txHash } = parseAMBMessage(message)
const { sender, executor, messageId } = parseAMBMessage(message)
// then
expect(sender).to.be.equal(msgSender)
expect(executor).to.be.equal(msgExecutor)
expect(txHash).to.be.equal(msgTxHash)
expect(messageId).to.be.equal(msgId)
})
})

@ -12,6 +12,10 @@ function decodeBridgeMode(bridgeModeHash) {
return BRIDGE_MODES.ERC_TO_NATIVE
case '0x2544fbb9':
return BRIDGE_MODES.ARBITRARY_MESSAGE
case '0x16ea01e9':
return BRIDGE_MODES.STAKE_AMB_ERC_TO_ERC
case '0x76595b56':
return BRIDGE_MODES.AMB_ERC_TO_ERC
default:
throw new Error(`Unrecognized bridge mode hash: '${bridgeModeHash}'`)
}
@ -46,10 +50,31 @@ const getTokenType = async (bridgeTokenContract, bridgeAddress) => {
return ERC_TYPES.ERC20
}
} catch (e) {
return ERC_TYPES.ERC20
try {
const isBridge = await bridgeTokenContract.methods.isBridge(bridgeAddress).call()
if (isBridge) {
return ERC_TYPES.ERC677
} else {
return ERC_TYPES.ERC20
}
} catch (e) {
return ERC_TYPES.ERC20
}
}
}
const isErcToErcMode = bridgeMode => {
return (
bridgeMode === BRIDGE_MODES.ERC_TO_ERC ||
bridgeMode === BRIDGE_MODES.AMB_ERC_TO_ERC ||
bridgeMode === BRIDGE_MODES.STAKE_AMB_ERC_TO_ERC
)
}
const isMediatorMode = bridgeMode => {
return bridgeMode === BRIDGE_MODES.AMB_ERC_TO_ERC || bridgeMode === BRIDGE_MODES.STAKE_AMB_ERC_TO_ERC
}
const getUnit = bridgeMode => {
let unitHome = null
let unitForeign = null
@ -62,6 +87,9 @@ const getUnit = bridgeMode => {
} else if (bridgeMode === BRIDGE_MODES.ERC_TO_NATIVE) {
unitHome = 'Native coins'
unitForeign = 'Tokens'
} else if (bridgeMode === BRIDGE_MODES.STAKE_AMB_ERC_TO_ERC) {
unitHome = 'Tokens'
unitForeign = 'Tokens'
} else {
throw new Error(`Unrecognized bridge mode: ${bridgeMode}`)
}
@ -260,5 +288,7 @@ module.exports = {
normalizeGasPrice,
gasPriceFromSupplier,
gasPriceFromContract,
gasPriceWithinLimits
gasPriceWithinLimits,
isErcToErcMode,
isMediatorMode
}

@ -1 +1 @@
Subproject commit a7ce4441ab77e1c3e4d01017d862c53516933645
Subproject commit 56c2c39722be96381084834db68f8d7acc4805fa

@ -0,0 +1,14 @@
# Molecule managed
{% if item.registry is defined %}
FROM {{ item.registry.url }}/{{ item.image }}
{% else %}
FROM {{ item.image }}
{% endif %}
RUN if [ $(command -v apt-get) ]; then apt-get update && apt-get install -y python sudo bash ca-certificates && apt-get clean; \
elif [ $(command -v dnf) ]; then dnf makecache && dnf --assumeyes install python sudo python-devel python*-dnf bash && dnf clean all; \
elif [ $(command -v yum) ]; then yum makecache fast && yum install -y python sudo yum-plugin-ovl bash && sed -i 's/plugins=0/plugins=1/g' /etc/yum.conf && yum clean all; \
elif [ $(command -v zypper) ]; then zypper refresh && zypper install -y python sudo bash python-xml && zypper clean -a; \
elif [ $(command -v apk) ]; then apk update && apk add --no-cache python sudo bash ca-certificates; \
elif [ $(command -v xbps-install) ]; then xbps-install -Syu && xbps-install -y python sudo bash ca-certificates && xbps-remove -O; fi

@ -0,0 +1,54 @@
---
driver:
name: docker
platforms:
- name: oracle-amb-host
groups:
- ultimate
- amb
children:
- oracle
image: ubuntu:16.04
privileged: true
network_mode: host
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- name: ui-amb-stake-erc-to-erc-host
groups:
- ultimate
- amb-stake-erc-to-erc
children:
- ui
image: ubuntu:16.04
privileged: true
network_mode: host
volumes:
- /var/run/docker.sock:/var/run/docker.sock
provisioner:
name: ansible
playbooks:
prepare: ../prepare.yml
converge: ../ultimate-commons/converge.yml
inventory:
host_vars:
oracle-amb-host:
COMMON_HOME_RPC_URL: "http://parity1:8545"
COMMON_FOREIGN_RPC_URL: "http://parity2:8545"
ORACLE_VALIDATOR_ADDRESS: "0xaaB52d66283F7A1D5978bcFcB55721ACB467384b"
ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY: "8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9"
ui-amb-stake-erc-to-erc-host:
COMMON_HOME_RPC_URL: "http://localhost:8541"
COMMON_FOREIGN_RPC_URL: "http://localhost:8542"
verifier:
name: testinfra
lint:
name: flake8
scenario:
name: ultimate-amb-stake-erc-to-erc
test_sequence:
- cleanup
- destroy
- syntax
- create
- prepare
- converge

@ -0,0 +1,4 @@
---
COMMON_HOME_BRIDGE_ADDRESS: "0xc26Aa60Ff574f157616D3aEE70e08aAC129E1dFC"
COMMON_FOREIGN_BRIDGE_ADDRESS: "0xc26Aa60Ff574f157616D3aEE70e08aAC129E1dFC"
UI_PORT: 3003

@ -32,6 +32,7 @@ COMMON_FOREIGN_GAS_PRICE_FACTOR: 1
## UI
UI_TITLE: "TokenBridge UI app - %c"
UI_OG_TITLE: "POA Bridge UI"
UI_DESCRIPTION: "The TokenBridge serves as a method of transferring MakerDAO stable tokens between the Ethereum network to xDai chain in a quick and cost-efficient manner."
UI_PORT: 3001
UI_HOME_EXPLORER_TX_TEMPLATE: https://blockscout.com/poa/dai/tx/%s
@ -40,6 +41,8 @@ UI_HOME_EXPLORER_ADDRESS_TEMPLATE: https://blockscout.com/poa/dai/address/%s
UI_FOREIGN_EXPLORER_ADDRESS_TEMPLATE: https://blockscout.com/eth/mainnet/address/%s
UI_HOME_GAS_PRICE_UPDATE_INTERVAL: 600000
UI_FOREIGN_GAS_PRICE_UPDATE_INTERVAL: 600000
UI_STYLES: "core"
UI_PUBLIC_URL: "https://dai-bridge.poa.network"
## Monitor
MONITOR_BRIDGE_NAME: "xdai"

@ -34,6 +34,7 @@ ORACLE_FOREIGN_GAS_PRICE_UPDATE_INTERVAL: 600000
## UI
UI_TITLE: "TokenBridge UI app - %c"
UI_OG_TITLE: "POA Bridge UI"
UI_DESCRIPTION: "The POA cross-chain bridge serves as a method of transferring POA native tokens from the POA Network to the Ethereum network in a quick and cost-efficient manner."
UI_PORT: 3001
UI_HOME_EXPLORER_TX_TEMPLATE: https://blockscout.com/poa/sokol/tx/%s
@ -42,6 +43,8 @@ UI_HOME_EXPLORER_ADDRESS_TEMPLATE: https://blockscout.com/poa/sokol/address/%s
UI_FOREIGN_EXPLORER_ADDRESS_TEMPLATE: https://blockscout.com/eth/kovan/address/%s
UI_HOME_GAS_PRICE_UPDATE_INTERVAL: 600000
UI_FOREIGN_GAS_PRICE_UPDATE_INTERVAL: 600000
UI_STYLES: "core"
UI_PUBLIC_URL: "http://localhost:3001"
## Monitor
MONITOR_BRIDGE_NAME: "bridge"

@ -33,6 +33,7 @@ ORACLE_FOREIGN_GAS_PRICE_UPDATE_INTERVAL: 600000
#ui
UI_TITLE: "TokenBridge UI app - %c"
UI_OG_TITLE: "POA Bridge UI"
UI_DESCRIPTION: "The POA cross-chain bridge serves as a method of transferring POA native tokens from the POA Network to the Ethereum network in a quick and cost-efficient manner."
UI_HOME_EXPLORER_TX_TEMPLATE: https://blockscout.com/poa/sokol/tx/%s
UI_FOREIGN_EXPLORER_TX_TEMPLATE: https://blockscout.com/eth/kovan/tx/%s
@ -40,6 +41,8 @@ UI_HOME_EXPLORER_ADDRESS_TEMPLATE: https://blockscout.com/poa/sokol/address/%s
UI_FOREIGN_EXPLORER_ADDRESS_TEMPLATE: https://blockscout.com/eth/kovan/address/%s
UI_HOME_GAS_PRICE_UPDATE_INTERVAL: 600000
UI_FOREIGN_GAS_PRICE_UPDATE_INTERVAL: 600000
UI_STYLES: "core"
UI_PUBLIC_URL: "http://localhost"
#monitor
MONITOR_BRIDGE_NAME: "bridge"

@ -33,6 +33,7 @@ COMMON_FOREIGN_GAS_PRICE_FACTOR: 1
## UI
UI_TITLE: "TokenBridge UI app - %c"
UI_OG_TITLE: "POA Bridge UI"
UI_DESCRIPTION: "The TokenBridge serves as a method of transferring native tokens from the Ethereum Classic Network to the Ethereum network in a quick and cost-efficient manner."
UI_PORT: 3001
UI_HOME_EXPLORER_TX_TEMPLATE: https://blockscout.com/etc/mainnet/tx/%s
@ -41,6 +42,8 @@ UI_HOME_EXPLORER_ADDRESS_TEMPLATE: https://blockscout.com/etc/mainnet/address/%s
UI_FOREIGN_EXPLORER_ADDRESS_TEMPLATE: https://blockscout.com/eth/mainnet/address/%s
UI_HOME_GAS_PRICE_UPDATE_INTERVAL: 600000
UI_FOREIGN_GAS_PRICE_UPDATE_INTERVAL: 600000
UI_STYLES: "classic"
UI_PUBLIC_URL: "https://wetc.app"
## Monitor
MONITOR_BRIDGE_NAME: "wetc"

@ -0,0 +1,28 @@
#!/usr/bin/python3
from yaml import safe_load, safe_dump
from argparse import ArgumentParser
from os.path import basename
import sys
parser = ArgumentParser()
parser.add_argument('composefile', type=str, nargs=1, metavar='compose-file', help='docker-compose.yml')
parser.add_argument('-d', action='store_true', help='output result instead of writing the file', dest='debug')
if basename(sys.argv[0]) == "ipykernel_launcher.py":
args = parser.parse_args(['docker-compose.yml'])
else:
args = parser.parse_args()
file_to_operate = args.composefile[0]
with open(file_to_operate) as composefile:
composecnt=composefile.read()
yml = safe_load(composecnt)
for i in yml['services']:
yml['services'][i]['logging'] = {'driver': 'syslog','options': {'tag': '{{.Name}}/{{.ID}}'}}
if args.debug or (basename(sys.argv[0]) == "ipykernel_launcher.py"):
print(safe_dump(yml))
else:
with open(file_to_operate, 'w') as composefile:
safe_dump(yml, composefile, explicit_start=True)

@ -1,19 +1,5 @@
---
- name: Slurp docker compose file
slurp:
src: "{{ bridge_path }}/oracle/{{ file }}.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/{{ file }}.yml"
- name: Change logging facility to forward logs to syslog
script: modify_to_use_syslog.py "{{ bridge_path }}/oracle/{{ file }}.yml"
args:
executable: python3

@ -34,9 +34,9 @@ COMMON_FOREIGN_GAS_PRICE_FACTOR={{ COMMON_FOREIGN_GAS_PRICE_FACTOR }}
# Default
UI_TITLE={{ UI_TITLE }}
UI_OG_TITLE={{ UI_OG_TITLE }}
UI_DESCRIPTION={{ UI_DESCRIPTION }}
UI_PORT={{ UI_PORT }}
UI_PUBLIC_URL={{ UI_PUBLIC_URL }}
{% if UI_STYLES | default('') != '' %}
UI_STYLES={{ UI_STYLES }}
{% endif %}

@ -21,3 +21,5 @@ COMMON_FOREIGN_GAS_PRICE_FACTOR=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

@ -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

@ -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

@ -0,0 +1,23 @@
COMMON_HOME_BRIDGE_ADDRESS=0xc26Aa60Ff574f157616D3aEE70e08aAC129E1dFC
COMMON_FOREIGN_BRIDGE_ADDRESS=0xc26Aa60Ff574f157616D3aEE70e08aAC129E1dFC
COMMON_FOREIGN_RPC_URL=http://localhost:8542
COMMON_HOME_RPC_URL=http://localhost:8541
UI_NATIVE_TOKEN_DISPLAY_NAME=POA
UI_HOME_NETWORK_DISPLAY_NAME=Sokol
UI_FOREIGN_NETWORK_DISPLAY_NAME=Kovan
UI_HOME_EXPLORER_TX_TEMPLATE=https://blockscout.com/poa/sokol/tx//%s
UI_FOREIGN_EXPLORER_TX_TEMPLATE=https://blockscout.com/eth/kovan/tx/%s
UI_HOME_EXPLORER_ADDRESS_TEMPLATE=https://blockscout.com/poa/sokol/address/%s
UI_FOREIGN_EXPLORER_ADDRESS_TEMPLATE=https://blockscout.com/eth/kovan/address/%s
COMMON_HOME_GAS_PRICE_SUPPLIER_URL=https://gasprice.poa.network/
COMMON_HOME_GAS_PRICE_SPEED_TYPE=standard
COMMON_HOME_GAS_PRICE_FALLBACK=5000000000
UI_HOME_GAS_PRICE_UPDATE_INTERVAL=15000
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=5000000000
UI_FOREIGN_GAS_PRICE_UPDATE_INTERVAL=15000
COMMON_FOREIGN_GAS_PRICE_FACTOR=1
UI_PORT=3000
UI_STYLES=stake

@ -20,3 +20,4 @@ COMMON_FOREIGN_GAS_PRICE_FALLBACK=5000000000
UI_FOREIGN_GAS_PRICE_UPDATE_INTERVAL=15000
COMMON_FOREIGN_GAS_PRICE_FACTOR=1
UI_PORT=3000
UI_STYLES=core

@ -20,3 +20,4 @@ COMMON_FOREIGN_GAS_PRICE_FALLBACK=5000000000
UI_FOREIGN_GAS_PRICE_UPDATE_INTERVAL=15000
COMMON_FOREIGN_GAS_PRICE_FACTOR=1
UI_PORT=3000
UI_STYLES=core

@ -20,3 +20,4 @@ COMMON_FOREIGN_GAS_PRICE_FALLBACK=5000000000
UI_FOREIGN_GAS_PRICE_UPDATE_INTERVAL=15000
COMMON_FOREIGN_GAS_PRICE_FACTOR=1
UI_PORT=3000
UI_STYLES=core

@ -63,6 +63,14 @@
"foreignBox": "0x6C4EaAb8756d53Bf599FFe2347FAFF1123D6C8A1",
"monitor": "http://monitor-amb:3013/bridge"
},
"ambStakeErcToErc": {
"home": "0xc26Aa60Ff574f157616D3aEE70e08aAC129E1dFC",
"foreign": "0xc26Aa60Ff574f157616D3aEE70e08aAC129E1dFC",
"homeToken": "0x6f359aC418a5f7538F7755A33C9c7dDf38785524",
"foreignToken": "0x6f359aC418a5f7538F7755A33C9c7dDf38785524",
"blockReward": "0xF9698Eb93702dfdd0e2d802088d4c21822a8A977",
"ui": "http://localhost:3003"
},
"homeRPC": {
"URL": "http://parity1:8545",
"ID": "77"

@ -0,0 +1,30 @@
BRIDGE_MODE=STAKE_AMB_ERC_TO_ERC
DEPLOYMENT_ACCOUNT_PRIVATE_KEY=8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9
DEPLOYMENT_GAS_LIMIT_EXTRA=0.2
HOME_DEPLOYMENT_GAS_PRICE=10000000000
FOREIGN_DEPLOYMENT_GAS_PRICE=10000000000
GET_RECEIPT_INTERVAL_IN_MILLISECONDS=50
HOME_STAKE_TOKEN_ADDRESS=0x6f359aC418a5f7538F7755A33C9c7dDf38785524
FOREIGN_STAKE_TOKEN_ADDRESS=0x6f359aC418a5f7538F7755A33C9c7dDf38785524
BLOCK_REWARD_ADDRESS=0xF9698Eb93702dfdd0e2d802088d4c21822a8A977
HOME_RPC_URL=http://parity1:8545
HOME_BRIDGE_OWNER=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b
HOME_UPGRADEABLE_ADMIN=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b
HOME_DAILY_LIMIT=30000000000000000000000000
HOME_MAX_AMOUNT_PER_TX=1500000000000000000000000
HOME_MIN_AMOUNT_PER_TX=10000000000000000
HOME_TRANSACTIONS_FEE=0
FOREIGN_RPC_URL=http://parity2:8545
FOREIGN_BRIDGE_OWNER=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b
FOREIGN_UPGRADEABLE_ADMIN=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b
FOREIGN_DAILY_LIMIT=15000000000000000000000000
FOREIGN_MAX_AMOUNT_PER_TX=750000000000000000000000
FOREIGN_MIN_AMOUNT_PER_TX=10000000000000000
HOME_AMB_BRIDGE=0x0AEe1FCD12dDFab6265F7f8956e6E012A9Fe4Aa0
FOREIGN_AMB_BRIDGE=0x0AEe1FCD12dDFab6265F7f8956e6E012A9Fe4Aa0
HOME_MEDIATOR_REQUEST_GAS_LIMIT=2000000
FOREIGN_MEDIATOR_REQUEST_GAS_LIMIT=2000000
BRIDGEABLE_TOKEN_NAME='not used'
BRIDGEABLE_TOKEN_SYMBOL='not used'
BRIDGEABLE_TOKEN_DECIMALS='18'

@ -93,6 +93,15 @@ services:
command: "true"
networks:
- ultimate
ui-amb-stake-erc20-erc20:
build:
context: ..
dockerfile: ui/Dockerfile
args:
DOT_ENV_PATH: e2e-commons/components-envs/ui-amb-stake-erc20-erc20.env
command: "true"
networks:
- ultimate
monitor:
build:
context: ..

@ -43,3 +43,13 @@ echo -e "\n\n############ Deploying test contract for amb ############\n"
cd "$DEPLOY_PATH"
node src/utils/deployTestBox.js
cd - > /dev/null
echo -e "\n\n############ Deploying amb stake erc to erc ############\n"
cp "$ENVS_PATH/amb-stake-erc-to-erc.env" "$DEPLOY_PATH/.env"
node deployMultiBridgeToken.js
node deployBridgeTokenRewardable.js
cd "$DEPLOY_PATH"
node deploy.js
cd - > /dev/null
node setupStakeTokens.js
cd - > /dev/null

@ -0,0 +1,43 @@
const path = require('path')
const { user } = require('../constants.json')
const contractsPath = '../../contracts'
require('dotenv').config({
path: path.join(__dirname, contractsPath, '/deploy/.env')
})
const { deployContract, sendRawTxHome, privateKeyToAddress } = require(`${contractsPath}/deploy/src/deploymentUtils`)
const { web3Home, deploymentPrivateKey } = require(`${contractsPath}/deploy/src/web3`)
const ERC677BridgeTokenRewardable = require(`${contractsPath}/build/contracts/ERC677BridgeTokenRewardable.json`)
const { DEPLOYMENT_ACCOUNT_PRIVATE_KEY } = process.env
const DEPLOYMENT_ACCOUNT_ADDRESS = privateKeyToAddress(DEPLOYMENT_ACCOUNT_PRIVATE_KEY)
async function deployBridgeTokenRewardable() {
try {
let homeNonce = await web3Home.eth.getTransactionCount(DEPLOYMENT_ACCOUNT_ADDRESS)
console.log('\n[Home] Deploying ERC677BridgeTokenRewardable Test token')
const stakeToken = await deployContract(ERC677BridgeTokenRewardable, ['STAKE', 'STAKE', '18', '77'], {
from: DEPLOYMENT_ACCOUNT_ADDRESS,
network: 'home',
nonce: homeNonce
})
homeNonce++
console.log('[Home] Stake Token: ', stakeToken.options.address)
const mintData = await stakeToken.methods
.mint(user.address, '500000000000000000000')
.encodeABI({ from: DEPLOYMENT_ACCOUNT_ADDRESS })
await sendRawTxHome({
data: mintData,
nonce: homeNonce,
to: stakeToken.options.address,
privateKey: deploymentPrivateKey,
url: process.env.HOME_RPC_URL
})
} catch (e) {
console.log(e)
throw e
}
}
deployBridgeTokenRewardable()

@ -0,0 +1,43 @@
const path = require('path')
const { user } = require('../constants.json')
const contractsPath = '../../contracts'
require('dotenv').config({
path: path.join(__dirname, contractsPath, '/deploy/.env')
})
const { deployContract, sendRawTxForeign, privateKeyToAddress } = require(`${contractsPath}/deploy/src/deploymentUtils`)
const { web3Foreign, deploymentPrivateKey } = require(`${contractsPath}/deploy/src/web3`)
const ERC677MultiBridgeToken = require(`${contractsPath}/build/contracts/ERC677MultiBridgeToken.json`)
const { DEPLOYMENT_ACCOUNT_PRIVATE_KEY } = process.env
const DEPLOYMENT_ACCOUNT_ADDRESS = privateKeyToAddress(DEPLOYMENT_ACCOUNT_PRIVATE_KEY)
async function deployMultiBridgeToken() {
try {
let foreignNonce = await web3Foreign.eth.getTransactionCount(DEPLOYMENT_ACCOUNT_ADDRESS)
console.log('\n[Foreign] Deploying ERC677MultiBridgeToken Test token')
const stakeToken = await deployContract(ERC677MultiBridgeToken, ['STAKE', 'STAKE', '18', '42'], {
from: DEPLOYMENT_ACCOUNT_ADDRESS,
network: 'foreign',
nonce: foreignNonce
})
foreignNonce++
console.log('[Foreign] Stake Token: ', stakeToken.options.address)
const mintData = await stakeToken.methods
.mint(user.address, '500000000000000000000')
.encodeABI({ from: DEPLOYMENT_ACCOUNT_ADDRESS })
await sendRawTxForeign({
data: mintData,
nonce: foreignNonce,
to: stakeToken.options.address,
privateKey: deploymentPrivateKey,
url: process.env.FOREIGN_RPC_URL
})
} catch (e) {
console.log(e)
throw e
}
}
deployMultiBridgeToken()

@ -0,0 +1,111 @@
const path = require('path')
const { ambStakeErcToErc, validator, secondValidator, thirdValidator } = require('../constants.json')
const contractsPath = '../../contracts'
require('dotenv').config({
path: path.join(__dirname, contractsPath, '/deploy/.env')
})
const { sendRawTxHome, sendRawTxForeign, privateKeyToAddress } = require(`${contractsPath}/deploy/src/deploymentUtils`)
const { web3Home, web3Foreign, deploymentPrivateKey } = require(`${contractsPath}/deploy/src/web3`)
const BlockReward = require(`${contractsPath}/build/contracts/BlockReward.json`)
const ERC677BridgeTokenRewardable = require(`${contractsPath}/build/contracts/ERC677BridgeTokenRewardable.json`)
const ERC677MultiBridgeToken = require(`${contractsPath}/build/contracts/ERC677MultiBridgeToken.json`)
const { DEPLOYMENT_ACCOUNT_PRIVATE_KEY } = process.env
const DEPLOYMENT_ACCOUNT_ADDRESS = privateKeyToAddress(DEPLOYMENT_ACCOUNT_PRIVATE_KEY)
async function setupStakeTokens() {
try {
let homeNonce = await web3Home.eth.getTransactionCount(DEPLOYMENT_ACCOUNT_ADDRESS)
const blockReward = new web3Home.eth.Contract(BlockReward.abi, ambStakeErcToErc.blockReward)
console.log('\n[Home] Set token in block reward')
const setTokenData = await blockReward.methods.setToken(ambStakeErcToErc.homeToken).encodeABI()
await sendRawTxHome({
data: setTokenData,
nonce: homeNonce,
to: blockReward.options.address,
privateKey: deploymentPrivateKey,
url: process.env.HOME_RPC_URL
})
homeNonce++
console.log('\n[Home] Set validators rewards in block reward')
const setValidatorsRewardsData = await blockReward.methods
.setValidatorsRewards([validator.address, secondValidator.address, thirdValidator.address])
.encodeABI()
await sendRawTxHome({
data: setValidatorsRewardsData,
nonce: homeNonce,
to: blockReward.options.address,
privateKey: deploymentPrivateKey,
url: process.env.HOME_RPC_URL
})
homeNonce++
const homeToken = new web3Home.eth.Contract(ERC677BridgeTokenRewardable.abi, ambStakeErcToErc.homeToken)
console.log('\n[Home] Set block reward in token')
const setBlockRewardData = await homeToken.methods.setBlockRewardContract(ambStakeErcToErc.blockReward).encodeABI()
await sendRawTxHome({
data: setBlockRewardData,
nonce: homeNonce,
to: homeToken.options.address,
privateKey: deploymentPrivateKey,
url: process.env.HOME_RPC_URL
})
homeNonce++
console.log('\n[Home] Add bridge in token')
const addBridgeData = await homeToken.methods.addBridge(ambStakeErcToErc.home).encodeABI()
await sendRawTxHome({
data: addBridgeData,
nonce: homeNonce,
to: homeToken.options.address,
privateKey: deploymentPrivateKey,
url: process.env.HOME_RPC_URL
})
homeNonce++
console.log('\n[Home] transfer token ownership to mediator')
const transferOwnershipData = await homeToken.methods.transferOwnership(ambStakeErcToErc.home).encodeABI()
await sendRawTxHome({
data: transferOwnershipData,
nonce: homeNonce,
to: homeToken.options.address,
privateKey: deploymentPrivateKey,
url: process.env.HOME_RPC_URL
})
homeNonce++
let foreignNonce = await web3Foreign.eth.getTransactionCount(DEPLOYMENT_ACCOUNT_ADDRESS)
const foreignToken = new web3Foreign.eth.Contract(ERC677MultiBridgeToken.abi, ambStakeErcToErc.foreignToken)
console.log('\n[Foreign] Add bridge in token')
const addBridgeForeignData = await homeToken.methods.addBridge(ambStakeErcToErc.foreign).encodeABI()
await sendRawTxForeign({
data: addBridgeForeignData,
nonce: foreignNonce,
to: foreignToken.options.address,
privateKey: deploymentPrivateKey,
url: process.env.FOREIGN_RPC_URL
})
foreignNonce++
console.log('\n[Foreign] transfer token ownership to mediator')
const transferOwnershipForeignData = await homeToken.methods.transferOwnership(ambStakeErcToErc.foreign).encodeABI()
await sendRawTxForeign({
data: transferOwnershipForeignData,
nonce: foreignNonce,
to: foreignToken.options.address,
privateKey: deploymentPrivateKey,
url: process.env.FOREIGN_RPC_URL
})
foreignNonce++
} catch (e) {
console.log(e)
throw e
}
}
setupStakeTokens()

@ -8,6 +8,9 @@ docker network create --driver bridge ultimate || true
docker-compose up -d parity1 parity2 e2e
startValidator () {
# make sure that old image tags are not cached
docker-compose $1 build
docker-compose $1 run -d --name $4 redis
docker-compose $1 run -d --name $5 rabbit
docker-compose $1 run $2 $3 -d oracle yarn watcher:signature-request
@ -71,11 +74,12 @@ while [ "$1" != "" ]; do
fi
if [ "$1" == "ui" ]; then
docker-compose up -d ui ui-erc20 ui-erc20-native
docker-compose up -d ui ui-erc20 ui-erc20-native ui-amb-stake-erc20-erc20
docker-compose run -d -p 3000:3000 ui yarn start
docker-compose run -d -p 3001:3000 ui-erc20 yarn start
docker-compose run -d -p 3002:3000 ui-erc20-native yarn start
docker-compose run -d -p 3003:3000 ui-amb-stake-erc20-erc20 yarn start
fi
if [ "$1" == "deploy" ]; then
@ -106,5 +110,9 @@ while [ "$1" != "" ]; do
../deployment-e2e/molecule.sh ultimate-amb
fi
if [ "$1" == "ultimate-amb-stake-erc-to-erc" ]; then
../deployment-e2e/molecule.sh ultimate-amb-stake-erc-to-erc
fi
shift # Shift all the parameters down by one
done

@ -31,6 +31,7 @@ async function checkWorker() {
const foreign = Object.assign({}, balances.foreign, events.foreign)
const status = Object.assign({}, balances, events, { home }, { foreign })
if (!status) throw new Error('status is empty: ' + JSON.stringify(status))
status.health = true
writeFile(`/responses/${MONITOR_BRIDGE_NAME}/getBalances.json`, status)
logger.debug('calling validators()')
@ -59,6 +60,7 @@ async function checkWorker() {
}
vBalances.ok = vBalances.homeOk && vBalances.foreignOk
vBalances.health = true
writeFile(`/responses/${MONITOR_BRIDGE_NAME}/validators.json`, vBalances)
logger.debug('Done')
} catch (e) {

@ -16,12 +16,14 @@ async function checkWorker2() {
(evStats.onlyInForeignDeposits || evStats.home.processedMsgNotDeliveredInForeign).length === 0 &&
(evStats.onlyInHomeWithdrawals || evStats.foreign.deliveredMsgNotProcessedInHome).length === 0 &&
(evStats.onlyInForeignWithdrawals || evStats.foreign.processedMsgNotDeliveredInHome).length === 0
evStats.health = true
writeFile(`/responses/${MONITOR_BRIDGE_NAME}/eventsStats.json`, evStats)
logger.debug('calling alerts()')
const _alerts = await alerts()
if (!_alerts) throw new Error('alerts is empty: ' + JSON.stringify(_alerts))
_alerts.ok = !_alerts.executeAffirmations.mostRecentTxHash && !_alerts.executeSignatures.mostRecentTxHash
_alerts.health = true
writeFile(`/responses/${MONITOR_BRIDGE_NAME}/alerts.json`, _alerts)
logger.debug('Done x2')
} catch (e) {

@ -19,6 +19,7 @@ async function checkWorker3() {
const transfers = await stuckTransfers()
if (!transfers) throw new Error('transfers is empty: ' + JSON.stringify(transfers))
transfers.ok = transfers.total.length === 0
transfers.health = true
writeFile(`/responses/${MONITOR_BRIDGE_NAME}/stuckTransfers.json`, transfers)
logger.debug('Done')
}

@ -3,7 +3,6 @@ const BN = require('bignumber.js')
const Web3 = require('web3')
const logger = require('./logger')('getBalances')
const { BRIDGE_MODES } = require('../commons')
const { blockNumberHalfDuplexDisabled } = require('./utils/tokenUtils')
const Web3Utils = Web3.utils
@ -86,28 +85,7 @@ async function main(bridgeMode) {
const erc20Contract = new web3Foreign.eth.Contract(ERC20_ABI, erc20Address)
let investedAmountInDai = 0
let bridgeDsrBalance = 0
let foreignHalfDuplexErc20Balance = 0
let displayChaiToken = false
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(null, blockNumberHalfDuplexDisabled)
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')
}
try {
logger.debug('calling foreignBridge.methods.isChaiTokenEnabled')
@ -142,30 +120,16 @@ async function main(bridgeMode) {
const foreignErc20BalanceBN = new BN(foreignErc20Balance)
const investedAmountInDaiBN = new BN(investedAmountInDai)
const bridgeDsrBalanceBN = new BN(bridgeDsrBalance)
const halfDuplexErc20BalanceBN = displayHalfDuplexToken ? new BN(foreignHalfDuplexErc20Balance) : new BN(0)
const diff = foreignErc20BalanceBN
.plus(halfDuplexErc20BalanceBN)
.plus(investedAmountInDaiBN)
.minus(totalSupplyBN)
.toFixed()
let foreign = {
const foreign = {
erc20Balance: Web3Utils.fromWei(foreignErc20Balance)
}
if (displayHalfDuplexToken && tokenSwapAllowed) {
foreign = {
...foreign,
halfDuplexErc20Balance: Web3Utils.fromWei(foreignHalfDuplexErc20Balance)
}
} else if (displayHalfDuplexToken && !tokenSwapAllowed) {
foreign = {
...foreign,
halfDuplexErc20BalanceAfterES: Web3Utils.fromWei(foreignHalfDuplexErc20Balance)
}
}
if (displayChaiToken) {
foreign.investedErc20Balance = Web3Utils.fromWei(investedAmountInDai)
foreign.accumulatedInterest = Web3Utils.fromWei(bridgeDsrBalanceBN.minus(investedAmountInDaiBN).toString(10))

@ -1,11 +1,60 @@
#!/bin/bash
CONFIGDIR="configs"
RESPONSESDIR="responses"
IMAGETAG="latest"
cd $(dirname $0)/..
if /usr/local/bin/docker-compose ps | grep -q -i 'monitor'; then
for file in configs/*.env
tstart=`date +"%s"`
for file in ${CONFIGDIR}/*.env
do
docker run --rm --env-file $file -v $(pwd)/responses:/mono/monitor/responses poanetwork/tokenbridge-monitor:latest /bin/bash -c 'yarn check-all'
echo "${file} handling..."
bridgename=`source ${file} && echo ${MONITOR_BRIDGE_NAME}`
reportdir=${RESPONSESDIR}"/"${bridgename}
if [ ! -d ${reportdir} ]; then
mkdir -p ${reportdir}
fi
checksumfile=${bridgename}".shasum"
rm -f ${checksumfile}
for json in alerts.json eventsStats.json getBalances.json validators.json stuckTransfers.json; do
if [ -f ${reportdir}/${json} ]; then
shasum -a 256 ${reportdir}/${json} >> ${checksumfile}
fi
done
containername=${bridgename}"-checker"
docker container stats --no-stream ${containername} 2>/dev/null 1>&2
if [ ! "$?" == "0" ]; then
docker run --rm --env-file $file -v $(pwd)/${RESPONSESDIR}:/mono/monitor/responses \
--name ${containername} poanetwork/tokenbridge-monitor:${IMAGETAG} \
/bin/bash -c 'yarn check-all'
shasum -a 256 -s -c ${checksumfile}
if [ "$?" == "0" ]; then
echo "JSON files have not been updated - the monitor is not healthy"
for json in alerts.json eventsStats.json getBalances.json validators.json stuckTransfers.json; do
if [ -f ${reportdir}/${json} ]; then
echo '{"health": false, "lastChecked": '`date +"%s"`'}' > ${reportdir}/${json}
fi
done
else
echo "JSON files have been updated - new metrics collected"
fi
else
echo "${containername} have not finished yet" >&2
fi
rm ${checksumfile}
echo "========================================"
done
tend=`date +"%s"`
tdiff=`expr ${tend} - ${tstart}`
echo "Total time to run: ${tdiff}"
else
echo "Monitor is not running, skipping checks."
fi
fi

@ -114,6 +114,8 @@ async function main(mode) {
let directTransfers = transferEvents
const tokensSwappedAbiExists = FOREIGN_ABI.filter(e => e.type === 'event' && e.name === 'TokensSwapped')[0]
if (tokensSwappedAbiExists) {
logger.debug('collecting half duplex tokens participated in the bridge balance')
logger.debug("calling foreignBridge.getPastEvents('TokensSwapped')")
const tokensSwappedEvents = await getPastEvents(foreignBridge, {
event: 'TokensSwapped',
fromBlock: MONITOR_FOREIGN_START_BLOCK,
@ -128,7 +130,7 @@ async function main(mode) {
// Exclude chai token from previous erc20
try {
logger.debug('calling foreignBridge.chaiToken()')
logger.debug('calling foreignBridge.chaiToken() to remove it from half duplex tokens list')
const chaiToken = await foreignBridge.methods.chaiToken().call()
uniqueTokenAddressesSet.delete(chaiToken)
} catch (e) {
@ -136,7 +138,7 @@ async function main(mode) {
}
// Exclude dai token from previous erc20
try {
logger.debug('calling foreignBridge.erc20token()')
logger.debug('calling foreignBridge.erc20token() to remove it from half duplex tokens list')
const daiToken = await foreignBridge.methods.erc20token().call()
uniqueTokenAddressesSet.delete(daiToken)
} catch (e) {
@ -148,6 +150,8 @@ async function main(mode) {
uniqueTokenAddresses.map(async tokenAddress => {
const halfDuplexTokenContract = new web3Foreign.eth.Contract(ERC20_ABI, tokenAddress)
logger.debug('Half duplex token:', tokenAddress)
logger.debug("calling halfDuplexTokenContract.getPastEvents('Transfer')")
const halfDuplexTransferEvents = (await getPastEvents(halfDuplexTokenContract, {
event: 'Transfer',
fromBlock: MONITOR_FOREIGN_START_BLOCK,
@ -158,6 +162,7 @@ async function main(mode) {
})).map(normalizeEvent)
// Remove events after the ES
logger.debug('filtering half duplex transfers happened before ES')
const validHalfDuplexTransfers = await filterTransferBeforeES(halfDuplexTransferEvents)
transferEvents = [...validHalfDuplexTransfers, ...transferEvents]

@ -1,14 +1,9 @@
const web3Utils = require('web3').utils
const { addTxHashToData, parseAMBMessage } = require('../../commons')
const { parseAMBMessage } = require('../../commons')
function deliveredMsgNotProcessed(processedList) {
return deliveredMsg => {
const msg = parseAMBMessage(
addTxHashToData({
encodedData: deliveredMsg.returnValues.encodedData,
transactionHash: deliveredMsg.transactionHash
})
)
const msg = parseAMBMessage(deliveredMsg.returnValues.encodedData)
return (
processedList.filter(processedMsg => {
return messageEqualsEvent(msg, processedMsg.returnValues)
@ -21,12 +16,7 @@ function processedMsgNotDelivered(deliveredList) {
return processedMsg => {
return (
deliveredList.filter(deliveredMsg => {
const msg = parseAMBMessage(
addTxHashToData({
encodedData: deliveredMsg.returnValues.encodedData,
transactionHash: deliveredMsg.transactionHash
})
)
const msg = parseAMBMessage(deliveredMsg.returnValues.encodedData)
return messageEqualsEvent(msg, processedMsg.returnValues)
}).length === 0
)
@ -37,7 +27,7 @@ function messageEqualsEvent(parsedMsg, event) {
return (
web3Utils.toChecksumAddress(parsedMsg.sender) === event.sender &&
web3Utils.toChecksumAddress(parsedMsg.executor) === event.executor &&
parsedMsg.txHash === event.transactionHash
parsedMsg.messageId === event.messageId
)
}

@ -14,6 +14,7 @@
"worker:convert-to-chai": "./scripts/start-worker.sh worker convert-to-chai-worker",
"sender:home": "./scripts/start-worker.sh sender home-sender",
"sender:foreign": "./scripts/start-worker.sh sender foreign-sender",
"confirm:transfer": "./scripts/start-worker.sh confirmRelay transfer-watcher",
"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",

@ -8,11 +8,12 @@ LOGS_DIR="logs/"
WORKER="${WORKERS_DIR}${1}.js"
CONFIG="${2}.config.js"
LOG="${LOGS_DIR}${2}.txt"
TX_HASH="${3}"
CHECKS=$(node scripts/initialChecks.js)
if [ "${NODE_ENV}" = "production" ]; then
exec node "${WORKER}" "${CONFIG}" "$CHECKS"
exec node "${WORKER}" "${CONFIG}" "$CHECKS" "$TX_HASH"
else
node "${WORKER}" "${CONFIG}" "$CHECKS" | tee -a "${LOG}" | pino-pretty
node "${WORKER}" "${CONFIG}" "$CHECKS" "$TX_HASH" | tee -a "${LOG}" | pino-pretty
fi

186
oracle/src/confirmRelay.js Normal file

@ -0,0 +1,186 @@
require('../env')
const path = require('path')
const { isAttached, connectWatcherToQueue, connection } = require('./services/amqpClient')
const logger = require('./services/logger')
const GasPrice = require('./services/gasPrice')
const rpcUrlsManager = require('./services/getRpcUrlsManager')
const { getNonce, getChainId, getEventsFromTx } = require('./tx/web3')
const { sendTx } = require('./tx/sendTx')
const { checkHTTPS, watchdog, syncForEach, addExtraGas } = require('./utils/utils')
const { EXIT_CODES, EXTRA_GAS_PERCENTAGE } = require('./utils/constants')
const { ORACLE_VALIDATOR_ADDRESS, ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY, ORACLE_ALLOW_HTTP_FOR_RPC } = process.env
if (process.argv.length < 5) {
logger.error('Please check the number of arguments, transaction hash is not present')
process.exit(EXIT_CODES.GENERAL_ERROR)
}
const config = require(path.join('../config/', process.argv[2]))
const txHash = process.argv[4]
const processSignatureRequests = require('./events/processSignatureRequests')(config)
const processCollectedSignatures = require('./events/processCollectedSignatures')(config)
const processAffirmationRequests = require('./events/processAffirmationRequests')(config)
const processTransfers = require('./events/processTransfers')(config)
const processAMBSignatureRequests = require('./events/processAMBSignatureRequests')(config)
const processAMBCollectedSignatures = require('./events/processAMBCollectedSignatures')(config)
const processAMBAffirmationRequests = require('./events/processAMBAffirmationRequests')(config)
const web3Instance = config.web3
const { eventContractAddress } = config
const eventContract = new web3Instance.eth.Contract(config.eventAbi, eventContractAddress)
let attached
async function initialize() {
try {
const checkHttps = checkHTTPS(ORACLE_ALLOW_HTTP_FOR_RPC, logger)
rpcUrlsManager.homeUrls.forEach(checkHttps('home'))
rpcUrlsManager.foreignUrls.forEach(checkHttps('foreign'))
attached = await isAttached()
if (attached) {
logger.info('RabbitMQ container is available, using oracle sender')
} else {
logger.info('RabbitMQ container is not available, using internal sender')
}
connectWatcherToQueue({
queueName: config.queue,
workerQueue: config.workerQueue,
cb: runMain
})
} catch (e) {
logger.error(e)
process.exit(EXIT_CODES.GENERAL_ERROR)
}
}
async function runMain({ sendToQueue }) {
try {
const sendJob = attached ? sendToQueue : sendJobTx
if (!attached || connection.isConnected()) {
if (config.maxProcessingTime) {
await watchdog(() => main({ sendJob, txHash }), config.maxProcessingTime, () => {
logger.fatal('Max processing time reached')
process.exit(EXIT_CODES.MAX_TIME_REACHED)
})
} else {
await main({ sendJob, txHash })
}
} else {
setTimeout(() => {
runMain({ sendToQueue })
}, config.pollingInterval)
}
} catch (e) {
logger.error(e)
}
}
function processEvents(events) {
switch (config.id) {
case 'native-erc-signature-request':
case 'erc-erc-signature-request':
case 'erc-native-signature-request':
return processSignatureRequests(events)
case 'native-erc-collected-signatures':
case 'erc-erc-collected-signatures':
case 'erc-native-collected-signatures':
return processCollectedSignatures(events)
case 'native-erc-affirmation-request':
case 'erc677-erc677-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 'amb-signature-request':
return processAMBSignatureRequests(events)
case 'amb-collected-signatures':
return processAMBCollectedSignatures(events)
case 'amb-affirmation-request':
return processAMBAffirmationRequests(events)
default:
return []
}
}
async function main({ sendJob, txHash }) {
try {
const events = await getEventsFromTx({
web3: web3Instance,
contract: eventContract,
event: config.event,
txHash,
filter: config.eventFilter
})
logger.info(`Found ${events.length} ${config.event} events`)
if (events.length) {
const job = await processEvents(events)
logger.info('Transactions to send:', job.length)
if (job.length) {
await sendJob(job)
}
}
} catch (e) {
logger.error(e)
}
await connection.close()
logger.debug('Finished')
}
async function sendJobTx(jobs) {
const gasPrice = await GasPrice.start(config.queue, true)
const chainId = await getChainId(config.queue)
let nonce = await getNonce(web3Instance, ORACLE_VALIDATOR_ADDRESS)
await syncForEach(jobs, async job => {
const gasLimit = addExtraGas(job.gasEstimate, EXTRA_GAS_PERCENTAGE)
try {
logger.info(`Sending transaction with nonce ${nonce}`)
const txHash = await sendTx({
chain: config.queue,
data: job.data,
nonce,
gasPrice: gasPrice.toString(10),
amount: '0',
gasLimit,
privateKey: ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY,
to: job.to,
chainId,
web3: web3Instance
})
nonce++
logger.info(
{ eventTransactionHash: job.transactionReference, generatedTransactionHash: txHash },
`Tx generated ${txHash} for event Tx ${job.transactionReference}`
)
} catch (e) {
logger.error(
{ eventTransactionHash: job.transactionReference, error: e.message },
`Tx Failed for event Tx ${job.transactionReference}.`,
e.message
)
if (e.message.includes('Insufficient funds')) {
const currentBalance = await web3Instance.eth.getBalance(ORACLE_VALIDATOR_ADDRESS)
const minimumBalance = gasLimit.multipliedBy(gasPrice)
logger.error(
`Insufficient funds: ${currentBalance}. Stop processing messages until the balance is at least ${minimumBalance}.`
)
}
}
})
}
initialize()

@ -6,7 +6,7 @@ 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 { parseAMBMessage } = require('../../../../commons')
const { AlreadyProcessedError, AlreadySignedError, InvalidValidatorError } = require('../../utils/errors')
const limit = promiseLimit(MAX_CONCURRENT_EVENTS)
@ -30,20 +30,16 @@ function processAffirmationRequestsBuilder(config) {
rootLogger.debug(`Processing ${affirmationRequests.length} AffirmationRequest events`)
const callbacks = affirmationRequests
.map(affirmationRequest => async () => {
const { encodedData } = affirmationRequest.returnValues
const { messageId, encodedData: message } = affirmationRequest.returnValues
const logger = rootLogger.child({
eventTransactionHash: affirmationRequest.transactionHash
})
const message = addTxHashToData({
encodedData,
transactionHash: affirmationRequest.transactionHash
eventTransactionHash: affirmationRequest.transactionHash,
eventMessageId: messageId
})
const { sender, executor } = parseAMBMessage(message)
logger.info({ sender, executor }, `Processing affirmationRequest ${affirmationRequest.transactionHash}`)
logger.info({ sender, executor }, `Processing affirmationRequest ${messageId}`)
let gasEstimate
try {
@ -63,12 +59,10 @@ function processAffirmationRequestsBuilder(config) {
logger.fatal({ address: config.validatorAddress }, 'Invalid validator')
process.exit(EXIT_CODES.INCOMPATIBILITY)
} else if (e instanceof AlreadySignedError) {
logger.info(`Already signed affirmationRequest ${affirmationRequest.transactionHash}`)
logger.info(`Already signed affirmationRequest ${messageId}`)
return
} else if (e instanceof AlreadyProcessedError) {
logger.info(
`affirmationRequest ${affirmationRequest.transactionHash} was already processed by other validators`
)
logger.info(`affirmationRequest ${messageId} was already processed by other validators`)
return
} else {
logger.error(e, 'Unknown error while processing transaction')

@ -17,7 +17,7 @@ async function estimateGas({
r,
s,
signatures,
txHash,
messageId,
address
}) {
try {
@ -32,7 +32,7 @@ async function estimateGas({
// check if the message was already processed
logger.debug('Check if the message was already processed')
const alreadyProcessed = await foreignBridge.methods.relayedMessages(txHash).call()
const alreadyProcessed = await foreignBridge.methods.relayedMessages(messageId).call()
if (alreadyProcessed) {
throw new AlreadyProcessedError()
}

@ -69,7 +69,8 @@ function processCollectedSignaturesBuilder(config) {
await Promise.all(signaturePromises)
const signatures = packSignatures(signaturesArray)
const { txHash } = parseAMBMessage(message)
const { messageId } = parseAMBMessage(message)
logger.info(`Processing messageId: ${messageId}`)
let gasEstimate
try {
@ -83,7 +84,7 @@ function processCollectedSignaturesBuilder(config) {
signatures,
message,
numberOfCollectedSignatures: NumberOfCollectedSignatures,
txHash,
messageId,
address: config.validatorAddress
})
logger.debug({ gasEstimate }, 'Gas estimated')

@ -4,7 +4,7 @@ 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 { parseAMBMessage } = require('../../../../commons')
const estimateGas = require('../processSignatureRequests/estimateGas')
const { AlreadyProcessedError, AlreadySignedError, InvalidValidatorError } = require('../../utils/errors')
const { EXIT_CODES, MAX_CONCURRENT_EVENTS } = require('../../utils/constants')
@ -32,19 +32,15 @@ function processSignatureRequestsBuilder(config) {
rootLogger.debug(`Processing ${signatureRequests.length} SignatureRequest events`)
const callbacks = signatureRequests
.map(signatureRequest => async () => {
const { encodedData } = signatureRequest.returnValues
const { messageId, encodedData: message } = signatureRequest.returnValues
const logger = rootLogger.child({
eventTransactionHash: signatureRequest.transactionHash
})
const message = addTxHashToData({
encodedData,
transactionHash: signatureRequest.transactionHash
eventTransactionHash: signatureRequest.transactionHash,
eventMessageId: messageId
})
const { sender, executor } = parseAMBMessage(message)
logger.info({ sender, executor }, `Processing signatureRequest ${signatureRequest.transactionHash}`)
logger.info({ sender, executor }, `Processing signatureRequest ${messageId}`)
const signature = web3Home.eth.accounts.sign(message, `0x${ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY}`)
@ -67,12 +63,10 @@ function processSignatureRequestsBuilder(config) {
logger.fatal({ address: config.validatorAddress }, 'Invalid validator')
process.exit(EXIT_CODES.INCOMPATIBILITY)
} else if (e instanceof AlreadySignedError) {
logger.info(`Already signed signatureRequest ${signatureRequest.transactionHash}`)
logger.info(`Already signed signatureRequest ${messageId}`)
return
} else if (e instanceof AlreadyProcessedError) {
logger.info(
`signatureRequest ${signatureRequest.transactionHash} was already processed by other validators`
)
logger.info(`signatureRequest ${messageId} was already processed by other validators`)
return
} else {
logger.error(e, 'Unknown error while processing transaction')

@ -1,4 +1,6 @@
require('../../env')
const url = require('url')
const dns = require('dns')
const connection = require('amqp-connection-manager').connect(process.env.ORACLE_QUEUE_URL)
const logger = require('./logger')
const { getRetrySequence } = require('../utils/utils')
@ -11,6 +13,14 @@ connection.on('disconnect', () => {
logger.error('Disconnected from amqp Broker')
})
async function isAttached() {
if (!process.env.ORACLE_QUEUE_URL) {
return false
}
const amqpHost = new url.URL(process.env.ORACLE_QUEUE_URL).hostname
return new Promise(res => dns.lookup(amqpHost, err => res(err === null)))
}
function connectWatcherToQueue({ queueName, workerQueue, cb }) {
const queueList = workerQueue ? [queueName, workerQueue] : [queueName]
@ -119,6 +129,7 @@ async function generateRetry({ data, msgRetries, channelWrapper, channel, queueN
}
module.exports = {
isAttached,
connectWatcherToQueue,
connectSenderToQueue,
connectWorkerToQueue,

@ -45,7 +45,7 @@ const fetchGasPrice = async (speedType, factor, bridgeContract, gasPriceSupplier
return cachedGasPrice
}
async function start(chainId) {
async function start(chainId, fetchOnce) {
clearInterval(fetchGasPriceInterval)
let bridgeContract = null
@ -73,10 +73,16 @@ async function start(chainId) {
throw new Error(`Unrecognized chainId '${chainId}'`)
}
if (fetchOnce) {
await fetchGasPrice(speedType, factor, bridgeContract, () => fetch(gasPriceSupplierUrl))
return getPrice()
}
fetchGasPriceInterval = setIntervalAndRun(
() => fetchGasPrice(speedType, factor, bridgeContract, () => fetch(gasPriceSupplierUrl)),
updateInterval
)
return null
}
function getPrice() {

@ -69,10 +69,34 @@ async function getEvents({ contract, event, fromBlock, toBlock, filter }) {
}
}
async function getEventsFromTx({ web3, contract, event, txHash, filter }) {
try {
const contractAddress = contract.options.address
logger.info({ contractAddress, event, txHash }, 'Getting past events for specific transaction')
const { logs } = await web3.eth.getTransactionReceipt(txHash)
const eventAbi = contract.options.jsonInterface.find(abi => abi.name === event)
const decodeAbi = contract._decodeEventABI.bind(eventAbi)
const pastEvents = logs
.filter(event => event.topics[0] === eventAbi.signature)
.map(decodeAbi)
.filter(event =>
eventAbi.inputs.every(arg => {
const encodeParam = param => web3.eth.abi.encodeParameter(arg.type, param)
return !filter[arg.name] || encodeParam(filter[arg.name]) === encodeParam(event.returnValues[arg.name])
})
)
logger.debug({ contractAddress, event, count: pastEvents.length }, 'Past events obtained')
return pastEvents
} catch (e) {
throw new Error(`${event} events cannot be obtained`)
}
}
module.exports = {
getNonce,
getBlockNumber,
getChainId,
getRequiredBlockConfirmations,
getEvents
getEvents,
getEventsFromTx
}

@ -7,18 +7,18 @@
"license": "MIT",
"private": true,
"devDependencies": {
"wsrun": "3.6.5",
"eslint-config-prettier": "3.1.0",
"eslint": "5.16.0",
"eslint-config-airbnb": "17.1.0",
"eslint-config-prettier": "3.1.0",
"eslint-plugin-flowtype": "^3.8.1",
"eslint-plugin-jsx-a11y": "^6.2.1",
"eslint-plugin-node": "7.0.1",
"eslint-plugin-prettier": "2.6.2",
"eslint-plugin-react": "^7.13.0",
"eslint-plugin-react-hooks": "^1.6.0",
"eslint-plugin-flowtype": "^3.8.1",
"mocha": "^5.2.0",
"prettier": "1.14.3",
"mocha": "^5.2.0"
"wsrun": "3.6.5"
},
"workspaces": [
"commons",
@ -29,11 +29,14 @@
"monitor",
"monitor-e2e",
"contracts",
"burner-wallet-plugin"
"burner-wallet-plugin",
"alm"
],
"scripts": {
"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",
"build": "yarn build:ui && yarn build:alm && yarn build:plugin",
"build:ui": "yarn workspace ui run build",
"build:alm": "yarn workspace alm run build",
"build:plugin": "yarn workspace burner-wallet-plugin run build",
"lint": "yarn wsrun --exclude token-bridge-contracts lint",
"test": "yarn wsrun --exclude oracle-e2e --exclude ui-e2e --exclude monitor-e2e test",

@ -19,6 +19,7 @@
"maxCodeSize": 24576,
"maxCodeSizeTransition": "0x0",
"eip140Transition": "0x0",
"eip145Transition": "0x0",
"eip211Transition": "0x0",
"eip214Transition": "0x0",
"eip658Transition": "0x0",

@ -19,6 +19,7 @@
"maxCodeSize": 24576,
"maxCodeSizeTransition": "0x0",
"eip140Transition": "0x0",
"eip145Transition": "0x0",
"eip211Transition": "0x0",
"eip214Transition": "0x0",
"eip658Transition": "0x0",

@ -5,6 +5,7 @@ const {
nativeToErcBridge,
ercToErcBridge,
ercToNativeBridge,
ambStakeErcToErc,
homeRPC,
foreignRPC
} = require('../e2e-commons/constants.json')
@ -38,6 +39,10 @@ class Utils {
return ercToNativeBridge.ui
}
static async getAMBStakeStartURL() {
return ambStakeErcToErc.ui
}
static async startBrowserWithMetamask() {
const source = './MetaMask.crx'
const options = new chrome.Options()

@ -2,7 +2,7 @@ const { By } = require('selenium-webdriver/lib/by')
const { Page } = require('./Page.js')
const fieldAmount = By.id('amount')
const buttonTransfer = By.className('bridge-form-button ')
const buttonTransfer = By.className('bridge-form-button')
const buttonOk = By.className('swal-button swal-button--confirm')
const fieldsBalance = By.className('network-balance')
const classWeb3Loaded = By.className('web3-loaded')

@ -308,4 +308,94 @@ test.describe('e2e-test for bridge.poa, version 1.5.0', async function() {
return await assert.strictEqual(result, true, 'Test FAILED. Foreign POA balance is not correct after transaction')
})
})
test.describe('AMB-STAKE-ERC-TO-ERC', async () => {
test.it('User is able to open main page of bridge-ui ', async () => {
startURL = await Utils.getAMBStakeStartURL()
const result = await mainPage.open(startURL)
console.log('Test URL: ' + startURL)
return await assert.strictEqual(result, true, 'Test FAILED. Build failed.')
})
test.it('Home page: disclaimer is displayed ', async () => {
const result = await mainPage.confirmDisclaimer()
return await assert.strictEqual(result, true, 'Test FAILED. Disclaimer is not displayed')
})
test.it('Main page: foreign erc20 balance is displayed', async () => {
await foreignAccount.setMetaMaskNetwork()
foreignBalanceBefore = await mainPage.getForeignPOABalance()
console.log('foreignBalanceBefore = ' + foreignBalanceBefore)
const result = foreignBalanceBefore !== 0
return await assert.strictEqual(result, true, 'Test FAILED. Foreign erc20 balance is zero')
})
test.it('Main page: home erc20 balance is displayed', async () => {
homeBalanceBefore = await mainPage.getHomePOABalance()
console.log('homeBalanceBefore = ' + homeBalanceBefore)
const result = homeBalanceBefore !== 0
return await assert.strictEqual(result, true, 'Test FAILED. Home erc20 balance is zero or not displayed ')
})
test.it('User is able to send tokens from Foreign account to Home account', async () => {
homeBalanceBefore = await mainPage.getForeignPOABalance()
foreignBalanceBefore = await mainPage.getHomePOABalance()
const result = await foreignAccount.transferTokens(maxAmountPerTransactionLimit)
return await assert.strictEqual(
result,
true,
'Test FAILED. User is able send tokens from Foreign account to Home account'
)
})
test.it('Foreign POA balance has correctly changed after transaction', async () => {
const newForeignBalance = await mainPage.getHomePOABalance()
const shouldBe = foreignBalanceBefore - maxAmountPerTransactionLimit
console.log('newForeignBalance = ' + newForeignBalance)
console.log('shouldBe = ' + shouldBe)
const result = Math.abs(shouldBe - newForeignBalance) < maxAmountPerTransactionLimit / 100
return await assert.strictEqual(result, true, 'Test FAILED.Foreign POA balance is not correct after transaction')
})
test.it('Home account has received correct amount of tokens after transaction', async () => {
const newHomeBalance = await mainPage.getForeignPOABalance()
const shouldBe = homeBalanceBefore + maxAmountPerTransactionLimit
console.log('newHomeBalance = ' + newHomeBalance)
console.log('shouldBe = ' + shouldBe)
const result = Math.abs(shouldBe - newHomeBalance) < maxAmountPerTransactionLimit / 100
return await assert.strictEqual(result, true, 'Test FAILED.Home POA balance is not correct after transaction')
})
test.it('User is able to send tokens from Home account to Foreign account', async () => {
await homeAccount.setMetaMaskNetwork()
homeBalanceBefore = await mainPage.getHomePOABalance()
foreignBalanceBefore = await mainPage.getForeignPOABalance()
const result = await homeAccount.transferTokens(maxAmountPerTransactionLimit)
return await assert.strictEqual(
result,
true,
'Test FAILED. User is able send tokens from Home account to Foreign account'
)
})
test.it('Home POA balance has correctly changed after transaction', async () => {
const newHomeBalance = await mainPage.getHomePOABalance()
const shouldBe = homeBalanceBefore - maxAmountPerTransactionLimit
console.log('newHomeBalance = ' + newHomeBalance)
console.log('shouldBe = ' + shouldBe)
const result = Math.abs(shouldBe - newHomeBalance) < maxAmountPerTransactionLimit / 100
homeBalanceBefore = newHomeBalance
return await assert.strictEqual(result, true, 'Test FAILED.Home POA balance is not correct after transaction')
})
test.it('Foreign account has received correct amount of tokens after transaction', async () => {
const newForeignBalance = await mainPage.getForeignPOABalance()
const shouldBe = foreignBalanceBefore + maxAmountPerTransactionLimit
console.log('newForeignBalance = ' + newForeignBalance)
console.log('shouldBe = ' + shouldBe)
const result = Math.abs(shouldBe - newForeignBalance) < maxAmountPerTransactionLimit / 100
return await assert.strictEqual(result, true, 'Test FAILED. Foreign POA balance is not correct after transaction')
})
})
})

@ -32,11 +32,15 @@ COMMON_FOREIGN_GAS_PRICE_FACTOR=1
# Default
UI_TITLE=TokenBridge UI app - %c
UI_OG_TITLE=POA Bridge UI
UI_DESCRIPTION=The POA cross-chain bridge serves as a method of transferring POA native tokens from the POA Network to the Ethereum network in a quick and cost-efficient manner.
UI_PORT=3000
UI_PUBLIC_URL=https://bridge.poa.net
# RSK
#UI_DESCRIPTION=The TokenBridge serves as a method of transferring Bancor Network tokens between the Ethereum network and the Rootstock network in a quick and cost-efficient manner.
# To use Ethereum-classic styles
#UI_STYLES=classic
# To use Ethereum-classic styles: classic
# To use STAKE styles: stake
# To use poa core styles: core
UI_STYLES=core

@ -7,6 +7,7 @@
"bignumber.js": "^6.0.0",
"coveralls": "^3.0.0",
"customize-cra": "^0.2.12",
"date-fns": "^2.13.0",
"dotenv": "^7.0.0",
"fs-extra": "^5.0.0",
"mobx": "^4.0.2",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

BIN
ui/public/favicons/core.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

@ -15,4 +15,4 @@
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 852 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 852 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

@ -5,14 +5,13 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<meta property="og:title" content="POA Bridge UI" />
<meta property="og:title" content="%REACT_APP_UI_OG_TITLE%" />
<meta property="og:description" content="%REACT_APP_UI_DESCRIPTION%" />
<meta property="og:url" content="https://poanetwork.github.io/bridge-ui" />
<meta property="og:url" content="%REACT_APP_UI_PUBLIC_URL%" />
<meta property="og:type" content="website" />
<meta property="og:image" content="/images/bridgeogimage.jpg">
<link href="https://fonts.googleapis.com/css?family=Nunito:300,400,700" rel="stylesheet">
<link rel="manifest" href="%PUBLIC_URL%/favicons/manifest.json">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicons/favicon.ico">
<meta property="og:image" content="%REACT_APP_UI_PUBLIC_URL%/images/ogimage-%REACT_APP_UI_STYLES%.png">
<link rel="manifest" href="/favicons/manifest.json">
<link rel="shortcut icon" href="/favicons/%REACT_APP_UI_STYLES%.ico">
<title>TokenBridge UI app</title>
</head>

@ -6,12 +6,7 @@ const fs = require('fs')
const stylePath = path.resolve(__dirname, '..', 'src', 'assets', 'stylesheets')
const destinationFilename = 'application.css'
let filename
if (process.env.UI_STYLES === 'classic') {
filename = 'application.classic.css'
} else {
filename = 'application.core.css'
}
const theme = process.env.UI_STYLES || 'core'
const filename = `application.${theme}.css`
fs.copyFileSync(path.resolve(stylePath, filename), path.resolve(stylePath, destinationFilename))

@ -0,0 +1,7 @@
<svg width="220" height="105" viewBox="0 0 220 105" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="path-1-inside-1" fill="white">
<path fill-rule="evenodd" clip-rule="evenodd" d="M112.828 1.85788C111.266 0.295785 108.734 0.295786 107.172 1.85788L100.029 9.00001H4C1.79086 9.00001 0 10.7909 0 13V101C0 103.209 1.79086 105 4 105H216C218.209 105 220 103.209 220 101V13C220 10.7909 218.209 9.00001 216 9.00001H119.971L112.828 1.85788Z"/>
</mask>
<path fill-rule="evenodd" clip-rule="evenodd" d="M112.828 1.85788C111.266 0.295785 108.734 0.295786 107.172 1.85788L100.029 9.00001H4C1.79086 9.00001 0 10.7909 0 13V101C0 103.209 1.79086 105 4 105H216C218.209 105 220 103.209 220 101V13C220 10.7909 218.209 9.00001 216 9.00001H119.971L112.828 1.85788Z" fill="white"/>
<path d="M107.172 1.85788L106.464 1.15077V1.15078L107.172 1.85788ZM112.828 1.85788L113.536 1.15078V1.15077L112.828 1.85788ZM100.029 9.00001V10H100.444L100.737 9.70711L100.029 9.00001ZM119.971 9.00001L119.263 9.70711L119.556 10H119.971V9.00001ZM107.879 2.56499C109.05 1.39342 110.95 1.39342 112.121 2.56499L113.536 1.15077C111.583 -0.801845 108.417 -0.801845 106.464 1.15077L107.879 2.56499ZM100.737 9.70711L107.879 2.56499L106.464 1.15078L99.3223 8.2929L100.737 9.70711ZM4 10H100.029V8.00001H4V10ZM1 13C1 11.3432 2.34315 10 4 10V8.00001C1.23858 8.00001 -1 10.2386 -1 13H1ZM1 101V13H-1V101H1ZM4 104C2.34314 104 1 102.657 1 101H-1C-1 103.761 1.23857 106 4 106V104ZM216 104H4V106H216V104ZM219 101C219 102.657 217.657 104 216 104V106C218.761 106 221 103.761 221 101H219ZM219 13V101H221V13H219ZM216 10C217.657 10 219 11.3432 219 13H221C221 10.2386 218.761 8.00001 216 8.00001V10ZM119.971 10H216V8.00001H119.971V10ZM112.121 2.56499L119.263 9.70711L120.678 8.2929L113.536 1.15078L112.121 2.56499Z" fill="#E6ECF1" mask="url(#path-1-inside-1)"/>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

@ -0,0 +1,7 @@
<svg width="127" height="89" viewBox="0 0 127 89" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="path-1-inside-1" fill="white">
<path fill-rule="evenodd" clip-rule="evenodd" d="M65.8284 1.17157C64.2663 -0.390524 61.7337 -0.390524 60.1716 1.17157L53.3431 8.00001H4C1.79086 8.00001 0 9.79086 0 12V85C0 87.2091 1.79086 89 4 89H123C125.209 89 127 87.2092 127 85V12C127 9.79087 125.209 8.00001 123 8.00001H72.6569L65.8284 1.17157Z"/>
</mask>
<path fill-rule="evenodd" clip-rule="evenodd" d="M65.8284 1.17157C64.2663 -0.390524 61.7337 -0.390524 60.1716 1.17157L53.3431 8.00001H4C1.79086 8.00001 0 9.79086 0 12V85C0 87.2091 1.79086 89 4 89H123C125.209 89 127 87.2092 127 85V12C127 9.79087 125.209 8.00001 123 8.00001H72.6569L65.8284 1.17157Z" fill="white"/>
<path d="M60.1716 1.17157L60.8787 1.87868V1.87868L60.1716 1.17157ZM65.8284 1.17157L66.5355 0.464466V0.464465L65.8284 1.17157ZM53.3431 8.00001V9.00001H53.7574L54.0502 8.70711L53.3431 8.00001ZM72.6569 8.00001L71.9498 8.70711L72.2426 9.00001H72.6569V8.00001ZM60.8787 1.87868C62.0503 0.707108 63.9497 0.707105 65.1213 1.87868L66.5355 0.464465C64.5829 -1.48815 61.4171 -1.48816 59.4645 0.464467L60.8787 1.87868ZM54.0502 8.70711L60.8787 1.87868L59.4645 0.464466L52.636 7.2929L54.0502 8.70711ZM4 9.00001H53.3431V7.00001H4V9.00001ZM1 12C1 10.3431 2.34314 9.00001 4 9.00001V7.00001C1.23858 7.00001 -1 9.23858 -1 12H1ZM1 85V12H-1V85H1ZM4 88C2.34314 88 1 86.6569 1 85H-1C-1 87.7614 1.23857 90 4 90V88ZM123 88H4V90H123V88ZM126 85C126 86.6569 124.657 88 123 88V90C125.761 90 128 87.7614 128 85H126ZM126 12V85H128V12H126ZM123 9.00001C124.657 9.00001 126 10.3432 126 12H128C128 9.23858 125.761 7.00001 123 7.00001V9.00001ZM72.6569 9.00001H123V7.00001H72.6569V9.00001ZM65.1213 1.87868L71.9498 8.70711L73.364 7.2929L66.5355 0.464466L65.1213 1.87868Z" fill="#E6ECF1" mask="url(#path-1-inside-1)"/>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

Some files were not shown because too many files have changed in this diff Show More