diff --git a/alm/src/components/ExecutionConfirmation.tsx b/alm/src/components/ExecutionConfirmation.tsx index 1541e7bf..0ee1ff88 100644 --- a/alm/src/components/ExecutionConfirmation.tsx +++ b/alm/src/components/ExecutionConfirmation.tsx @@ -36,10 +36,12 @@ export const ExecutionConfirmation = ({ 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) @@ -71,7 +73,7 @@ export const ExecutionConfirmation = ({ {requiredManualExecution ? 'Execution info' : 'Executed by'} Status - {!requiredManualExecution && Age} + {showAgeColumn && Age} {availableManualExecution && Actions} @@ -87,7 +89,7 @@ export const ExecutionConfirmation = ({ )} {getExecutionStatusElement(executionData.status)} - {!requiredManualExecution && ( + {showAgeColumn && ( {executionData.timestamp > 0 ? ( diff --git a/alm/src/components/ManualExecutionButton.tsx b/alm/src/components/ManualExecutionButton.tsx index e39c6150..5a7d1f90 100644 --- a/alm/src/components/ManualExecutionButton.tsx +++ b/alm/src/components/ManualExecutionButton.tsx @@ -2,9 +2,17 @@ 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 { INCORRECT_CHAIN_ERROR, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants' +import { + DOUBLE_EXECUTION_ATTEMPT_ERROR, + EXECUTION_FAILED_ERROR, + EXECUTION_OUT_OF_GAS_ERROR, + 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' const StyledButton = styled.button` color: var(--button-color); @@ -61,7 +69,9 @@ export const ManualExecutionButton = ({ if (!library || !foreign.bridgeContract || !signatureCollected || !signatureCollected.length) return const signatures = packSignatures(signatureCollected.map(signatureToVRS)) - const data = foreign.bridgeContract.methods.executeSignatures(messageData, signatures).encodeABI() + const messageId = messageData.slice(0, 66) + const bridge = foreign.bridgeContract + const data = bridge.methods.executeSignatures(messageData, signatures).encodeABI() setManualExecution(false) library.eth @@ -80,7 +90,27 @@ export const ManualExecutionButton = ({ }) setPendingExecution(true) }) - .on('error', (e: Error) => setError(e.message)) + .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) + 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, diff --git a/alm/src/components/commons/ErrorAlert.tsx b/alm/src/components/commons/ErrorAlert.tsx index af3e3f59..236353b0 100644 --- a/alm/src/components/commons/ErrorAlert.tsx +++ b/alm/src/components/commons/ErrorAlert.tsx @@ -2,6 +2,7 @@ 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); @@ -15,17 +16,33 @@ const CloseIconContainer = styled.div` ` const TextContainer = styled.div` + white-space: pre-wrap; flex-direction: column; ` -export const ErrorAlert = ({ onClick, error }: { onClick: () => void; error: string }) => ( -
- - - {error} - - - - -
-) +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 = ( + + {errorArray[1]} + + ) + } + return ( +
+ + + + {text} + {link} + + + + + +
+ ) +} diff --git a/alm/src/config/constants.ts b/alm/src/config/constants.ts index 5685440d..070ad9e8 100644 --- a/alm/src/config/constants.ts +++ b/alm/src/config/constants.ts @@ -66,3 +66,12 @@ export const VALIDATOR_CONFIRMATION_STATUS = { 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.` diff --git a/alm/src/utils/getFinalizationEvent.ts b/alm/src/utils/getFinalizationEvent.ts index 8f383a08..f3ff25c5 100644 --- a/alm/src/utils/getFinalizationEvent.ts +++ b/alm/src/utils/getFinalizationEvent.ts @@ -11,6 +11,37 @@ import { import { getBlock, MessageObject } from './web3' import validatorsCache from '../services/ValidatorsCache' +export const getSuccessExecutionData = async (contract: Contract, eventName: string, web3: Web3, messageId: 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 contract.getPastEvents(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.SUCCESS, + validator: validatorAddress, + txHash: event.transactionHash, + timestamp: blockTimestamp, + executionResult: event.returnValues.status + } + } + return null +} + export const getFinalizationEvent = async ( contract: Maybe, eventName: string, @@ -29,32 +60,9 @@ export const getFinalizationEvent = async ( setExecutionEventsFetched: Function ) => { if (!contract || !web3 || !waitingBlocksResolved) return - // 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 contract.getPastEvents(eventName, { - fromBlock: 0, - toBlock: 'latest', - filter: { - messageId: message.id - } - }) - 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) - - setResult({ - status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, - validator: validatorAddress, - txHash: event.transactionHash, - timestamp: blockTimestamp, - executionResult: event.returnValues.status - }) + const successExecutionData = await getSuccessExecutionData(contract, eventName, web3, message.id) + if (successExecutionData) { + setResult(successExecutionData) } else { setExecutionEventsFetched(true) // If event is defined, it means it is a message from Home to Foreign