diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 79671ef58..19ba97635 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -182,6 +182,7 @@ jobs:
deployment:
runs-on: ubuntu-latest
needs:
+ - build-e2e-images
- build-molecule-runner
strategy:
fail-fast: false
@@ -197,11 +198,11 @@ jobs:
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
- if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop' || startsWith(github.ref, 'refs/tags')
strategy:
fail-fast: false
matrix:
@@ -209,12 +210,16 @@ jobs:
include:
- task: erc-to-erc
ui-e2e-grep: 'ERC TO ERC'
+ ui-config: 'e2e-commons/components-envs/ui-erc20.env'
- task: erc-to-native
ui-e2e-grep: 'ERC TO NATIVE'
+ ui-config: 'e2e-commons/components-envs/ui-erc20-native.env'
- task: native-to-erc
ui-e2e-grep: 'NATIVE TO ERC'
+ ui-config: 'e2e-commons/components-envs/ui.env'
- task: amb-stake-erc-to-erc
ui-e2e-grep: 'AMB-STAKE-ERC-TO-ERC'
+ ui-config: 'e2e-commons/components-envs/ui-amb-stake-erc20-erc20.env'
steps:
- uses: actions/checkout@v2
with:
@@ -240,7 +245,20 @@ jobs:
- name: Deploy contracts
run: ${{ steps.cache-repo.outputs.cache-hit }} && e2e-commons/up.sh deploy blocks
- name: Pull e2e oracle image
- run: docker-compose -f ./e2e-commons/docker-compose.yml pull oracle
+ run: |
+ docker-compose -f ./e2e-commons/docker-compose.yml pull oracle
+ docker tag ${DOCKER_IMAGE_BASE}/tokenbridge-e2e-oracle:${ORACLE_TAG} poanetwork/tokenbridge-oracle:latest
+ - if: ${{ matrix.ui-e2e-grep }}
+ name: Pull e2e ui image
+ run: |
+ docker-compose -f ./e2e-commons/docker-compose.yml pull ui
+ docker build \
+ --build-arg DOCKER_IMAGE_BASE=${DOCKER_IMAGE_BASE} \
+ --build-arg UI_TAG=${UI_TAG} \
+ --build-arg DOT_ENV_PATH=${{ matrix.ui-config }} \
+ -f ./e2e-commons/Dockerfile.ui \
+ -t ui_ui:latest \
+ .
- name: Deploy oracle and ui
run: deployment-e2e/molecule.sh ultimate-${{ matrix.task }}
- name: Reset docker socket permissions
diff --git a/.gitignore b/.gitignore
index 7cea05397..9655fe720 100644
--- a/.gitignore
+++ b/.gitignore
@@ -49,3 +49,6 @@ monitor/responses/*
monitor/cache/*
!monitor/cache/.gitkeep
!monitor/.gitkeep
+
+# Local Netlify folder
+.netlify
\ No newline at end of file
diff --git a/alm/package.json b/alm/package.json
index dfee20a9f..41ede67e7 100644
--- a/alm/package.json
+++ b/alm/package.json
@@ -3,6 +3,7 @@
"version": "0.1.0",
"private": true,
"dependencies": {
+ "@ethersproject/bignumber": ">=5.0.0-beta.130",
"@react-hook/window-size": "^3.0.6",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
@@ -15,6 +16,8 @@
"@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",
"date-fns": "^2.14.0",
"dotenv": "^8.2.0",
@@ -27,8 +30,9 @@
"react-scripts": "3.0.1",
"styled-components": "^5.1.1",
"typescript": "^3.5.2",
- "web3": "1.2.7",
- "web3-eth-contract": "1.2.7"
+ "web3": "1.2.11",
+ "web3-eth-contract": "1.2.11",
+ "web3-utils": "1.2.11"
},
"scripts": {
"start": "yarn createSnapshots && ./load-env.sh react-app-rewired start",
diff --git a/alm/public/_redirects b/alm/public/_redirects
new file mode 100644
index 000000000..78f7f2067
--- /dev/null
+++ b/alm/public/_redirects
@@ -0,0 +1 @@
+/* /index.html 200
\ No newline at end of file
diff --git a/alm/src/App.tsx b/alm/src/App.tsx
index 3ab3eaf66..81f3bc0cd 100644
--- a/alm/src/App.tsx
+++ b/alm/src/App.tsx
@@ -1,14 +1,18 @@
import React from 'react'
import { BrowserRouter } from 'react-router-dom'
+import { Web3ReactProvider } from '@web3-react/core'
+import Web3 from 'web3'
import { MainPage } from './components/MainPage'
import { StateProvider } from './state/StateProvider'
function App() {
return (
-
-
-
+ new Web3(provider)}>
+
+
+
+
)
}
diff --git a/alm/src/components/ConfirmationsContainer.tsx b/alm/src/components/ConfirmationsContainer.tsx
index 4edc9488a..57ff0b7d0 100644
--- a/alm/src/components/ConfirmationsContainer.tsx
+++ b/alm/src/components/ConfirmationsContainer.tsx
@@ -49,7 +49,16 @@ export const ConfirmationsContainer = ({ message, receipt, fromHome, timestamp }
} = useStateProvider()
const { requiredSignatures, validatorList } = useValidatorContract({ fromHome, receipt })
const { blockConfirmations } = useBlockConfirmations({ fromHome, receipt })
- const { confirmations, status, executionData, signatureCollected, waitingBlocksResolved } = useMessageConfirmations({
+ const {
+ confirmations,
+ status,
+ executionData,
+ signatureCollected,
+ waitingBlocksResolved,
+ setExecutionData,
+ executionEventsFetched,
+ setPendingExecution
+ } = useMessageConfirmations({
message,
receipt,
fromHome,
@@ -102,7 +111,17 @@ export const ConfirmationsContainer = ({ message, receipt, fromHome, timestamp }
validatorList={validatorList}
waitingBlocksResolved={waitingBlocksResolved}
/>
- {signatureCollected && }
+ {signatureCollected && (
+
+ )}
)
diff --git a/alm/src/components/ExecutionConfirmation.tsx b/alm/src/components/ExecutionConfirmation.tsx
index d1af0a908..1541e7bf6 100644
--- a/alm/src/components/ExecutionConfirmation.tsx
+++ b/alm/src/components/ExecutionConfirmation.tsx
@@ -1,24 +1,45 @@
import React from 'react'
import { formatTimestamp, formatTxHash, getExplorerTxUrl } from '../utils/networks'
import { useWindowWidth } from '@react-hook/window-size'
-import { SEARCHING_TX, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants'
+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 { 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'
const StyledExecutionConfirmation = styled.div`
margin-top: 30px;
`
export interface ExecutionConfirmationParams {
+ messageData: string
executionData: ExecutionData
+ setExecutionData: Function
+ signatureCollected: boolean | string[]
isHome: boolean
+ executionEventsFetched: boolean
+ setPendingExecution: Function
}
-export const ExecutionConfirmation = ({ executionData, isHome }: ExecutionConfirmationParams) => {
+export const ExecutionConfirmation = ({
+ messageData,
+ executionData,
+ setExecutionData,
+ signatureCollected,
+ isHome,
+ executionEventsFetched,
+ setPendingExecution
+}: ExecutionConfirmationParams) => {
+ const availableManualExecution =
+ !isHome &&
+ (executionData.status === VALIDATOR_CONFIRMATION_STATUS.WAITING ||
+ (executionData.status === VALIDATOR_CONFIRMATION_STATUS.UNDEFINED &&
+ executionEventsFetched &&
+ !!executionData.validator))
+ const requiredManualExecution = availableManualExecution && ALM_HOME_TO_FOREIGN_MANUAL_EXECUTION
const windowWidth = useWindowWidth()
const txExplorerLink = getExplorerTxUrl(executionData.txHash, isHome)
@@ -48,26 +69,47 @@ export const ExecutionConfirmation = ({ executionData, isHome }: ExecutionConfir
- Executed by |
+ {requiredManualExecution ? 'Execution info' : 'Executed by'} |
Status |
- Age |
+ {!requiredManualExecution && Age | }
+ {availableManualExecution && Actions | }
- {formattedValidator ? formattedValidator : } |
- {getExecutionStatusElement(executionData.status)}
-
- {executionData.timestamp > 0 ? (
-
- {formatTimestamp(executionData.timestamp)}
-
- ) : executionData.status === VALIDATOR_CONFIRMATION_STATUS.WAITING ? (
- ''
+
+ {requiredManualExecution ? (
+ 'Manual user action is required to complete the operation'
+ ) : formattedValidator ? (
+ formattedValidator
) : (
- SEARCHING_TX
+
)}
-
+ |
+ {getExecutionStatusElement(executionData.status)}
+ {!requiredManualExecution && (
+
+ {executionData.timestamp > 0 ? (
+
+ {formatTimestamp(executionData.timestamp)}
+
+ ) : executionData.status === VALIDATOR_CONFIRMATION_STATUS.WAITING ? (
+ ''
+ ) : (
+ SEARCHING_TX
+ )}
+
+ )}
+ {availableManualExecution && (
+
+
+ |
+ )}
diff --git a/alm/src/components/MainPage.tsx b/alm/src/components/MainPage.tsx
index 88ced8153..9697d65c1 100644
--- a/alm/src/components/MainPage.tsx
+++ b/alm/src/components/MainPage.tsx
@@ -8,6 +8,7 @@ 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'
+import { ErrorAlert } from './commons/ErrorAlert'
const StyledMainPage = styled.div`
text-align: center;
@@ -51,7 +52,7 @@ export interface FormSubmitParams {
export const MainPage = () => {
const history = useHistory()
- const { home, foreign } = useStateProvider()
+ const { home, foreign, error, setError } = useStateProvider()
const [networkName, setNetworkName] = useState('')
const [receipt, setReceipt] = useState>(null)
const [showInfoAlert, setShowInfoAlert] = useState(false)
@@ -131,6 +132,7 @@ export const MainPage = () => {
)}
+ {error && setError('')} error={error} />}
} />
{
+ const { foreign, setError } = useStateProvider()
+ const { library, activate, account, active } = useWeb3React()
+ const [manualExecution, setManualExecution] = useState(false)
+
+ 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) return
+
+ const signatures = packSignatures(signatureCollected.map(signatureToVRS))
+ const data = foreign.bridgeContract.methods.executeSignatures(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', (e: Error) => setError(e.message))
+ },
+ [
+ manualExecution,
+ library,
+ activate,
+ active,
+ account,
+ foreign.chainId,
+ foreign.bridgeAddress,
+ foreign.bridgeContract,
+ setError,
+ messageData,
+ signatureCollected,
+ setExecutionData,
+ setPendingExecution
+ ]
+ )
+
+ return (
+
+ setManualExecution(true)}>
+ Execute
+
+
+ )
+}
diff --git a/alm/src/components/commons/CloseIcon.tsx b/alm/src/components/commons/CloseIcon.tsx
index 702e3edf5..732421152 100644
--- a/alm/src/components/commons/CloseIcon.tsx
+++ b/alm/src/components/commons/CloseIcon.tsx
@@ -1,6 +1,6 @@
import React from 'react'
-export const CloseIcon = () => (
+export const CloseIcon = ({ color }: { color?: string }) => (
)
diff --git a/alm/src/components/commons/ErrorAlert.tsx b/alm/src/components/commons/ErrorAlert.tsx
new file mode 100644
index 000000000..af3e3f598
--- /dev/null
+++ b/alm/src/components/commons/ErrorAlert.tsx
@@ -0,0 +1,31 @@
+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(--failed-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 ErrorAlert = ({ onClick, error }: { onClick: () => void; error: string }) => (
+
+
+
+ {error}
+
+
+
+
+
+)
diff --git a/alm/src/components/commons/InfoIcon.tsx b/alm/src/components/commons/InfoIcon.tsx
index f8d84f927..9fd62c6ec 100644
--- a/alm/src/components/commons/InfoIcon.tsx
+++ b/alm/src/components/commons/InfoIcon.tsx
@@ -1,6 +1,6 @@
import React from 'react'
-export const InfoIcon = () => (
+export const InfoIcon = ({ color }: { color?: string }) => (