Add pretty error messages for manual execution transaction reverts (#511)
This commit is contained in:
parent
0451d6e373
commit
e1536755f4
@ -36,10 +36,12 @@ export const ExecutionConfirmation = ({
|
|||||||
const availableManualExecution =
|
const availableManualExecution =
|
||||||
!isHome &&
|
!isHome &&
|
||||||
(executionData.status === VALIDATOR_CONFIRMATION_STATUS.WAITING ||
|
(executionData.status === VALIDATOR_CONFIRMATION_STATUS.WAITING ||
|
||||||
|
executionData.status === VALIDATOR_CONFIRMATION_STATUS.FAILED ||
|
||||||
(executionData.status === VALIDATOR_CONFIRMATION_STATUS.UNDEFINED &&
|
(executionData.status === VALIDATOR_CONFIRMATION_STATUS.UNDEFINED &&
|
||||||
executionEventsFetched &&
|
executionEventsFetched &&
|
||||||
!!executionData.validator))
|
!!executionData.validator))
|
||||||
const requiredManualExecution = availableManualExecution && ALM_HOME_TO_FOREIGN_MANUAL_EXECUTION
|
const requiredManualExecution = availableManualExecution && ALM_HOME_TO_FOREIGN_MANUAL_EXECUTION
|
||||||
|
const showAgeColumn = !requiredManualExecution || executionData.status === VALIDATOR_CONFIRMATION_STATUS.FAILED
|
||||||
const windowWidth = useWindowWidth()
|
const windowWidth = useWindowWidth()
|
||||||
|
|
||||||
const txExplorerLink = getExplorerTxUrl(executionData.txHash, isHome)
|
const txExplorerLink = getExplorerTxUrl(executionData.txHash, isHome)
|
||||||
@ -71,7 +73,7 @@ export const ExecutionConfirmation = ({
|
|||||||
<tr>
|
<tr>
|
||||||
<th>{requiredManualExecution ? 'Execution info' : 'Executed by'}</th>
|
<th>{requiredManualExecution ? 'Execution info' : 'Executed by'}</th>
|
||||||
<th className="text-center">Status</th>
|
<th className="text-center">Status</th>
|
||||||
{!requiredManualExecution && <th className="text-center">Age</th>}
|
{showAgeColumn && <th className="text-center">Age</th>}
|
||||||
{availableManualExecution && <th className="text-center">Actions</th>}
|
{availableManualExecution && <th className="text-center">Actions</th>}
|
||||||
</tr>
|
</tr>
|
||||||
</Thead>
|
</Thead>
|
||||||
@ -87,7 +89,7 @@ export const ExecutionConfirmation = ({
|
|||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
<StatusTd className="text-center">{getExecutionStatusElement(executionData.status)}</StatusTd>
|
<StatusTd className="text-center">{getExecutionStatusElement(executionData.status)}</StatusTd>
|
||||||
{!requiredManualExecution && (
|
{showAgeColumn && (
|
||||||
<AgeTd className="text-center">
|
<AgeTd className="text-center">
|
||||||
{executionData.timestamp > 0 ? (
|
{executionData.timestamp > 0 ? (
|
||||||
<ExplorerTxLink href={txExplorerLink} target="_blank">
|
<ExplorerTxLink href={txExplorerLink} target="_blank">
|
||||||
|
@ -2,9 +2,17 @@ import React, { useState, useEffect } from 'react'
|
|||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
import { InjectedConnector } from '@web3-react/injected-connector'
|
import { InjectedConnector } from '@web3-react/injected-connector'
|
||||||
import { useWeb3React } from '@web3-react/core'
|
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 { useStateProvider } from '../state/StateProvider'
|
||||||
import { signatureToVRS, packSignatures } from '../utils/signatures'
|
import { signatureToVRS, packSignatures } from '../utils/signatures'
|
||||||
|
import { getSuccessExecutionData } from '../utils/getFinalizationEvent'
|
||||||
|
import { TransactionReceipt } from 'web3-eth'
|
||||||
|
|
||||||
const StyledButton = styled.button`
|
const StyledButton = styled.button`
|
||||||
color: var(--button-color);
|
color: var(--button-color);
|
||||||
@ -61,7 +69,9 @@ export const ManualExecutionButton = ({
|
|||||||
if (!library || !foreign.bridgeContract || !signatureCollected || !signatureCollected.length) return
|
if (!library || !foreign.bridgeContract || !signatureCollected || !signatureCollected.length) return
|
||||||
|
|
||||||
const signatures = packSignatures(signatureCollected.map(signatureToVRS))
|
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)
|
setManualExecution(false)
|
||||||
|
|
||||||
library.eth
|
library.eth
|
||||||
@ -80,7 +90,27 @@ export const ManualExecutionButton = ({
|
|||||||
})
|
})
|
||||||
setPendingExecution(true)
|
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,
|
manualExecution,
|
||||||
|
@ -2,6 +2,7 @@ import React from 'react'
|
|||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
import { InfoIcon } from './InfoIcon'
|
import { InfoIcon } from './InfoIcon'
|
||||||
import { CloseIcon } from './CloseIcon'
|
import { CloseIcon } from './CloseIcon'
|
||||||
|
import { ExplorerTxLink } from './ExplorerTxLink'
|
||||||
|
|
||||||
const StyledErrorAlert = styled.div`
|
const StyledErrorAlert = styled.div`
|
||||||
border: 1px solid var(--failed-color);
|
border: 1px solid var(--failed-color);
|
||||||
@ -15,17 +16,33 @@ const CloseIconContainer = styled.div`
|
|||||||
`
|
`
|
||||||
|
|
||||||
const TextContainer = styled.div`
|
const TextContainer = styled.div`
|
||||||
|
white-space: pre-wrap;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
`
|
`
|
||||||
|
|
||||||
export const ErrorAlert = ({ onClick, error }: { onClick: () => void; error: string }) => (
|
export const ErrorAlert = ({ onClick, error }: { onClick: () => void; error: string }) => {
|
||||||
<div className="row is-center">
|
const errorArray = error.split('%link')
|
||||||
<StyledErrorAlert className="col-10 is-vertical-align row">
|
const text = errorArray[0]
|
||||||
<InfoIcon color="var(--failed-color)" />
|
let link
|
||||||
<TextContainer className="col-10">{error}</TextContainer>
|
if (errorArray.length > 1) {
|
||||||
<CloseIconContainer className="col-1 is-vertical-align is-center" onClick={onClick}>
|
link = (
|
||||||
<CloseIcon color="var(--failed-color)" />
|
<ExplorerTxLink href={errorArray[1]} target="_blank" rel="noopener noreferrer">
|
||||||
</CloseIconContainer>
|
{errorArray[1]}
|
||||||
</StyledErrorAlert>
|
</ExplorerTxLink>
|
||||||
</div>
|
)
|
||||||
)
|
}
|
||||||
|
return (
|
||||||
|
<div className="row is-center">
|
||||||
|
<StyledErrorAlert className="col-10 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -66,3 +66,12 @@ export const VALIDATOR_CONFIRMATION_STATUS = {
|
|||||||
export const SEARCHING_TX = 'Searching Transaction...'
|
export const SEARCHING_TX = 'Searching Transaction...'
|
||||||
|
|
||||||
export const INCORRECT_CHAIN_ERROR = `Incorrect chain chosen. Switch to ${FOREIGN_NETWORK_NAME} in the wallet.`
|
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.`
|
||||||
|
@ -11,6 +11,37 @@ import {
|
|||||||
import { getBlock, MessageObject } from './web3'
|
import { getBlock, MessageObject } from './web3'
|
||||||
import validatorsCache from '../services/ValidatorsCache'
|
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 (
|
export const getFinalizationEvent = async (
|
||||||
contract: Maybe<Contract>,
|
contract: Maybe<Contract>,
|
||||||
eventName: string,
|
eventName: string,
|
||||||
@ -29,32 +60,9 @@ export const getFinalizationEvent = async (
|
|||||||
setExecutionEventsFetched: Function
|
setExecutionEventsFetched: Function
|
||||||
) => {
|
) => {
|
||||||
if (!contract || !web3 || !waitingBlocksResolved) return
|
if (!contract || !web3 || !waitingBlocksResolved) return
|
||||||
// Since it filters by the message id, only one event will be fetched
|
const successExecutionData = await getSuccessExecutionData(contract, eventName, web3, message.id)
|
||||||
// so there is no need to limit the range of the block to reduce the network traffic
|
if (successExecutionData) {
|
||||||
const events: EventData[] = await contract.getPastEvents(eventName, {
|
setResult(successExecutionData)
|
||||||
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
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
setExecutionEventsFetched(true)
|
setExecutionEventsFetched(true)
|
||||||
// If event is defined, it means it is a message from Home to Foreign
|
// If event is defined, it means it is a message from Home to Foreign
|
||||||
|
Loading…
Reference in New Issue
Block a user