Compare commits
No commits in common. "master" and "2.2.1" have entirely different histories.
321
.circleci/config.yml
Normal file
321
.circleci/config.yml
Normal file
@ -0,0 +1,321 @@
|
|||||||
|
version: 2.1
|
||||||
|
|
||||||
|
orbs:
|
||||||
|
tokenbridge-orb:
|
||||||
|
commands:
|
||||||
|
install-chrome:
|
||||||
|
steps:
|
||||||
|
- run:
|
||||||
|
name: Update dpkg
|
||||||
|
command: |
|
||||||
|
sudo apt-get clean
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install dpkg
|
||||||
|
- run:
|
||||||
|
name: Install Chrome
|
||||||
|
command: |
|
||||||
|
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:
|
||||||
|
- run:
|
||||||
|
name: Install Node
|
||||||
|
command: |
|
||||||
|
export NVM_DIR="/opt/circleci/.nvm"
|
||||||
|
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
|
||||||
|
|
||||||
|
nvm install 10.16.3 && nvm alias default 10.16.3
|
||||||
|
|
||||||
|
echo 'export NVM_DIR="/opt/circleci/.nvm"' >> $BASH_ENV
|
||||||
|
echo ' [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"' >> $BASH_ENV
|
||||||
|
install-yarn:
|
||||||
|
steps:
|
||||||
|
- run:
|
||||||
|
name: Install Yarn
|
||||||
|
command: |
|
||||||
|
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
|
||||||
|
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
|
||||||
|
sudo apt-get update && sudo apt-get -y install yarn
|
||||||
|
yarn-install-cached-on-machine:
|
||||||
|
steps:
|
||||||
|
- restore_cache:
|
||||||
|
name: Restore Machine Yarn Package Cache
|
||||||
|
keys:
|
||||||
|
- yarn-machine-{{ checksum "package.json" }}-{{ checksum "yarn.lock" }}
|
||||||
|
- run:
|
||||||
|
name: Install npm dependencies using Yarn
|
||||||
|
command: nvm use default; yarn install --frozen-lockfile
|
||||||
|
- save_cache:
|
||||||
|
name: Save Machine Yarn Package Cache
|
||||||
|
key: yarn-machine-{{ checksum "package.json" }}-{{ checksum "yarn.lock" }}
|
||||||
|
paths:
|
||||||
|
- ~/.cache/yarn
|
||||||
|
wait-for-oracle:
|
||||||
|
parameters:
|
||||||
|
redis-key:
|
||||||
|
type: string
|
||||||
|
steps:
|
||||||
|
- run:
|
||||||
|
name: Install redis tools
|
||||||
|
command: sudo apt-get install -y redis-tools
|
||||||
|
- run:
|
||||||
|
name: Wait for the Oracle to start
|
||||||
|
command: |
|
||||||
|
set +e
|
||||||
|
i=0
|
||||||
|
while [[ $(redis-cli GET << parameters.redis-key >> ) ]]; do
|
||||||
|
((i++))
|
||||||
|
if [ "$i" -gt 30 ]
|
||||||
|
then
|
||||||
|
exit -1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Sleeping..."
|
||||||
|
sleep 3
|
||||||
|
done
|
||||||
|
executors:
|
||||||
|
docker-node:
|
||||||
|
docker:
|
||||||
|
- image: circleci/node:10.15
|
||||||
|
machine-with-docker-caching:
|
||||||
|
machine:
|
||||||
|
image: circleci/classic:latest
|
||||||
|
docker_layer_caching: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
initialize:
|
||||||
|
executor: tokenbridge-orb/docker-node
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- run: git submodule update --init
|
||||||
|
- restore_cache:
|
||||||
|
name: Restore Yarn Package Cache
|
||||||
|
keys:
|
||||||
|
- yarn-{{ checksum "package.json" }}-{{ checksum "yarn.lock" }}
|
||||||
|
- run: git submodule status > submodule.status
|
||||||
|
- restore_cache:
|
||||||
|
name: Restore contracts submodule with compiled contracts
|
||||||
|
keys:
|
||||||
|
- contracts-{{ checksum "submodule.status" }}
|
||||||
|
- run: yarn install --frozen-lockfile
|
||||||
|
- save_cache:
|
||||||
|
name: Save Yarn Package Cache
|
||||||
|
key: yarn-{{ checksum "package.json" }}-{{ checksum "yarn.lock" }}
|
||||||
|
paths:
|
||||||
|
- ~/.cache/yarn
|
||||||
|
- run: touch install_deploy.log; test -d contracts/build/contracts || yarn install:deploy &> install_deploy.log
|
||||||
|
- store_artifacts:
|
||||||
|
path: install_deploy.log
|
||||||
|
- run: test -d contracts/build/contracts || yarn compile:contracts
|
||||||
|
- save_cache:
|
||||||
|
name: Save contracts submodule with compiled contracts
|
||||||
|
key: contracts-{{ checksum "submodule.status" }}
|
||||||
|
paths:
|
||||||
|
- contracts
|
||||||
|
- save_cache:
|
||||||
|
name: Save initialized project for subsequent jobs
|
||||||
|
key: initialize-{{ .Environment.CIRCLE_SHA1 }}
|
||||||
|
paths:
|
||||||
|
- ~/project
|
||||||
|
initialize-root:
|
||||||
|
executor: tokenbridge-orb/docker-node
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- run: sudo su - -c 'export CI=true && cd /home/circleci/project && yarn initialize && yarn test'
|
||||||
|
build:
|
||||||
|
executor: tokenbridge-orb/docker-node
|
||||||
|
steps:
|
||||||
|
- restore_cache:
|
||||||
|
key: initialize-{{ .Environment.CIRCLE_SHA1 }}
|
||||||
|
- run: yarn run build
|
||||||
|
lint:
|
||||||
|
executor: tokenbridge-orb/docker-node
|
||||||
|
steps:
|
||||||
|
- restore_cache:
|
||||||
|
key: initialize-{{ .Environment.CIRCLE_SHA1 }}
|
||||||
|
- run: yarn run lint
|
||||||
|
test:
|
||||||
|
executor: tokenbridge-orb/docker-node
|
||||||
|
steps:
|
||||||
|
- restore_cache:
|
||||||
|
key: initialize-{{ .Environment.CIRCLE_SHA1 }}
|
||||||
|
- run: yarn run test
|
||||||
|
oracle-e2e:
|
||||||
|
executor: tokenbridge-orb/docker-node
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- run: git submodule update --init
|
||||||
|
- setup_remote_docker:
|
||||||
|
docker_layer_caching: true
|
||||||
|
- run: yarn run oracle-e2e
|
||||||
|
ui-e2e:
|
||||||
|
executor: tokenbridge-orb/machine-with-docker-caching
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- tokenbridge-orb/install-node
|
||||||
|
- tokenbridge-orb/install-yarn
|
||||||
|
- tokenbridge-orb/install-chrome
|
||||||
|
- run: git submodule update --init
|
||||||
|
- tokenbridge-orb/yarn-install-cached-on-machine
|
||||||
|
- run: yarn run ui-e2e
|
||||||
|
monitor-e2e:
|
||||||
|
executor: tokenbridge-orb/machine-with-docker-caching
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- run: git submodule update --init
|
||||||
|
- run: ./monitor-e2e/run-tests.sh
|
||||||
|
cover:
|
||||||
|
executor: tokenbridge-orb/docker-node
|
||||||
|
steps:
|
||||||
|
- restore_cache:
|
||||||
|
key: initialize-{{ .Environment.CIRCLE_SHA1 }}
|
||||||
|
- run: yarn workspace ui run coverage
|
||||||
|
- run: yarn workspace ui run coveralls
|
||||||
|
deployment-oracle:
|
||||||
|
executor: tokenbridge-orb/machine-with-docker-caching
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- run: git submodule update --init
|
||||||
|
- run:
|
||||||
|
name: Run the scenario
|
||||||
|
command: deployment-e2e/molecule.sh oracle
|
||||||
|
no_output_timeout: 40m
|
||||||
|
deployment-ui:
|
||||||
|
executor: tokenbridge-orb/machine-with-docker-caching
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- run: git submodule update --init
|
||||||
|
- run:
|
||||||
|
name: Run the scenario
|
||||||
|
command: deployment-e2e/molecule.sh ui
|
||||||
|
no_output_timeout: 40m
|
||||||
|
deployment-monitor:
|
||||||
|
executor: tokenbridge-orb/machine-with-docker-caching
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- run: git submodule update --init
|
||||||
|
- run:
|
||||||
|
name: Run the scenario
|
||||||
|
command: deployment-e2e/molecule.sh monitor
|
||||||
|
no_output_timeout: 40m
|
||||||
|
deployment-repo:
|
||||||
|
executor: tokenbridge-orb/machine-with-docker-caching
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- run: git submodule update --init
|
||||||
|
- tokenbridge-orb/install-node
|
||||||
|
- tokenbridge-orb/install-yarn
|
||||||
|
- tokenbridge-orb/yarn-install-cached-on-machine
|
||||||
|
- run:
|
||||||
|
name: Run the scenario
|
||||||
|
command: deployment-e2e/molecule.sh repo
|
||||||
|
no_output_timeout: 40m
|
||||||
|
deployment-multiple:
|
||||||
|
executor: tokenbridge-orb/machine-with-docker-caching
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- run: git submodule update --init
|
||||||
|
- run:
|
||||||
|
name: Run the scenario
|
||||||
|
command: deployment-e2e/molecule.sh multiple
|
||||||
|
no_output_timeout: 40m
|
||||||
|
ultimate:
|
||||||
|
executor: tokenbridge-orb/machine-with-docker-caching
|
||||||
|
parameters:
|
||||||
|
scenario-name:
|
||||||
|
description: "Molecule scenario name used to create the infrastructure"
|
||||||
|
type: string
|
||||||
|
redis-key:
|
||||||
|
description: "Redis key checked for non-emptiness to assert if Oracle is running"
|
||||||
|
type: string
|
||||||
|
ui-e2e-grep:
|
||||||
|
description: "Mocha grep string used to run ui-e2e tests specific to given type of bridge"
|
||||||
|
default: ''
|
||||||
|
type: string
|
||||||
|
oracle-e2e-script:
|
||||||
|
description: "Yarn script string used to run oracle-e2e tests specific to given type of bridge"
|
||||||
|
default: ''
|
||||||
|
type: string
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- run: git submodule update --init
|
||||||
|
- tokenbridge-orb/install-node
|
||||||
|
- tokenbridge-orb/install-chrome
|
||||||
|
- tokenbridge-orb/install-yarn
|
||||||
|
- tokenbridge-orb/yarn-install-cached-on-machine
|
||||||
|
- run:
|
||||||
|
name: Prepare the infrastructure
|
||||||
|
command: e2e-commons/up.sh deploy << parameters.scenario-name >> blocks
|
||||||
|
no_output_timeout: 50m
|
||||||
|
- tokenbridge-orb/wait-for-oracle:
|
||||||
|
redis-key: << parameters.redis-key >>
|
||||||
|
- when:
|
||||||
|
condition: << parameters.ui-e2e-grep >>
|
||||||
|
steps:
|
||||||
|
- run:
|
||||||
|
name: Run the ui-e2e tests
|
||||||
|
command: |
|
||||||
|
nvm use default;
|
||||||
|
cd ui-e2e; yarn mocha -g "<< parameters.ui-e2e-grep >>" -b ./test.js
|
||||||
|
- when:
|
||||||
|
condition: << parameters.oracle-e2e-script >>
|
||||||
|
steps:
|
||||||
|
- run:
|
||||||
|
name: Run the oracle-e2e tests
|
||||||
|
command: cd e2e-commons && docker-compose run e2e yarn workspace oracle-e2e run << parameters.oracle-e2e-script >>
|
||||||
|
workflows:
|
||||||
|
tokenbridge:
|
||||||
|
jobs:
|
||||||
|
- initialize
|
||||||
|
- initialize-root:
|
||||||
|
filters:
|
||||||
|
branches:
|
||||||
|
only: master
|
||||||
|
- build:
|
||||||
|
requires:
|
||||||
|
- initialize
|
||||||
|
- lint:
|
||||||
|
requires:
|
||||||
|
- initialize
|
||||||
|
- test:
|
||||||
|
requires:
|
||||||
|
- initialize
|
||||||
|
- cover:
|
||||||
|
requires:
|
||||||
|
- initialize
|
||||||
|
filters:
|
||||||
|
branches:
|
||||||
|
only: master
|
||||||
|
- oracle-e2e
|
||||||
|
- ui-e2e
|
||||||
|
- monitor-e2e
|
||||||
|
- deployment-oracle
|
||||||
|
- deployment-ui
|
||||||
|
- deployment-monitor
|
||||||
|
- deployment-repo
|
||||||
|
- deployment-multiple
|
||||||
|
- ultimate:
|
||||||
|
name: "ultimate: native to erc"
|
||||||
|
scenario-name: native-to-erc
|
||||||
|
redis-key: native-erc-collected-signatures:lastProcessedBlock
|
||||||
|
ui-e2e-grep: "NATIVE TO ERC"
|
||||||
|
- ultimate:
|
||||||
|
name: "ultimate: erc to native"
|
||||||
|
scenario-name: erc-to-native
|
||||||
|
redis-key: erc-native-collected-signatures:lastProcessedBlock
|
||||||
|
ui-e2e-grep: "ERC TO NATIVE"
|
||||||
|
- ultimate:
|
||||||
|
name: "ultimate: erc to erc"
|
||||||
|
scenario-name: erc-to-erc
|
||||||
|
redis-key: erc-erc-collected-signatures:lastProcessedBlock
|
||||||
|
ui-e2e-grep: "ERC TO ERC"
|
||||||
|
- ultimate:
|
||||||
|
name: "ultimate: amb"
|
||||||
|
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"
|
||||||
@ -8,17 +8,9 @@
|
|||||||
**/docs
|
**/docs
|
||||||
**/*.md
|
**/*.md
|
||||||
|
|
||||||
monitor/**/*.env*
|
|
||||||
oracle/**/*.env*
|
|
||||||
!**/.env.example
|
|
||||||
|
|
||||||
contracts/test
|
contracts/test
|
||||||
contracts/build
|
contracts/build
|
||||||
oracle/test
|
oracle/test
|
||||||
monitor/test
|
|
||||||
monitor/responses
|
|
||||||
monitor/cache/*
|
|
||||||
commons/test
|
|
||||||
oracle/**/*.png
|
oracle/**/*.png
|
||||||
oracle/**/*.jpg
|
oracle/**/*.jpg
|
||||||
audit
|
audit
|
||||||
|
|||||||
@ -3,4 +3,3 @@ submodules
|
|||||||
coverage
|
coverage
|
||||||
lib
|
lib
|
||||||
dist
|
dist
|
||||||
build
|
|
||||||
|
|||||||
227
.github/workflows/main.yml
vendored
227
.github/workflows/main.yml
vendored
@ -1,227 +0,0 @@
|
|||||||
name: tokenbridge
|
|
||||||
|
|
||||||
on: [push]
|
|
||||||
|
|
||||||
env:
|
|
||||||
DOCKER_REGISTRY: docker.pkg.github.com
|
|
||||||
DOCKER_REPO: poanetwork/tokenbridge
|
|
||||||
DOCKER_IMAGE_BASE: docker.pkg.github.com/poanetwork/tokenbridge
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
initialize:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
outputs:
|
|
||||||
cache_key: ${{ steps.get_cache_key.outputs.cache_key }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/setup-node@v1
|
|
||||||
with:
|
|
||||||
node-version: 12
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
- name: Set cache key
|
|
||||||
id: get_cache_key
|
|
||||||
run: |
|
|
||||||
git submodule status > submodule.status
|
|
||||||
echo "::set-output name=cache_key::cache-repo-${{ hashFiles('yarn.lock', 'package.json', 'submodule.status') }}"
|
|
||||||
- uses: actions/cache@v2
|
|
||||||
id: cache-repo
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
**/node_modules
|
|
||||||
contracts/build
|
|
||||||
key: ${{ steps.get_cache_key.outputs.cache_key }}
|
|
||||||
- name: Install dependencies and compile contracts
|
|
||||||
if: ${{ !steps.cache-repo.outputs.cache-hit }}
|
|
||||||
run: |
|
|
||||||
yarn install --frozen-lockfile
|
|
||||||
yarn run install:deploy
|
|
||||||
yarn run compile:contracts
|
|
||||||
validate:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs:
|
|
||||||
- initialize
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
task: [build, lint, test]
|
|
||||||
steps:
|
|
||||||
- uses: actions/setup-node@v1
|
|
||||||
with:
|
|
||||||
node-version: 12
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
- uses: actions/cache@v2
|
|
||||||
id: cache-repo
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
**/node_modules
|
|
||||||
contracts/build
|
|
||||||
key: ${{ needs.initialize.outputs.cache_key }}
|
|
||||||
- name: yarn run ${{ matrix.task }}
|
|
||||||
run: ${{ steps.cache-repo.outputs.cache-hit }} && yarn run ${{ matrix.task }}
|
|
||||||
build-e2e-images:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
- name: Evaluate e2e docker images tags
|
|
||||||
run: |
|
|
||||||
git submodule status > submodule.status
|
|
||||||
echo "E2E_TAG=${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'Dockerfile.e2e', 'commons', 'oracle-e2e', 'monitor-e2e', 'e2e-commons') }}" >> $GITHUB_ENV
|
|
||||||
echo "ORACLE_TAG=${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'commons', 'oracle') }}" >> $GITHUB_ENV
|
|
||||||
echo "MONITOR_TAG=${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'commons', 'monitor') }}" >> $GITHUB_ENV
|
|
||||||
echo "ALM_TAG=${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'commons', 'alm') }}" >> $GITHUB_ENV
|
|
||||||
- name: Rebuild and push updated images
|
|
||||||
run: |
|
|
||||||
function check_if_image_exists() {
|
|
||||||
curl -fsSlL "https://${{ github.actor }}:${{ github.token }}@${DOCKER_REGISTRY}/v2/${DOCKER_REPO}/tokenbridge-e2e-$1/manifests/$2" > /dev/null
|
|
||||||
}
|
|
||||||
updated=()
|
|
||||||
if ! check_if_image_exists e2e ${E2E_TAG}; then updated+=("e2e"); fi
|
|
||||||
if ! check_if_image_exists oracle ${ORACLE_TAG}; then updated+=("oracle-amb"); fi
|
|
||||||
if ! check_if_image_exists monitor ${MONITOR_TAG}; then updated+=("monitor-amb"); fi
|
|
||||||
if ! check_if_image_exists alm ${ALM_TAG}; then updated+=("alm"); fi
|
|
||||||
if [ ${#updated[@]} -gt 0 ]; then
|
|
||||||
echo "Updated services: ${updated[@]}"
|
|
||||||
cd e2e-commons
|
|
||||||
docker login ${DOCKER_REGISTRY} -u ${{ github.actor }} -p ${{ github.token }}
|
|
||||||
docker-compose build ${updated[@]}
|
|
||||||
docker-compose push ${updated[@]}
|
|
||||||
else
|
|
||||||
echo "Nothing relevant was changed in the source"
|
|
||||||
fi
|
|
||||||
build-molecule-runner:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
- name: Evaluate e2e molecule runner tag
|
|
||||||
run: echo "MOLECULE_RUNNER_TAG=${{ hashFiles('./deployment-e2e/Dockerfile') }}" >> $GITHUB_ENV
|
|
||||||
- name: Rebuild and push molecule runner e2e image
|
|
||||||
run: |
|
|
||||||
function check_if_image_exists() {
|
|
||||||
curl -fsSlL "https://${{ github.actor }}:${{ github.token }}@${DOCKER_REGISTRY}/v2/${DOCKER_REPO}/tokenbridge-e2e-$1/manifests/$2" > /dev/null
|
|
||||||
}
|
|
||||||
if check_if_image_exists molecule_runner ${MOLECULE_RUNNER_TAG}; then
|
|
||||||
echo "Image already exists"
|
|
||||||
else
|
|
||||||
cd e2e-commons
|
|
||||||
docker login ${DOCKER_REGISTRY} -u ${{ github.actor }} -p ${{ github.token }}
|
|
||||||
docker-compose build molecule_runner
|
|
||||||
docker-compose push molecule_runner
|
|
||||||
fi
|
|
||||||
e2e:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs:
|
|
||||||
- initialize
|
|
||||||
- build-e2e-images
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
task: [oracle-e2e, monitor-e2e, alm-e2e]
|
|
||||||
include:
|
|
||||||
- task: alm-e2e
|
|
||||||
use-cache: true
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
- name: Evaluate e2e docker images tags
|
|
||||||
run: |
|
|
||||||
git submodule status > submodule.status
|
|
||||||
echo "E2E_TAG=${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'Dockerfile.e2e', 'commons', 'oracle-e2e', 'monitor-e2e', 'e2e-commons') }}" >> $GITHUB_ENV
|
|
||||||
echo "ORACLE_TAG=${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'commons', 'oracle') }}" >> $GITHUB_ENV
|
|
||||||
echo "MONITOR_TAG=${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'commons', 'monitor') }}" >> $GITHUB_ENV
|
|
||||||
echo "ALM_TAG=${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'commons', 'alm') }}" >> $GITHUB_ENV
|
|
||||||
- if: ${{ matrix.use-cache }}
|
|
||||||
uses: actions/cache@v2
|
|
||||||
id: cache-repo
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
**/node_modules
|
|
||||||
contracts/build
|
|
||||||
key: ${{ needs.initialize.outputs.cache_key }}
|
|
||||||
- name: Login to docker registry
|
|
||||||
run: docker login ${DOCKER_REGISTRY} -u ${{ github.actor }} -p ${{ github.token }}
|
|
||||||
- name: yarn run ${{ matrix.task }}
|
|
||||||
run: ${{ !matrix.use-cache || steps.cache-repo.outputs.cache-hit }} && yarn run ${{ matrix.task }}
|
|
||||||
- name: Upload logs
|
|
||||||
if: always()
|
|
||||||
uses: actions/upload-artifact@v2
|
|
||||||
with:
|
|
||||||
name: logs-${{ matrix.task }}
|
|
||||||
path: e2e-commons/logs
|
|
||||||
deployment:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs:
|
|
||||||
- build-e2e-images
|
|
||||||
- build-molecule-runner
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
task: [oracle, monitor, multiple, repo]
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
- name: Evaluate e2e molecule runner tag
|
|
||||||
run: echo "MOLECULE_RUNNER_TAG=${{ hashFiles('./deployment-e2e/Dockerfile') }}" >> $GITHUB_ENV
|
|
||||||
- name: Login to docker registry
|
|
||||||
run: docker login ${DOCKER_REGISTRY} -u ${{ github.actor }} -p ${{ github.token }}
|
|
||||||
- run: deployment-e2e/molecule.sh ${{ matrix.task }}
|
|
||||||
ultimate:
|
|
||||||
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop' || startsWith(github.ref, 'refs/tags') || contains(github.event.head_commit.message, 'ultimate')
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs:
|
|
||||||
- initialize
|
|
||||||
- build-e2e-images
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
task: [amb, erc-to-native]
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
- name: Evaluate e2e docker images tags
|
|
||||||
run: |
|
|
||||||
git submodule status > submodule.status
|
|
||||||
echo "E2E_TAG=${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'Dockerfile.e2e', 'commons', 'oracle-e2e', 'monitor-e2e', 'e2e-commons') }}" >> $GITHUB_ENV
|
|
||||||
echo "ORACLE_TAG=${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'commons', 'oracle') }}" >> $GITHUB_ENV
|
|
||||||
echo "MONITOR_TAG=${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'commons', 'monitor') }}" >> $GITHUB_ENV
|
|
||||||
echo "ALM_TAG=${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'commons', 'alm') }}" >> $GITHUB_ENV
|
|
||||||
echo "MOLECULE_RUNNER_TAG=${{ hashFiles('./deployment-e2e/Dockerfile') }}" >> $GITHUB_ENV
|
|
||||||
- uses: actions/cache@v2
|
|
||||||
id: cache-repo
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
**/node_modules
|
|
||||||
contracts/build
|
|
||||||
key: ${{ needs.initialize.outputs.cache_key }}
|
|
||||||
- name: Login to docker registry
|
|
||||||
run: docker login ${DOCKER_REGISTRY} -u ${{ github.actor }} -p ${{ github.token }}
|
|
||||||
- name: Deploy contracts
|
|
||||||
run: ${{ steps.cache-repo.outputs.cache-hit }} && e2e-commons/up.sh deploy generate-amb-tx blocks
|
|
||||||
- name: Pull e2e oracle image
|
|
||||||
run: |
|
|
||||||
docker-compose -f ./e2e-commons/docker-compose.yml pull oracle-amb
|
|
||||||
docker tag ${DOCKER_IMAGE_BASE}/tokenbridge-e2e-oracle:${ORACLE_TAG} poanetwork/tokenbridge-oracle:latest
|
|
||||||
- name: Deploy oracle
|
|
||||||
run: deployment-e2e/molecule.sh ultimate-${{ matrix.task }}
|
|
||||||
- name: Reset docker socket permissions
|
|
||||||
run: sudo chown -R $USER:docker /var/run/docker.sock
|
|
||||||
- name: Run oracle e2e tests
|
|
||||||
run: docker-compose -f ./e2e-commons/docker-compose.yml run -e ULTIMATE=true e2e yarn workspace oracle-e2e run ${{ matrix.task }}
|
|
||||||
- name: Save logs
|
|
||||||
if: always()
|
|
||||||
run: e2e-commons/down.sh
|
|
||||||
- name: Upload logs
|
|
||||||
if: always()
|
|
||||||
uses: actions/upload-artifact@v2
|
|
||||||
with:
|
|
||||||
name: logs-ultimate-${{ matrix.task }}
|
|
||||||
path: e2e-commons/logs
|
|
||||||
13
.gitignore
vendored
13
.gitignore
vendored
@ -10,8 +10,11 @@ dist
|
|||||||
|
|
||||||
# misc
|
# misc
|
||||||
.DS_Store
|
.DS_Store
|
||||||
*.env*
|
.env
|
||||||
!.env.example
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
.idea
|
.idea
|
||||||
.nyc_output
|
.nyc_output
|
||||||
logs/
|
logs/
|
||||||
@ -46,9 +49,5 @@ __pycache__
|
|||||||
|
|
||||||
#monitor
|
#monitor
|
||||||
monitor/responses/*
|
monitor/responses/*
|
||||||
monitor/cache/*
|
monitor/configs/*.env
|
||||||
!monitor/cache/.gitkeep
|
|
||||||
!monitor/.gitkeep
|
!monitor/.gitkeep
|
||||||
|
|
||||||
# Local Netlify folder
|
|
||||||
.netlify
|
|
||||||
@ -8,11 +8,11 @@ COMMON_HOME_RPC_URL | The HTTPS URL(s) used to communicate to the RPC nodes in t
|
|||||||
COMMON_FOREIGN_RPC_URL | The HTTPS URL(s) used to communicate to the RPC nodes in the Foreign network. Several URLs can be specified, delimited by spaces. If the connection to one of these nodes is lost the next URL is used for connection. | URL(s)
|
COMMON_FOREIGN_RPC_URL | The HTTPS URL(s) used to communicate to the RPC nodes in the Foreign network. Several URLs can be specified, delimited by spaces. If the connection to one of these nodes is lost the next URL is used for connection. | URL(s)
|
||||||
COMMON_HOME_BRIDGE_ADDRESS | The address of the bridge contract address in the Home network. It is used to listen to events from and send validators' transactions to the Home network. | hexidecimal beginning with "0x"
|
COMMON_HOME_BRIDGE_ADDRESS | The address of the bridge contract address in the Home network. It is used to listen to events from and send validators' transactions to the Home network. | hexidecimal beginning with "0x"
|
||||||
COMMON_FOREIGN_BRIDGE_ADDRESS | The address of the bridge contract address in the Foreign network. It is used to listen to events from and send validators' transactions to the Foreign network. | hexidecimal beginning with "0x"
|
COMMON_FOREIGN_BRIDGE_ADDRESS | The address of the bridge contract address in the Foreign network. It is used to listen to events from and send validators' transactions to the Foreign network. | hexidecimal beginning with "0x"
|
||||||
COMMON_HOME_GAS_PRICE_SUPPLIER_URL | The URL used to get a JSON response from the gas price prediction oracle for the Home network. The gas price provided by the oracle is used to send the validator's transactions to the RPC node. Since it is assumed that the Home network has a predefined gas price (e.g. the gas price in the Core of POA.Network is `1 GWei`), the gas price oracle parameter can be omitted for such networks. Set to `eip1559-gas-estimation` if you want to use EIP1559 RPC-based gas estimation. | URL
|
COMMON_HOME_GAS_PRICE_SUPPLIER_URL | The URL used to get a JSON response from the gas price prediction oracle for the Home network. The gas price provided by the oracle is used to send the validator's transactions to the RPC node. Since it is assumed that the Home network has a predefined gas price (e.g. the gas price in the Core of POA.Network is `1 GWei`), the gas price oracle parameter can be omitted for such networks. | URL
|
||||||
COMMON_HOME_GAS_PRICE_SPEED_TYPE | Assuming the gas price oracle responds with the following JSON structure: `{"fast": 20.0, "block_time": 12.834, "health": true, "standard": 6.0, "block_number": 6470469, "instant": 71.0, "slow": 1.889}`, this parameter specifies the desirable transaction speed. The speed type can be omitted when `COMMON_HOME_GAS_PRICE_SUPPLIER_URL` is not used. | `instant` / `fast` / `standard` / `slow`
|
COMMON_HOME_GAS_PRICE_SPEED_TYPE | Assuming the gas price oracle responds with the following JSON structure: `{"fast": 20.0, "block_time": 12.834, "health": true, "standard": 6.0, "block_number": 6470469, "instant": 71.0, "slow": 1.889}`, this parameter specifies the desirable transaction speed. The speed type can be omitted when `COMMON_HOME_GAS_PRICE_SUPPLIER_URL` is not used. | `instant` / `fast` / `standard` / `slow`
|
||||||
COMMON_HOME_GAS_PRICE_FALLBACK | The gas price (in Wei) that is used if both the oracle and the fall back gas price specified in the Home Bridge contract are not available. | integer
|
COMMON_HOME_GAS_PRICE_FALLBACK | The gas price (in Wei) that is used if both the oracle and the fall back gas price specified in the Home Bridge contract are not available. | integer
|
||||||
COMMON_HOME_GAS_PRICE_FACTOR | A value that will multiply the gas price of the oracle to convert it to gwei. If the oracle API returns gas prices in gwei then this can be set to `1`. Also, it could be used to intentionally pay more gas than suggested by the oracle to guarantee the transaction verification. E.g. `1.25` or `1.5`. | integer
|
COMMON_HOME_GAS_PRICE_FACTOR | A value that will multiply the gas price of the oracle to convert it to gwei. If the oracle API returns gas prices in gwei then this can be set to `1`. Also, it could be used to intentionally pay more gas than suggested by the oracle to guarantee the transaction verification. E.g. `1.25` or `1.5`. | integer
|
||||||
COMMON_FOREIGN_GAS_PRICE_SUPPLIER_URL | The URL used to get a JSON response from the gas price prediction oracle for the Foreign network. The provided gas price is used to send the validator's transactions to the RPC node. If the Foreign network is Ethereum Foundation mainnet, the oracle URL can be: https://gasprice.poa.network. Otherwise this parameter can be omitted. Set to `gas-price-oracle` if you want to use npm `gas-price-oracle` package for retrieving gas price from multiple sources. Set to `eip1559-gas-estimation` if you want to use EIP1559 RPC-based gas estimation. | URL
|
COMMON_FOREIGN_GAS_PRICE_SUPPLIER_URL | The URL used to get a JSON response from the gas price prediction oracle for the Foreign network. The provided gas price is used to send the validator's transactions to the RPC node. If the Foreign network is Ethereum Foundation mainnet, the oracle URL can be: https://gasprice.poa.network. Otherwise this parameter can be omitted. | URL
|
||||||
COMMON_FOREIGN_GAS_PRICE_SPEED_TYPE | Assuming the gas price oracle responds with the following JSON structure: `{"fast": 20.0, "block_time": 12.834, "health": true, "standard": 6.0, "block_number": 6470469, "instant": 71.0, "slow": 1.889}`, this parameter specifies the desirable transaction speed. The speed type can be omitted when `COMMON_FOREIGN_GAS_PRICE_SUPPLIER_URL`is not used. | `instant` / `fast` / `standard` / `slow`
|
COMMON_FOREIGN_GAS_PRICE_SPEED_TYPE | Assuming the gas price oracle responds with the following JSON structure: `{"fast": 20.0, "block_time": 12.834, "health": true, "standard": 6.0, "block_number": 6470469, "instant": 71.0, "slow": 1.889}`, this parameter specifies the desirable transaction speed. The speed type can be omitted when `COMMON_FOREIGN_GAS_PRICE_SUPPLIER_URL`is not used. | `instant` / `fast` / `standard` / `slow`
|
||||||
COMMON_FOREIGN_GAS_PRICE_FALLBACK | The gas price (in Wei) used if both the oracle and fall back gas price specified in the Foreign Bridge contract are not available. | integer
|
COMMON_FOREIGN_GAS_PRICE_FALLBACK | The gas price (in Wei) used if both the oracle and fall back gas price specified in the Foreign Bridge contract are not available. | integer
|
||||||
COMMON_FOREIGN_GAS_PRICE_FACTOR | A value that will multiply the gas price of the oracle to convert it to gwei. If the oracle API returns gas prices in gwei then this can be set to `1`. Also, it could be used to intentionally pay more gas than suggested by the oracle to guarantee the transaction verification. E.g. `1.25` or `1.5`. | integer
|
COMMON_FOREIGN_GAS_PRICE_FACTOR | A value that will multiply the gas price of the oracle to convert it to gwei. If the oracle API returns gas prices in gwei then this can be set to `1`. Also, it could be used to intentionally pay more gas than suggested by the oracle to guarantee the transaction verification. E.g. `1.25` or `1.5`. | integer
|
||||||
@ -22,7 +22,7 @@ COMMON_FOREIGN_GAS_PRICE_FACTOR | A value that will multiply the gas price of th
|
|||||||
|
|
||||||
name | description | value
|
name | description | value
|
||||||
--- | --- | ---
|
--- | --- | ---
|
||||||
ORACLE_BRIDGE_MODE | The bridge mode. The bridge starts listening to a different set of events based on this parameter. | ERC_TO_NATIVE / ARBITRARY_MESSAGE
|
ORACLE_BRIDGE_MODE | The bridge mode. The bridge starts listening to a different set of events based on this parameter. | NATIVE_TO_ERC / ERC_TO_ERC / ERC_TO_NATIVE
|
||||||
ORACLE_ALLOW_HTTP_FOR_RPC | **Only use in test environments - must be omitted in production environments.**. If this parameter is specified and set to `yes`, RPC URLs can be specified in form of HTTP links. A warning that the connection is insecure will be written to the logs. | `yes` / `no`
|
ORACLE_ALLOW_HTTP_FOR_RPC | **Only use in test environments - must be omitted in production environments.**. If this parameter is specified and set to `yes`, RPC URLs can be specified in form of HTTP links. A warning that the connection is insecure will be written to the logs. | `yes` / `no`
|
||||||
ORACLE_HOME_RPC_POLLING_INTERVAL | The interval in milliseconds used to request the RPC node in the Home network for new blocks. The interval should match the average production time for a new block. | integer
|
ORACLE_HOME_RPC_POLLING_INTERVAL | The interval in milliseconds used to request the RPC node in the Home network for new blocks. The interval should match the average production time for a new block. | integer
|
||||||
ORACLE_FOREIGN_RPC_POLLING_INTERVAL | The interval in milliseconds used to request the RPC node in the Foreign network for new blocks. The interval should match the average production time for a new block. | integer
|
ORACLE_FOREIGN_RPC_POLLING_INTERVAL | The interval in milliseconds used to request the RPC node in the Foreign network for new blocks. The interval should match the average production time for a new block. | integer
|
||||||
@ -36,31 +36,29 @@ ORACLE_LOG_LEVEL | Set the level of details in the logs. | `trace` / `debug` / `
|
|||||||
ORACLE_MAX_PROCESSING_TIME | The workers processes will be killed if this amount of time (in milliseconds) is elapsed before they finish processing. It is recommended to set this value to 4 times the value of the longest polling time (set with the `HOME_POLLING_INTERVAL` and `FOREIGN_POLLING_INTERVAL` variables). To disable this, set the time to 0. | integer
|
ORACLE_MAX_PROCESSING_TIME | The workers processes will be killed if this amount of time (in milliseconds) is elapsed before they finish processing. It is recommended to set this value to 4 times the value of the longest polling time (set with the `HOME_POLLING_INTERVAL` and `FOREIGN_POLLING_INTERVAL` variables). To disable this, set the time to 0. | integer
|
||||||
ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY | The private key of the bridge validator used to sign confirmations before sending transactions to the bridge contracts. The validator account is calculated automatically from the private key. Every bridge instance (set of watchers and senders) must have its own unique private key. The specified private key is used to sign transactions on both sides of the bridge. | hexidecimal without "0x"
|
ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY | The private key of the bridge validator used to sign confirmations before sending transactions to the bridge contracts. The validator account is calculated automatically from the private key. Every bridge instance (set of watchers and senders) must have its own unique private key. The specified private key is used to sign transactions on both sides of the bridge. | hexidecimal without "0x"
|
||||||
ORACLE_VALIDATOR_ADDRESS | The public address of the bridge validator | hexidecimal with "0x"
|
ORACLE_VALIDATOR_ADDRESS | The public address of the bridge validator | hexidecimal with "0x"
|
||||||
ORACLE_TX_REDUNDANCY | If set to `true`, instructs oracle to send `eth_sendRawTransaction` requests through all available RPC urls defined in `COMMON_HOME_RPC_URL` and `COMMON_FOREIGN_RPC_URL` variables instead of using first available one
|
|
||||||
ORACLE_HOME_TO_FOREIGN_ALLOWANCE_LIST | Filename with a list of addresses, separated by newlines. If set, determines the privileged set of accounts whose requests will be automatically processed by the CollectedSignatures watcher. | string
|
|
||||||
ORACLE_HOME_TO_FOREIGN_BLOCK_LIST | Filename with a list of addresses, separated by newlines. If set, determines the blocked set of accounts whose requests will not be automatically processed by the CollectedSignatures watcher. Has a lower priority than the `ORACLE_HOME_TO_FOREIGN_ALLOWANCE_LIST` | string
|
## UI configuration
|
||||||
ORACLE_HOME_TO_FOREIGN_CHECK_SENDER | If set to `true`, instructs the oracle to do an extra check for transaction origin in the block/allowance list. `false` by default. | `true` / `false`
|
|
||||||
ORACLE_ALWAYS_RELAY_SIGNATURES | If set to `true`, the oracle will always relay signatures even if it was not the last who finilized the signatures collecting process. The default is `false`. | `true` / `false`
|
name | description | value
|
||||||
ORACLE_RPC_REQUEST_TIMEOUT | Timeout in milliseconds for a single RPC request. Default value is `ORACLE_*_RPC_POLLING_INTERVAL * 2`. | integer
|
--- | --- | ---
|
||||||
ORACLE_HOME_TX_RESEND_INTERVAL | Interval in milliseconds for automatic resending of stuck transactions for Home sender service. Defaults to 20 minutes. | integer
|
UI_TITLE | The title for the bridge UI page. `%c` will be replaced by the name of the network. | string
|
||||||
ORACLE_FOREIGN_TX_RESEND_INTERVAL | Interval in milliseconds for automatic resending of stuck transactions for Foreign sender service. Defaults to 20 minutes. | integer
|
UI_OG_TITLE | The meta title for the deployed bridge page. | string
|
||||||
ORACLE_SHUTDOWN_SERVICE_URL | Optional external URL to some other service/monitor/configuration manager that controls the remote shutdown process. GET request should return `application/json` message with the following schema: `{ shutdown: true/false }`. | URL
|
UI_DESCRIPTION | The meta description for the deployed bridge page. | string
|
||||||
ORACLE_SHUTDOWN_SERVICE_POLLING_INTERVAL | Optional interval in milliseconds used to request the side RPC node or external shutdown service. Default is 120000. | integer
|
UI_NATIVE_TOKEN_DISPLAY_NAME | name of the home native coin | string
|
||||||
ORACLE_SIDE_RPC_URL | Optional HTTPS URL(s) for communication with the external shutdown service or side RPC nodes, used for shutdown manager activities. Several URLs can be specified, delimited by spaces. If the connection to one of these nodes is lost the next URL is used for connection. | URL(s)
|
UI_HOME_NETWORK_DISPLAY_NAME | name to be displayed for home network | string
|
||||||
ORACLE_FOREIGN_ARCHIVE_RPC_URL | Optional HTTPS URL(s) for communication with the archive nodes on the foreign network. Only used in AMB bridge mode for async information request processing. Several URLs can be specified, delimited by spaces. If the connection to one of these nodes is lost the next URL is used for connection. | URL(s)
|
UI_FOREIGN_NETWORK_DISPLAY_NAME | name to be displayed for foreign network | string
|
||||||
ORACLE_SHUTDOWN_CONTRACT_ADDRESS | Optional contract address in the side chain accessible through `ORACLE_SIDE_RPC_URL`, where the method passed in `ORACLE_SHUTDOWN_CONTRACT_METHOD` is implemented. | `address`
|
UI_HOME_WITHOUT_EVENTS | `true` if home network doesn't support events | true/false
|
||||||
ORACLE_SHUTDOWN_CONTRACT_METHOD | Method signature to be used in the side chain to identify the current shutdown status. Method should return boolean. Default value is `isShutdown()`. | `function signature`
|
UI_FOREIGN_WITHOUT_EVENTS | `true` if foreign network doesn't support events | true/false
|
||||||
ORACLE_FOREIGN_RPC_BLOCK_POLLING_LIMIT | Max length for the block range used in `eth_getLogs` requests for polling contract events for the Foreign chain. Infinite, if not provided. | `integer`
|
UI_HOME_EXPLORER_TX_TEMPLATE | template link to transaction on home explorer. `%s` will be replaced by transaction hash | URL template
|
||||||
ORACLE_HOME_RPC_BLOCK_POLLING_LIMIT | Max length for the block range used in `eth_getLogs` requests for polling contract events for the Home chain. Infinite, if not provided. | `integer`
|
UI_FOREIGN_EXPLORER_TX_TEMPLATE | template link to transaction on foreign explorer. `%s` will be replaced by transaction hash | URL template
|
||||||
ORACLE_JSONRPC_ERROR_CODES | Override default JSON rpc error codes that can trigger RPC fallback to the next URL from the list (or a retry in case of a single RPC URL). Default is `-32603,-32002,-32005`. Should be a comma-separated list of negative integers. | `string`
|
UI_HOME_EXPLORER_ADDRESS_TEMPLATE | template link to address on home explorer. `%s` will be replaced by address | URL template
|
||||||
ORACLE_HOME_EVENTS_REPROCESSING | If set to `true`, home events happened in the past will be refetched and processed once again, to ensure that nothing was missed on the first pass. | `bool`
|
UI_FOREIGN_EXPLORER_ADDRESS_TEMPLATE | template link to address on foreign explorer. `%s` will be replaced by address | URL template
|
||||||
ORACLE_HOME_EVENTS_REPROCESSING_BATCH_SIZE | Batch size for one `eth_getLogs` request when reprocessing old logs in the home chain. Defaults to `1000` | `integer`
|
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
|
||||||
ORACLE_HOME_EVENTS_REPROCESSING_BLOCK_DELAY | Block confirmations number, after which old logs are being reprocessed in the home chain. Defaults to `500` | `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
|
||||||
ORACLE_HOME_RPC_SYNC_STATE_CHECK_INTERVAL | Interval for checking JSON RPC sync state, by requesting the latest block number. Oracle will switch to the fallback JSON RPC in case sync process is stuck | `integer`
|
UI_PORT | The port for the UI app. | integer
|
||||||
ORACLE_FOREIGN_EVENTS_REPROCESSING | If set to `true`, foreign events happened in the past will be refetched and processed once again, to ensure that nothing was missed on the first pass. | `bool`
|
UI_STYLES | The set of styles to render the bridge UI page. | core/classic/stake
|
||||||
ORACLE_FOREIGN_EVENTS_REPROCESSING_BATCH_SIZE | Batch size for one `eth_getLogs` request when reprocessing old logs in the foreign chain. Defaults to `500` | `integer`
|
UI_PUBLIC_URL | The public url for the deployed bridge page | string
|
||||||
ORACLE_FOREIGN_EVENTS_REPROCESSING_BLOCK_DELAY | Block confirmations number, after which old logs are being reprocessed in the foreign chain. Defaults to `250` | `integer`
|
|
||||||
ORACLE_FOREIGN_RPC_SYNC_STATE_CHECK_INTERVAL | Interval for checking JSON RPC sync state, by requesting the latest block number. Oracle will switch to the fallback JSON RPC in case sync process is stuck | `integer`
|
|
||||||
|
|
||||||
|
|
||||||
## Monitor configuration
|
## Monitor configuration
|
||||||
@ -74,9 +72,4 @@ MONITOR_VALIDATOR_FOREIGN_TX_LIMIT | Average gas usage of a transaction sent by
|
|||||||
MONITOR_TX_NUMBER_THRESHOLD | If estimated number of transaction is equal to or below this value, the monitor will report that the validator has less funds than it is required. | integer
|
MONITOR_TX_NUMBER_THRESHOLD | If estimated number of transaction is equal to or below this value, the monitor will report that the validator has less funds than it is required. | integer
|
||||||
MONITOR_PORT | The port for the Monitor. | integer
|
MONITOR_PORT | The port for the Monitor. | integer
|
||||||
MONITOR_BRIDGE_NAME | The name to be used in the url path for the bridge | string
|
MONITOR_BRIDGE_NAME | The name to be used in the url path for the bridge | string
|
||||||
MONITOR_CACHE_EVENTS | If set to true, monitor will cache obtained events for other workers runs | `true` / `false`
|
MONITOR_CACHE_EVENTS | If set to true, monitor will cache obtained events for other workers runs
|
||||||
MONITOR_HOME_TO_FOREIGN_ALLOWANCE_LIST | File with a list of addresses, separated by newlines. If set, determines the privileged set of accounts whose requests should be automatically processed by the CollectedSignatures watcher. | string
|
|
||||||
MONITOR_HOME_TO_FOREIGN_BLOCK_LIST | File with a list of addresses, separated by newlines. If set, determines the set of accounts whose requests should be marked as unclaimed. Has a lower priority than the `MONITOR_HOME_TO_FOREIGN_ALLOWANCE_LIST`. | string
|
|
||||||
MONITOR_HOME_TO_FOREIGN_CHECK_SENDER | If set to `true`, instructs the oracle to do an extra check for transaction origin in the block/allowance list. `false` by default. | `true` / `false`
|
|
||||||
MONITOR_HOME_VALIDATORS_BALANCE_ENABLE | If set, defines the list of home validator addresses for which balance should be checked. | `string`
|
|
||||||
MONITOR_FOREIGN_VALIDATORS_BALANCE_ENABLE | If set, defines the list of foreign validator addresses for which balance should be checked. | `string`
|
|
||||||
|
|||||||
@ -1,33 +1,15 @@
|
|||||||
FROM node:12 as contracts
|
FROM node:10
|
||||||
|
|
||||||
WORKDIR /mono
|
|
||||||
|
|
||||||
COPY contracts/package.json contracts/package-lock.json ./contracts/
|
|
||||||
|
|
||||||
WORKDIR /mono/contracts
|
|
||||||
RUN npm install --only=prod
|
|
||||||
|
|
||||||
COPY ./contracts/truffle-config.js ./
|
|
||||||
COPY ./contracts/contracts ./contracts
|
|
||||||
RUN npm run compile
|
|
||||||
|
|
||||||
FROM node:12
|
|
||||||
|
|
||||||
WORKDIR /mono
|
WORKDIR /mono
|
||||||
COPY package.json .
|
COPY package.json .
|
||||||
COPY --from=contracts /mono/contracts/build ./contracts/build
|
|
||||||
COPY commons/package.json ./commons/
|
|
||||||
COPY oracle-e2e/package.json ./oracle-e2e/
|
COPY oracle-e2e/package.json ./oracle-e2e/
|
||||||
COPY monitor-e2e/package.json ./monitor-e2e/
|
COPY monitor-e2e/package.json ./monitor-e2e/
|
||||||
COPY oracle/src/utils/constants.js ./oracle/src/utils/constants.js
|
COPY contracts/package.json ./contracts/
|
||||||
|
|
||||||
COPY yarn.lock .
|
COPY yarn.lock .
|
||||||
RUN NOYARNPOSTINSTALL=1 yarn install --frozen-lockfile --production
|
RUN yarn install --frozen-lockfile
|
||||||
|
COPY ./contracts ./contracts
|
||||||
COPY ./contracts/deploy ./contracts/deploy
|
|
||||||
RUN yarn install:deploy
|
RUN yarn install:deploy
|
||||||
|
RUN yarn compile:contracts
|
||||||
|
|
||||||
COPY commons/ ./commons/
|
COPY . .
|
||||||
COPY oracle-e2e/ ./oracle-e2e/
|
|
||||||
COPY monitor-e2e/ ./monitor-e2e/
|
|
||||||
COPY e2e-commons/ ./e2e-commons/
|
|
||||||
|
|||||||
14
README.md
14
README.md
@ -1,4 +1,4 @@
|
|||||||

|
[](https://circleci.com/gh/poanetwork/tokenbridge)
|
||||||
[](https://gitter.im/poanetwork/poa-bridge?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
[](https://gitter.im/poanetwork/poa-bridge?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||||
[](https://www.gnu.org/licenses/lgpl-3.0)
|
[](https://www.gnu.org/licenses/lgpl-3.0)
|
||||||
|
|
||||||
@ -19,11 +19,13 @@ Sub-repositories maintained within this monorepo are listed below.
|
|||||||
|
|
||||||
| Sub-repository | Description |
|
| Sub-repository | Description |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| [Oracle](oracle/README.md) | Responsible for listening to bridge related events and authorizing asset transfers. |
|
| [Oracle](oracle/README.md) | Oracle responsible for listening to bridge related events and authorizing asset transfers. |
|
||||||
|
| [UI](ui/README.md) | DApp interface to transfer tokens and coins between chains. |
|
||||||
| [Monitor](monitor/README.md) | Tool for checking balances and unprocessed events in bridged networks. |
|
| [Monitor](monitor/README.md) | Tool for checking balances and unprocessed events in bridged networks. |
|
||||||
| [Deployment](deployment/README.md) | Ansible playbooks for deploying cross-chain bridges. |
|
| [Deployment](deployment/README.md) | Ansible playbooks for deploying cross-chain bridges. |
|
||||||
| [Oracle-E2E](oracle-e2e/README.md) | End to end tests for the Oracle |
|
| [Oracle-E2E](oracle-e2e/README.md) | End to end tests for the Oracle |
|
||||||
| [Monitor-E2E](monitor-e2e/README.md) | End to end tests for the Monitor |
|
| [Monitor-E2E](monitor-e2e/README.md) | End to end tests for the Monitor |
|
||||||
|
| [UI-E2E](ui-e2e/README.md) | End to end tests for the UI |
|
||||||
| [Deployment-E2E](deployment-e2e/README.md) | End to end tests for the Deployment |
|
| [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 |
|
| [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 |
|
| [E2E-Commons](e2e-commons/README.md) | Common utilities and configuration used in end to end tests |
|
||||||
@ -54,6 +56,8 @@ Additionally there are [Smart Contracts](https://github.com/poanetwork/tokenbrid
|
|||||||
|
|
||||||
The POA TokenBridge provides four 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] `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.
|
- [x] `Arbitrary-Message`: Transfer arbitrary data between two networks as so the data could be interpreted as an arbitrary contract method invocation.
|
||||||
|
|
||||||
@ -64,7 +68,7 @@ Clone the repository:
|
|||||||
git clone https://github.com/poanetwork/tokenbridge
|
git clone https://github.com/poanetwork/tokenbridge
|
||||||
```
|
```
|
||||||
|
|
||||||
If there is no need to build docker images for the TokenBridge components (oracle, monitor), initialize submodules, install dependencies, compile the Smart Contracts:
|
If there is no need to build docker images for the TokenBridge components (oracle, monitor, UI), initialize submodules, install dependencies, compile the Smart Contracts:
|
||||||
```
|
```
|
||||||
yarn initialize
|
yarn initialize
|
||||||
```
|
```
|
||||||
@ -87,7 +91,7 @@ Running tests for all projects:
|
|||||||
yarn test
|
yarn test
|
||||||
```
|
```
|
||||||
|
|
||||||
Additionally there are end-to-end tests for [Oracle](oracle-e2e/README.md) and [Monitor](monitor-e2e/README.md).
|
Additionally there are end-to-end tests for [Oracle](oracle-e2e/README.md) and [UI](ui-e2e/README.md).
|
||||||
|
|
||||||
For details on building, running and developing please refer to respective READMEs in sub-repositories.
|
For details on building, running and developing please refer to respective READMEs in sub-repositories.
|
||||||
|
|
||||||
@ -106,4 +110,4 @@ This project is licensed under the GNU Lesser General Public License v3.0. See t
|
|||||||
|
|
||||||
## References
|
## References
|
||||||
|
|
||||||
* [TokenBridge Documentation](https://docs.tokenbridge.net/)
|
* [TokenBridge Documentation](http://www.tokenbridge.net/)
|
||||||
|
|||||||
@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": [
|
|
||||||
"plugin:node/recommended",
|
|
||||||
"airbnb-base",
|
|
||||||
"../.eslintrc"
|
|
||||||
],
|
|
||||||
"plugins": ["node", "jest"],
|
|
||||||
"env": {
|
|
||||||
"jest/globals": true
|
|
||||||
},
|
|
||||||
"globals": {
|
|
||||||
"page": true,
|
|
||||||
"browser": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,24 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "alm-e2e",
|
|
||||||
"version": "0.0.1",
|
|
||||||
"private": true,
|
|
||||||
"scripts": {
|
|
||||||
"test": "jest --detectOpenHandles",
|
|
||||||
"lint": "eslint . --ignore-path ../.eslintignore",
|
|
||||||
"lint:fix": "eslint . --fix"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"jest": "24.7.1",
|
|
||||||
"jest-puppeteer": "^4.4.0",
|
|
||||||
"puppeteer": "^5.2.1"
|
|
||||||
},
|
|
||||||
"jest": {
|
|
||||||
"preset": "jest-puppeteer"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"eslint-plugin-jest": "^23.18.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 12.22"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
cd $(dirname $0)
|
|
||||||
|
|
||||||
../e2e-commons/up.sh deploy generate-amb-tx blocks alm alm-e2e
|
|
||||||
|
|
||||||
# run oracle amb e2e tests to generate transactions for alm
|
|
||||||
docker-compose -f ../e2e-commons/docker-compose.yml run e2e yarn workspace oracle-e2e run alm
|
|
||||||
|
|
||||||
yarn test
|
|
||||||
rc=$?
|
|
||||||
|
|
||||||
../e2e-commons/down.sh
|
|
||||||
exit $rc
|
|
||||||
@ -1,50 +0,0 @@
|
|||||||
const puppeteer = require('puppeteer')
|
|
||||||
const { waitUntil } = require('./utils/utils')
|
|
||||||
|
|
||||||
jest.setTimeout(60000)
|
|
||||||
|
|
||||||
const statusText = 'Success'
|
|
||||||
const statusSelector = 'label[data-id="status"]'
|
|
||||||
|
|
||||||
const homeToForeignTxURL = 'http://localhost:3004/77/0x295efbe6ae98937ef35d939376c9bd752b4dc6f6899a9d5ddd6a57cea3d76c89'
|
|
||||||
const foreignToHomeTxURL = 'http://localhost:3004/42/0x7262f7dbe6c30599edded2137fbbe93c271b37f5c54dd27f713f0cf510e3b4dd'
|
|
||||||
|
|
||||||
describe('ALM', () => {
|
|
||||||
let browser
|
|
||||||
let page
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
browser = await puppeteer.launch()
|
|
||||||
page = await browser.newPage()
|
|
||||||
})
|
|
||||||
|
|
||||||
afterAll(async () => {
|
|
||||||
await browser.close()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should be titled "AMB Live Monitoring"', async () => {
|
|
||||||
await page.goto(foreignToHomeTxURL)
|
|
||||||
|
|
||||||
await expect(page.title()).resolves.toMatch('AMB Live Monitoring')
|
|
||||||
})
|
|
||||||
it('should display information of foreign to home transaction', async () => {
|
|
||||||
await page.goto(foreignToHomeTxURL)
|
|
||||||
|
|
||||||
await page.waitForSelector(statusSelector)
|
|
||||||
await waitUntil(async () => {
|
|
||||||
const element = await page.$(statusSelector)
|
|
||||||
const text = await page.evaluate(element => element.textContent, element)
|
|
||||||
return text === statusText
|
|
||||||
})
|
|
||||||
})
|
|
||||||
it('should display information of home to foreign transaction', async () => {
|
|
||||||
await page.goto(homeToForeignTxURL)
|
|
||||||
|
|
||||||
await page.waitForSelector(statusSelector)
|
|
||||||
await waitUntil(async () => {
|
|
||||||
const element = await page.$(statusSelector)
|
|
||||||
const text = await page.evaluate(element => element.textContent, element)
|
|
||||||
return text === statusText
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
const waitUntil = async (predicate, step = 100, timeout = 20000) => {
|
|
||||||
const stopTime = Date.now() + timeout
|
|
||||||
while (Date.now() <= stopTime) {
|
|
||||||
const result = await predicate()
|
|
||||||
if (result) {
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
await new Promise(resolve => setTimeout(resolve, step)) // sleep
|
|
||||||
}
|
|
||||||
throw new Error(`waitUntil timed out after ${timeout} ms`)
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
waitUntil
|
|
||||||
}
|
|
||||||
@ -3,13 +3,3 @@ COMMON_FOREIGN_BRIDGE_ADDRESS=0xFe446bEF1DbF7AFE24E81e05BC8B271C1BA9a560
|
|||||||
|
|
||||||
COMMON_HOME_RPC_URL=https://sokol.poa.network
|
COMMON_HOME_RPC_URL=https://sokol.poa.network
|
||||||
COMMON_FOREIGN_RPC_URL=https://kovan.infura.io/v3/
|
COMMON_FOREIGN_RPC_URL=https://kovan.infura.io/v3/
|
||||||
|
|
||||||
ALM_HOME_NETWORK_NAME=Sokol Testnet
|
|
||||||
ALM_FOREIGN_NETWORK_NAME=Kovan Testnet
|
|
||||||
|
|
||||||
ALM_HOME_EXPLORER_TX_TEMPLATE=https://blockscout.com/poa/sokol/tx/%s
|
|
||||||
ALM_FOREIGN_EXPLORER_TX_TEMPLATE=https://blockscout.com/eth/kovan/tx/%s
|
|
||||||
|
|
||||||
ALM_HOME_EXPLORER_API=https://blockscout.com/poa/sokol/api
|
|
||||||
ALM_FOREIGN_EXPLORER_API=https://kovan.etherscan.io/api?apikey=YourApiKeyToken
|
|
||||||
PORT=8080
|
|
||||||
|
|||||||
2
alm/.gitignore
vendored
2
alm/.gitignore
vendored
@ -1,7 +1,5 @@
|
|||||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
src/snapshots/*.json
|
|
||||||
|
|
||||||
# dependencies
|
# dependencies
|
||||||
/node_modules
|
/node_modules
|
||||||
/.pnp
|
/.pnp
|
||||||
|
|||||||
@ -1,38 +1,23 @@
|
|||||||
FROM node:12 as contracts
|
FROM node:12
|
||||||
|
|
||||||
WORKDIR /mono
|
|
||||||
|
|
||||||
COPY contracts/package.json contracts/package-lock.json ./contracts/
|
|
||||||
|
|
||||||
WORKDIR /mono/contracts
|
|
||||||
RUN npm install --only=prod
|
|
||||||
|
|
||||||
COPY ./contracts/truffle-config.js ./
|
|
||||||
COPY ./contracts/contracts ./contracts
|
|
||||||
RUN npm run compile
|
|
||||||
|
|
||||||
FROM node:12 as alm-builder
|
|
||||||
|
|
||||||
WORKDIR /mono
|
WORKDIR /mono
|
||||||
COPY package.json .
|
COPY package.json .
|
||||||
COPY --from=contracts /mono/contracts/build ./contracts/build
|
COPY contracts/package.json ./contracts/
|
||||||
COPY commons/package.json ./commons/
|
COPY commons/package.json ./commons/
|
||||||
COPY alm/package.json ./alm/
|
COPY alm/package.json ./alm/
|
||||||
COPY yarn.lock .
|
COPY yarn.lock .
|
||||||
RUN NOYARNPOSTINSTALL=1 yarn install --frozen-lockfile
|
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 ./commons ./commons
|
||||||
COPY ./alm ./alm
|
|
||||||
|
|
||||||
|
COPY ./alm ./alm
|
||||||
ARG DOT_ENV_PATH=./alm/.env
|
ARG DOT_ENV_PATH=./alm/.env
|
||||||
COPY ${DOT_ENV_PATH} ./alm/.env
|
COPY ${DOT_ENV_PATH} ./alm/.env
|
||||||
|
|
||||||
WORKDIR /mono/alm
|
WORKDIR /mono/alm
|
||||||
RUN yarn run build
|
CMD echo "To start the application run:" \
|
||||||
|
"yarn start"
|
||||||
|
|
||||||
FROM node:12 as alm-production
|
|
||||||
RUN yarn global add serve
|
|
||||||
WORKDIR /app
|
|
||||||
COPY --from=alm-builder /mono/alm/build .
|
|
||||||
CMD serve -p $PORT -s .
|
|
||||||
|
|||||||
@ -5,10 +5,8 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: ..
|
context: ..
|
||||||
dockerfile: alm/Dockerfile
|
dockerfile: alm/Dockerfile
|
||||||
ports:
|
|
||||||
- "${PORT}:${PORT}"
|
|
||||||
env_file: ./.env
|
env_file: ./.env
|
||||||
environment:
|
environment:
|
||||||
- NODE_ENV=production
|
- NODE_ENV=production
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
entrypoint: serve -p ${PORT} -s .
|
entrypoint: yarn start
|
||||||
|
|||||||
@ -3,44 +3,26 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ethersproject/bignumber": ">=5.0.0-beta.130",
|
|
||||||
"@react-hook/window-size": "^3.0.6",
|
|
||||||
"@testing-library/jest-dom": "^4.2.4",
|
"@testing-library/jest-dom": "^4.2.4",
|
||||||
"@testing-library/react": "^9.3.2",
|
"@testing-library/react": "^9.3.2",
|
||||||
"@testing-library/user-event": "^7.1.2",
|
"@testing-library/user-event": "^7.1.2",
|
||||||
"@types/jest": "^24.0.0",
|
"@types/jest": "^24.0.0",
|
||||||
"@types/node": "^12.0.0",
|
"@types/node": "^12.0.0",
|
||||||
"@types/promise-retry": "^1.1.3",
|
|
||||||
"@types/react": "^16.9.0",
|
"@types/react": "^16.9.0",
|
||||||
"@types/react-dom": "^16.9.0",
|
"@types/react-dom": "^16.9.0",
|
||||||
"@types/react-router-dom": "^5.1.5",
|
|
||||||
"@types/styled-components": "^5.1.0",
|
|
||||||
"@use-it/interval": "^0.1.3",
|
|
||||||
"@web3-react/core": "^6.1.1",
|
|
||||||
"@web3-react/injected-connector": "^6.0.7",
|
|
||||||
"customize-cra": "^1.0.0",
|
"customize-cra": "^1.0.0",
|
||||||
"date-fns": "^2.14.0",
|
|
||||||
"dotenv": "^8.2.0",
|
|
||||||
"fast-memoize": "^2.5.2",
|
|
||||||
"promise-retry": "^2.0.1",
|
|
||||||
"react": "^16.13.1",
|
"react": "^16.13.1",
|
||||||
"react-app-rewired": "^2.1.6",
|
"react-app-rewired": "^2.1.6",
|
||||||
"react-dom": "^16.13.1",
|
"react-dom": "^16.13.1",
|
||||||
"react-router-dom": "^5.2.0",
|
|
||||||
"react-scripts": "3.0.1",
|
"react-scripts": "3.0.1",
|
||||||
"styled-components": "^5.1.1",
|
"typescript": "^3.5.2"
|
||||||
"typescript": "^3.5.2",
|
|
||||||
"web3": "1.2.11",
|
|
||||||
"web3-eth-contract": "1.2.11",
|
|
||||||
"web3-utils": "1.2.11"
|
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "yarn createSnapshots && ./load-env.sh react-app-rewired start",
|
"start": "./load-env.sh react-app-rewired start",
|
||||||
"build": "yarn createSnapshots && ./load-env.sh react-app-rewired build",
|
"build": "./load-env.sh react-app-rewired build",
|
||||||
"test": "react-app-rewired test",
|
"test": "react-app-rewired test",
|
||||||
"eject": "react-app-rewired eject",
|
"eject": "react-app-rewired eject",
|
||||||
"lint": "eslint '*/**/*.{js,ts,tsx}' --ignore-path ../.eslintignore",
|
"lint": "eslint '*/**/*.{js,ts,tsx}' --ignore-path ../.eslintignore"
|
||||||
"createSnapshots": "node scripts/createSnapshots.js"
|
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"extends": "react-app"
|
"extends": "react-app"
|
||||||
@ -58,7 +40,6 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"eslint-plugin-prettier": "^3.1.3",
|
"eslint-plugin-prettier": "^3.1.3"
|
||||||
"node-fetch": "^2.6.1"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1 +0,0 @@
|
|||||||
/* /index.html 200
|
|
||||||
@ -7,9 +7,8 @@
|
|||||||
<meta name="theme-color" content="#000000" />
|
<meta name="theme-color" content="#000000" />
|
||||||
<meta
|
<meta
|
||||||
name="description"
|
name="description"
|
||||||
content="AMB Live Monitoring"
|
content="Web site created using create-react-app"
|
||||||
/>
|
/>
|
||||||
<link rel="stylesheet" href="https://unpkg.com/chota@latest">
|
|
||||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||||
<!--
|
<!--
|
||||||
manifest.json provides metadata used when your web app is installed on a
|
manifest.json provides metadata used when your web app is installed on a
|
||||||
@ -25,8 +24,7 @@
|
|||||||
work correctly both with client-side routing and a non-root public URL.
|
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`.
|
Learn how to configure a non-root public URL by running `npm run build`.
|
||||||
-->
|
-->
|
||||||
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,700" rel="stylesheet">
|
<title>React App</title>
|
||||||
<title>AMB Live Monitoring</title>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
|||||||
BIN
alm/public/logo192.png
Normal file
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
BIN
alm/public/logo512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.4 KiB |
@ -1,11 +1,21 @@
|
|||||||
{
|
{
|
||||||
"short_name": "ALM",
|
"short_name": "React App",
|
||||||
"name": "AMB Live Monitoring",
|
"name": "Create React App Sample",
|
||||||
"icons": [
|
"icons": [
|
||||||
{
|
{
|
||||||
"src": "favicon.ico",
|
"src": "favicon.ico",
|
||||||
"sizes": "64x64 32x32 24x24 16x16",
|
"sizes": "64x64 32x32 24x24 16x16",
|
||||||
"type": "image/x-icon"
|
"type": "image/x-icon"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "logo192.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "192x192"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "logo512.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "512x512"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"start_url": ".",
|
"start_url": ".",
|
||||||
|
|||||||
@ -1,155 +0,0 @@
|
|||||||
const { BRIDGE_VALIDATORS_ABI, HOME_AMB_ABI } = require('commons')
|
|
||||||
|
|
||||||
const path = require('path')
|
|
||||||
require('dotenv').config()
|
|
||||||
const Web3 = require('web3')
|
|
||||||
const fetch = require('node-fetch')
|
|
||||||
const { URL } = require('url')
|
|
||||||
|
|
||||||
const fs = require('fs')
|
|
||||||
|
|
||||||
const {
|
|
||||||
COMMON_HOME_RPC_URL,
|
|
||||||
COMMON_HOME_BRIDGE_ADDRESS,
|
|
||||||
COMMON_FOREIGN_RPC_URL,
|
|
||||||
COMMON_FOREIGN_BRIDGE_ADDRESS,
|
|
||||||
ALM_FOREIGN_EXPLORER_API,
|
|
||||||
ALM_HOME_EXPLORER_API
|
|
||||||
} = process.env
|
|
||||||
|
|
||||||
const generateSnapshot = async (side, url, bridgeAddress) => {
|
|
||||||
const snapshotPath = `../src/snapshots/${side}.json`
|
|
||||||
const snapshotFullPath = path.join(__dirname, snapshotPath)
|
|
||||||
const snapshot = {}
|
|
||||||
|
|
||||||
const web3 = new Web3(new Web3.providers.HttpProvider(url))
|
|
||||||
const api = side === 'home' ? ALM_HOME_EXPLORER_API : ALM_FOREIGN_EXPLORER_API
|
|
||||||
|
|
||||||
const getPastEventsWithFallback = (contract, eventName, options) =>
|
|
||||||
contract.getPastEvents(eventName, options).catch(async e => {
|
|
||||||
if (e.message.includes('exceed maximum block range')) {
|
|
||||||
const abi = contract.options.jsonInterface.find(abi => abi.type === 'event' && abi.name === eventName)
|
|
||||||
|
|
||||||
const url = new URL(api)
|
|
||||||
url.searchParams.append('module', 'logs')
|
|
||||||
url.searchParams.append('action', 'getLogs')
|
|
||||||
url.searchParams.append('address', contract.options.address)
|
|
||||||
url.searchParams.append('fromBlock', options.fromBlock)
|
|
||||||
url.searchParams.append('toBlock', options.toBlock || 'latest')
|
|
||||||
url.searchParams.append('topic0', web3.eth.abi.encodeEventSignature(abi))
|
|
||||||
|
|
||||||
const logs = await fetch(url).then(res => res.json())
|
|
||||||
|
|
||||||
return logs.result.map(log => ({
|
|
||||||
transactionHash: log.transactionHash,
|
|
||||||
blockNumber: parseInt(log.blockNumber.slice(2), 16),
|
|
||||||
returnValues: web3.eth.abi.decodeLog(abi.inputs, log.data, log.topics.slice(1))
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
throw e
|
|
||||||
})
|
|
||||||
|
|
||||||
const currentBlockNumber = await web3.eth.getBlockNumber()
|
|
||||||
snapshot.snapshotBlockNumber = currentBlockNumber
|
|
||||||
|
|
||||||
// Save chainId
|
|
||||||
snapshot.chainId = await web3.eth.getChainId()
|
|
||||||
|
|
||||||
const bridgeContract = new web3.eth.Contract(HOME_AMB_ABI, bridgeAddress)
|
|
||||||
|
|
||||||
// Save RequiredBlockConfirmationChanged events
|
|
||||||
let requiredBlockConfirmationChangedEvents = await getPastEventsWithFallback(
|
|
||||||
bridgeContract,
|
|
||||||
'RequiredBlockConfirmationChanged',
|
|
||||||
{
|
|
||||||
fromBlock: 0,
|
|
||||||
toBlock: currentBlockNumber
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// In case RequiredBlockConfirmationChanged was not emitted during initialization in early versions of AMB
|
|
||||||
// manually generate an event for this. Example Sokol - Kovan bridge
|
|
||||||
if (requiredBlockConfirmationChangedEvents.length === 0) {
|
|
||||||
const deployedAtBlock = await bridgeContract.methods.deployedAtBlock().call()
|
|
||||||
const blockConfirmations = await bridgeContract.methods.requiredBlockConfirmations().call()
|
|
||||||
|
|
||||||
requiredBlockConfirmationChangedEvents.push({
|
|
||||||
blockNumber: parseInt(deployedAtBlock),
|
|
||||||
returnValues: {
|
|
||||||
requiredBlockConfirmations: blockConfirmations
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
snapshot.RequiredBlockConfirmationChanged = requiredBlockConfirmationChangedEvents.map(e => ({
|
|
||||||
blockNumber: e.blockNumber,
|
|
||||||
returnValues: {
|
|
||||||
requiredBlockConfirmations: e.returnValues.requiredBlockConfirmations
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
|
|
||||||
const validatorAddress = await bridgeContract.methods.validatorContract().call()
|
|
||||||
const validatorContract = new web3.eth.Contract(BRIDGE_VALIDATORS_ABI, validatorAddress)
|
|
||||||
|
|
||||||
// Save RequiredSignaturesChanged events
|
|
||||||
const RequiredSignaturesChangedEvents = await getPastEventsWithFallback(
|
|
||||||
validatorContract,
|
|
||||||
'RequiredSignaturesChanged',
|
|
||||||
{
|
|
||||||
fromBlock: 0,
|
|
||||||
toBlock: currentBlockNumber
|
|
||||||
}
|
|
||||||
)
|
|
||||||
snapshot.RequiredSignaturesChanged = RequiredSignaturesChangedEvents.map(e => ({
|
|
||||||
blockNumber: e.blockNumber,
|
|
||||||
returnValues: {
|
|
||||||
requiredSignatures: e.returnValues.requiredSignatures
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
|
|
||||||
// Save ValidatorAdded events
|
|
||||||
const validatorAddedEvents = await getPastEventsWithFallback(validatorContract, 'ValidatorAdded', {
|
|
||||||
fromBlock: 0,
|
|
||||||
toBlock: currentBlockNumber
|
|
||||||
})
|
|
||||||
|
|
||||||
snapshot.ValidatorAdded = validatorAddedEvents.map(e => ({
|
|
||||||
blockNumber: e.blockNumber,
|
|
||||||
returnValues: {
|
|
||||||
validator: e.returnValues.validator
|
|
||||||
},
|
|
||||||
event: 'ValidatorAdded'
|
|
||||||
}))
|
|
||||||
|
|
||||||
// Save ValidatorRemoved events
|
|
||||||
const validatorRemovedEvents = await getPastEventsWithFallback(validatorContract, 'ValidatorRemoved', {
|
|
||||||
fromBlock: 0,
|
|
||||||
toBlock: currentBlockNumber
|
|
||||||
})
|
|
||||||
|
|
||||||
snapshot.ValidatorRemoved = validatorRemovedEvents.map(e => ({
|
|
||||||
blockNumber: e.blockNumber,
|
|
||||||
returnValues: {
|
|
||||||
validator: e.returnValues.validator
|
|
||||||
},
|
|
||||||
event: 'ValidatorRemoved'
|
|
||||||
}))
|
|
||||||
|
|
||||||
// Write snapshot
|
|
||||||
fs.writeFileSync(snapshotFullPath, JSON.stringify(snapshot, null, 2))
|
|
||||||
}
|
|
||||||
|
|
||||||
const main = async () => {
|
|
||||||
await Promise.all([
|
|
||||||
generateSnapshot('home', COMMON_HOME_RPC_URL, COMMON_HOME_BRIDGE_ADDRESS),
|
|
||||||
generateSnapshot('foreign', COMMON_FOREIGN_RPC_URL, COMMON_FOREIGN_BRIDGE_ADDRESS)
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
main()
|
|
||||||
.then(() => process.exit(0))
|
|
||||||
.catch(error => {
|
|
||||||
console.log('Error while creating snapshots')
|
|
||||||
console.error(error)
|
|
||||||
process.exit(0)
|
|
||||||
})
|
|
||||||
14
alm/src/App.css
Normal file
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
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()
|
||||||
|
})
|
||||||
@ -1,19 +1,13 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { BrowserRouter } from 'react-router-dom'
|
import './App.css'
|
||||||
import { Web3ReactProvider } from '@web3-react/core'
|
|
||||||
import Web3 from 'web3'
|
|
||||||
import { MainPage } from './components/MainPage'
|
|
||||||
import { StateProvider } from './state/StateProvider'
|
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
<BrowserRouter>
|
<div className="App">
|
||||||
<Web3ReactProvider getLibrary={provider => new Web3(provider)}>
|
<header className="App-header">
|
||||||
<StateProvider>
|
<p>AMB Live Monitoring</p>
|
||||||
<MainPage />
|
</header>
|
||||||
</StateProvider>
|
</div>
|
||||||
</Web3ReactProvider>
|
|
||||||
</BrowserRouter>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,319 +0,0 @@
|
|||||||
import { AbiItem } from 'web3-utils'
|
|
||||||
|
|
||||||
const abi: AbiItem[] = [
|
|
||||||
{
|
|
||||||
constant: true,
|
|
||||||
inputs: [],
|
|
||||||
name: 'validatorCount',
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: '',
|
|
||||||
type: 'uint256'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'view',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: true,
|
|
||||||
inputs: [],
|
|
||||||
name: 'getBridgeValidatorsInterfacesVersion',
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: 'major',
|
|
||||||
type: 'uint64'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'minor',
|
|
||||||
type: 'uint64'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'patch',
|
|
||||||
type: 'uint64'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'pure',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: true,
|
|
||||||
inputs: [],
|
|
||||||
name: 'isInitialized',
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: '',
|
|
||||||
type: 'bool'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'view',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: true,
|
|
||||||
inputs: [],
|
|
||||||
name: 'validatorList',
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: '',
|
|
||||||
type: 'address[]'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'view',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: false,
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
name: '_requiredSignatures',
|
|
||||||
type: 'uint256'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
name: 'setRequiredSignatures',
|
|
||||||
outputs: [],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'nonpayable',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: true,
|
|
||||||
inputs: [],
|
|
||||||
name: 'requiredSignatures',
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: '',
|
|
||||||
type: 'uint256'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'view',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: true,
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
name: '_address',
|
|
||||||
type: 'address'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
name: 'getNextValidator',
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: '',
|
|
||||||
type: 'address'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'view',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: true,
|
|
||||||
inputs: [],
|
|
||||||
name: 'owner',
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: '',
|
|
||||||
type: 'address'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'view',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: true,
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
name: '_validator',
|
|
||||||
type: 'address'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
name: 'isValidatorDuty',
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: '',
|
|
||||||
type: 'bool'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'view',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: true,
|
|
||||||
inputs: [],
|
|
||||||
name: 'deployedAtBlock',
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: '',
|
|
||||||
type: 'uint256'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'view',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: true,
|
|
||||||
inputs: [],
|
|
||||||
name: 'F_ADDR',
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: '',
|
|
||||||
type: 'address'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'view',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: false,
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
name: 'newOwner',
|
|
||||||
type: 'address'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
name: 'transferOwnership',
|
|
||||||
outputs: [],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'nonpayable',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: true,
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
name: '_validator',
|
|
||||||
type: 'address'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
name: 'isValidator',
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: '',
|
|
||||||
type: 'bool'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'view',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
anonymous: false,
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
indexed: true,
|
|
||||||
name: 'validator',
|
|
||||||
type: 'address'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
name: 'ValidatorAdded',
|
|
||||||
type: 'event'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
anonymous: false,
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
indexed: true,
|
|
||||||
name: 'validator',
|
|
||||||
type: 'address'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
name: 'ValidatorRemoved',
|
|
||||||
type: 'event'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
anonymous: false,
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
indexed: false,
|
|
||||||
name: 'requiredSignatures',
|
|
||||||
type: 'uint256'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
name: 'RequiredSignaturesChanged',
|
|
||||||
type: 'event'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
anonymous: false,
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
indexed: false,
|
|
||||||
name: 'previousOwner',
|
|
||||||
type: 'address'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
indexed: false,
|
|
||||||
name: 'newOwner',
|
|
||||||
type: 'address'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
name: 'OwnershipTransferred',
|
|
||||||
type: 'event'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: false,
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
name: '_requiredSignatures',
|
|
||||||
type: 'uint256'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '_initialValidators',
|
|
||||||
type: 'address[]'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '_owner',
|
|
||||||
type: 'address'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
name: 'initialize',
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: '',
|
|
||||||
type: 'bool'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'nonpayable',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: false,
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
name: '_validator',
|
|
||||||
type: 'address'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
name: 'addValidator',
|
|
||||||
outputs: [],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'nonpayable',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: false,
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
name: '_validator',
|
|
||||||
type: 'address'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
name: 'removeValidator',
|
|
||||||
outputs: [],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'nonpayable',
|
|
||||||
type: 'function'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
export default abi
|
|
||||||
@ -1,607 +0,0 @@
|
|||||||
import { AbiItem } from 'web3-utils'
|
|
||||||
|
|
||||||
const abi: AbiItem[] = [
|
|
||||||
{
|
|
||||||
constant: true,
|
|
||||||
inputs: [],
|
|
||||||
name: 'transactionHash',
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: '',
|
|
||||||
type: 'bytes32'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'view',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: true,
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
name: '_txHash',
|
|
||||||
type: 'bytes32'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
name: 'relayedMessages',
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: '',
|
|
||||||
type: 'bool'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'view',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: false,
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
name: '_sourceChainId',
|
|
||||||
type: 'uint256'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '_destinationChainId',
|
|
||||||
type: 'uint256'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '_validatorContract',
|
|
||||||
type: 'address'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '_maxGasPerTx',
|
|
||||||
type: 'uint256'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '_gasPrice',
|
|
||||||
type: 'uint256'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '_requiredBlockConfirmations',
|
|
||||||
type: 'uint256'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '_owner',
|
|
||||||
type: 'address'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
name: 'initialize',
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: '',
|
|
||||||
type: 'bool'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'nonpayable',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: true,
|
|
||||||
inputs: [],
|
|
||||||
name: 'isInitialized',
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: '',
|
|
||||||
type: 'bool'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'view',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: true,
|
|
||||||
inputs: [],
|
|
||||||
name: 'requiredBlockConfirmations',
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: '',
|
|
||||||
type: 'uint256'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'view',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: false,
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
name: '_data',
|
|
||||||
type: 'bytes'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '_signatures',
|
|
||||||
type: 'bytes'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
name: 'executeSignatures',
|
|
||||||
outputs: [],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'nonpayable',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: false,
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
name: '_data',
|
|
||||||
type: 'bytes'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '_signatures',
|
|
||||||
type: 'bytes'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
name: 'safeExecuteSignaturesWithAutoGasLimit',
|
|
||||||
outputs: [],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'nonpayable',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: true,
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
name: '_data',
|
|
||||||
type: 'bytes'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
name: 'getMinimumGasUsage',
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: 'gas',
|
|
||||||
type: 'uint256'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'pure',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: true,
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
name: '_messageId',
|
|
||||||
type: 'bytes32'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
name: 'failedMessageReceiver',
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: '',
|
|
||||||
type: 'address'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'view',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: true,
|
|
||||||
inputs: [],
|
|
||||||
name: 'getBridgeMode',
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: '_data',
|
|
||||||
type: 'bytes4'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'pure',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: false,
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
name: '_sourceChainId',
|
|
||||||
type: 'uint256'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '_destinationChainId',
|
|
||||||
type: 'uint256'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
name: 'setChainIds',
|
|
||||||
outputs: [],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'nonpayable',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: true,
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
name: '_messageId',
|
|
||||||
type: 'bytes32'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
name: 'failedMessageSender',
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: '',
|
|
||||||
type: 'address'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'view',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: true,
|
|
||||||
inputs: [],
|
|
||||||
name: 'messageId',
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: '',
|
|
||||||
type: 'bytes32'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'view',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: false,
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
name: '_token',
|
|
||||||
type: 'address'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '_to',
|
|
||||||
type: 'address'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
name: 'claimTokens',
|
|
||||||
outputs: [],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'nonpayable',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: false,
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
name: '_maxGasPerTx',
|
|
||||||
type: 'uint256'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
name: 'setMaxGasPerTx',
|
|
||||||
outputs: [],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'nonpayable',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: true,
|
|
||||||
inputs: [],
|
|
||||||
name: 'requiredSignatures',
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: '',
|
|
||||||
type: 'uint256'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'view',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: true,
|
|
||||||
inputs: [],
|
|
||||||
name: 'owner',
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: '',
|
|
||||||
type: 'address'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'view',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: true,
|
|
||||||
inputs: [],
|
|
||||||
name: 'validatorContract',
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: '',
|
|
||||||
type: 'address'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'view',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: true,
|
|
||||||
inputs: [],
|
|
||||||
name: 'deployedAtBlock',
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: '',
|
|
||||||
type: 'uint256'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'view',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: true,
|
|
||||||
inputs: [],
|
|
||||||
name: 'getBridgeInterfacesVersion',
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: 'major',
|
|
||||||
type: 'uint64'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'minor',
|
|
||||||
type: 'uint64'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'patch',
|
|
||||||
type: 'uint64'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'pure',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: true,
|
|
||||||
inputs: [],
|
|
||||||
name: 'messageSourceChainId',
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: '',
|
|
||||||
type: 'uint256'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'view',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: false,
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
name: '_blockConfirmations',
|
|
||||||
type: 'uint256'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
name: 'setRequiredBlockConfirmations',
|
|
||||||
outputs: [],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'nonpayable',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: false,
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
name: '_gasPrice',
|
|
||||||
type: 'uint256'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
name: 'setGasPrice',
|
|
||||||
outputs: [],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'nonpayable',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: true,
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
name: '_messageId',
|
|
||||||
type: 'bytes32'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
name: 'messageCallStatus',
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: '',
|
|
||||||
type: 'bool'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'view',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: true,
|
|
||||||
inputs: [],
|
|
||||||
name: 'messageSender',
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: '',
|
|
||||||
type: 'address'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'view',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: false,
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
name: '_contract',
|
|
||||||
type: 'address'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '_data',
|
|
||||||
type: 'bytes'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '_gas',
|
|
||||||
type: 'uint256'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
name: 'requireToPassMessage',
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: '',
|
|
||||||
type: 'bytes32'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'nonpayable',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: true,
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
name: '_messageId',
|
|
||||||
type: 'bytes32'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
name: 'failedMessageDataHash',
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: '',
|
|
||||||
type: 'bytes32'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'view',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: true,
|
|
||||||
inputs: [],
|
|
||||||
name: 'maxGasPerTx',
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: '',
|
|
||||||
type: 'uint256'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'view',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: false,
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
name: 'newOwner',
|
|
||||||
type: 'address'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
name: 'transferOwnership',
|
|
||||||
outputs: [],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'nonpayable',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: true,
|
|
||||||
inputs: [],
|
|
||||||
name: 'gasPrice',
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: '',
|
|
||||||
type: 'uint256'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'view',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
anonymous: false,
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
indexed: true,
|
|
||||||
name: 'messageId',
|
|
||||||
type: 'bytes32'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
indexed: false,
|
|
||||||
name: 'encodedData',
|
|
||||||
type: 'bytes'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
name: 'UserRequestForAffirmation',
|
|
||||||
type: 'event'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
anonymous: false,
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
indexed: true,
|
|
||||||
name: 'sender',
|
|
||||||
type: 'address'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
indexed: true,
|
|
||||||
name: 'executor',
|
|
||||||
type: 'address'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
indexed: true,
|
|
||||||
name: 'messageId',
|
|
||||||
type: 'bytes32'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
indexed: false,
|
|
||||||
name: 'status',
|
|
||||||
type: 'bool'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
name: 'RelayedMessage',
|
|
||||||
type: 'event'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
anonymous: false,
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
indexed: false,
|
|
||||||
name: 'gasPrice',
|
|
||||||
type: 'uint256'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
name: 'GasPriceChanged',
|
|
||||||
type: 'event'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
anonymous: false,
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
indexed: false,
|
|
||||||
name: 'requiredBlockConfirmations',
|
|
||||||
type: 'uint256'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
name: 'RequiredBlockConfirmationChanged',
|
|
||||||
type: 'event'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
anonymous: false,
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
indexed: false,
|
|
||||||
name: 'previousOwner',
|
|
||||||
type: 'address'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
indexed: false,
|
|
||||||
name: 'newOwner',
|
|
||||||
type: 'address'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
name: 'OwnershipTransferred',
|
|
||||||
type: 'event'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
export default abi
|
|
||||||
@ -1,777 +0,0 @@
|
|||||||
import { AbiItem } from 'web3-utils'
|
|
||||||
|
|
||||||
const abi: AbiItem[] = [
|
|
||||||
{
|
|
||||||
constant: true,
|
|
||||||
inputs: [],
|
|
||||||
name: 'transactionHash',
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: '',
|
|
||||||
type: 'bytes32'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'view',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: true,
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
name: '_message',
|
|
||||||
type: 'bytes32'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
name: 'numMessagesSigned',
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: '',
|
|
||||||
type: 'uint256'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'view',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: true,
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
name: '_hash',
|
|
||||||
type: 'bytes32'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '_index',
|
|
||||||
type: 'uint256'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
name: 'signature',
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: '',
|
|
||||||
type: 'bytes'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'view',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: false,
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
name: '_sourceChainId',
|
|
||||||
type: 'uint256'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '_destinationChainId',
|
|
||||||
type: 'uint256'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '_validatorContract',
|
|
||||||
type: 'address'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '_maxGasPerTx',
|
|
||||||
type: 'uint256'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '_gasPrice',
|
|
||||||
type: 'uint256'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '_requiredBlockConfirmations',
|
|
||||||
type: 'uint256'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '_owner',
|
|
||||||
type: 'address'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
name: 'initialize',
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: '',
|
|
||||||
type: 'bool'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'nonpayable',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: true,
|
|
||||||
inputs: [],
|
|
||||||
name: 'isInitialized',
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: '',
|
|
||||||
type: 'bool'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'view',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: true,
|
|
||||||
inputs: [],
|
|
||||||
name: 'requiredBlockConfirmations',
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: '',
|
|
||||||
type: 'uint256'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'view',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: true,
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
name: '_data',
|
|
||||||
type: 'bytes'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
name: 'getMinimumGasUsage',
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: 'gas',
|
|
||||||
type: 'uint256'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'pure',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: true,
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
name: '_messageId',
|
|
||||||
type: 'bytes32'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
name: 'failedMessageReceiver',
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: '',
|
|
||||||
type: 'address'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'view',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: true,
|
|
||||||
inputs: [],
|
|
||||||
name: 'getBridgeMode',
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: '_data',
|
|
||||||
type: 'bytes4'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'pure',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: false,
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
name: '_sourceChainId',
|
|
||||||
type: 'uint256'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '_destinationChainId',
|
|
||||||
type: 'uint256'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
name: 'setChainIds',
|
|
||||||
outputs: [],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'nonpayable',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: true,
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
name: '_hash',
|
|
||||||
type: 'bytes32'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
name: 'message',
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: '',
|
|
||||||
type: 'bytes'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'view',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: true,
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
name: '_messageId',
|
|
||||||
type: 'bytes32'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
name: 'failedMessageSender',
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: '',
|
|
||||||
type: 'address'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'view',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: false,
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
name: 'signature',
|
|
||||||
type: 'bytes'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'message',
|
|
||||||
type: 'bytes'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
name: 'submitSignature',
|
|
||||||
outputs: [],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'nonpayable',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: true,
|
|
||||||
inputs: [],
|
|
||||||
name: 'messageId',
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: '',
|
|
||||||
type: 'bytes32'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'view',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: false,
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
name: '_token',
|
|
||||||
type: 'address'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '_to',
|
|
||||||
type: 'address'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
name: 'claimTokens',
|
|
||||||
outputs: [],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'nonpayable',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: true,
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
name: '_hash',
|
|
||||||
type: 'bytes32'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
name: 'numAffirmationsSigned',
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: '',
|
|
||||||
type: 'uint256'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'view',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: true,
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
name: '_hash',
|
|
||||||
type: 'bytes32'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
name: 'affirmationsSigned',
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: '',
|
|
||||||
type: 'bool'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'view',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: false,
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
name: '_maxGasPerTx',
|
|
||||||
type: 'uint256'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
name: 'setMaxGasPerTx',
|
|
||||||
outputs: [],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'nonpayable',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: true,
|
|
||||||
inputs: [],
|
|
||||||
name: 'requiredSignatures',
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: '',
|
|
||||||
type: 'uint256'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'view',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: true,
|
|
||||||
inputs: [],
|
|
||||||
name: 'owner',
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: '',
|
|
||||||
type: 'address'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'view',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: true,
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
name: '_message',
|
|
||||||
type: 'bytes32'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
name: 'messagesSigned',
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: '',
|
|
||||||
type: 'bool'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'view',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: true,
|
|
||||||
inputs: [],
|
|
||||||
name: 'validatorContract',
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: '',
|
|
||||||
type: 'address'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'view',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: true,
|
|
||||||
inputs: [],
|
|
||||||
name: 'deployedAtBlock',
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: '',
|
|
||||||
type: 'uint256'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'view',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: true,
|
|
||||||
inputs: [],
|
|
||||||
name: 'getBridgeInterfacesVersion',
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: 'major',
|
|
||||||
type: 'uint64'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'minor',
|
|
||||||
type: 'uint64'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'patch',
|
|
||||||
type: 'uint64'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'pure',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: true,
|
|
||||||
inputs: [],
|
|
||||||
name: 'messageSourceChainId',
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: '',
|
|
||||||
type: 'uint256'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'view',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: false,
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
name: '_blockConfirmations',
|
|
||||||
type: 'uint256'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
name: 'setRequiredBlockConfirmations',
|
|
||||||
outputs: [],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'nonpayable',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: false,
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
name: '_gasPrice',
|
|
||||||
type: 'uint256'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
name: 'setGasPrice',
|
|
||||||
outputs: [],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'nonpayable',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: true,
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
name: '_messageId',
|
|
||||||
type: 'bytes32'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
name: 'messageCallStatus',
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: '',
|
|
||||||
type: 'bool'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'view',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: true,
|
|
||||||
inputs: [],
|
|
||||||
name: 'messageSender',
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: '',
|
|
||||||
type: 'address'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'view',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: false,
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
name: '_contract',
|
|
||||||
type: 'address'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '_data',
|
|
||||||
type: 'bytes'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '_gas',
|
|
||||||
type: 'uint256'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
name: 'requireToPassMessage',
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: '',
|
|
||||||
type: 'bytes32'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'nonpayable',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: true,
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
name: '_messageId',
|
|
||||||
type: 'bytes32'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
name: 'failedMessageDataHash',
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: '',
|
|
||||||
type: 'bytes32'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'view',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: true,
|
|
||||||
inputs: [],
|
|
||||||
name: 'maxGasPerTx',
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: '',
|
|
||||||
type: 'uint256'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'view',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: false,
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
name: 'message',
|
|
||||||
type: 'bytes'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
name: 'executeAffirmation',
|
|
||||||
outputs: [],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'nonpayable',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: false,
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
name: 'newOwner',
|
|
||||||
type: 'address'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
name: 'transferOwnership',
|
|
||||||
outputs: [],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'nonpayable',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: true,
|
|
||||||
inputs: [],
|
|
||||||
name: 'gasPrice',
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: '',
|
|
||||||
type: 'uint256'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'view',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constant: true,
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
name: '_number',
|
|
||||||
type: 'uint256'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
name: 'isAlreadyProcessed',
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: '',
|
|
||||||
type: 'bool'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
payable: false,
|
|
||||||
stateMutability: 'pure',
|
|
||||||
type: 'function'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
anonymous: false,
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
indexed: true,
|
|
||||||
name: 'messageId',
|
|
||||||
type: 'bytes32'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
indexed: false,
|
|
||||||
name: 'encodedData',
|
|
||||||
type: 'bytes'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
name: 'UserRequestForSignature',
|
|
||||||
type: 'event'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
anonymous: false,
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
indexed: true,
|
|
||||||
name: 'sender',
|
|
||||||
type: 'address'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
indexed: true,
|
|
||||||
name: 'executor',
|
|
||||||
type: 'address'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
indexed: true,
|
|
||||||
name: 'messageId',
|
|
||||||
type: 'bytes32'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
indexed: false,
|
|
||||||
name: 'status',
|
|
||||||
type: 'bool'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
name: 'AffirmationCompleted',
|
|
||||||
type: 'event'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
anonymous: false,
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
indexed: true,
|
|
||||||
name: 'signer',
|
|
||||||
type: 'address'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
indexed: false,
|
|
||||||
name: 'messageHash',
|
|
||||||
type: 'bytes32'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
name: 'SignedForUserRequest',
|
|
||||||
type: 'event'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
anonymous: false,
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
indexed: true,
|
|
||||||
name: 'signer',
|
|
||||||
type: 'address'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
indexed: false,
|
|
||||||
name: 'messageHash',
|
|
||||||
type: 'bytes32'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
name: 'SignedForAffirmation',
|
|
||||||
type: 'event'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
anonymous: false,
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
indexed: false,
|
|
||||||
name: 'authorityResponsibleForRelay',
|
|
||||||
type: 'address'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
indexed: false,
|
|
||||||
name: 'messageHash',
|
|
||||||
type: 'bytes32'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
indexed: false,
|
|
||||||
name: 'NumberOfCollectedSignatures',
|
|
||||||
type: 'uint256'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
name: 'CollectedSignatures',
|
|
||||||
type: 'event'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
anonymous: false,
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
indexed: false,
|
|
||||||
name: 'gasPrice',
|
|
||||||
type: 'uint256'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
name: 'GasPriceChanged',
|
|
||||||
type: 'event'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
anonymous: false,
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
indexed: false,
|
|
||||||
name: 'requiredBlockConfirmations',
|
|
||||||
type: 'uint256'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
name: 'RequiredBlockConfirmationChanged',
|
|
||||||
type: 'event'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
anonymous: false,
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
indexed: false,
|
|
||||||
name: 'previousOwner',
|
|
||||||
type: 'address'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
indexed: false,
|
|
||||||
name: 'newOwner',
|
|
||||||
type: 'address'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
name: 'OwnershipTransferred',
|
|
||||||
type: 'event'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
export default abi
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
export { default as HOME_AMB_ABI } from './HomeAMB'
|
|
||||||
export { default as FOREIGN_AMB_ABI } from './ForeignAMB'
|
|
||||||
export { default as BRIDGE_VALIDATORS_ABI } from './BridgeValidators'
|
|
||||||
@ -1,150 +0,0 @@
|
|||||||
import React, { useEffect, useState } from 'react'
|
|
||||||
import { TransactionReceipt } from 'web3-eth'
|
|
||||||
import { useMessageConfirmations } from '../hooks/useMessageConfirmations'
|
|
||||||
import { MessageObject } from '../utils/web3'
|
|
||||||
import styled from 'styled-components'
|
|
||||||
import { CONFIRMATIONS_STATUS, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants'
|
|
||||||
import { CONFIRMATIONS_STATUS_LABEL, CONFIRMATIONS_STATUS_LABEL_HOME } from '../config/descriptions'
|
|
||||||
import { SimpleLoading } from './commons/Loading'
|
|
||||||
import { ValidatorsConfirmations } from './ValidatorsConfirmations'
|
|
||||||
import { getConfirmationsStatusDescription } from '../utils/networks'
|
|
||||||
import { useStateProvider } from '../state/StateProvider'
|
|
||||||
import { ExecutionConfirmation } from './ExecutionConfirmation'
|
|
||||||
import { useValidatorContract } from '../hooks/useValidatorContract'
|
|
||||||
import { useBlockConfirmations } from '../hooks/useBlockConfirmations'
|
|
||||||
import { MultiLine } from './commons/MultiLine'
|
|
||||||
import { ExplorerTxLink } from './commons/ExplorerTxLink'
|
|
||||||
|
|
||||||
const StatusLabel = styled.label`
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 18px;
|
|
||||||
`
|
|
||||||
|
|
||||||
const StatusResultLabel = styled.label`
|
|
||||||
font-size: 18px;
|
|
||||||
padding-left: 10px;
|
|
||||||
`
|
|
||||||
|
|
||||||
const StyledConfirmationContainer = styled.div`
|
|
||||||
background-color: var(--bg-color);
|
|
||||||
padding: 10px;
|
|
||||||
border-radius: 4px;
|
|
||||||
`
|
|
||||||
|
|
||||||
const StatusDescription = styled.div`
|
|
||||||
padding-top: 10px;
|
|
||||||
`
|
|
||||||
|
|
||||||
export interface ConfirmationsContainerParams {
|
|
||||||
message: MessageObject
|
|
||||||
receipt: Maybe<TransactionReceipt>
|
|
||||||
fromHome: boolean
|
|
||||||
homeStartBlock: Maybe<number>
|
|
||||||
foreignStartBlock: Maybe<number>
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ConfirmationsContainer = ({
|
|
||||||
message,
|
|
||||||
receipt,
|
|
||||||
fromHome,
|
|
||||||
homeStartBlock,
|
|
||||||
foreignStartBlock
|
|
||||||
}: ConfirmationsContainerParams) => {
|
|
||||||
const {
|
|
||||||
home: { name: homeName },
|
|
||||||
foreign: { name: foreignName }
|
|
||||||
} = useStateProvider()
|
|
||||||
const src = useValidatorContract(fromHome, receipt ? receipt.blockNumber : 0)
|
|
||||||
const [executionBlockNumber, setExecutionBlockNumber] = useState(0)
|
|
||||||
const dst = useValidatorContract(!fromHome, executionBlockNumber || 'latest')
|
|
||||||
const { blockConfirmations } = useBlockConfirmations({ fromHome, receipt })
|
|
||||||
const {
|
|
||||||
confirmations,
|
|
||||||
status,
|
|
||||||
executionData,
|
|
||||||
signatureCollected,
|
|
||||||
waitingBlocksResolved,
|
|
||||||
setExecutionData,
|
|
||||||
executionEventsFetched,
|
|
||||||
setPendingExecution
|
|
||||||
} = useMessageConfirmations({
|
|
||||||
message,
|
|
||||||
receipt,
|
|
||||||
fromHome,
|
|
||||||
homeStartBlock,
|
|
||||||
foreignStartBlock,
|
|
||||||
requiredSignatures: src.requiredSignatures,
|
|
||||||
validatorList: src.validatorList,
|
|
||||||
targetValidatorList: dst.validatorList,
|
|
||||||
blockConfirmations
|
|
||||||
})
|
|
||||||
|
|
||||||
useEffect(
|
|
||||||
() => {
|
|
||||||
if (executionBlockNumber || executionData.status !== VALIDATOR_CONFIRMATION_STATUS.EXECUTION_SUCCESS) return
|
|
||||||
|
|
||||||
setExecutionBlockNumber(executionData.blockNumber)
|
|
||||||
},
|
|
||||||
[executionData.status, executionBlockNumber, executionData.blockNumber]
|
|
||||||
)
|
|
||||||
|
|
||||||
const statusLabel = fromHome ? CONFIRMATIONS_STATUS_LABEL_HOME : CONFIRMATIONS_STATUS_LABEL
|
|
||||||
|
|
||||||
const parseDescription = () => {
|
|
||||||
let description = getConfirmationsStatusDescription(status, homeName, foreignName, fromHome)
|
|
||||||
let link
|
|
||||||
const descArray = description.split('%link')
|
|
||||||
if (descArray.length > 1) {
|
|
||||||
description = descArray[0]
|
|
||||||
link = (
|
|
||||||
<ExplorerTxLink href={descArray[1]} target="_blank" rel="noopener noreferrer">
|
|
||||||
{descArray[1]}
|
|
||||||
</ExplorerTxLink>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{description}
|
|
||||||
{link}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="row is-center">
|
|
||||||
<StyledConfirmationContainer className="col-9">
|
|
||||||
<div className="row is-center">
|
|
||||||
<StatusLabel>Status:</StatusLabel>
|
|
||||||
<StatusResultLabel data-id="status">
|
|
||||||
{status !== CONFIRMATIONS_STATUS.UNDEFINED ? statusLabel[status] : <SimpleLoading />}
|
|
||||||
</StatusResultLabel>
|
|
||||||
</div>
|
|
||||||
<StatusDescription className="row is-center">
|
|
||||||
<MultiLine className="col-10">
|
|
||||||
{status !== CONFIRMATIONS_STATUS.UNDEFINED ? parseDescription() : ''}
|
|
||||||
</MultiLine>
|
|
||||||
</StatusDescription>
|
|
||||||
<ValidatorsConfirmations
|
|
||||||
confirmations={fromHome ? confirmations.filter(c => dst.validatorList.includes(c.validator)) : confirmations}
|
|
||||||
requiredSignatures={dst.requiredSignatures}
|
|
||||||
validatorList={dst.validatorList}
|
|
||||||
waitingBlocksResolved={waitingBlocksResolved}
|
|
||||||
/>
|
|
||||||
{signatureCollected && (
|
|
||||||
<ExecutionConfirmation
|
|
||||||
message={message}
|
|
||||||
executionData={executionData}
|
|
||||||
isHome={!fromHome}
|
|
||||||
confirmations={confirmations}
|
|
||||||
setExecutionData={setExecutionData}
|
|
||||||
executionEventsFetched={executionEventsFetched}
|
|
||||||
setPendingExecution={setPendingExecution}
|
|
||||||
dstRequiredSignatures={dst.requiredSignatures}
|
|
||||||
dstValidatorList={dst.validatorList}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</StyledConfirmationContainer>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@ -1,172 +0,0 @@
|
|||||||
import React, { useEffect, useState } from 'react'
|
|
||||||
import { formatTimestamp, formatTxHash, getExplorerTxUrl } from '../utils/networks'
|
|
||||||
import { useWindowWidth } from '@react-hook/window-size'
|
|
||||||
import { SEARCHING_TX, VALIDATOR_CONFIRMATION_STATUS, ALM_HOME_TO_FOREIGN_MANUAL_EXECUTION } from '../config/constants'
|
|
||||||
import { SimpleLoading } from './commons/Loading'
|
|
||||||
import styled from 'styled-components'
|
|
||||||
import { ConfirmationParam, ExecutionData } from '../hooks/useMessageConfirmations'
|
|
||||||
import { GreyLabel, RedLabel, SuccessLabel } from './commons/Labels'
|
|
||||||
import { ExplorerTxLink } from './commons/ExplorerTxLink'
|
|
||||||
import { Thead, AgeTd, StatusTd } from './commons/Table'
|
|
||||||
import { ManualExecutionButton } from './ManualExecutionButton'
|
|
||||||
import { useStateProvider } from '../state/StateProvider'
|
|
||||||
import { matchesRule, MessageObject, WarnRule } from '../utils/web3'
|
|
||||||
import { WarningAlert } from './commons/WarningAlert'
|
|
||||||
import { ErrorAlert } from './commons/ErrorAlert'
|
|
||||||
|
|
||||||
const StyledExecutionConfirmation = styled.div`
|
|
||||||
margin-top: 30px;
|
|
||||||
`
|
|
||||||
|
|
||||||
export interface ExecutionConfirmationParams {
|
|
||||||
message: MessageObject
|
|
||||||
executionData: ExecutionData
|
|
||||||
setExecutionData: Function
|
|
||||||
confirmations: ConfirmationParam[]
|
|
||||||
isHome: boolean
|
|
||||||
executionEventsFetched: boolean
|
|
||||||
setPendingExecution: Function
|
|
||||||
dstRequiredSignatures: number
|
|
||||||
dstValidatorList: string[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ExecutionConfirmation = ({
|
|
||||||
message,
|
|
||||||
executionData,
|
|
||||||
setExecutionData,
|
|
||||||
confirmations,
|
|
||||||
isHome,
|
|
||||||
executionEventsFetched,
|
|
||||||
setPendingExecution,
|
|
||||||
dstRequiredSignatures,
|
|
||||||
dstValidatorList
|
|
||||||
}: ExecutionConfirmationParams) => {
|
|
||||||
const { foreign } = useStateProvider()
|
|
||||||
const [safeExecutionAvailable, setSafeExecutionAvailable] = useState(false)
|
|
||||||
const [error, setError] = useState('')
|
|
||||||
const [warning, setWarning] = useState('')
|
|
||||||
const availableManualExecution =
|
|
||||||
!isHome &&
|
|
||||||
(executionData.status === VALIDATOR_CONFIRMATION_STATUS.WAITING ||
|
|
||||||
executionData.status === VALIDATOR_CONFIRMATION_STATUS.FAILED ||
|
|
||||||
(executionData.status === VALIDATOR_CONFIRMATION_STATUS.UNDEFINED &&
|
|
||||||
executionEventsFetched &&
|
|
||||||
!!executionData.validator))
|
|
||||||
const requiredManualExecution = availableManualExecution && ALM_HOME_TO_FOREIGN_MANUAL_EXECUTION
|
|
||||||
const showAgeColumn = !requiredManualExecution || executionData.status === VALIDATOR_CONFIRMATION_STATUS.FAILED
|
|
||||||
const windowWidth = useWindowWidth()
|
|
||||||
|
|
||||||
const txExplorerLink = getExplorerTxUrl(executionData.txHash, isHome)
|
|
||||||
const formattedValidator =
|
|
||||||
windowWidth < 850 && executionData.validator ? formatTxHash(executionData.validator) : executionData.validator
|
|
||||||
|
|
||||||
useEffect(
|
|
||||||
() => {
|
|
||||||
if (!availableManualExecution || !foreign.bridgeContract) return
|
|
||||||
|
|
||||||
const p = foreign.bridgeContract.methods.getBridgeInterfacesVersion().call()
|
|
||||||
p.then(({ major, minor }: any) => {
|
|
||||||
major = parseInt(major, 10)
|
|
||||||
minor = parseInt(minor, 10)
|
|
||||||
if (major < 5 || (major === 5 && minor < 7)) return
|
|
||||||
|
|
||||||
setSafeExecutionAvailable(true)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
[availableManualExecution, foreign.bridgeContract]
|
|
||||||
)
|
|
||||||
|
|
||||||
useEffect(
|
|
||||||
() => {
|
|
||||||
if (!message.data || !executionData || !availableManualExecution) return
|
|
||||||
|
|
||||||
try {
|
|
||||||
const fileName = 'warnRules'
|
|
||||||
const rules: WarnRule[] = require(`../snapshots/${fileName}.json`)
|
|
||||||
for (let rule of rules) {
|
|
||||||
if (matchesRule(rule, message)) {
|
|
||||||
setWarning(rule.message)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {}
|
|
||||||
},
|
|
||||||
[availableManualExecution, executionData, message, message.data, setWarning]
|
|
||||||
)
|
|
||||||
|
|
||||||
const getExecutionStatusElement = (validatorStatus = '') => {
|
|
||||||
switch (validatorStatus) {
|
|
||||||
case VALIDATOR_CONFIRMATION_STATUS.EXECUTION_SUCCESS:
|
|
||||||
return <SuccessLabel>{validatorStatus}</SuccessLabel>
|
|
||||||
case VALIDATOR_CONFIRMATION_STATUS.FAILED:
|
|
||||||
return <RedLabel>{validatorStatus}</RedLabel>
|
|
||||||
case VALIDATOR_CONFIRMATION_STATUS.PENDING:
|
|
||||||
case VALIDATOR_CONFIRMATION_STATUS.WAITING:
|
|
||||||
return <GreyLabel>{validatorStatus}</GreyLabel>
|
|
||||||
default:
|
|
||||||
return executionData.validator ? (
|
|
||||||
<GreyLabel>{VALIDATOR_CONFIRMATION_STATUS.WAITING}</GreyLabel>
|
|
||||||
) : (
|
|
||||||
<SimpleLoading />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<StyledExecutionConfirmation>
|
|
||||||
{error && <ErrorAlert onClick={() => setError('')} error={error} />}
|
|
||||||
{warning && <WarningAlert onClick={() => setWarning('')} error={warning} />}
|
|
||||||
<table>
|
|
||||||
<Thead>
|
|
||||||
<tr>
|
|
||||||
<th>{requiredManualExecution ? 'Execution info' : 'Executed by'}</th>
|
|
||||||
<th className="text-center">Status</th>
|
|
||||||
{showAgeColumn && <th className="text-center">Age</th>}
|
|
||||||
{availableManualExecution && <th className="text-center">Actions</th>}
|
|
||||||
</tr>
|
|
||||||
</Thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
{requiredManualExecution ? (
|
|
||||||
'Manual user action is required to complete the operation'
|
|
||||||
) : formattedValidator ? (
|
|
||||||
formattedValidator
|
|
||||||
) : (
|
|
||||||
<SimpleLoading />
|
|
||||||
)}
|
|
||||||
</td>
|
|
||||||
<StatusTd className="text-center">{getExecutionStatusElement(executionData.status)}</StatusTd>
|
|
||||||
{showAgeColumn && (
|
|
||||||
<AgeTd className="text-center">
|
|
||||||
{executionData.timestamp > 0 ? (
|
|
||||||
<ExplorerTxLink href={txExplorerLink} target="_blank">
|
|
||||||
{formatTimestamp(executionData.timestamp)}
|
|
||||||
</ExplorerTxLink>
|
|
||||||
) : executionData.status === VALIDATOR_CONFIRMATION_STATUS.WAITING ? (
|
|
||||||
''
|
|
||||||
) : (
|
|
||||||
SEARCHING_TX
|
|
||||||
)}
|
|
||||||
</AgeTd>
|
|
||||||
)}
|
|
||||||
{availableManualExecution && (
|
|
||||||
<td>
|
|
||||||
<ManualExecutionButton
|
|
||||||
safeExecutionAvailable={safeExecutionAvailable}
|
|
||||||
messageData={message.data}
|
|
||||||
setExecutionData={setExecutionData}
|
|
||||||
confirmations={confirmations}
|
|
||||||
setPendingExecution={setPendingExecution}
|
|
||||||
setError={setError}
|
|
||||||
requiredSignatures={dstRequiredSignatures}
|
|
||||||
validatorList={dstValidatorList}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
)}
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</StyledExecutionConfirmation>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@ -1,69 +0,0 @@
|
|||||||
import React, { useState, FormEvent } from 'react'
|
|
||||||
import styled from 'styled-components'
|
|
||||||
import { FormSubmitParams } from './MainPage'
|
|
||||||
import { Button } from './commons/Button'
|
|
||||||
import { TransactionSelector } from './TransactionSelector'
|
|
||||||
import { TransactionReceipt } from 'web3-eth'
|
|
||||||
|
|
||||||
const LabelText = styled.label`
|
|
||||||
line-height: 36px;
|
|
||||||
max-width: 140px;
|
|
||||||
`
|
|
||||||
|
|
||||||
const Input = styled.input`
|
|
||||||
background-color: var(--bg-color);
|
|
||||||
color: var(--font-color);
|
|
||||||
max-width: 100%;
|
|
||||||
border-color: var(--color-primary) !important;
|
|
||||||
&:hover,
|
|
||||||
&:active,
|
|
||||||
&:focus {
|
|
||||||
border-color: var(--button-color) !important;
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
export const Form = ({ onSubmit }: { onSubmit: ({ chainId, txHash, receipt }: FormSubmitParams) => void }) => {
|
|
||||||
const [txHash, setTxHash] = useState('')
|
|
||||||
const [searchTx, setSearchTx] = useState(false)
|
|
||||||
|
|
||||||
const formSubmit = (e: FormEvent) => {
|
|
||||||
e.preventDefault()
|
|
||||||
setSearchTx(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
const onSelected = (chainId: number, receipt: TransactionReceipt) => {
|
|
||||||
onSubmit({ chainId, txHash, receipt })
|
|
||||||
}
|
|
||||||
|
|
||||||
const onBack = () => {
|
|
||||||
setTxHash('')
|
|
||||||
setSearchTx(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (searchTx) {
|
|
||||||
return <TransactionSelector txHash={txHash} onSelected={onSelected} onBack={onBack} />
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<form onSubmit={formSubmit}>
|
|
||||||
<div className="row is-center">
|
|
||||||
<LabelText className="col-2">Bridgeable tx hash:</LabelText>
|
|
||||||
<div className="col-7">
|
|
||||||
<Input
|
|
||||||
placeholder="Enter transaction hash"
|
|
||||||
type="text"
|
|
||||||
onChange={e => setTxHash(e.target.value)}
|
|
||||||
required
|
|
||||||
pattern="^0x[a-fA-F0-9]{64}$"
|
|
||||||
value={txHash}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="col-1">
|
|
||||||
<Button className="button outline" type="submit">
|
|
||||||
Check
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@ -1,148 +0,0 @@
|
|||||||
import React, { useEffect, useState, useCallback } from 'react'
|
|
||||||
import styled from 'styled-components'
|
|
||||||
import { Route, useHistory } from 'react-router-dom'
|
|
||||||
import { Form } from './Form'
|
|
||||||
import { StatusContainer } from './StatusContainer'
|
|
||||||
import { useStateProvider } from '../state/StateProvider'
|
|
||||||
import { TransactionReceipt } from 'web3-eth'
|
|
||||||
import { InfoAlert } from './commons/InfoAlert'
|
|
||||||
import { ExplorerTxLink } from './commons/ExplorerTxLink'
|
|
||||||
import { FOREIGN_NETWORK_NAME, HOME_NETWORK_NAME } from '../config/constants'
|
|
||||||
|
|
||||||
const StyledMainPage = styled.div`
|
|
||||||
text-align: center;
|
|
||||||
min-height: 100vh;
|
|
||||||
`
|
|
||||||
|
|
||||||
const Header = styled.header`
|
|
||||||
background-color: #001529;
|
|
||||||
color: #ffffff;
|
|
||||||
margin-bottom: 50px;
|
|
||||||
`
|
|
||||||
|
|
||||||
const HeaderContainer = styled.header`
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
font-size: 16px;
|
|
||||||
height: 64px;
|
|
||||||
line-height: 64px;
|
|
||||||
padding: 0 50px;
|
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
|
||||||
padding: 0 20px;
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
const AlertP = styled.p`
|
|
||||||
align-items: start;
|
|
||||||
margin-bottom: 0;
|
|
||||||
@media (max-width: 600px) {
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
export interface FormSubmitParams {
|
|
||||||
chainId: number
|
|
||||||
txHash: string
|
|
||||||
receipt: TransactionReceipt
|
|
||||||
}
|
|
||||||
|
|
||||||
export const MainPage = () => {
|
|
||||||
const history = useHistory()
|
|
||||||
const { home, foreign } = useStateProvider()
|
|
||||||
const [networkName, setNetworkName] = useState('')
|
|
||||||
const [receipt, setReceipt] = useState<Maybe<TransactionReceipt>>(null)
|
|
||||||
const [showInfoAlert, setShowInfoAlert] = useState(false)
|
|
||||||
|
|
||||||
const loadFromStorage = useCallback(() => {
|
|
||||||
const hideAlert = window.localStorage.getItem('hideInfoAlert')
|
|
||||||
setShowInfoAlert(!hideAlert)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
useEffect(
|
|
||||||
() => {
|
|
||||||
loadFromStorage()
|
|
||||||
},
|
|
||||||
[loadFromStorage]
|
|
||||||
)
|
|
||||||
|
|
||||||
const onAlertClose = useCallback(
|
|
||||||
() => {
|
|
||||||
window.localStorage.setItem('hideInfoAlert', 'true')
|
|
||||||
loadFromStorage()
|
|
||||||
},
|
|
||||||
[loadFromStorage]
|
|
||||||
)
|
|
||||||
|
|
||||||
const setNetworkData = (chainId: number) => {
|
|
||||||
const network = chainId === home.chainId ? home.name : foreign.name
|
|
||||||
|
|
||||||
setNetworkName(network)
|
|
||||||
}
|
|
||||||
|
|
||||||
const onFormSubmit = ({ chainId, txHash, receipt }: FormSubmitParams) => {
|
|
||||||
setNetworkData(chainId)
|
|
||||||
setReceipt(receipt)
|
|
||||||
history.push(`/${chainId}/${txHash}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
const resetNetworkHeader = () => {
|
|
||||||
setNetworkName('')
|
|
||||||
}
|
|
||||||
|
|
||||||
const setNetworkFromParams = (chainId: number) => {
|
|
||||||
setNetworkData(chainId)
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const w = window as any
|
|
||||||
if (w.ethereum) {
|
|
||||||
w.ethereum.autoRefreshOnNetworkChange = false
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<StyledMainPage>
|
|
||||||
<Header>
|
|
||||||
<HeaderContainer>
|
|
||||||
<span>AMB Live Monitoring</span>
|
|
||||||
<span>{networkName}</span>
|
|
||||||
</HeaderContainer>
|
|
||||||
</Header>
|
|
||||||
<div className="container">
|
|
||||||
{showInfoAlert && (
|
|
||||||
<InfoAlert onClick={onAlertClose}>
|
|
||||||
<p className="is-left text-left">
|
|
||||||
The Arbitrary Message Bridge Live Monitoring application provides real-time status updates for messages
|
|
||||||
bridged between {HOME_NETWORK_NAME} and {FOREIGN_NETWORK_NAME}. You can check current tx status, view
|
|
||||||
validator info, and troubleshoot potential issues with bridge transfers.
|
|
||||||
</p>
|
|
||||||
<AlertP className="is-left text-left">
|
|
||||||
For more information refer to
|
|
||||||
<ExplorerTxLink
|
|
||||||
href="https://docs.tokenbridge.net/about-tokenbridge/components/amb-live-monitoring-application"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
the ALM documentation
|
|
||||||
</ExplorerTxLink>
|
|
||||||
</AlertP>
|
|
||||||
</InfoAlert>
|
|
||||||
)}
|
|
||||||
<Route exact path={['/']} children={<Form onSubmit={onFormSubmit} />} />
|
|
||||||
<Route
|
|
||||||
path={['/:chainId/:txHash/:messageIdParam', '/:chainId/:txHash']}
|
|
||||||
children={
|
|
||||||
<StatusContainer
|
|
||||||
onBackToMain={resetNetworkHeader}
|
|
||||||
setNetworkFromParams={setNetworkFromParams}
|
|
||||||
receiptParam={receipt}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</StyledMainPage>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@ -1,231 +0,0 @@
|
|||||||
import React, { useState, useEffect } from 'react'
|
|
||||||
import styled from 'styled-components'
|
|
||||||
import { InjectedConnector } from '@web3-react/injected-connector'
|
|
||||||
import { useWeb3React } from '@web3-react/core'
|
|
||||||
import {
|
|
||||||
DOUBLE_EXECUTION_ATTEMPT_ERROR,
|
|
||||||
EXECUTION_FAILED_ERROR,
|
|
||||||
EXECUTION_OUT_OF_GAS_ERROR,
|
|
||||||
FOREIGN_EXPLORER_API,
|
|
||||||
INCORRECT_CHAIN_ERROR,
|
|
||||||
VALIDATOR_CONFIRMATION_STATUS
|
|
||||||
} from '../config/constants'
|
|
||||||
import { useStateProvider } from '../state/StateProvider'
|
|
||||||
import { signatureToVRS, packSignatures } from '../utils/signatures'
|
|
||||||
import { getSuccessExecutionData } from '../utils/getFinalizationEvent'
|
|
||||||
import { TransactionReceipt } from 'web3-eth'
|
|
||||||
import { ConfirmationParam } from '../hooks/useMessageConfirmations'
|
|
||||||
|
|
||||||
const ActionButton = styled.button`
|
|
||||||
color: var(--button-color);
|
|
||||||
border-color: var(--font-color);
|
|
||||||
margin-top: 10px;
|
|
||||||
min-width: 120px;
|
|
||||||
padding: 1rem;
|
|
||||||
&:focus {
|
|
||||||
outline: var(--button-color);
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
interface ManualExecutionButtonParams {
|
|
||||||
safeExecutionAvailable: boolean
|
|
||||||
messageData: string
|
|
||||||
setExecutionData: Function
|
|
||||||
confirmations: ConfirmationParam[]
|
|
||||||
setPendingExecution: Function
|
|
||||||
setError: Function
|
|
||||||
requiredSignatures: number
|
|
||||||
validatorList: string[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ManualExecutionButton = ({
|
|
||||||
safeExecutionAvailable,
|
|
||||||
messageData,
|
|
||||||
setExecutionData,
|
|
||||||
confirmations,
|
|
||||||
setPendingExecution,
|
|
||||||
setError,
|
|
||||||
requiredSignatures,
|
|
||||||
validatorList
|
|
||||||
}: ManualExecutionButtonParams) => {
|
|
||||||
const { foreign } = useStateProvider()
|
|
||||||
const { library, activate, account, active } = useWeb3React()
|
|
||||||
const [manualExecution, setManualExecution] = useState(false)
|
|
||||||
const [allowFailures, setAllowFailures] = useState(false)
|
|
||||||
const [ready, setReady] = useState(false)
|
|
||||||
const [title, setTitle] = useState('Loading')
|
|
||||||
const [validSignatures, setValidSignatures] = useState<string[]>([])
|
|
||||||
|
|
||||||
useEffect(
|
|
||||||
() => {
|
|
||||||
if (
|
|
||||||
!foreign.bridgeContract ||
|
|
||||||
!foreign.web3 ||
|
|
||||||
!confirmations ||
|
|
||||||
!confirmations.length ||
|
|
||||||
!requiredSignatures ||
|
|
||||||
!validatorList ||
|
|
||||||
!validatorList.length
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
const signatures = []
|
|
||||||
for (let i = 0; i < confirmations.length && signatures.length < requiredSignatures; i++) {
|
|
||||||
const sig = confirmations[i].signature
|
|
||||||
if (!sig) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
const { v, r, s } = signatureToVRS(sig)
|
|
||||||
const signer = foreign.web3.eth.accounts.recover(messageData, `0x${v}`, `0x${r}`, `0x${s}`)
|
|
||||||
if (validatorList.includes(signer)) {
|
|
||||||
signatures.push(sig)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (signatures.length >= requiredSignatures) {
|
|
||||||
setValidSignatures(signatures.slice(0, requiredSignatures))
|
|
||||||
setTitle('Execute')
|
|
||||||
setReady(true)
|
|
||||||
} else {
|
|
||||||
setTitle('Unavailable')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[
|
|
||||||
foreign.bridgeContract,
|
|
||||||
foreign.web3,
|
|
||||||
validatorList,
|
|
||||||
requiredSignatures,
|
|
||||||
messageData,
|
|
||||||
setValidSignatures,
|
|
||||||
confirmations
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
useEffect(
|
|
||||||
() => {
|
|
||||||
if (!manualExecution || !foreign.chainId) return
|
|
||||||
|
|
||||||
if (!active) {
|
|
||||||
activate(new InjectedConnector({ supportedChainIds: [foreign.chainId] }), e => {
|
|
||||||
if (e.message.includes('Unsupported chain id')) {
|
|
||||||
setError(INCORRECT_CHAIN_ERROR)
|
|
||||||
const { ethereum } = window as any
|
|
||||||
|
|
||||||
// remove the error message after chain is correctly changed to the foreign one
|
|
||||||
const listener = (chainId: string) => {
|
|
||||||
if (parseInt(chainId.slice(2), 16) === foreign.chainId) {
|
|
||||||
ethereum.removeListener('chainChanged', listener)
|
|
||||||
setError((error: string) => (error === INCORRECT_CHAIN_ERROR ? '' : error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ethereum.on('chainChanged', listener)
|
|
||||||
} else {
|
|
||||||
setError(e.message)
|
|
||||||
}
|
|
||||||
setManualExecution(false)
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!library || !foreign.bridgeContract || !foreign.web3 || !validSignatures || !validSignatures.length) return
|
|
||||||
|
|
||||||
const signatures = packSignatures(validSignatures.map(signatureToVRS))
|
|
||||||
const messageId = messageData.slice(0, 66)
|
|
||||||
const bridge = foreign.bridgeContract
|
|
||||||
const executeMethod =
|
|
||||||
safeExecutionAvailable && !allowFailures
|
|
||||||
? bridge.methods.safeExecuteSignaturesWithAutoGasLimit
|
|
||||||
: bridge.methods.executeSignatures
|
|
||||||
const data = executeMethod(messageData, signatures).encodeABI()
|
|
||||||
setManualExecution(false)
|
|
||||||
|
|
||||||
library.eth
|
|
||||||
.sendTransaction({
|
|
||||||
from: account,
|
|
||||||
to: foreign.bridgeAddress,
|
|
||||||
data
|
|
||||||
})
|
|
||||||
.on('transactionHash', (txHash: string) => {
|
|
||||||
setExecutionData({
|
|
||||||
status: VALIDATOR_CONFIRMATION_STATUS.PENDING,
|
|
||||||
validator: account,
|
|
||||||
txHash,
|
|
||||||
timestamp: Math.floor(new Date().getTime() / 1000.0),
|
|
||||||
executionResult: false
|
|
||||||
})
|
|
||||||
setPendingExecution(true)
|
|
||||||
})
|
|
||||||
.on('error', async (e: Error, receipt: TransactionReceipt) => {
|
|
||||||
if (e.message.includes('Transaction has been reverted by the EVM')) {
|
|
||||||
const successExecutionData = await getSuccessExecutionData(
|
|
||||||
bridge,
|
|
||||||
'RelayedMessage',
|
|
||||||
library,
|
|
||||||
messageId,
|
|
||||||
FOREIGN_EXPLORER_API
|
|
||||||
)
|
|
||||||
if (successExecutionData) {
|
|
||||||
setExecutionData(successExecutionData)
|
|
||||||
setError(DOUBLE_EXECUTION_ATTEMPT_ERROR)
|
|
||||||
} else {
|
|
||||||
const { gas } = await library.eth.getTransaction(receipt.transactionHash)
|
|
||||||
setExecutionData({
|
|
||||||
status: VALIDATOR_CONFIRMATION_STATUS.FAILED,
|
|
||||||
validator: account,
|
|
||||||
txHash: receipt.transactionHash,
|
|
||||||
timestamp: Math.floor(new Date().getTime() / 1000.0),
|
|
||||||
executionResult: false
|
|
||||||
})
|
|
||||||
setError(gas === receipt.gasUsed ? EXECUTION_OUT_OF_GAS_ERROR : EXECUTION_FAILED_ERROR)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
setError(e.message)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
[
|
|
||||||
manualExecution,
|
|
||||||
library,
|
|
||||||
activate,
|
|
||||||
active,
|
|
||||||
account,
|
|
||||||
foreign.chainId,
|
|
||||||
foreign.bridgeAddress,
|
|
||||||
foreign.bridgeContract,
|
|
||||||
setError,
|
|
||||||
messageData,
|
|
||||||
setExecutionData,
|
|
||||||
setPendingExecution,
|
|
||||||
safeExecutionAvailable,
|
|
||||||
allowFailures,
|
|
||||||
foreign.web3,
|
|
||||||
validSignatures
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<div className="is-center">
|
|
||||||
<ActionButton disabled={!ready} className="button outline" onClick={() => setManualExecution(true)}>
|
|
||||||
{title}
|
|
||||||
</ActionButton>
|
|
||||||
</div>
|
|
||||||
{safeExecutionAvailable && (
|
|
||||||
<div
|
|
||||||
title="Allow executed message to fail and record its failure on-chain without reverting the whole transaction.
|
|
||||||
Use fixed gas limit for execution."
|
|
||||||
className="is-center"
|
|
||||||
style={{ paddingTop: 10 }}
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
id="allow-failures"
|
|
||||||
checked={allowFailures}
|
|
||||||
onChange={e => setAllowFailures(e.target.checked)}
|
|
||||||
/>
|
|
||||||
<label htmlFor="allow-failures">Unsafe mode</label>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@ -1,47 +0,0 @@
|
|||||||
import React, { useState } from 'react'
|
|
||||||
import { Button } from './commons/Button'
|
|
||||||
import { RadioButtonLabel, RadioButtonContainer } from './commons/RadioButton'
|
|
||||||
import { useWindowWidth } from '@react-hook/window-size'
|
|
||||||
import { formatTxHashExtended } from '../utils/networks'
|
|
||||||
import { MessageObject } from '../utils/web3'
|
|
||||||
|
|
||||||
export interface MessageSelectorParams {
|
|
||||||
messages: Array<MessageObject>
|
|
||||||
onMessageSelected: (index: number) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export const MessageSelector = ({ messages, onMessageSelected }: MessageSelectorParams) => {
|
|
||||||
const [messageIndex, setMessageIndex] = useState(0)
|
|
||||||
const windowWidth = useWindowWidth()
|
|
||||||
|
|
||||||
const onSelect = () => {
|
|
||||||
onMessageSelected(messageIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="row is-center">
|
|
||||||
<div className="col-7-lg col-12 is-marginless">
|
|
||||||
{messages.map((message, i) => (
|
|
||||||
<RadioButtonContainer className="row is-center is-vertical-align" key={i} onClick={() => setMessageIndex(i)}>
|
|
||||||
<input
|
|
||||||
className="is-marginless"
|
|
||||||
type="radio"
|
|
||||||
name="message"
|
|
||||||
value={i}
|
|
||||||
checked={i === messageIndex}
|
|
||||||
onChange={() => setMessageIndex(i)}
|
|
||||||
/>
|
|
||||||
<RadioButtonLabel htmlFor={i.toString()}>
|
|
||||||
{windowWidth < 700 ? formatTxHashExtended(message.id) : message.id}
|
|
||||||
</RadioButtonLabel>
|
|
||||||
</RadioButtonContainer>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<div className="col-1-lg col-12 is-marginless">
|
|
||||||
<Button className="button outline" onClick={onSelect}>
|
|
||||||
Select
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@ -1,47 +0,0 @@
|
|||||||
import React, { useState } from 'react'
|
|
||||||
import { Button } from './commons/Button'
|
|
||||||
import { RadioButtonLabel, RadioButtonContainer } from './commons/RadioButton'
|
|
||||||
import { useStateProvider } from '../state/StateProvider'
|
|
||||||
|
|
||||||
export const NetworkTransactionSelector = ({ onNetworkSelected }: { onNetworkSelected: (chainId: number) => void }) => {
|
|
||||||
const { home, foreign } = useStateProvider()
|
|
||||||
const [chainId, setChainId] = useState(home.chainId)
|
|
||||||
|
|
||||||
const networks = [home, foreign]
|
|
||||||
|
|
||||||
const onSelect = () => {
|
|
||||||
onNetworkSelected(chainId)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<p>The transaction was found in both networks, please select one:</p>
|
|
||||||
<div className="row is-center">
|
|
||||||
<div className="col-3-lg col-12 is-marginless">
|
|
||||||
{networks.map((network, i) => (
|
|
||||||
<RadioButtonContainer
|
|
||||||
className="row is-center is-vertical-align"
|
|
||||||
key={i}
|
|
||||||
onClick={() => setChainId(network.chainId)}
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
className="is-marginless"
|
|
||||||
type="radio"
|
|
||||||
name="message"
|
|
||||||
value={network.chainId}
|
|
||||||
checked={network.chainId === chainId}
|
|
||||||
onChange={() => setChainId(network.chainId)}
|
|
||||||
/>
|
|
||||||
<RadioButtonLabel htmlFor={i.toString()}>{network.name}</RadioButtonLabel>
|
|
||||||
</RadioButtonContainer>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<div className="col-3-lg col-12 is-marginless">
|
|
||||||
<Button className="button outline" onClick={onSelect}>
|
|
||||||
Select
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@ -1,118 +0,0 @@
|
|||||||
import React, { useEffect } from 'react'
|
|
||||||
import { useHistory, useParams } from 'react-router-dom'
|
|
||||||
import { useTransactionStatus } from '../hooks/useTransactionStatus'
|
|
||||||
import { formatTxHash, getExplorerTxUrl, getTransactionStatusDescription, validTxHash } from '../utils/networks'
|
|
||||||
import { TRANSACTION_STATUS } from '../config/constants'
|
|
||||||
import { MessageSelector } from './MessageSelector'
|
|
||||||
import { Loading } from './commons/Loading'
|
|
||||||
import { useStateProvider } from '../state/StateProvider'
|
|
||||||
import { ExplorerTxLink } from './commons/ExplorerTxLink'
|
|
||||||
import { ConfirmationsContainer } from './ConfirmationsContainer'
|
|
||||||
import { TransactionReceipt } from 'web3-eth'
|
|
||||||
import { BackButton } from './commons/BackButton'
|
|
||||||
import { useClosestBlock } from '../hooks/useClosestBlock'
|
|
||||||
|
|
||||||
export interface StatusContainerParam {
|
|
||||||
onBackToMain: () => void
|
|
||||||
setNetworkFromParams: (chainId: number) => void
|
|
||||||
receiptParam: Maybe<TransactionReceipt>
|
|
||||||
}
|
|
||||||
|
|
||||||
export const StatusContainer = ({ onBackToMain, setNetworkFromParams, receiptParam }: StatusContainerParam) => {
|
|
||||||
const { home, foreign } = useStateProvider()
|
|
||||||
const history = useHistory()
|
|
||||||
const { chainId, txHash, messageIdParam } = useParams()
|
|
||||||
const validChainId = chainId === home.chainId.toString() || chainId === foreign.chainId.toString()
|
|
||||||
const validParameters = validChainId && validTxHash(txHash)
|
|
||||||
const isHome = chainId === home.chainId.toString()
|
|
||||||
|
|
||||||
const { messages, receipt, status, description, timestamp, loading } = useTransactionStatus({
|
|
||||||
txHash: validParameters ? txHash : '',
|
|
||||||
chainId: validParameters ? parseInt(chainId) : 0,
|
|
||||||
receiptParam
|
|
||||||
})
|
|
||||||
const homeStartBlock = useClosestBlock(true, isHome, receipt, timestamp)
|
|
||||||
const foreignStartBlock = useClosestBlock(false, isHome, receipt, timestamp)
|
|
||||||
|
|
||||||
const selectedMessageId = messageIdParam === undefined || messages[messageIdParam] === undefined ? -1 : messageIdParam
|
|
||||||
|
|
||||||
useEffect(
|
|
||||||
() => {
|
|
||||||
if (validChainId) {
|
|
||||||
setNetworkFromParams(parseInt(chainId))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[validChainId, chainId, setNetworkFromParams]
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!validParameters && home.chainId && foreign.chainId) {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<p>
|
|
||||||
Chain Id: {chainId} and/or Transaction Hash: {txHash} are not valid
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (loading) {
|
|
||||||
return <Loading />
|
|
||||||
}
|
|
||||||
|
|
||||||
const onMessageSelected = (messageId: number) => {
|
|
||||||
history.push(`/${chainId}/${txHash}/${messageId}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
const displayMessageSelector = status === TRANSACTION_STATUS.SUCCESS_MULTIPLE_MESSAGES && selectedMessageId === -1
|
|
||||||
const multiMessageSelected = status === TRANSACTION_STATUS.SUCCESS_MULTIPLE_MESSAGES && selectedMessageId !== -1
|
|
||||||
const displayReference = multiMessageSelected ? messages[selectedMessageId].id : txHash
|
|
||||||
const formattedMessageId = formatTxHash(displayReference)
|
|
||||||
|
|
||||||
const txExplorerLink = getExplorerTxUrl(txHash, isHome)
|
|
||||||
const displayExplorerLink = status !== TRANSACTION_STATUS.NOT_FOUND
|
|
||||||
|
|
||||||
const displayConfirmations = status === TRANSACTION_STATUS.SUCCESS_ONE_MESSAGE || multiMessageSelected
|
|
||||||
const messageToConfirm =
|
|
||||||
messages.length > 1 ? messages[selectedMessageId] : messages.length > 0 ? messages[0] : { id: '', data: '' }
|
|
||||||
|
|
||||||
let displayedDescription: string = multiMessageSelected
|
|
||||||
? getTransactionStatusDescription(TRANSACTION_STATUS.SUCCESS_ONE_MESSAGE, timestamp)
|
|
||||||
: description
|
|
||||||
let link
|
|
||||||
const descArray = displayedDescription.split('%link')
|
|
||||||
if (descArray.length > 1) {
|
|
||||||
displayedDescription = descArray[0]
|
|
||||||
link = (
|
|
||||||
<ExplorerTxLink href={descArray[1]} target="_blank" rel="noopener noreferrer">
|
|
||||||
{descArray[1]}
|
|
||||||
</ExplorerTxLink>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{status && (
|
|
||||||
<p>
|
|
||||||
The transaction{' '}
|
|
||||||
{displayExplorerLink && (
|
|
||||||
<ExplorerTxLink href={txExplorerLink} target="_blank">
|
|
||||||
{formattedMessageId}
|
|
||||||
</ExplorerTxLink>
|
|
||||||
)}
|
|
||||||
{!displayExplorerLink && <label>{formattedMessageId}</label>} {displayedDescription} {link}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
{displayMessageSelector && <MessageSelector messages={messages} onMessageSelected={onMessageSelected} />}
|
|
||||||
{displayConfirmations && (
|
|
||||||
<ConfirmationsContainer
|
|
||||||
message={messageToConfirm}
|
|
||||||
receipt={receipt}
|
|
||||||
fromHome={isHome}
|
|
||||||
homeStartBlock={homeStartBlock}
|
|
||||||
foreignStartBlock={foreignStartBlock}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<BackButton onBackToMain={onBackToMain} />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@ -1,67 +0,0 @@
|
|||||||
import React, { useEffect } from 'react'
|
|
||||||
import { useTransactionFinder } from '../hooks/useTransactionFinder'
|
|
||||||
import { useStateProvider } from '../state/StateProvider'
|
|
||||||
import { TRANSACTION_STATUS } from '../config/constants'
|
|
||||||
import { TransactionReceipt } from 'web3-eth'
|
|
||||||
import { Loading } from './commons/Loading'
|
|
||||||
import { NetworkTransactionSelector } from './NetworkTransactionSelector'
|
|
||||||
import { BackButton } from './commons/BackButton'
|
|
||||||
import { TRANSACTION_STATUS_DESCRIPTION } from '../config/descriptions'
|
|
||||||
import { MultiLine } from './commons/MultiLine'
|
|
||||||
import styled from 'styled-components'
|
|
||||||
|
|
||||||
const StyledMultiLine = styled(MultiLine)`
|
|
||||||
margin-bottom: 40px;
|
|
||||||
`
|
|
||||||
|
|
||||||
export const TransactionSelector = ({
|
|
||||||
txHash,
|
|
||||||
onSelected,
|
|
||||||
onBack
|
|
||||||
}: {
|
|
||||||
txHash: string
|
|
||||||
onSelected: (chainId: number, receipt: TransactionReceipt) => void
|
|
||||||
onBack: () => void
|
|
||||||
}) => {
|
|
||||||
const { home, foreign } = useStateProvider()
|
|
||||||
const { receipt: homeReceipt, status: homeStatus } = useTransactionFinder({ txHash, web3: home.web3 })
|
|
||||||
const { receipt: foreignReceipt, status: foreignStatus } = useTransactionFinder({ txHash, web3: foreign.web3 })
|
|
||||||
|
|
||||||
useEffect(
|
|
||||||
() => {
|
|
||||||
if (!home.chainId || !foreign.chainId) return
|
|
||||||
if (homeStatus === TRANSACTION_STATUS.FOUND && foreignStatus === TRANSACTION_STATUS.NOT_FOUND) {
|
|
||||||
if (!homeReceipt) return
|
|
||||||
onSelected(home.chainId, homeReceipt)
|
|
||||||
} else if (foreignStatus === TRANSACTION_STATUS.FOUND && homeStatus === TRANSACTION_STATUS.NOT_FOUND) {
|
|
||||||
if (!foreignReceipt) return
|
|
||||||
onSelected(foreign.chainId, foreignReceipt)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[homeReceipt, homeStatus, foreignReceipt, foreignStatus, home.chainId, foreign.chainId, onSelected]
|
|
||||||
)
|
|
||||||
|
|
||||||
const onSelectedNetwork = (chainId: number) => {
|
|
||||||
const chain = chainId === home.chainId ? home.chainId : foreign.chainId
|
|
||||||
const receipt = chainId === home.chainId ? homeReceipt : foreignReceipt
|
|
||||||
|
|
||||||
if (!receipt) return
|
|
||||||
onSelected(chain, receipt)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (foreignStatus === TRANSACTION_STATUS.FOUND && homeStatus === TRANSACTION_STATUS.FOUND) {
|
|
||||||
return <NetworkTransactionSelector onNetworkSelected={onSelectedNetwork} />
|
|
||||||
}
|
|
||||||
|
|
||||||
if (foreignStatus === TRANSACTION_STATUS.NOT_FOUND && homeStatus === TRANSACTION_STATUS.NOT_FOUND) {
|
|
||||||
const message = TRANSACTION_STATUS_DESCRIPTION[TRANSACTION_STATUS.NOT_FOUND]
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<StyledMultiLine>{message}</StyledMultiLine>
|
|
||||||
<BackButton onBackToMain={onBack} />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return <Loading />
|
|
||||||
}
|
|
||||||
@ -1,105 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import { formatTimestamp, formatTxHash, getExplorerTxUrl } from '../utils/networks'
|
|
||||||
import { useWindowWidth } from '@react-hook/window-size'
|
|
||||||
import { RECENT_AGE, SEARCHING_TX, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants'
|
|
||||||
import { SimpleLoading } from './commons/Loading'
|
|
||||||
import styled from 'styled-components'
|
|
||||||
import { ConfirmationParam } from '../hooks/useMessageConfirmations'
|
|
||||||
import { GreyLabel, RedLabel, SuccessLabel } from './commons/Labels'
|
|
||||||
import { ExplorerTxLink } from './commons/ExplorerTxLink'
|
|
||||||
import { Thead, AgeTd, StatusTd } from './commons/Table'
|
|
||||||
|
|
||||||
const RequiredConfirmations = styled.label`
|
|
||||||
font-size: 14px;
|
|
||||||
`
|
|
||||||
|
|
||||||
export interface ValidatorsConfirmationsParams {
|
|
||||||
confirmations: Array<ConfirmationParam>
|
|
||||||
requiredSignatures: number
|
|
||||||
validatorList: string[]
|
|
||||||
waitingBlocksResolved: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ValidatorsConfirmations = ({
|
|
||||||
confirmations,
|
|
||||||
requiredSignatures,
|
|
||||||
validatorList,
|
|
||||||
waitingBlocksResolved
|
|
||||||
}: ValidatorsConfirmationsParams) => {
|
|
||||||
const windowWidth = useWindowWidth()
|
|
||||||
|
|
||||||
const getValidatorStatusElement = (validatorStatus = '') => {
|
|
||||||
switch (validatorStatus) {
|
|
||||||
case VALIDATOR_CONFIRMATION_STATUS.SUCCESS:
|
|
||||||
case VALIDATOR_CONFIRMATION_STATUS.MANUAL:
|
|
||||||
case VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID:
|
|
||||||
return <SuccessLabel>{VALIDATOR_CONFIRMATION_STATUS.SUCCESS}</SuccessLabel>
|
|
||||||
case VALIDATOR_CONFIRMATION_STATUS.FAILED:
|
|
||||||
return <RedLabel>{validatorStatus}</RedLabel>
|
|
||||||
case VALIDATOR_CONFIRMATION_STATUS.PENDING:
|
|
||||||
case VALIDATOR_CONFIRMATION_STATUS.WAITING:
|
|
||||||
case VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED:
|
|
||||||
return <GreyLabel>{validatorStatus}</GreyLabel>
|
|
||||||
default:
|
|
||||||
return waitingBlocksResolved ? (
|
|
||||||
<GreyLabel>{VALIDATOR_CONFIRMATION_STATUS.WAITING}</GreyLabel>
|
|
||||||
) : (
|
|
||||||
<SimpleLoading />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<table>
|
|
||||||
<Thead>
|
|
||||||
<tr>
|
|
||||||
<th>Validator</th>
|
|
||||||
<th className="text-center">Status</th>
|
|
||||||
<th className="text-center">Age</th>
|
|
||||||
</tr>
|
|
||||||
</Thead>
|
|
||||||
<tbody>
|
|
||||||
{confirmations.map((confirmation, i) => {
|
|
||||||
const displayedStatus = confirmation.status
|
|
||||||
const explorerLink = getExplorerTxUrl(confirmation.txHash, true)
|
|
||||||
let elementIfNoTimestamp: any = <SimpleLoading />
|
|
||||||
switch (displayedStatus) {
|
|
||||||
case '':
|
|
||||||
case VALIDATOR_CONFIRMATION_STATUS.UNDEFINED:
|
|
||||||
if (waitingBlocksResolved) {
|
|
||||||
elementIfNoTimestamp = SEARCHING_TX
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case VALIDATOR_CONFIRMATION_STATUS.WAITING:
|
|
||||||
case VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED:
|
|
||||||
elementIfNoTimestamp = ''
|
|
||||||
break
|
|
||||||
case VALIDATOR_CONFIRMATION_STATUS.MANUAL:
|
|
||||||
elementIfNoTimestamp = RECENT_AGE
|
|
||||||
break
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<tr key={i}>
|
|
||||||
<td>{windowWidth < 850 ? formatTxHash(confirmation.validator) : confirmation.validator}</td>
|
|
||||||
<StatusTd className="text-center">{getValidatorStatusElement(displayedStatus)}</StatusTd>
|
|
||||||
<AgeTd className="text-center">
|
|
||||||
{confirmation && confirmation.timestamp > 0 ? (
|
|
||||||
<ExplorerTxLink href={explorerLink} target="_blank">
|
|
||||||
{formatTimestamp(confirmation.timestamp)}
|
|
||||||
</ExplorerTxLink>
|
|
||||||
) : (
|
|
||||||
elementIfNoTimestamp
|
|
||||||
)}
|
|
||||||
</AgeTd>
|
|
||||||
</tr>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<RequiredConfirmations>
|
|
||||||
At least <strong>{requiredSignatures}</strong> of <strong>{validatorList.length}</strong> confirmations required
|
|
||||||
</RequiredConfirmations>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@ -1,35 +0,0 @@
|
|||||||
import { Link } from 'react-router-dom'
|
|
||||||
import { LeftArrow } from './LeftArrow'
|
|
||||||
import React from 'react'
|
|
||||||
import styled from 'styled-components'
|
|
||||||
|
|
||||||
const StyledButton = styled.button`
|
|
||||||
color: var(--button-color);
|
|
||||||
border-color: var(--font-color);
|
|
||||||
margin-top: 10px;
|
|
||||||
&:focus {
|
|
||||||
outline: var(--button-color);
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
const BackLabel = styled.label`
|
|
||||||
margin-left: 5px;
|
|
||||||
cursor: pointer;
|
|
||||||
`
|
|
||||||
|
|
||||||
export interface BackButtonParam {
|
|
||||||
onBackToMain: () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export const BackButton = ({ onBackToMain }: BackButtonParam) => (
|
|
||||||
<div className="row is-center">
|
|
||||||
<div className="col-9">
|
|
||||||
<Link to="/" onClick={onBackToMain}>
|
|
||||||
<StyledButton className="button outline is-left">
|
|
||||||
<LeftArrow />
|
|
||||||
<BackLabel>Search another transaction</BackLabel>
|
|
||||||
</StyledButton>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
import styled from 'styled-components'
|
|
||||||
|
|
||||||
export const Button = styled.button`
|
|
||||||
height: 36px;
|
|
||||||
color: var(--button-color);
|
|
||||||
border-color: var(--button-color);
|
|
||||||
&:focus {
|
|
||||||
outline: var(--button-color);
|
|
||||||
}
|
|
||||||
`
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
|
|
||||||
export const CloseIcon = ({ color }: { color?: string }) => (
|
|
||||||
<svg
|
|
||||||
aria-hidden="true"
|
|
||||||
focusable="false"
|
|
||||||
data-prefix="fa"
|
|
||||||
data-icon="times"
|
|
||||||
className="svg-inline--fa fa-times fa-w-11 fa-lg "
|
|
||||||
role="img"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 352 512"
|
|
||||||
fill={color || '#1890ff'}
|
|
||||||
height="1em"
|
|
||||||
>
|
|
||||||
<path d="M242.72 256l100.07-100.07c12.28-12.28 12.28-32.19 0-44.48l-22.24-22.24c-12.28-12.28-32.19-12.28-44.48 0L176 189.28 75.93 89.21c-12.28-12.28-32.19-12.28-44.48 0L9.21 111.45c-12.28 12.28-12.28 32.19 0 44.48L109.28 256 9.21 356.07c-12.28 12.28-12.28 32.19 0 44.48l22.24 22.24c12.28 12.28 32.2 12.28 44.48 0L176 322.72l100.07 100.07c12.28 12.28 32.2 12.28 44.48 0l22.24-22.24c12.28-12.28 12.28-32.19 0-44.48L242.72 256z" />
|
|
||||||
</svg>
|
|
||||||
)
|
|
||||||
@ -1,48 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import styled from 'styled-components'
|
|
||||||
import { InfoIcon } from './InfoIcon'
|
|
||||||
import { CloseIcon } from './CloseIcon'
|
|
||||||
import { ExplorerTxLink } from './ExplorerTxLink'
|
|
||||||
|
|
||||||
const StyledErrorAlert = styled.div`
|
|
||||||
border: 1px solid var(--failed-color);
|
|
||||||
border-radius: 4px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
padding-top: 10px;
|
|
||||||
`
|
|
||||||
|
|
||||||
const CloseIconContainer = styled.div`
|
|
||||||
cursor: pointer;
|
|
||||||
`
|
|
||||||
|
|
||||||
const TextContainer = styled.div`
|
|
||||||
white-space: pre-wrap;
|
|
||||||
flex-direction: column;
|
|
||||||
`
|
|
||||||
|
|
||||||
export const ErrorAlert = ({ onClick, error }: { onClick: () => void; error: string }) => {
|
|
||||||
const errorArray = error.split('%link')
|
|
||||||
const text = errorArray[0]
|
|
||||||
let link
|
|
||||||
if (errorArray.length > 1) {
|
|
||||||
link = (
|
|
||||||
<ExplorerTxLink href={errorArray[1]} target="_blank" rel="noopener noreferrer">
|
|
||||||
{errorArray[1]}
|
|
||||||
</ExplorerTxLink>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div className="row is-center">
|
|
||||||
<StyledErrorAlert className="col-12 is-vertical-align row">
|
|
||||||
<InfoIcon color="var(--failed-color)" />
|
|
||||||
<TextContainer className="col-10">
|
|
||||||
{text}
|
|
||||||
{link}
|
|
||||||
</TextContainer>
|
|
||||||
<CloseIconContainer className="col-1 is-vertical-align is-center" onClick={onClick}>
|
|
||||||
<CloseIcon color="var(--failed-color)" />
|
|
||||||
</CloseIconContainer>
|
|
||||||
</StyledErrorAlert>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
import styled from 'styled-components'
|
|
||||||
|
|
||||||
export const ExplorerTxLink = styled.a`
|
|
||||||
color: var(--link-color);
|
|
||||||
text-decoration: underline;
|
|
||||||
font-weight: bold;
|
|
||||||
`
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import styled from 'styled-components'
|
|
||||||
import { InfoIcon } from './InfoIcon'
|
|
||||||
import { CloseIcon } from './CloseIcon'
|
|
||||||
|
|
||||||
const StyledInfoAlert = styled.div`
|
|
||||||
border: 1px solid var(--button-color);
|
|
||||||
border-radius: 4px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
padding-top: 10px;
|
|
||||||
`
|
|
||||||
|
|
||||||
const CloseIconContainer = styled.div`
|
|
||||||
cursor: pointer;
|
|
||||||
`
|
|
||||||
|
|
||||||
const TextContainer = styled.div`
|
|
||||||
flex-direction: column;
|
|
||||||
`
|
|
||||||
|
|
||||||
export const InfoAlert = ({ onClick, children }: { onClick: () => void; children: React.ReactChild[] }) => (
|
|
||||||
<div className="row is-center">
|
|
||||||
<StyledInfoAlert className="col-10 is-vertical-align row">
|
|
||||||
<InfoIcon />
|
|
||||||
<TextContainer className="col-10">{children}</TextContainer>
|
|
||||||
<CloseIconContainer className="col-1 is-vertical-align is-center" onClick={onClick}>
|
|
||||||
<CloseIcon />
|
|
||||||
</CloseIconContainer>
|
|
||||||
</StyledInfoAlert>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
|
|
||||||
export const InfoIcon = ({ color }: { color?: string }) => (
|
|
||||||
<svg
|
|
||||||
className="col-1 is-left"
|
|
||||||
viewBox="64 64 896 896"
|
|
||||||
focusable="false"
|
|
||||||
data-icon="info-circle"
|
|
||||||
width="1em"
|
|
||||||
height="1em"
|
|
||||||
fill={color || '#1890ff'}
|
|
||||||
aria-hidden="true"
|
|
||||||
>
|
|
||||||
<path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm32 664c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V456c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v272zm-32-344a48.01 48.01 0 010-96 48.01 48.01 0 010 96z" />
|
|
||||||
</svg>
|
|
||||||
)
|
|
||||||
@ -1,22 +0,0 @@
|
|||||||
import styled from 'styled-components'
|
|
||||||
|
|
||||||
export const SuccessLabel = styled.label`
|
|
||||||
color: var(--success-color);
|
|
||||||
background-color: var(--success-bg-color);
|
|
||||||
padding: 0.4rem 0.7rem;
|
|
||||||
border-radius: 4px;
|
|
||||||
`
|
|
||||||
|
|
||||||
export const GreyLabel = styled.label`
|
|
||||||
color: var(--not-required-color);
|
|
||||||
background-color: var(--not-required-bg-color);
|
|
||||||
padding: 0.4rem 0.7rem;
|
|
||||||
border-radius: 4px;
|
|
||||||
`
|
|
||||||
|
|
||||||
export const RedLabel = styled.label`
|
|
||||||
color: var(--failed-color);
|
|
||||||
background-color: var(--failed-bg-color);
|
|
||||||
padding: 0.4rem 0.7rem;
|
|
||||||
border-radius: 4px;
|
|
||||||
`
|
|
||||||
@ -1,20 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import { useContext } from 'react'
|
|
||||||
import { ThemeContext } from 'styled-components'
|
|
||||||
|
|
||||||
export const LeftArrow = () => {
|
|
||||||
const themeContext = useContext(ThemeContext)
|
|
||||||
return (
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
version="1.1"
|
|
||||||
id="mdi-arrow-left"
|
|
||||||
width="24"
|
|
||||||
height="24"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
fill={themeContext.buttonColor}
|
|
||||||
>
|
|
||||||
<path d="M20,11V13H8L13.5,18.5L12.08,19.92L4.16,12L12.08,4.08L13.5,5.5L8,11H20Z" />
|
|
||||||
</svg>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@ -1,165 +0,0 @@
|
|||||||
import React, { useContext } from 'react'
|
|
||||||
import { ThemeContext } from 'styled-components'
|
|
||||||
|
|
||||||
export interface LoadingParams {
|
|
||||||
width?: string
|
|
||||||
height?: string
|
|
||||||
displayMessage?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Loading = ({ width = '50px', height = '50px', displayMessage = true }: LoadingParams) => {
|
|
||||||
const themeContext = useContext(ThemeContext)
|
|
||||||
return (
|
|
||||||
<div className="row is-center">
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
style={{ background: 'none', display: 'block', shapeRendering: 'auto' }}
|
|
||||||
width={width}
|
|
||||||
height={height}
|
|
||||||
viewBox="0 0 100 100"
|
|
||||||
preserveAspectRatio="xMidYMid"
|
|
||||||
>
|
|
||||||
<g transform="rotate(0 50 50)">
|
|
||||||
<rect x="47" y="24" rx="3" ry="6" width="6" height="12" fill={themeContext.buttonColor}>
|
|
||||||
<animate
|
|
||||||
attributeName="opacity"
|
|
||||||
values="1;0"
|
|
||||||
keyTimes="0;1"
|
|
||||||
dur="1s"
|
|
||||||
begin="-0.9166666666666666s"
|
|
||||||
repeatCount="indefinite"
|
|
||||||
/>
|
|
||||||
</rect>
|
|
||||||
</g>
|
|
||||||
<g transform="rotate(30 50 50)">
|
|
||||||
<rect x="47" y="24" rx="3" ry="6" width="6" height="12" fill={themeContext.buttonColor}>
|
|
||||||
<animate
|
|
||||||
attributeName="opacity"
|
|
||||||
values="1;0"
|
|
||||||
keyTimes="0;1"
|
|
||||||
dur="1s"
|
|
||||||
begin="-0.8333333333333334s"
|
|
||||||
repeatCount="indefinite"
|
|
||||||
/>
|
|
||||||
</rect>
|
|
||||||
</g>
|
|
||||||
<g transform="rotate(60 50 50)">
|
|
||||||
<rect x="47" y="24" rx="3" ry="6" width="6" height="12" fill={themeContext.buttonColor}>
|
|
||||||
<animate
|
|
||||||
attributeName="opacity"
|
|
||||||
values="1;0"
|
|
||||||
keyTimes="0;1"
|
|
||||||
dur="1s"
|
|
||||||
begin="-0.75s"
|
|
||||||
repeatCount="indefinite"
|
|
||||||
/>
|
|
||||||
</rect>
|
|
||||||
</g>
|
|
||||||
<g transform="rotate(90 50 50)">
|
|
||||||
<rect x="47" y="24" rx="3" ry="6" width="6" height="12" fill={themeContext.buttonColor}>
|
|
||||||
<animate
|
|
||||||
attributeName="opacity"
|
|
||||||
values="1;0"
|
|
||||||
keyTimes="0;1"
|
|
||||||
dur="1s"
|
|
||||||
begin="-0.6666666666666666s"
|
|
||||||
repeatCount="indefinite"
|
|
||||||
/>
|
|
||||||
</rect>
|
|
||||||
</g>
|
|
||||||
<g transform="rotate(120 50 50)">
|
|
||||||
<rect x="47" y="24" rx="3" ry="6" width="6" height="12" fill={themeContext.buttonColor}>
|
|
||||||
<animate
|
|
||||||
attributeName="opacity"
|
|
||||||
values="1;0"
|
|
||||||
keyTimes="0;1"
|
|
||||||
dur="1s"
|
|
||||||
begin="-0.5833333333333334s"
|
|
||||||
repeatCount="indefinite"
|
|
||||||
/>
|
|
||||||
</rect>
|
|
||||||
</g>
|
|
||||||
<g transform="rotate(150 50 50)">
|
|
||||||
<rect x="47" y="24" rx="3" ry="6" width="6" height="12" fill={themeContext.buttonColor}>
|
|
||||||
<animate
|
|
||||||
attributeName="opacity"
|
|
||||||
values="1;0"
|
|
||||||
keyTimes="0;1"
|
|
||||||
dur="1s"
|
|
||||||
begin="-0.5s"
|
|
||||||
repeatCount="indefinite"
|
|
||||||
/>
|
|
||||||
</rect>
|
|
||||||
</g>
|
|
||||||
<g transform="rotate(180 50 50)">
|
|
||||||
<rect x="47" y="24" rx="3" ry="6" width="6" height="12" fill={themeContext.buttonColor}>
|
|
||||||
<animate
|
|
||||||
attributeName="opacity"
|
|
||||||
values="1;0"
|
|
||||||
keyTimes="0;1"
|
|
||||||
dur="1s"
|
|
||||||
begin="-0.4166666666666667s"
|
|
||||||
repeatCount="indefinite"
|
|
||||||
/>
|
|
||||||
</rect>
|
|
||||||
</g>
|
|
||||||
<g transform="rotate(210 50 50)">
|
|
||||||
<rect x="47" y="24" rx="3" ry="6" width="6" height="12" fill={themeContext.buttonColor}>
|
|
||||||
<animate
|
|
||||||
attributeName="opacity"
|
|
||||||
values="1;0"
|
|
||||||
keyTimes="0;1"
|
|
||||||
dur="1s"
|
|
||||||
begin="-0.3333333333333333s"
|
|
||||||
repeatCount="indefinite"
|
|
||||||
/>
|
|
||||||
</rect>
|
|
||||||
</g>
|
|
||||||
<g transform="rotate(240 50 50)">
|
|
||||||
<rect x="47" y="24" rx="3" ry="6" width="6" height="12" fill={themeContext.buttonColor}>
|
|
||||||
<animate
|
|
||||||
attributeName="opacity"
|
|
||||||
values="1;0"
|
|
||||||
keyTimes="0;1"
|
|
||||||
dur="1s"
|
|
||||||
begin="-0.25s"
|
|
||||||
repeatCount="indefinite"
|
|
||||||
/>
|
|
||||||
</rect>
|
|
||||||
</g>
|
|
||||||
<g transform="rotate(270 50 50)">
|
|
||||||
<rect x="47" y="24" rx="3" ry="6" width="6" height="12" fill={themeContext.buttonColor}>
|
|
||||||
<animate
|
|
||||||
attributeName="opacity"
|
|
||||||
values="1;0"
|
|
||||||
keyTimes="0;1"
|
|
||||||
dur="1s"
|
|
||||||
begin="-0.16666666666666666s"
|
|
||||||
repeatCount="indefinite"
|
|
||||||
/>
|
|
||||||
</rect>
|
|
||||||
</g>
|
|
||||||
<g transform="rotate(300 50 50)">
|
|
||||||
<rect x="47" y="24" rx="3" ry="6" width="6" height="12" fill={themeContext.buttonColor}>
|
|
||||||
<animate
|
|
||||||
attributeName="opacity"
|
|
||||||
values="1;0"
|
|
||||||
keyTimes="0;1"
|
|
||||||
dur="1s"
|
|
||||||
begin="-0.08333333333333333s"
|
|
||||||
repeatCount="indefinite"
|
|
||||||
/>
|
|
||||||
</rect>
|
|
||||||
</g>
|
|
||||||
<g transform="rotate(330 50 50)">
|
|
||||||
<rect x="47" y="24" rx="3" ry="6" width="6" height="12" fill={themeContext.buttonColor}>
|
|
||||||
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="0s" repeatCount="indefinite" />
|
|
||||||
</rect>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
{displayMessage && <label style={{ color: themeContext.buttonColor }}>Loading...</label>}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const SimpleLoading = () => <Loading width="30px" height="30px" displayMessage={false} />
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
import styled from 'styled-components'
|
|
||||||
|
|
||||||
export const MultiLine = styled.div`
|
|
||||||
white-space: pre-wrap;
|
|
||||||
`
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
import styled from 'styled-components'
|
|
||||||
|
|
||||||
export const RadioButtonLabel = styled.label`
|
|
||||||
padding-left: 5px;
|
|
||||||
`
|
|
||||||
|
|
||||||
export const RadioButtonContainer = styled.div`
|
|
||||||
padding: 10px;
|
|
||||||
`
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
import styled from 'styled-components'
|
|
||||||
|
|
||||||
export const Thead = styled.thead`
|
|
||||||
border-bottom: 2px solid #9e9e9e;
|
|
||||||
`
|
|
||||||
|
|
||||||
export const StatusTd = styled.td`
|
|
||||||
width: 150px;
|
|
||||||
`
|
|
||||||
|
|
||||||
export const AgeTd = styled.td`
|
|
||||||
width: 180px;
|
|
||||||
`
|
|
||||||
@ -1,34 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import styled from 'styled-components'
|
|
||||||
import { InfoIcon } from './InfoIcon'
|
|
||||||
import { CloseIcon } from './CloseIcon'
|
|
||||||
|
|
||||||
const StyledErrorAlert = styled.div`
|
|
||||||
border: 1px solid var(--warning-color);
|
|
||||||
border-radius: 4px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
padding-top: 10px;
|
|
||||||
`
|
|
||||||
|
|
||||||
const CloseIconContainer = styled.div`
|
|
||||||
cursor: pointer;
|
|
||||||
`
|
|
||||||
|
|
||||||
const TextContainer = styled.div`
|
|
||||||
white-space: pre-wrap;
|
|
||||||
flex-direction: column;
|
|
||||||
`
|
|
||||||
|
|
||||||
export const WarningAlert = ({ onClick, error }: { onClick: () => void; error: string }) => {
|
|
||||||
return (
|
|
||||||
<div className="row is-center">
|
|
||||||
<StyledErrorAlert className="col-12 is-vertical-align row">
|
|
||||||
<InfoIcon color="var(--warning-color)" />
|
|
||||||
<TextContainer className="col-10">{error}</TextContainer>
|
|
||||||
<CloseIconContainer className="col-1 is-vertical-align is-center" onClick={onClick}>
|
|
||||||
<CloseIcon color="var(--warning-color)" />
|
|
||||||
</CloseIconContainer>
|
|
||||||
</StyledErrorAlert>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@ -1,81 +0,0 @@
|
|||||||
export const HOME_BRIDGE_ADDRESS: string = process.env.REACT_APP_COMMON_HOME_BRIDGE_ADDRESS || ''
|
|
||||||
export const FOREIGN_BRIDGE_ADDRESS: string = process.env.REACT_APP_COMMON_FOREIGN_BRIDGE_ADDRESS || ''
|
|
||||||
|
|
||||||
export const HOME_RPC_URL: string = process.env.REACT_APP_COMMON_HOME_RPC_URL || ''
|
|
||||||
export const FOREIGN_RPC_URL: string = process.env.REACT_APP_COMMON_FOREIGN_RPC_URL || ''
|
|
||||||
|
|
||||||
export const HOME_NETWORK_NAME: string = process.env.REACT_APP_ALM_HOME_NETWORK_NAME || ''
|
|
||||||
export const FOREIGN_NETWORK_NAME: string = process.env.REACT_APP_ALM_FOREIGN_NETWORK_NAME || ''
|
|
||||||
|
|
||||||
export const HOME_EXPLORER_TX_TEMPLATE: string = process.env.REACT_APP_ALM_HOME_EXPLORER_TX_TEMPLATE || ''
|
|
||||||
export const FOREIGN_EXPLORER_TX_TEMPLATE: string = process.env.REACT_APP_ALM_FOREIGN_EXPLORER_TX_TEMPLATE || ''
|
|
||||||
|
|
||||||
export const HOME_EXPLORER_API: string = process.env.REACT_APP_ALM_HOME_EXPLORER_API || ''
|
|
||||||
export const FOREIGN_EXPLORER_API: string = process.env.REACT_APP_ALM_FOREIGN_EXPLORER_API || ''
|
|
||||||
|
|
||||||
export const ALM_HOME_TO_FOREIGN_MANUAL_EXECUTION: boolean =
|
|
||||||
(process.env.REACT_APP_ALM_HOME_TO_FOREIGN_MANUAL_EXECUTION || '').toLowerCase() === 'true'
|
|
||||||
|
|
||||||
export const HOME_RPC_POLLING_INTERVAL: number = 5000
|
|
||||||
export const FOREIGN_RPC_POLLING_INTERVAL: number = 5000
|
|
||||||
export const BLOCK_RANGE: number = 500
|
|
||||||
export const MAX_TX_SEARCH_BLOCK_RANGE: number = 10000
|
|
||||||
|
|
||||||
export const EXECUTE_AFFIRMATION_HASH = 'e7a2c01f'
|
|
||||||
export const SUBMIT_SIGNATURE_HASH = '630cea8e'
|
|
||||||
export const EXECUTE_SIGNATURES_HASH = '3f7658fd'
|
|
||||||
|
|
||||||
export const CACHE_KEY_SUCCESS = 'success-confirmation-validator-'
|
|
||||||
export const CACHE_KEY_FAILED = 'failed-confirmation-validator-'
|
|
||||||
export const CACHE_KEY_EXECUTION_FAILED = 'failed-execution-validator-'
|
|
||||||
|
|
||||||
export const TRANSACTION_STATUS = {
|
|
||||||
SUCCESS_MULTIPLE_MESSAGES: 'SUCCESS_MULTIPLE_MESSAGES',
|
|
||||||
SUCCESS_ONE_MESSAGE: 'SUCCESS_ONE_MESSAGE',
|
|
||||||
SUCCESS_NO_MESSAGES: 'SUCCESS_NO_MESSAGES',
|
|
||||||
FAILED: 'FAILED',
|
|
||||||
FOUND: 'FOUND',
|
|
||||||
NOT_FOUND: 'NOT_FOUND',
|
|
||||||
UNDEFINED: 'UNDEFINED'
|
|
||||||
}
|
|
||||||
|
|
||||||
export const CONFIRMATIONS_STATUS = {
|
|
||||||
SUCCESS: 'SUCCESS',
|
|
||||||
SUCCESS_MESSAGE_FAILED: 'SUCCESS_MESSAGE_FAILED',
|
|
||||||
EXECUTION_FAILED: 'EXECUTION_FAILED',
|
|
||||||
EXECUTION_PENDING: 'EXECUTION_PENDING',
|
|
||||||
EXECUTION_WAITING: 'EXECUTION_WAITING',
|
|
||||||
FAILED: 'FAILED',
|
|
||||||
PENDING: 'PENDING',
|
|
||||||
SEARCHING: 'SEARCHING',
|
|
||||||
WAITING_VALIDATORS: 'WAITING_VALIDATORS',
|
|
||||||
WAITING_CHAIN: 'WAITING_CHAIN',
|
|
||||||
UNDEFINED: 'UNDEFINED'
|
|
||||||
}
|
|
||||||
|
|
||||||
export const VALIDATOR_CONFIRMATION_STATUS = {
|
|
||||||
SUCCESS: 'Confirmed',
|
|
||||||
MANUAL: 'Manual',
|
|
||||||
EXECUTION_SUCCESS: 'Executed',
|
|
||||||
FAILED: 'Failed',
|
|
||||||
FAILED_VALID: 'Failed valid',
|
|
||||||
PENDING: 'Pending',
|
|
||||||
WAITING: 'Waiting',
|
|
||||||
NOT_REQUIRED: 'Not required',
|
|
||||||
UNDEFINED: 'UNDEFINED'
|
|
||||||
}
|
|
||||||
|
|
||||||
export const RECENT_AGE = 'Recent'
|
|
||||||
|
|
||||||
export const SEARCHING_TX = 'Searching Transaction...'
|
|
||||||
|
|
||||||
export const INCORRECT_CHAIN_ERROR = `Incorrect chain chosen. Switch to ${FOREIGN_NETWORK_NAME} in the wallet.`
|
|
||||||
|
|
||||||
export const DOUBLE_EXECUTION_ATTEMPT_ERROR = `Your execution transaction has been reverted.
|
|
||||||
However, the execution completed successfully in the transaction sent by a different party.`
|
|
||||||
|
|
||||||
export const EXECUTION_FAILED_ERROR = `Your execution transaction has been reverted.
|
|
||||||
Please, contact the support by messaging on %linkhttps://forum.poa.network/c/support`
|
|
||||||
|
|
||||||
export const EXECUTION_OUT_OF_GAS_ERROR = `Your execution transaction has been reverted due to Out-of-Gas error.
|
|
||||||
Please, resend the transaction and provide more gas to it.`
|
|
||||||
@ -1,76 +0,0 @@
|
|||||||
// %t will be replaced by the time -> x minutes/hours/days ago
|
|
||||||
import { ALM_HOME_TO_FOREIGN_MANUAL_EXECUTION } from './constants'
|
|
||||||
|
|
||||||
export const TRANSACTION_STATUS_DESCRIPTION: { [key: string]: string } = {
|
|
||||||
SUCCESS_MULTIPLE_MESSAGES: 'was initiated %t and contains several bridge messages. Specify one of them:',
|
|
||||||
SUCCESS_ONE_MESSAGE: 'was initiated %t',
|
|
||||||
SUCCESS_NO_MESSAGES:
|
|
||||||
'successfully mined %t but it does not seem to contain any request to the bridge, \nso nothing needs to be confirmed by the validators. \nIf you are sure that the transaction should contain a request to the bridge,\ncontact to the validators by \nmessaging on %linkhttps://forum.poa.network/c/support',
|
|
||||||
FAILED: 'failed %t',
|
|
||||||
NOT_FOUND:
|
|
||||||
'Transaction not found. \n1. Check that the transaction hash is correct. \n2. Wait several blocks for the transaction to be\nmined, gas price affects mining speed.'
|
|
||||||
}
|
|
||||||
|
|
||||||
export const CONFIRMATIONS_STATUS_LABEL: { [key: string]: string } = {
|
|
||||||
SUCCESS: 'Success',
|
|
||||||
SUCCESS_MESSAGE_FAILED: 'Success',
|
|
||||||
FAILED: 'Failed',
|
|
||||||
PENDING: 'Pending',
|
|
||||||
WAITING_VALIDATORS: 'Waiting',
|
|
||||||
SEARCHING: 'Waiting',
|
|
||||||
WAITING_CHAIN: 'Waiting'
|
|
||||||
}
|
|
||||||
|
|
||||||
export const CONFIRMATIONS_STATUS_LABEL_HOME: { [key: string]: string } = {
|
|
||||||
SUCCESS: 'Success',
|
|
||||||
SUCCESS_MESSAGE_FAILED: 'Success',
|
|
||||||
EXECUTION_FAILED: 'Execution failed',
|
|
||||||
EXECUTION_PENDING: 'Execution pending',
|
|
||||||
EXECUTION_WAITING: ALM_HOME_TO_FOREIGN_MANUAL_EXECUTION ? 'Manual execution waiting' : 'Execution waiting',
|
|
||||||
FAILED: 'Confirmation Failed',
|
|
||||||
PENDING: 'Confirmation Pending',
|
|
||||||
WAITING_VALIDATORS: 'Confirmation Waiting',
|
|
||||||
SEARCHING: 'Confirmation Waiting',
|
|
||||||
WAITING_CHAIN: 'Confirmation Waiting'
|
|
||||||
}
|
|
||||||
|
|
||||||
// use %link to identify a link
|
|
||||||
export const CONFIRMATIONS_STATUS_DESCRIPTION: { [key: string]: string } = {
|
|
||||||
SUCCESS: '',
|
|
||||||
SUCCESS_MESSAGE_FAILED:
|
|
||||||
'The specified transaction was included in a block,\nthe validators collected signatures and the cross-chain relay was executed correctly,\nbut the contained message execution failed.\nContact the support of the application you used to produce the transaction for the clarifications.',
|
|
||||||
FAILED:
|
|
||||||
'The specified transaction was included in a block,\nbut confirmations sent by a majority of validators\nfailed. The cross-chain relay request will not be\nprocessed. Contact to the validators by\nmessaging on %linkhttps://forum.poa.network/c/support',
|
|
||||||
PENDING:
|
|
||||||
'The specified transaction was included in a block. A\nmajority of validators sent confirmations which have\nnot yet been added to a block.',
|
|
||||||
WAITING_VALIDATORS:
|
|
||||||
'The specified transaction was included in a block.\nSome validators have sent confirmations, others are\nwaiting for chain finalization.\nCheck status again after a few blocks. If the issue still persists contact to the validators by messaging on %linkhttps://forum.poa.network/c/support',
|
|
||||||
SEARCHING:
|
|
||||||
'The specified transaction was included in a block. The app is looking for confirmations. Either\n1. Validators are waiting for chain finalization before sending their signatures.\n2. Validators are not active.\n3. The bridge was stopped.\nCheck status again after a few blocks. If the issue still persists contact to the validators by messaging on %linkhttps://forum.poa.network/c/support',
|
|
||||||
WAITING_CHAIN:
|
|
||||||
'The specified transaction was included in a block.\nValidators are waiting for chain finalization before\nsending their confirmations.'
|
|
||||||
}
|
|
||||||
|
|
||||||
// use %link to identify a link
|
|
||||||
export const CONFIRMATIONS_STATUS_DESCRIPTION_HOME: { [key: string]: string } = {
|
|
||||||
SUCCESS: '',
|
|
||||||
SUCCESS_MESSAGE_FAILED:
|
|
||||||
'The specified transaction was included in a block,\nthe validators collected signatures and the cross-chain relay was executed correctly,\nbut the contained message execution failed.\nContact the support of the application you used to produce the transaction for the clarifications.',
|
|
||||||
EXECUTION_FAILED:
|
|
||||||
'The specified transaction was included in a block\nand the validators collected signatures. The\n transaction with collected signatures was\nsent but did not succeed. Contact to the validators by messaging\non %linkhttps://forum.poa.network/c/support',
|
|
||||||
EXECUTION_PENDING:
|
|
||||||
'The specified transaction was included in a block\nand the validators collected signatures. The\n transaction with collected signatures was\nsent but is not yet added to a block.',
|
|
||||||
EXECUTION_WAITING: ALM_HOME_TO_FOREIGN_MANUAL_EXECUTION
|
|
||||||
? 'The specified transaction was included in a block\nand the validators collected signatures.\nNow the manual user action is required to complete message execution.\n Please, press the "Execute" button.'
|
|
||||||
: 'The specified transaction was included in a block\nand the validators collected signatures. Either\n1. One of the validators is waiting for chain finalization.\n2. A validator skipped its duty to relay signatures.\n3. The execution transaction is still pending (e.g. due to the gas price spike).\nCheck status again after a few blocks or force execution by pressing the "Execute" button.\nIf the issue still persists contact to the validators by messaging on %linkhttps://forum.poa.network/c/support',
|
|
||||||
FAILED:
|
|
||||||
'The specified transaction was included in a block,\nbut transactions with signatures sent by a majority of\nvalidators failed. The cross-chain relay request will\nnot be processed. Contact to the validators by\nmessaging on %linkhttps://forum.poa.network/c/support',
|
|
||||||
PENDING:
|
|
||||||
'The specified transaction was included in a block.\nA majority of validators sent signatures which have not\nyet been added to a block.',
|
|
||||||
WAITING_VALIDATORS:
|
|
||||||
'The specified transaction was included in a block.\nSome validators have sent signatures, others are\nwaiting for chain finalization.\nCheck status again after a few blocks. If the issue still persists contact to the validators by messaging on %linkhttps://forum.poa.network/c/support',
|
|
||||||
SEARCHING:
|
|
||||||
'The specified transaction was included in a block. The app is looking for confirmations. Either\n1. Validators are waiting for chain finalization before sending their signatures.\n2. Validators are not active.\n3. The bridge was stopped.\nCheck status again after a few blocks. If the issue still persists contact to the validators by messaging on %linkhttps://forum.poa.network/c/support',
|
|
||||||
WAITING_CHAIN:
|
|
||||||
'The specified transaction was included in a block.\nValidators are waiting for chain finalization\nbefore sending their signatures.'
|
|
||||||
}
|
|
||||||
1
alm/src/global.d.ts
vendored
1
alm/src/global.d.ts
vendored
@ -1 +0,0 @@
|
|||||||
declare type Maybe<T> = T | null
|
|
||||||
@ -1,47 +0,0 @@
|
|||||||
import { useEffect, useState } from 'react'
|
|
||||||
import { TransactionReceipt } from 'web3-eth'
|
|
||||||
import { useStateProvider } from '../state/StateProvider'
|
|
||||||
import { Contract } from 'web3-eth-contract'
|
|
||||||
import { getRequiredBlockConfirmations } from '../utils/contract'
|
|
||||||
import { foreignSnapshotProvider, homeSnapshotProvider, SnapshotProvider } from '../services/SnapshotProvider'
|
|
||||||
import Web3 from 'web3'
|
|
||||||
import { FOREIGN_EXPLORER_API, HOME_EXPLORER_API } from '../config/constants'
|
|
||||||
|
|
||||||
export interface UseBlockConfirmationsParams {
|
|
||||||
fromHome: boolean
|
|
||||||
receipt: Maybe<TransactionReceipt>
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useBlockConfirmations = ({ receipt, fromHome }: UseBlockConfirmationsParams) => {
|
|
||||||
const [blockConfirmations, setBlockConfirmations] = useState(0)
|
|
||||||
|
|
||||||
const { home, foreign } = useStateProvider()
|
|
||||||
|
|
||||||
const callRequireBlockConfirmations = async (
|
|
||||||
contract: Contract,
|
|
||||||
receipt: TransactionReceipt,
|
|
||||||
setResult: Function,
|
|
||||||
snapshotProvider: SnapshotProvider,
|
|
||||||
web3: Web3,
|
|
||||||
api: string
|
|
||||||
) => {
|
|
||||||
const result = await getRequiredBlockConfirmations(contract, receipt.blockNumber, snapshotProvider, web3, api)
|
|
||||||
setResult(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(
|
|
||||||
() => {
|
|
||||||
const bridgeContract = fromHome ? home.bridgeContract : foreign.bridgeContract
|
|
||||||
const snapshotProvider = fromHome ? homeSnapshotProvider : foreignSnapshotProvider
|
|
||||||
const web3 = fromHome ? home.web3 : foreign.web3
|
|
||||||
const api = fromHome ? HOME_EXPLORER_API : FOREIGN_EXPLORER_API
|
|
||||||
if (!bridgeContract || !receipt || !web3) return
|
|
||||||
callRequireBlockConfirmations(bridgeContract, receipt, setBlockConfirmations, snapshotProvider, web3, api)
|
|
||||||
},
|
|
||||||
[home.bridgeContract, foreign.bridgeContract, receipt, fromHome, home.web3, foreign.web3]
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
|
||||||
blockConfirmations
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,38 +0,0 @@
|
|||||||
import { useEffect, useState } from 'react'
|
|
||||||
import { HOME_AMB_ABI, FOREIGN_AMB_ABI } from '../abis'
|
|
||||||
import { FOREIGN_BRIDGE_ADDRESS, HOME_BRIDGE_ADDRESS } from '../config/constants'
|
|
||||||
import { Contract } from 'web3-eth-contract'
|
|
||||||
import Web3 from 'web3'
|
|
||||||
|
|
||||||
export interface useBridgeContractsParams {
|
|
||||||
homeWeb3: Web3
|
|
||||||
foreignWeb3: Web3
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useBridgeContracts = ({ homeWeb3, foreignWeb3 }: useBridgeContractsParams) => {
|
|
||||||
const [homeBridge, setHomeBridge] = useState<Maybe<Contract>>(null)
|
|
||||||
const [foreignBridge, setForeignBridge] = useState<Maybe<Contract>>(null)
|
|
||||||
|
|
||||||
useEffect(
|
|
||||||
() => {
|
|
||||||
if (!homeWeb3) return
|
|
||||||
const homeContract = new homeWeb3.eth.Contract(HOME_AMB_ABI, HOME_BRIDGE_ADDRESS)
|
|
||||||
setHomeBridge(homeContract)
|
|
||||||
},
|
|
||||||
[homeWeb3]
|
|
||||||
)
|
|
||||||
|
|
||||||
useEffect(
|
|
||||||
() => {
|
|
||||||
if (!foreignWeb3) return
|
|
||||||
const foreignContract = new foreignWeb3.eth.Contract(FOREIGN_AMB_ABI, FOREIGN_BRIDGE_ADDRESS)
|
|
||||||
setForeignBridge(foreignContract)
|
|
||||||
},
|
|
||||||
[foreignWeb3]
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
|
||||||
homeBridge,
|
|
||||||
foreignBridge
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,68 +0,0 @@
|
|||||||
import { useEffect, useState } from 'react'
|
|
||||||
import { TransactionReceipt } from 'web3-eth'
|
|
||||||
import { useStateProvider } from '../state/StateProvider'
|
|
||||||
import { FOREIGN_EXPLORER_API, HOME_EXPLORER_API } from '../config/constants'
|
|
||||||
import { getClosestBlockByTimestamp } from '../utils/explorer'
|
|
||||||
|
|
||||||
export function useClosestBlock(
|
|
||||||
searchHome: boolean,
|
|
||||||
fromHome: boolean,
|
|
||||||
receipt: Maybe<TransactionReceipt>,
|
|
||||||
timestamp: number
|
|
||||||
) {
|
|
||||||
const { home, foreign } = useStateProvider()
|
|
||||||
const [blockNumber, setBlockNumber] = useState<number | null>(null)
|
|
||||||
|
|
||||||
useEffect(
|
|
||||||
() => {
|
|
||||||
if (!receipt || blockNumber || !timestamp) return
|
|
||||||
|
|
||||||
if (fromHome === searchHome) {
|
|
||||||
setBlockNumber(receipt.blockNumber)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const web3 = searchHome ? home.web3 : foreign.web3
|
|
||||||
if (!web3) return
|
|
||||||
|
|
||||||
const getBlock = async () => {
|
|
||||||
// try to fast-fetch closest block number from the chain explorer
|
|
||||||
try {
|
|
||||||
const api = searchHome ? HOME_EXPLORER_API : FOREIGN_EXPLORER_API
|
|
||||||
setBlockNumber(await getClosestBlockByTimestamp(api, timestamp))
|
|
||||||
return
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
const lastBlock = await web3.eth.getBlock('latest')
|
|
||||||
if (lastBlock.timestamp <= timestamp) {
|
|
||||||
setBlockNumber(lastBlock.number)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const oldBlock = await web3.eth.getBlock(Math.max(lastBlock.number - 10000, 1))
|
|
||||||
const blockDiff = lastBlock.number - oldBlock.number
|
|
||||||
const timeDiff = (lastBlock.timestamp as number) - (oldBlock.timestamp as number)
|
|
||||||
const averageBlockTime = timeDiff / blockDiff
|
|
||||||
let currentBlock = lastBlock
|
|
||||||
|
|
||||||
let prevBlockDiff = Infinity
|
|
||||||
while (true) {
|
|
||||||
const timeDiff = (currentBlock.timestamp as number) - timestamp
|
|
||||||
const blockDiff = Math.ceil(timeDiff / averageBlockTime)
|
|
||||||
if (Math.abs(blockDiff) < 5 || Math.abs(blockDiff) >= Math.abs(prevBlockDiff)) {
|
|
||||||
setBlockNumber(currentBlock.number - blockDiff - 5)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
prevBlockDiff = blockDiff
|
|
||||||
currentBlock = await web3.eth.getBlock(currentBlock.number - blockDiff)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getBlock()
|
|
||||||
},
|
|
||||||
[blockNumber, foreign.web3, fromHome, home.web3, receipt, searchHome, timestamp]
|
|
||||||
)
|
|
||||||
|
|
||||||
return blockNumber
|
|
||||||
}
|
|
||||||
@ -1,444 +0,0 @@
|
|||||||
import { useStateProvider } from '../state/StateProvider'
|
|
||||||
import { TransactionReceipt } from 'web3-eth'
|
|
||||||
import { MessageObject } from '../utils/web3'
|
|
||||||
import { useEffect, useState } from 'react'
|
|
||||||
import { EventData } from 'web3-eth-contract'
|
|
||||||
import {
|
|
||||||
BLOCK_RANGE,
|
|
||||||
CONFIRMATIONS_STATUS,
|
|
||||||
FOREIGN_RPC_POLLING_INTERVAL,
|
|
||||||
HOME_RPC_POLLING_INTERVAL,
|
|
||||||
VALIDATOR_CONFIRMATION_STATUS
|
|
||||||
} from '../config/constants'
|
|
||||||
import { homeBlockNumberProvider, foreignBlockNumberProvider } from '../services/BlockNumberProvider'
|
|
||||||
import { getConfirmationsForTx } from '../utils/getConfirmationsForTx'
|
|
||||||
import { getFinalizationEvent } from '../utils/getFinalizationEvent'
|
|
||||||
import {
|
|
||||||
getValidatorFailedTransactionsForMessage,
|
|
||||||
getExecutionFailedTransactionForMessage,
|
|
||||||
getValidatorPendingTransactionsForMessage,
|
|
||||||
getExecutionPendingTransactionsForMessage,
|
|
||||||
getValidatorSuccessTransactionsForMessage
|
|
||||||
} from '../utils/explorer'
|
|
||||||
|
|
||||||
export interface useMessageConfirmationsParams {
|
|
||||||
message: MessageObject
|
|
||||||
receipt: Maybe<TransactionReceipt>
|
|
||||||
fromHome: boolean
|
|
||||||
homeStartBlock: Maybe<number>
|
|
||||||
foreignStartBlock: Maybe<number>
|
|
||||||
requiredSignatures: number
|
|
||||||
validatorList: string[]
|
|
||||||
targetValidatorList: string[]
|
|
||||||
blockConfirmations: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ConfirmationParam {
|
|
||||||
validator: string
|
|
||||||
status: string
|
|
||||||
txHash: string
|
|
||||||
timestamp: number
|
|
||||||
signature?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ExecutionData {
|
|
||||||
status: string
|
|
||||||
validator: string
|
|
||||||
txHash: string
|
|
||||||
timestamp: number
|
|
||||||
executionResult: boolean
|
|
||||||
blockNumber: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useMessageConfirmations = ({
|
|
||||||
message,
|
|
||||||
receipt,
|
|
||||||
fromHome,
|
|
||||||
homeStartBlock,
|
|
||||||
foreignStartBlock,
|
|
||||||
requiredSignatures,
|
|
||||||
validatorList,
|
|
||||||
targetValidatorList,
|
|
||||||
blockConfirmations
|
|
||||||
}: useMessageConfirmationsParams) => {
|
|
||||||
const { home, foreign } = useStateProvider()
|
|
||||||
const [confirmations, setConfirmations] = useState<ConfirmationParam[]>([])
|
|
||||||
const [status, setStatus] = useState(CONFIRMATIONS_STATUS.UNDEFINED)
|
|
||||||
const [waitingBlocks, setWaitingBlocks] = useState(false)
|
|
||||||
const [waitingBlocksResolved, setWaitingBlocksResolved] = useState(false)
|
|
||||||
const [signatureCollected, setSignatureCollected] = useState(false)
|
|
||||||
const [executionEventsFetched, setExecutionEventsFetched] = useState(false)
|
|
||||||
const [collectedSignaturesEvent, setCollectedSignaturesEvent] = useState<Maybe<EventData>>(null)
|
|
||||||
const [executionData, setExecutionData] = useState<ExecutionData>({
|
|
||||||
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
|
||||||
validator: '',
|
|
||||||
txHash: '',
|
|
||||||
timestamp: 0,
|
|
||||||
executionResult: false,
|
|
||||||
blockNumber: 0
|
|
||||||
})
|
|
||||||
const [waitingBlocksForExecution, setWaitingBlocksForExecution] = useState(false)
|
|
||||||
const [waitingBlocksForExecutionResolved, setWaitingBlocksForExecutionResolved] = useState(false)
|
|
||||||
const [failedConfirmations, setFailedConfirmations] = useState(false)
|
|
||||||
const [failedExecution, setFailedExecution] = useState(false)
|
|
||||||
const [pendingConfirmations, setPendingConfirmations] = useState(false)
|
|
||||||
const [pendingExecution, setPendingExecution] = useState(false)
|
|
||||||
|
|
||||||
const existsConfirmation = (confirmationArray: ConfirmationParam[]) =>
|
|
||||||
confirmationArray.some(
|
|
||||||
c => c.status !== VALIDATOR_CONFIRMATION_STATUS.UNDEFINED && c.status !== VALIDATOR_CONFIRMATION_STATUS.WAITING
|
|
||||||
)
|
|
||||||
|
|
||||||
// start watching blocks at the start
|
|
||||||
useEffect(
|
|
||||||
() => {
|
|
||||||
if (!home.web3 || !foreign.web3) return
|
|
||||||
|
|
||||||
homeBlockNumberProvider.start(home.web3)
|
|
||||||
foreignBlockNumberProvider.start(foreign.web3)
|
|
||||||
},
|
|
||||||
[foreign.web3, home.web3]
|
|
||||||
)
|
|
||||||
|
|
||||||
// Check if the validators are waiting for block confirmations to verify the message
|
|
||||||
useEffect(
|
|
||||||
() => {
|
|
||||||
if (!receipt || !blockConfirmations || waitingBlocksResolved) return
|
|
||||||
|
|
||||||
let timeoutId: number
|
|
||||||
|
|
||||||
const blockProvider = fromHome ? homeBlockNumberProvider : foreignBlockNumberProvider
|
|
||||||
const interval = fromHome ? HOME_RPC_POLLING_INTERVAL : FOREIGN_RPC_POLLING_INTERVAL
|
|
||||||
const targetBlock = receipt.blockNumber + blockConfirmations
|
|
||||||
const validatorsWaiting = validatorList.map(validator => ({
|
|
||||||
validator,
|
|
||||||
status: VALIDATOR_CONFIRMATION_STATUS.WAITING,
|
|
||||||
txHash: '',
|
|
||||||
timestamp: 0
|
|
||||||
}))
|
|
||||||
|
|
||||||
const checkSignaturesWaitingForBLocks = () => {
|
|
||||||
const currentBlock = blockProvider.get()
|
|
||||||
|
|
||||||
if (currentBlock && currentBlock >= targetBlock) {
|
|
||||||
setWaitingBlocksResolved(true)
|
|
||||||
setWaitingBlocks(false)
|
|
||||||
} else if (currentBlock) {
|
|
||||||
setWaitingBlocks(true)
|
|
||||||
setConfirmations(validatorsWaiting)
|
|
||||||
timeoutId = setTimeout(checkSignaturesWaitingForBLocks, interval)
|
|
||||||
} else {
|
|
||||||
timeoutId = setTimeout(checkSignaturesWaitingForBLocks, 500)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
checkSignaturesWaitingForBLocks()
|
|
||||||
|
|
||||||
return () => clearTimeout(timeoutId)
|
|
||||||
},
|
|
||||||
[blockConfirmations, fromHome, receipt, validatorList, waitingBlocksResolved]
|
|
||||||
)
|
|
||||||
|
|
||||||
// The collected signature event is only fetched once the signatures are collected on tx from home to foreign, to calculate if
|
|
||||||
// the execution tx on the foreign network is waiting for block confirmations
|
|
||||||
// This is executed if the message is in Home to Foreign direction only
|
|
||||||
useEffect(
|
|
||||||
() => {
|
|
||||||
if (!fromHome || !receipt || !home.web3 || !home.bridgeContract || !signatureCollected) return
|
|
||||||
|
|
||||||
let timeoutId: number
|
|
||||||
let isCancelled = false
|
|
||||||
|
|
||||||
const messageHash = home.web3.utils.soliditySha3Raw(message.data)
|
|
||||||
const contract = home.bridgeContract
|
|
||||||
|
|
||||||
const getCollectedSignaturesEvent = async (fromBlock: number, toBlock: number) => {
|
|
||||||
const currentBlock = homeBlockNumberProvider.get()
|
|
||||||
|
|
||||||
if (currentBlock) {
|
|
||||||
// prevent errors if the toBlock parameter is bigger than the latest
|
|
||||||
const securedToBlock = toBlock >= currentBlock ? currentBlock : toBlock
|
|
||||||
const events = await contract.getPastEvents('CollectedSignatures', {
|
|
||||||
fromBlock,
|
|
||||||
toBlock: securedToBlock
|
|
||||||
})
|
|
||||||
const event = events.find(e => e.returnValues.messageHash === messageHash)
|
|
||||||
if (event) {
|
|
||||||
setCollectedSignaturesEvent(event)
|
|
||||||
} else if (!isCancelled) {
|
|
||||||
timeoutId = setTimeout(() => getCollectedSignaturesEvent(securedToBlock, securedToBlock + BLOCK_RANGE), 500)
|
|
||||||
}
|
|
||||||
} else if (!isCancelled) {
|
|
||||||
timeoutId = setTimeout(() => getCollectedSignaturesEvent(fromBlock, toBlock), 500)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getCollectedSignaturesEvent(receipt.blockNumber, receipt.blockNumber + BLOCK_RANGE)
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
clearTimeout(timeoutId)
|
|
||||||
isCancelled = true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[fromHome, home.bridgeContract, home.web3, message.data, receipt, signatureCollected]
|
|
||||||
)
|
|
||||||
|
|
||||||
// Check if the responsible validator is waiting for block confirmations to execute the message on foreign network
|
|
||||||
// This is executed if the message is in Home to Foreign direction only
|
|
||||||
useEffect(
|
|
||||||
() => {
|
|
||||||
if (!fromHome || !home.web3 || !collectedSignaturesEvent || !blockConfirmations) return
|
|
||||||
if (waitingBlocksForExecutionResolved) return
|
|
||||||
|
|
||||||
let timeoutId: number
|
|
||||||
|
|
||||||
const targetBlock = collectedSignaturesEvent.blockNumber + blockConfirmations
|
|
||||||
|
|
||||||
const checkWaitingBlocksForExecution = () => {
|
|
||||||
const currentBlock = homeBlockNumberProvider.get()
|
|
||||||
|
|
||||||
if (currentBlock && currentBlock >= targetBlock) {
|
|
||||||
const undefinedExecutionState = {
|
|
||||||
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
|
||||||
validator: collectedSignaturesEvent.returnValues.authorityResponsibleForRelay,
|
|
||||||
txHash: '',
|
|
||||||
timestamp: 0,
|
|
||||||
executionResult: false
|
|
||||||
}
|
|
||||||
setExecutionData(
|
|
||||||
(data: any) =>
|
|
||||||
data.status === VALIDATOR_CONFIRMATION_STATUS.UNDEFINED ||
|
|
||||||
data.status === VALIDATOR_CONFIRMATION_STATUS.WAITING
|
|
||||||
? undefinedExecutionState
|
|
||||||
: data
|
|
||||||
)
|
|
||||||
setWaitingBlocksForExecutionResolved(true)
|
|
||||||
setWaitingBlocksForExecution(false)
|
|
||||||
} else if (currentBlock) {
|
|
||||||
setWaitingBlocksForExecution(true)
|
|
||||||
const waitingExecutionState = {
|
|
||||||
status: VALIDATOR_CONFIRMATION_STATUS.WAITING,
|
|
||||||
validator: collectedSignaturesEvent.returnValues.authorityResponsibleForRelay,
|
|
||||||
txHash: '',
|
|
||||||
timestamp: 0,
|
|
||||||
executionResult: false
|
|
||||||
}
|
|
||||||
setExecutionData(
|
|
||||||
(data: any) =>
|
|
||||||
data.status === VALIDATOR_CONFIRMATION_STATUS.UNDEFINED ||
|
|
||||||
data.status === VALIDATOR_CONFIRMATION_STATUS.WAITING
|
|
||||||
? waitingExecutionState
|
|
||||||
: data
|
|
||||||
)
|
|
||||||
timeoutId = setTimeout(() => checkWaitingBlocksForExecution(), HOME_RPC_POLLING_INTERVAL)
|
|
||||||
} else {
|
|
||||||
timeoutId = setTimeout(() => checkWaitingBlocksForExecution(), 500)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
checkWaitingBlocksForExecution()
|
|
||||||
|
|
||||||
return () => clearTimeout(timeoutId)
|
|
||||||
},
|
|
||||||
[collectedSignaturesEvent, fromHome, blockConfirmations, home.web3, waitingBlocksForExecutionResolved]
|
|
||||||
)
|
|
||||||
|
|
||||||
// Checks if validators verified the message
|
|
||||||
// To avoid making extra requests, this is only executed when validators finished waiting for blocks confirmations
|
|
||||||
useEffect(
|
|
||||||
() => {
|
|
||||||
if (!waitingBlocksResolved || !homeStartBlock || !requiredSignatures || !home.web3 || !home.bridgeContract) return
|
|
||||||
if (!validatorList || !validatorList.length) return
|
|
||||||
|
|
||||||
let timeoutId: number
|
|
||||||
let isCancelled = false
|
|
||||||
|
|
||||||
if (fromHome) {
|
|
||||||
if (!targetValidatorList || !targetValidatorList.length) return
|
|
||||||
const msgHash = home.web3.utils.sha3(message.data)!
|
|
||||||
const allValidators = [...validatorList, ...targetValidatorList].filter((v, i, s) => s.indexOf(v) === i)
|
|
||||||
const manualConfirmations = []
|
|
||||||
for (let i = 0; i < allValidators.length; i++) {
|
|
||||||
try {
|
|
||||||
const overrideSignatures: {
|
|
||||||
[key: string]: string
|
|
||||||
} = require(`../snapshots/signatures_${allValidators[i]}.json`)
|
|
||||||
if (overrideSignatures[msgHash]) {
|
|
||||||
console.log(`Adding manual signature from ${allValidators[i]}`)
|
|
||||||
manualConfirmations.push({
|
|
||||||
status: VALIDATOR_CONFIRMATION_STATUS.MANUAL,
|
|
||||||
validator: allValidators[i],
|
|
||||||
timestamp: 0,
|
|
||||||
txHash: '',
|
|
||||||
signature: overrideSignatures[msgHash]
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
console.log(`No manual signature from ${allValidators[i]} was found`)
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.log(`Signatures overrides are not present for ${allValidators[i]}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setConfirmations(manualConfirmations)
|
|
||||||
}
|
|
||||||
|
|
||||||
getConfirmationsForTx(
|
|
||||||
message.data,
|
|
||||||
home.web3,
|
|
||||||
validatorList,
|
|
||||||
home.bridgeContract,
|
|
||||||
fromHome,
|
|
||||||
setConfirmations,
|
|
||||||
requiredSignatures,
|
|
||||||
setSignatureCollected,
|
|
||||||
id => (timeoutId = id),
|
|
||||||
() => isCancelled,
|
|
||||||
homeStartBlock,
|
|
||||||
getValidatorFailedTransactionsForMessage,
|
|
||||||
setFailedConfirmations,
|
|
||||||
getValidatorPendingTransactionsForMessage,
|
|
||||||
setPendingConfirmations,
|
|
||||||
getValidatorSuccessTransactionsForMessage
|
|
||||||
)
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
clearTimeout(timeoutId)
|
|
||||||
isCancelled = true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[
|
|
||||||
fromHome,
|
|
||||||
message.data,
|
|
||||||
home.web3,
|
|
||||||
validatorList,
|
|
||||||
home.bridgeContract,
|
|
||||||
requiredSignatures,
|
|
||||||
waitingBlocksResolved,
|
|
||||||
homeStartBlock,
|
|
||||||
targetValidatorList
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
// Gets finalization event to display the information about the execution of the message
|
|
||||||
// In a message from Home to Foreign it will be executed after finishing waiting for block confirmations for the execution transaction on Foreign
|
|
||||||
// In a message from Foreign to Home it will be executed after finishing waiting for block confirmations of the message request
|
|
||||||
useEffect(
|
|
||||||
() => {
|
|
||||||
if ((fromHome && !waitingBlocksForExecutionResolved) || (!fromHome && !waitingBlocksResolved)) return
|
|
||||||
|
|
||||||
const bridgeContract = fromHome ? foreign.bridgeContract : home.bridgeContract
|
|
||||||
const web3 = fromHome ? foreign.web3 : home.web3
|
|
||||||
const startBlock = fromHome ? foreignStartBlock : homeStartBlock
|
|
||||||
if (!startBlock || !bridgeContract || !web3) return
|
|
||||||
|
|
||||||
let timeoutId: number
|
|
||||||
let isCancelled = false
|
|
||||||
|
|
||||||
getFinalizationEvent(
|
|
||||||
fromHome,
|
|
||||||
bridgeContract,
|
|
||||||
web3,
|
|
||||||
setExecutionData,
|
|
||||||
message,
|
|
||||||
id => (timeoutId = id),
|
|
||||||
() => isCancelled,
|
|
||||||
startBlock,
|
|
||||||
collectedSignaturesEvent,
|
|
||||||
getExecutionFailedTransactionForMessage,
|
|
||||||
setFailedExecution,
|
|
||||||
getExecutionPendingTransactionsForMessage,
|
|
||||||
setPendingExecution,
|
|
||||||
setExecutionEventsFetched
|
|
||||||
)
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
clearTimeout(timeoutId)
|
|
||||||
isCancelled = true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[
|
|
||||||
fromHome,
|
|
||||||
foreign.bridgeContract,
|
|
||||||
home.bridgeContract,
|
|
||||||
message,
|
|
||||||
foreign.web3,
|
|
||||||
home.web3,
|
|
||||||
waitingBlocksResolved,
|
|
||||||
waitingBlocksForExecutionResolved,
|
|
||||||
collectedSignaturesEvent,
|
|
||||||
foreignStartBlock,
|
|
||||||
homeStartBlock
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
// Sets the message status based in the collected information
|
|
||||||
useEffect(
|
|
||||||
() => {
|
|
||||||
if (
|
|
||||||
executionData.status === VALIDATOR_CONFIRMATION_STATUS.EXECUTION_SUCCESS &&
|
|
||||||
existsConfirmation(confirmations)
|
|
||||||
) {
|
|
||||||
const newStatus = executionData.executionResult
|
|
||||||
? CONFIRMATIONS_STATUS.SUCCESS
|
|
||||||
: CONFIRMATIONS_STATUS.SUCCESS_MESSAGE_FAILED
|
|
||||||
setStatus(newStatus)
|
|
||||||
|
|
||||||
foreignBlockNumberProvider.stop()
|
|
||||||
homeBlockNumberProvider.stop()
|
|
||||||
} else if (signatureCollected) {
|
|
||||||
if (fromHome) {
|
|
||||||
if (waitingBlocksForExecution) {
|
|
||||||
setStatus(CONFIRMATIONS_STATUS.EXECUTION_WAITING)
|
|
||||||
} else if (failedExecution) {
|
|
||||||
setStatus(CONFIRMATIONS_STATUS.EXECUTION_FAILED)
|
|
||||||
} else if (pendingExecution) {
|
|
||||||
setStatus(CONFIRMATIONS_STATUS.EXECUTION_PENDING)
|
|
||||||
} else if (waitingBlocksForExecutionResolved) {
|
|
||||||
setStatus(CONFIRMATIONS_STATUS.EXECUTION_WAITING)
|
|
||||||
} else {
|
|
||||||
setStatus(CONFIRMATIONS_STATUS.EXECUTION_WAITING)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
setStatus(CONFIRMATIONS_STATUS.UNDEFINED)
|
|
||||||
}
|
|
||||||
} else if (waitingBlocks) {
|
|
||||||
setStatus(CONFIRMATIONS_STATUS.WAITING_CHAIN)
|
|
||||||
} else if (failedConfirmations) {
|
|
||||||
setStatus(CONFIRMATIONS_STATUS.FAILED)
|
|
||||||
} else if (pendingConfirmations) {
|
|
||||||
setStatus(CONFIRMATIONS_STATUS.PENDING)
|
|
||||||
} else if (waitingBlocksResolved && existsConfirmation(confirmations)) {
|
|
||||||
setStatus(CONFIRMATIONS_STATUS.WAITING_VALIDATORS)
|
|
||||||
} else if (waitingBlocksResolved) {
|
|
||||||
setStatus(CONFIRMATIONS_STATUS.SEARCHING)
|
|
||||||
} else {
|
|
||||||
setStatus(CONFIRMATIONS_STATUS.UNDEFINED)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[
|
|
||||||
executionData,
|
|
||||||
fromHome,
|
|
||||||
signatureCollected,
|
|
||||||
waitingBlocks,
|
|
||||||
waitingBlocksForExecution,
|
|
||||||
failedConfirmations,
|
|
||||||
failedExecution,
|
|
||||||
pendingConfirmations,
|
|
||||||
pendingExecution,
|
|
||||||
waitingBlocksResolved,
|
|
||||||
confirmations,
|
|
||||||
waitingBlocksForExecutionResolved
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
|
||||||
confirmations,
|
|
||||||
status,
|
|
||||||
signatureCollected,
|
|
||||||
executionData,
|
|
||||||
setExecutionData,
|
|
||||||
waitingBlocksResolved,
|
|
||||||
executionEventsFetched,
|
|
||||||
setPendingExecution
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,28 +0,0 @@
|
|||||||
import { useEffect, useState } from 'react'
|
|
||||||
import { getChainId, getWeb3 } from '../utils/web3'
|
|
||||||
import { SnapshotProvider } from '../services/SnapshotProvider'
|
|
||||||
|
|
||||||
export const useNetwork = (url: string, snapshotProvider: SnapshotProvider) => {
|
|
||||||
const [loading, setLoading] = useState(true)
|
|
||||||
const [chainId, setChainId] = useState(0)
|
|
||||||
const web3 = getWeb3(url)
|
|
||||||
|
|
||||||
useEffect(
|
|
||||||
() => {
|
|
||||||
setLoading(true)
|
|
||||||
const getWeb3ChainId = async () => {
|
|
||||||
const id = await getChainId(web3, snapshotProvider)
|
|
||||||
setChainId(id)
|
|
||||||
setLoading(false)
|
|
||||||
}
|
|
||||||
getWeb3ChainId()
|
|
||||||
},
|
|
||||||
[web3, snapshotProvider]
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
|
||||||
web3,
|
|
||||||
chainId,
|
|
||||||
loading
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,39 +0,0 @@
|
|||||||
import { useEffect, useState } from 'react'
|
|
||||||
import { TransactionReceipt } from 'web3-eth'
|
|
||||||
import { HOME_RPC_POLLING_INTERVAL, TRANSACTION_STATUS } from '../config/constants'
|
|
||||||
import Web3 from 'web3'
|
|
||||||
|
|
||||||
export const useTransactionFinder = ({ txHash, web3 }: { txHash: string; web3: Maybe<Web3> }) => {
|
|
||||||
const [status, setStatus] = useState(TRANSACTION_STATUS.UNDEFINED)
|
|
||||||
const [receipt, setReceipt] = useState<Maybe<TransactionReceipt>>(null)
|
|
||||||
|
|
||||||
useEffect(
|
|
||||||
() => {
|
|
||||||
if (!txHash || !web3) return
|
|
||||||
|
|
||||||
let timeoutId: number
|
|
||||||
|
|
||||||
const getReceipt = async () => {
|
|
||||||
const txReceipt = await web3.eth.getTransactionReceipt(txHash)
|
|
||||||
setReceipt(txReceipt)
|
|
||||||
|
|
||||||
if (!txReceipt) {
|
|
||||||
setStatus(TRANSACTION_STATUS.NOT_FOUND)
|
|
||||||
timeoutId = setTimeout(getReceipt, HOME_RPC_POLLING_INTERVAL)
|
|
||||||
} else {
|
|
||||||
setStatus(TRANSACTION_STATUS.FOUND)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getReceipt()
|
|
||||||
|
|
||||||
return () => clearTimeout(timeoutId)
|
|
||||||
},
|
|
||||||
[txHash, web3]
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
|
||||||
status,
|
|
||||||
receipt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,120 +0,0 @@
|
|||||||
import { useEffect, useState } from 'react'
|
|
||||||
import { TransactionReceipt } from 'web3-eth'
|
|
||||||
import { HOME_RPC_POLLING_INTERVAL, TRANSACTION_STATUS } from '../config/constants'
|
|
||||||
import { getTransactionStatusDescription } from '../utils/networks'
|
|
||||||
import { useStateProvider } from '../state/StateProvider'
|
|
||||||
import { getHomeMessagesFromReceipt, getForeignMessagesFromReceipt, MessageObject, getBlock } from '../utils/web3'
|
|
||||||
import useInterval from '@use-it/interval'
|
|
||||||
|
|
||||||
export const useTransactionStatus = ({
|
|
||||||
txHash,
|
|
||||||
chainId,
|
|
||||||
receiptParam
|
|
||||||
}: {
|
|
||||||
txHash: string
|
|
||||||
chainId: number
|
|
||||||
receiptParam: Maybe<TransactionReceipt>
|
|
||||||
}) => {
|
|
||||||
const { home, foreign } = useStateProvider()
|
|
||||||
const [messages, setMessages] = useState<Array<MessageObject>>([])
|
|
||||||
const [status, setStatus] = useState('')
|
|
||||||
const [description, setDescription] = useState('')
|
|
||||||
const [receipt, setReceipt] = useState<Maybe<TransactionReceipt>>(null)
|
|
||||||
const [timestamp, setTimestamp] = useState(0)
|
|
||||||
const [loading, setLoading] = useState(true)
|
|
||||||
|
|
||||||
// Update description so the time displayed is accurate
|
|
||||||
useInterval(() => {
|
|
||||||
if (!status || !timestamp || !description) return
|
|
||||||
setDescription(getTransactionStatusDescription(status, timestamp))
|
|
||||||
}, 30000)
|
|
||||||
|
|
||||||
useEffect(
|
|
||||||
() => {
|
|
||||||
if (!chainId || !txHash || !home.chainId || !foreign.chainId || !home.web3 || !foreign.web3) return
|
|
||||||
const isHome = chainId === home.chainId
|
|
||||||
const web3 = isHome ? home.web3 : foreign.web3
|
|
||||||
|
|
||||||
let timeoutId: number
|
|
||||||
|
|
||||||
const getReceipt = async () => {
|
|
||||||
setLoading(true)
|
|
||||||
|
|
||||||
let txReceipt
|
|
||||||
|
|
||||||
if (receiptParam) {
|
|
||||||
txReceipt = receiptParam
|
|
||||||
} else {
|
|
||||||
txReceipt = await web3.eth.getTransactionReceipt(txHash)
|
|
||||||
}
|
|
||||||
|
|
||||||
setReceipt(txReceipt)
|
|
||||||
|
|
||||||
if (!txReceipt) {
|
|
||||||
setStatus(TRANSACTION_STATUS.NOT_FOUND)
|
|
||||||
setDescription(getTransactionStatusDescription(TRANSACTION_STATUS.NOT_FOUND))
|
|
||||||
setMessages([{ id: txHash, data: '' }])
|
|
||||||
timeoutId = setTimeout(() => getReceipt(), HOME_RPC_POLLING_INTERVAL)
|
|
||||||
} else {
|
|
||||||
const blockNumber = txReceipt.blockNumber
|
|
||||||
const block = await getBlock(web3, blockNumber)
|
|
||||||
const blockTimestamp = typeof block.timestamp === 'string' ? parseInt(block.timestamp) : block.timestamp
|
|
||||||
setTimestamp(blockTimestamp)
|
|
||||||
|
|
||||||
if (txReceipt.status) {
|
|
||||||
let bridgeMessages: Array<MessageObject>
|
|
||||||
if (isHome) {
|
|
||||||
bridgeMessages = getHomeMessagesFromReceipt(txReceipt, web3, home.bridgeAddress)
|
|
||||||
} else {
|
|
||||||
bridgeMessages = getForeignMessagesFromReceipt(txReceipt, web3, foreign.bridgeAddress)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bridgeMessages.length === 0) {
|
|
||||||
setMessages([{ id: txHash, data: '' }])
|
|
||||||
setStatus(TRANSACTION_STATUS.SUCCESS_NO_MESSAGES)
|
|
||||||
setDescription(getTransactionStatusDescription(TRANSACTION_STATUS.SUCCESS_NO_MESSAGES, blockTimestamp))
|
|
||||||
} else if (bridgeMessages.length === 1) {
|
|
||||||
setMessages(bridgeMessages)
|
|
||||||
setStatus(TRANSACTION_STATUS.SUCCESS_ONE_MESSAGE)
|
|
||||||
setDescription(getTransactionStatusDescription(TRANSACTION_STATUS.SUCCESS_ONE_MESSAGE, blockTimestamp))
|
|
||||||
} else {
|
|
||||||
setMessages(bridgeMessages)
|
|
||||||
setStatus(TRANSACTION_STATUS.SUCCESS_MULTIPLE_MESSAGES)
|
|
||||||
setDescription(
|
|
||||||
getTransactionStatusDescription(TRANSACTION_STATUS.SUCCESS_MULTIPLE_MESSAGES, blockTimestamp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
setStatus(TRANSACTION_STATUS.FAILED)
|
|
||||||
setDescription(getTransactionStatusDescription(TRANSACTION_STATUS.FAILED, blockTimestamp))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setLoading(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
getReceipt()
|
|
||||||
|
|
||||||
return () => clearTimeout(timeoutId)
|
|
||||||
},
|
|
||||||
[
|
|
||||||
txHash,
|
|
||||||
chainId,
|
|
||||||
home.chainId,
|
|
||||||
foreign.chainId,
|
|
||||||
home.web3,
|
|
||||||
foreign.web3,
|
|
||||||
home.bridgeAddress,
|
|
||||||
foreign.bridgeAddress,
|
|
||||||
receiptParam
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
|
||||||
messages,
|
|
||||||
status,
|
|
||||||
description,
|
|
||||||
receipt,
|
|
||||||
timestamp,
|
|
||||||
loading
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,76 +0,0 @@
|
|||||||
import { useEffect, useState } from 'react'
|
|
||||||
import { Contract } from 'web3-eth-contract'
|
|
||||||
import Web3 from 'web3'
|
|
||||||
import { getRequiredSignatures, getValidatorAddress, getValidatorList } from '../utils/contract'
|
|
||||||
import { BRIDGE_VALIDATORS_ABI } from '../abis'
|
|
||||||
import { useStateProvider } from '../state/StateProvider'
|
|
||||||
import { foreignSnapshotProvider, homeSnapshotProvider, SnapshotProvider } from '../services/SnapshotProvider'
|
|
||||||
import { FOREIGN_EXPLORER_API, HOME_EXPLORER_API } from '../config/constants'
|
|
||||||
|
|
||||||
export const useValidatorContract = (isHome: boolean, blockNumber: number | 'latest') => {
|
|
||||||
const [validatorContract, setValidatorContract] = useState<Maybe<Contract>>(null)
|
|
||||||
const [requiredSignatures, setRequiredSignatures] = useState(0)
|
|
||||||
const [validatorList, setValidatorList] = useState<string[]>([])
|
|
||||||
|
|
||||||
const { home, foreign } = useStateProvider()
|
|
||||||
|
|
||||||
const callValidatorContract = async (bridgeContract: Maybe<Contract>, web3: Web3, setValidatorContract: Function) => {
|
|
||||||
if (!web3 || !bridgeContract) return
|
|
||||||
const address = await getValidatorAddress(bridgeContract)
|
|
||||||
const contract = new web3.eth.Contract(BRIDGE_VALIDATORS_ABI, address)
|
|
||||||
setValidatorContract(contract)
|
|
||||||
}
|
|
||||||
|
|
||||||
const callRequiredSignatures = async (
|
|
||||||
contract: Maybe<Contract>,
|
|
||||||
blockNumber: number | 'latest',
|
|
||||||
setResult: Function,
|
|
||||||
snapshotProvider: SnapshotProvider,
|
|
||||||
web3: Web3,
|
|
||||||
api: string
|
|
||||||
) => {
|
|
||||||
if (!contract) return
|
|
||||||
const result = await getRequiredSignatures(contract, blockNumber, snapshotProvider, web3, api)
|
|
||||||
setResult(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
const callValidatorList = async (
|
|
||||||
contract: Maybe<Contract>,
|
|
||||||
blockNumber: number | 'latest',
|
|
||||||
setResult: Function,
|
|
||||||
snapshotProvider: SnapshotProvider,
|
|
||||||
web3: Web3,
|
|
||||||
api: string
|
|
||||||
) => {
|
|
||||||
if (!contract) return
|
|
||||||
const result = await getValidatorList(contract, blockNumber, snapshotProvider, web3, api)
|
|
||||||
setResult(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
const web3 = isHome ? home.web3 : foreign.web3
|
|
||||||
const api = isHome ? HOME_EXPLORER_API : FOREIGN_EXPLORER_API
|
|
||||||
const bridgeContract = isHome ? home.bridgeContract : foreign.bridgeContract
|
|
||||||
const snapshotProvider = isHome ? homeSnapshotProvider : foreignSnapshotProvider
|
|
||||||
|
|
||||||
useEffect(
|
|
||||||
() => {
|
|
||||||
if (!web3 || !bridgeContract) return
|
|
||||||
callValidatorContract(bridgeContract, web3, setValidatorContract)
|
|
||||||
},
|
|
||||||
[web3, bridgeContract]
|
|
||||||
)
|
|
||||||
|
|
||||||
useEffect(
|
|
||||||
() => {
|
|
||||||
if (!web3 || !blockNumber) return
|
|
||||||
callRequiredSignatures(validatorContract, blockNumber, setRequiredSignatures, snapshotProvider, web3, api)
|
|
||||||
callValidatorList(validatorContract, blockNumber, setValidatorList, snapshotProvider, web3, api)
|
|
||||||
},
|
|
||||||
[validatorContract, blockNumber, web3, snapshotProvider, api]
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
|
||||||
requiredSignatures,
|
|
||||||
validatorList
|
|
||||||
}
|
|
||||||
}
|
|
||||||
8
alm/src/index.css
Normal file
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;
|
||||||
|
}
|
||||||
@ -1,16 +1,11 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import ReactDOM from 'react-dom'
|
import ReactDOM from 'react-dom'
|
||||||
import { ThemeProvider } from 'styled-components'
|
import './index.css'
|
||||||
import { GlobalStyle } from './themes/GlobalStyle'
|
|
||||||
import App from './App'
|
import App from './App'
|
||||||
import Light from './themes/Light'
|
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<ThemeProvider theme={Light}>
|
<App />
|
||||||
<GlobalStyle />
|
|
||||||
<App />
|
|
||||||
</ThemeProvider>
|
|
||||||
</React.StrictMode>,
|
</React.StrictMode>,
|
||||||
document.getElementById('root')
|
document.getElementById('root')
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,64 +0,0 @@
|
|||||||
import Web3 from 'web3'
|
|
||||||
import differenceInMilliseconds from 'date-fns/differenceInMilliseconds'
|
|
||||||
import { FOREIGN_RPC_POLLING_INTERVAL, HOME_RPC_POLLING_INTERVAL } from '../config/constants'
|
|
||||||
|
|
||||||
export class BlockNumberProvider {
|
|
||||||
private running: number
|
|
||||||
private web3: Maybe<Web3>
|
|
||||||
private ref: number | undefined
|
|
||||||
private value: Maybe<number>
|
|
||||||
private lastValueTimestamp: Maybe<Date>
|
|
||||||
private readonly interval: number
|
|
||||||
|
|
||||||
constructor(interval = 5000) {
|
|
||||||
this.running = 0
|
|
||||||
this.web3 = null
|
|
||||||
this.ref = undefined
|
|
||||||
this.value = null
|
|
||||||
this.lastValueTimestamp = null
|
|
||||||
this.interval = interval
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
start(web3: Maybe<Web3>) {
|
|
||||||
if (!this.running) {
|
|
||||||
clearTimeout(this.ref)
|
|
||||||
this.web3 = web3
|
|
||||||
this.running = this.running + 1
|
|
||||||
this.fetchLastBlock()
|
|
||||||
} else {
|
|
||||||
this.running = this.running + 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stop() {
|
|
||||||
this.running = this.running > 0 ? this.running - 1 : 0
|
|
||||||
|
|
||||||
if (!this.running) {
|
|
||||||
clearTimeout(this.ref)
|
|
||||||
this.ref = undefined
|
|
||||||
this.web3 = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get() {
|
|
||||||
return this.value
|
|
||||||
}
|
|
||||||
|
|
||||||
private async fetchLastBlock() {
|
|
||||||
if (!this.web3) return
|
|
||||||
const now = new Date()
|
|
||||||
const distance = differenceInMilliseconds(now, this.lastValueTimestamp || 0)
|
|
||||||
|
|
||||||
if (distance >= this.interval) {
|
|
||||||
this.value = await this.web3.eth.getBlockNumber()
|
|
||||||
this.lastValueTimestamp = now
|
|
||||||
}
|
|
||||||
|
|
||||||
this.ref = setTimeout(() => this.fetchLastBlock(), this.interval)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const homeBlockNumberProvider = new BlockNumberProvider(HOME_RPC_POLLING_INTERVAL)
|
|
||||||
export const foreignBlockNumberProvider = new BlockNumberProvider(FOREIGN_RPC_POLLING_INTERVAL)
|
|
||||||
@ -1,69 +0,0 @@
|
|||||||
const initialValue = {
|
|
||||||
chainId: 0,
|
|
||||||
RequiredBlockConfirmationChanged: [],
|
|
||||||
RequiredSignaturesChanged: [],
|
|
||||||
ValidatorAdded: [],
|
|
||||||
ValidatorRemoved: [],
|
|
||||||
snapshotBlockNumber: 0
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SnapshotEvent {
|
|
||||||
blockNumber: number
|
|
||||||
returnValues: any
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SnapshotValidatorEvent {
|
|
||||||
blockNumber: number
|
|
||||||
returnValues: any
|
|
||||||
event: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Snapshot {
|
|
||||||
chainId: number
|
|
||||||
RequiredBlockConfirmationChanged: SnapshotEvent[]
|
|
||||||
RequiredSignaturesChanged: SnapshotEvent[]
|
|
||||||
ValidatorAdded: SnapshotValidatorEvent[]
|
|
||||||
ValidatorRemoved: SnapshotValidatorEvent[]
|
|
||||||
snapshotBlockNumber: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export class SnapshotProvider {
|
|
||||||
private data: Snapshot
|
|
||||||
|
|
||||||
constructor(side: string) {
|
|
||||||
let data = initialValue
|
|
||||||
try {
|
|
||||||
data = require(`../snapshots/${side}.json`)
|
|
||||||
} catch (e) {
|
|
||||||
console.log('Snapshot not found')
|
|
||||||
}
|
|
||||||
this.data = data
|
|
||||||
}
|
|
||||||
|
|
||||||
chainId() {
|
|
||||||
return this.data.chainId
|
|
||||||
}
|
|
||||||
|
|
||||||
snapshotBlockNumber() {
|
|
||||||
return this.data.snapshotBlockNumber
|
|
||||||
}
|
|
||||||
|
|
||||||
requiredBlockConfirmationEvents(toBlock: number) {
|
|
||||||
return this.data.RequiredBlockConfirmationChanged.filter(e => e.blockNumber <= toBlock)
|
|
||||||
}
|
|
||||||
|
|
||||||
requiredSignaturesEvents(toBlock: number) {
|
|
||||||
return this.data.RequiredSignaturesChanged.filter(e => e.blockNumber <= toBlock)
|
|
||||||
}
|
|
||||||
|
|
||||||
validatorAddedEvents(fromBlock: number) {
|
|
||||||
return this.data.ValidatorAdded.filter(e => e.blockNumber >= fromBlock)
|
|
||||||
}
|
|
||||||
|
|
||||||
validatorRemovedEvents(fromBlock: number) {
|
|
||||||
return this.data.ValidatorRemoved.filter(e => e.blockNumber >= fromBlock)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const homeSnapshotProvider = new SnapshotProvider('home')
|
|
||||||
export const foreignSnapshotProvider = new SnapshotProvider('foreign')
|
|
||||||
@ -1,29 +0,0 @@
|
|||||||
import { ConfirmationParam } from '../hooks/useMessageConfirmations'
|
|
||||||
|
|
||||||
class ValidatorsCache {
|
|
||||||
private readonly store: { [key: string]: boolean }
|
|
||||||
private readonly dataStore: { [key: string]: ConfirmationParam }
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.store = {}
|
|
||||||
this.dataStore = {}
|
|
||||||
}
|
|
||||||
|
|
||||||
get(key: string) {
|
|
||||||
return this.store[key]
|
|
||||||
}
|
|
||||||
|
|
||||||
set(key: string, value: boolean) {
|
|
||||||
this.store[key] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
getData(key: string) {
|
|
||||||
return this.dataStore[key]
|
|
||||||
}
|
|
||||||
|
|
||||||
setData(key: string, value: ConfirmationParam) {
|
|
||||||
this.dataStore[key] = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default new ValidatorsCache()
|
|
||||||
@ -1,79 +0,0 @@
|
|||||||
import React, { createContext, ReactNode } from 'react'
|
|
||||||
import { useNetwork } from '../hooks/useNetwork'
|
|
||||||
import {
|
|
||||||
HOME_RPC_URL,
|
|
||||||
FOREIGN_RPC_URL,
|
|
||||||
HOME_BRIDGE_ADDRESS,
|
|
||||||
FOREIGN_BRIDGE_ADDRESS,
|
|
||||||
HOME_NETWORK_NAME,
|
|
||||||
FOREIGN_NETWORK_NAME
|
|
||||||
} from '../config/constants'
|
|
||||||
import Web3 from 'web3'
|
|
||||||
import { useBridgeContracts } from '../hooks/useBridgeContracts'
|
|
||||||
import { Contract } from 'web3-eth-contract'
|
|
||||||
import { foreignSnapshotProvider, homeSnapshotProvider } from '../services/SnapshotProvider'
|
|
||||||
|
|
||||||
export interface BaseNetworkParams {
|
|
||||||
chainId: number
|
|
||||||
name: string
|
|
||||||
web3: Maybe<Web3>
|
|
||||||
bridgeAddress: string
|
|
||||||
bridgeContract: Maybe<Contract>
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface StateContext {
|
|
||||||
home: BaseNetworkParams
|
|
||||||
foreign: BaseNetworkParams
|
|
||||||
loading: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
const initialState = {
|
|
||||||
home: {
|
|
||||||
chainId: 0,
|
|
||||||
name: '',
|
|
||||||
web3: null,
|
|
||||||
bridgeAddress: HOME_BRIDGE_ADDRESS,
|
|
||||||
bridgeContract: null
|
|
||||||
},
|
|
||||||
foreign: {
|
|
||||||
chainId: 0,
|
|
||||||
name: '',
|
|
||||||
web3: null,
|
|
||||||
bridgeAddress: FOREIGN_BRIDGE_ADDRESS,
|
|
||||||
bridgeContract: null
|
|
||||||
},
|
|
||||||
loading: true
|
|
||||||
}
|
|
||||||
|
|
||||||
const StateContext = createContext<StateContext>(initialState)
|
|
||||||
|
|
||||||
export const StateProvider = ({ children }: { children: ReactNode }) => {
|
|
||||||
const homeNetwork = useNetwork(HOME_RPC_URL, homeSnapshotProvider)
|
|
||||||
const foreignNetwork = useNetwork(FOREIGN_RPC_URL, foreignSnapshotProvider)
|
|
||||||
const { homeBridge, foreignBridge } = useBridgeContracts({
|
|
||||||
homeWeb3: homeNetwork.web3,
|
|
||||||
foreignWeb3: foreignNetwork.web3
|
|
||||||
})
|
|
||||||
|
|
||||||
const value = {
|
|
||||||
home: {
|
|
||||||
bridgeAddress: HOME_BRIDGE_ADDRESS,
|
|
||||||
name: HOME_NETWORK_NAME,
|
|
||||||
bridgeContract: homeBridge,
|
|
||||||
...homeNetwork
|
|
||||||
},
|
|
||||||
foreign: {
|
|
||||||
bridgeAddress: FOREIGN_BRIDGE_ADDRESS,
|
|
||||||
name: FOREIGN_NETWORK_NAME,
|
|
||||||
bridgeContract: foreignBridge,
|
|
||||||
...foreignNetwork
|
|
||||||
},
|
|
||||||
loading: homeNetwork.loading || foreignNetwork.loading
|
|
||||||
}
|
|
||||||
|
|
||||||
return <StateContext.Provider value={value}>{children}</StateContext.Provider>
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useStateProvider = (): StateContext => {
|
|
||||||
return React.useContext(StateContext)
|
|
||||||
}
|
|
||||||
@ -1,22 +0,0 @@
|
|||||||
const theme = {
|
|
||||||
backgroundColor: '#121212',
|
|
||||||
fontColor: '#f5f5f5',
|
|
||||||
buttonColor: '#f5f5f5',
|
|
||||||
colorPrimary: '#272727',
|
|
||||||
colorGrey: '#272727',
|
|
||||||
colorLightGrey: '#272727',
|
|
||||||
linkColor: '#ffffff',
|
|
||||||
success: {
|
|
||||||
textColor: '#00c9a7',
|
|
||||||
backgroundColor: '#004d40'
|
|
||||||
},
|
|
||||||
notRequired: {
|
|
||||||
textColor: '#bdbdbd',
|
|
||||||
backgroundColor: '#424242'
|
|
||||||
},
|
|
||||||
failed: {
|
|
||||||
textColor: '#EF5350',
|
|
||||||
backgroundColor: '#4E342E'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export default theme
|
|
||||||
@ -1,34 +0,0 @@
|
|||||||
import { createGlobalStyle } from 'styled-components'
|
|
||||||
|
|
||||||
import theme from './Light'
|
|
||||||
|
|
||||||
type ThemeType = typeof theme
|
|
||||||
|
|
||||||
export const GlobalStyle = createGlobalStyle<{ theme: ThemeType }>`
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
:root {
|
|
||||||
--bg-color: ${props => props.theme.backgroundColor};
|
|
||||||
--font-color: ${props => props.theme.fontColor};
|
|
||||||
--button-color: ${props => props.theme.buttonColor};
|
|
||||||
--color-primary: ${props => props.theme.colorPrimary};
|
|
||||||
--color-grey: ${props => props.theme.colorGrey};
|
|
||||||
--color-lightGrey: ${props => props.theme.colorLightGrey};
|
|
||||||
--link-color: ${props => props.theme.linkColor};
|
|
||||||
--success-color: ${props => props.theme.success.textColor};
|
|
||||||
--success-bg-color: ${props => props.theme.success.backgroundColor};
|
|
||||||
--not-required-color: ${props => props.theme.notRequired.textColor};
|
|
||||||
--not-required-bg-color: ${props => props.theme.notRequired.backgroundColor};
|
|
||||||
--failed-color: ${props => props.theme.failed.textColor};
|
|
||||||
--failed-bg-color: ${props => props.theme.failed.backgroundColor};
|
|
||||||
--warning-color: ${props => props.theme.warning.textColor};
|
|
||||||
--warning-bg-color: ${props => props.theme.warning.backgroundColor};
|
|
||||||
}
|
|
||||||
`
|
|
||||||
@ -1,26 +0,0 @@
|
|||||||
const theme = {
|
|
||||||
backgroundColor: '#FFFFFF',
|
|
||||||
fontColor: 'rgba(0, 0, 0, 0.65)',
|
|
||||||
buttonColor: '#1890ff',
|
|
||||||
colorPrimary: '#BDBDBD',
|
|
||||||
colorGrey: '#1890ff',
|
|
||||||
colorLightGrey: '#1890ff',
|
|
||||||
linkColor: '#1890ff',
|
|
||||||
success: {
|
|
||||||
textColor: '#388E3C',
|
|
||||||
backgroundColor: 'rgba(0,201,167,.1)'
|
|
||||||
},
|
|
||||||
notRequired: {
|
|
||||||
textColor: '#77838f',
|
|
||||||
backgroundColor: 'rgba(119,131,143,.1)'
|
|
||||||
},
|
|
||||||
failed: {
|
|
||||||
textColor: '#de4437',
|
|
||||||
backgroundColor: 'rgba(222,68,55,.1)'
|
|
||||||
},
|
|
||||||
warning: {
|
|
||||||
textColor: '#ffa758',
|
|
||||||
backgroundColor: 'rgba(222,68,55,.1)'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export default theme
|
|
||||||
@ -1,469 +0,0 @@
|
|||||||
import 'jest'
|
|
||||||
import { getRequiredBlockConfirmations, getRequiredSignatures, getValidatorList } from '../contract'
|
|
||||||
import { Contract } from 'web3-eth-contract'
|
|
||||||
import { SnapshotProvider } from '../../services/SnapshotProvider'
|
|
||||||
|
|
||||||
describe('getRequiredBlockConfirmations', () => {
|
|
||||||
const methodsBuilder = (value: string) => ({
|
|
||||||
requiredBlockConfirmations: () => {
|
|
||||||
return {
|
|
||||||
call: () => {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Should call requiredBlockConfirmations method if no events present', async () => {
|
|
||||||
const contract = ({
|
|
||||||
getPastEvents: async () => {
|
|
||||||
return []
|
|
||||||
},
|
|
||||||
methods: methodsBuilder('1')
|
|
||||||
} as unknown) as Contract
|
|
||||||
|
|
||||||
const snapshotProvider = ({
|
|
||||||
requiredBlockConfirmationEvents: () => {
|
|
||||||
return []
|
|
||||||
},
|
|
||||||
snapshotBlockNumber: () => {
|
|
||||||
return 10
|
|
||||||
}
|
|
||||||
} as unknown) as SnapshotProvider
|
|
||||||
|
|
||||||
const result = await getRequiredBlockConfirmations(contract, 10, snapshotProvider)
|
|
||||||
|
|
||||||
expect(result).toEqual(1)
|
|
||||||
})
|
|
||||||
test('Should not call to get events if block number was included in the snapshot', async () => {
|
|
||||||
const contract = ({
|
|
||||||
getPastEvents: jest.fn().mockImplementation(async () => []),
|
|
||||||
methods: methodsBuilder('3')
|
|
||||||
} as unknown) as Contract
|
|
||||||
|
|
||||||
const snapshotProvider = ({
|
|
||||||
requiredBlockConfirmationEvents: () => {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
blockNumber: 8,
|
|
||||||
returnValues: {
|
|
||||||
requiredBlockConfirmations: '1'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
snapshotBlockNumber: () => {
|
|
||||||
return 15
|
|
||||||
}
|
|
||||||
} as unknown) as SnapshotProvider
|
|
||||||
|
|
||||||
const result = await getRequiredBlockConfirmations(contract, 10, snapshotProvider)
|
|
||||||
|
|
||||||
expect(result).toEqual(1)
|
|
||||||
expect(contract.getPastEvents).toBeCalledTimes(0)
|
|
||||||
})
|
|
||||||
test('Should call to get events if block number was not included in the snapshot', async () => {
|
|
||||||
const contract = ({
|
|
||||||
getPastEvents: jest.fn().mockImplementation(async () => [
|
|
||||||
{
|
|
||||||
blockNumber: 9,
|
|
||||||
returnValues: {
|
|
||||||
requiredBlockConfirmations: '2'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]),
|
|
||||||
methods: methodsBuilder('3')
|
|
||||||
} as unknown) as Contract
|
|
||||||
|
|
||||||
const snapshotProvider = ({
|
|
||||||
requiredBlockConfirmationEvents: () => {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
blockNumber: 8,
|
|
||||||
returnValues: {
|
|
||||||
requiredBlockConfirmations: '1'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
snapshotBlockNumber: () => {
|
|
||||||
return 8
|
|
||||||
}
|
|
||||||
} as unknown) as SnapshotProvider
|
|
||||||
|
|
||||||
const result = await getRequiredBlockConfirmations(contract, 10, snapshotProvider)
|
|
||||||
|
|
||||||
expect(result).toEqual(2)
|
|
||||||
expect(contract.getPastEvents).toBeCalledTimes(1)
|
|
||||||
expect(contract.getPastEvents).toHaveBeenCalledWith('RequiredBlockConfirmationChanged', {
|
|
||||||
fromBlock: 9,
|
|
||||||
toBlock: 10
|
|
||||||
})
|
|
||||||
})
|
|
||||||
test('Should use the most updated event', async () => {
|
|
||||||
const contract = ({
|
|
||||||
getPastEvents: jest.fn().mockImplementation(async () => [
|
|
||||||
{
|
|
||||||
blockNumber: 9,
|
|
||||||
returnValues: {
|
|
||||||
requiredBlockConfirmations: '2'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
blockNumber: 11,
|
|
||||||
returnValues: {
|
|
||||||
requiredBlockConfirmations: '3'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]),
|
|
||||||
methods: methodsBuilder('3')
|
|
||||||
} as unknown) as Contract
|
|
||||||
|
|
||||||
const snapshotProvider = ({
|
|
||||||
requiredBlockConfirmationEvents: () => {
|
|
||||||
return []
|
|
||||||
},
|
|
||||||
snapshotBlockNumber: () => {
|
|
||||||
return 11
|
|
||||||
}
|
|
||||||
} as unknown) as SnapshotProvider
|
|
||||||
|
|
||||||
const result = await getRequiredBlockConfirmations(contract, 15, snapshotProvider)
|
|
||||||
|
|
||||||
expect(result).toEqual(3)
|
|
||||||
expect(contract.getPastEvents).toBeCalledTimes(1)
|
|
||||||
expect(contract.getPastEvents).toHaveBeenCalledWith('RequiredBlockConfirmationChanged', {
|
|
||||||
fromBlock: 12,
|
|
||||||
toBlock: 15
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
describe('getRequiredSignatures', () => {
|
|
||||||
test('Should not call to get events if block number was included in the snapshot', async () => {
|
|
||||||
const contract = ({
|
|
||||||
getPastEvents: jest.fn().mockImplementation(async () => [])
|
|
||||||
} as unknown) as Contract
|
|
||||||
|
|
||||||
const snapshotProvider = ({
|
|
||||||
requiredSignaturesEvents: () => {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
blockNumber: 7,
|
|
||||||
returnValues: {
|
|
||||||
requiredSignatures: '1'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
blockNumber: 8,
|
|
||||||
returnValues: {
|
|
||||||
requiredSignatures: '2'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
snapshotBlockNumber: () => {
|
|
||||||
return 10
|
|
||||||
}
|
|
||||||
} as unknown) as SnapshotProvider
|
|
||||||
|
|
||||||
const result = await getRequiredSignatures(contract, 10, snapshotProvider)
|
|
||||||
|
|
||||||
expect(result).toEqual(2)
|
|
||||||
expect(contract.getPastEvents).toBeCalledTimes(0)
|
|
||||||
})
|
|
||||||
test('Should call to get events if block number is higher than the snapshot block number', async () => {
|
|
||||||
const contract = ({
|
|
||||||
getPastEvents: jest.fn().mockImplementation(async () => [
|
|
||||||
{
|
|
||||||
blockNumber: 15,
|
|
||||||
returnValues: {
|
|
||||||
requiredSignatures: '3'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
])
|
|
||||||
} as unknown) as Contract
|
|
||||||
|
|
||||||
const snapshotProvider = ({
|
|
||||||
requiredSignaturesEvents: () => {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
blockNumber: 7,
|
|
||||||
returnValues: {
|
|
||||||
requiredSignatures: '1'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
blockNumber: 8,
|
|
||||||
returnValues: {
|
|
||||||
requiredSignatures: '2'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
snapshotBlockNumber: () => {
|
|
||||||
return 10
|
|
||||||
}
|
|
||||||
} as unknown) as SnapshotProvider
|
|
||||||
|
|
||||||
const result = await getRequiredSignatures(contract, 20, snapshotProvider)
|
|
||||||
|
|
||||||
expect(result).toEqual(3)
|
|
||||||
expect(contract.getPastEvents).toBeCalledTimes(1)
|
|
||||||
expect(contract.getPastEvents).toHaveBeenCalledWith('RequiredSignaturesChanged', {
|
|
||||||
fromBlock: 11,
|
|
||||||
toBlock: 20
|
|
||||||
})
|
|
||||||
})
|
|
||||||
test('Should use the most updated event before the block number', async () => {
|
|
||||||
const contract = ({
|
|
||||||
getPastEvents: jest.fn().mockImplementation(async () => [
|
|
||||||
{
|
|
||||||
blockNumber: 15,
|
|
||||||
returnValues: {
|
|
||||||
requiredSignatures: '4'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
])
|
|
||||||
} as unknown) as Contract
|
|
||||||
|
|
||||||
const snapshotProvider = ({
|
|
||||||
requiredSignaturesEvents: () => {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
blockNumber: 5,
|
|
||||||
returnValues: {
|
|
||||||
requiredSignatures: '1'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
blockNumber: 6,
|
|
||||||
returnValues: {
|
|
||||||
requiredSignatures: '2'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
snapshotBlockNumber: () => {
|
|
||||||
return 10
|
|
||||||
}
|
|
||||||
} as unknown) as SnapshotProvider
|
|
||||||
|
|
||||||
const result = await getRequiredSignatures(contract, 7, snapshotProvider)
|
|
||||||
|
|
||||||
expect(result).toEqual(2)
|
|
||||||
expect(contract.getPastEvents).toBeCalledTimes(0)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
describe('getValidatorList', () => {
|
|
||||||
const validator1 = '0x45b96809336A8b714BFbdAB3E4B5e0fe5d839908'
|
|
||||||
const validator2 = '0xAe8bFfc8BBc6AAa9E21ED1E4e4957fe798BEA25f'
|
|
||||||
const validator3 = '0x285A6eB779be4db94dA65e2F3518B1c5F0f71244'
|
|
||||||
const methodsBuilder = (value: string[]) => ({
|
|
||||||
validatorList: () => {
|
|
||||||
return {
|
|
||||||
call: () => {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
test('Should return the current validator list if no events found', async () => {
|
|
||||||
const currentValidators = [validator1, validator2, validator3]
|
|
||||||
const contract = ({
|
|
||||||
getPastEvents: jest.fn().mockImplementation(async () => []),
|
|
||||||
methods: methodsBuilder(currentValidators)
|
|
||||||
} as unknown) as Contract
|
|
||||||
|
|
||||||
const snapshotProvider = ({
|
|
||||||
validatorAddedEvents: () => {
|
|
||||||
return []
|
|
||||||
},
|
|
||||||
validatorRemovedEvents: () => {
|
|
||||||
return []
|
|
||||||
},
|
|
||||||
snapshotBlockNumber: () => {
|
|
||||||
return 10
|
|
||||||
}
|
|
||||||
} as unknown) as SnapshotProvider
|
|
||||||
|
|
||||||
const list = await getValidatorList(contract, 20, snapshotProvider)
|
|
||||||
|
|
||||||
expect(list.length).toEqual(3)
|
|
||||||
expect(list).toEqual(expect.arrayContaining(currentValidators))
|
|
||||||
expect(contract.getPastEvents).toBeCalledTimes(2)
|
|
||||||
expect(contract.getPastEvents).toHaveBeenCalledWith('ValidatorAdded', {
|
|
||||||
fromBlock: 20
|
|
||||||
})
|
|
||||||
expect(contract.getPastEvents).toHaveBeenCalledWith('ValidatorRemoved', {
|
|
||||||
fromBlock: 20
|
|
||||||
})
|
|
||||||
})
|
|
||||||
test('If validator was added later from snapshot it should not include it', async () => {
|
|
||||||
const currentValidators = [validator1, validator2, validator3]
|
|
||||||
const contract = ({
|
|
||||||
getPastEvents: jest.fn().mockImplementation(async () => []),
|
|
||||||
methods: methodsBuilder(currentValidators)
|
|
||||||
} as unknown) as Contract
|
|
||||||
|
|
||||||
const snapshotProvider = ({
|
|
||||||
validatorAddedEvents: () => {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
blockNumber: 9,
|
|
||||||
returnValues: {
|
|
||||||
validator: validator3
|
|
||||||
},
|
|
||||||
event: 'ValidatorAdded'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
validatorRemovedEvents: () => {
|
|
||||||
return []
|
|
||||||
},
|
|
||||||
snapshotBlockNumber: () => {
|
|
||||||
return 10
|
|
||||||
}
|
|
||||||
} as unknown) as SnapshotProvider
|
|
||||||
|
|
||||||
const list = await getValidatorList(contract, 5, snapshotProvider)
|
|
||||||
|
|
||||||
expect(list.length).toEqual(2)
|
|
||||||
expect(list).toEqual(expect.arrayContaining([validator1, validator2]))
|
|
||||||
expect(contract.getPastEvents).toBeCalledTimes(2)
|
|
||||||
expect(contract.getPastEvents).toHaveBeenCalledWith('ValidatorAdded', {
|
|
||||||
fromBlock: 11
|
|
||||||
})
|
|
||||||
expect(contract.getPastEvents).toHaveBeenCalledWith('ValidatorRemoved', {
|
|
||||||
fromBlock: 11
|
|
||||||
})
|
|
||||||
})
|
|
||||||
test('If validator was added later from chain it should not include it', async () => {
|
|
||||||
const currentValidators = [validator1, validator2, validator3]
|
|
||||||
const contract = ({
|
|
||||||
getPastEvents: jest.fn().mockImplementation(async event => {
|
|
||||||
if (event === 'ValidatorAdded') {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
blockNumber: 9,
|
|
||||||
returnValues: {
|
|
||||||
validator: validator3
|
|
||||||
},
|
|
||||||
event: 'ValidatorAdded'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
} else {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
methods: methodsBuilder(currentValidators)
|
|
||||||
} as unknown) as Contract
|
|
||||||
|
|
||||||
const snapshotProvider = ({
|
|
||||||
validatorAddedEvents: () => {
|
|
||||||
return []
|
|
||||||
},
|
|
||||||
validatorRemovedEvents: () => {
|
|
||||||
return []
|
|
||||||
},
|
|
||||||
snapshotBlockNumber: () => {
|
|
||||||
return 10
|
|
||||||
}
|
|
||||||
} as unknown) as SnapshotProvider
|
|
||||||
|
|
||||||
const list = await getValidatorList(contract, 15, snapshotProvider)
|
|
||||||
|
|
||||||
expect(list.length).toEqual(2)
|
|
||||||
expect(list).toEqual(expect.arrayContaining([validator1, validator2]))
|
|
||||||
expect(contract.getPastEvents).toBeCalledTimes(2)
|
|
||||||
expect(contract.getPastEvents).toHaveBeenCalledWith('ValidatorAdded', {
|
|
||||||
fromBlock: 15
|
|
||||||
})
|
|
||||||
expect(contract.getPastEvents).toHaveBeenCalledWith('ValidatorRemoved', {
|
|
||||||
fromBlock: 15
|
|
||||||
})
|
|
||||||
})
|
|
||||||
test('If validator was removed later from snapshot it should include it', async () => {
|
|
||||||
const currentValidators = [validator1, validator2]
|
|
||||||
const contract = ({
|
|
||||||
getPastEvents: jest.fn().mockImplementation(async () => []),
|
|
||||||
methods: methodsBuilder(currentValidators)
|
|
||||||
} as unknown) as Contract
|
|
||||||
|
|
||||||
const snapshotProvider = ({
|
|
||||||
validatorAddedEvents: () => {
|
|
||||||
return []
|
|
||||||
},
|
|
||||||
validatorRemovedEvents: () => {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
blockNumber: 9,
|
|
||||||
returnValues: {
|
|
||||||
validator: validator3
|
|
||||||
},
|
|
||||||
event: 'ValidatorRemoved'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
snapshotBlockNumber: () => {
|
|
||||||
return 10
|
|
||||||
}
|
|
||||||
} as unknown) as SnapshotProvider
|
|
||||||
|
|
||||||
const list = await getValidatorList(contract, 5, snapshotProvider)
|
|
||||||
|
|
||||||
expect(list.length).toEqual(3)
|
|
||||||
expect(list).toEqual(expect.arrayContaining([validator1, validator2, validator3]))
|
|
||||||
expect(contract.getPastEvents).toBeCalledTimes(2)
|
|
||||||
expect(contract.getPastEvents).toHaveBeenCalledWith('ValidatorAdded', {
|
|
||||||
fromBlock: 11
|
|
||||||
})
|
|
||||||
expect(contract.getPastEvents).toHaveBeenCalledWith('ValidatorRemoved', {
|
|
||||||
fromBlock: 11
|
|
||||||
})
|
|
||||||
})
|
|
||||||
test('If validator was removed later from chain it should include it', async () => {
|
|
||||||
const currentValidators = [validator1, validator2]
|
|
||||||
const contract = ({
|
|
||||||
getPastEvents: jest.fn().mockImplementation(async event => {
|
|
||||||
if (event === 'ValidatorRemoved') {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
blockNumber: 9,
|
|
||||||
returnValues: {
|
|
||||||
validator: validator3
|
|
||||||
},
|
|
||||||
event: 'ValidatorRemoved'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
} else {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
methods: methodsBuilder(currentValidators)
|
|
||||||
} as unknown) as Contract
|
|
||||||
|
|
||||||
const snapshotProvider = ({
|
|
||||||
validatorAddedEvents: () => {
|
|
||||||
return []
|
|
||||||
},
|
|
||||||
validatorRemovedEvents: () => {
|
|
||||||
return []
|
|
||||||
},
|
|
||||||
snapshotBlockNumber: () => {
|
|
||||||
return 10
|
|
||||||
}
|
|
||||||
} as unknown) as SnapshotProvider
|
|
||||||
|
|
||||||
const list = await getValidatorList(contract, 15, snapshotProvider)
|
|
||||||
|
|
||||||
expect(list.length).toEqual(3)
|
|
||||||
expect(list).toEqual(expect.arrayContaining([validator1, validator2, validator3]))
|
|
||||||
expect(contract.getPastEvents).toBeCalledTimes(2)
|
|
||||||
expect(contract.getPastEvents).toHaveBeenCalledWith('ValidatorAdded', {
|
|
||||||
fromBlock: 15
|
|
||||||
})
|
|
||||||
expect(contract.getPastEvents).toHaveBeenCalledWith('ValidatorRemoved', {
|
|
||||||
fromBlock: 15
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@ -1,172 +0,0 @@
|
|||||||
import 'jest'
|
|
||||||
import {
|
|
||||||
getFailedTransactions,
|
|
||||||
getSuccessTransactions,
|
|
||||||
filterValidatorSignatureTransaction,
|
|
||||||
getExecutionFailedTransactionForMessage,
|
|
||||||
APITransaction,
|
|
||||||
getValidatorPendingTransactionsForMessage,
|
|
||||||
getExecutionPendingTransactionsForMessage
|
|
||||||
} from '../explorer'
|
|
||||||
import { EXECUTE_AFFIRMATION_HASH, EXECUTE_SIGNATURES_HASH, SUBMIT_SIGNATURE_HASH } from '../../config/constants'
|
|
||||||
|
|
||||||
const messageData = '0x123456'
|
|
||||||
const OTHER_HASH = 'aabbccdd'
|
|
||||||
const bridgeAddress = '0xFe446bEF1DbF7AFE24E81e05BC8B271C1BA9a560'
|
|
||||||
const otherAddress = '0xD4075FB57fCf038bFc702c915Ef9592534bED5c1'
|
|
||||||
const validator1 = '0x45b96809336A8b714BFbdAB3E4B5e0fe5d839908'
|
|
||||||
const validator2 = '0xAe8bFfc8BBc6AAa9E21ED1E4e4957fe798BEA25f'
|
|
||||||
const validator3 = '0x285A6eB779be4db94dA65e2F3518B1c5F0f71244'
|
|
||||||
|
|
||||||
describe('getFailedTransactions', () => {
|
|
||||||
test('should only return failed transactions', async () => {
|
|
||||||
const to = otherAddress
|
|
||||||
const transactions = [
|
|
||||||
{ isError: '0', to, from: validator1 },
|
|
||||||
{ isError: '1', to, from: validator1 },
|
|
||||||
{ isError: '0', to, from: validator2 },
|
|
||||||
{ isError: '1', to, from: validator2 },
|
|
||||||
{ isError: '1', to, from: validator3 }
|
|
||||||
]
|
|
||||||
|
|
||||||
const fetchAccountTransactions = jest.fn().mockImplementation(() => transactions)
|
|
||||||
const result = await getFailedTransactions(validator1, to, 0, 1, '', fetchAccountTransactions)
|
|
||||||
expect(result.length).toEqual(1)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
describe('getSuccessTransactions', () => {
|
|
||||||
test('should only return success transactions', async () => {
|
|
||||||
const to = otherAddress
|
|
||||||
const transactions = [
|
|
||||||
{ isError: '0', to, from: validator1 },
|
|
||||||
{ isError: '1', to, from: validator1 },
|
|
||||||
{ isError: '0', to, from: validator2 },
|
|
||||||
{ isError: '1', to, from: validator2 },
|
|
||||||
{ isError: '1', to, from: validator3 }
|
|
||||||
]
|
|
||||||
|
|
||||||
const fetchAccountTransactions = jest.fn().mockImplementation(() => transactions)
|
|
||||||
const result = await getSuccessTransactions(validator1, to, 0, 1, '', fetchAccountTransactions)
|
|
||||||
expect(result.length).toEqual(1)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
describe('filterValidatorSignatureTransaction', () => {
|
|
||||||
test('should return submit signatures related transaction', () => {
|
|
||||||
const transactions = [
|
|
||||||
{ input: `0x${SUBMIT_SIGNATURE_HASH}112233` },
|
|
||||||
{ input: `0x${SUBMIT_SIGNATURE_HASH}123456` },
|
|
||||||
{ input: `0x${OTHER_HASH}123456` },
|
|
||||||
{ input: `0x${OTHER_HASH}112233` }
|
|
||||||
] as APITransaction[]
|
|
||||||
|
|
||||||
const result = filterValidatorSignatureTransaction(transactions, messageData)
|
|
||||||
expect(result.length).toEqual(1)
|
|
||||||
expect(result[0]).toEqual({ input: `0x${SUBMIT_SIGNATURE_HASH}123456` })
|
|
||||||
})
|
|
||||||
test('should return execute affirmation related transaction', () => {
|
|
||||||
const transactions = [
|
|
||||||
{ input: `0x${EXECUTE_AFFIRMATION_HASH}112233` },
|
|
||||||
{ input: `0x${EXECUTE_AFFIRMATION_HASH}123456` },
|
|
||||||
{ input: `0x${OTHER_HASH}123456` },
|
|
||||||
{ input: `0x${OTHER_HASH}112233` }
|
|
||||||
] as APITransaction[]
|
|
||||||
|
|
||||||
const result = filterValidatorSignatureTransaction(transactions, messageData)
|
|
||||||
expect(result.length).toEqual(1)
|
|
||||||
expect(result[0]).toEqual({ input: `0x${EXECUTE_AFFIRMATION_HASH}123456` })
|
|
||||||
})
|
|
||||||
})
|
|
||||||
describe('getExecutionFailedTransactionForMessage', () => {
|
|
||||||
test('should return failed transaction related to signatures execution', async () => {
|
|
||||||
const transactions = [
|
|
||||||
{ input: `0x${EXECUTE_SIGNATURES_HASH}112233` },
|
|
||||||
{ input: `0x${EXECUTE_SIGNATURES_HASH}123456` },
|
|
||||||
{ input: `0x${OTHER_HASH}123456` },
|
|
||||||
{ input: `0x${OTHER_HASH}112233` }
|
|
||||||
] as APITransaction[]
|
|
||||||
const fetchAccountTransactions = jest.fn().mockImplementation(() => transactions)
|
|
||||||
|
|
||||||
const result = await getExecutionFailedTransactionForMessage(
|
|
||||||
{
|
|
||||||
account: '',
|
|
||||||
to: '',
|
|
||||||
messageData,
|
|
||||||
startBlock: 0,
|
|
||||||
endBlock: 1
|
|
||||||
},
|
|
||||||
fetchAccountTransactions
|
|
||||||
)
|
|
||||||
expect(result.length).toEqual(1)
|
|
||||||
expect(result[0]).toEqual({ input: `0x${EXECUTE_SIGNATURES_HASH}123456` })
|
|
||||||
})
|
|
||||||
})
|
|
||||||
describe('getValidatorPendingTransactionsForMessage', () => {
|
|
||||||
test('should return pending transaction for submit signature transaction', async () => {
|
|
||||||
const transactions = [
|
|
||||||
{ input: `0x${SUBMIT_SIGNATURE_HASH}112233`, to: bridgeAddress },
|
|
||||||
{ input: `0x${SUBMIT_SIGNATURE_HASH}123456`, to: bridgeAddress },
|
|
||||||
{ input: `0x${SUBMIT_SIGNATURE_HASH}123456`, to: otherAddress },
|
|
||||||
{ input: `0x${OTHER_HASH}123456`, to: bridgeAddress },
|
|
||||||
{ input: `0x${OTHER_HASH}112233`, to: bridgeAddress }
|
|
||||||
] as APITransaction[]
|
|
||||||
const fetchAccountTransactions = jest.fn().mockImplementation(() => transactions)
|
|
||||||
|
|
||||||
const result = await getValidatorPendingTransactionsForMessage(
|
|
||||||
{
|
|
||||||
account: '',
|
|
||||||
to: bridgeAddress,
|
|
||||||
messageData
|
|
||||||
},
|
|
||||||
fetchAccountTransactions
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(result.length).toEqual(1)
|
|
||||||
expect(result[0]).toEqual({ input: `0x${SUBMIT_SIGNATURE_HASH}123456`, to: bridgeAddress })
|
|
||||||
})
|
|
||||||
test('should return pending transaction for execute affirmation transaction', async () => {
|
|
||||||
const transactions = [
|
|
||||||
{ input: `0x${EXECUTE_AFFIRMATION_HASH}112233`, to: bridgeAddress },
|
|
||||||
{ input: `0x${EXECUTE_AFFIRMATION_HASH}123456`, to: bridgeAddress },
|
|
||||||
{ input: `0x${EXECUTE_AFFIRMATION_HASH}123456`, to: otherAddress },
|
|
||||||
{ input: `0x${OTHER_HASH}123456`, to: bridgeAddress },
|
|
||||||
{ input: `0x${OTHER_HASH}112233`, to: bridgeAddress }
|
|
||||||
] as APITransaction[]
|
|
||||||
const fetchAccountTransactions = jest.fn().mockImplementation(() => transactions)
|
|
||||||
|
|
||||||
const result = await getValidatorPendingTransactionsForMessage(
|
|
||||||
{
|
|
||||||
account: '',
|
|
||||||
to: bridgeAddress,
|
|
||||||
messageData
|
|
||||||
},
|
|
||||||
fetchAccountTransactions
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(result.length).toEqual(1)
|
|
||||||
expect(result[0]).toEqual({ input: `0x${EXECUTE_AFFIRMATION_HASH}123456`, to: bridgeAddress })
|
|
||||||
})
|
|
||||||
})
|
|
||||||
describe('getExecutionPendingTransactionsForMessage', () => {
|
|
||||||
test('should return pending transaction for signatures execution transaction', async () => {
|
|
||||||
const transactions = [
|
|
||||||
{ input: `0x${EXECUTE_SIGNATURES_HASH}112233`, to: bridgeAddress },
|
|
||||||
{ input: `0x${EXECUTE_SIGNATURES_HASH}123456`, to: bridgeAddress },
|
|
||||||
{ input: `0x${EXECUTE_SIGNATURES_HASH}123456`, to: otherAddress },
|
|
||||||
{ input: `0x${OTHER_HASH}123456`, to: bridgeAddress },
|
|
||||||
{ input: `0x${OTHER_HASH}112233`, to: bridgeAddress }
|
|
||||||
] as APITransaction[]
|
|
||||||
const fetchAccountTransactions = jest.fn().mockImplementation(() => transactions)
|
|
||||||
|
|
||||||
const result = await getExecutionPendingTransactionsForMessage(
|
|
||||||
{
|
|
||||||
account: '',
|
|
||||||
to: bridgeAddress,
|
|
||||||
messageData
|
|
||||||
},
|
|
||||||
fetchAccountTransactions
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(result.length).toEqual(1)
|
|
||||||
expect(result[0]).toEqual({ input: `0x${EXECUTE_SIGNATURES_HASH}123456`, to: bridgeAddress })
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@ -1,830 +0,0 @@
|
|||||||
import 'jest'
|
|
||||||
import { getConfirmationsForTx } from '../getConfirmationsForTx'
|
|
||||||
import * as helpers from '../validatorConfirmationHelpers'
|
|
||||||
import Web3 from 'web3'
|
|
||||||
import { Contract } from 'web3-eth-contract'
|
|
||||||
import { APIPendingTransaction, APITransaction } from '../explorer'
|
|
||||||
import { VALIDATOR_CONFIRMATION_STATUS } from '../../config/constants'
|
|
||||||
import { ConfirmationParam } from '../../hooks/useMessageConfirmations'
|
|
||||||
|
|
||||||
jest.mock('../validatorConfirmationHelpers')
|
|
||||||
|
|
||||||
const getSuccessExecutionTransaction = helpers.getSuccessExecutionTransaction as jest.Mock<any>
|
|
||||||
const getValidatorConfirmation = helpers.getValidatorConfirmation as jest.Mock<any>
|
|
||||||
const getValidatorFailedTransaction = helpers.getValidatorFailedTransaction as jest.Mock<any>
|
|
||||||
const getValidatorPendingTransaction = helpers.getValidatorPendingTransaction as jest.Mock<any>
|
|
||||||
|
|
||||||
const messageData = '0x111111111'
|
|
||||||
const web3 = {
|
|
||||||
utils: {
|
|
||||||
soliditySha3Raw: (data: string) => `0xaaaa${data.replace('0x', '')}`
|
|
||||||
},
|
|
||||||
eth: {
|
|
||||||
accounts: new Web3().eth.accounts
|
|
||||||
}
|
|
||||||
} as Web3
|
|
||||||
const validator1 = '0x45b96809336A8b714BFbdAB3E4B5e0fe5d839908'
|
|
||||||
const validator2 = '0xAe8bFfc8BBc6AAa9E21ED1E4e4957fe798BEA25f'
|
|
||||||
const validator3 = '0x285A6eB779be4db94dA65e2F3518B1c5F0f71244'
|
|
||||||
const validatorList = [validator1, validator2, validator3]
|
|
||||||
const signature =
|
|
||||||
'0x6f5b74905669999f1abdb52e1e215506907e1849aac7b31854da458b33a5954e15b165007c3703cfd16e61ca46a96a56727ed11fa47be359d3834515accd016e1b'
|
|
||||||
const bridgeContract = {
|
|
||||||
methods: {
|
|
||||||
signature: () => ({
|
|
||||||
call: () => signature
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} as Contract
|
|
||||||
const requiredSignatures = 2
|
|
||||||
const isCancelled = () => false
|
|
||||||
let subscriptions: Array<number> = []
|
|
||||||
const timestamp = 1594045859
|
|
||||||
const getFailedTransactions = (): Promise<APITransaction[]> => Promise.resolve([])
|
|
||||||
const getPendingTransactions = (): Promise<APIPendingTransaction[]> => Promise.resolve([])
|
|
||||||
const getSuccessTransactions = (): Promise<APITransaction[]> => Promise.resolve([])
|
|
||||||
|
|
||||||
const unsubscribe = () => {
|
|
||||||
subscriptions.forEach(s => {
|
|
||||||
clearTimeout(s)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
// Clear all instances and calls to constructor and all methods:
|
|
||||||
getSuccessExecutionTransaction.mockClear()
|
|
||||||
getValidatorConfirmation.mockClear()
|
|
||||||
getValidatorFailedTransaction.mockClear()
|
|
||||||
getValidatorPendingTransaction.mockClear()
|
|
||||||
subscriptions = []
|
|
||||||
})
|
|
||||||
describe('getConfirmationsForTx', () => {
|
|
||||||
test('should set validator confirmations status when signatures collected even if validator transactions not found yet and set remaining validator as not required', async () => {
|
|
||||||
getValidatorConfirmation.mockImplementation(() => async (validator: string) => ({
|
|
||||||
validator,
|
|
||||||
status: validator !== validator3 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
|
|
||||||
}))
|
|
||||||
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
|
||||||
validator: validatorData.validator,
|
|
||||||
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
|
|
||||||
txHash: '',
|
|
||||||
timestamp: 0
|
|
||||||
}))
|
|
||||||
getValidatorFailedTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
|
||||||
validator: validatorData.validator,
|
|
||||||
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
|
||||||
txHash: '',
|
|
||||||
timestamp: 0
|
|
||||||
}))
|
|
||||||
getValidatorPendingTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
|
||||||
validator: validatorData.validator,
|
|
||||||
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
|
||||||
txHash: '',
|
|
||||||
timestamp: 0
|
|
||||||
}))
|
|
||||||
|
|
||||||
const setResult = jest.fn()
|
|
||||||
const setSignatureCollected = jest.fn()
|
|
||||||
const setFailedConfirmations = jest.fn()
|
|
||||||
const setPendingConfirmations = jest.fn()
|
|
||||||
|
|
||||||
await getConfirmationsForTx(
|
|
||||||
messageData,
|
|
||||||
web3,
|
|
||||||
validatorList,
|
|
||||||
bridgeContract,
|
|
||||||
true,
|
|
||||||
setResult,
|
|
||||||
requiredSignatures,
|
|
||||||
setSignatureCollected,
|
|
||||||
subscriptions.push.bind(subscriptions),
|
|
||||||
isCancelled,
|
|
||||||
timestamp,
|
|
||||||
getFailedTransactions,
|
|
||||||
setFailedConfirmations,
|
|
||||||
getPendingTransactions,
|
|
||||||
setPendingConfirmations,
|
|
||||||
getSuccessTransactions
|
|
||||||
)
|
|
||||||
|
|
||||||
unsubscribe()
|
|
||||||
|
|
||||||
expect(subscriptions.length).toEqual(1)
|
|
||||||
expect(setResult).toBeCalledTimes(2)
|
|
||||||
expect(getValidatorConfirmation).toBeCalledTimes(1)
|
|
||||||
expect(getSuccessExecutionTransaction).toBeCalledTimes(1)
|
|
||||||
expect(setSignatureCollected).toBeCalledTimes(1)
|
|
||||||
expect(setSignatureCollected.mock.calls[0][0]).toEqual(true)
|
|
||||||
|
|
||||||
expect(getValidatorFailedTransaction).toBeCalledTimes(1)
|
|
||||||
expect(setFailedConfirmations).toBeCalledTimes(1)
|
|
||||||
expect(setFailedConfirmations.mock.calls[0][0]).toEqual(false)
|
|
||||||
|
|
||||||
expect(getValidatorPendingTransaction).toBeCalledTimes(0)
|
|
||||||
expect(setPendingConfirmations).toBeCalledTimes(1)
|
|
||||||
expect(setPendingConfirmations.mock.calls[0][0]).toEqual(false)
|
|
||||||
|
|
||||||
const res1 = setResult.mock.calls[0][0]()
|
|
||||||
const res2 = setResult.mock.calls[1][0](res1)
|
|
||||||
expect(res1).toEqual(
|
|
||||||
expect.arrayContaining([
|
|
||||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
|
||||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
|
||||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
|
|
||||||
])
|
|
||||||
)
|
|
||||||
expect(res2).toEqual(
|
|
||||||
expect.arrayContaining([
|
|
||||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
|
||||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
|
||||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED, txHash: '', timestamp: 0 }
|
|
||||||
])
|
|
||||||
)
|
|
||||||
})
|
|
||||||
test('should set validator confirmations status when signatures not collected even if validator transactions not found yet', async () => {
|
|
||||||
getValidatorConfirmation.mockImplementation(() => async (validator: string) => ({
|
|
||||||
validator,
|
|
||||||
status: validator === validator3 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
|
|
||||||
}))
|
|
||||||
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
|
||||||
validator: validatorData.validator,
|
|
||||||
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
|
|
||||||
txHash: '',
|
|
||||||
timestamp: 0
|
|
||||||
}))
|
|
||||||
getValidatorFailedTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
|
||||||
validator: validatorData.validator,
|
|
||||||
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
|
||||||
txHash: '',
|
|
||||||
timestamp: 0
|
|
||||||
}))
|
|
||||||
getValidatorPendingTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
|
||||||
validator: validatorData.validator,
|
|
||||||
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
|
||||||
txHash: '',
|
|
||||||
timestamp: 0
|
|
||||||
}))
|
|
||||||
|
|
||||||
const setResult = jest.fn()
|
|
||||||
const setSignatureCollected = jest.fn()
|
|
||||||
const setFailedConfirmations = jest.fn()
|
|
||||||
const setPendingConfirmations = jest.fn()
|
|
||||||
|
|
||||||
await getConfirmationsForTx(
|
|
||||||
messageData,
|
|
||||||
web3,
|
|
||||||
validatorList,
|
|
||||||
bridgeContract,
|
|
||||||
true,
|
|
||||||
setResult,
|
|
||||||
requiredSignatures,
|
|
||||||
setSignatureCollected,
|
|
||||||
subscriptions.push.bind(subscriptions),
|
|
||||||
isCancelled,
|
|
||||||
timestamp,
|
|
||||||
getFailedTransactions,
|
|
||||||
setFailedConfirmations,
|
|
||||||
getPendingTransactions,
|
|
||||||
setPendingConfirmations,
|
|
||||||
getSuccessTransactions
|
|
||||||
)
|
|
||||||
|
|
||||||
unsubscribe()
|
|
||||||
|
|
||||||
expect(setResult).toBeCalledTimes(1)
|
|
||||||
expect(getValidatorConfirmation).toBeCalledTimes(1)
|
|
||||||
expect(getSuccessExecutionTransaction).toBeCalledTimes(1)
|
|
||||||
expect(setSignatureCollected).toBeCalledTimes(1)
|
|
||||||
expect(setSignatureCollected.mock.calls[0][0]).toEqual(false)
|
|
||||||
|
|
||||||
expect(getValidatorFailedTransaction).toBeCalledTimes(1)
|
|
||||||
expect(setFailedConfirmations).toBeCalledTimes(1)
|
|
||||||
expect(setFailedConfirmations.mock.calls[0][0]).toEqual(false)
|
|
||||||
|
|
||||||
expect(getValidatorPendingTransaction).toBeCalledTimes(1)
|
|
||||||
expect(setPendingConfirmations).toBeCalledTimes(1)
|
|
||||||
expect(setPendingConfirmations.mock.calls[0][0]).toEqual(false)
|
|
||||||
})
|
|
||||||
test('should set validator confirmations status, validator transactions and not retry', async () => {
|
|
||||||
getValidatorConfirmation.mockImplementation(() => async (validator: string) => ({
|
|
||||||
validator,
|
|
||||||
status: validator !== validator3 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
|
|
||||||
}))
|
|
||||||
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
|
||||||
validator: validatorData.validator,
|
|
||||||
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
|
|
||||||
txHash: validatorData.validator !== validator3 ? '0x123' : '',
|
|
||||||
timestamp: validatorData.validator !== validator3 ? 123 : 0
|
|
||||||
}))
|
|
||||||
getValidatorFailedTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
|
||||||
validator: validatorData.validator,
|
|
||||||
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
|
||||||
txHash: '',
|
|
||||||
timestamp: 0
|
|
||||||
}))
|
|
||||||
getValidatorPendingTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
|
||||||
validator: validatorData.validator,
|
|
||||||
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
|
||||||
txHash: '',
|
|
||||||
timestamp: 0
|
|
||||||
}))
|
|
||||||
|
|
||||||
const setResult = jest.fn()
|
|
||||||
const setSignatureCollected = jest.fn()
|
|
||||||
const setFailedConfirmations = jest.fn()
|
|
||||||
const setPendingConfirmations = jest.fn()
|
|
||||||
|
|
||||||
await getConfirmationsForTx(
|
|
||||||
messageData,
|
|
||||||
web3,
|
|
||||||
validatorList,
|
|
||||||
bridgeContract,
|
|
||||||
true,
|
|
||||||
setResult,
|
|
||||||
requiredSignatures,
|
|
||||||
setSignatureCollected,
|
|
||||||
subscriptions.push.bind(subscriptions),
|
|
||||||
isCancelled,
|
|
||||||
timestamp,
|
|
||||||
getFailedTransactions,
|
|
||||||
setFailedConfirmations,
|
|
||||||
getPendingTransactions,
|
|
||||||
setPendingConfirmations,
|
|
||||||
getSuccessTransactions
|
|
||||||
)
|
|
||||||
|
|
||||||
unsubscribe()
|
|
||||||
|
|
||||||
expect(subscriptions.length).toEqual(0)
|
|
||||||
expect(setResult).toBeCalledTimes(3)
|
|
||||||
expect(getValidatorConfirmation).toBeCalledTimes(1)
|
|
||||||
expect(getSuccessExecutionTransaction).toBeCalledTimes(1)
|
|
||||||
expect(setSignatureCollected).toBeCalledTimes(1)
|
|
||||||
expect(setSignatureCollected.mock.calls[0][0]).toEqual(true)
|
|
||||||
|
|
||||||
expect(getValidatorFailedTransaction).toBeCalledTimes(1)
|
|
||||||
expect(setFailedConfirmations).toBeCalledTimes(1)
|
|
||||||
expect(setFailedConfirmations.mock.calls[0][0]).toEqual(false)
|
|
||||||
|
|
||||||
expect(getValidatorPendingTransaction).toBeCalledTimes(0)
|
|
||||||
expect(setPendingConfirmations).toBeCalledTimes(1)
|
|
||||||
expect(setPendingConfirmations.mock.calls[0][0]).toEqual(false)
|
|
||||||
|
|
||||||
const res1 = setResult.mock.calls[0][0]()
|
|
||||||
const res2 = setResult.mock.calls[1][0](res1)
|
|
||||||
const res3 = setResult.mock.calls[2][0](res2)
|
|
||||||
expect(res1).toEqual(
|
|
||||||
expect.arrayContaining([
|
|
||||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
|
||||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
|
||||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
|
|
||||||
])
|
|
||||||
)
|
|
||||||
expect(res2).toEqual(
|
|
||||||
expect.arrayContaining([
|
|
||||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
|
||||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
|
||||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
|
|
||||||
])
|
|
||||||
)
|
|
||||||
expect(res3).toEqual(
|
|
||||||
expect.arrayContaining([
|
|
||||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
|
||||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
|
||||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED, txHash: '', timestamp: 0 }
|
|
||||||
])
|
|
||||||
)
|
|
||||||
})
|
|
||||||
test('should set validator confirmations status, validator transactions, keep failed found transaction and not retry', async () => {
|
|
||||||
const validator4 = '0x9d2dC11C342F4eF3C5491A048D0f0eBCd2D8f7C3'
|
|
||||||
const validatorList = [validator1, validator2, validator3, validator4]
|
|
||||||
getValidatorConfirmation.mockImplementation(() => async (validator: string) => ({
|
|
||||||
validator,
|
|
||||||
status:
|
|
||||||
validator !== validator3 && validator !== validator4
|
|
||||||
? VALIDATOR_CONFIRMATION_STATUS.SUCCESS
|
|
||||||
: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
|
|
||||||
}))
|
|
||||||
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
|
||||||
validator: validatorData.validator,
|
|
||||||
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
|
|
||||||
txHash: validatorData.validator !== validator3 && validatorData.validator !== validator4 ? '0x123' : '',
|
|
||||||
timestamp: validatorData.validator !== validator3 && validatorData.validator !== validator4 ? 123 : 0
|
|
||||||
}))
|
|
||||||
getValidatorFailedTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
|
||||||
validator: validatorData.validator,
|
|
||||||
status:
|
|
||||||
validatorData.validator === validator3
|
|
||||||
? VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID
|
|
||||||
: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
|
||||||
txHash: validatorData.validator === validator3 ? '0x123' : '',
|
|
||||||
timestamp: validatorData.validator === validator3 ? 123 : 0
|
|
||||||
}))
|
|
||||||
getValidatorPendingTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
|
||||||
validator: validatorData.validator,
|
|
||||||
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
|
||||||
txHash: '',
|
|
||||||
timestamp: 0
|
|
||||||
}))
|
|
||||||
|
|
||||||
const setResult = jest.fn()
|
|
||||||
const setSignatureCollected = jest.fn()
|
|
||||||
const setFailedConfirmations = jest.fn()
|
|
||||||
const setPendingConfirmations = jest.fn()
|
|
||||||
|
|
||||||
await getConfirmationsForTx(
|
|
||||||
messageData,
|
|
||||||
web3,
|
|
||||||
validatorList,
|
|
||||||
bridgeContract,
|
|
||||||
true,
|
|
||||||
setResult,
|
|
||||||
requiredSignatures,
|
|
||||||
setSignatureCollected,
|
|
||||||
subscriptions.push.bind(subscriptions),
|
|
||||||
isCancelled,
|
|
||||||
timestamp,
|
|
||||||
getFailedTransactions,
|
|
||||||
setFailedConfirmations,
|
|
||||||
getPendingTransactions,
|
|
||||||
setPendingConfirmations,
|
|
||||||
getSuccessTransactions
|
|
||||||
)
|
|
||||||
|
|
||||||
unsubscribe()
|
|
||||||
|
|
||||||
expect(subscriptions.length).toEqual(0)
|
|
||||||
expect(setResult).toBeCalledTimes(4)
|
|
||||||
expect(getValidatorConfirmation).toBeCalledTimes(1)
|
|
||||||
expect(getSuccessExecutionTransaction).toBeCalledTimes(1)
|
|
||||||
expect(setSignatureCollected).toBeCalledTimes(1)
|
|
||||||
expect(setSignatureCollected.mock.calls[0][0]).toEqual(true)
|
|
||||||
|
|
||||||
expect(getValidatorFailedTransaction).toBeCalledTimes(1)
|
|
||||||
expect(setFailedConfirmations).toBeCalledTimes(1)
|
|
||||||
expect(setFailedConfirmations.mock.calls[0][0]).toEqual(false)
|
|
||||||
|
|
||||||
expect(getValidatorPendingTransaction).toBeCalledTimes(0)
|
|
||||||
expect(setPendingConfirmations).toBeCalledTimes(1)
|
|
||||||
expect(setPendingConfirmations.mock.calls[0][0]).toEqual(false)
|
|
||||||
|
|
||||||
const res1 = setResult.mock.calls[0][0]()
|
|
||||||
const res2 = setResult.mock.calls[1][0](res1)
|
|
||||||
const res3 = setResult.mock.calls[2][0](res2)
|
|
||||||
const res4 = setResult.mock.calls[3][0](res3)
|
|
||||||
expect(res1).toEqual(
|
|
||||||
expect.arrayContaining([
|
|
||||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
|
||||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
|
||||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
|
|
||||||
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
|
|
||||||
])
|
|
||||||
)
|
|
||||||
expect(res2).toEqual(
|
|
||||||
expect.arrayContaining([
|
|
||||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
|
||||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
|
||||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
|
|
||||||
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
|
|
||||||
])
|
|
||||||
)
|
|
||||||
expect(res3).toEqual(
|
|
||||||
expect.arrayContaining([
|
|
||||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
|
||||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
|
||||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID, txHash: '0x123', timestamp: 123 },
|
|
||||||
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
|
|
||||||
])
|
|
||||||
)
|
|
||||||
expect(res4).toEqual(
|
|
||||||
expect.arrayContaining([
|
|
||||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
|
||||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
|
||||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID, txHash: '0x123', timestamp: 123 },
|
|
||||||
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED, txHash: '', timestamp: 0 }
|
|
||||||
])
|
|
||||||
)
|
|
||||||
})
|
|
||||||
test('should look for failed and pending transactions for not confirmed validators', async () => {
|
|
||||||
// Validator1 success
|
|
||||||
// Validator2 failed
|
|
||||||
// Validator3 Pending
|
|
||||||
|
|
||||||
getValidatorConfirmation.mockImplementation(() => async (validator: string) => ({
|
|
||||||
validator,
|
|
||||||
status: validator === validator1 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
|
|
||||||
}))
|
|
||||||
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
|
||||||
validator: validatorData.validator,
|
|
||||||
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
|
|
||||||
txHash: validatorData.validator === validator1 ? '0x123' : '',
|
|
||||||
timestamp: validatorData.validator === validator1 ? 123 : 0
|
|
||||||
}))
|
|
||||||
getValidatorFailedTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
|
||||||
validator: validatorData.validator,
|
|
||||||
status:
|
|
||||||
validatorData.validator === validator2
|
|
||||||
? VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID
|
|
||||||
: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
|
||||||
txHash: validatorData.validator === validator2 ? '0x123' : '',
|
|
||||||
timestamp: validatorData.validator === validator2 ? 123 : 0
|
|
||||||
}))
|
|
||||||
getValidatorPendingTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
|
||||||
validator: validatorData.validator,
|
|
||||||
status:
|
|
||||||
validatorData.validator === validator3
|
|
||||||
? VALIDATOR_CONFIRMATION_STATUS.PENDING
|
|
||||||
: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
|
||||||
txHash: validatorData.validator === validator3 ? '0x123' : '',
|
|
||||||
timestamp: validatorData.validator === validator3 ? 123 : 0
|
|
||||||
}))
|
|
||||||
|
|
||||||
const setResult = jest.fn()
|
|
||||||
const setSignatureCollected = jest.fn()
|
|
||||||
const setFailedConfirmations = jest.fn()
|
|
||||||
const setPendingConfirmations = jest.fn()
|
|
||||||
|
|
||||||
await getConfirmationsForTx(
|
|
||||||
messageData,
|
|
||||||
web3,
|
|
||||||
validatorList,
|
|
||||||
bridgeContract,
|
|
||||||
true,
|
|
||||||
setResult,
|
|
||||||
requiredSignatures,
|
|
||||||
setSignatureCollected,
|
|
||||||
subscriptions.push.bind(subscriptions),
|
|
||||||
isCancelled,
|
|
||||||
timestamp,
|
|
||||||
getFailedTransactions,
|
|
||||||
setFailedConfirmations,
|
|
||||||
getPendingTransactions,
|
|
||||||
setPendingConfirmations,
|
|
||||||
getSuccessTransactions
|
|
||||||
)
|
|
||||||
|
|
||||||
unsubscribe()
|
|
||||||
|
|
||||||
expect(setResult).toBeCalledTimes(4)
|
|
||||||
expect(getValidatorConfirmation).toBeCalledTimes(1)
|
|
||||||
expect(getSuccessExecutionTransaction).toBeCalledTimes(1)
|
|
||||||
expect(setSignatureCollected).toBeCalledTimes(1)
|
|
||||||
expect(setSignatureCollected.mock.calls[0][0]).toEqual(false)
|
|
||||||
|
|
||||||
expect(getValidatorFailedTransaction).toBeCalledTimes(1)
|
|
||||||
expect(setFailedConfirmations).toBeCalledTimes(1)
|
|
||||||
expect(setFailedConfirmations.mock.calls[0][0]).toEqual(false)
|
|
||||||
|
|
||||||
expect(getValidatorPendingTransaction).toBeCalledTimes(1)
|
|
||||||
expect(setPendingConfirmations).toBeCalledTimes(1)
|
|
||||||
expect(setPendingConfirmations.mock.calls[0][0]).toEqual(true)
|
|
||||||
|
|
||||||
const res1 = setResult.mock.calls[0][0]()
|
|
||||||
const res2 = setResult.mock.calls[1][0](res1)
|
|
||||||
const res3 = setResult.mock.calls[2][0](res2)
|
|
||||||
const res4 = setResult.mock.calls[3][0](res3)
|
|
||||||
expect(res1).toEqual(
|
|
||||||
expect.arrayContaining([
|
|
||||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
|
||||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
|
|
||||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
|
|
||||||
])
|
|
||||||
)
|
|
||||||
expect(res2).toEqual(
|
|
||||||
expect.arrayContaining([
|
|
||||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
|
||||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
|
|
||||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
|
|
||||||
])
|
|
||||||
)
|
|
||||||
expect(res3).toEqual(
|
|
||||||
expect.arrayContaining([
|
|
||||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
|
||||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
|
|
||||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.PENDING, txHash: '0x123', timestamp: 123 }
|
|
||||||
])
|
|
||||||
)
|
|
||||||
expect(res4).toEqual(
|
|
||||||
expect.arrayContaining([
|
|
||||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
|
||||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID, txHash: '0x123', timestamp: 123 },
|
|
||||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.PENDING, txHash: '0x123', timestamp: 123 }
|
|
||||||
])
|
|
||||||
)
|
|
||||||
})
|
|
||||||
test('should set as failed if enough signatures failed', async () => {
|
|
||||||
// Validator1 success
|
|
||||||
// Validator2 failed
|
|
||||||
// Validator3 failed
|
|
||||||
|
|
||||||
getValidatorConfirmation.mockImplementation(() => async (validator: string) => ({
|
|
||||||
validator,
|
|
||||||
status: validator === validator1 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
|
|
||||||
}))
|
|
||||||
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
|
||||||
validator: validatorData.validator,
|
|
||||||
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
|
|
||||||
txHash: validatorData.validator === validator1 ? '0x123' : '',
|
|
||||||
timestamp: validatorData.validator === validator1 ? 123 : 0
|
|
||||||
}))
|
|
||||||
getValidatorFailedTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
|
||||||
validator: validatorData.validator,
|
|
||||||
status:
|
|
||||||
validatorData.validator !== validator1
|
|
||||||
? VALIDATOR_CONFIRMATION_STATUS.FAILED
|
|
||||||
: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
|
||||||
txHash: validatorData.validator !== validator1 ? '0x123' : '',
|
|
||||||
timestamp: validatorData.validator !== validator1 ? 123 : 0
|
|
||||||
}))
|
|
||||||
getValidatorPendingTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
|
||||||
validator: validatorData.validator,
|
|
||||||
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
|
||||||
txHash: '',
|
|
||||||
timestamp: 0
|
|
||||||
}))
|
|
||||||
|
|
||||||
const setResult = jest.fn()
|
|
||||||
const setSignatureCollected = jest.fn()
|
|
||||||
const setFailedConfirmations = jest.fn()
|
|
||||||
const setPendingConfirmations = jest.fn()
|
|
||||||
|
|
||||||
await getConfirmationsForTx(
|
|
||||||
messageData,
|
|
||||||
web3,
|
|
||||||
validatorList,
|
|
||||||
bridgeContract,
|
|
||||||
true,
|
|
||||||
setResult,
|
|
||||||
requiredSignatures,
|
|
||||||
setSignatureCollected,
|
|
||||||
subscriptions.push.bind(subscriptions),
|
|
||||||
isCancelled,
|
|
||||||
timestamp,
|
|
||||||
getFailedTransactions,
|
|
||||||
setFailedConfirmations,
|
|
||||||
getPendingTransactions,
|
|
||||||
setPendingConfirmations,
|
|
||||||
getSuccessTransactions
|
|
||||||
)
|
|
||||||
|
|
||||||
unsubscribe()
|
|
||||||
|
|
||||||
expect(subscriptions.length).toEqual(0)
|
|
||||||
expect(setResult).toBeCalledTimes(3)
|
|
||||||
expect(getValidatorConfirmation).toBeCalledTimes(1)
|
|
||||||
expect(getSuccessExecutionTransaction).toBeCalledTimes(1)
|
|
||||||
expect(setSignatureCollected).toBeCalledTimes(1)
|
|
||||||
expect(setSignatureCollected.mock.calls[0][0]).toEqual(false)
|
|
||||||
|
|
||||||
expect(getValidatorFailedTransaction).toBeCalledTimes(1)
|
|
||||||
expect(setFailedConfirmations).toBeCalledTimes(1)
|
|
||||||
expect(setFailedConfirmations.mock.calls[0][0]).toEqual(true)
|
|
||||||
|
|
||||||
expect(getValidatorPendingTransaction).toBeCalledTimes(1)
|
|
||||||
expect(setPendingConfirmations).toBeCalledTimes(1)
|
|
||||||
expect(setPendingConfirmations.mock.calls[0][0]).toEqual(false)
|
|
||||||
|
|
||||||
const res1 = setResult.mock.calls[0][0]()
|
|
||||||
const res2 = setResult.mock.calls[1][0](res1)
|
|
||||||
const res3 = setResult.mock.calls[2][0](res2)
|
|
||||||
expect(res1).toEqual(
|
|
||||||
expect.arrayContaining([
|
|
||||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
|
||||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
|
|
||||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
|
|
||||||
])
|
|
||||||
)
|
|
||||||
expect(res2).toEqual(
|
|
||||||
expect.arrayContaining([
|
|
||||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
|
||||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
|
|
||||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
|
|
||||||
])
|
|
||||||
)
|
|
||||||
expect(res3).toEqual(
|
|
||||||
expect.arrayContaining([
|
|
||||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
|
||||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },
|
|
||||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 }
|
|
||||||
])
|
|
||||||
)
|
|
||||||
})
|
|
||||||
test('should remove pending state after transaction mined', async () => {
|
|
||||||
const validator4 = '0x9d2dC11C342F4eF3C5491A048D0f0eBCd2D8f7C3'
|
|
||||||
const validatorList = [validator1, validator2, validator3, validator4]
|
|
||||||
|
|
||||||
// Validator1 success (ts=100)
|
|
||||||
// Validator2 failed (ts=200)
|
|
||||||
// Validator3 Pending (ts=300)
|
|
||||||
// Validator4 Excess confirmation (Failed) (ts=400)
|
|
||||||
|
|
||||||
getValidatorConfirmation
|
|
||||||
.mockImplementationOnce(() => async (validator: string) => ({
|
|
||||||
validator,
|
|
||||||
status:
|
|
||||||
validator === validator1 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
|
|
||||||
}))
|
|
||||||
.mockImplementation(() => async (validator: string) => ({
|
|
||||||
validator,
|
|
||||||
status:
|
|
||||||
validator === validator1 || validator === validator3
|
|
||||||
? VALIDATOR_CONFIRMATION_STATUS.SUCCESS
|
|
||||||
: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
|
|
||||||
}))
|
|
||||||
getSuccessExecutionTransaction
|
|
||||||
.mockImplementationOnce(() => async (validatorData: ConfirmationParam) => ({
|
|
||||||
validator: validatorData.validator,
|
|
||||||
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
|
|
||||||
txHash: validatorData.validator === validator1 ? '0x100' : '',
|
|
||||||
timestamp: validatorData.validator === validator1 ? 100 : 0
|
|
||||||
}))
|
|
||||||
.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
|
||||||
validator: validatorData.validator,
|
|
||||||
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
|
|
||||||
txHash:
|
|
||||||
validatorData.validator === validator1 ? '0x100' : validatorData.validator === validator3 ? '0x300' : '',
|
|
||||||
timestamp: validatorData.validator === validator1 ? 100 : validatorData.validator === validator3 ? 300 : ''
|
|
||||||
}))
|
|
||||||
getValidatorFailedTransaction
|
|
||||||
.mockImplementationOnce(() => async (validatorData: ConfirmationParam) => ({
|
|
||||||
validator: validatorData.validator,
|
|
||||||
status:
|
|
||||||
validatorData.validator === validator2
|
|
||||||
? VALIDATOR_CONFIRMATION_STATUS.FAILED
|
|
||||||
: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
|
||||||
txHash: validatorData.validator === validator2 ? '0x200' : '',
|
|
||||||
timestamp: validatorData.validator === validator2 ? 200 : 0
|
|
||||||
}))
|
|
||||||
.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
|
||||||
validator: validatorData.validator,
|
|
||||||
status:
|
|
||||||
validatorData.validator === validator2 || validatorData.validator === validator4
|
|
||||||
? validatorData.validator === validator2
|
|
||||||
? VALIDATOR_CONFIRMATION_STATUS.FAILED
|
|
||||||
: VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID
|
|
||||||
: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
|
||||||
txHash:
|
|
||||||
validatorData.validator === validator2 ? '0x200' : validatorData.validator === validator4 ? '0x400' : '',
|
|
||||||
timestamp: validatorData.validator === validator2 ? 200 : validatorData.validator === validator4 ? 400 : ''
|
|
||||||
}))
|
|
||||||
getValidatorPendingTransaction
|
|
||||||
.mockImplementationOnce(() => async (validatorData: ConfirmationParam) => ({
|
|
||||||
validator: validatorData.validator,
|
|
||||||
status:
|
|
||||||
validatorData.validator === validator3
|
|
||||||
? VALIDATOR_CONFIRMATION_STATUS.PENDING
|
|
||||||
: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
|
||||||
txHash: validatorData.validator === validator3 ? '0x300' : '',
|
|
||||||
timestamp: validatorData.validator === validator3 ? 300 : 0
|
|
||||||
}))
|
|
||||||
.mockImplementationOnce(() => async (validatorData: ConfirmationParam) => ({
|
|
||||||
validator: validatorData.validator,
|
|
||||||
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
|
||||||
txHash: '',
|
|
||||||
timestamp: 0
|
|
||||||
}))
|
|
||||||
|
|
||||||
const setResult = jest.fn()
|
|
||||||
const setSignatureCollected = jest.fn()
|
|
||||||
const setFailedConfirmations = jest.fn()
|
|
||||||
const setPendingConfirmations = jest.fn()
|
|
||||||
|
|
||||||
await getConfirmationsForTx(
|
|
||||||
messageData,
|
|
||||||
web3,
|
|
||||||
validatorList,
|
|
||||||
bridgeContract,
|
|
||||||
true,
|
|
||||||
setResult,
|
|
||||||
requiredSignatures,
|
|
||||||
setSignatureCollected,
|
|
||||||
subscriptions.push.bind(subscriptions),
|
|
||||||
isCancelled,
|
|
||||||
timestamp,
|
|
||||||
getFailedTransactions,
|
|
||||||
setFailedConfirmations,
|
|
||||||
getPendingTransactions,
|
|
||||||
setPendingConfirmations,
|
|
||||||
getSuccessTransactions
|
|
||||||
)
|
|
||||||
|
|
||||||
unsubscribe()
|
|
||||||
|
|
||||||
expect(setResult).toBeCalledTimes(4)
|
|
||||||
expect(getValidatorConfirmation).toBeCalledTimes(1)
|
|
||||||
expect(getSuccessExecutionTransaction).toBeCalledTimes(1)
|
|
||||||
expect(setSignatureCollected).toBeCalledTimes(1)
|
|
||||||
expect(setSignatureCollected.mock.calls[0][0]).toEqual(false)
|
|
||||||
|
|
||||||
expect(getValidatorFailedTransaction).toBeCalledTimes(1)
|
|
||||||
expect(setFailedConfirmations).toBeCalledTimes(1)
|
|
||||||
expect(setFailedConfirmations.mock.calls[0][0]).toEqual(true)
|
|
||||||
|
|
||||||
expect(getValidatorPendingTransaction).toBeCalledTimes(1)
|
|
||||||
expect(setPendingConfirmations).toBeCalledTimes(1)
|
|
||||||
expect(setPendingConfirmations.mock.calls[0][0]).toEqual(true)
|
|
||||||
|
|
||||||
const res1 = setResult.mock.calls[0][0]()
|
|
||||||
const res2 = setResult.mock.calls[1][0](res1)
|
|
||||||
const res3 = setResult.mock.calls[2][0](res2)
|
|
||||||
const res4 = setResult.mock.calls[3][0](res3)
|
|
||||||
expect(res1).toEqual(
|
|
||||||
expect.arrayContaining([
|
|
||||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
|
||||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
|
|
||||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
|
|
||||||
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
|
|
||||||
])
|
|
||||||
)
|
|
||||||
expect(res2).toEqual(
|
|
||||||
expect.arrayContaining([
|
|
||||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x100', timestamp: 100 },
|
|
||||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
|
|
||||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
|
|
||||||
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
|
|
||||||
])
|
|
||||||
)
|
|
||||||
expect(res3).toEqual(
|
|
||||||
expect.arrayContaining([
|
|
||||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x100', timestamp: 100 },
|
|
||||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
|
|
||||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.PENDING, txHash: '0x300', timestamp: 300 },
|
|
||||||
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
|
|
||||||
])
|
|
||||||
)
|
|
||||||
expect(res4).toEqual(
|
|
||||||
expect.arrayContaining([
|
|
||||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x100', timestamp: 100 },
|
|
||||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x200', timestamp: 200 },
|
|
||||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.PENDING, txHash: '0x300', timestamp: 300 },
|
|
||||||
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
|
|
||||||
])
|
|
||||||
)
|
|
||||||
|
|
||||||
await getConfirmationsForTx(
|
|
||||||
messageData,
|
|
||||||
web3,
|
|
||||||
validatorList,
|
|
||||||
bridgeContract,
|
|
||||||
true,
|
|
||||||
setResult,
|
|
||||||
requiredSignatures,
|
|
||||||
setSignatureCollected,
|
|
||||||
subscriptions.push.bind(subscriptions),
|
|
||||||
isCancelled,
|
|
||||||
timestamp,
|
|
||||||
getFailedTransactions,
|
|
||||||
setFailedConfirmations,
|
|
||||||
getPendingTransactions,
|
|
||||||
setPendingConfirmations,
|
|
||||||
getSuccessTransactions
|
|
||||||
)
|
|
||||||
|
|
||||||
unsubscribe()
|
|
||||||
|
|
||||||
expect(setResult).toBeCalledTimes(7)
|
|
||||||
expect(getValidatorConfirmation).toBeCalledTimes(2)
|
|
||||||
expect(getSuccessExecutionTransaction).toBeCalledTimes(2)
|
|
||||||
expect(setSignatureCollected).toBeCalledTimes(2)
|
|
||||||
expect(setSignatureCollected.mock.calls[0][0]).toEqual(false)
|
|
||||||
expect(setSignatureCollected.mock.calls[1][0]).toEqual(true)
|
|
||||||
|
|
||||||
expect(getValidatorFailedTransaction).toBeCalledTimes(2)
|
|
||||||
expect(setFailedConfirmations).toBeCalledTimes(2)
|
|
||||||
expect(setFailedConfirmations.mock.calls[0][0]).toEqual(true)
|
|
||||||
expect(setFailedConfirmations.mock.calls[1][0]).toEqual(false)
|
|
||||||
|
|
||||||
expect(getValidatorPendingTransaction).toBeCalledTimes(1)
|
|
||||||
expect(setPendingConfirmations).toBeCalledTimes(2)
|
|
||||||
expect(setPendingConfirmations.mock.calls[0][0]).toEqual(true)
|
|
||||||
expect(setPendingConfirmations.mock.calls[1][0]).toEqual(false)
|
|
||||||
|
|
||||||
const res5 = setResult.mock.calls[4][0](res4)
|
|
||||||
const res6 = setResult.mock.calls[5][0](res5)
|
|
||||||
const res7 = setResult.mock.calls[6][0](res6)
|
|
||||||
expect(res5).toEqual(
|
|
||||||
expect.arrayContaining([
|
|
||||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x100', timestamp: 100 },
|
|
||||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x200', timestamp: 200 },
|
|
||||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x300', timestamp: 300 },
|
|
||||||
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
|
|
||||||
])
|
|
||||||
)
|
|
||||||
expect(res6).toEqual(
|
|
||||||
expect.arrayContaining([
|
|
||||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x100', timestamp: 100 },
|
|
||||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x200', timestamp: 200 },
|
|
||||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x300', timestamp: 300 },
|
|
||||||
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
|
|
||||||
])
|
|
||||||
)
|
|
||||||
expect(res7).toEqual(
|
|
||||||
expect.arrayContaining([
|
|
||||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x100', timestamp: 100 },
|
|
||||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x200', timestamp: 200 },
|
|
||||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x300', timestamp: 300 },
|
|
||||||
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID, txHash: '0x400', timestamp: 400 }
|
|
||||||
])
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@ -1,309 +0,0 @@
|
|||||||
import 'jest'
|
|
||||||
import { Contract, EventData } from 'web3-eth-contract'
|
|
||||||
import Web3 from 'web3'
|
|
||||||
import { getFinalizationEvent } from '../getFinalizationEvent'
|
|
||||||
import { VALIDATOR_CONFIRMATION_STATUS } from '../../config/constants'
|
|
||||||
|
|
||||||
const timestamp = 1594045859
|
|
||||||
const validator1 = '0x45b96809336A8b714BFbdAB3E4B5e0fe5d839908'
|
|
||||||
const txHash = '0xdab36c9210e7e45fb82af10ffe4960461e41661dce0c9cd36b2843adaa1df156'
|
|
||||||
|
|
||||||
const web3 = ({
|
|
||||||
eth: {
|
|
||||||
getTransactionReceipt: () => ({
|
|
||||||
from: validator1
|
|
||||||
}),
|
|
||||||
getBlock: () => ({ timestamp })
|
|
||||||
},
|
|
||||||
utils: {
|
|
||||||
toChecksumAddress: (a: string) => a
|
|
||||||
}
|
|
||||||
} as unknown) as Web3
|
|
||||||
const message = {
|
|
||||||
id: '0x123',
|
|
||||||
data: '0x123456789'
|
|
||||||
}
|
|
||||||
const isCancelled = () => false
|
|
||||||
let subscriptions: Array<number> = []
|
|
||||||
|
|
||||||
const event = {
|
|
||||||
transactionHash: txHash,
|
|
||||||
blockNumber: 5523145,
|
|
||||||
returnValues: {
|
|
||||||
status: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const bridgeAddress = '0xFe446bEF1DbF7AFE24E81e05BC8B271C1BA9a560'
|
|
||||||
|
|
||||||
const unsubscribe = () => {
|
|
||||||
subscriptions.forEach(s => {
|
|
||||||
clearTimeout(s)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
subscriptions = []
|
|
||||||
})
|
|
||||||
describe('getFinalizationEvent', () => {
|
|
||||||
test('should get finalization event and not try to get failed or pending transactions', async () => {
|
|
||||||
const contract = ({
|
|
||||||
getPastEvents: async () => {
|
|
||||||
return [event]
|
|
||||||
}
|
|
||||||
} as unknown) as Contract
|
|
||||||
|
|
||||||
const collectedSignaturesEvent = null
|
|
||||||
const setResult = jest.fn()
|
|
||||||
const getFailedExecution = jest.fn()
|
|
||||||
const setFailedExecution = jest.fn()
|
|
||||||
const getPendingExecution = jest.fn()
|
|
||||||
const setPendingExecution = jest.fn()
|
|
||||||
const setExecutionEventsFetched = jest.fn()
|
|
||||||
|
|
||||||
await getFinalizationEvent(
|
|
||||||
true,
|
|
||||||
contract,
|
|
||||||
web3,
|
|
||||||
setResult,
|
|
||||||
message,
|
|
||||||
subscriptions.push.bind(subscriptions),
|
|
||||||
isCancelled,
|
|
||||||
timestamp,
|
|
||||||
collectedSignaturesEvent,
|
|
||||||
getFailedExecution,
|
|
||||||
setFailedExecution,
|
|
||||||
getPendingExecution,
|
|
||||||
setPendingExecution,
|
|
||||||
setExecutionEventsFetched
|
|
||||||
)
|
|
||||||
|
|
||||||
unsubscribe()
|
|
||||||
|
|
||||||
expect(subscriptions.length).toEqual(0)
|
|
||||||
expect(setResult).toBeCalledTimes(1)
|
|
||||||
expect(setResult.mock.calls[0][0]).toEqual({
|
|
||||||
validator: validator1,
|
|
||||||
status: VALIDATOR_CONFIRMATION_STATUS.EXECUTION_SUCCESS,
|
|
||||||
txHash,
|
|
||||||
timestamp,
|
|
||||||
executionResult: true,
|
|
||||||
blockNumber: 5523145
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(getFailedExecution).toBeCalledTimes(0)
|
|
||||||
expect(setFailedExecution).toBeCalledTimes(0)
|
|
||||||
|
|
||||||
expect(getPendingExecution).toBeCalledTimes(0)
|
|
||||||
expect(setPendingExecution).toBeCalledTimes(0)
|
|
||||||
})
|
|
||||||
test('should retry to get finalization event and not try to get failed or pending transactions if foreign to home transaction', async () => {
|
|
||||||
const contract = ({
|
|
||||||
getPastEvents: async () => {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
} as unknown) as Contract
|
|
||||||
|
|
||||||
const collectedSignaturesEvent = null
|
|
||||||
const setResult = jest.fn()
|
|
||||||
const getFailedExecution = jest.fn()
|
|
||||||
const setFailedExecution = jest.fn()
|
|
||||||
const getPendingExecution = jest.fn()
|
|
||||||
const setPendingExecution = jest.fn()
|
|
||||||
const setExecutionEventsFetched = jest.fn()
|
|
||||||
|
|
||||||
await getFinalizationEvent(
|
|
||||||
true,
|
|
||||||
contract,
|
|
||||||
web3,
|
|
||||||
setResult,
|
|
||||||
message,
|
|
||||||
subscriptions.push.bind(subscriptions),
|
|
||||||
isCancelled,
|
|
||||||
timestamp,
|
|
||||||
collectedSignaturesEvent,
|
|
||||||
getFailedExecution,
|
|
||||||
setFailedExecution,
|
|
||||||
getPendingExecution,
|
|
||||||
setPendingExecution,
|
|
||||||
setExecutionEventsFetched
|
|
||||||
)
|
|
||||||
|
|
||||||
unsubscribe()
|
|
||||||
|
|
||||||
expect(subscriptions.length).toEqual(1)
|
|
||||||
expect(setResult).toBeCalledTimes(0)
|
|
||||||
|
|
||||||
expect(getFailedExecution).toBeCalledTimes(0)
|
|
||||||
expect(setFailedExecution).toBeCalledTimes(0)
|
|
||||||
|
|
||||||
expect(getPendingExecution).toBeCalledTimes(0)
|
|
||||||
expect(setPendingExecution).toBeCalledTimes(0)
|
|
||||||
})
|
|
||||||
test('should retry to get finalization event and try to get failed and pending transactions if home to foreign transaction', async () => {
|
|
||||||
const contract = ({
|
|
||||||
getPastEvents: async () => {
|
|
||||||
return []
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
address: bridgeAddress
|
|
||||||
}
|
|
||||||
} as unknown) as Contract
|
|
||||||
|
|
||||||
const collectedSignaturesEvent = ({
|
|
||||||
returnValues: {
|
|
||||||
authorityResponsibleForRelay: validator1
|
|
||||||
}
|
|
||||||
} as unknown) as EventData
|
|
||||||
const setResult = jest.fn()
|
|
||||||
const getFailedExecution = jest.fn().mockResolvedValue([])
|
|
||||||
const setFailedExecution = jest.fn()
|
|
||||||
const getPendingExecution = jest.fn().mockResolvedValue([])
|
|
||||||
const setPendingExecution = jest.fn()
|
|
||||||
const setExecutionEventsFetched = jest.fn()
|
|
||||||
|
|
||||||
await getFinalizationEvent(
|
|
||||||
true,
|
|
||||||
contract,
|
|
||||||
web3,
|
|
||||||
setResult,
|
|
||||||
message,
|
|
||||||
subscriptions.push.bind(subscriptions),
|
|
||||||
isCancelled,
|
|
||||||
timestamp,
|
|
||||||
collectedSignaturesEvent,
|
|
||||||
getFailedExecution,
|
|
||||||
setFailedExecution,
|
|
||||||
getPendingExecution,
|
|
||||||
setPendingExecution,
|
|
||||||
setExecutionEventsFetched
|
|
||||||
)
|
|
||||||
|
|
||||||
unsubscribe()
|
|
||||||
|
|
||||||
expect(subscriptions.length).toEqual(1)
|
|
||||||
expect(setResult).toBeCalledTimes(0)
|
|
||||||
|
|
||||||
expect(getFailedExecution).toBeCalledTimes(1)
|
|
||||||
expect(setFailedExecution).toBeCalledTimes(0)
|
|
||||||
|
|
||||||
expect(getPendingExecution).toBeCalledTimes(1)
|
|
||||||
expect(setPendingExecution).toBeCalledTimes(0)
|
|
||||||
})
|
|
||||||
test('should retry to get finalization event and not to try to get failed transaction if pending transactions found if home to foreign transaction', async () => {
|
|
||||||
const contract = ({
|
|
||||||
getPastEvents: async () => {
|
|
||||||
return []
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
address: bridgeAddress
|
|
||||||
}
|
|
||||||
} as unknown) as Contract
|
|
||||||
|
|
||||||
const collectedSignaturesEvent = ({
|
|
||||||
returnValues: {
|
|
||||||
authorityResponsibleForRelay: validator1
|
|
||||||
}
|
|
||||||
} as unknown) as EventData
|
|
||||||
const setResult = jest.fn()
|
|
||||||
const getFailedExecution = jest.fn().mockResolvedValue([])
|
|
||||||
const setFailedExecution = jest.fn()
|
|
||||||
const getPendingExecution = jest.fn().mockResolvedValue([{ hash: txHash }])
|
|
||||||
const setPendingExecution = jest.fn()
|
|
||||||
const setExecutionEventsFetched = jest.fn()
|
|
||||||
|
|
||||||
await getFinalizationEvent(
|
|
||||||
true,
|
|
||||||
contract,
|
|
||||||
web3,
|
|
||||||
setResult,
|
|
||||||
message,
|
|
||||||
subscriptions.push.bind(subscriptions),
|
|
||||||
isCancelled,
|
|
||||||
timestamp,
|
|
||||||
collectedSignaturesEvent,
|
|
||||||
getFailedExecution,
|
|
||||||
setFailedExecution,
|
|
||||||
getPendingExecution,
|
|
||||||
setPendingExecution,
|
|
||||||
setExecutionEventsFetched
|
|
||||||
)
|
|
||||||
|
|
||||||
unsubscribe()
|
|
||||||
|
|
||||||
expect(subscriptions.length).toEqual(1)
|
|
||||||
expect(setResult).toBeCalledTimes(1)
|
|
||||||
expect(setResult.mock.calls[0][0]).toEqual({
|
|
||||||
validator: validator1,
|
|
||||||
status: VALIDATOR_CONFIRMATION_STATUS.PENDING,
|
|
||||||
txHash,
|
|
||||||
timestamp: expect.any(Number),
|
|
||||||
executionResult: false,
|
|
||||||
blockNumber: 0
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(getFailedExecution).toBeCalledTimes(0)
|
|
||||||
expect(setFailedExecution).toBeCalledTimes(0)
|
|
||||||
|
|
||||||
expect(getPendingExecution).toBeCalledTimes(1)
|
|
||||||
expect(setPendingExecution).toBeCalledTimes(1)
|
|
||||||
})
|
|
||||||
test('should retry to get finalization event even if failed transaction found if home to foreign transaction', async () => {
|
|
||||||
const contract = ({
|
|
||||||
getPastEvents: async () => {
|
|
||||||
return []
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
address: bridgeAddress
|
|
||||||
}
|
|
||||||
} as unknown) as Contract
|
|
||||||
|
|
||||||
const collectedSignaturesEvent = ({
|
|
||||||
returnValues: {
|
|
||||||
authorityResponsibleForRelay: validator1
|
|
||||||
}
|
|
||||||
} as unknown) as EventData
|
|
||||||
const setResult = jest.fn()
|
|
||||||
const getFailedExecution = jest.fn().mockResolvedValue([{ timeStamp: timestamp, hash: txHash }])
|
|
||||||
const setFailedExecution = jest.fn()
|
|
||||||
const getPendingExecution = jest.fn().mockResolvedValue([])
|
|
||||||
const setPendingExecution = jest.fn()
|
|
||||||
const setExecutionEventsFetched = jest.fn()
|
|
||||||
|
|
||||||
await getFinalizationEvent(
|
|
||||||
true,
|
|
||||||
contract,
|
|
||||||
web3,
|
|
||||||
setResult,
|
|
||||||
message,
|
|
||||||
subscriptions.push.bind(subscriptions),
|
|
||||||
isCancelled,
|
|
||||||
timestamp,
|
|
||||||
collectedSignaturesEvent,
|
|
||||||
getFailedExecution,
|
|
||||||
setFailedExecution,
|
|
||||||
getPendingExecution,
|
|
||||||
setPendingExecution,
|
|
||||||
setExecutionEventsFetched
|
|
||||||
)
|
|
||||||
|
|
||||||
unsubscribe()
|
|
||||||
|
|
||||||
expect(subscriptions.length).toEqual(1)
|
|
||||||
expect(setResult).toBeCalledTimes(1)
|
|
||||||
expect(setResult.mock.calls[0][0]).toEqual({
|
|
||||||
validator: validator1,
|
|
||||||
status: VALIDATOR_CONFIRMATION_STATUS.FAILED,
|
|
||||||
txHash,
|
|
||||||
timestamp: expect.any(Number),
|
|
||||||
executionResult: false,
|
|
||||||
blockNumber: expect.any(Number)
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(getFailedExecution).toBeCalledTimes(1)
|
|
||||||
expect(setFailedExecution).toBeCalledTimes(1)
|
|
||||||
|
|
||||||
expect(getPendingExecution).toBeCalledTimes(1)
|
|
||||||
expect(setPendingExecution).toBeCalledTimes(0)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@ -1,150 +0,0 @@
|
|||||||
import { Contract } from 'web3-eth-contract'
|
|
||||||
import { EventData } from 'web3-eth-contract'
|
|
||||||
import { SnapshotProvider } from '../services/SnapshotProvider'
|
|
||||||
import { getLogs } from './explorer'
|
|
||||||
import Web3 from 'web3'
|
|
||||||
|
|
||||||
const getPastEventsWithFallback = (
|
|
||||||
api: string,
|
|
||||||
web3: Web3 | null,
|
|
||||||
contract: Contract,
|
|
||||||
eventName: string,
|
|
||||||
options: any
|
|
||||||
) =>
|
|
||||||
contract
|
|
||||||
.getPastEvents(eventName, options)
|
|
||||||
.catch(() => (api && web3 ? getLogs(api, web3, contract, eventName, options) : []))
|
|
||||||
|
|
||||||
export const getRequiredBlockConfirmations = async (
|
|
||||||
contract: Contract,
|
|
||||||
blockNumber: number,
|
|
||||||
snapshotProvider: SnapshotProvider,
|
|
||||||
web3: Web3 | null = null,
|
|
||||||
api: string = ''
|
|
||||||
) => {
|
|
||||||
let blockConfirmations
|
|
||||||
|
|
||||||
try {
|
|
||||||
blockConfirmations = await contract.methods.requiredBlockConfirmations().call()
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
if (blockConfirmations) {
|
|
||||||
return parseInt(blockConfirmations)
|
|
||||||
}
|
|
||||||
|
|
||||||
const eventsFromSnapshot = snapshotProvider.requiredBlockConfirmationEvents(blockNumber)
|
|
||||||
const snapshotBlockNumber = snapshotProvider.snapshotBlockNumber()
|
|
||||||
|
|
||||||
let contractEvents: EventData[] = []
|
|
||||||
if (blockNumber > snapshotBlockNumber) {
|
|
||||||
contractEvents = await getPastEventsWithFallback(api, web3, contract, 'RequiredBlockConfirmationChanged', {
|
|
||||||
fromBlock: snapshotBlockNumber + 1,
|
|
||||||
toBlock: blockNumber
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const events = [...eventsFromSnapshot, ...contractEvents]
|
|
||||||
|
|
||||||
// Use the value from last event before the transaction
|
|
||||||
const event = events[events.length - 1]
|
|
||||||
blockConfirmations = event.returnValues.requiredBlockConfirmations
|
|
||||||
|
|
||||||
return parseInt(blockConfirmations)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getValidatorAddress = (contract: Contract) => contract.methods.validatorContract().call()
|
|
||||||
|
|
||||||
export const getRequiredSignatures = async (
|
|
||||||
contract: Contract,
|
|
||||||
blockNumber: number | 'latest',
|
|
||||||
snapshotProvider: SnapshotProvider,
|
|
||||||
web3: Web3 | null = null,
|
|
||||||
api: string = ''
|
|
||||||
) => {
|
|
||||||
let requiredSignatures
|
|
||||||
|
|
||||||
try {
|
|
||||||
requiredSignatures = await contract.methods.requiredSignatures().call()
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
if (requiredSignatures) {
|
|
||||||
return parseInt(requiredSignatures)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (blockNumber === 'latest') {
|
|
||||||
return contract.methods.requiredSignatures().call()
|
|
||||||
}
|
|
||||||
|
|
||||||
const eventsFromSnapshot = snapshotProvider.requiredSignaturesEvents(blockNumber)
|
|
||||||
const snapshotBlockNumber = snapshotProvider.snapshotBlockNumber()
|
|
||||||
|
|
||||||
let contractEvents: EventData[] = []
|
|
||||||
if (blockNumber > snapshotBlockNumber) {
|
|
||||||
contractEvents = await getPastEventsWithFallback(api, web3, contract, 'RequiredSignaturesChanged', {
|
|
||||||
fromBlock: snapshotBlockNumber + 1,
|
|
||||||
toBlock: blockNumber
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const events = [...eventsFromSnapshot, ...contractEvents]
|
|
||||||
|
|
||||||
// Use the value form last event before the transaction
|
|
||||||
const event = events[events.length - 1]
|
|
||||||
;({ requiredSignatures } = event.returnValues)
|
|
||||||
return parseInt(requiredSignatures)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getValidatorList = async (
|
|
||||||
contract: Contract,
|
|
||||||
blockNumber: number | 'latest',
|
|
||||||
snapshotProvider: SnapshotProvider,
|
|
||||||
web3: Web3 | null = null,
|
|
||||||
api: string = ''
|
|
||||||
) => {
|
|
||||||
try {
|
|
||||||
const currentList = await contract.methods.validatorList().call()
|
|
||||||
|
|
||||||
if (currentList) {
|
|
||||||
return currentList
|
|
||||||
}
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
const addedEventsFromSnapshot = snapshotProvider.validatorAddedEvents(blockNumber)
|
|
||||||
const removedEventsFromSnapshot = snapshotProvider.validatorRemovedEvents(blockNumber)
|
|
||||||
const snapshotBlockNumber = snapshotProvider.snapshotBlockNumber()
|
|
||||||
|
|
||||||
const fromBlock = snapshotBlockNumber > blockNumber ? snapshotBlockNumber + 1 : blockNumber
|
|
||||||
const [currentList, added, removed] = await Promise.all([
|
|
||||||
contract.methods.validatorList().call(),
|
|
||||||
getPastEventsWithFallback(api, web3, contract, 'ValidatorAdded', {
|
|
||||||
fromBlock
|
|
||||||
}),
|
|
||||||
getPastEventsWithFallback(api, web3, contract, 'ValidatorRemoved', {
|
|
||||||
fromBlock
|
|
||||||
})
|
|
||||||
])
|
|
||||||
|
|
||||||
// Ordered desc
|
|
||||||
const orderedEvents = [...addedEventsFromSnapshot, ...added, ...removedEventsFromSnapshot, ...removed].sort(
|
|
||||||
({ blockNumber: prev }, { blockNumber: next }) => next - prev
|
|
||||||
)
|
|
||||||
|
|
||||||
// Stored as a Set to avoid duplicates
|
|
||||||
const validatorList = new Set(currentList)
|
|
||||||
|
|
||||||
orderedEvents.forEach(e => {
|
|
||||||
const { validator } = e.returnValues
|
|
||||||
if (e.event === 'ValidatorRemoved') {
|
|
||||||
validatorList.add(validator)
|
|
||||||
} else if (e.event === 'ValidatorAdded') {
|
|
||||||
validatorList.delete(validator)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return Array.from(validatorList)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getMessagesSigned = (contract: Contract, hash: string) => contract.methods.messagesSigned(hash).call()
|
|
||||||
|
|
||||||
export const getAffirmationsSigned = (contract: Contract, hash: string) =>
|
|
||||||
contract.methods.affirmationsSigned(hash).call()
|
|
||||||
@ -1,312 +0,0 @@
|
|||||||
import {
|
|
||||||
BLOCK_RANGE,
|
|
||||||
EXECUTE_AFFIRMATION_HASH,
|
|
||||||
EXECUTE_SIGNATURES_HASH,
|
|
||||||
FOREIGN_EXPLORER_API,
|
|
||||||
HOME_EXPLORER_API,
|
|
||||||
MAX_TX_SEARCH_BLOCK_RANGE,
|
|
||||||
SUBMIT_SIGNATURE_HASH
|
|
||||||
} from '../config/constants'
|
|
||||||
import { AbiItem } from 'web3-utils'
|
|
||||||
import Web3 from 'web3'
|
|
||||||
import { Contract } from 'web3-eth-contract'
|
|
||||||
|
|
||||||
export interface APITransaction {
|
|
||||||
from: string
|
|
||||||
timeStamp: string
|
|
||||||
isError: string
|
|
||||||
input: string
|
|
||||||
to: string
|
|
||||||
hash: string
|
|
||||||
blockNumber: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface APIPendingTransaction {
|
|
||||||
input: string
|
|
||||||
to: string
|
|
||||||
hash: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PendingTransactionsParams {
|
|
||||||
account: string
|
|
||||||
api: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AccountTransactionsParams {
|
|
||||||
account: string
|
|
||||||
startBlock: number
|
|
||||||
endBlock: number
|
|
||||||
api: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface GetPendingTransactionParams {
|
|
||||||
account: string
|
|
||||||
to: string
|
|
||||||
messageData: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface GetTransactionParams extends GetPendingTransactionParams {
|
|
||||||
startBlock: number
|
|
||||||
endBlock: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export const fetchAccountTransactions = async ({ account, startBlock, endBlock, api }: AccountTransactionsParams) => {
|
|
||||||
const url = new URL(api)
|
|
||||||
url.searchParams.append('module', 'account')
|
|
||||||
url.searchParams.append('action', 'txlist')
|
|
||||||
url.searchParams.append('address', account)
|
|
||||||
url.searchParams.append('filterby', 'to')
|
|
||||||
url.searchParams.append('startblock', startBlock.toString())
|
|
||||||
url.searchParams.append('endblock', endBlock.toString())
|
|
||||||
|
|
||||||
const result = await fetch(url.toString()).then(res => res.json())
|
|
||||||
|
|
||||||
if (result.message === 'No transactions found') {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.result || []
|
|
||||||
}
|
|
||||||
|
|
||||||
export const fetchPendingTransactions = async ({
|
|
||||||
account,
|
|
||||||
api
|
|
||||||
}: PendingTransactionsParams): Promise<APIPendingTransaction[]> => {
|
|
||||||
if (!api.includes('blockscout')) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
const url = new URL(api)
|
|
||||||
url.searchParams.append('module', 'account')
|
|
||||||
url.searchParams.append('action', 'pendingtxlist')
|
|
||||||
url.searchParams.append('address', account)
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = await fetch(url.toString()).then(res => res.json())
|
|
||||||
if (result.status === '0') {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.result
|
|
||||||
} catch (e) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getClosestBlockByTimestamp = async (api: string, timestamp: number): Promise<number> => {
|
|
||||||
if (api.includes('blockscout')) {
|
|
||||||
throw new Error('Blockscout does not support getblocknobytime')
|
|
||||||
}
|
|
||||||
|
|
||||||
const url = new URL(api)
|
|
||||||
url.searchParams.append('module', 'block')
|
|
||||||
url.searchParams.append('action', 'getblocknobytime')
|
|
||||||
url.searchParams.append('timestamp', timestamp.toString())
|
|
||||||
url.searchParams.append('closest', 'before')
|
|
||||||
|
|
||||||
const blockNumber = await fetch(url.toString()).then(res => res.json())
|
|
||||||
|
|
||||||
return parseInt(blockNumber.result)
|
|
||||||
}
|
|
||||||
|
|
||||||
// fast version of fetchAccountTransactions
|
|
||||||
// sequentially fetches transactions in small batches
|
|
||||||
// caches the result
|
|
||||||
const transactionsCache: { [key: string]: { lastBlock: number; transactions: APITransaction[] } } = {}
|
|
||||||
export const getAccountTransactions = async ({
|
|
||||||
account,
|
|
||||||
startBlock,
|
|
||||||
endBlock,
|
|
||||||
api
|
|
||||||
}: AccountTransactionsParams): Promise<APITransaction[]> => {
|
|
||||||
const key = `${account}-${startBlock}-${api}`
|
|
||||||
|
|
||||||
// initialize empty cache if it doesn't exist yet
|
|
||||||
if (!transactionsCache[key]) {
|
|
||||||
transactionsCache[key] = { lastBlock: startBlock - 1, transactions: [] }
|
|
||||||
}
|
|
||||||
|
|
||||||
// if cache contains events up to block X,
|
|
||||||
// new batch is fetched for range [X + 1, X + 1 + BLOCK_RANGE]
|
|
||||||
const newStartBlock = transactionsCache[key].lastBlock + 1
|
|
||||||
const newEndBlock = newStartBlock + BLOCK_RANGE
|
|
||||||
|
|
||||||
// search for new transactions only if max allowed block range is not yet exceeded
|
|
||||||
if (newEndBlock <= startBlock + MAX_TX_SEARCH_BLOCK_RANGE) {
|
|
||||||
const newTransactions = await fetchAccountTransactions({
|
|
||||||
account,
|
|
||||||
startBlock: newStartBlock,
|
|
||||||
endBlock: newEndBlock,
|
|
||||||
api
|
|
||||||
})
|
|
||||||
|
|
||||||
const transactions = transactionsCache[key].transactions.concat(...newTransactions)
|
|
||||||
|
|
||||||
// cache updated transactions list
|
|
||||||
transactionsCache[key].transactions = transactions
|
|
||||||
|
|
||||||
// enbBlock is assumed to be the current block number of the chain
|
|
||||||
// if the whole range is finalized, last block can be safely updated to the end of the range
|
|
||||||
// this works even if there are no transactions in the list
|
|
||||||
if (newEndBlock < endBlock) {
|
|
||||||
transactionsCache[key].lastBlock = newEndBlock
|
|
||||||
} else if (transactions.length > 0) {
|
|
||||||
transactionsCache[key].lastBlock = parseInt(transactions[transactions.length - 1].blockNumber, 10)
|
|
||||||
}
|
|
||||||
|
|
||||||
return transactions
|
|
||||||
}
|
|
||||||
|
|
||||||
console.warn(`Reached max transaction searching range, returning previously cached transactions for ${account}`)
|
|
||||||
return transactionsCache[key].transactions
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getLogs = async (
|
|
||||||
api: string,
|
|
||||||
web3: Web3,
|
|
||||||
contract: Contract,
|
|
||||||
event: string,
|
|
||||||
options: { fromBlock: number; toBlock: number | 'latest'; topics: (string | null)[] }
|
|
||||||
) => {
|
|
||||||
const abi = contract.options.jsonInterface.find((abi: AbiItem) => abi.type === 'event' && abi.name === event)!
|
|
||||||
|
|
||||||
const url = new URL(api)
|
|
||||||
url.searchParams.append('module', 'logs')
|
|
||||||
url.searchParams.append('action', 'getLogs')
|
|
||||||
url.searchParams.append('address', contract.options.address)
|
|
||||||
url.searchParams.append('fromBlock', options.fromBlock.toString())
|
|
||||||
url.searchParams.append('toBlock', (options.toBlock || 'latest').toString())
|
|
||||||
|
|
||||||
const topics = [web3.eth.abi.encodeEventSignature(abi), ...(options.topics || [])]
|
|
||||||
for (let i = 0; i < topics.length; i++) {
|
|
||||||
if (topics[i] !== null) {
|
|
||||||
url.searchParams.append(`topic${i}`, topics[i] as string)
|
|
||||||
for (let j = 0; j < i; j++) {
|
|
||||||
if (topics[j] !== null) {
|
|
||||||
url.searchParams.append(`topic${j}_${i}_opr`, 'and')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const logs = await fetch(url.toString()).then(res => res.json())
|
|
||||||
|
|
||||||
return logs.result.map((log: any) => ({
|
|
||||||
transactionHash: log.transactionHash,
|
|
||||||
blockNumber: parseInt(log.blockNumber.slice(2), 16),
|
|
||||||
returnValues: web3.eth.abi.decodeLog(abi.inputs!, log.data, log.topics.slice(1))
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
const filterSender = (from: string) => (tx: APITransaction) => tx.from.toLowerCase() === from.toLowerCase()
|
|
||||||
|
|
||||||
export const getFailedTransactions = async (
|
|
||||||
account: string,
|
|
||||||
to: string,
|
|
||||||
startBlock: number,
|
|
||||||
endBlock: number,
|
|
||||||
api: string,
|
|
||||||
getAccountTransactionsMethod = getAccountTransactions
|
|
||||||
): Promise<APITransaction[]> => {
|
|
||||||
const transactions = await getAccountTransactionsMethod({ account: to, startBlock, endBlock, api })
|
|
||||||
|
|
||||||
return transactions.filter(t => t.isError !== '0').filter(filterSender(account))
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getSuccessTransactions = async (
|
|
||||||
account: string,
|
|
||||||
to: string,
|
|
||||||
startBlock: number,
|
|
||||||
endBlock: number,
|
|
||||||
api: string,
|
|
||||||
getAccountTransactionsMethod = getAccountTransactions
|
|
||||||
): Promise<APITransaction[]> => {
|
|
||||||
const transactions = await getAccountTransactionsMethod({ account: to, startBlock, endBlock, api })
|
|
||||||
|
|
||||||
return transactions.filter(t => t.isError === '0').filter(filterSender(account))
|
|
||||||
}
|
|
||||||
|
|
||||||
export const filterValidatorSignatureTransaction = (
|
|
||||||
transactions: APITransaction[],
|
|
||||||
messageData: string
|
|
||||||
): APITransaction[] => {
|
|
||||||
const messageDataValue = messageData.replace('0x', '')
|
|
||||||
return transactions.filter(
|
|
||||||
t =>
|
|
||||||
(t.input.includes(SUBMIT_SIGNATURE_HASH) || t.input.includes(EXECUTE_AFFIRMATION_HASH)) &&
|
|
||||||
t.input.includes(messageDataValue)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getValidatorFailedTransactionsForMessage = async ({
|
|
||||||
account,
|
|
||||||
to,
|
|
||||||
messageData,
|
|
||||||
startBlock,
|
|
||||||
endBlock
|
|
||||||
}: GetTransactionParams): Promise<APITransaction[]> => {
|
|
||||||
const failedTransactions = await getFailedTransactions(account, to, startBlock, endBlock, HOME_EXPLORER_API)
|
|
||||||
|
|
||||||
return filterValidatorSignatureTransaction(failedTransactions, messageData)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getValidatorSuccessTransactionsForMessage = async ({
|
|
||||||
account,
|
|
||||||
to,
|
|
||||||
messageData,
|
|
||||||
startBlock,
|
|
||||||
endBlock
|
|
||||||
}: GetTransactionParams): Promise<APITransaction[]> => {
|
|
||||||
const transactions = await getSuccessTransactions(account, to, startBlock, endBlock, HOME_EXPLORER_API)
|
|
||||||
|
|
||||||
return filterValidatorSignatureTransaction(transactions, messageData)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getExecutionFailedTransactionForMessage = async (
|
|
||||||
{ account, to, messageData, startBlock, endBlock }: GetTransactionParams,
|
|
||||||
getFailedTransactionsMethod = getFailedTransactions
|
|
||||||
): Promise<APITransaction[]> => {
|
|
||||||
const failedTransactions = await getFailedTransactionsMethod(account, to, startBlock, endBlock, FOREIGN_EXPLORER_API)
|
|
||||||
|
|
||||||
const messageDataValue = messageData.replace('0x', '')
|
|
||||||
return failedTransactions.filter(t => t.input.includes(EXECUTE_SIGNATURES_HASH) && t.input.includes(messageDataValue))
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getValidatorPendingTransactionsForMessage = async (
|
|
||||||
{ account, to, messageData }: GetPendingTransactionParams,
|
|
||||||
fetchPendingTransactionsMethod = fetchPendingTransactions
|
|
||||||
): Promise<APIPendingTransaction[]> => {
|
|
||||||
const pendingTransactions = await fetchPendingTransactionsMethod({
|
|
||||||
account,
|
|
||||||
api: HOME_EXPLORER_API
|
|
||||||
})
|
|
||||||
|
|
||||||
const toAddressLowerCase = to.toLowerCase()
|
|
||||||
const messageDataValue = messageData.replace('0x', '')
|
|
||||||
|
|
||||||
return pendingTransactions.filter(
|
|
||||||
t =>
|
|
||||||
t.to.toLowerCase() === toAddressLowerCase &&
|
|
||||||
(t.input.includes(SUBMIT_SIGNATURE_HASH) || t.input.includes(EXECUTE_AFFIRMATION_HASH)) &&
|
|
||||||
t.input.includes(messageDataValue)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getExecutionPendingTransactionsForMessage = async (
|
|
||||||
{ account, to, messageData }: GetPendingTransactionParams,
|
|
||||||
fetchPendingTransactionsMethod = fetchPendingTransactions
|
|
||||||
): Promise<APIPendingTransaction[]> => {
|
|
||||||
const pendingTransactions = await fetchPendingTransactionsMethod({
|
|
||||||
account,
|
|
||||||
api: FOREIGN_EXPLORER_API
|
|
||||||
})
|
|
||||||
|
|
||||||
const toAddressLowerCase = to.toLowerCase()
|
|
||||||
const messageDataValue = messageData.replace('0x', '')
|
|
||||||
|
|
||||||
return pendingTransactions.filter(
|
|
||||||
t =>
|
|
||||||
t.to.toLowerCase() === toAddressLowerCase &&
|
|
||||||
t.input.includes(EXECUTE_SIGNATURES_HASH) &&
|
|
||||||
t.input.includes(messageDataValue)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@ -1,201 +0,0 @@
|
|||||||
import Web3 from 'web3'
|
|
||||||
import { Contract } from 'web3-eth-contract'
|
|
||||||
import { HOME_RPC_POLLING_INTERVAL, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants'
|
|
||||||
import { GetTransactionParams, APITransaction, APIPendingTransaction, GetPendingTransactionParams } from './explorer'
|
|
||||||
import {
|
|
||||||
getValidatorConfirmation,
|
|
||||||
getValidatorFailedTransaction,
|
|
||||||
getValidatorPendingTransaction,
|
|
||||||
getSuccessExecutionTransaction
|
|
||||||
} from './validatorConfirmationHelpers'
|
|
||||||
import { ConfirmationParam } from '../hooks/useMessageConfirmations'
|
|
||||||
import { signatureToVRS } from './signatures'
|
|
||||||
|
|
||||||
const mergeConfirmations = (oldConfirmations: ConfirmationParam[], newConfirmations: ConfirmationParam[]) => {
|
|
||||||
const confirmations = [...oldConfirmations]
|
|
||||||
newConfirmations.forEach(validatorData => {
|
|
||||||
const index = confirmations.findIndex(e => e.validator === validatorData.validator)
|
|
||||||
if (index === -1) {
|
|
||||||
confirmations.push(validatorData)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const currentStatus = confirmations[index].status
|
|
||||||
const newStatus = validatorData.status
|
|
||||||
if (
|
|
||||||
validatorData.txHash ||
|
|
||||||
!!validatorData.signature ||
|
|
||||||
(newStatus !== currentStatus && newStatus !== VALIDATOR_CONFIRMATION_STATUS.UNDEFINED)
|
|
||||||
) {
|
|
||||||
confirmations[index] = {
|
|
||||||
status: validatorData.status,
|
|
||||||
validator: validatorData.validator,
|
|
||||||
timestamp: confirmations[index].timestamp || validatorData.timestamp,
|
|
||||||
txHash: confirmations[index].txHash || validatorData.txHash,
|
|
||||||
signature: confirmations[index].signature || validatorData.signature
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return confirmations
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getConfirmationsForTx = async (
|
|
||||||
messageData: string,
|
|
||||||
web3: Web3,
|
|
||||||
validatorList: string[],
|
|
||||||
bridgeContract: Contract,
|
|
||||||
fromHome: boolean,
|
|
||||||
setResult: Function,
|
|
||||||
requiredSignatures: number,
|
|
||||||
setSignatureCollected: Function,
|
|
||||||
setTimeoutId: (timeoutId: number) => void,
|
|
||||||
isCancelled: () => boolean,
|
|
||||||
startBlock: number,
|
|
||||||
getFailedTransactions: (args: GetTransactionParams) => Promise<APITransaction[]>,
|
|
||||||
setFailedConfirmations: Function,
|
|
||||||
getPendingTransactions: (args: GetPendingTransactionParams) => Promise<APIPendingTransaction[]>,
|
|
||||||
setPendingConfirmations: Function,
|
|
||||||
getSuccessTransactions: (args: GetTransactionParams) => Promise<APITransaction[]>
|
|
||||||
) => {
|
|
||||||
const hashMsg = web3.utils.soliditySha3Raw(messageData)
|
|
||||||
let validatorConfirmations = await Promise.all(
|
|
||||||
validatorList.map(getValidatorConfirmation(web3, hashMsg, bridgeContract, fromHome))
|
|
||||||
)
|
|
||||||
|
|
||||||
const updateConfirmations = (confirmations: ConfirmationParam[]) => {
|
|
||||||
if (confirmations.length === 0) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
validatorConfirmations = mergeConfirmations(validatorConfirmations, confirmations)
|
|
||||||
setResult((currentConfirmations: ConfirmationParam[]) => {
|
|
||||||
if (currentConfirmations && currentConfirmations.length) {
|
|
||||||
return mergeConfirmations(currentConfirmations, confirmations)
|
|
||||||
}
|
|
||||||
return confirmations
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const successConfirmations = validatorConfirmations.filter(c => c.status === VALIDATOR_CONFIRMATION_STATUS.SUCCESS)
|
|
||||||
const notSuccessConfirmations = validatorConfirmations.filter(c => c.status !== VALIDATOR_CONFIRMATION_STATUS.SUCCESS)
|
|
||||||
const hasEnoughSignatures = successConfirmations.length >= requiredSignatures
|
|
||||||
|
|
||||||
updateConfirmations(validatorConfirmations)
|
|
||||||
setSignatureCollected(hasEnoughSignatures)
|
|
||||||
|
|
||||||
if (hasEnoughSignatures) {
|
|
||||||
setPendingConfirmations(false)
|
|
||||||
if (fromHome) {
|
|
||||||
// fetch collected signatures for possible manual processing
|
|
||||||
const signatures = await Promise.all(
|
|
||||||
Array.from(Array(requiredSignatures).keys()).map(i => bridgeContract.methods.signature(hashMsg, i).call())
|
|
||||||
)
|
|
||||||
const confirmations = signatures.flatMap(sig => {
|
|
||||||
const { v, r, s } = signatureToVRS(sig)
|
|
||||||
const address = web3.eth.accounts.recover(messageData, `0x${v}`, `0x${r}`, `0x${s}`)
|
|
||||||
return successConfirmations.filter(c => c.validator === address).map(c => ({ ...c, signature: sig }))
|
|
||||||
})
|
|
||||||
updateConfirmations(confirmations)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// get transactions from success signatures
|
|
||||||
const successConfirmationWithData = await Promise.all(
|
|
||||||
successConfirmations.map(
|
|
||||||
getSuccessExecutionTransaction(web3, bridgeContract, fromHome, messageData, startBlock, getSuccessTransactions)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
const successConfirmationWithTxFound = successConfirmationWithData.filter(v => v.txHash !== '')
|
|
||||||
updateConfirmations(successConfirmationWithTxFound)
|
|
||||||
|
|
||||||
// If signatures not collected, look for pending transactions
|
|
||||||
if (!hasEnoughSignatures) {
|
|
||||||
// Check if confirmation is pending
|
|
||||||
const validatorPendingConfirmationsChecks = await Promise.all(
|
|
||||||
notSuccessConfirmations.map(getValidatorPendingTransaction(bridgeContract, messageData, getPendingTransactions))
|
|
||||||
)
|
|
||||||
|
|
||||||
const validatorPendingConfirmations = validatorPendingConfirmationsChecks.filter(
|
|
||||||
c => c.status === VALIDATOR_CONFIRMATION_STATUS.PENDING
|
|
||||||
)
|
|
||||||
updateConfirmations(validatorPendingConfirmations)
|
|
||||||
setPendingConfirmations(validatorPendingConfirmations.length > 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
const undefinedConfirmations = validatorConfirmations.filter(
|
|
||||||
c => c.status === VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
|
|
||||||
)
|
|
||||||
|
|
||||||
// Check if confirmation failed
|
|
||||||
const validatorFailedConfirmationsChecks = await Promise.all(
|
|
||||||
undefinedConfirmations.map(
|
|
||||||
getValidatorFailedTransaction(web3, bridgeContract, messageData, startBlock, getFailedTransactions)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
let validatorFailedConfirmations = validatorFailedConfirmationsChecks.filter(
|
|
||||||
c => c.status === VALIDATOR_CONFIRMATION_STATUS.FAILED || c.status === VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID
|
|
||||||
)
|
|
||||||
if (hasEnoughSignatures && !fromHome) {
|
|
||||||
const lastTS = Math.max(...successConfirmationWithTxFound.map(c => c.timestamp || 0))
|
|
||||||
validatorFailedConfirmations = validatorFailedConfirmations.map(
|
|
||||||
c =>
|
|
||||||
c.timestamp < lastTS
|
|
||||||
? c
|
|
||||||
: {
|
|
||||||
...c,
|
|
||||||
status: VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
setFailedConfirmations(
|
|
||||||
!hasEnoughSignatures && validatorFailedConfirmations.some(c => c.status === VALIDATOR_CONFIRMATION_STATUS.FAILED)
|
|
||||||
)
|
|
||||||
updateConfirmations(validatorFailedConfirmations)
|
|
||||||
|
|
||||||
const missingConfirmations = validatorConfirmations.filter(
|
|
||||||
c => c.status === VALIDATOR_CONFIRMATION_STATUS.UNDEFINED || c.status === VALIDATOR_CONFIRMATION_STATUS.PENDING
|
|
||||||
)
|
|
||||||
|
|
||||||
if (hasEnoughSignatures) {
|
|
||||||
// If signatures collected, it should set other signatures not found as not required
|
|
||||||
const notRequiredConfirmations = missingConfirmations.map(c => ({
|
|
||||||
validator: c.validator,
|
|
||||||
status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED,
|
|
||||||
timestamp: 0,
|
|
||||||
txHash: ''
|
|
||||||
}))
|
|
||||||
updateConfirmations(notRequiredConfirmations)
|
|
||||||
}
|
|
||||||
|
|
||||||
// retry if not all signatures are collected and some confirmations are still missing
|
|
||||||
// or some success transactions were not fetched successfully
|
|
||||||
if (
|
|
||||||
(!hasEnoughSignatures && missingConfirmations.length > 0) ||
|
|
||||||
successConfirmationWithTxFound.length < successConfirmationWithData.length
|
|
||||||
) {
|
|
||||||
if (!isCancelled()) {
|
|
||||||
const timeoutId = setTimeout(
|
|
||||||
() =>
|
|
||||||
getConfirmationsForTx(
|
|
||||||
messageData,
|
|
||||||
web3,
|
|
||||||
validatorList,
|
|
||||||
bridgeContract,
|
|
||||||
fromHome,
|
|
||||||
setResult,
|
|
||||||
requiredSignatures,
|
|
||||||
setSignatureCollected,
|
|
||||||
setTimeoutId,
|
|
||||||
isCancelled,
|
|
||||||
startBlock,
|
|
||||||
getFailedTransactions,
|
|
||||||
setFailedConfirmations,
|
|
||||||
getPendingTransactions,
|
|
||||||
setPendingConfirmations,
|
|
||||||
getSuccessTransactions
|
|
||||||
),
|
|
||||||
HOME_RPC_POLLING_INTERVAL
|
|
||||||
)
|
|
||||||
setTimeoutId(timeoutId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,182 +0,0 @@
|
|||||||
import { Contract, EventData } from 'web3-eth-contract'
|
|
||||||
import Web3 from 'web3'
|
|
||||||
import {
|
|
||||||
CACHE_KEY_EXECUTION_FAILED,
|
|
||||||
FOREIGN_EXPLORER_API,
|
|
||||||
FOREIGN_RPC_POLLING_INTERVAL,
|
|
||||||
HOME_EXPLORER_API,
|
|
||||||
HOME_RPC_POLLING_INTERVAL,
|
|
||||||
VALIDATOR_CONFIRMATION_STATUS
|
|
||||||
} from '../config/constants'
|
|
||||||
import { ExecutionData } from '../hooks/useMessageConfirmations'
|
|
||||||
import {
|
|
||||||
APIPendingTransaction,
|
|
||||||
APITransaction,
|
|
||||||
GetTransactionParams,
|
|
||||||
GetPendingTransactionParams,
|
|
||||||
getLogs
|
|
||||||
} from './explorer'
|
|
||||||
import { getBlock, MessageObject } from './web3'
|
|
||||||
import validatorsCache from '../services/ValidatorsCache'
|
|
||||||
import { foreignBlockNumberProvider, homeBlockNumberProvider } from '../services/BlockNumberProvider'
|
|
||||||
|
|
||||||
const getPastEventsWithFallback = (api: string, web3: Web3, contract: Contract, eventName: string, options: any) =>
|
|
||||||
contract.getPastEvents(eventName, options).catch(
|
|
||||||
() =>
|
|
||||||
api
|
|
||||||
? getLogs(api, web3, contract, eventName, {
|
|
||||||
fromBlock: options.fromBlock,
|
|
||||||
toBlock: options.toBlock,
|
|
||||||
topics: [null, null, options.filter.messageId]
|
|
||||||
})
|
|
||||||
: []
|
|
||||||
)
|
|
||||||
|
|
||||||
export const getSuccessExecutionData = async (
|
|
||||||
contract: Contract,
|
|
||||||
eventName: string,
|
|
||||||
web3: Web3,
|
|
||||||
messageId: string,
|
|
||||||
api: string = ''
|
|
||||||
) => {
|
|
||||||
// Since it filters by the message id, only one event will be fetched
|
|
||||||
// so there is no need to limit the range of the block to reduce the network traffic
|
|
||||||
const events: EventData[] = await getPastEventsWithFallback(api, web3, contract, eventName, {
|
|
||||||
fromBlock: 0,
|
|
||||||
toBlock: 'latest',
|
|
||||||
filter: {
|
|
||||||
messageId
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if (events.length > 0) {
|
|
||||||
const event = events[0]
|
|
||||||
const [txReceipt, block] = await Promise.all([
|
|
||||||
web3.eth.getTransactionReceipt(event.transactionHash),
|
|
||||||
getBlock(web3, event.blockNumber)
|
|
||||||
])
|
|
||||||
|
|
||||||
const blockTimestamp = typeof block.timestamp === 'string' ? parseInt(block.timestamp) : block.timestamp
|
|
||||||
const validatorAddress = web3.utils.toChecksumAddress(txReceipt.from)
|
|
||||||
|
|
||||||
return {
|
|
||||||
status: VALIDATOR_CONFIRMATION_STATUS.EXECUTION_SUCCESS,
|
|
||||||
validator: validatorAddress,
|
|
||||||
txHash: event.transactionHash,
|
|
||||||
timestamp: blockTimestamp,
|
|
||||||
executionResult: event.returnValues.status,
|
|
||||||
blockNumber: event.blockNumber
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getFinalizationEvent = async (
|
|
||||||
fromHome: boolean,
|
|
||||||
contract: Contract,
|
|
||||||
web3: Web3,
|
|
||||||
setResult: React.Dispatch<React.SetStateAction<ExecutionData>>,
|
|
||||||
message: MessageObject,
|
|
||||||
setTimeoutId: (timeoutId: number) => void,
|
|
||||||
isCancelled: () => boolean,
|
|
||||||
startBlock: number,
|
|
||||||
collectedSignaturesEvent: Maybe<EventData>,
|
|
||||||
getFailedExecution: (args: GetTransactionParams) => Promise<APITransaction[]>,
|
|
||||||
setFailedExecution: Function,
|
|
||||||
getPendingExecution: (args: GetPendingTransactionParams) => Promise<APIPendingTransaction[]>,
|
|
||||||
setPendingExecution: Function,
|
|
||||||
setExecutionEventsFetched: Function
|
|
||||||
) => {
|
|
||||||
const eventName = fromHome ? 'RelayedMessage' : 'AffirmationCompleted'
|
|
||||||
const api = fromHome ? FOREIGN_EXPLORER_API : HOME_EXPLORER_API
|
|
||||||
|
|
||||||
const successExecutionData = await getSuccessExecutionData(contract, eventName, web3, message.id, api)
|
|
||||||
|
|
||||||
if (successExecutionData) {
|
|
||||||
setResult(successExecutionData)
|
|
||||||
} else {
|
|
||||||
setExecutionEventsFetched(true)
|
|
||||||
// If event is defined, it means it is a message from Home to Foreign
|
|
||||||
if (collectedSignaturesEvent) {
|
|
||||||
const validator = collectedSignaturesEvent.returnValues.authorityResponsibleForRelay
|
|
||||||
|
|
||||||
const pendingTransactions = await getPendingExecution({
|
|
||||||
account: validator,
|
|
||||||
messageData: message.data,
|
|
||||||
to: contract.options.address
|
|
||||||
})
|
|
||||||
|
|
||||||
// If the transaction is pending it sets the status and avoid making the request for failed transactions
|
|
||||||
if (pendingTransactions.length > 0) {
|
|
||||||
const pendingTx = pendingTransactions[0]
|
|
||||||
|
|
||||||
const nowTimestamp = Math.floor(new Date().getTime() / 1000.0)
|
|
||||||
|
|
||||||
setResult({
|
|
||||||
status: VALIDATOR_CONFIRMATION_STATUS.PENDING,
|
|
||||||
validator: validator,
|
|
||||||
txHash: pendingTx.hash,
|
|
||||||
timestamp: nowTimestamp,
|
|
||||||
executionResult: false,
|
|
||||||
blockNumber: 0
|
|
||||||
})
|
|
||||||
setPendingExecution(true)
|
|
||||||
} else {
|
|
||||||
const validatorExecutionCacheKey = `${CACHE_KEY_EXECUTION_FAILED}${validator}-${message.id}`
|
|
||||||
const failedFromCache = validatorsCache.get(validatorExecutionCacheKey)
|
|
||||||
const blockProvider = fromHome ? foreignBlockNumberProvider : homeBlockNumberProvider
|
|
||||||
|
|
||||||
if (!failedFromCache) {
|
|
||||||
const failedTransactions = await getFailedExecution({
|
|
||||||
account: validator,
|
|
||||||
to: contract.options.address,
|
|
||||||
messageData: message.data,
|
|
||||||
startBlock,
|
|
||||||
endBlock: blockProvider.get() || 0
|
|
||||||
})
|
|
||||||
|
|
||||||
if (failedTransactions.length > 0) {
|
|
||||||
const failedTx = failedTransactions[0]
|
|
||||||
|
|
||||||
// If validator execution failed, we cache the result to avoid doing future requests for a result that won't change
|
|
||||||
validatorsCache.set(validatorExecutionCacheKey, true)
|
|
||||||
|
|
||||||
const timestamp = parseInt(failedTx.timeStamp)
|
|
||||||
setResult({
|
|
||||||
status: VALIDATOR_CONFIRMATION_STATUS.FAILED,
|
|
||||||
validator: validator,
|
|
||||||
txHash: failedTx.hash,
|
|
||||||
timestamp,
|
|
||||||
executionResult: false,
|
|
||||||
blockNumber: parseInt(failedTx.blockNumber)
|
|
||||||
})
|
|
||||||
setFailedExecution(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isCancelled()) {
|
|
||||||
const timeoutId = setTimeout(
|
|
||||||
() =>
|
|
||||||
getFinalizationEvent(
|
|
||||||
fromHome,
|
|
||||||
contract,
|
|
||||||
web3,
|
|
||||||
setResult,
|
|
||||||
message,
|
|
||||||
setTimeoutId,
|
|
||||||
isCancelled,
|
|
||||||
startBlock,
|
|
||||||
collectedSignaturesEvent,
|
|
||||||
getFailedExecution,
|
|
||||||
setFailedExecution,
|
|
||||||
getPendingExecution,
|
|
||||||
setPendingExecution,
|
|
||||||
setExecutionEventsFetched
|
|
||||||
),
|
|
||||||
fromHome ? FOREIGN_RPC_POLLING_INTERVAL : HOME_RPC_POLLING_INTERVAL
|
|
||||||
)
|
|
||||||
setTimeoutId(timeoutId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,42 +0,0 @@
|
|||||||
import { formatDistance } from 'date-fns'
|
|
||||||
import {
|
|
||||||
CONFIRMATIONS_STATUS_DESCRIPTION,
|
|
||||||
CONFIRMATIONS_STATUS_DESCRIPTION_HOME,
|
|
||||||
TRANSACTION_STATUS_DESCRIPTION
|
|
||||||
} from '../config/descriptions'
|
|
||||||
import { FOREIGN_EXPLORER_TX_TEMPLATE, HOME_EXPLORER_TX_TEMPLATE } from '../config/constants'
|
|
||||||
|
|
||||||
export const validTxHash = (txHash: string) => /^0x[a-fA-F0-9]{64}$/.test(txHash)
|
|
||||||
|
|
||||||
export const formatTxHash = (txHash: string) => `${txHash.substring(0, 6)}...${txHash.substring(txHash.length - 4)}`
|
|
||||||
|
|
||||||
export const getExplorerTxUrl = (txHash: string, isHome: boolean) => {
|
|
||||||
const template = isHome ? HOME_EXPLORER_TX_TEMPLATE : FOREIGN_EXPLORER_TX_TEMPLATE
|
|
||||||
return template.replace('%s', txHash)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const formatTxHashExtended = (txHash: string) =>
|
|
||||||
`${txHash.substring(0, 10)}...${txHash.substring(txHash.length - 8)}`
|
|
||||||
|
|
||||||
export const formatTimestamp = (timestamp: number): string => {
|
|
||||||
const txDate = new Date(0).setUTCSeconds(timestamp)
|
|
||||||
return formatDistance(txDate, new Date(), {
|
|
||||||
addSuffix: true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getTransactionStatusDescription = (status: string, timestamp: Maybe<number> = null) => {
|
|
||||||
let description = TRANSACTION_STATUS_DESCRIPTION[status]
|
|
||||||
|
|
||||||
if (timestamp) {
|
|
||||||
description = description.replace('%t', formatTimestamp(timestamp))
|
|
||||||
}
|
|
||||||
|
|
||||||
return description
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getConfirmationsStatusDescription = (status: string, home: string, foreign: string, fromHome: boolean) => {
|
|
||||||
const statusDescription = fromHome ? CONFIRMATIONS_STATUS_DESCRIPTION_HOME : CONFIRMATIONS_STATUS_DESCRIPTION
|
|
||||||
|
|
||||||
return statusDescription[status]
|
|
||||||
}
|
|
||||||
@ -1,26 +0,0 @@
|
|||||||
import Web3 from 'web3'
|
|
||||||
|
|
||||||
function strip0x(s: string) {
|
|
||||||
return Web3.utils.isHexStrict(s) ? s.substr(2) : s
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Signature {
|
|
||||||
v: string
|
|
||||||
r: string
|
|
||||||
s: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export function signatureToVRS(rawSignature: string): Signature {
|
|
||||||
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 }
|
|
||||||
}
|
|
||||||
|
|
||||||
export function packSignatures(array: Array<Signature>): string {
|
|
||||||
const length = strip0x(Web3.utils.toHex(array.length))
|
|
||||||
const msgLength = length.length === 1 ? `0${length}` : length
|
|
||||||
const [v, r, s] = array.reduce(([vs, rs, ss], { v, r, s }) => [vs + v, rs + r, ss + s], ['', '', ''])
|
|
||||||
return `0x${msgLength}${v}${r}${s}`
|
|
||||||
}
|
|
||||||
@ -1,176 +0,0 @@
|
|||||||
import Web3 from 'web3'
|
|
||||||
import { Contract } from 'web3-eth-contract'
|
|
||||||
import { ConfirmationParam } from '../hooks/useMessageConfirmations'
|
|
||||||
import validatorsCache from '../services/ValidatorsCache'
|
|
||||||
import { CACHE_KEY_FAILED, CACHE_KEY_SUCCESS, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants'
|
|
||||||
import { APIPendingTransaction, APITransaction, GetTransactionParams, GetPendingTransactionParams } from './explorer'
|
|
||||||
import { homeBlockNumberProvider } from '../services/BlockNumberProvider'
|
|
||||||
import { getAffirmationsSigned, getMessagesSigned } from './contract'
|
|
||||||
|
|
||||||
export const getValidatorConfirmation = (
|
|
||||||
web3: Web3,
|
|
||||||
hashMsg: string,
|
|
||||||
bridgeContract: Contract,
|
|
||||||
fromHome: boolean
|
|
||||||
) => async (validator: string): Promise<ConfirmationParam> => {
|
|
||||||
const hashSenderMsg = web3.utils.soliditySha3Raw(validator, hashMsg)
|
|
||||||
|
|
||||||
const fromCache = validatorsCache.getData(hashSenderMsg)
|
|
||||||
if (fromCache) {
|
|
||||||
return fromCache
|
|
||||||
}
|
|
||||||
|
|
||||||
const confirmationContractMethod = fromHome ? getMessagesSigned : getAffirmationsSigned
|
|
||||||
const confirmed = await confirmationContractMethod(bridgeContract, hashSenderMsg)
|
|
||||||
|
|
||||||
// If validator confirmed signature, we cache the result to avoid doing future requests for a result that won't change
|
|
||||||
if (confirmed) {
|
|
||||||
const confirmation: ConfirmationParam = {
|
|
||||||
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
|
|
||||||
validator,
|
|
||||||
timestamp: 0,
|
|
||||||
txHash: ''
|
|
||||||
}
|
|
||||||
validatorsCache.setData(hashSenderMsg, confirmation)
|
|
||||||
return confirmation
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
|
||||||
validator,
|
|
||||||
timestamp: 0,
|
|
||||||
txHash: ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getSuccessExecutionTransaction = (
|
|
||||||
web3: Web3,
|
|
||||||
bridgeContract: Contract,
|
|
||||||
fromHome: boolean,
|
|
||||||
messageData: string,
|
|
||||||
startBlock: number,
|
|
||||||
getSuccessTransactions: (args: GetTransactionParams) => Promise<APITransaction[]>
|
|
||||||
) => async (validatorData: ConfirmationParam): Promise<ConfirmationParam> => {
|
|
||||||
const { validator } = validatorData
|
|
||||||
const validatorCacheKey = `${CACHE_KEY_SUCCESS}${validatorData.validator}-${messageData}`
|
|
||||||
const fromCache = validatorsCache.getData(validatorCacheKey)
|
|
||||||
|
|
||||||
if (fromCache && fromCache.txHash) {
|
|
||||||
return fromCache
|
|
||||||
}
|
|
||||||
|
|
||||||
const transactions = await getSuccessTransactions({
|
|
||||||
account: validatorData.validator,
|
|
||||||
to: bridgeContract.options.address,
|
|
||||||
messageData,
|
|
||||||
startBlock,
|
|
||||||
endBlock: homeBlockNumberProvider.get() || 0
|
|
||||||
})
|
|
||||||
|
|
||||||
let txHashTimestamp = 0
|
|
||||||
let txHash = ''
|
|
||||||
const status = VALIDATOR_CONFIRMATION_STATUS.SUCCESS
|
|
||||||
|
|
||||||
if (transactions.length > 0) {
|
|
||||||
const tx = transactions[0]
|
|
||||||
txHashTimestamp = parseInt(tx.timeStamp)
|
|
||||||
txHash = tx.hash
|
|
||||||
|
|
||||||
// cache the result
|
|
||||||
validatorsCache.setData(validatorCacheKey, {
|
|
||||||
validator,
|
|
||||||
status,
|
|
||||||
txHash,
|
|
||||||
timestamp: txHashTimestamp
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
validator,
|
|
||||||
status,
|
|
||||||
txHash,
|
|
||||||
timestamp: txHashTimestamp
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getValidatorFailedTransaction = (
|
|
||||||
web3: Web3,
|
|
||||||
bridgeContract: Contract,
|
|
||||||
messageData: string,
|
|
||||||
startBlock: number,
|
|
||||||
getFailedTransactions: (args: GetTransactionParams) => Promise<APITransaction[]>
|
|
||||||
) => async (validatorData: ConfirmationParam): Promise<ConfirmationParam> => {
|
|
||||||
const validatorCacheKey = `${CACHE_KEY_FAILED}${validatorData.validator}-${messageData}`
|
|
||||||
const failedFromCache = validatorsCache.getData(validatorCacheKey)
|
|
||||||
|
|
||||||
if (failedFromCache && failedFromCache.txHash) {
|
|
||||||
return failedFromCache
|
|
||||||
}
|
|
||||||
|
|
||||||
const failedTransactions = await getFailedTransactions({
|
|
||||||
account: validatorData.validator,
|
|
||||||
to: bridgeContract.options.address,
|
|
||||||
messageData,
|
|
||||||
startBlock,
|
|
||||||
endBlock: homeBlockNumberProvider.get() || 0
|
|
||||||
})
|
|
||||||
// If validator signature failed, we cache the result to avoid doing future requests for a result that won't change
|
|
||||||
if (failedTransactions.length > 0) {
|
|
||||||
const failedTx = failedTransactions[0]
|
|
||||||
const confirmation: ConfirmationParam = {
|
|
||||||
status: VALIDATOR_CONFIRMATION_STATUS.FAILED,
|
|
||||||
validator: validatorData.validator,
|
|
||||||
txHash: failedTx.hash,
|
|
||||||
timestamp: parseInt(failedTx.timeStamp)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (failedTx.input && failedTx.input.length > 10) {
|
|
||||||
try {
|
|
||||||
const res = web3.eth.abi.decodeParameters(['bytes', 'bytes'], `0x${failedTx.input.slice(10)}`)
|
|
||||||
confirmation.signature = res[0]
|
|
||||||
confirmation.status = VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID
|
|
||||||
console.log(`Adding manual signature from failed message from ${validatorData.validator}`)
|
|
||||||
} catch {}
|
|
||||||
}
|
|
||||||
validatorsCache.setData(validatorCacheKey, confirmation)
|
|
||||||
return confirmation
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
|
||||||
validator: validatorData.validator,
|
|
||||||
txHash: '',
|
|
||||||
timestamp: 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getValidatorPendingTransaction = (
|
|
||||||
bridgeContract: Contract,
|
|
||||||
messageData: string,
|
|
||||||
getPendingTransactions: (args: GetPendingTransactionParams) => Promise<APIPendingTransaction[]>
|
|
||||||
) => async (validatorData: ConfirmationParam): Promise<ConfirmationParam> => {
|
|
||||||
const failedTransactions = await getPendingTransactions({
|
|
||||||
account: validatorData.validator,
|
|
||||||
to: bridgeContract.options.address,
|
|
||||||
messageData
|
|
||||||
})
|
|
||||||
|
|
||||||
const newStatus =
|
|
||||||
failedTransactions.length > 0 ? VALIDATOR_CONFIRMATION_STATUS.PENDING : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
|
|
||||||
|
|
||||||
let timestamp = 0
|
|
||||||
let txHash = ''
|
|
||||||
|
|
||||||
if (failedTransactions.length > 0) {
|
|
||||||
const failedTx = failedTransactions[0]
|
|
||||||
timestamp = Math.floor(new Date().getTime() / 1000.0)
|
|
||||||
txHash = failedTx.hash
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
validator: validatorData.validator,
|
|
||||||
status: newStatus,
|
|
||||||
txHash,
|
|
||||||
timestamp
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,121 +0,0 @@
|
|||||||
import Web3 from 'web3'
|
|
||||||
import { BlockTransactionString } from 'web3-eth'
|
|
||||||
import { TransactionReceipt } from 'web3-eth'
|
|
||||||
import { AbiItem } from 'web3-utils'
|
|
||||||
import memoize from 'fast-memoize'
|
|
||||||
import promiseRetry from 'promise-retry'
|
|
||||||
import { HOME_AMB_ABI, FOREIGN_AMB_ABI } from '../abis'
|
|
||||||
import { SnapshotProvider } from '../services/SnapshotProvider'
|
|
||||||
|
|
||||||
export interface MessageObject {
|
|
||||||
id: string
|
|
||||||
data: string
|
|
||||||
sender?: string
|
|
||||||
executor?: string
|
|
||||||
obToken?: string
|
|
||||||
obReceiver?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface WarnRule {
|
|
||||||
message: string
|
|
||||||
sender?: string
|
|
||||||
executor?: string
|
|
||||||
obToken?: string
|
|
||||||
obReceiver?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export const matchesRule = (rule: WarnRule, msg: MessageObject) => {
|
|
||||||
if (!msg.executor || !msg.sender) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if (!!rule.executor && rule.executor.toLowerCase() !== msg.executor.toLowerCase()) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if (!!rule.sender && rule.sender.toLowerCase() !== msg.sender.toLowerCase()) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if (!!rule.obToken && (!msg.obToken || rule.obToken.toLowerCase() !== msg.obToken.toLowerCase())) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if (!!rule.obReceiver && (!msg.obReceiver || rule.obReceiver.toLowerCase() !== msg.obReceiver.toLowerCase())) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
const rawGetWeb3 = (url: string) => new Web3(new Web3.providers.HttpProvider(url))
|
|
||||||
const memoized = memoize(rawGetWeb3)
|
|
||||||
|
|
||||||
export const getWeb3 = (url: string) => memoized(url)
|
|
||||||
|
|
||||||
export const filterEventsByAbi = (
|
|
||||||
txReceipt: TransactionReceipt,
|
|
||||||
web3: Web3,
|
|
||||||
bridgeAddress: string,
|
|
||||||
eventAbi: AbiItem
|
|
||||||
): MessageObject[] => {
|
|
||||||
const eventHash = web3.eth.abi.encodeEventSignature(eventAbi)
|
|
||||||
const events = txReceipt.logs.filter(e => e.address === bridgeAddress && e.topics[0] === eventHash)
|
|
||||||
|
|
||||||
if (!eventAbi || !eventAbi.inputs || !eventAbi.inputs.length) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
const inputs = eventAbi.inputs
|
|
||||||
return events.map(e => {
|
|
||||||
const { messageId, encodedData } = web3.eth.abi.decodeLog(inputs, e.data, [e.topics[1]])
|
|
||||||
let sender, executor, obToken, obReceiver
|
|
||||||
if (encodedData.length >= 160) {
|
|
||||||
sender = `0x${encodedData.slice(66, 106)}`
|
|
||||||
executor = `0x${encodedData.slice(106, 146)}`
|
|
||||||
const dataOffset =
|
|
||||||
160 + (parseInt(encodedData.slice(154, 156), 16) + parseInt(encodedData.slice(156, 158), 16)) * 2 + 8
|
|
||||||
if (encodedData.length >= dataOffset + 64) {
|
|
||||||
obToken = `0x${encodedData.slice(dataOffset + 24, dataOffset + 64)}`
|
|
||||||
}
|
|
||||||
if (encodedData.length >= dataOffset + 128) {
|
|
||||||
obReceiver = `0x${encodedData.slice(dataOffset + 88, dataOffset + 128)}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
id: messageId || '',
|
|
||||||
data: encodedData || '',
|
|
||||||
sender,
|
|
||||||
executor,
|
|
||||||
obToken,
|
|
||||||
obReceiver
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getHomeMessagesFromReceipt = (txReceipt: TransactionReceipt, web3: Web3, bridgeAddress: string) => {
|
|
||||||
const UserRequestForSignatureAbi: AbiItem = HOME_AMB_ABI.filter(
|
|
||||||
(e: AbiItem) => e.type === 'event' && e.name === 'UserRequestForSignature'
|
|
||||||
)[0]
|
|
||||||
return filterEventsByAbi(txReceipt, web3, bridgeAddress, UserRequestForSignatureAbi)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getForeignMessagesFromReceipt = (txReceipt: TransactionReceipt, web3: Web3, bridgeAddress: string) => {
|
|
||||||
const userRequestForAffirmationAbi: AbiItem = FOREIGN_AMB_ABI.filter(
|
|
||||||
(e: AbiItem) => e.type === 'event' && e.name === 'UserRequestForAffirmation'
|
|
||||||
)[0]
|
|
||||||
return filterEventsByAbi(txReceipt, web3, bridgeAddress, userRequestForAffirmationAbi)
|
|
||||||
}
|
|
||||||
|
|
||||||
// In some rare cases the block data is not available yet for the block of a new event detected
|
|
||||||
// so this logic retry to get the block in case it fails
|
|
||||||
export const getBlock = async (web3: Web3, blockNumber: number): Promise<BlockTransactionString> =>
|
|
||||||
promiseRetry(async retry => {
|
|
||||||
const result = await web3.eth.getBlock(blockNumber)
|
|
||||||
if (!result) {
|
|
||||||
return retry('Error getting block data')
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
})
|
|
||||||
|
|
||||||
export const getChainId = async (web3: Web3, snapshotProvider: SnapshotProvider) => {
|
|
||||||
let id = snapshotProvider.chainId()
|
|
||||||
if (id === 0) {
|
|
||||||
id = await web3.eth.getChainId()
|
|
||||||
}
|
|
||||||
return id
|
|
||||||
}
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -2,37 +2,19 @@ import React from 'react'
|
|||||||
import ReactDOM from 'react-dom'
|
import ReactDOM from 'react-dom'
|
||||||
import BurnerCore from '@burner-wallet/core'
|
import BurnerCore from '@burner-wallet/core'
|
||||||
import { InjectedSigner, LocalSigner } from '@burner-wallet/core/signers'
|
import { InjectedSigner, LocalSigner } from '@burner-wallet/core/signers'
|
||||||
import { XDaiBridge } from '@burner-wallet/exchange'
|
import { InfuraGateway, InjectedGateway } from '@burner-wallet/core/gateways'
|
||||||
import { xdai } from '@burner-wallet/assets'
|
|
||||||
import { InfuraGateway, InjectedGateway, XDaiGateway } from '@burner-wallet/core/gateways'
|
|
||||||
import Exchange from '@burner-wallet/exchange'
|
import Exchange from '@burner-wallet/exchange'
|
||||||
import ModernUI from '@burner-wallet/modern-ui'
|
import ModernUI from '@burner-wallet/modern-ui'
|
||||||
import {
|
import { Etc, Wetc, TokenBridgeGateway, WETCBridge } from '@poanet/tokenbridge-bw-exchange'
|
||||||
Etc,
|
|
||||||
Wetc,
|
|
||||||
Dai,
|
|
||||||
qDai,
|
|
||||||
MOON,
|
|
||||||
xMOON,
|
|
||||||
TokenBridgeGateway,
|
|
||||||
WETCBridge,
|
|
||||||
QDAIBridge,
|
|
||||||
MOONBridge
|
|
||||||
} from '@poanet/tokenbridge-bw-exchange'
|
|
||||||
import MetamaskPlugin from '@burner-wallet/metamask-plugin'
|
import MetamaskPlugin from '@burner-wallet/metamask-plugin'
|
||||||
|
|
||||||
const core = new BurnerCore({
|
const core = new BurnerCore({
|
||||||
signers: [new InjectedSigner(), new LocalSigner()],
|
signers: [new InjectedSigner(), new LocalSigner()],
|
||||||
gateways: [
|
gateways: [new InjectedGateway(), new InfuraGateway(process.env.REACT_APP_INFURA_KEY), new TokenBridgeGateway()],
|
||||||
new InjectedGateway(),
|
assets: [Wetc, Etc]
|
||||||
new XDaiGateway(),
|
|
||||||
new InfuraGateway(process.env.REACT_APP_INFURA_KEY),
|
|
||||||
new TokenBridgeGateway()
|
|
||||||
],
|
|
||||||
assets: [xdai, Wetc, Etc, Dai, qDai, MOON, xMOON]
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const exchange = new Exchange([new XDaiBridge(), new WETCBridge(), new QDAIBridge(), new MOONBridge()])
|
const exchange = new Exchange([new WETCBridge()])
|
||||||
|
|
||||||
const BurnerWallet = () => <ModernUI title="Staging Wallet" core={core} plugins={[exchange, new MetamaskPlugin()]} />
|
const BurnerWallet = () => <ModernUI title="Staging Wallet" core={core} plugins={[exchange, new MetamaskPlugin()]} />
|
||||||
|
|
||||||
|
|||||||
@ -16,7 +16,8 @@
|
|||||||
"react": "^16.8.6",
|
"react": "^16.8.6",
|
||||||
"react-dom": "^16.8.6",
|
"react-dom": "^16.8.6",
|
||||||
"react-scripts": "3.0.1",
|
"react-scripts": "3.0.1",
|
||||||
"typescript": "3.5.1"
|
"typescript": "3.5.1",
|
||||||
|
"web3": "1.0.0-beta.55"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "react-scripts start",
|
"start": "react-scripts start",
|
||||||
|
|||||||
@ -1,53 +0,0 @@
|
|||||||
import { Gateway } from '@burner-wallet/core/gateways'
|
|
||||||
import Web3 from 'web3'
|
|
||||||
|
|
||||||
export default class LocalhostGateway extends Gateway {
|
|
||||||
private readonly providers: object
|
|
||||||
private readonly providerStrings: { [id: string]: string }
|
|
||||||
constructor() {
|
|
||||||
super()
|
|
||||||
this.providerStrings = {
|
|
||||||
'111': 'http://localhost:8545',
|
|
||||||
'1337': 'http://localhost:8546'
|
|
||||||
}
|
|
||||||
this.providers = {}
|
|
||||||
}
|
|
||||||
|
|
||||||
isAvailable() {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
getNetworks() {
|
|
||||||
return ['111', '1337']
|
|
||||||
}
|
|
||||||
|
|
||||||
_provider(network) {
|
|
||||||
if (!this.providers[network]) {
|
|
||||||
this._makeProvider(network)
|
|
||||||
}
|
|
||||||
return this.providers[network]
|
|
||||||
}
|
|
||||||
|
|
||||||
_makeProvider(network) {
|
|
||||||
if (!this.providerStrings[network]) {
|
|
||||||
throw new Error(`Network ${network} not supported by LocalhostGateway`)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.providers[network] = new Web3.providers.HttpProvider(this.providerStrings[network])
|
|
||||||
}
|
|
||||||
|
|
||||||
send(network, payload) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
if (this.getNetworks().indexOf(network) === -1) {
|
|
||||||
return reject(new Error('LocalhostGateway does not support this network'))
|
|
||||||
}
|
|
||||||
|
|
||||||
this._provider(network).send(payload, (err, response) => {
|
|
||||||
if (err) {
|
|
||||||
return reject(err)
|
|
||||||
}
|
|
||||||
return resolve(response.result)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -6,23 +6,13 @@ import { InjectedSigner, LocalSigner } from '@burner-wallet/core/signers'
|
|||||||
import { InfuraGateway, InjectedGateway } from '@burner-wallet/core/gateways'
|
import { InfuraGateway, InjectedGateway } from '@burner-wallet/core/gateways'
|
||||||
import ModernUI from '@burner-wallet/modern-ui'
|
import ModernUI from '@burner-wallet/modern-ui'
|
||||||
import Exchange from '@burner-wallet/exchange'
|
import Exchange from '@burner-wallet/exchange'
|
||||||
import {
|
import { Mediator, sPOA, ERC677Asset, TokenBridgeGateway } from '@poanet/tokenbridge-bw-exchange'
|
||||||
Mediator,
|
|
||||||
sPOA,
|
|
||||||
ERC677Asset,
|
|
||||||
TokenBridgeGateway,
|
|
||||||
NativeMediatorAsset,
|
|
||||||
MediatorErcToNative,
|
|
||||||
BridgeableERC20Asset
|
|
||||||
} from '@poanet/tokenbridge-bw-exchange'
|
|
||||||
import MetamaskPlugin from '@burner-wallet/metamask-plugin'
|
import MetamaskPlugin from '@burner-wallet/metamask-plugin'
|
||||||
import LocalhostGateway from './LocalhostGateway'
|
|
||||||
|
|
||||||
let assetIdAtHome = 'assetAtHome'
|
let assetIdAtHome = 'assetAtHome'
|
||||||
const assetIdAtForeign = 'assetAtForeign'
|
const assetIdAtForeign = 'assetAtForeign'
|
||||||
let assetAtHome: Asset
|
let assetAtHome: Asset
|
||||||
let assetAtForeign: Asset
|
let assetAtForeign: Asset
|
||||||
let testBridge: Mediator
|
|
||||||
|
|
||||||
if (process.env.REACT_APP_MODE === 'AMB_NATIVE_TO_ERC677') {
|
if (process.env.REACT_APP_MODE === 'AMB_NATIVE_TO_ERC677') {
|
||||||
sPOA.setMediatorAddress(process.env.REACT_APP_HOME_MEDIATOR_ADDRESS)
|
sPOA.setMediatorAddress(process.env.REACT_APP_HOME_MEDIATOR_ADDRESS)
|
||||||
@ -38,16 +28,8 @@ if (process.env.REACT_APP_MODE === 'AMB_NATIVE_TO_ERC677') {
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
address: process.env.REACT_APP_FOREIGN_TOKEN_ADDRESS
|
address: process.env.REACT_APP_FOREIGN_TOKEN_ADDRESS
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
testBridge = new Mediator({
|
// process.env.REACT_APP_MODE === 'AMB_ERC677_TO_ERC677'
|
||||||
assetA: assetIdAtHome,
|
|
||||||
// @ts-ignore
|
|
||||||
assetABridge: process.env.REACT_APP_HOME_MEDIATOR_ADDRESS,
|
|
||||||
assetB: assetIdAtForeign,
|
|
||||||
// @ts-ignore
|
|
||||||
assetBBridge: process.env.REACT_APP_FOREIGN_MEDIATOR_ADDRESS
|
|
||||||
})
|
|
||||||
} else if (process.env.REACT_APP_MODE === 'AMB_ERC677_TO_ERC677') {
|
|
||||||
assetAtHome = new ERC677Asset({
|
assetAtHome = new ERC677Asset({
|
||||||
id: 'assetAtHome',
|
id: 'assetAtHome',
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@ -67,53 +49,20 @@ if (process.env.REACT_APP_MODE === 'AMB_NATIVE_TO_ERC677') {
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
address: process.env.REACT_APP_FOREIGN_TOKEN_ADDRESS
|
address: process.env.REACT_APP_FOREIGN_TOKEN_ADDRESS
|
||||||
})
|
})
|
||||||
|
|
||||||
testBridge = new Mediator({
|
|
||||||
assetA: assetIdAtHome,
|
|
||||||
// @ts-ignore
|
|
||||||
assetABridge: process.env.REACT_APP_HOME_MEDIATOR_ADDRESS,
|
|
||||||
assetB: assetIdAtForeign,
|
|
||||||
// @ts-ignore
|
|
||||||
assetBBridge: process.env.REACT_APP_FOREIGN_MEDIATOR_ADDRESS
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
// process.env.REACT_APP_MODE === 'AMB_ERC20_TO_NATIVE'
|
|
||||||
assetAtHome = new NativeMediatorAsset({
|
|
||||||
id: assetIdAtHome,
|
|
||||||
name: 'qDAI',
|
|
||||||
network: process.env.REACT_APP_HOME_NETWORK as string,
|
|
||||||
mediatorAddress: process.env.REACT_APP_HOME_MEDIATOR_ADDRESS
|
|
||||||
})
|
|
||||||
|
|
||||||
assetAtForeign = new BridgeableERC20Asset({
|
|
||||||
id: 'assetAtForeign',
|
|
||||||
// @ts-ignore
|
|
||||||
name: process.env.REACT_APP_FOREIGN_TOKEN_NAME,
|
|
||||||
// @ts-ignore
|
|
||||||
network: process.env.REACT_APP_FOREIGN_NETWORK,
|
|
||||||
// @ts-ignore
|
|
||||||
address: process.env.REACT_APP_FOREIGN_TOKEN_ADDRESS,
|
|
||||||
bridgeModes: ['erc-to-native-amb']
|
|
||||||
})
|
|
||||||
|
|
||||||
testBridge = new MediatorErcToNative({
|
|
||||||
assetA: assetIdAtHome,
|
|
||||||
// @ts-ignore
|
|
||||||
assetABridge: process.env.REACT_APP_HOME_MEDIATOR_ADDRESS,
|
|
||||||
assetB: assetIdAtForeign,
|
|
||||||
// @ts-ignore
|
|
||||||
assetBBridge: process.env.REACT_APP_FOREIGN_MEDIATOR_ADDRESS
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const testBridge = new Mediator({
|
||||||
|
assetA: assetIdAtHome,
|
||||||
|
// @ts-ignore
|
||||||
|
assetABridge: process.env.REACT_APP_HOME_MEDIATOR_ADDRESS,
|
||||||
|
assetB: assetIdAtForeign,
|
||||||
|
// @ts-ignore
|
||||||
|
assetBBridge: process.env.REACT_APP_FOREIGN_MEDIATOR_ADDRESS
|
||||||
|
})
|
||||||
|
|
||||||
const core = new BurnerCore({
|
const core = new BurnerCore({
|
||||||
signers: [new InjectedSigner(), new LocalSigner({ privateKey: process.env.REACT_APP_PK, saveKey: false })],
|
signers: [new InjectedSigner(), new LocalSigner({ privateKey: process.env.REACT_APP_PK, saveKey: false })],
|
||||||
gateways: [
|
gateways: [new InjectedGateway(), new TokenBridgeGateway(), new InfuraGateway(process.env.REACT_APP_INFURA_KEY)],
|
||||||
new InjectedGateway(),
|
|
||||||
new LocalhostGateway(),
|
|
||||||
new TokenBridgeGateway(),
|
|
||||||
new InfuraGateway(process.env.REACT_APP_INFURA_KEY)
|
|
||||||
],
|
|
||||||
assets: [assetAtHome, assetAtForeign]
|
assets: [assetAtHome, assetAtForeign]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -17,7 +17,6 @@
|
|||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"noImplicitAny": false,
|
|
||||||
"jsx": "preserve"
|
"jsx": "preserve"
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
|
|||||||
@ -4,16 +4,12 @@ This plugin defines a Bridge trading pair to be used in the Exchange Plugin.
|
|||||||
|
|
||||||
Bridge trading pairs and assets supported:
|
Bridge trading pairs and assets supported:
|
||||||
* ETC - WETC Bridge
|
* ETC - WETC Bridge
|
||||||
* MOON - xMOON Bridge
|
|
||||||
* DAI - qDAI Bridge (For qDAI Bridge, it's necessary to use a custom DAI token from this repo instead of the DAI asset provided by burner-wallet)
|
|
||||||
|
|
||||||
It also provides some generic resources that can be used and extended:
|
It also provides some generic resources that can be used and extended:
|
||||||
* **ERC677Asset** - A representation of an Erc677 token.
|
* **ERC677Asset** - A representation of an Erc677 token
|
||||||
* **BridgeableERC20Asset** - A representation of Erc20 token with a possibility of bridging it via a call to `relayTokens`.
|
|
||||||
* **NativeMediatorAsset** - Represents a native token that interacts with a Mediator extension.
|
* **NativeMediatorAsset** - Represents a native token that interacts with a Mediator extension.
|
||||||
* **Mediator Pair** - Represents an Exchange Pair that interacts with mediators extensions.
|
* **Mediator Pair** - Represents an Exchange Pair that interacts with mediators extensions.
|
||||||
* **MediatorErcToNative Pair** - Represents a modified Mediator Pair that interacts with a tokenbridge erc-to-native mediators contracts.
|
* **TokenBridgeGateway** - A gateway to operate with ETC, POA Sokol and POA Core networks.
|
||||||
* **TokenBridgeGateway** - A gateway to operate with ETC, POA Sokol, POA Core and qDAI networks.
|
|
||||||
|
|
||||||
### Install package
|
### Install package
|
||||||
```
|
```
|
||||||
@ -22,22 +18,13 @@ yarn add @poanet/tokenbridge-bw-exchange
|
|||||||
|
|
||||||
### Usage
|
### Usage
|
||||||
|
|
||||||
#### WETCBridge example
|
|
||||||
In this example, we use `TokenBridgeGateway` for connecting to the Ethereum Classic and `InfuraGateway` for connecting to the Ethereum Mainnet.
|
|
||||||
|
|
||||||
`WETCBridge` operates with two assets: `WETC` (Ethereum Mainnet) and `ETC` (Ethereum Classic), they should be added in the assets list.
|
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
import BurnerCore from '@burner-wallet/core'
|
import { Etc, Wetc, EtcGateway, WETCBridge } from '@poanet/tokenbridge-bw-exchange'
|
||||||
import Exchange from '@burner-wallet/exchange'
|
|
||||||
import { LocalSigner } from '@burner-wallet/core/signers'
|
|
||||||
import { Etc, Wetc, TokenBridgeGateway, WETCBridge } from '@poanet/tokenbridge-bw-exchange'
|
|
||||||
import { InfuraGateway } from '@burner-wallet/core/gateways'
|
|
||||||
|
|
||||||
const core = new BurnerCore({
|
const core = new BurnerCore({
|
||||||
signers: [new LocalSigner()],
|
...
|
||||||
gateways: [new TokenBridgeGateway(), new InfuraGateway(process.env.REACT_APP_INFURA_KEY)],
|
gateways: [new EtcGateway(), new InfuraGateway(process.env.REACT_APP_INFURA_KEY)],
|
||||||
assets: [Wetc, Etc]
|
assets: [Etc, Wetc]
|
||||||
})
|
})
|
||||||
|
|
||||||
const exchange = new Exchange({
|
const exchange = new Exchange({
|
||||||
@ -45,33 +32,6 @@ const exchange = new Exchange({
|
|||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
### Using several exchanges simultaneously
|
|
||||||
In this example, we use `TokenBridgeGateway` for connecting to the qDAI chain, `XDaiGatewai` for connecting to the xDAI chain and `InfuraGateway` for connecting to the Ethereum Mainnet and Rinkeby Network.
|
|
||||||
|
|
||||||
`QDAIBridge` operates with two assets: `qDAI` (qDAI chain) and `DAI` (Ethereum Mainnet). Note that we use a custom DAI token from the `@poanet/tokenbridge-bw-exchange`, this is necessary for allowing bridge operations on this token.
|
|
||||||
|
|
||||||
`MOONBridge` operates with two assets: `MOON` (Rinkeby network) and `xMOON` (xDAI chain).
|
|
||||||
|
|
||||||
All four assets should be added to the assets list.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import BurnerCore from '@burner-wallet/core'
|
|
||||||
import Exchange from '@burner-wallet/exchange'
|
|
||||||
import { LocalSigner } from '@burner-wallet/core/signers'
|
|
||||||
import { InfuraGateway, XDaiGateway } from '@burner-wallet/core/gateways'
|
|
||||||
import { Dai, qDai, MOON, xMOON, TokenBridgeGateway, QDAIBridge, MOONBridge } from '@poanet/tokenbridge-bw-exchange'
|
|
||||||
|
|
||||||
const core = new BurnerCore({
|
|
||||||
signers: [new LocalSigner()],
|
|
||||||
gateways: [new TokenBridgeGateway(), new XDaiGateway(), new InfuraGateway(process.env.REACT_APP_INFURA_KEY)],
|
|
||||||
assets: [Dai, qDai, MOON, xMOON]
|
|
||||||
})
|
|
||||||
|
|
||||||
const exchange = new Exchange({
|
|
||||||
pairs: [new QDAIBridge(), new MOONBridge()]
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
This is how the exchange plugin will look like:
|
This is how the exchange plugin will look like:
|
||||||
|
|
||||||

|

|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@poanet/tokenbridge-bw-exchange",
|
"name": "@poanet/tokenbridge-bw-exchange",
|
||||||
"version": "1.1.0",
|
"version": "1.0.0",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
|
|||||||
@ -1,12 +0,0 @@
|
|||||||
import { Mediator } from '../burner-wallet'
|
|
||||||
|
|
||||||
export default class MOONBridge extends Mediator {
|
|
||||||
constructor() {
|
|
||||||
super({
|
|
||||||
assetA: 'xmoon',
|
|
||||||
assetABridge: '0x1E0507046130c31DEb20EC2f870ad070Ff266079',
|
|
||||||
assetB: 'moon',
|
|
||||||
assetBBridge: '0xFEaB457D95D9990b7eb6c943c839258245541754'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
import { MediatorErcToNative } from '../burner-wallet'
|
|
||||||
|
|
||||||
export default class QDAIBridge extends MediatorErcToNative {
|
|
||||||
constructor() {
|
|
||||||
super({
|
|
||||||
assetA: 'qdai',
|
|
||||||
assetABridge: '0xFEaB457D95D9990b7eb6c943c839258245541754',
|
|
||||||
assetB: 'dai',
|
|
||||||
assetBBridge: '0xf6edFA16926f30b0520099028A145F4E06FD54ed'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user