From bcdf691000c3eaa1f4a656641a2369377e356298 Mon Sep 17 00:00:00 2001 From: Gerardo Nardelli Date: Tue, 9 Jun 2020 10:37:43 -0300 Subject: [PATCH 01/13] Add Alm project structure and transaction form (#353) --- alm/.env.example | 6 + alm/package.json | 10 +- alm/public/index.html | 3 +- alm/public/logo192.png | Bin 5347 -> 0 bytes alm/public/logo512.png | Bin 9664 -> 0 bytes alm/public/manifest.json | 14 +- alm/src/App.css | 14 - alm/src/App.tsx | 11 +- alm/src/components/Form.tsx | 90 +++ alm/src/components/MainPage.tsx | 48 ++ alm/src/components/MessageSelector.tsx | 46 ++ alm/src/components/StatusContainer.tsx | 76 +++ alm/src/components/commons/Button.tsx | 5 + alm/src/components/commons/ExplorerTxLink.tsx | 6 + alm/src/components/commons/Loading.tsx | 153 +++++ alm/src/components/commons/RadioButton.tsx | 9 + alm/src/config/constants.ts | 19 + alm/src/config/descriptions.ts | 8 + alm/src/global.d.ts | 1 + alm/src/hooks/useNetwork.ts | 27 + alm/src/hooks/useTransactionStatus.ts | 99 +++ alm/src/index.css | 8 - alm/src/index.tsx | 9 +- alm/src/state/StateProvider.tsx | 67 ++ alm/src/themes/Dark.tsx | 9 + alm/src/themes/GlobalStyle.tsx | 25 + alm/src/utils/networks.ts | 32 + alm/src/utils/web3.ts | 36 + ui/package.json | 4 +- yarn.lock | 645 +++++++++++++++++- 30 files changed, 1400 insertions(+), 80 deletions(-) delete mode 100644 alm/public/logo192.png delete mode 100644 alm/public/logo512.png delete mode 100644 alm/src/App.css create mode 100644 alm/src/components/Form.tsx create mode 100644 alm/src/components/MainPage.tsx create mode 100644 alm/src/components/MessageSelector.tsx create mode 100644 alm/src/components/StatusContainer.tsx create mode 100644 alm/src/components/commons/Button.tsx create mode 100644 alm/src/components/commons/ExplorerTxLink.tsx create mode 100644 alm/src/components/commons/Loading.tsx create mode 100644 alm/src/components/commons/RadioButton.tsx create mode 100644 alm/src/config/constants.ts create mode 100644 alm/src/config/descriptions.ts create mode 100644 alm/src/global.d.ts create mode 100644 alm/src/hooks/useNetwork.ts create mode 100644 alm/src/hooks/useTransactionStatus.ts delete mode 100644 alm/src/index.css create mode 100644 alm/src/state/StateProvider.tsx create mode 100644 alm/src/themes/Dark.tsx create mode 100644 alm/src/themes/GlobalStyle.tsx create mode 100644 alm/src/utils/networks.ts create mode 100644 alm/src/utils/web3.ts diff --git a/alm/.env.example b/alm/.env.example index 36568a102..1fb92664e 100644 --- a/alm/.env.example +++ b/alm/.env.example @@ -3,3 +3,9 @@ COMMON_FOREIGN_BRIDGE_ADDRESS=0xFe446bEF1DbF7AFE24E81e05BC8B271C1BA9a560 COMMON_HOME_RPC_URL=https://sokol.poa.network 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 diff --git a/alm/package.json b/alm/package.json index 784ca2193..2c8cc44db 100644 --- a/alm/package.json +++ b/alm/package.json @@ -3,6 +3,7 @@ "version": "0.1.0", "private": true, "dependencies": { + "@react-hook/window-size": "^3.0.6", "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^7.1.2", @@ -10,12 +11,19 @@ "@types/node": "^12.0.0", "@types/react": "^16.9.0", "@types/react-dom": "^16.9.0", + "@types/react-router-dom": "^5.1.5", + "@types/styled-components": "^5.1.0", "customize-cra": "^1.0.0", + "date-fns": "^2.14.0", + "fast-memoize": "^2.5.2", "react": "^16.13.1", "react-app-rewired": "^2.1.6", "react-dom": "^16.13.1", + "react-router-dom": "^5.2.0", "react-scripts": "3.0.1", - "typescript": "^3.5.2" + "styled-components": "^5.1.1", + "typescript": "^3.5.2", + "web3": "^1.2.8" }, "scripts": { "start": "./load-env.sh react-app-rewired start", diff --git a/alm/public/index.html b/alm/public/index.html index aa069f27c..bc20b244c 100644 --- a/alm/public/index.html +++ b/alm/public/index.html @@ -7,8 +7,9 @@ + - React App + AMB Live Monitoring diff --git a/alm/src/App.test.tsx b/alm/src/App.test.tsx index a2949045e..fe49a358e 100644 --- a/alm/src/App.test.tsx +++ b/alm/src/App.test.tsx @@ -1,9 +1,5 @@ import React from 'react' -import { render } from '@testing-library/react' -import App from './App' test('renders learn react link', () => { - const { getByText } = render() - const linkElement = getByText(/AMB Live Monitoring/i) - expect(linkElement).toBeInTheDocument() + // Removed basic test from setup. Keeping this so CI passes until we add unit tests. }) diff --git a/alm/src/components/ConfirmationsContainer.tsx b/alm/src/components/ConfirmationsContainer.tsx new file mode 100644 index 000000000..3fcee5a2f --- /dev/null +++ b/alm/src/components/ConfirmationsContainer.tsx @@ -0,0 +1,72 @@ +import React 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 } from '../config/constants' +import { CONFIRMATIONS_STATUS_LABEL } 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' + +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(--color-primary); + padding: 10px; + border-radius: 4px; +` + +const StatusDescription = styled.div` + padding-top: 10px; +` + +export interface ConfirmationsContainerParams { + message: MessageObject + receipt: Maybe + fromHome: boolean +} + +export const ConfirmationsContainer = ({ message, receipt, fromHome }: ConfirmationsContainerParams) => { + const { + home: { name: homeName }, + foreign: { name: foreignName } + } = useStateProvider() + const { confirmations, status, executionData, signatureCollected } = useMessageConfirmations({ + message, + receipt, + fromHome + }) + + return ( +
+ +
+ Status: + + {status !== CONFIRMATIONS_STATUS.UNDEFINED ? CONFIRMATIONS_STATUS_LABEL[status] : } + +
+ +

+ {status !== CONFIRMATIONS_STATUS.UNDEFINED + ? getConfirmationsStatusDescription(status, homeName, foreignName) + : ''} +

+
+ + {signatureCollected && } +
+
+ ) +} diff --git a/alm/src/components/ExecutionConfirmation.tsx b/alm/src/components/ExecutionConfirmation.tsx new file mode 100644 index 000000000..755e6c99e --- /dev/null +++ b/alm/src/components/ExecutionConfirmation.tsx @@ -0,0 +1,66 @@ +import React from 'react' +import { formatTimestamp, formatTxHash, getExplorerTxUrl } from '../utils/networks' +import { useWindowWidth } from '@react-hook/window-size' +import { VALIDATOR_CONFIRMATION_STATUS } from '../config/constants' +import { SimpleLoading } from './commons/Loading' +import styled from 'styled-components' +import { ExecutionData } from '../hooks/useMessageConfirmations' +import { GreyLabel, SuccessLabel } from './commons/Labels' +import { ExplorerTxLink } from './commons/ExplorerTxLink' + +const Thead = styled.thead` + border-bottom: 2px solid #9e9e9e; +` + +const StyledExecutionConfirmation = styled.div` + margin-top: 30px; +` + +export interface ExecutionConfirmationParams { + executionData: ExecutionData + isHome: boolean +} + +export const ExecutionConfirmation = ({ executionData, isHome }: ExecutionConfirmationParams) => { + const windowWidth = useWindowWidth() + + const txExplorerLink = getExplorerTxUrl(executionData.txHash, isHome) + const formattedValidator = + windowWidth < 850 && executionData.validator ? formatTxHash(executionData.validator) : executionData.validator + + const getExecutionStatusElement = (validatorStatus = '') => { + switch (validatorStatus) { + case VALIDATOR_CONFIRMATION_STATUS.SUCCESS: + return {validatorStatus} + case VALIDATOR_CONFIRMATION_STATUS.WAITING: + return {validatorStatus} + default: + return + } + } + + return ( + + + + + + + + + + + + + + + + +
Executed byStatusAge
{formattedValidator ? formattedValidator : }{getExecutionStatusElement(executionData.status)} + + {executionData.timestamp > 0 ? formatTimestamp(executionData.timestamp) : ''} + +
+
+ ) +} diff --git a/alm/src/components/Form.tsx b/alm/src/components/Form.tsx index 7f8e2ab73..9576c1c3f 100644 --- a/alm/src/components/Form.tsx +++ b/alm/src/components/Form.tsx @@ -14,6 +14,7 @@ const LabelText = styled.label` const Input = styled.input` background-color: var(--color-primary); color: var(--font-color); + max-width: 100%; ` export const Form = ({ onSubmit }: { onSubmit: ({ chainId, txHash }: FormSubmitParams) => void }) => { diff --git a/alm/src/components/MainPage.tsx b/alm/src/components/MainPage.tsx index 21fcfae5f..9eeec8361 100644 --- a/alm/src/components/MainPage.tsx +++ b/alm/src/components/MainPage.tsx @@ -1,6 +1,6 @@ import React from 'react' import styled from 'styled-components' -import { Route, useHistory } from 'react-router-dom' +import { Route, useHistory, Link } from 'react-router-dom' import { Form } from './Form' import { StatusContainer } from './StatusContainer' import { StateProvider } from '../state/StateProvider' @@ -18,6 +18,10 @@ const Header = styled.header` font-size: calc(10px + 2vmin); ` +const Title = styled.p` + color: var(--font-color); +` + export interface FormSubmitParams { chainId: number txHash: string @@ -33,13 +37,12 @@ export const MainPage = () => {
-

AMB Live Monitoring

+ + AMB Live Monitoring +
- } - /> + } /> } />
diff --git a/alm/src/components/MessageSelector.tsx b/alm/src/components/MessageSelector.tsx index 1e192f6b5..32aaa3eca 100644 --- a/alm/src/components/MessageSelector.tsx +++ b/alm/src/components/MessageSelector.tsx @@ -3,9 +3,10 @@ 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 + messages: Array onMessageSelected: (index: number) => void } @@ -31,7 +32,7 @@ export const MessageSelector = ({ messages, onMessageSelected }: MessageSelector onChange={() => setMessageIndex(i)} /> - {windowWidth < 700 ? formatTxHashExtended(message) : message} + {windowWidth < 700 ? formatTxHashExtended(message.id) : message.id} ))} diff --git a/alm/src/components/StatusContainer.tsx b/alm/src/components/StatusContainer.tsx index 4491f6c7d..6d9591199 100644 --- a/alm/src/components/StatusContainer.tsx +++ b/alm/src/components/StatusContainer.tsx @@ -7,6 +7,7 @@ import { MessageSelector } from './MessageSelector' import { Loading } from './commons/Loading' import { useStateProvider } from '../state/StateProvider' import { ExplorerTxLink } from './commons/ExplorerTxLink' +import { ConfirmationsContainer } from './ConfirmationsContainer' export const StatusContainer = () => { const { home, foreign } = useStateProvider() @@ -15,15 +16,14 @@ export const StatusContainer = () => { const validChainId = chainId === home.chainId.toString() || chainId === foreign.chainId.toString() const validParameters = validChainId && validTxHash(txHash) - const { messagesId, status, description, timestamp, loading } = useTransactionStatus({ + const { messages, receipt, status, description, timestamp, loading } = useTransactionStatus({ txHash: validParameters ? txHash : '', chainId: validParameters ? parseInt(chainId) : 0 }) - const selectedMessageId = - messageIdParam === undefined || messagesId[messageIdParam] === undefined ? -1 : messageIdParam + const selectedMessageId = messageIdParam === undefined || messages[messageIdParam] === undefined ? -1 : messageIdParam - if (!validParameters) { + if (!validParameters && home.chainId && foreign.chainId) { return (

@@ -43,7 +43,7 @@ export const StatusContainer = () => { const displayMessageSelector = status === TRANSACTION_STATUS.SUCCESS_MULTIPLE_MESSAGES && selectedMessageId === -1 const multiMessageSelected = status === TRANSACTION_STATUS.SUCCESS_MULTIPLE_MESSAGES && selectedMessageId !== -1 - const displayReference = multiMessageSelected ? messagesId[selectedMessageId] : txHash + const displayReference = multiMessageSelected ? messages[selectedMessageId].id : txHash const formattedMessageId = formatTxHash(displayReference) const displayedDescription = multiMessageSelected @@ -54,6 +54,9 @@ export const StatusContainer = () => { 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: '' } return (

{status && ( @@ -70,7 +73,10 @@ export const StatusContainer = () => { {displayedDescription}

)} - {displayMessageSelector && } + {displayMessageSelector && } + {displayConfirmations && ( + + )}
) } diff --git a/alm/src/components/ValidatorsConfirmations.tsx b/alm/src/components/ValidatorsConfirmations.tsx new file mode 100644 index 000000000..4bff387d4 --- /dev/null +++ b/alm/src/components/ValidatorsConfirmations.tsx @@ -0,0 +1,69 @@ +import React from 'react' +import { formatTxHashExtended } from '../utils/networks' +import { useStateProvider } from '../state/StateProvider' +import { useWindowWidth } from '@react-hook/window-size' +import { VALIDATOR_CONFIRMATION_STATUS } from '../config/constants' +import { SimpleLoading } from './commons/Loading' +import styled from 'styled-components' +import { ConfirmationParam } from '../hooks/useMessageConfirmations' +import { GreyLabel, SuccessLabel } from './commons/Labels' + +const Thead = styled.thead` + border-bottom: 2px solid #9e9e9e; +` + +const RequiredConfirmations = styled.label` + font-size: 14px; +` + +export interface ValidatorsConfirmationsParams { + confirmations: Array +} + +export const ValidatorsConfirmations = ({ confirmations }: ValidatorsConfirmationsParams) => { + const { + home: { requiredSignatures, validatorList } + } = useStateProvider() + const windowWidth = useWindowWidth() + + const getValidatorStatusElement = (validatorStatus = '') => { + switch (validatorStatus) { + case VALIDATOR_CONFIRMATION_STATUS.SUCCESS: + return {validatorStatus} + case VALIDATOR_CONFIRMATION_STATUS.WAITING: + case VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED: + return {validatorStatus} + default: + return + } + } + + return ( +
+ + + + + + + + + {validatorList.map((validator, i) => { + const filteredConfirmation = confirmations.filter(c => c.validator === validator) + const confirmation = filteredConfirmation.length > 0 ? filteredConfirmation[0] : null + const displayedStatus = confirmation && confirmation.status ? confirmation.status : '' + return ( + + + + + ) + })} + +
ValidatorConfirmations
{windowWidth < 850 ? formatTxHashExtended(validator) : validator}{getValidatorStatusElement(displayedStatus)}
+ + {requiredSignatures} of {validatorList.length} confirmations required + +
+ ) +} diff --git a/alm/src/components/commons/Labels.tsx b/alm/src/components/commons/Labels.tsx new file mode 100644 index 000000000..282031220 --- /dev/null +++ b/alm/src/components/commons/Labels.tsx @@ -0,0 +1,15 @@ +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; +` diff --git a/alm/src/components/commons/Loading.tsx b/alm/src/components/commons/Loading.tsx index def8626db..8519ede21 100644 --- a/alm/src/components/commons/Loading.tsx +++ b/alm/src/components/commons/Loading.tsx @@ -1,12 +1,18 @@ import React from 'react' -export const Loading = () => ( +export interface LoadingParams { + width?: string + height?: string + displayMessage?: boolean +} + +export const Loading = ({ width = '50px', height = '50px', displayMessage = true }: LoadingParams) => (
@@ -148,6 +154,8 @@ export const Loading = () => ( - + {displayMessage && }
) + +export const SimpleLoading = () => diff --git a/alm/src/config/constants.ts b/alm/src/config/constants.ts index 88df9ba2b..fef4e53c2 100644 --- a/alm/src/config/constants.ts +++ b/alm/src/config/constants.ts @@ -10,6 +10,10 @@ export const FOREIGN_NETWORK_NAME: string = process.env.REACT_APP_ALM_FOREIGN_NE 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_RPC_POLLING_INTERVAL: number = 5000 +export const FOREIGN_RPC_POLLING_INTERVAL: number = 15000 +export const BLOCK_RANGE: number = 50 + export const TRANSACTION_STATUS = { SUCCESS_MULTIPLE_MESSAGES: 'SUCCESS_MULTIPLE_MESSAGES', SUCCESS_ONE_MESSAGE: 'SUCCESS_ONE_MESSAGE', @@ -17,3 +21,24 @@ export const TRANSACTION_STATUS = { FAILED: 'FAILED', NOT_FOUND: 'NOT_FOUND' } + +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', + WAITING: 'WAITING', + UNDEFINED: 'UNDEFINED' +} + +export const VALIDATOR_CONFIRMATION_STATUS = { + SUCCESS: 'Success', + FAILED: 'Failed', + PENDING: 'Pending', + WAITING: 'Waiting', + NOT_REQUIRED: 'Not required', + UNDEFINED: 'UNDEFINED' +} diff --git a/alm/src/config/descriptions.ts b/alm/src/config/descriptions.ts index b28906fe0..fb395555a 100644 --- a/alm/src/config/descriptions.ts +++ b/alm/src/config/descriptions.ts @@ -6,3 +6,31 @@ export const TRANSACTION_STATUS_DESCRIPTION: { [key: string]: string } = { FAILED: 'failed %t', NOT_FOUND: 'was not found' } + +export const CONFIRMATIONS_STATUS_LABEL: { [key: string]: string } = { + SUCCESS: 'Success', + SUCCESS_MESSAGE_FAILED: 'Success', + EXECUTION_FAILED: 'Execution failed', + EXECUTION_PENDING: 'Execution pending', + EXECUTION_WAITING: 'Execution waiting', + FAILED: 'Failed', + PENDING: 'Pending', + WAITING: 'Waiting' +} + +// %homeChain will be replaced by the home network name +// %foreignChain will be replaced by the foreign network name +export const CONFIRMATIONS_STATUS_DESCRIPTION: { [key: string]: string } = { + SUCCESS: '', + SUCCESS_MESSAGE_FAILED: + 'Signatures have been collected in the %homeChain and they were successfully sent to the %foreignChain but the contained message execution failed.', + EXECUTION_FAILED: + 'Signatures have been collected in the %homeChain and they were sent to the %foreignChain but the transaction with signatures failed', + EXECUTION_PENDING: + 'Signatures have been collected in the %homeChain and they were sent to the %foreignChain but the transaction is in the pending state (transactions congestion or low gas price)', + EXECUTION_WAITING: 'Execution waiting', + FAILED: + 'Some validators sent improper transactions as so they were failed, collected confirmations are not enough to execute the relay request', + PENDING: 'Some confirmations are in pending state', + WAITING: 'Validators are waiting for the chain finalization' +} diff --git a/alm/src/hooks/useBridgeContracts.ts b/alm/src/hooks/useBridgeContracts.ts new file mode 100644 index 000000000..4e253eec7 --- /dev/null +++ b/alm/src/hooks/useBridgeContracts.ts @@ -0,0 +1,90 @@ +import { useEffect, useState } from 'react' +import { HOME_AMB_ABI, FOREIGN_AMB_ABI, BRIDGE_VALIDATORS_ABI } from '../../../commons' +import { FOREIGN_BRIDGE_ADDRESS, HOME_BRIDGE_ADDRESS } from '../config/constants' +import { Contract } from 'web3-eth-contract' +import Web3 from 'web3' +import { + getRequiredBlockConfirmations, + getRequiredSignatures, + getValidatorAddress, + getValidatorList +} from '../utils/contract' + +export interface useBridgeContractsParams { + homeWeb3: Web3 + foreignWeb3: Web3 +} + +export const useBridgeContracts = ({ homeWeb3, foreignWeb3 }: useBridgeContractsParams) => { + const [homeBridge, setHomeBridge] = useState>(null) + const [foreignBridge, setForeignBridge] = useState>(null) + const [homeBlockConfirmations, setHomeBlockConfirmations] = useState(0) + const [foreignBlockConfirmations, setForeignBlockConfirmations] = useState(0) + const [homeValidatorContract, setHomeValidatorContract] = useState>(null) + const [homeRequiredSignatures, setHomeRequiredSignatures] = useState(0) + const [homeValidatorList, setHomeValidatorList] = useState([]) + + const callRequireBlockConfirmations = async (contract: Maybe, setResult: Function) => { + if (!contract) return + const result = await getRequiredBlockConfirmations(contract) + setResult(result) + } + + const callValidatorContract = async (bridgeContract: Maybe, 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, setResult: Function) => { + if (!contract) return + const result = await getRequiredSignatures(contract) + setResult(result) + } + + const callValidatorList = async (contract: Maybe, setResult: Function) => { + if (!contract) return + const result = await getValidatorList(contract) + setResult(result) + } + + useEffect( + () => { + if (!homeWeb3) return + const homeContract = new homeWeb3.eth.Contract(HOME_AMB_ABI, HOME_BRIDGE_ADDRESS) + callRequireBlockConfirmations(homeContract, setHomeBlockConfirmations) + callValidatorContract(homeContract, homeWeb3, setHomeValidatorContract) + setHomeBridge(homeContract) + }, + [homeWeb3] + ) + + useEffect( + () => { + if (!foreignWeb3) return + const foreignContract = new foreignWeb3.eth.Contract(FOREIGN_AMB_ABI, FOREIGN_BRIDGE_ADDRESS) + callRequireBlockConfirmations(foreignContract, setForeignBlockConfirmations) + setForeignBridge(foreignContract) + }, + [foreignWeb3] + ) + + useEffect( + () => { + callRequiredSignatures(homeValidatorContract, setHomeRequiredSignatures) + callValidatorList(homeValidatorContract, setHomeValidatorList) + }, + [homeValidatorContract] + ) + + return { + homeBridge, + foreignBridge, + homeBlockConfirmations, + foreignBlockConfirmations, + homeValidatorContract, + homeRequiredSignatures, + homeValidatorList + } +} diff --git a/alm/src/hooks/useMessageConfirmations.ts b/alm/src/hooks/useMessageConfirmations.ts new file mode 100644 index 000000000..4615b15b1 --- /dev/null +++ b/alm/src/hooks/useMessageConfirmations.ts @@ -0,0 +1,305 @@ +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 { getAffirmationsSigned, getMessagesSigned } from '../utils/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 { checkSignaturesWaitingForBLocks } from '../utils/signatureWaitingForBlocks' +import { getCollectedSignaturesEvent } from '../utils/getCollectedSignaturesEvent' +import { checkWaitingBlocksForExecution } from '../utils/executionWaitingForBlocks' +import { getConfirmationsForTx } from '../utils/getConfirmationsForTx' +import { getFinalizationEvent } from '../utils/getFinalizationEvent' + +export interface useMessageConfirmationsParams { + message: MessageObject + receipt: Maybe + fromHome: boolean +} + +export interface ConfirmationParam { + validator: string + status: string +} + +export interface ExecutionData { + status: string + validator: string + txHash: string + timestamp: number + executionResult: boolean +} + +export const useMessageConfirmations = ({ message, receipt, fromHome }: useMessageConfirmationsParams) => { + const { home, foreign } = useStateProvider() + const [confirmations, setConfirmations] = useState>([]) + const [status, setStatus] = useState(CONFIRMATIONS_STATUS.UNDEFINED) + const [waitingBlocks, setWaitingBlocks] = useState(false) + const [waitingBlocksResolved, setWaitingBlocksResolved] = useState(false) + const [signatureCollected, setSignatureCollected] = useState(false) + const [collectedSignaturesEvent, setCollectedSignaturesEvent] = useState>(null) + const [executionData, setExecutionData] = useState({ + status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, + validator: '', + txHash: '', + timestamp: 0, + executionResult: false + }) + const [waitingBlocksForExecution, setWaitingBlocksForExecution] = useState(false) + const [waitingBlocksForExecutionResolved, setWaitingBlocksForExecutionResolved] = useState(false) + + // Check if the validators are waiting for block confirmations to verify the message + useEffect( + () => { + if (!receipt) return + + const subscriptions: Array = [] + + const unsubscribe = () => { + subscriptions.forEach(s => { + clearTimeout(s) + }) + } + + const blockProvider = fromHome ? homeBlockNumberProvider : foreignBlockNumberProvider + const interval = fromHome ? HOME_RPC_POLLING_INTERVAL : FOREIGN_RPC_POLLING_INTERVAL + const web3 = fromHome ? home.web3 : foreign.web3 + blockProvider.start(web3) + + const requiredBlockConfirmations = fromHome ? home.blockConfirmations : foreign.blockConfirmations + const targetBlock = receipt.blockNumber + requiredBlockConfirmations + + checkSignaturesWaitingForBLocks( + targetBlock, + setWaitingBlocks, + setWaitingBlocksResolved, + home.validatorList, + setConfirmations, + blockProvider, + interval, + subscriptions + ) + + return () => { + unsubscribe() + blockProvider.stop() + } + }, + [ + foreign.blockConfirmations, + foreign.web3, + fromHome, + home.blockConfirmations, + home.validatorList, + home.web3, + receipt + ] + ) + + // 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 || !signatureCollected) return + + const subscriptions: Array = [] + + const unsubscribe = () => { + subscriptions.forEach(s => { + clearTimeout(s) + }) + } + + homeBlockNumberProvider.start(home.web3) + + const fromBlock = receipt.blockNumber + const toBlock = fromBlock + BLOCK_RANGE + const messageHash = home.web3.utils.soliditySha3Raw(message.data) + + getCollectedSignaturesEvent( + home.web3, + home.bridgeContract, + fromBlock, + toBlock, + messageHash, + setCollectedSignaturesEvent, + subscriptions + ) + + return () => { + unsubscribe() + homeBlockNumberProvider.stop() + } + }, + [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 || !receipt || !collectedSignaturesEvent) return + + const subscriptions: Array = [] + + const unsubscribe = () => { + subscriptions.forEach(s => { + clearTimeout(s) + }) + } + + homeBlockNumberProvider.start(home.web3) + const targetBlock = collectedSignaturesEvent.blockNumber + home.blockConfirmations + + checkWaitingBlocksForExecution( + homeBlockNumberProvider, + HOME_RPC_POLLING_INTERVAL, + targetBlock, + collectedSignaturesEvent, + setWaitingBlocksForExecution, + setWaitingBlocksForExecutionResolved, + setExecutionData, + subscriptions + ) + + return () => { + unsubscribe() + homeBlockNumberProvider.stop() + } + }, + [collectedSignaturesEvent, fromHome, home.blockConfirmations, home.web3, receipt] + ) + + // 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) return + + const subscriptions: Array = [] + + const unsubscribe = () => { + subscriptions.forEach(s => { + clearTimeout(s) + }) + } + + const confirmationContractMethod = fromHome ? getMessagesSigned : getAffirmationsSigned + + getConfirmationsForTx( + message.data, + home.web3, + home.validatorList, + home.bridgeContract, + confirmationContractMethod, + setConfirmations, + home.requiredSignatures, + setSignatureCollected, + waitingBlocksResolved, + subscriptions + ) + + return () => { + unsubscribe() + } + }, + [ + fromHome, + message.data, + home.web3, + home.validatorList, + home.bridgeContract, + home.requiredSignatures, + waitingBlocksResolved + ] + ) + + // 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 subscriptions: Array = [] + + const unsubscribe = () => { + subscriptions.forEach(s => { + clearTimeout(s) + }) + } + + const contractEvent = fromHome ? 'RelayedMessage' : 'AffirmationCompleted' + const bridgeContract = fromHome ? foreign.bridgeContract : home.bridgeContract + const providedWeb3 = fromHome ? foreign.web3 : home.web3 + const interval = fromHome ? FOREIGN_RPC_POLLING_INTERVAL : HOME_RPC_POLLING_INTERVAL + + getFinalizationEvent( + bridgeContract, + contractEvent, + providedWeb3, + setExecutionData, + waitingBlocksResolved, + message.id, + interval, + subscriptions + ) + + return () => { + unsubscribe() + } + }, + [ + fromHome, + foreign.bridgeContract, + home.bridgeContract, + message.id, + foreign.web3, + home.web3, + waitingBlocksResolved, + waitingBlocksForExecutionResolved + ] + ) + + // Sets the message status based in the collected information + useEffect( + () => { + if (executionData.txHash) { + const newStatus = executionData.executionResult + ? CONFIRMATIONS_STATUS.SUCCESS + : CONFIRMATIONS_STATUS.SUCCESS_MESSAGE_FAILED + setStatus(newStatus) + } else if (signatureCollected) { + if (fromHome) { + if (waitingBlocksForExecution) { + setStatus(CONFIRMATIONS_STATUS.EXECUTION_WAITING) + } else { + setStatus(CONFIRMATIONS_STATUS.UNDEFINED) + } + } else { + setStatus(CONFIRMATIONS_STATUS.UNDEFINED) + } + } else if (waitingBlocks) { + setStatus(CONFIRMATIONS_STATUS.WAITING) + } else { + setStatus(CONFIRMATIONS_STATUS.UNDEFINED) + } + }, + [executionData, fromHome, signatureCollected, waitingBlocks, waitingBlocksForExecution] + ) + + return { + confirmations, + status, + signatureCollected, + executionData + } +} diff --git a/alm/src/hooks/useTransactionStatus.ts b/alm/src/hooks/useTransactionStatus.ts index 0a70b1d43..9a981744c 100644 --- a/alm/src/hooks/useTransactionStatus.ts +++ b/alm/src/hooks/useTransactionStatus.ts @@ -1,13 +1,13 @@ import { useEffect, useState } from 'react' import { TransactionReceipt } from 'web3-eth' -import { TRANSACTION_STATUS } from '../config/constants' +import { HOME_RPC_POLLING_INTERVAL, TRANSACTION_STATUS } from '../config/constants' import { getTransactionStatusDescription } from '../utils/networks' import { useStateProvider } from '../state/StateProvider' -import { getHomeMessagesFromReceipt, getForeignMessagesFromReceipt } from '../utils/web3' +import { getHomeMessagesFromReceipt, getForeignMessagesFromReceipt, MessageObject } from '../utils/web3' export const useTransactionStatus = ({ txHash, chainId }: { txHash: string; chainId: number }) => { const { home, foreign } = useStateProvider() - const [messagesId, setMessagesId] = useState>([]) + const [messages, setMessages] = useState>([]) const [status, setStatus] = useState('') const [description, setDescription] = useState('') const [receipt, setReceipt] = useState>(null) @@ -36,8 +36,8 @@ export const useTransactionStatus = ({ txHash, chainId }: { txHash: string; chai if (!txReceipt) { setStatus(TRANSACTION_STATUS.NOT_FOUND) setDescription(getTransactionStatusDescription(TRANSACTION_STATUS.NOT_FOUND)) - setMessagesId([txHash]) - const timeoutId = setTimeout(() => getReceipt(), 5000) + setMessages([{ id: txHash, data: '' }]) + const timeoutId = setTimeout(() => getReceipt(), HOME_RPC_POLLING_INTERVAL) subscriptions.push(timeoutId) } else { const blockNumber = txReceipt.blockNumber @@ -46,23 +46,23 @@ export const useTransactionStatus = ({ txHash, chainId }: { txHash: string; chai setTimestamp(blockTimestamp) if (txReceipt.status) { - let bridgeMessagesId + let bridgeMessages: Array if (isHome) { - bridgeMessagesId = getHomeMessagesFromReceipt(txReceipt, home.web3, home.bridgeAddress) + bridgeMessages = getHomeMessagesFromReceipt(txReceipt, home.web3, home.bridgeAddress) } else { - bridgeMessagesId = getForeignMessagesFromReceipt(txReceipt, foreign.web3, foreign.bridgeAddress) + bridgeMessages = getForeignMessagesFromReceipt(txReceipt, foreign.web3, foreign.bridgeAddress) } - if (bridgeMessagesId.length === 0) { - setMessagesId([txHash]) + if (bridgeMessages.length === 0) { + setMessages([{ id: txHash, data: '' }]) setStatus(TRANSACTION_STATUS.SUCCESS_NO_MESSAGES) setDescription(getTransactionStatusDescription(TRANSACTION_STATUS.SUCCESS_NO_MESSAGES, blockTimestamp)) - } else if (bridgeMessagesId.length === 1) { - setMessagesId(bridgeMessagesId) + } else if (bridgeMessages.length === 1) { + setMessages(bridgeMessages) setStatus(TRANSACTION_STATUS.SUCCESS_ONE_MESSAGE) setDescription(getTransactionStatusDescription(TRANSACTION_STATUS.SUCCESS_ONE_MESSAGE, blockTimestamp)) } else { - setMessagesId(bridgeMessagesId) + setMessages(bridgeMessages) setStatus(TRANSACTION_STATUS.SUCCESS_MULTIPLE_MESSAGES) setDescription( getTransactionStatusDescription(TRANSACTION_STATUS.SUCCESS_MULTIPLE_MESSAGES, blockTimestamp) @@ -89,7 +89,7 @@ export const useTransactionStatus = ({ txHash, chainId }: { txHash: string; chai ) return { - messagesId, + messages, status, description, receipt, diff --git a/alm/src/services/BlockNumberProvider.ts b/alm/src/services/BlockNumberProvider.ts new file mode 100644 index 000000000..a6606c87d --- /dev/null +++ b/alm/src/services/BlockNumberProvider.ts @@ -0,0 +1,64 @@ +import Web3 from 'web3' +import differenceInMilliseconds from 'date-fns/differenceInMilliseconds' +import { HOME_RPC_POLLING_INTERVAL } from '../config/constants' + +export class BlockNumberProvider { + private running: number + private web3: Maybe + private ref: number | undefined + private value: Maybe + private lastValueTimestamp: Maybe + 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) { + 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 - 1 + + 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(HOME_RPC_POLLING_INTERVAL) diff --git a/alm/src/services/ValidatorsCache.ts b/alm/src/services/ValidatorsCache.ts new file mode 100644 index 000000000..a3406943e --- /dev/null +++ b/alm/src/services/ValidatorsCache.ts @@ -0,0 +1,17 @@ +class ValidatorsCache { + private readonly store: { [key: string]: boolean } + + constructor() { + this.store = {} + } + + get(key: string) { + return this.store[key] + } + + set(key: string, value: boolean) { + this.store[key] = value + } +} + +export default new ValidatorsCache() diff --git a/alm/src/state/StateProvider.tsx b/alm/src/state/StateProvider.tsx index 54f3307b3..0c5172581 100644 --- a/alm/src/state/StateProvider.tsx +++ b/alm/src/state/StateProvider.tsx @@ -9,17 +9,27 @@ import { FOREIGN_NETWORK_NAME } from '../config/constants' import Web3 from 'web3' +import { useBridgeContracts } from '../hooks/useBridgeContracts' +import { Contract } from 'web3-eth-contract' -export interface NetworkParams { +export interface BaseNetworkParams { chainId: number name: string web3: Maybe bridgeAddress: string + bridgeContract: Maybe + blockConfirmations: number +} + +export interface HomeNetworkParams extends BaseNetworkParams { + validatorContract: Maybe + requiredSignatures: number + validatorList: Array } export interface StateContext { - home: NetworkParams - foreign: NetworkParams + home: HomeNetworkParams + foreign: BaseNetworkParams loading: boolean } @@ -28,13 +38,20 @@ const initialState = { chainId: 0, name: '', web3: null, - bridgeAddress: HOME_BRIDGE_ADDRESS + bridgeAddress: HOME_BRIDGE_ADDRESS, + bridgeContract: null, + blockConfirmations: 0, + validatorContract: null, + requiredSignatures: 0, + validatorList: [] }, foreign: { chainId: 0, name: '', web3: null, - bridgeAddress: FOREIGN_BRIDGE_ADDRESS + bridgeAddress: FOREIGN_BRIDGE_ADDRESS, + bridgeContract: null, + blockConfirmations: 0 }, loading: true } @@ -44,16 +61,35 @@ const StateContext = createContext(initialState) export const StateProvider = ({ children }: { children: ReactNode }) => { const homeNetwork = useNetwork(HOME_RPC_URL) const foreignNetwork = useNetwork(FOREIGN_RPC_URL) + const { + homeBridge, + foreignBridge, + homeBlockConfirmations, + foreignBlockConfirmations, + homeValidatorContract, + homeRequiredSignatures, + homeValidatorList + } = useBridgeContracts({ + homeWeb3: homeNetwork.web3, + foreignWeb3: foreignNetwork.web3 + }) const value = { home: { bridgeAddress: HOME_BRIDGE_ADDRESS, name: HOME_NETWORK_NAME, + bridgeContract: homeBridge, + blockConfirmations: homeBlockConfirmations, + validatorContract: homeValidatorContract, + requiredSignatures: homeRequiredSignatures, + validatorList: homeValidatorList, ...homeNetwork }, foreign: { bridgeAddress: FOREIGN_BRIDGE_ADDRESS, name: FOREIGN_NETWORK_NAME, + bridgeContract: foreignBridge, + blockConfirmations: foreignBlockConfirmations, ...foreignNetwork }, loading: homeNetwork.loading || foreignNetwork.loading diff --git a/alm/src/themes/Dark.tsx b/alm/src/themes/Dark.tsx index 26797df93..07a4e0f46 100644 --- a/alm/src/themes/Dark.tsx +++ b/alm/src/themes/Dark.tsx @@ -4,6 +4,14 @@ const theme = { colorPrimary: '#272727', colorGrey: '#272727', colorLightGrey: '#272727', - linkColor: '#ffffff' + linkColor: '#ffffff', + success: { + textColor: '#00c9a7', + backgroundColor: '#004d40' + }, + notRequired: { + textColor: '#bdbdbd', + backgroundColor: '#424242' + } } export default theme diff --git a/alm/src/themes/GlobalStyle.tsx b/alm/src/themes/GlobalStyle.tsx index 13f4e62f4..d10ea42d1 100644 --- a/alm/src/themes/GlobalStyle.tsx +++ b/alm/src/themes/GlobalStyle.tsx @@ -20,6 +20,10 @@ export const GlobalStyle = createGlobalStyle<{ theme: ThemeType }>` --color-primary: ${props => props.theme.colorPrimary}; --color-grey: ${props => props.theme.colorGrey}; --color-lightGrey: ${props => props.theme.colorLightGrey}; - --link-color: ${props => props.theme.linkColor} + --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}; } ` diff --git a/alm/src/utils/contract.ts b/alm/src/utils/contract.ts new file mode 100644 index 000000000..278713d4c --- /dev/null +++ b/alm/src/utils/contract.ts @@ -0,0 +1,20 @@ +import { Contract } from 'web3-eth-contract' + +export const getRequiredBlockConfirmations = async (contract: Contract) => { + const blockConfirmations = await contract.methods.requiredBlockConfirmations().call() + return parseInt(blockConfirmations) +} + +export const getValidatorAddress = (contract: Contract) => contract.methods.validatorContract().call() + +export const getRequiredSignatures = async (contract: Contract) => { + const requiredSignatures = await contract.methods.requiredSignatures().call() + return parseInt(requiredSignatures) +} + +export const getValidatorList = (contract: Contract) => contract.methods.validatorList().call() + +export const getMessagesSigned = (contract: Contract, hash: string) => contract.methods.messagesSigned(hash).call() + +export const getAffirmationsSigned = (contract: Contract, hash: string) => + contract.methods.affirmationsSigned(hash).call() diff --git a/alm/src/utils/executionWaitingForBlocks.ts b/alm/src/utils/executionWaitingForBlocks.ts new file mode 100644 index 000000000..612284913 --- /dev/null +++ b/alm/src/utils/executionWaitingForBlocks.ts @@ -0,0 +1,51 @@ +import { BlockNumberProvider } from '../services/BlockNumberProvider' +import { VALIDATOR_CONFIRMATION_STATUS } from '../config/constants' +import { EventData } from 'web3-eth-contract' + +export const checkWaitingBlocksForExecution = async ( + blockProvider: BlockNumberProvider, + interval: number, + targetBlock: number, + collectedSignaturesEvent: EventData, + setWaitingBlocksForExecution: Function, + setWaitingBlocksForExecutionResolved: Function, + setExecutionData: Function, + subscriptions: number[] +) => { + const currentBlock = blockProvider.get() + + if (currentBlock && currentBlock >= targetBlock) { + setWaitingBlocksForExecution(false) + setWaitingBlocksForExecutionResolved(true) + blockProvider.stop() + } else { + let nextInterval = interval + if (!currentBlock) { + nextInterval = 500 + } else { + setWaitingBlocksForExecution(true) + setExecutionData({ + status: VALIDATOR_CONFIRMATION_STATUS.WAITING, + validator: collectedSignaturesEvent.returnValues.authorityResponsibleForRelay, + txHash: '', + timestamp: 0, + executionResult: false + }) + } + const timeoutId = setTimeout( + () => + checkWaitingBlocksForExecution( + blockProvider, + interval, + targetBlock, + collectedSignaturesEvent, + setWaitingBlocksForExecution, + setWaitingBlocksForExecutionResolved, + setExecutionData, + subscriptions + ), + nextInterval + ) + subscriptions.push(timeoutId) + } +} diff --git a/alm/src/utils/getCollectedSignaturesEvent.ts b/alm/src/utils/getCollectedSignaturesEvent.ts new file mode 100644 index 000000000..312c1a5f8 --- /dev/null +++ b/alm/src/utils/getCollectedSignaturesEvent.ts @@ -0,0 +1,53 @@ +import Web3 from 'web3' +import { Contract, EventData } from 'web3-eth-contract' +import { homeBlockNumberProvider } from '../services/BlockNumberProvider' +import { BLOCK_RANGE } from '../config/constants' + +export const getCollectedSignaturesEvent = async ( + web3: Maybe, + contract: Maybe, + fromBlock: number, + toBlock: number, + messageHash: string, + setCollectedSignaturesEvent: Function, + subscriptions: number[] +) => { + if (!web3 || !contract) return + const currentBlock = homeBlockNumberProvider.get() + + let events: EventData[] = [] + let securedToBlock = toBlock + if (currentBlock) { + // prevent errors if the toBlock parameter is bigger than the latest + securedToBlock = toBlock >= currentBlock ? currentBlock : toBlock + events = await contract.getPastEvents('CollectedSignatures', { + fromBlock, + toBlock: securedToBlock + }) + } + + const filteredEvents = events.filter(e => e.returnValues.messageHash === messageHash) + + if (filteredEvents.length) { + const event = filteredEvents[0] + setCollectedSignaturesEvent(event) + homeBlockNumberProvider.stop() + } else { + const newFromBlock = currentBlock ? securedToBlock : fromBlock + const newToBlock = currentBlock ? toBlock + BLOCK_RANGE : toBlock + const timeoutId = setTimeout( + () => + getCollectedSignaturesEvent( + web3, + contract, + newFromBlock, + newToBlock, + messageHash, + setCollectedSignaturesEvent, + subscriptions + ), + 500 + ) + subscriptions.push(timeoutId) + } +} diff --git a/alm/src/utils/getConfirmationsForTx.ts b/alm/src/utils/getConfirmationsForTx.ts new file mode 100644 index 000000000..e5b29097e --- /dev/null +++ b/alm/src/utils/getConfirmationsForTx.ts @@ -0,0 +1,82 @@ +import Web3 from 'web3' +import { Contract } from 'web3-eth-contract' +import validatorsCache from '../services/ValidatorsCache' +import { HOME_RPC_POLLING_INTERVAL, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants' + +export const getConfirmationsForTx = async ( + messageData: string, + web3: Maybe, + validatorList: string[], + bridgeContract: Maybe, + confirmationContractMethod: Function, + setResult: Function, + requiredSignatures: number, + setSignatureCollected: Function, + waitingBlocksResolved: boolean, + subscriptions: number[] +) => { + if (!web3 || !validatorList || !bridgeContract || !waitingBlocksResolved) return + const hashMsg = web3.utils.soliditySha3Raw(messageData) + let validatorConfirmations = await Promise.all( + validatorList.map(async validator => { + const hashSenderMsg = web3.utils.soliditySha3Raw(validator, hashMsg) + + const signatureFromCache = validatorsCache.get(hashSenderMsg) + if (signatureFromCache) { + return { + validator, + status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS + } + } + + const confirmed = await confirmationContractMethod(bridgeContract, hashSenderMsg) + const status = confirmed ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED + + // If validator confirmed signature, we cache the result to avoid doing future requests for a result that won't change + if (confirmed) { + validatorsCache.set(hashSenderMsg, confirmed) + } + + return { + validator, + status + } + }) + ) + + const successConfirmations = validatorConfirmations.filter(c => c.status === VALIDATOR_CONFIRMATION_STATUS.SUCCESS) + + // If signatures not collected, it needs to retry in the next blocks + if (successConfirmations.length !== requiredSignatures) { + const timeoutId = setTimeout( + () => + getConfirmationsForTx( + messageData, + web3, + validatorList, + bridgeContract, + confirmationContractMethod, + setResult, + requiredSignatures, + setSignatureCollected, + waitingBlocksResolved, + subscriptions + ), + HOME_RPC_POLLING_INTERVAL + ) + subscriptions.push(timeoutId) + } else { + // If signatures collected, it should set other signatures as not required + const notSuccessConfirmations = validatorConfirmations.filter( + c => c.status !== VALIDATOR_CONFIRMATION_STATUS.SUCCESS + ) + const notRequiredConfirmations = notSuccessConfirmations.map(c => ({ + validator: c.validator, + status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED + })) + + validatorConfirmations = [...successConfirmations, ...notRequiredConfirmations] + setSignatureCollected(true) + } + setResult(validatorConfirmations) +} diff --git a/alm/src/utils/getFinalizationEvent.ts b/alm/src/utils/getFinalizationEvent.ts new file mode 100644 index 000000000..a391ebb5f --- /dev/null +++ b/alm/src/utils/getFinalizationEvent.ts @@ -0,0 +1,60 @@ +import { Contract, EventData } from 'web3-eth-contract' +import Web3 from 'web3' +import { VALIDATOR_CONFIRMATION_STATUS } from '../config/constants' +import { ExecutionData } from '../hooks/useMessageConfirmations' + +export const getFinalizationEvent = async ( + contract: Maybe, + eventName: string, + web3: Maybe, + setResult: React.Dispatch>, + waitingBlocksResolved: boolean, + messageId: string, + interval: number, + subscriptions: number[] +) => { + 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 + } + }) + if (events.length > 0) { + const event = events[0] + const [txReceipt, block] = await Promise.all([ + web3.eth.getTransactionReceipt(event.transactionHash), + web3.eth.getBlock(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 { + const timeoutId = setTimeout( + () => + getFinalizationEvent( + contract, + eventName, + web3, + setResult, + waitingBlocksResolved, + messageId, + interval, + subscriptions + ), + interval + ) + subscriptions.push(timeoutId) + } +} diff --git a/alm/src/utils/networks.ts b/alm/src/utils/networks.ts index b74fdda51..2b58df767 100644 --- a/alm/src/utils/networks.ts +++ b/alm/src/utils/networks.ts @@ -1,5 +1,5 @@ import { formatDistance } from 'date-fns' -import { TRANSACTION_STATUS_DESCRIPTION } from '../config/descriptions' +import { CONFIRMATIONS_STATUS_DESCRIPTION, 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) @@ -30,3 +30,12 @@ export const getTransactionStatusDescription = (status: string, timestamp: Maybe return description } + +export const getConfirmationsStatusDescription = (status: string, home: string, foreign: string) => { + let description = CONFIRMATIONS_STATUS_DESCRIPTION[status] + + description = description.replace('%homeChain', home) + description = description.replace('%foreignChain', foreign) + + return description +} diff --git a/alm/src/utils/signatureWaitingForBlocks.ts b/alm/src/utils/signatureWaitingForBlocks.ts new file mode 100644 index 000000000..74f768ff3 --- /dev/null +++ b/alm/src/utils/signatureWaitingForBlocks.ts @@ -0,0 +1,50 @@ +import { VALIDATOR_CONFIRMATION_STATUS } from '../config/constants' +import { BlockNumberProvider } from '../services/BlockNumberProvider' + +export const checkSignaturesWaitingForBLocks = async ( + targetBlock: number, + setWaitingStatus: Function, + setWaitingBlocksResolved: Function, + validatorList: string[], + setConfirmations: Function, + blockProvider: BlockNumberProvider, + interval: number, + subscriptions: number[] +) => { + const currentBlock = blockProvider.get() + + if (currentBlock && currentBlock >= targetBlock) { + setWaitingStatus(false) + setWaitingBlocksResolved(true) + blockProvider.stop() + } else { + let nextInterval = interval + if (!currentBlock) { + nextInterval = 500 + } else { + const validatorsWaiting = validatorList.map(validator => { + return { + validator, + status: VALIDATOR_CONFIRMATION_STATUS.WAITING + } + }) + setWaitingStatus(true) + setConfirmations(validatorsWaiting) + } + const timeoutId = setTimeout( + () => + checkSignaturesWaitingForBLocks( + targetBlock, + setWaitingStatus, + setWaitingBlocksResolved, + validatorList, + setConfirmations, + blockProvider, + interval, + subscriptions + ), + nextInterval + ) + subscriptions.push(timeoutId) + } +} diff --git a/alm/src/utils/web3.ts b/alm/src/utils/web3.ts index ad9bdabd2..d35dd3648 100644 --- a/alm/src/utils/web3.ts +++ b/alm/src/utils/web3.ts @@ -4,6 +4,11 @@ import { AbiItem } from 'web3-utils' import memoize from 'fast-memoize' import { HOME_AMB_ABI, FOREIGN_AMB_ABI } from '../../../commons' +export interface MessageObject { + id: string + data: string +} + const rawGetWeb3 = (url: string) => new Web3(new Web3.providers.HttpProvider(url)) const memoized = memoize(rawGetWeb3) @@ -14,11 +19,20 @@ export const filterEventsByAbi = ( 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) - return events.map(e => e.topics[1]) + return events.map(e => { + let decodedLogs: { [p: string]: string } = { + messageId: '', + encodedData: '' + } + if (eventAbi && eventAbi.inputs && eventAbi.inputs.length) { + decodedLogs = web3.eth.abi.decodeLog(eventAbi.inputs, e.data, [e.topics[1]]) + } + return { id: decodedLogs.messageId, data: decodedLogs.encodedData } + }) } export const getHomeMessagesFromReceipt = (txReceipt: TransactionReceipt, web3: Web3, bridgeAddress: string) => { diff --git a/yarn.lock b/yarn.lock index 21d23b325..b90bd96b8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1304,21 +1304,6 @@ "@ethersproject/properties" ">=5.0.0-beta.131" "@ethersproject/strings" ">=5.0.0-beta.130" -"@ethersproject/abi@5.0.0-beta.153": - version "5.0.0-beta.153" - resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.0.0-beta.153.tgz#43a37172b33794e4562999f6e2d555b7599a8eee" - integrity sha512-aXweZ1Z7vMNzJdLpR1CZUAIgnwjrZeUSvN9syCwlBaEBUFJmFY+HHnfuTI5vIhVs/mRkfJVrbEyl51JZQqyjAg== - dependencies: - "@ethersproject/address" ">=5.0.0-beta.128" - "@ethersproject/bignumber" ">=5.0.0-beta.130" - "@ethersproject/bytes" ">=5.0.0-beta.129" - "@ethersproject/constants" ">=5.0.0-beta.128" - "@ethersproject/hash" ">=5.0.0-beta.128" - "@ethersproject/keccak256" ">=5.0.0-beta.127" - "@ethersproject/logger" ">=5.0.0-beta.129" - "@ethersproject/properties" ">=5.0.0-beta.131" - "@ethersproject/strings" ">=5.0.0-beta.130" - "@ethersproject/address@>=5.0.0-beta.128": version "5.0.0-beta.134" resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.0.0-beta.134.tgz#9c1790c87b763dc547ac12e2dbc9fa78d0799a71" @@ -4868,7 +4853,7 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= -base-x@^3.0.2, base-x@^3.0.8: +base-x@^3.0.2: version "3.0.8" resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.8.tgz#1e1106c2537f0162e8b52474a557ebb09000018d" integrity sha512-Rl/1AWP4J/zRrk54hhlxH4drNxPJXYUaKffODVI53/dAsV4t9fBxyxYKAVPU1XBHxYwOWP9h9H0hM2MVw4YfJA== @@ -5804,17 +5789,6 @@ ci-info@^2.0.0: resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== -cids@^0.7.1: - version "0.7.5" - resolved "https://registry.yarnpkg.com/cids/-/cids-0.7.5.tgz#60a08138a99bfb69b6be4ceb63bfef7a396b28b2" - integrity sha512-zT7mPeghoWAu+ppn8+BS1tQ5qGmbMfB4AregnQjA/qHY3GC1m1ptI9GkWNlgeu38r7CuRdXB47uY2XgAYt6QVA== - dependencies: - buffer "^5.5.0" - class-is "^1.1.0" - multibase "~0.6.0" - multicodec "^1.0.0" - multihashes "~0.4.15" - cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" @@ -5823,11 +5797,6 @@ cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: inherits "^2.0.1" safe-buffer "^5.0.1" -class-is@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/class-is/-/class-is-1.1.0.tgz#9d3c0fba0440d211d843cec3dedfa48055005825" - integrity sha512-rhjH9AG1fvabIDoGRVH587413LPjTZgmDF9fOFCbFJQV4yuocX1mHxxvXI4g3cGwbVY9wAYIoKlg1N79frJKQw== - class-utils@^0.3.5: version "0.3.6" resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" @@ -6293,15 +6262,6 @@ content-disposition@0.5.3: dependencies: safe-buffer "5.1.2" -content-hash@^2.5.2: - version "2.5.2" - resolved "https://registry.yarnpkg.com/content-hash/-/content-hash-2.5.2.tgz#bbc2655e7c21f14fd3bfc7b7d4bfe6e454c9e211" - integrity sha512-FvIQKy0S1JaWV10sMsA7TRx8bpU+pqPkhbsfvOJAdjRXvYxEckAwQWGwtRjiaJfh+E0DvcWUGqcdjwMGFjsSdw== - dependencies: - cids "^0.7.1" - multicodec "^0.5.5" - multihashes "^0.4.15" - content-type@~1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" @@ -13821,22 +13781,6 @@ ms@^2.0.0: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -multibase@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/multibase/-/multibase-0.7.0.tgz#1adfc1c50abe05eefeb5091ac0c2728d6b84581b" - integrity sha512-TW8q03O0f6PNFTQDvh3xxH03c8CjGaaYrjkl9UQPG6rz53TQzzxJVCIWVjzcbN/Q5Y53Zd0IBQBMVktVgNx4Fg== - dependencies: - base-x "^3.0.8" - buffer "^5.5.0" - -multibase@~0.6.0: - version "0.6.1" - resolved "https://registry.yarnpkg.com/multibase/-/multibase-0.6.1.tgz#b76df6298536cc17b9f6a6db53ec88f85f8cc12b" - integrity sha512-pFfAwyTjbbQgNc3G7D48JkJxWtoJoBMaR4xQUOuB8RnCgRqaYmWNFeJTTvrJ2w51bjLq2zTby6Rqj9TQ9elSUw== - dependencies: - base-x "^3.0.8" - buffer "^5.5.0" - multicast-dns-service-types@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz#899f11d9686e5e05cb91b35d5f0e63b773cfc901" @@ -13850,30 +13794,6 @@ multicast-dns@^6.0.1: dns-packet "^1.3.1" thunky "^1.0.2" -multicodec@^0.5.5: - version "0.5.7" - resolved "https://registry.yarnpkg.com/multicodec/-/multicodec-0.5.7.tgz#1fb3f9dd866a10a55d226e194abba2dcc1ee9ffd" - integrity sha512-PscoRxm3f+88fAtELwUnZxGDkduE2HD9Q6GHUOywQLjOGT/HAdhjLDYNZ1e7VR0s0TP0EwZ16LNUTFpoBGivOA== - dependencies: - varint "^5.0.0" - -multicodec@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/multicodec/-/multicodec-1.0.1.tgz#4e2812d726b9f7c7d615d3ebc5787d36a08680f9" - integrity sha512-yrrU/K8zHyAH2B0slNVeq3AiwluflHpgQ3TAzwNJcuO2AoPyXgBT2EDkdbP1D8B/yFOY+S2hDYmFlI1vhVFkQw== - dependencies: - buffer "^5.5.0" - varint "^5.0.0" - -multihashes@^0.4.15, multihashes@~0.4.15: - version "0.4.19" - resolved "https://registry.yarnpkg.com/multihashes/-/multihashes-0.4.19.tgz#d7493cf028e48747122f350908ea13d12d204813" - integrity sha512-ej74GAfA20imjj00RO5h34aY3pGUFyzn9FJZFWwdeUHlHTkKmv90FrNpvYT4jYf1XXCy5O/5EjVnxTaESgOM6A== - dependencies: - buffer "^5.5.0" - multibase "^0.7.0" - varint "^5.0.0" - multimatch@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/multimatch/-/multimatch-3.0.0.tgz#0e2534cc6bc238d9ab67e1b9cd5fcd85a6dbf70b" @@ -20113,11 +20033,6 @@ value-or-function@^3.0.0: resolved "https://registry.yarnpkg.com/value-or-function/-/value-or-function-3.0.0.tgz#1c243a50b595c1be54a754bfece8563b9ff8d813" integrity sha1-HCQ6ULWVwb5Up1S/7OhWO5/42BM= -varint@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/varint/-/varint-5.0.0.tgz#d826b89f7490732fabc0c0ed693ed475dcb29ebf" - integrity sha1-2Ca4n3SQcy+rwMDtaT7Uddyynr8= - vary@^1, vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" @@ -20324,10 +20239,10 @@ web3-bzz@1.2.4: swarm-js "0.1.39" underscore "1.9.1" -web3-bzz@1.2.8: - version "1.2.8" - resolved "https://registry.yarnpkg.com/web3-bzz/-/web3-bzz-1.2.8.tgz#7ff2c2de362f82ae3825e48c70ec63b3aca2b8ef" - integrity sha512-jbi24/s2tT7FYuN+ktRvTa5em0GxhqcIYQ8FnD3Zb/ZoEPi+/7rH0Hh+WDol0pSZL+wdz/iM+Z2C9NE42z6EmQ== +web3-bzz@1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/web3-bzz/-/web3-bzz-1.2.7.tgz#aa0f3d162f0777a5f35367dc5b70012dd1e129d0" + integrity sha512-iTIWBR+Z+Bn09WprtKm46LmyNOasg2lUn++AjXkBTB8UNxlUybxtza84yl2ETTZUs0zuFzdSSAEgbjhygG+9oA== dependencies: "@types/node" "^10.12.18" got "9.6.0" @@ -20361,14 +20276,14 @@ web3-core-helpers@1.2.4: web3-eth-iban "1.2.4" web3-utils "1.2.4" -web3-core-helpers@1.2.8: - version "1.2.8" - resolved "https://registry.yarnpkg.com/web3-core-helpers/-/web3-core-helpers-1.2.8.tgz#86776d8f658b63bb630c84a314686661e599aa68" - integrity sha512-Wrl7ZPKn3Xyg0Hl5+shDnJcLP+EtTfThmQ1eCJLcg/BZqvLUR1SkOslNlhEojcYeBwhhymAKs8dfQbtYi+HMnw== +web3-core-helpers@1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/web3-core-helpers/-/web3-core-helpers-1.2.7.tgz#522f859775ea0d15e7e40359c46d4efc5da92aee" + integrity sha512-bdU++9QATGeCetVrMp8pV97aQtVkN5oLBf/TWu/qumC6jK/YqrvLlBJLdwbz0QveU8zOSap6GCvJbqKvmmbV2A== dependencies: underscore "1.9.1" - web3-eth-iban "1.2.8" - web3-utils "1.2.8" + web3-eth-iban "1.2.7" + web3-utils "1.2.7" web3-core-method@1.0.0-beta.30: version "1.0.0-beta.30" @@ -20403,16 +20318,16 @@ web3-core-method@1.2.4: web3-core-subscriptions "1.2.4" web3-utils "1.2.4" -web3-core-method@1.2.8: - version "1.2.8" - resolved "https://registry.yarnpkg.com/web3-core-method/-/web3-core-method-1.2.8.tgz#f28a79935432aebfa019e4a50f9b6ae6c9ef4297" - integrity sha512-69qbvOgx0Frw46dXvEKzYgtaPXpUaQAlQmczgb0ZUBHsEU2K7jTtFgBy6kVBgAwsXDvoZ99AX4SjpY2dTMwPkw== +web3-core-method@1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/web3-core-method/-/web3-core-method-1.2.7.tgz#73fd80d2bf0765ff6efc454db49ac83d1769a45e" + integrity sha512-e1TI0QUnByDMbQ8QHwnjxfjKw0LIgVRY4TYrlPijET9ebqUJU1HCayn/BHIMpV6LKyR1fQj9EldWyT64wZQXkg== dependencies: underscore "1.9.1" - web3-core-helpers "1.2.8" - web3-core-promievent "1.2.8" - web3-core-subscriptions "1.2.8" - web3-utils "1.2.8" + web3-core-helpers "1.2.7" + web3-core-promievent "1.2.7" + web3-core-subscriptions "1.2.7" + web3-utils "1.2.7" web3-core-promievent@1.0.0-beta.30: version "1.0.0-beta.30" @@ -20438,10 +20353,10 @@ web3-core-promievent@1.2.4: any-promise "1.3.0" eventemitter3 "3.1.2" -web3-core-promievent@1.2.8: - version "1.2.8" - resolved "https://registry.yarnpkg.com/web3-core-promievent/-/web3-core-promievent-1.2.8.tgz#a93ca2a19cae8b60883412619e04e69e11804eb5" - integrity sha512-3EdRieaHpBVVhfGjoREQfdoCM3xC0WwWjXXzT6oTldotfYC38kwk/GW8c8txYiLP/KxhslAN1cJSlXNOJjKSog== +web3-core-promievent@1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/web3-core-promievent/-/web3-core-promievent-1.2.7.tgz#fc7fa489f4cf76a040800f3dfd4b45c51bd3a39f" + integrity sha512-jNmsM/czCeMGQqKKwM9/HZVTJVIF96hdMVNN/V9TGvp+EEE7vDhB4pUocDnc/QF9Z/5QFBCVmvNWttlRgZmU0A== dependencies: eventemitter3 "3.1.2" @@ -20478,16 +20393,16 @@ web3-core-requestmanager@1.2.4: web3-providers-ipc "1.2.4" web3-providers-ws "1.2.4" -web3-core-requestmanager@1.2.8: - version "1.2.8" - resolved "https://registry.yarnpkg.com/web3-core-requestmanager/-/web3-core-requestmanager-1.2.8.tgz#da7259e72a433858d04c59b999c5116bfb797c09" - integrity sha512-bwc2ABG6yzgTy28fv4t59g+tf6+UmTRMoF8HqTeiNDffoMKP2akyKFZeu1oD2gE7j/7GA75TAUjwJ7pH9ek1MA== +web3-core-requestmanager@1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/web3-core-requestmanager/-/web3-core-requestmanager-1.2.7.tgz#9da0efce898ead7004d4ac50f748f5131cfe4d79" + integrity sha512-HJb/txjHixu1dxIebiZQKBoJCaNu4gsh7mq/uj6Z/w6tIHbybL90s/7ADyMED353yyJ2tDWtYJqeMVAR+KtdaA== dependencies: underscore "1.9.1" - web3-core-helpers "1.2.8" - web3-providers-http "1.2.8" - web3-providers-ipc "1.2.8" - web3-providers-ws "1.2.8" + web3-core-helpers "1.2.7" + web3-providers-http "1.2.7" + web3-providers-ipc "1.2.7" + web3-providers-ws "1.2.7" web3-core-subscriptions@1.0.0-beta.30: version "1.0.0-beta.30" @@ -20516,14 +20431,14 @@ web3-core-subscriptions@1.2.4: underscore "1.9.1" web3-core-helpers "1.2.4" -web3-core-subscriptions@1.2.8: - version "1.2.8" - resolved "https://registry.yarnpkg.com/web3-core-subscriptions/-/web3-core-subscriptions-1.2.8.tgz#50945498fb0bd655f842cbcc13873d96956aa93e" - integrity sha512-wmsRJ4ipwoF1mlOR+Oo8JdKigpkoVNQtqiRUuyQrTVhJx7GBuSaAIenpBYlkucC+RgByoGybR7Q3tTNJ6z/2tQ== +web3-core-subscriptions@1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/web3-core-subscriptions/-/web3-core-subscriptions-1.2.7.tgz#30c64aede03182832883b17c77e21cbb0933c86e" + integrity sha512-W/CzQYOUawdMDvkgA/fmLsnG5aMpbjrs78LZMbc0MFXLpH3ofqAgO2by4QZrrTShUUTeWS0ZuEkFFL/iFrSObw== dependencies: eventemitter3 "3.1.2" underscore "1.9.1" - web3-core-helpers "1.2.8" + web3-core-helpers "1.2.7" web3-core@1.0.0-beta.30: version "1.0.0-beta.30" @@ -20558,18 +20473,18 @@ web3-core@1.2.4: web3-core-requestmanager "1.2.4" web3-utils "1.2.4" -web3-core@1.2.8: - version "1.2.8" - resolved "https://registry.yarnpkg.com/web3-core/-/web3-core-1.2.8.tgz#2a488bb11519b71e7738265329bddc00fc200dd3" - integrity sha512-hvlYWyE1UcLoGa6qF1GoxGgi1quFsZOdwIUIVsAp+sp0plXp/Nqva2lAjJ+FFyWtVKbC7Zq+qwTJ4iP1aN0vTg== +web3-core@1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/web3-core/-/web3-core-1.2.7.tgz#9248b04331e458c76263d758c51b0cc612953900" + integrity sha512-QA0MTae0gXcr3KHe3cQ4x56+Wh43ZKWfMwg1gfCc3NNxPRM1jJ8qudzyptCAUcxUGXWpDG8syLIn1APDz5J8BQ== dependencies: "@types/bn.js" "^4.11.4" "@types/node" "^12.6.1" bignumber.js "^9.0.0" - web3-core-helpers "1.2.8" - web3-core-method "1.2.8" - web3-core-requestmanager "1.2.8" - web3-utils "1.2.8" + web3-core-helpers "1.2.7" + web3-core-method "1.2.7" + web3-core-requestmanager "1.2.7" + web3-utils "1.2.7" web3-eth-abi@1.0.0-beta.30: version "1.0.0-beta.30" @@ -20600,14 +20515,14 @@ web3-eth-abi@1.2.4: underscore "1.9.1" web3-utils "1.2.4" -web3-eth-abi@1.2.8: - version "1.2.8" - resolved "https://registry.yarnpkg.com/web3-eth-abi/-/web3-eth-abi-1.2.8.tgz#7537138f3e5cd1ccf98233fa07f388aa8dc1fff1" - integrity sha512-OKp/maLdKHPpQxZhEd0HgnCJFQajsGe42WOG6SVftlgzyR8Jjv4KNm46TKvb3hv5OJTKZWU7nZIxkEG+fyI58w== +web3-eth-abi@1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/web3-eth-abi/-/web3-eth-abi-1.2.7.tgz#6f3471b578649fddd844a14d397a3dd430fc44a5" + integrity sha512-4FnlT1q+D0XBkxSMXlIb/eG337uQeMaUdtVQ4PZ3XzxqpcoDuMgXm4o+3NRxnWmr4AMm6QKjM+hcC7c0mBKcyg== dependencies: - "@ethersproject/abi" "5.0.0-beta.153" + ethers "4.0.0-beta.3" underscore "1.9.1" - web3-utils "1.2.8" + web3-utils "1.2.7" web3-eth-abi@^1.0.0-beta.24: version "1.2.6" @@ -20668,10 +20583,10 @@ web3-eth-accounts@1.2.4: web3-core-method "1.2.4" web3-utils "1.2.4" -web3-eth-accounts@1.2.8: - version "1.2.8" - resolved "https://registry.yarnpkg.com/web3-eth-accounts/-/web3-eth-accounts-1.2.8.tgz#e63afc6d4902f2beb0cf60e6b755c86fa5b5ccd7" - integrity sha512-QODqSD4SZN/1oWfvlvsuum6Ud9+2FUTW4VTPJh245YTewCpa0M5+Fzug3UTeUZNv88STwC//dV72zReITNh4ZQ== +web3-eth-accounts@1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/web3-eth-accounts/-/web3-eth-accounts-1.2.7.tgz#087f55d04a01b815b93151aac2fc1677436b9c59" + integrity sha512-AE7QWi/iIQIjXwlAPtlMabm/OPFF0a1PhxT1EiTckpYNP8fYs6jW7lYxEtJPPJIKqfMjoi1xkEqTVR1YZQ88lg== dependencies: "@web3-js/scrypt-shim" "^0.1.0" crypto-browserify "3.12.0" @@ -20680,10 +20595,10 @@ web3-eth-accounts@1.2.8: ethereumjs-tx "^2.1.1" underscore "1.9.1" uuid "3.3.2" - web3-core "1.2.8" - web3-core-helpers "1.2.8" - web3-core-method "1.2.8" - web3-utils "1.2.8" + web3-core "1.2.7" + web3-core-helpers "1.2.7" + web3-core-method "1.2.7" + web3-utils "1.2.7" web3-eth-contract@1.0.0-beta.30: version "1.0.0-beta.30" @@ -20728,20 +20643,20 @@ web3-eth-contract@1.2.4: web3-eth-abi "1.2.4" web3-utils "1.2.4" -web3-eth-contract@1.2.8: - version "1.2.8" - resolved "https://registry.yarnpkg.com/web3-eth-contract/-/web3-eth-contract-1.2.8.tgz#ff75920ac698a70781edcebbf75287a6d0f14499" - integrity sha512-EWRLVhZksbzGAyHd7RaOsakjCJBA2BREWiJmBDlrxDBqw8HltXFzKdkRug/mwVNa5ZYMabKSRF/MMh0Sx06CFw== +web3-eth-contract@1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/web3-eth-contract/-/web3-eth-contract-1.2.7.tgz#13d7f6003d6221f9a5fd61c2d3b5d039477c9674" + integrity sha512-uW23Y0iL7XroRNbf9fWZ1N6OYhEYTJX8gTuYASuRnpYrISN5QGiQML6pq/NCzqypR1bl5E0fuINZQSK/xefIVw== dependencies: "@types/bn.js" "^4.11.4" underscore "1.9.1" - web3-core "1.2.8" - web3-core-helpers "1.2.8" - web3-core-method "1.2.8" - web3-core-promievent "1.2.8" - web3-core-subscriptions "1.2.8" - web3-eth-abi "1.2.8" - web3-utils "1.2.8" + web3-core "1.2.7" + web3-core-helpers "1.2.7" + web3-core-method "1.2.7" + web3-core-promievent "1.2.7" + web3-core-subscriptions "1.2.7" + web3-eth-abi "1.2.7" + web3-utils "1.2.7" web3-eth-ens@1.2.4: version "1.2.4" @@ -20757,20 +20672,19 @@ web3-eth-ens@1.2.4: web3-eth-contract "1.2.4" web3-utils "1.2.4" -web3-eth-ens@1.2.8: - version "1.2.8" - resolved "https://registry.yarnpkg.com/web3-eth-ens/-/web3-eth-ens-1.2.8.tgz#247daddfdbf7533adb0f45cd2f75c75e52f7e678" - integrity sha512-zsFXY26BMGkihPkEO5qj9AEqyxPH4mclbzYs1dyrw7sHFmrUvhZc+jLGT9WyQGoujq37RN2l/tlOpCaFVVR8ng== +web3-eth-ens@1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/web3-eth-ens/-/web3-eth-ens-1.2.7.tgz#0bfa7d4b6c7753abbb31a2eb01a364b538f4c860" + integrity sha512-SPRnvUNWQ0CnnTDBteGIJkvFWEizJcAHlVsrFLICwcwFZu+appjX1UOaoGu2h3GXWtc/XZlu7B451Gi+Os2cTg== dependencies: - content-hash "^2.5.2" eth-ens-namehash "2.0.8" underscore "1.9.1" - web3-core "1.2.8" - web3-core-helpers "1.2.8" - web3-core-promievent "1.2.8" - web3-eth-abi "1.2.8" - web3-eth-contract "1.2.8" - web3-utils "1.2.8" + web3-core "1.2.7" + web3-core-helpers "1.2.7" + web3-core-promievent "1.2.7" + web3-eth-abi "1.2.7" + web3-eth-contract "1.2.7" + web3-utils "1.2.7" web3-eth-iban@1.0.0-beta.30: version "1.0.0-beta.30" @@ -20796,13 +20710,13 @@ web3-eth-iban@1.2.4: bn.js "4.11.8" web3-utils "1.2.4" -web3-eth-iban@1.2.8: - version "1.2.8" - resolved "https://registry.yarnpkg.com/web3-eth-iban/-/web3-eth-iban-1.2.8.tgz#414e80a7fb2d1ea16490bc2c8fc29a996aec5612" - integrity sha512-xgPUOuDOQJYloUS334/wot6jvp6K8JBz8UvQ1tAxU9LO2v2DW+IDTJ5gQ6TdutTmzdDi97KdwhwnQwhQh5Z1PA== +web3-eth-iban@1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/web3-eth-iban/-/web3-eth-iban-1.2.7.tgz#832809c28586be3c667a713b77a2bcba11b7970f" + integrity sha512-2NrClz1PoQ3nSJBd+91ylCOVga9qbTxjRofq/oSCoHVAEvz3WZyttx9k5DC+0rWqwJF1h69ufFvdHAAlmN/4lg== dependencies: bn.js "4.11.8" - web3-utils "1.2.8" + web3-utils "1.2.7" web3-eth-personal@1.0.0-beta.30: version "1.0.0-beta.30" @@ -20838,17 +20752,17 @@ web3-eth-personal@1.2.4: web3-net "1.2.4" web3-utils "1.2.4" -web3-eth-personal@1.2.8: - version "1.2.8" - resolved "https://registry.yarnpkg.com/web3-eth-personal/-/web3-eth-personal-1.2.8.tgz#8ebb27210b4c9c9555a30c5bb2ce8db12f84cd24" - integrity sha512-sWhxF1cpF9pB1wMISrOSy/i8IB1NWtvoXT9dfkWtvByGf3JfC2DlnllLaA1f9ohyvxnR+QTgPKgOQDknmqDstw== +web3-eth-personal@1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/web3-eth-personal/-/web3-eth-personal-1.2.7.tgz#322cc2b14c37737b21772a53e4185686a04bf9be" + integrity sha512-2OAa1Spz0uB29dwCM8+1y0So7E47A4gKznjBEwXIYEcUIsvwT5X7ofFhC2XxyRpqlIWZSQAxRSSJFyupRRXzyw== dependencies: "@types/node" "^12.6.1" - web3-core "1.2.8" - web3-core-helpers "1.2.8" - web3-core-method "1.2.8" - web3-net "1.2.8" - web3-utils "1.2.8" + web3-core "1.2.7" + web3-core-helpers "1.2.7" + web3-core-method "1.2.7" + web3-net "1.2.7" + web3-utils "1.2.7" web3-eth@1.0.0-beta.30: version "1.0.0-beta.30" @@ -20905,24 +20819,24 @@ web3-eth@1.2.4: web3-net "1.2.4" web3-utils "1.2.4" -web3-eth@1.2.8: - version "1.2.8" - resolved "https://registry.yarnpkg.com/web3-eth/-/web3-eth-1.2.8.tgz#cf6a16fae4d7c12b90cfb6ef570cb1a2acc34c1b" - integrity sha512-CEnVIIR1zZQ9vQh/kPFAUbvbbHYkC84y15jdhRUDDGR6bs4FxO2NNWR2YDtNe038lrz747tZahsC9kEiGkJFZQ== +web3-eth@1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/web3-eth/-/web3-eth-1.2.7.tgz#9427daefd3641200679c2946f77fc184dbfb5b4c" + integrity sha512-ljLd0oB4IjWkzFGVan4HkYhJXhSXgn9iaSaxdJixKGntZPgWMJfxeA+uLwTrlxrWzhvy4f+39WnT7wCh5e9TGg== dependencies: underscore "1.9.1" - web3-core "1.2.8" - web3-core-helpers "1.2.8" - web3-core-method "1.2.8" - web3-core-subscriptions "1.2.8" - web3-eth-abi "1.2.8" - web3-eth-accounts "1.2.8" - web3-eth-contract "1.2.8" - web3-eth-ens "1.2.8" - web3-eth-iban "1.2.8" - web3-eth-personal "1.2.8" - web3-net "1.2.8" - web3-utils "1.2.8" + web3-core "1.2.7" + web3-core-helpers "1.2.7" + web3-core-method "1.2.7" + web3-core-subscriptions "1.2.7" + web3-eth-abi "1.2.7" + web3-eth-accounts "1.2.7" + web3-eth-contract "1.2.7" + web3-eth-ens "1.2.7" + web3-eth-iban "1.2.7" + web3-eth-personal "1.2.7" + web3-net "1.2.7" + web3-utils "1.2.7" web3-net@1.0.0-beta.30: version "1.0.0-beta.30" @@ -20951,14 +20865,14 @@ web3-net@1.2.4: web3-core-method "1.2.4" web3-utils "1.2.4" -web3-net@1.2.8: - version "1.2.8" - resolved "https://registry.yarnpkg.com/web3-net/-/web3-net-1.2.8.tgz#582fc2d4ba32c2e5c7761624e4be7c5434142d66" - integrity sha512-Nsq6qgncvvThOjC+sQ+NfDH8L7jclQCFzLFYa9wsd5J6HJ6f5gJl/mv6rsZQX9iDEYDPKkDfyqHktynOBgKWMQ== +web3-net@1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/web3-net/-/web3-net-1.2.7.tgz#c355621a8769c9c1a967c801e7db90c92a0e3808" + integrity sha512-j9qeZrS1FNyCeA0BfdLojkxOZQz3FKa1DJI+Dw9fEVhZS68vLOFANu2RB96gR9BoPHo5+k5D3NsKOoxt1gw3Gg== dependencies: - web3-core "1.2.8" - web3-core-method "1.2.8" - web3-utils "1.2.8" + web3-core "1.2.7" + web3-core-method "1.2.7" + web3-utils "1.2.7" web3-provider-engine@14.0.6: version "14.0.6" @@ -21037,12 +20951,12 @@ web3-providers-http@1.2.4: web3-core-helpers "1.2.4" xhr2-cookies "1.1.0" -web3-providers-http@1.2.8: - version "1.2.8" - resolved "https://registry.yarnpkg.com/web3-providers-http/-/web3-providers-http-1.2.8.tgz#cd7fc4d49df6980b5dd0fb1b5a808bc4b6a0069d" - integrity sha512-Esj4SpgabmBDOR4QD3qYapzwFYWHigcdgdjvt/VWT5/7TD10o52hr+Nsvp3/XV5AFrcCMdY+lzKFLVH24u0sww== +web3-providers-http@1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/web3-providers-http/-/web3-providers-http-1.2.7.tgz#31eb15390c103169b3d7d31bdb1ccae9e3f1629d" + integrity sha512-vazGx5onuH/zogrwkUaLFJwFcJ6CckP65VFSHoiV+GTQdkOqgoDIha7StKkslvDz4XJ2FuY/zOZHbtuOYeltXQ== dependencies: - web3-core-helpers "1.2.8" + web3-core-helpers "1.2.7" xhr2-cookies "1.1.0" web3-providers-ipc@1.0.0-beta.30: @@ -21072,14 +20986,14 @@ web3-providers-ipc@1.2.4: underscore "1.9.1" web3-core-helpers "1.2.4" -web3-providers-ipc@1.2.8: - version "1.2.8" - resolved "https://registry.yarnpkg.com/web3-providers-ipc/-/web3-providers-ipc-1.2.8.tgz#47be918ddd077999aa14703169b76c807f45d894" - integrity sha512-ts3/UXCTRADPASdJ27vBVmcfM+lfG9QVBxGedY6+oNIo5EPxBUtsz94R32sfvFd6ofPsz6gOhK/M/ZKiJoi1sg== +web3-providers-ipc@1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/web3-providers-ipc/-/web3-providers-ipc-1.2.7.tgz#4e6716e8723d431df3d6bfa1acd2f7c04e7071ad" + integrity sha512-/zc0y724H2zbkV4UbGGMhsEiLfafjagIzfrsWZnyTZUlSB0OGRmmFm2EkLJAgtXrLiodaHHyXKM0vB8S24bxdA== dependencies: oboe "2.1.4" underscore "1.9.1" - web3-core-helpers "1.2.8" + web3-core-helpers "1.2.7" web3-providers-ws@1.0.0-beta.30: version "1.0.0-beta.30" @@ -21108,15 +21022,15 @@ web3-providers-ws@1.2.4: underscore "1.9.1" web3-core-helpers "1.2.4" -web3-providers-ws@1.2.8: - version "1.2.8" - resolved "https://registry.yarnpkg.com/web3-providers-ws/-/web3-providers-ws-1.2.8.tgz#9e6454edc82d753d398c8d1e044632c234434a46" - integrity sha512-Gcm0n82wd/XVeGFGTx+v56UqyrV9EyB2r1QFaBx4mS+VHbW2MCOdiRbNDfoZQslflnCWl8oHsivJ8Tya9kqlTQ== +web3-providers-ws@1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/web3-providers-ws/-/web3-providers-ws-1.2.7.tgz#95b1cc5dc25e9b9d6630d6754f9354313b62f532" + integrity sha512-b5XzqDpRkNVe6MFs5K6iqOEyjQikHtg3KuU2/ClCDV37hm0WN4xCRVMC0LwegulbDXZej3zT9+1CYzGaGFREzA== dependencies: "@web3-js/websocket" "^1.0.29" eventemitter3 "^4.0.0" underscore "1.9.1" - web3-core-helpers "1.2.8" + web3-core-helpers "1.2.7" web3-shh@1.0.0-beta.30: version "1.0.0-beta.30" @@ -21148,15 +21062,15 @@ web3-shh@1.2.4: web3-core-subscriptions "1.2.4" web3-net "1.2.4" -web3-shh@1.2.8: - version "1.2.8" - resolved "https://registry.yarnpkg.com/web3-shh/-/web3-shh-1.2.8.tgz#5162d9d13bc6838d390df1cd39e5f87235c1c2ae" - integrity sha512-e29qKSfuZWDmxCG/uB48Nth6DCFFr2h2U+uI/fHEuhEjAEkBHopPNLc3ixrCTc6pqMocfJRPHJq/yET9PYN3oQ== +web3-shh@1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/web3-shh/-/web3-shh-1.2.7.tgz#5382c7bc2f39539eb2841c4576d23ade25720461" + integrity sha512-f6PAgcpG0ZAo98KqCmeHoDEx5qzm3d5plet18DkT4U6WIeYowKdec8vZaLPRR7c2XreXFJ2gQf45CB7oqR7U/w== dependencies: - web3-core "1.2.8" - web3-core-method "1.2.8" - web3-core-subscriptions "1.2.8" - web3-net "1.2.8" + web3-core "1.2.7" + web3-core-method "1.2.7" + web3-core-subscriptions "1.2.7" + web3-net "1.2.7" web3-utils@1.0.0-beta.30: version "1.0.0-beta.30" @@ -21212,10 +21126,10 @@ web3-utils@1.2.6: underscore "1.9.1" utf8 "3.0.0" -web3-utils@1.2.8: - version "1.2.8" - resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-1.2.8.tgz#5321d91715cd4c0869005705a33c4c042a532b18" - integrity sha512-9SIVGFLajwlmo5joC4DGxuy2OeDkRCXVWT8JWcDQ+BayNVHyAWGvn0oGkQ0ys14Un0KK6bjjKoD0xYs4k+FaVw== +web3-utils@1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-1.2.7.tgz#b68e232917e4376f81cf38ef79878e5903d18e93" + integrity sha512-FBh/CPJND+eiPeUF9KVbTyTZtXNWxPWtByBaWS6e2x4ACazPX711EeNaZaChIOGSLGe6se2n7kg6wnawe/MjuQ== dependencies: bn.js "4.11.8" eth-lib "0.2.7" @@ -21266,18 +21180,18 @@ web3@1.2.4: web3-shh "1.2.4" web3-utils "1.2.4" -web3@^1.2.8: - version "1.2.8" - resolved "https://registry.yarnpkg.com/web3/-/web3-1.2.8.tgz#20b24baa769e0224a708ef5bf196a5b83d19540b" - integrity sha512-rXUn16VKxn2aIe9v0KX+bSm2JXdq/Vnj3lZ0Rub2Q5YUSycHdCBaDtJRukl/jB5ygAdyr5/cUwvJzhNDJSYsGw== - dependencies: - web3-bzz "1.2.8" - web3-core "1.2.8" - web3-eth "1.2.8" - web3-eth-personal "1.2.8" - web3-net "1.2.8" - web3-shh "1.2.8" - web3-utils "1.2.8" +web3@1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/web3/-/web3-1.2.7.tgz#fcb83571036c1c6f475bc984785982a444e8d78e" + integrity sha512-jAAJHMfUlTps+jH2li1ckDFEpPrEEriU/ubegSTGRl3KRdNhEqT93+3kd7FHJTn3NgjcyURo2+f7Da1YcZL8Mw== + dependencies: + web3-bzz "1.2.7" + web3-core "1.2.7" + web3-eth "1.2.7" + web3-eth-personal "1.2.7" + web3-net "1.2.7" + web3-shh "1.2.7" + web3-utils "1.2.7" webidl-conversions@^4.0.2: version "4.0.2" From d2606997a35a719d9bde90ad9436d32f22cd13d8 Mon Sep 17 00:00:00 2001 From: Gerardo Nardelli Date: Tue, 23 Jun 2020 10:48:58 -0300 Subject: [PATCH 03/13] Add ALM failed validator transactions detection (#357) --- alm/.env.example | 3 + alm/package.json | 2 + alm/src/components/ConfirmationsContainer.tsx | 6 +- alm/src/components/ExecutionConfirmation.tsx | 4 +- alm/src/components/StatusContainer.tsx | 2 +- .../components/ValidatorsConfirmations.tsx | 4 +- alm/src/components/commons/Labels.tsx | 7 + alm/src/config/constants.ts | 12 ++ alm/src/hooks/useMessageConfirmations.ts | 46 +++-- alm/src/hooks/useTransactionStatus.ts | 4 +- alm/src/themes/Dark.tsx | 4 + alm/src/themes/GlobalStyle.tsx | 2 + alm/src/utils/executionWaitingForBlocks.ts | 7 + alm/src/utils/explorer.ts | 164 +++++++++++++++++ alm/src/utils/getConfirmationsForTx.ts | 170 +++++++++++++----- alm/src/utils/getFinalizationEvent.ts | 60 ++++++- alm/src/utils/web3.ts | 13 ++ yarn.lock | 30 ++++ 18 files changed, 471 insertions(+), 69 deletions(-) create mode 100644 alm/src/utils/explorer.ts diff --git a/alm/.env.example b/alm/.env.example index 1fb92664e..16ae54928 100644 --- a/alm/.env.example +++ b/alm/.env.example @@ -9,3 +9,6 @@ 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 diff --git a/alm/package.json b/alm/package.json index bafc9ef26..8d45f626c 100644 --- a/alm/package.json +++ b/alm/package.json @@ -9,6 +9,7 @@ "@testing-library/user-event": "^7.1.2", "@types/jest": "^24.0.0", "@types/node": "^12.0.0", + "@types/promise-retry": "^1.1.3", "@types/react": "^16.9.0", "@types/react-dom": "^16.9.0", "@types/react-router-dom": "^5.1.5", @@ -16,6 +17,7 @@ "customize-cra": "^1.0.0", "date-fns": "^2.14.0", "fast-memoize": "^2.5.2", + "promise-retry": "^2.0.1", "react": "^16.13.1", "react-app-rewired": "^2.1.6", "react-dom": "^16.13.1", diff --git a/alm/src/components/ConfirmationsContainer.tsx b/alm/src/components/ConfirmationsContainer.tsx index 3fcee5a2f..34cdb1f00 100644 --- a/alm/src/components/ConfirmationsContainer.tsx +++ b/alm/src/components/ConfirmationsContainer.tsx @@ -35,9 +35,10 @@ export interface ConfirmationsContainerParams { message: MessageObject receipt: Maybe fromHome: boolean + timestamp: number } -export const ConfirmationsContainer = ({ message, receipt, fromHome }: ConfirmationsContainerParams) => { +export const ConfirmationsContainer = ({ message, receipt, fromHome, timestamp }: ConfirmationsContainerParams) => { const { home: { name: homeName }, foreign: { name: foreignName } @@ -45,7 +46,8 @@ export const ConfirmationsContainer = ({ message, receipt, fromHome }: Confirmat const { confirmations, status, executionData, signatureCollected } = useMessageConfirmations({ message, receipt, - fromHome + fromHome, + timestamp }) return ( diff --git a/alm/src/components/ExecutionConfirmation.tsx b/alm/src/components/ExecutionConfirmation.tsx index 755e6c99e..54b658e62 100644 --- a/alm/src/components/ExecutionConfirmation.tsx +++ b/alm/src/components/ExecutionConfirmation.tsx @@ -5,7 +5,7 @@ import { VALIDATOR_CONFIRMATION_STATUS } from '../config/constants' import { SimpleLoading } from './commons/Loading' import styled from 'styled-components' import { ExecutionData } from '../hooks/useMessageConfirmations' -import { GreyLabel, SuccessLabel } from './commons/Labels' +import { GreyLabel, RedLabel, SuccessLabel } from './commons/Labels' import { ExplorerTxLink } from './commons/ExplorerTxLink' const Thead = styled.thead` @@ -32,6 +32,8 @@ export const ExecutionConfirmation = ({ executionData, isHome }: ExecutionConfir switch (validatorStatus) { case VALIDATOR_CONFIRMATION_STATUS.SUCCESS: return {validatorStatus} + case VALIDATOR_CONFIRMATION_STATUS.FAILED: + return {validatorStatus} case VALIDATOR_CONFIRMATION_STATUS.WAITING: return {validatorStatus} default: diff --git a/alm/src/components/StatusContainer.tsx b/alm/src/components/StatusContainer.tsx index 6d9591199..0f3ebc56c 100644 --- a/alm/src/components/StatusContainer.tsx +++ b/alm/src/components/StatusContainer.tsx @@ -75,7 +75,7 @@ export const StatusContainer = () => { )} {displayMessageSelector && } {displayConfirmations && ( - + )}
) diff --git a/alm/src/components/ValidatorsConfirmations.tsx b/alm/src/components/ValidatorsConfirmations.tsx index 4bff387d4..1cbd2103a 100644 --- a/alm/src/components/ValidatorsConfirmations.tsx +++ b/alm/src/components/ValidatorsConfirmations.tsx @@ -6,7 +6,7 @@ import { VALIDATOR_CONFIRMATION_STATUS } from '../config/constants' import { SimpleLoading } from './commons/Loading' import styled from 'styled-components' import { ConfirmationParam } from '../hooks/useMessageConfirmations' -import { GreyLabel, SuccessLabel } from './commons/Labels' +import { GreyLabel, RedLabel, SuccessLabel } from './commons/Labels' const Thead = styled.thead` border-bottom: 2px solid #9e9e9e; @@ -30,6 +30,8 @@ export const ValidatorsConfirmations = ({ confirmations }: ValidatorsConfirmatio switch (validatorStatus) { case VALIDATOR_CONFIRMATION_STATUS.SUCCESS: return {validatorStatus} + case VALIDATOR_CONFIRMATION_STATUS.FAILED: + return {validatorStatus} case VALIDATOR_CONFIRMATION_STATUS.WAITING: case VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED: return {validatorStatus} diff --git a/alm/src/components/commons/Labels.tsx b/alm/src/components/commons/Labels.tsx index 282031220..579745b9d 100644 --- a/alm/src/components/commons/Labels.tsx +++ b/alm/src/components/commons/Labels.tsx @@ -13,3 +13,10 @@ export const GreyLabel = styled.label` 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; +` diff --git a/alm/src/config/constants.ts b/alm/src/config/constants.ts index fef4e53c2..505a9cd5b 100644 --- a/alm/src/config/constants.ts +++ b/alm/src/config/constants.ts @@ -10,9 +10,21 @@ export const FOREIGN_NETWORK_NAME: string = process.env.REACT_APP_ALM_FOREIGN_NE 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 HOME_RPC_POLLING_INTERVAL: number = 5000 export const FOREIGN_RPC_POLLING_INTERVAL: number = 15000 export const BLOCK_RANGE: number = 50 +export const ONE_DAY_TIMESTAMP: number = 86400 +export const THREE_DAYS_TIMESTAMP: number = 259200 + +export const EXECUTE_AFFIRMATION_HASH = 'e7a2c01f' +export const SUBMIT_SIGNATURE_HASH = '630cea8e' +export const EXECUTE_SIGNATURES_HASH = '3f7658fd' + +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', diff --git a/alm/src/hooks/useMessageConfirmations.ts b/alm/src/hooks/useMessageConfirmations.ts index 4615b15b1..d93f15674 100644 --- a/alm/src/hooks/useMessageConfirmations.ts +++ b/alm/src/hooks/useMessageConfirmations.ts @@ -17,11 +17,13 @@ import { getCollectedSignaturesEvent } from '../utils/getCollectedSignaturesEven import { checkWaitingBlocksForExecution } from '../utils/executionWaitingForBlocks' import { getConfirmationsForTx } from '../utils/getConfirmationsForTx' import { getFinalizationEvent } from '../utils/getFinalizationEvent' +import { getValidatorFailedTransactionsForMessage, getExecutionFailedTransactionForMessage } from '../utils/explorer' export interface useMessageConfirmationsParams { message: MessageObject receipt: Maybe fromHome: boolean + timestamp: number } export interface ConfirmationParam { @@ -37,7 +39,7 @@ export interface ExecutionData { executionResult: boolean } -export const useMessageConfirmations = ({ message, receipt, fromHome }: useMessageConfirmationsParams) => { +export const useMessageConfirmations = ({ message, receipt, fromHome, timestamp }: useMessageConfirmationsParams) => { const { home, foreign } = useStateProvider() const [confirmations, setConfirmations] = useState>([]) const [status, setStatus] = useState(CONFIRMATIONS_STATUS.UNDEFINED) @@ -54,6 +56,8 @@ export const useMessageConfirmations = ({ message, receipt, fromHome }: useMessa }) const [waitingBlocksForExecution, setWaitingBlocksForExecution] = useState(false) const [waitingBlocksForExecutionResolved, setWaitingBlocksForExecutionResolved] = useState(false) + const [failedConfirmations, setFailedConfirmations] = useState(false) + const [failedExecution, setFailedExecution] = useState(false) // Check if the validators are waiting for block confirmations to verify the message useEffect( @@ -182,7 +186,7 @@ export const useMessageConfirmations = ({ message, receipt, fromHome }: useMessa // To avoid making extra requests, this is only executed when validators finished waiting for blocks confirmations useEffect( () => { - if (!waitingBlocksResolved) return + if (!waitingBlocksResolved || !timestamp) return const subscriptions: Array = [] @@ -204,7 +208,10 @@ export const useMessageConfirmations = ({ message, receipt, fromHome }: useMessa home.requiredSignatures, setSignatureCollected, waitingBlocksResolved, - subscriptions + subscriptions, + timestamp, + getValidatorFailedTransactionsForMessage, + setFailedConfirmations ) return () => { @@ -218,7 +225,8 @@ export const useMessageConfirmations = ({ message, receipt, fromHome }: useMessa home.validatorList, home.bridgeContract, home.requiredSignatures, - waitingBlocksResolved + waitingBlocksResolved, + timestamp ] ) @@ -248,9 +256,13 @@ export const useMessageConfirmations = ({ message, receipt, fromHome }: useMessa providedWeb3, setExecutionData, waitingBlocksResolved, - message.id, + message, interval, - subscriptions + subscriptions, + timestamp, + collectedSignaturesEvent, + getExecutionFailedTransactionForMessage, + setFailedExecution ) return () => { @@ -261,18 +273,20 @@ export const useMessageConfirmations = ({ message, receipt, fromHome }: useMessa fromHome, foreign.bridgeContract, home.bridgeContract, - message.id, + message, foreign.web3, home.web3, waitingBlocksResolved, - waitingBlocksForExecutionResolved + waitingBlocksForExecutionResolved, + timestamp, + collectedSignaturesEvent ] ) // Sets the message status based in the collected information useEffect( () => { - if (executionData.txHash) { + if (executionData.status === VALIDATOR_CONFIRMATION_STATUS.SUCCESS) { const newStatus = executionData.executionResult ? CONFIRMATIONS_STATUS.SUCCESS : CONFIRMATIONS_STATUS.SUCCESS_MESSAGE_FAILED @@ -281,6 +295,8 @@ export const useMessageConfirmations = ({ message, receipt, fromHome }: useMessa if (fromHome) { if (waitingBlocksForExecution) { setStatus(CONFIRMATIONS_STATUS.EXECUTION_WAITING) + } else if (failedExecution) { + setStatus(CONFIRMATIONS_STATUS.EXECUTION_FAILED) } else { setStatus(CONFIRMATIONS_STATUS.UNDEFINED) } @@ -289,11 +305,21 @@ export const useMessageConfirmations = ({ message, receipt, fromHome }: useMessa } } else if (waitingBlocks) { setStatus(CONFIRMATIONS_STATUS.WAITING) + } else if (failedConfirmations) { + setStatus(CONFIRMATIONS_STATUS.FAILED) } else { setStatus(CONFIRMATIONS_STATUS.UNDEFINED) } }, - [executionData, fromHome, signatureCollected, waitingBlocks, waitingBlocksForExecution] + [ + executionData, + fromHome, + signatureCollected, + waitingBlocks, + waitingBlocksForExecution, + failedConfirmations, + failedExecution + ] ) return { diff --git a/alm/src/hooks/useTransactionStatus.ts b/alm/src/hooks/useTransactionStatus.ts index 9a981744c..c6ad73e2a 100644 --- a/alm/src/hooks/useTransactionStatus.ts +++ b/alm/src/hooks/useTransactionStatus.ts @@ -3,7 +3,7 @@ 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 } from '../utils/web3' +import { getHomeMessagesFromReceipt, getForeignMessagesFromReceipt, MessageObject, getBlock } from '../utils/web3' export const useTransactionStatus = ({ txHash, chainId }: { txHash: string; chainId: number }) => { const { home, foreign } = useStateProvider() @@ -41,7 +41,7 @@ export const useTransactionStatus = ({ txHash, chainId }: { txHash: string; chai subscriptions.push(timeoutId) } else { const blockNumber = txReceipt.blockNumber - const block = await web3.eth.getBlock(blockNumber) + const block = await getBlock(web3, blockNumber) const blockTimestamp = typeof block.timestamp === 'string' ? parseInt(block.timestamp) : block.timestamp setTimestamp(blockTimestamp) diff --git a/alm/src/themes/Dark.tsx b/alm/src/themes/Dark.tsx index 07a4e0f46..4bb635313 100644 --- a/alm/src/themes/Dark.tsx +++ b/alm/src/themes/Dark.tsx @@ -12,6 +12,10 @@ const theme = { notRequired: { textColor: '#bdbdbd', backgroundColor: '#424242' + }, + failed: { + textColor: '#EF5350', + backgroundColor: '#4E342E' } } export default theme diff --git a/alm/src/themes/GlobalStyle.tsx b/alm/src/themes/GlobalStyle.tsx index d10ea42d1..04485df09 100644 --- a/alm/src/themes/GlobalStyle.tsx +++ b/alm/src/themes/GlobalStyle.tsx @@ -25,5 +25,7 @@ export const GlobalStyle = createGlobalStyle<{ theme: ThemeType }>` --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}; } ` diff --git a/alm/src/utils/executionWaitingForBlocks.ts b/alm/src/utils/executionWaitingForBlocks.ts index 612284913..958348266 100644 --- a/alm/src/utils/executionWaitingForBlocks.ts +++ b/alm/src/utils/executionWaitingForBlocks.ts @@ -15,6 +15,13 @@ export const checkWaitingBlocksForExecution = async ( const currentBlock = blockProvider.get() if (currentBlock && currentBlock >= targetBlock) { + setExecutionData({ + status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, + validator: collectedSignaturesEvent.returnValues.authorityResponsibleForRelay, + txHash: '', + timestamp: 0, + executionResult: false + }) setWaitingBlocksForExecution(false) setWaitingBlocksForExecutionResolved(true) blockProvider.stop() diff --git a/alm/src/utils/explorer.ts b/alm/src/utils/explorer.ts new file mode 100644 index 000000000..a6ba84ac0 --- /dev/null +++ b/alm/src/utils/explorer.ts @@ -0,0 +1,164 @@ +import { + EXECUTE_AFFIRMATION_HASH, + EXECUTE_SIGNATURES_HASH, + FOREIGN_EXPLORER_API, + HOME_EXPLORER_API, + SUBMIT_SIGNATURE_HASH +} from '../config/constants' + +export interface APITransaction { + timeStamp: string + isError: string + input: string + to: string + hash: string +} + +export interface AccountTransactionsParams { + account: string + to: string + startTimestamp: number + endTimestamp: number + api: string +} + +export interface GetFailedTransactionParams { + account: string + to: string + messageData: string + startTimestamp: number + endTimestamp: number +} + +export const fetchAccountTransactionsFromBlockscout = async ({ + account, + to, + startTimestamp, + endTimestamp, + api +}: AccountTransactionsParams): Promise => { + const url = `${api}?module=account&action=txlist&address=${account}&filterby=from=${account}&to=${to}&starttimestamp=${startTimestamp}&endtimestamp=${endTimestamp}` + + try { + const result = await fetch(url).then(res => res.json()) + if (result.status === '0') { + return [] + } + + return result.result + } catch (e) { + console.log(e) + return [] + } +} + +export const getBlockByTimestampUrl = (api: string, timestamp: number) => + `${api}?module=block&action=getblocknobytime×tamp=${timestamp}&closest=before` + +export const fetchAccountTransactionsFromEtherscan = async ({ + account, + to, + startTimestamp, + endTimestamp, + api +}: AccountTransactionsParams): Promise => { + const startBlockUrl = getBlockByTimestampUrl(api, startTimestamp) + const endBlockUrl = getBlockByTimestampUrl(api, endTimestamp) + let fromBlock = 0 + let toBlock = 9999999999999 + try { + const [fromBlockResult, toBlockResult] = await Promise.all([ + fetch(startBlockUrl).then(res => res.json()), + fetch(endBlockUrl).then(res => res.json()) + ]) + + if (fromBlockResult.status !== '0') { + fromBlock = parseInt(fromBlockResult.result) + } + + if (toBlockResult.status !== '0') { + toBlock = parseInt(toBlockResult.result) + } + } catch (e) { + console.log(e) + return [] + } + + const url = `${api}?module=account&action=txlist&address=${account}&startblock=${fromBlock}&endblock=${toBlock}` + + try { + const result = await fetch(url).then(res => res.json()) + + if (result.status === '0') { + return [] + } + + const toAddressLowerCase = to.toLowerCase() + const transactions: APITransaction[] = result.result + return transactions.filter(t => t.to.toLowerCase() === toAddressLowerCase) + } catch (e) { + console.log(e) + return [] + } +} + +export const fetchAccountTransactions = (api: string) => { + return api.includes('blockscout') ? fetchAccountTransactionsFromBlockscout : fetchAccountTransactionsFromEtherscan +} + +export const getFailedTransactions = async ( + account: string, + to: string, + startTimestamp: number, + endTimestamp: number, + api: string, + fetchAccountTransactions: (args: AccountTransactionsParams) => Promise +): Promise => { + const transactions = await fetchAccountTransactions({ account, to, startTimestamp, endTimestamp, api }) + + return transactions.filter(t => t.isError !== '0') +} + +export const getValidatorFailedTransactionsForMessage = async ({ + account, + to, + messageData, + startTimestamp, + endTimestamp +}: GetFailedTransactionParams): Promise => { + const failedTransactions = await getFailedTransactions( + account, + to, + startTimestamp, + endTimestamp, + HOME_EXPLORER_API, + fetchAccountTransactionsFromBlockscout + ) + + const messageDataValue = messageData.replace('0x', '') + return failedTransactions.filter( + t => + (t.input.includes(SUBMIT_SIGNATURE_HASH) || t.input.includes(EXECUTE_AFFIRMATION_HASH)) && + t.input.includes(messageDataValue) + ) +} + +export const getExecutionFailedTransactionForMessage = async ({ + account, + to, + messageData, + startTimestamp, + endTimestamp +}: GetFailedTransactionParams): Promise => { + const failedTransactions = await getFailedTransactions( + account, + to, + startTimestamp, + endTimestamp, + FOREIGN_EXPLORER_API, + fetchAccountTransactions(FOREIGN_EXPLORER_API) + ) + + const messageDataValue = messageData.replace('0x', '') + return failedTransactions.filter(t => t.input.includes(EXECUTE_SIGNATURES_HASH) && t.input.includes(messageDataValue)) +} diff --git a/alm/src/utils/getConfirmationsForTx.ts b/alm/src/utils/getConfirmationsForTx.ts index e5b29097e..009ad46ee 100644 --- a/alm/src/utils/getConfirmationsForTx.ts +++ b/alm/src/utils/getConfirmationsForTx.ts @@ -1,7 +1,81 @@ import Web3 from 'web3' import { Contract } from 'web3-eth-contract' import validatorsCache from '../services/ValidatorsCache' -import { HOME_RPC_POLLING_INTERVAL, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants' +import { + CACHE_KEY_FAILED, + HOME_RPC_POLLING_INTERVAL, + ONE_DAY_TIMESTAMP, + VALIDATOR_CONFIRMATION_STATUS +} from '../config/constants' +import { GetFailedTransactionParams, APITransaction } from './explorer' +import { ConfirmationParam } from '../hooks/useMessageConfirmations' + +export const getValidatorConfirmation = ( + web3: Web3, + hashMsg: string, + bridgeContract: Contract, + confirmationContractMethod: Function +) => async (validator: string): Promise => { + const hashSenderMsg = web3.utils.soliditySha3Raw(validator, hashMsg) + + const signatureFromCache = validatorsCache.get(hashSenderMsg) + if (signatureFromCache) { + return { + validator, + status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS + } + } + + const confirmed = await confirmationContractMethod(bridgeContract, hashSenderMsg) + const status = confirmed ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED + + // If validator confirmed signature, we cache the result to avoid doing future requests for a result that won't change + if (confirmed) { + validatorsCache.set(hashSenderMsg, confirmed) + } + + return { + validator, + status + } +} + +export const getValidatorFailedTransaction = ( + bridgeContract: Contract, + messageData: string, + timestamp: number, + getFailedTransactions: (args: GetFailedTransactionParams) => Promise +) => async (validatorData: ConfirmationParam): Promise => { + const validatorCacheKey = `${CACHE_KEY_FAILED}${validatorData.validator}` + const failedFromCache = validatorsCache.get(validatorCacheKey) + + if (failedFromCache) { + return { + validator: validatorData.validator, + status: VALIDATOR_CONFIRMATION_STATUS.FAILED + } + } + + const failedTransactions = await getFailedTransactions({ + account: validatorData.validator, + to: bridgeContract.options.address, + messageData, + startTimestamp: timestamp, + endTimestamp: timestamp + ONE_DAY_TIMESTAMP + }) + const newStatus = + failedTransactions.length > 0 ? VALIDATOR_CONFIRMATION_STATUS.FAILED : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED + + // If validator signature failed, we cache the result to avoid doing future requests for a result that won't change + if (failedTransactions.length > 0) { + validatorsCache.set(validatorCacheKey, true) + } + + return { + validator: validatorData.validator, + status: newStatus + } +} export const getConfirmationsForTx = async ( messageData: string, @@ -13,63 +87,69 @@ export const getConfirmationsForTx = async ( requiredSignatures: number, setSignatureCollected: Function, waitingBlocksResolved: boolean, - subscriptions: number[] + subscriptions: number[], + timestamp: number, + getFailedTransactions: (args: GetFailedTransactionParams) => Promise, + setFailedConfirmations: Function ) => { if (!web3 || !validatorList || !bridgeContract || !waitingBlocksResolved) return const hashMsg = web3.utils.soliditySha3Raw(messageData) let validatorConfirmations = await Promise.all( - validatorList.map(async validator => { - const hashSenderMsg = web3.utils.soliditySha3Raw(validator, hashMsg) - - const signatureFromCache = validatorsCache.get(hashSenderMsg) - if (signatureFromCache) { - return { - validator, - status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS - } - } - - const confirmed = await confirmationContractMethod(bridgeContract, hashSenderMsg) - const status = confirmed ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED - - // If validator confirmed signature, we cache the result to avoid doing future requests for a result that won't change - if (confirmed) { - validatorsCache.set(hashSenderMsg, confirmed) - } - - return { - validator, - status - } - }) + validatorList.map(getValidatorConfirmation(web3, hashMsg, bridgeContract, confirmationContractMethod)) ) const successConfirmations = validatorConfirmations.filter(c => c.status === VALIDATOR_CONFIRMATION_STATUS.SUCCESS) + const notSuccessConfirmations = validatorConfirmations.filter(c => c.status !== VALIDATOR_CONFIRMATION_STATUS.SUCCESS) + // If signatures not collected, it needs to retry in the next blocks if (successConfirmations.length !== requiredSignatures) { - const timeoutId = setTimeout( - () => - getConfirmationsForTx( - messageData, - web3, - validatorList, - bridgeContract, - confirmationContractMethod, - setResult, - requiredSignatures, - setSignatureCollected, - waitingBlocksResolved, - subscriptions - ), - HOME_RPC_POLLING_INTERVAL + // Check if confirmation failed + const validatorFailedConfirmationsChecks = await Promise.all( + notSuccessConfirmations.map( + getValidatorFailedTransaction(bridgeContract, messageData, timestamp, getFailedTransactions) + ) ) - subscriptions.push(timeoutId) + const validatorFailedConfirmations = validatorFailedConfirmationsChecks.filter( + c => c.status === VALIDATOR_CONFIRMATION_STATUS.FAILED + ) + validatorFailedConfirmations.forEach(validatorData => { + const index = validatorConfirmations.findIndex(e => e.validator === validatorData.validator) + validatorConfirmations[index] = validatorData + }) + const messageConfirmationsFailed = validatorFailedConfirmations.length > validatorList.length - requiredSignatures + if (messageConfirmationsFailed) { + setFailedConfirmations(true) + } + + const missingConfirmations = validatorConfirmations.filter( + c => c.status === VALIDATOR_CONFIRMATION_STATUS.UNDEFINED + ) + + if (missingConfirmations.length > 0) { + const timeoutId = setTimeout( + () => + getConfirmationsForTx( + messageData, + web3, + validatorList, + bridgeContract, + confirmationContractMethod, + setResult, + requiredSignatures, + setSignatureCollected, + waitingBlocksResolved, + subscriptions, + timestamp, + getFailedTransactions, + setFailedConfirmations + ), + HOME_RPC_POLLING_INTERVAL + ) + subscriptions.push(timeoutId) + } } else { // If signatures collected, it should set other signatures as not required - const notSuccessConfirmations = validatorConfirmations.filter( - c => c.status !== VALIDATOR_CONFIRMATION_STATUS.SUCCESS - ) const notRequiredConfirmations = notSuccessConfirmations.map(c => ({ validator: c.validator, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED diff --git a/alm/src/utils/getFinalizationEvent.ts b/alm/src/utils/getFinalizationEvent.ts index a391ebb5f..b87e15ed4 100644 --- a/alm/src/utils/getFinalizationEvent.ts +++ b/alm/src/utils/getFinalizationEvent.ts @@ -1,7 +1,10 @@ import { Contract, EventData } from 'web3-eth-contract' import Web3 from 'web3' -import { VALIDATOR_CONFIRMATION_STATUS } from '../config/constants' +import { CACHE_KEY_EXECUTION_FAILED, THREE_DAYS_TIMESTAMP, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants' import { ExecutionData } from '../hooks/useMessageConfirmations' +import { APITransaction, GetFailedTransactionParams } from './explorer' +import { getBlock, MessageObject } from './web3' +import validatorsCache from '../services/ValidatorsCache' export const getFinalizationEvent = async ( contract: Maybe, @@ -9,9 +12,13 @@ export const getFinalizationEvent = async ( web3: Maybe, setResult: React.Dispatch>, waitingBlocksResolved: boolean, - messageId: string, + message: MessageObject, interval: number, - subscriptions: number[] + subscriptions: number[], + timestamp: number, + collectedSignaturesEvent: Maybe, + getFailedExecution: (args: GetFailedTransactionParams) => Promise, + setFailedExecution: Function ) => { if (!contract || !web3 || !waitingBlocksResolved) return // Since it filters by the message id, only one event will be fetched @@ -20,14 +27,14 @@ export const getFinalizationEvent = async ( fromBlock: 0, toBlock: 'latest', filter: { - messageId + messageId: message.id } }) if (events.length > 0) { const event = events[0] const [txReceipt, block] = await Promise.all([ web3.eth.getTransactionReceipt(event.transactionHash), - web3.eth.getBlock(event.blockNumber) + getBlock(web3, event.blockNumber) ]) const blockTimestamp = typeof block.timestamp === 'string' ? parseInt(block.timestamp) : block.timestamp @@ -41,6 +48,41 @@ export const getFinalizationEvent = async ( executionResult: event.returnValues.status }) } else { + // If event is defined, it means it is a message from Home to Foreign + if (collectedSignaturesEvent) { + const validator = collectedSignaturesEvent.returnValues.authorityResponsibleForRelay + + const validatorExecutionCacheKey = `${CACHE_KEY_EXECUTION_FAILED}${validator}` + const failedFromCache = validatorsCache.get(validatorExecutionCacheKey) + + if (!failedFromCache) { + const failedTransactions = await getFailedExecution({ + account: validator, + to: contract.options.address, + messageData: message.data, + startTimestamp: timestamp, + endTimestamp: timestamp + THREE_DAYS_TIMESTAMP + }) + + 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 + }) + setFailedExecution(true) + } + } + } + const timeoutId = setTimeout( () => getFinalizationEvent( @@ -49,9 +91,13 @@ export const getFinalizationEvent = async ( web3, setResult, waitingBlocksResolved, - messageId, + message, interval, - subscriptions + subscriptions, + timestamp, + collectedSignaturesEvent, + getFailedExecution, + setFailedExecution ), interval ) diff --git a/alm/src/utils/web3.ts b/alm/src/utils/web3.ts index d35dd3648..db91d59ec 100644 --- a/alm/src/utils/web3.ts +++ b/alm/src/utils/web3.ts @@ -1,7 +1,9 @@ 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 '../../../commons' export interface MessageObject { @@ -48,3 +50,14 @@ export const getForeignMessagesFromReceipt = (txReceipt: TransactionReceipt, web )[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 => + promiseRetry(async retry => { + const result = await web3.eth.getBlock(blockNumber) + if (!result) { + return retry('Error getting block data') + } + return result + }) diff --git a/yarn.lock b/yarn.lock index b90bd96b8..20ab80397 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2980,6 +2980,13 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.59.tgz#9e34261f30183f9777017a13d185dfac6b899e04" integrity sha512-8RkBivJrDCyPpBXhVZcjh7cQxVBSmRk9QM7hOketZzp6Tg79c0N8kkpAIito9bnJ3HCVCHVYz+KHTEbfQNfeVQ== +"@types/promise-retry@^1.1.3": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@types/promise-retry/-/promise-retry-1.1.3.tgz#baab427419da9088a1d2f21bf56249c21b3dd43c" + integrity sha512-LxIlEpEX6frE3co3vCO2EUJfHIta1IOmhDlcAsR4GMMv9hev1iTI9VwberVGkePJAuLZs5rMucrV8CziCfuJMw== + dependencies: + "@types/retry" "*" + "@types/prop-types@*": version "15.7.3" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7" @@ -3042,6 +3049,11 @@ "@types/prop-types" "*" csstype "^2.2.0" +"@types/retry@*": + version "0.12.0" + resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d" + integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA== + "@types/solidity-parser-antlr@^0.2.3": version "0.2.3" resolved "https://registry.yarnpkg.com/@types/solidity-parser-antlr/-/solidity-parser-antlr-0.2.3.tgz#bb2d9c6511bf483afe4fc3e2714da8a924e59e3f" @@ -7712,6 +7724,11 @@ err-code@^1.0.0: resolved "https://registry.yarnpkg.com/err-code/-/err-code-1.1.2.tgz#06e0116d3028f6aef4806849eb0ea6a748ae6960" integrity sha1-BuARbTAo9q70gGhJ6w6mp0iuaWA= +err-code@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/err-code/-/err-code-2.0.3.tgz#23c2f3b756ffdfc608d30e27c9a941024807e7f9" + integrity sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA== + errno@^0.1.3, errno@~0.1.1, errno@~0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618" @@ -16100,6 +16117,14 @@ promise-retry@^1.1.1: err-code "^1.0.0" retry "^0.10.0" +promise-retry@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/promise-retry/-/promise-retry-2.0.1.tgz#ff747a13620ab57ba688f5fc67855410c370da22" + integrity sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g== + dependencies: + err-code "^2.0.2" + retry "^0.12.0" + promise-to-callback@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/promise-to-callback/-/promise-to-callback-1.0.0.tgz#5d2a749010bfb67d963598fcd3960746a68feef7" @@ -17410,6 +17435,11 @@ retry@^0.10.0: resolved "https://registry.yarnpkg.com/retry/-/retry-0.10.1.tgz#e76388d217992c252750241d3d3956fed98d8ff4" integrity sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q= +retry@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" + integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs= + rgb-regex@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/rgb-regex/-/rgb-regex-1.0.1.tgz#c0e0d6882df0e23be254a475e8edd41915feaeb1" From d228bb7ea9eaa0d7461aee92f5ec205cd417a1ef Mon Sep 17 00:00:00 2001 From: Gerardo Nardelli Date: Tue, 23 Jun 2020 10:49:59 -0300 Subject: [PATCH 04/13] Add button to search another transaction in ALM (#364) --- alm/src/components/StatusContainer.tsx | 25 +++++++++++++++++++++++- alm/src/components/commons/LeftArrow.tsx | 20 +++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 alm/src/components/commons/LeftArrow.tsx diff --git a/alm/src/components/StatusContainer.tsx b/alm/src/components/StatusContainer.tsx index 0f3ebc56c..a8d0d000c 100644 --- a/alm/src/components/StatusContainer.tsx +++ b/alm/src/components/StatusContainer.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { useHistory, useParams } from 'react-router-dom' +import { Link, 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' @@ -8,6 +8,19 @@ import { Loading } from './commons/Loading' import { useStateProvider } from '../state/StateProvider' import { ExplorerTxLink } from './commons/ExplorerTxLink' import { ConfirmationsContainer } from './ConfirmationsContainer' +import { LeftArrow } from './commons/LeftArrow' +import styled from 'styled-components' + +const BackButton = styled.button` + color: var(--font-color); + border-color: var(--font-color); + margin-top: 10px; +` + +const BackLabel = styled.label` + margin-left: 5px; + cursor: pointer; +` export const StatusContainer = () => { const { home, foreign } = useStateProvider() @@ -77,6 +90,16 @@ export const StatusContainer = () => { {displayConfirmations && ( )} +
+
+ + + + Search another transaction + + +
+
) } diff --git a/alm/src/components/commons/LeftArrow.tsx b/alm/src/components/commons/LeftArrow.tsx new file mode 100644 index 000000000..1700297a7 --- /dev/null +++ b/alm/src/components/commons/LeftArrow.tsx @@ -0,0 +1,20 @@ +import React from 'react' +import { useContext } from 'react' +import { ThemeContext } from 'styled-components' + +export const LeftArrow = () => { + const themeContext = useContext(ThemeContext) + return ( + + + + ) +} From 9e9e891db8feec5fefc5b43a55514caa38c3a47d Mon Sep 17 00:00:00 2001 From: Gerardo Nardelli Date: Tue, 23 Jun 2020 11:24:10 -0300 Subject: [PATCH 05/13] Add ALM Pending validator transactions detection (#363) --- alm/src/components/ExecutionConfirmation.tsx | 1 + .../components/ValidatorsConfirmations.tsx | 1 + alm/src/config/constants.ts | 2 +- alm/src/hooks/useMessageConfirmations.ts | 25 +++++- alm/src/utils/explorer.ts | 77 ++++++++++++++++++ alm/src/utils/getConfirmationsForTx.ts | 59 ++++++++++++-- alm/src/utils/getFinalizationEvent.ts | 81 +++++++++++++------ 7 files changed, 211 insertions(+), 35 deletions(-) diff --git a/alm/src/components/ExecutionConfirmation.tsx b/alm/src/components/ExecutionConfirmation.tsx index 54b658e62..5bc40ea5b 100644 --- a/alm/src/components/ExecutionConfirmation.tsx +++ b/alm/src/components/ExecutionConfirmation.tsx @@ -34,6 +34,7 @@ export const ExecutionConfirmation = ({ executionData, isHome }: ExecutionConfir return {validatorStatus} case VALIDATOR_CONFIRMATION_STATUS.FAILED: return {validatorStatus} + case VALIDATOR_CONFIRMATION_STATUS.PENDING: case VALIDATOR_CONFIRMATION_STATUS.WAITING: return {validatorStatus} default: diff --git a/alm/src/components/ValidatorsConfirmations.tsx b/alm/src/components/ValidatorsConfirmations.tsx index 1cbd2103a..94bdd17bb 100644 --- a/alm/src/components/ValidatorsConfirmations.tsx +++ b/alm/src/components/ValidatorsConfirmations.tsx @@ -32,6 +32,7 @@ export const ValidatorsConfirmations = ({ confirmations }: ValidatorsConfirmatio return {validatorStatus} case VALIDATOR_CONFIRMATION_STATUS.FAILED: return {validatorStatus} + case VALIDATOR_CONFIRMATION_STATUS.PENDING: case VALIDATOR_CONFIRMATION_STATUS.WAITING: case VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED: return {validatorStatus} diff --git a/alm/src/config/constants.ts b/alm/src/config/constants.ts index 505a9cd5b..5fe96bc9d 100644 --- a/alm/src/config/constants.ts +++ b/alm/src/config/constants.ts @@ -14,7 +14,7 @@ export const HOME_EXPLORER_API: string = process.env.REACT_APP_ALM_HOME_EXPLORER export const FOREIGN_EXPLORER_API: string = process.env.REACT_APP_ALM_FOREIGN_EXPLORER_API || '' export const HOME_RPC_POLLING_INTERVAL: number = 5000 -export const FOREIGN_RPC_POLLING_INTERVAL: number = 15000 +export const FOREIGN_RPC_POLLING_INTERVAL: number = 5000 export const BLOCK_RANGE: number = 50 export const ONE_DAY_TIMESTAMP: number = 86400 export const THREE_DAYS_TIMESTAMP: number = 259200 diff --git a/alm/src/hooks/useMessageConfirmations.ts b/alm/src/hooks/useMessageConfirmations.ts index d93f15674..fd6a17749 100644 --- a/alm/src/hooks/useMessageConfirmations.ts +++ b/alm/src/hooks/useMessageConfirmations.ts @@ -17,7 +17,12 @@ import { getCollectedSignaturesEvent } from '../utils/getCollectedSignaturesEven import { checkWaitingBlocksForExecution } from '../utils/executionWaitingForBlocks' import { getConfirmationsForTx } from '../utils/getConfirmationsForTx' import { getFinalizationEvent } from '../utils/getFinalizationEvent' -import { getValidatorFailedTransactionsForMessage, getExecutionFailedTransactionForMessage } from '../utils/explorer' +import { + getValidatorFailedTransactionsForMessage, + getExecutionFailedTransactionForMessage, + getValidatorPendingTransactionsForMessage, + getExecutionPendingTransactionsForMessage +} from '../utils/explorer' export interface useMessageConfirmationsParams { message: MessageObject @@ -58,6 +63,8 @@ export const useMessageConfirmations = ({ message, receipt, fromHome, timestamp 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) // Check if the validators are waiting for block confirmations to verify the message useEffect( @@ -211,7 +218,9 @@ export const useMessageConfirmations = ({ message, receipt, fromHome, timestamp subscriptions, timestamp, getValidatorFailedTransactionsForMessage, - setFailedConfirmations + setFailedConfirmations, + getValidatorPendingTransactionsForMessage, + setPendingConfirmations ) return () => { @@ -262,7 +271,9 @@ export const useMessageConfirmations = ({ message, receipt, fromHome, timestamp timestamp, collectedSignaturesEvent, getExecutionFailedTransactionForMessage, - setFailedExecution + setFailedExecution, + getExecutionPendingTransactionsForMessage, + setPendingExecution ) return () => { @@ -297,6 +308,8 @@ export const useMessageConfirmations = ({ message, receipt, fromHome, timestamp setStatus(CONFIRMATIONS_STATUS.EXECUTION_WAITING) } else if (failedExecution) { setStatus(CONFIRMATIONS_STATUS.EXECUTION_FAILED) + } else if (pendingExecution) { + setStatus(CONFIRMATIONS_STATUS.EXECUTION_PENDING) } else { setStatus(CONFIRMATIONS_STATUS.UNDEFINED) } @@ -307,6 +320,8 @@ export const useMessageConfirmations = ({ message, receipt, fromHome, timestamp setStatus(CONFIRMATIONS_STATUS.WAITING) } else if (failedConfirmations) { setStatus(CONFIRMATIONS_STATUS.FAILED) + } else if (pendingConfirmations) { + setStatus(CONFIRMATIONS_STATUS.PENDING) } else { setStatus(CONFIRMATIONS_STATUS.UNDEFINED) } @@ -318,7 +333,9 @@ export const useMessageConfirmations = ({ message, receipt, fromHome, timestamp waitingBlocks, waitingBlocksForExecution, failedConfirmations, - failedExecution + failedExecution, + pendingConfirmations, + pendingExecution ] ) diff --git a/alm/src/utils/explorer.ts b/alm/src/utils/explorer.ts index a6ba84ac0..8d745c58e 100644 --- a/alm/src/utils/explorer.ts +++ b/alm/src/utils/explorer.ts @@ -14,6 +14,17 @@ export interface APITransaction { hash: string } +export interface APIPendingTransaction { + input: string + to: string + hash: string +} + +export interface PendingTransactionsParams { + account: string + api: string +} + export interface AccountTransactionsParams { account: string to: string @@ -30,6 +41,12 @@ export interface GetFailedTransactionParams { endTimestamp: number } +export interface GetPendingTransactionParams { + account: string + to: string + messageData: string +} + export const fetchAccountTransactionsFromBlockscout = async ({ account, to, @@ -106,6 +123,24 @@ export const fetchAccountTransactions = (api: string) => { return api.includes('blockscout') ? fetchAccountTransactionsFromBlockscout : fetchAccountTransactionsFromEtherscan } +export const fetchPendingTransactions = async ({ + account, + api +}: PendingTransactionsParams): Promise => { + const url = `${api}?module=account&action=pendingtxlist&address=${account}` + + try { + const result = await fetch(url).then(res => res.json()) + if (result.status === '0') { + return [] + } + + return result.result + } catch (e) { + return [] + } +} + export const getFailedTransactions = async ( account: string, to: string, @@ -162,3 +197,45 @@ export const getExecutionFailedTransactionForMessage = async ({ 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): Promise => { + const pendingTransactions = await fetchPendingTransactions({ + 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): Promise => { + const pendingTransactions = await fetchPendingTransactions({ + 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) + ) +} diff --git a/alm/src/utils/getConfirmationsForTx.ts b/alm/src/utils/getConfirmationsForTx.ts index 009ad46ee..f570c6b06 100644 --- a/alm/src/utils/getConfirmationsForTx.ts +++ b/alm/src/utils/getConfirmationsForTx.ts @@ -7,7 +7,12 @@ import { ONE_DAY_TIMESTAMP, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants' -import { GetFailedTransactionParams, APITransaction } from './explorer' +import { + GetFailedTransactionParams, + APITransaction, + APIPendingTransaction, + GetPendingTransactionParams +} from './explorer' import { ConfirmationParam } from '../hooks/useMessageConfirmations' export const getValidatorConfirmation = ( @@ -77,6 +82,26 @@ export const getValidatorFailedTransaction = ( } } +export const getValidatorPendingTransaction = ( + bridgeContract: Contract, + messageData: string, + getPendingTransactions: (args: GetPendingTransactionParams) => Promise +) => async (validatorData: ConfirmationParam): Promise => { + 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 + + return { + validator: validatorData.validator, + status: newStatus + } +} + export const getConfirmationsForTx = async ( messageData: string, web3: Maybe, @@ -90,7 +115,9 @@ export const getConfirmationsForTx = async ( subscriptions: number[], timestamp: number, getFailedTransactions: (args: GetFailedTransactionParams) => Promise, - setFailedConfirmations: Function + setFailedConfirmations: Function, + getPendingTransactions: (args: GetPendingTransactionParams) => Promise, + setPendingConfirmations: Function ) => { if (!web3 || !validatorList || !bridgeContract || !waitingBlocksResolved) return const hashMsg = web3.utils.soliditySha3Raw(messageData) @@ -104,9 +131,29 @@ export const getConfirmationsForTx = async ( // If signatures not collected, it needs to retry in the next blocks if (successConfirmations.length !== requiredSignatures) { + // 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 + ) + + validatorPendingConfirmations.forEach(validatorData => { + const index = validatorConfirmations.findIndex(e => e.validator === validatorData.validator) + validatorConfirmations[index] = validatorData + }) + + if (validatorPendingConfirmations.length > 0) { + setPendingConfirmations(true) + } + + const undefinedConfirmations = validatorConfirmations.filter( + c => c.status === VALIDATOR_CONFIRMATION_STATUS.UNDEFINED + ) // Check if confirmation failed const validatorFailedConfirmationsChecks = await Promise.all( - notSuccessConfirmations.map( + undefinedConfirmations.map( getValidatorFailedTransaction(bridgeContract, messageData, timestamp, getFailedTransactions) ) ) @@ -123,7 +170,7 @@ export const getConfirmationsForTx = async ( } const missingConfirmations = validatorConfirmations.filter( - c => c.status === VALIDATOR_CONFIRMATION_STATUS.UNDEFINED + c => c.status === VALIDATOR_CONFIRMATION_STATUS.UNDEFINED || c.status === VALIDATOR_CONFIRMATION_STATUS.PENDING ) if (missingConfirmations.length > 0) { @@ -142,7 +189,9 @@ export const getConfirmationsForTx = async ( subscriptions, timestamp, getFailedTransactions, - setFailedConfirmations + setFailedConfirmations, + getPendingTransactions, + setPendingConfirmations ), HOME_RPC_POLLING_INTERVAL ) diff --git a/alm/src/utils/getFinalizationEvent.ts b/alm/src/utils/getFinalizationEvent.ts index b87e15ed4..1407b2acc 100644 --- a/alm/src/utils/getFinalizationEvent.ts +++ b/alm/src/utils/getFinalizationEvent.ts @@ -2,7 +2,12 @@ import { Contract, EventData } from 'web3-eth-contract' import Web3 from 'web3' import { CACHE_KEY_EXECUTION_FAILED, THREE_DAYS_TIMESTAMP, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants' import { ExecutionData } from '../hooks/useMessageConfirmations' -import { APITransaction, GetFailedTransactionParams } from './explorer' +import { + APIPendingTransaction, + APITransaction, + GetFailedTransactionParams, + GetPendingTransactionParams +} from './explorer' import { getBlock, MessageObject } from './web3' import validatorsCache from '../services/ValidatorsCache' @@ -18,7 +23,9 @@ export const getFinalizationEvent = async ( timestamp: number, collectedSignaturesEvent: Maybe, getFailedExecution: (args: GetFailedTransactionParams) => Promise, - setFailedExecution: Function + setFailedExecution: Function, + getPendingExecution: (args: GetPendingTransactionParams) => Promise, + setPendingExecution: Function ) => { if (!contract || !web3 || !waitingBlocksResolved) return // Since it filters by the message id, only one event will be fetched @@ -52,33 +59,55 @@ export const getFinalizationEvent = async ( if (collectedSignaturesEvent) { const validator = collectedSignaturesEvent.returnValues.authorityResponsibleForRelay - const validatorExecutionCacheKey = `${CACHE_KEY_EXECUTION_FAILED}${validator}` - const failedFromCache = validatorsCache.get(validatorExecutionCacheKey) + const pendingTransactions = await getPendingExecution({ + account: validator, + messageData: message.data, + to: contract.options.address + }) - if (!failedFromCache) { - const failedTransactions = await getFailedExecution({ - account: validator, - to: contract.options.address, - messageData: message.data, - startTimestamp: timestamp, - endTimestamp: timestamp + THREE_DAYS_TIMESTAMP - }) + // 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] - if (failedTransactions.length > 0) { - const failedTx = failedTransactions[0] + const nowTimestamp = Math.floor(new Date().getTime() / 1000.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) + setResult({ + status: VALIDATOR_CONFIRMATION_STATUS.PENDING, + validator: validator, + txHash: pendingTx.hash, + timestamp: nowTimestamp, + executionResult: false + }) + setPendingExecution(true) + } else { + const validatorExecutionCacheKey = `${CACHE_KEY_EXECUTION_FAILED}${validator}` + const failedFromCache = validatorsCache.get(validatorExecutionCacheKey) - const timestamp = parseInt(failedTx.timeStamp) - setResult({ - status: VALIDATOR_CONFIRMATION_STATUS.FAILED, - validator: validator, - txHash: failedTx.hash, - timestamp, - executionResult: false + if (!failedFromCache) { + const failedTransactions = await getFailedExecution({ + account: validator, + to: contract.options.address, + messageData: message.data, + startTimestamp: timestamp, + endTimestamp: timestamp + THREE_DAYS_TIMESTAMP }) - setFailedExecution(true) + + 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 + }) + setFailedExecution(true) + } } } } @@ -97,7 +126,9 @@ export const getFinalizationEvent = async ( timestamp, collectedSignaturesEvent, getFailedExecution, - setFailedExecution + setFailedExecution, + getPendingExecution, + setPendingExecution ), interval ) From 0eb7c4127878ae278e356db394677853d43a6a82 Mon Sep 17 00:00:00 2001 From: Gerardo Nardelli Date: Wed, 24 Jun 2020 14:47:12 -0300 Subject: [PATCH 06/13] Build the list of validators and required signatures at the moment of the transaction (#367) --- alm/src/components/ConfirmationsContainer.tsx | 12 +++- .../components/ValidatorsConfirmations.tsx | 12 ++-- alm/src/hooks/useBridgeContracts.ts | 45 +----------- alm/src/hooks/useMessageConfirmations.ts | 31 +++++---- alm/src/hooks/useValidatorContract.ts | 68 +++++++++++++++++++ alm/src/state/StateProvider.tsx | 26 +------ alm/src/utils/contract.ts | 40 ++++++++++- 7 files changed, 144 insertions(+), 90 deletions(-) create mode 100644 alm/src/hooks/useValidatorContract.ts diff --git a/alm/src/components/ConfirmationsContainer.tsx b/alm/src/components/ConfirmationsContainer.tsx index 34cdb1f00..94272c2b8 100644 --- a/alm/src/components/ConfirmationsContainer.tsx +++ b/alm/src/components/ConfirmationsContainer.tsx @@ -10,6 +10,7 @@ import { ValidatorsConfirmations } from './ValidatorsConfirmations' import { getConfirmationsStatusDescription } from '../utils/networks' import { useStateProvider } from '../state/StateProvider' import { ExecutionConfirmation } from './ExecutionConfirmation' +import { useValidatorContract } from '../hooks/useValidatorContract' const StatusLabel = styled.label` font-weight: bold; @@ -43,11 +44,14 @@ export const ConfirmationsContainer = ({ message, receipt, fromHome, timestamp } home: { name: homeName }, foreign: { name: foreignName } } = useStateProvider() + const { requiredSignatures, validatorList } = useValidatorContract({ fromHome, receipt }) const { confirmations, status, executionData, signatureCollected } = useMessageConfirmations({ message, receipt, fromHome, - timestamp + timestamp, + requiredSignatures, + validatorList }) return ( @@ -66,7 +70,11 @@ export const ConfirmationsContainer = ({ message, receipt, fromHome, timestamp } : ''}

- + {signatureCollected && } diff --git a/alm/src/components/ValidatorsConfirmations.tsx b/alm/src/components/ValidatorsConfirmations.tsx index 94bdd17bb..fa53a2f27 100644 --- a/alm/src/components/ValidatorsConfirmations.tsx +++ b/alm/src/components/ValidatorsConfirmations.tsx @@ -1,6 +1,5 @@ import React from 'react' import { formatTxHashExtended } from '../utils/networks' -import { useStateProvider } from '../state/StateProvider' import { useWindowWidth } from '@react-hook/window-size' import { VALIDATOR_CONFIRMATION_STATUS } from '../config/constants' import { SimpleLoading } from './commons/Loading' @@ -18,12 +17,15 @@ const RequiredConfirmations = styled.label` export interface ValidatorsConfirmationsParams { confirmations: Array + requiredSignatures: number + validatorList: string[] } -export const ValidatorsConfirmations = ({ confirmations }: ValidatorsConfirmationsParams) => { - const { - home: { requiredSignatures, validatorList } - } = useStateProvider() +export const ValidatorsConfirmations = ({ + confirmations, + requiredSignatures, + validatorList +}: ValidatorsConfirmationsParams) => { const windowWidth = useWindowWidth() const getValidatorStatusElement = (validatorStatus = '') => { diff --git a/alm/src/hooks/useBridgeContracts.ts b/alm/src/hooks/useBridgeContracts.ts index 4e253eec7..e82edff19 100644 --- a/alm/src/hooks/useBridgeContracts.ts +++ b/alm/src/hooks/useBridgeContracts.ts @@ -1,14 +1,9 @@ import { useEffect, useState } from 'react' -import { HOME_AMB_ABI, FOREIGN_AMB_ABI, BRIDGE_VALIDATORS_ABI } from '../../../commons' +import { HOME_AMB_ABI, FOREIGN_AMB_ABI } from '../../../commons' import { FOREIGN_BRIDGE_ADDRESS, HOME_BRIDGE_ADDRESS } from '../config/constants' import { Contract } from 'web3-eth-contract' import Web3 from 'web3' -import { - getRequiredBlockConfirmations, - getRequiredSignatures, - getValidatorAddress, - getValidatorList -} from '../utils/contract' +import { getRequiredBlockConfirmations } from '../utils/contract' export interface useBridgeContractsParams { homeWeb3: Web3 @@ -20,9 +15,6 @@ export const useBridgeContracts = ({ homeWeb3, foreignWeb3 }: useBridgeContracts const [foreignBridge, setForeignBridge] = useState>(null) const [homeBlockConfirmations, setHomeBlockConfirmations] = useState(0) const [foreignBlockConfirmations, setForeignBlockConfirmations] = useState(0) - const [homeValidatorContract, setHomeValidatorContract] = useState>(null) - const [homeRequiredSignatures, setHomeRequiredSignatures] = useState(0) - const [homeValidatorList, setHomeValidatorList] = useState([]) const callRequireBlockConfirmations = async (contract: Maybe, setResult: Function) => { if (!contract) return @@ -30,31 +22,11 @@ export const useBridgeContracts = ({ homeWeb3, foreignWeb3 }: useBridgeContracts setResult(result) } - const callValidatorContract = async (bridgeContract: Maybe, 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, setResult: Function) => { - if (!contract) return - const result = await getRequiredSignatures(contract) - setResult(result) - } - - const callValidatorList = async (contract: Maybe, setResult: Function) => { - if (!contract) return - const result = await getValidatorList(contract) - setResult(result) - } - useEffect( () => { if (!homeWeb3) return const homeContract = new homeWeb3.eth.Contract(HOME_AMB_ABI, HOME_BRIDGE_ADDRESS) callRequireBlockConfirmations(homeContract, setHomeBlockConfirmations) - callValidatorContract(homeContract, homeWeb3, setHomeValidatorContract) setHomeBridge(homeContract) }, [homeWeb3] @@ -70,21 +42,10 @@ export const useBridgeContracts = ({ homeWeb3, foreignWeb3 }: useBridgeContracts [foreignWeb3] ) - useEffect( - () => { - callRequiredSignatures(homeValidatorContract, setHomeRequiredSignatures) - callValidatorList(homeValidatorContract, setHomeValidatorList) - }, - [homeValidatorContract] - ) - return { homeBridge, foreignBridge, homeBlockConfirmations, - foreignBlockConfirmations, - homeValidatorContract, - homeRequiredSignatures, - homeValidatorList + foreignBlockConfirmations } } diff --git a/alm/src/hooks/useMessageConfirmations.ts b/alm/src/hooks/useMessageConfirmations.ts index fd6a17749..6faaef2ad 100644 --- a/alm/src/hooks/useMessageConfirmations.ts +++ b/alm/src/hooks/useMessageConfirmations.ts @@ -29,6 +29,8 @@ export interface useMessageConfirmationsParams { receipt: Maybe fromHome: boolean timestamp: number + requiredSignatures: number + validatorList: string[] } export interface ConfirmationParam { @@ -44,7 +46,14 @@ export interface ExecutionData { executionResult: boolean } -export const useMessageConfirmations = ({ message, receipt, fromHome, timestamp }: useMessageConfirmationsParams) => { +export const useMessageConfirmations = ({ + message, + receipt, + fromHome, + timestamp, + requiredSignatures, + validatorList +}: useMessageConfirmationsParams) => { const { home, foreign } = useStateProvider() const [confirmations, setConfirmations] = useState>([]) const [status, setStatus] = useState(CONFIRMATIONS_STATUS.UNDEFINED) @@ -91,7 +100,7 @@ export const useMessageConfirmations = ({ message, receipt, fromHome, timestamp targetBlock, setWaitingBlocks, setWaitingBlocksResolved, - home.validatorList, + validatorList, setConfirmations, blockProvider, interval, @@ -103,15 +112,7 @@ export const useMessageConfirmations = ({ message, receipt, fromHome, timestamp blockProvider.stop() } }, - [ - foreign.blockConfirmations, - foreign.web3, - fromHome, - home.blockConfirmations, - home.validatorList, - home.web3, - receipt - ] + [foreign.blockConfirmations, foreign.web3, fromHome, home.blockConfirmations, validatorList, home.web3, receipt] ) // The collected signature event is only fetched once the signatures are collected on tx from home to foreign, to calculate if @@ -208,11 +209,11 @@ export const useMessageConfirmations = ({ message, receipt, fromHome, timestamp getConfirmationsForTx( message.data, home.web3, - home.validatorList, + validatorList, home.bridgeContract, confirmationContractMethod, setConfirmations, - home.requiredSignatures, + requiredSignatures, setSignatureCollected, waitingBlocksResolved, subscriptions, @@ -231,9 +232,9 @@ export const useMessageConfirmations = ({ message, receipt, fromHome, timestamp fromHome, message.data, home.web3, - home.validatorList, + validatorList, home.bridgeContract, - home.requiredSignatures, + requiredSignatures, waitingBlocksResolved, timestamp ] diff --git a/alm/src/hooks/useValidatorContract.ts b/alm/src/hooks/useValidatorContract.ts new file mode 100644 index 000000000..c48635741 --- /dev/null +++ b/alm/src/hooks/useValidatorContract.ts @@ -0,0 +1,68 @@ +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 '../../../commons' +import { useStateProvider } from '../state/StateProvider' +import { TransactionReceipt } from 'web3-eth' + +export interface useValidatorContractParams { + fromHome: boolean + receipt: Maybe +} + +export const useValidatorContract = ({ receipt, fromHome }: useValidatorContractParams) => { + const [validatorContract, setValidatorContract] = useState>(null) + const [requiredSignatures, setRequiredSignatures] = useState(0) + const [validatorList, setValidatorList] = useState([]) + + const { home, foreign } = useStateProvider() + + const callValidatorContract = async (bridgeContract: Maybe, 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, + receipt: TransactionReceipt, + setResult: Function + ) => { + if (!contract) return + const result = await getRequiredSignatures(contract, receipt.blockNumber) + setResult(result) + } + + const callValidatorList = async (contract: Maybe, receipt: TransactionReceipt, setResult: Function) => { + if (!contract) return + const result = await getValidatorList(contract, receipt.blockNumber) + setResult(result) + } + + useEffect( + () => { + const web3 = fromHome ? home.web3 : foreign.web3 + const bridgeContract = fromHome ? home.bridgeContract : foreign.bridgeContract + + if (!web3 || !bridgeContract) return + callValidatorContract(bridgeContract, web3, setValidatorContract) + }, + [home.web3, foreign.web3, home.bridgeContract, foreign.bridgeContract, fromHome] + ) + + useEffect( + () => { + if (!receipt) return + callRequiredSignatures(validatorContract, receipt, setRequiredSignatures) + callValidatorList(validatorContract, receipt, setValidatorList) + }, + [validatorContract, receipt] + ) + + return { + requiredSignatures, + validatorList + } +} diff --git a/alm/src/state/StateProvider.tsx b/alm/src/state/StateProvider.tsx index 0c5172581..3920c0953 100644 --- a/alm/src/state/StateProvider.tsx +++ b/alm/src/state/StateProvider.tsx @@ -21,14 +21,8 @@ export interface BaseNetworkParams { blockConfirmations: number } -export interface HomeNetworkParams extends BaseNetworkParams { - validatorContract: Maybe - requiredSignatures: number - validatorList: Array -} - export interface StateContext { - home: HomeNetworkParams + home: BaseNetworkParams foreign: BaseNetworkParams loading: boolean } @@ -40,10 +34,7 @@ const initialState = { web3: null, bridgeAddress: HOME_BRIDGE_ADDRESS, bridgeContract: null, - blockConfirmations: 0, - validatorContract: null, - requiredSignatures: 0, - validatorList: [] + blockConfirmations: 0 }, foreign: { chainId: 0, @@ -61,15 +52,7 @@ const StateContext = createContext(initialState) export const StateProvider = ({ children }: { children: ReactNode }) => { const homeNetwork = useNetwork(HOME_RPC_URL) const foreignNetwork = useNetwork(FOREIGN_RPC_URL) - const { - homeBridge, - foreignBridge, - homeBlockConfirmations, - foreignBlockConfirmations, - homeValidatorContract, - homeRequiredSignatures, - homeValidatorList - } = useBridgeContracts({ + const { homeBridge, foreignBridge, homeBlockConfirmations, foreignBlockConfirmations } = useBridgeContracts({ homeWeb3: homeNetwork.web3, foreignWeb3: foreignNetwork.web3 }) @@ -80,9 +63,6 @@ export const StateProvider = ({ children }: { children: ReactNode }) => { name: HOME_NETWORK_NAME, bridgeContract: homeBridge, blockConfirmations: homeBlockConfirmations, - validatorContract: homeValidatorContract, - requiredSignatures: homeRequiredSignatures, - validatorList: homeValidatorList, ...homeNetwork }, foreign: { diff --git a/alm/src/utils/contract.ts b/alm/src/utils/contract.ts index 278713d4c..f1e6bd98f 100644 --- a/alm/src/utils/contract.ts +++ b/alm/src/utils/contract.ts @@ -7,12 +7,46 @@ export const getRequiredBlockConfirmations = async (contract: Contract) => { export const getValidatorAddress = (contract: Contract) => contract.methods.validatorContract().call() -export const getRequiredSignatures = async (contract: Contract) => { - const requiredSignatures = await contract.methods.requiredSignatures().call() +export const getRequiredSignatures = async (contract: Contract, blockNumber: number) => { + const events = await contract.getPastEvents('RequiredSignaturesChanged', { + fromBlock: 0, + toBlock: blockNumber + }) + + // Use the value form last event before the transaction + const event = events[events.length - 1] + const { requiredSignatures } = event.returnValues return parseInt(requiredSignatures) } -export const getValidatorList = (contract: Contract) => contract.methods.validatorList().call() +export const getValidatorList = async (contract: Contract, blockNumber: number) => { + let currentList: string[] = await contract.methods.validatorList().call() + const [added, removed] = await Promise.all([ + contract.getPastEvents('ValidatorAdded', { + fromBlock: blockNumber + }), + contract.getPastEvents('ValidatorRemoved', { + fromBlock: blockNumber + }) + ]) + + // Ordered desc + const orderedEvents = [...added, ...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() From 2ca07e998ad6f1e100cb4cd3c1eb8b3a9e42abaa Mon Sep 17 00:00:00 2001 From: Gerardo Nardelli Date: Thu, 25 Jun 2020 18:18:54 -0300 Subject: [PATCH 07/13] Display Age field for validator signatures in ALM (#371) --- alm/package.json | 1 + alm/src/components/ExecutionConfirmation.tsx | 2 +- .../components/ValidatorsConfirmations.tsx | 23 ++- alm/src/config/constants.ts | 1 + alm/src/hooks/useMessageConfirmations.ts | 13 +- alm/src/hooks/useTransactionStatus.ts | 7 + alm/src/services/ValidatorsCache.ts | 12 ++ alm/src/utils/explorer.ts | 49 ++++- alm/src/utils/getConfirmationsForTx.ts | 179 ++++++++++++++---- yarn.lock | 5 + 10 files changed, 244 insertions(+), 48 deletions(-) diff --git a/alm/package.json b/alm/package.json index 8d45f626c..651193518 100644 --- a/alm/package.json +++ b/alm/package.json @@ -14,6 +14,7 @@ "@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", "customize-cra": "^1.0.0", "date-fns": "^2.14.0", "fast-memoize": "^2.5.2", diff --git a/alm/src/components/ExecutionConfirmation.tsx b/alm/src/components/ExecutionConfirmation.tsx index 5bc40ea5b..d2f5fee5f 100644 --- a/alm/src/components/ExecutionConfirmation.tsx +++ b/alm/src/components/ExecutionConfirmation.tsx @@ -57,7 +57,7 @@ export const ExecutionConfirmation = ({ executionData, isHome }: ExecutionConfir {formattedValidator ? formattedValidator : } {getExecutionStatusElement(executionData.status)} - + {executionData.timestamp > 0 ? formatTimestamp(executionData.timestamp) : ''} diff --git a/alm/src/components/ValidatorsConfirmations.tsx b/alm/src/components/ValidatorsConfirmations.tsx index fa53a2f27..df8d94def 100644 --- a/alm/src/components/ValidatorsConfirmations.tsx +++ b/alm/src/components/ValidatorsConfirmations.tsx @@ -1,11 +1,12 @@ import React from 'react' -import { formatTxHashExtended } from '../utils/networks' +import { formatTimestamp, formatTxHash, getExplorerTxUrl } from '../utils/networks' import { useWindowWidth } from '@react-hook/window-size' import { 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' const Thead = styled.thead` border-bottom: 2px solid #9e9e9e; @@ -49,7 +50,8 @@ export const ValidatorsConfirmations = ({ Validator - Confirmations + Status + Age @@ -57,10 +59,25 @@ export const ValidatorsConfirmations = ({ const filteredConfirmation = confirmations.filter(c => c.validator === validator) const confirmation = filteredConfirmation.length > 0 ? filteredConfirmation[0] : null const displayedStatus = confirmation && confirmation.status ? confirmation.status : '' + const explorerLink = confirmation && confirmation.txHash ? getExplorerTxUrl(confirmation.txHash, true) : '' + const elementIfNoTimestamp = + displayedStatus !== VALIDATOR_CONFIRMATION_STATUS.WAITING && + displayedStatus !== VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED ? ( + + ) : ( + '' + ) return ( - {windowWidth < 850 ? formatTxHashExtended(validator) : validator} + {windowWidth < 850 ? formatTxHash(validator) : validator} {getValidatorStatusElement(displayedStatus)} + + + {confirmation && confirmation.timestamp > 0 + ? formatTimestamp(confirmation.timestamp) + : elementIfNoTimestamp} + + ) })} diff --git a/alm/src/config/constants.ts b/alm/src/config/constants.ts index 5fe96bc9d..09fcb5c6f 100644 --- a/alm/src/config/constants.ts +++ b/alm/src/config/constants.ts @@ -23,6 +23,7 @@ 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-' diff --git a/alm/src/hooks/useMessageConfirmations.ts b/alm/src/hooks/useMessageConfirmations.ts index 6faaef2ad..b63457c60 100644 --- a/alm/src/hooks/useMessageConfirmations.ts +++ b/alm/src/hooks/useMessageConfirmations.ts @@ -21,7 +21,8 @@ import { getValidatorFailedTransactionsForMessage, getExecutionFailedTransactionForMessage, getValidatorPendingTransactionsForMessage, - getExecutionPendingTransactionsForMessage + getExecutionPendingTransactionsForMessage, + getValidatorSuccessTransactionsForMessage } from '../utils/explorer' export interface useMessageConfirmationsParams { @@ -33,11 +34,16 @@ export interface useMessageConfirmationsParams { validatorList: string[] } -export interface ConfirmationParam { +export interface BasicConfirmationParam { validator: string status: string } +export interface ConfirmationParam extends BasicConfirmationParam { + txHash: string + timestamp: number +} + export interface ExecutionData { status: string validator: string @@ -221,7 +227,8 @@ export const useMessageConfirmations = ({ getValidatorFailedTransactionsForMessage, setFailedConfirmations, getValidatorPendingTransactionsForMessage, - setPendingConfirmations + setPendingConfirmations, + getValidatorSuccessTransactionsForMessage ) return () => { diff --git a/alm/src/hooks/useTransactionStatus.ts b/alm/src/hooks/useTransactionStatus.ts index c6ad73e2a..4c20d5def 100644 --- a/alm/src/hooks/useTransactionStatus.ts +++ b/alm/src/hooks/useTransactionStatus.ts @@ -4,6 +4,7 @@ import { HOME_RPC_POLLING_INTERVAL, TRANSACTION_STATUS } from '../config/constan 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 }: { txHash: string; chainId: number }) => { const { home, foreign } = useStateProvider() @@ -14,6 +15,12 @@ export const useTransactionStatus = ({ txHash, chainId }: { txHash: string; chai 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( () => { const subscriptions: Array = [] diff --git a/alm/src/services/ValidatorsCache.ts b/alm/src/services/ValidatorsCache.ts index a3406943e..9708edf3a 100644 --- a/alm/src/services/ValidatorsCache.ts +++ b/alm/src/services/ValidatorsCache.ts @@ -1,8 +1,12 @@ +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) { @@ -12,6 +16,14 @@ class ValidatorsCache { 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() diff --git a/alm/src/utils/explorer.ts b/alm/src/utils/explorer.ts index 8d745c58e..8561a6ab5 100644 --- a/alm/src/utils/explorer.ts +++ b/alm/src/utils/explorer.ts @@ -154,6 +154,31 @@ export const getFailedTransactions = async ( return transactions.filter(t => t.isError !== '0') } +export const getSuccessTransactions = async ( + account: string, + to: string, + startTimestamp: number, + endTimestamp: number, + api: string, + fetchAccountTransactions: (args: AccountTransactionsParams) => Promise +): Promise => { + const transactions = await fetchAccountTransactions({ account, to, startTimestamp, endTimestamp, api }) + + return transactions.filter(t => t.isError === '0') +} + +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, @@ -170,12 +195,26 @@ export const getValidatorFailedTransactionsForMessage = async ({ fetchAccountTransactionsFromBlockscout ) - const messageDataValue = messageData.replace('0x', '') - return failedTransactions.filter( - t => - (t.input.includes(SUBMIT_SIGNATURE_HASH) || t.input.includes(EXECUTE_AFFIRMATION_HASH)) && - t.input.includes(messageDataValue) + return filterValidatorSignatureTransaction(failedTransactions, messageData) +} + +export const getValidatorSuccessTransactionsForMessage = async ({ + account, + to, + messageData, + startTimestamp, + endTimestamp +}: GetFailedTransactionParams): Promise => { + const transactions = await getSuccessTransactions( + account, + to, + startTimestamp, + endTimestamp, + HOME_EXPLORER_API, + fetchAccountTransactionsFromBlockscout ) + + return filterValidatorSignatureTransaction(transactions, messageData) } export const getExecutionFailedTransactionForMessage = async ({ diff --git a/alm/src/utils/getConfirmationsForTx.ts b/alm/src/utils/getConfirmationsForTx.ts index f570c6b06..1fa2d7c2e 100644 --- a/alm/src/utils/getConfirmationsForTx.ts +++ b/alm/src/utils/getConfirmationsForTx.ts @@ -3,6 +3,7 @@ import { Contract } from 'web3-eth-contract' import validatorsCache from '../services/ValidatorsCache' import { CACHE_KEY_FAILED, + CACHE_KEY_SUCCESS, HOME_RPC_POLLING_INTERVAL, ONE_DAY_TIMESTAMP, VALIDATOR_CONFIRMATION_STATUS @@ -13,14 +14,14 @@ import { APIPendingTransaction, GetPendingTransactionParams } from './explorer' -import { ConfirmationParam } from '../hooks/useMessageConfirmations' +import { BasicConfirmationParam, ConfirmationParam } from '../hooks/useMessageConfirmations' export const getValidatorConfirmation = ( web3: Web3, hashMsg: string, bridgeContract: Contract, confirmationContractMethod: Function -) => async (validator: string): Promise => { +) => async (validator: string): Promise => { const hashSenderMsg = web3.utils.soliditySha3Raw(validator, hashMsg) const signatureFromCache = validatorsCache.get(hashSenderMsg) @@ -45,20 +46,65 @@ export const getValidatorConfirmation = ( } } +export const getValidatorSuccessTransaction = ( + bridgeContract: Contract, + messageData: string, + timestamp: number, + getSuccessTransactions: (args: GetFailedTransactionParams) => Promise +) => async (validatorData: BasicConfirmationParam): Promise => { + const { validator } = validatorData + const validatorCacheKey = `${CACHE_KEY_SUCCESS}${validatorData.validator}` + const fromCache = validatorsCache.getData(validatorCacheKey) + + if (fromCache && fromCache.txHash) { + return fromCache + } + + const transactions = await getSuccessTransactions({ + account: validatorData.validator, + to: bridgeContract.options.address, + messageData, + startTimestamp: timestamp, + endTimestamp: timestamp + ONE_DAY_TIMESTAMP + }) + + 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 = ( bridgeContract: Contract, messageData: string, timestamp: number, getFailedTransactions: (args: GetFailedTransactionParams) => Promise -) => async (validatorData: ConfirmationParam): Promise => { +) => async (validatorData: BasicConfirmationParam): Promise => { const validatorCacheKey = `${CACHE_KEY_FAILED}${validatorData.validator}` - const failedFromCache = validatorsCache.get(validatorCacheKey) + const failedFromCache = validatorsCache.getData(validatorCacheKey) - if (failedFromCache) { - return { - validator: validatorData.validator, - status: VALIDATOR_CONFIRMATION_STATUS.FAILED - } + if (failedFromCache && failedFromCache.txHash) { + return failedFromCache } const failedTransactions = await getFailedTransactions({ @@ -71,14 +117,27 @@ export const getValidatorFailedTransaction = ( const newStatus = failedTransactions.length > 0 ? VALIDATOR_CONFIRMATION_STATUS.FAILED : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED + let txHashTimestamp = 0 + let txHash = '' // If validator signature failed, we cache the result to avoid doing future requests for a result that won't change if (failedTransactions.length > 0) { - validatorsCache.set(validatorCacheKey, true) + const failedTx = failedTransactions[0] + txHashTimestamp = parseInt(failedTx.timeStamp) + txHash = failedTx.hash + + validatorsCache.setData(validatorCacheKey, { + validator: validatorData.validator, + status: newStatus, + txHash, + timestamp: txHashTimestamp + }) } return { validator: validatorData.validator, - status: newStatus + status: newStatus, + txHash, + timestamp: txHashTimestamp } } @@ -86,7 +145,7 @@ export const getValidatorPendingTransaction = ( bridgeContract: Contract, messageData: string, getPendingTransactions: (args: GetPendingTransactionParams) => Promise -) => async (validatorData: ConfirmationParam): Promise => { +) => async (validatorData: BasicConfirmationParam): Promise => { const failedTransactions = await getPendingTransactions({ account: validatorData.validator, to: bridgeContract.options.address, @@ -96,9 +155,20 @@ export const getValidatorPendingTransaction = ( 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 + status: newStatus, + txHash, + timestamp } } @@ -117,9 +187,14 @@ export const getConfirmationsForTx = async ( getFailedTransactions: (args: GetFailedTransactionParams) => Promise, setFailedConfirmations: Function, getPendingTransactions: (args: GetPendingTransactionParams) => Promise, - setPendingConfirmations: Function + setPendingConfirmations: Function, + getSuccessTransactions: (args: GetFailedTransactionParams) => Promise ) => { if (!web3 || !validatorList || !bridgeContract || !waitingBlocksResolved) return + + // If all the information was not collected, then it should retry + let shouldRetry = false + const hashMsg = web3.utils.soliditySha3Raw(messageData) let validatorConfirmations = await Promise.all( validatorList.map(getValidatorConfirmation(web3, hashMsg, bridgeContract, confirmationContractMethod)) @@ -174,28 +249,7 @@ export const getConfirmationsForTx = async ( ) if (missingConfirmations.length > 0) { - const timeoutId = setTimeout( - () => - getConfirmationsForTx( - messageData, - web3, - validatorList, - bridgeContract, - confirmationContractMethod, - setResult, - requiredSignatures, - setSignatureCollected, - waitingBlocksResolved, - subscriptions, - timestamp, - getFailedTransactions, - setFailedConfirmations, - getPendingTransactions, - setPendingConfirmations - ), - HOME_RPC_POLLING_INTERVAL - ) - subscriptions.push(timeoutId) + shouldRetry = true } } else { // If signatures collected, it should set other signatures as not required @@ -207,5 +261,58 @@ export const getConfirmationsForTx = async ( validatorConfirmations = [...successConfirmations, ...notRequiredConfirmations] setSignatureCollected(true) } + + // Set confirmations to update UI and continue requesting the transactions for the signatures setResult(validatorConfirmations) + + // get transactions from success signatures + const successConfirmationWithData = await Promise.all( + validatorConfirmations + .filter(c => c.status === VALIDATOR_CONFIRMATION_STATUS.SUCCESS) + .map(getValidatorSuccessTransaction(bridgeContract, messageData, timestamp, getSuccessTransactions)) + ) + + const successConfirmationWithTxFound = successConfirmationWithData.filter(v => v.txHash !== '') + + const updatedValidatorConfirmations = [...validatorConfirmations] + + if (successConfirmationWithTxFound.length > 0) { + successConfirmationWithTxFound.forEach(validatorData => { + const index = updatedValidatorConfirmations.findIndex(e => e.validator === validatorData.validator) + updatedValidatorConfirmations[index] = validatorData + }) + } + + setResult(updatedValidatorConfirmations) + + // Retry if not all transaction were found for validator confirmations + if (successConfirmationWithTxFound.length < successConfirmationWithData.length) { + shouldRetry = true + } + + if (shouldRetry) { + const timeoutId = setTimeout( + () => + getConfirmationsForTx( + messageData, + web3, + validatorList, + bridgeContract, + confirmationContractMethod, + setResult, + requiredSignatures, + setSignatureCollected, + waitingBlocksResolved, + subscriptions, + timestamp, + getFailedTransactions, + setFailedConfirmations, + getPendingTransactions, + setPendingConfirmations, + getSuccessTransactions + ), + HOME_RPC_POLLING_INTERVAL + ) + subscriptions.push(timeoutId) + } } diff --git a/yarn.lock b/yarn.lock index 20ab80397..4edd5fa56 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3220,6 +3220,11 @@ lodash.unescape "4.0.1" semver "5.5.0" +"@use-it/interval@^0.1.3": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@use-it/interval/-/interval-0.1.3.tgz#5d1096b2295d7a5dda8e8022f3abb5f9d9ef27f8" + integrity sha512-chshdtDZTFoWA9aszBz1Cc04Ca9NBD2JTi/GMjdJ+HGm4q7Vy1v71+2mm22r7Kfb2nYW+lTRsPcEHdB/VFVHsQ== + "@web3-js/scrypt-shim@^0.1.0": version "0.1.0" resolved "https://registry.yarnpkg.com/@web3-js/scrypt-shim/-/scrypt-shim-0.1.0.tgz#0bf7529ab6788311d3e07586f7d89107c3bea2cc" From 4a727dc15954532a9689017b1be2279b8470f537 Mon Sep 17 00:00:00 2001 From: Gerardo Nardelli Date: Fri, 26 Jun 2020 12:47:45 -0300 Subject: [PATCH 08/13] Add ALM new styles (#373) --- .eslintignore | 1 + alm/public/index.html | 1 + alm/src/App.tsx | 5 +- alm/src/abis/BridgeValidators.ts | 319 +++++++ alm/src/abis/ForeignAMB.ts | 589 +++++++++++++ alm/src/abis/HomeAMB.ts | 777 ++++++++++++++++++ alm/src/abis/index.ts | 3 + alm/src/components/ConfirmationsContainer.tsx | 2 +- alm/src/components/Form.tsx | 24 +- alm/src/components/MainPage.tsx | 76 +- alm/src/components/MessageSelector.tsx | 2 +- alm/src/components/StatusContainer.tsx | 40 +- alm/src/components/commons/Button.tsx | 5 + alm/src/components/commons/ExplorerTxLink.tsx | 1 + alm/src/components/commons/LeftArrow.tsx | 2 +- alm/src/components/commons/Loading.tsx | 308 +++---- alm/src/hooks/useBridgeContracts.ts | 2 +- alm/src/hooks/useMessageConfirmations.ts | 2 +- alm/src/hooks/useValidatorContract.ts | 2 +- alm/src/index.tsx | 4 +- alm/src/themes/Dark.tsx | 1 + alm/src/themes/GlobalStyle.tsx | 3 +- alm/src/themes/Light.ts | 22 + alm/src/utils/getConfirmationsForTx.ts | 3 +- alm/src/utils/web3.ts | 2 +- 25 files changed, 1990 insertions(+), 206 deletions(-) create mode 100644 alm/src/abis/BridgeValidators.ts create mode 100644 alm/src/abis/ForeignAMB.ts create mode 100644 alm/src/abis/HomeAMB.ts create mode 100644 alm/src/abis/index.ts create mode 100644 alm/src/themes/Light.ts diff --git a/.eslintignore b/.eslintignore index 08d0d3d48..2fc7af035 100644 --- a/.eslintignore +++ b/.eslintignore @@ -3,3 +3,4 @@ submodules coverage lib dist +build diff --git a/alm/public/index.html b/alm/public/index.html index 89efacd59..36f04479a 100644 --- a/alm/public/index.html +++ b/alm/public/index.html @@ -25,6 +25,7 @@ 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`. --> + AMB Live Monitoring diff --git a/alm/src/App.tsx b/alm/src/App.tsx index 524c6e653..3ab3eaf66 100644 --- a/alm/src/App.tsx +++ b/alm/src/App.tsx @@ -1,11 +1,14 @@ import React from 'react' import { BrowserRouter } from 'react-router-dom' import { MainPage } from './components/MainPage' +import { StateProvider } from './state/StateProvider' function App() { return ( - + + + ) } diff --git a/alm/src/abis/BridgeValidators.ts b/alm/src/abis/BridgeValidators.ts new file mode 100644 index 000000000..10fa0b52e --- /dev/null +++ b/alm/src/abis/BridgeValidators.ts @@ -0,0 +1,319 @@ +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 diff --git a/alm/src/abis/ForeignAMB.ts b/alm/src/abis/ForeignAMB.ts new file mode 100644 index 000000000..d49572e69 --- /dev/null +++ b/alm/src/abis/ForeignAMB.ts @@ -0,0 +1,589 @@ +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: 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 diff --git a/alm/src/abis/HomeAMB.ts b/alm/src/abis/HomeAMB.ts new file mode 100644 index 000000000..8236299c6 --- /dev/null +++ b/alm/src/abis/HomeAMB.ts @@ -0,0 +1,777 @@ +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 diff --git a/alm/src/abis/index.ts b/alm/src/abis/index.ts new file mode 100644 index 000000000..b49359630 --- /dev/null +++ b/alm/src/abis/index.ts @@ -0,0 +1,3 @@ +export { default as HOME_AMB_ABI } from './HomeAMB' +export { default as FOREIGN_AMB_ABI } from './ForeignAMB' +export { default as BRIDGE_VALIDATORS_ABI } from './BridgeValidators' diff --git a/alm/src/components/ConfirmationsContainer.tsx b/alm/src/components/ConfirmationsContainer.tsx index 94272c2b8..ead23412d 100644 --- a/alm/src/components/ConfirmationsContainer.tsx +++ b/alm/src/components/ConfirmationsContainer.tsx @@ -23,7 +23,7 @@ const StatusResultLabel = styled.label` ` const StyledConfirmationContainer = styled.div` - background-color: var(--color-primary); + background-color: var(--bg-color); padding: 10px; border-radius: 4px; ` diff --git a/alm/src/components/Form.tsx b/alm/src/components/Form.tsx index 9576c1c3f..222dd4477 100644 --- a/alm/src/components/Form.tsx +++ b/alm/src/components/Form.tsx @@ -12,27 +12,39 @@ const LabelText = styled.label` ` const Input = styled.input` - background-color: var(--color-primary); + 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 }: FormSubmitParams) => void }) => { +export const Form = ({ + onSubmit, + lastUsedChain +}: { + onSubmit: ({ chainId, txHash }: FormSubmitParams) => void + lastUsedChain: number +}) => { const { home, foreign, loading } = useStateProvider() const { chainId: paramChainId, txHash: paramTxHash } = useParams() - const [chainId, setChainId] = useState(0) + const [chainId, setChainId] = useState(lastUsedChain) const [txHash, setTxHash] = useState('') useEffect( () => { if (!paramChainId) { - setChainId(foreign.chainId) + setChainId(lastUsedChain > 0 ? lastUsedChain : foreign.chainId) } else { setChainId(parseInt(paramChainId)) setTxHash(paramTxHash) } }, - [foreign.chainId, paramChainId, paramTxHash] + [foreign.chainId, paramChainId, paramTxHash, lastUsedChain] ) const formSubmit = (e: FormEvent) => { @@ -55,7 +67,7 @@ export const Form = ({ onSubmit }: { onSubmit: ({ chainId, txHash }: FormSubmitP />
-
diff --git a/alm/src/components/MainPage.tsx b/alm/src/components/MainPage.tsx index 9eeec8361..61867b6e9 100644 --- a/alm/src/components/MainPage.tsx +++ b/alm/src/components/MainPage.tsx @@ -1,9 +1,9 @@ -import React from 'react' +import React, { useState } from 'react' import styled from 'styled-components' -import { Route, useHistory, Link } from 'react-router-dom' +import { Route, useHistory } from 'react-router-dom' import { Form } from './Form' import { StatusContainer } from './StatusContainer' -import { StateProvider } from '../state/StateProvider' +import { useStateProvider } from '../state/StateProvider' const StyledMainPage = styled.div` text-align: center; @@ -11,15 +11,24 @@ const StyledMainPage = styled.div` ` const Header = styled.header` + background-color: #001529; + color: #ffffff; + margin-bottom: 50px; +` + +const HeaderContainer = styled.header` display: flex; - flex-direction: column; + flex-direction: row; align-items: center; - justify-content: center; - font-size: calc(10px + 2vmin); -` + justify-content: space-between; + font-size: 16px; + height: 64px; + line-height: 64px; + padding: 0 50px; -const Title = styled.p` - color: var(--font-color); + @media (max-width: 600px) { + padding: 0 20px; + } ` export interface FormSubmitParams { @@ -29,23 +38,46 @@ export interface FormSubmitParams { export const MainPage = () => { const history = useHistory() + const { home, foreign } = useStateProvider() + const [selectedChainId, setSelectedChainId] = useState(0) + const [networkName, setNetworkName] = useState('') + + const setNetworkData = (chainId: number) => { + const network = chainId === home.chainId ? home.name : foreign.name + + setNetworkName(network) + setSelectedChainId(chainId) + } + const onFormSubmit = ({ chainId, txHash }: FormSubmitParams) => { + setNetworkData(chainId) + history.push(`/${chainId}/${txHash}`) } + const resetNetworkHeader = () => { + setNetworkName('') + } + + const setNetworkFromParams = (chainId: number) => { + setNetworkData(chainId) + } + return ( - - -
- - AMB Live Monitoring - -
-
- } /> - } /> -
-
-
+ +
+ + AMB Live Monitoring + {networkName} + +
+
+ } /> + } + /> +
+
) } diff --git a/alm/src/components/MessageSelector.tsx b/alm/src/components/MessageSelector.tsx index 32aaa3eca..7332e2b25 100644 --- a/alm/src/components/MessageSelector.tsx +++ b/alm/src/components/MessageSelector.tsx @@ -38,7 +38,7 @@ export const MessageSelector = ({ messages, onMessageSelected }: MessageSelector ))}
-
diff --git a/alm/src/components/StatusContainer.tsx b/alm/src/components/StatusContainer.tsx index a8d0d000c..99e1d8a28 100644 --- a/alm/src/components/StatusContainer.tsx +++ b/alm/src/components/StatusContainer.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, { useEffect } from 'react' import { Link, useHistory, useParams } from 'react-router-dom' import { useTransactionStatus } from '../hooks/useTransactionStatus' import { formatTxHash, getExplorerTxUrl, getTransactionStatusDescription, validTxHash } from '../utils/networks' @@ -12,9 +12,12 @@ import { LeftArrow } from './commons/LeftArrow' import styled from 'styled-components' const BackButton = styled.button` - color: var(--font-color); + color: var(--button-color); border-color: var(--font-color); margin-top: 10px; + &:focus { + outline: var(--button-color); + } ` const BackLabel = styled.label` @@ -22,7 +25,12 @@ const BackLabel = styled.label` cursor: pointer; ` -export const StatusContainer = () => { +export interface StatusContainerParam { + onBackToMain: () => void + setNetworkFromParams: (chainId: number) => void +} + +export const StatusContainer = ({ onBackToMain, setNetworkFromParams }: StatusContainerParam) => { const { home, foreign } = useStateProvider() const history = useHistory() const { chainId, txHash, messageIdParam } = useParams() @@ -36,6 +44,15 @@ export const StatusContainer = () => { 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 (
@@ -75,15 +92,12 @@ export const StatusContainer = () => { {status && (

The request{' '} - - {displayExplorerLink && ( - - {formattedMessageId} - - )} - {!displayExplorerLink && } - {' '} - {displayedDescription} + {displayExplorerLink && ( + + {formattedMessageId} + + )} + {!displayExplorerLink && } {displayedDescription}

)} {displayMessageSelector && } @@ -92,7 +106,7 @@ export const StatusContainer = () => { )}
- + Search another transaction diff --git a/alm/src/components/commons/Button.tsx b/alm/src/components/commons/Button.tsx index faeecbb6f..248590e93 100644 --- a/alm/src/components/commons/Button.tsx +++ b/alm/src/components/commons/Button.tsx @@ -2,4 +2,9 @@ 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); + } ` diff --git a/alm/src/components/commons/ExplorerTxLink.tsx b/alm/src/components/commons/ExplorerTxLink.tsx index fd23a405d..6c69383dc 100644 --- a/alm/src/components/commons/ExplorerTxLink.tsx +++ b/alm/src/components/commons/ExplorerTxLink.tsx @@ -3,4 +3,5 @@ import styled from 'styled-components' export const ExplorerTxLink = styled.a` color: var(--link-color); text-decoration: underline; + font-weight: bold; ` diff --git a/alm/src/components/commons/LeftArrow.tsx b/alm/src/components/commons/LeftArrow.tsx index 1700297a7..abef676c0 100644 --- a/alm/src/components/commons/LeftArrow.tsx +++ b/alm/src/components/commons/LeftArrow.tsx @@ -12,7 +12,7 @@ export const LeftArrow = () => { width="24" height="24" viewBox="0 0 24 24" - fill={themeContext.fontColor} + fill={themeContext.buttonColor} > diff --git a/alm/src/components/commons/Loading.tsx b/alm/src/components/commons/Loading.tsx index 8519ede21..7adfc6c9c 100644 --- a/alm/src/components/commons/Loading.tsx +++ b/alm/src/components/commons/Loading.tsx @@ -1,4 +1,5 @@ -import React from 'react' +import React, { useContext } from 'react' +import { ThemeContext } from 'styled-components' export interface LoadingParams { width?: string @@ -6,156 +7,159 @@ export interface LoadingParams { displayMessage?: boolean } -export const Loading = ({ width = '50px', height = '50px', displayMessage = true }: LoadingParams) => ( -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {displayMessage && } -
-) +export const Loading = ({ width = '50px', height = '50px', displayMessage = true }: LoadingParams) => { + const themeContext = useContext(ThemeContext) + return ( +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {displayMessage && } +
+ ) +} export const SimpleLoading = () => diff --git a/alm/src/hooks/useBridgeContracts.ts b/alm/src/hooks/useBridgeContracts.ts index e82edff19..02baeeea3 100644 --- a/alm/src/hooks/useBridgeContracts.ts +++ b/alm/src/hooks/useBridgeContracts.ts @@ -1,5 +1,5 @@ import { useEffect, useState } from 'react' -import { HOME_AMB_ABI, FOREIGN_AMB_ABI } from '../../../commons' +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' diff --git a/alm/src/hooks/useMessageConfirmations.ts b/alm/src/hooks/useMessageConfirmations.ts index b63457c60..299bb3876 100644 --- a/alm/src/hooks/useMessageConfirmations.ts +++ b/alm/src/hooks/useMessageConfirmations.ts @@ -200,7 +200,7 @@ export const useMessageConfirmations = ({ // To avoid making extra requests, this is only executed when validators finished waiting for blocks confirmations useEffect( () => { - if (!waitingBlocksResolved || !timestamp) return + if (!waitingBlocksResolved || !timestamp || !requiredSignatures) return const subscriptions: Array = [] diff --git a/alm/src/hooks/useValidatorContract.ts b/alm/src/hooks/useValidatorContract.ts index c48635741..07ea7cfb2 100644 --- a/alm/src/hooks/useValidatorContract.ts +++ b/alm/src/hooks/useValidatorContract.ts @@ -2,7 +2,7 @@ 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 '../../../commons' +import { BRIDGE_VALIDATORS_ABI } from '../abis' import { useStateProvider } from '../state/StateProvider' import { TransactionReceipt } from 'web3-eth' diff --git a/alm/src/index.tsx b/alm/src/index.tsx index fdfebe3fa..11563b055 100644 --- a/alm/src/index.tsx +++ b/alm/src/index.tsx @@ -3,11 +3,11 @@ import ReactDOM from 'react-dom' import { ThemeProvider } from 'styled-components' import { GlobalStyle } from './themes/GlobalStyle' import App from './App' -import Dark from './themes/Dark' +import Light from './themes/Light' ReactDOM.render( - + diff --git a/alm/src/themes/Dark.tsx b/alm/src/themes/Dark.tsx index 4bb635313..425e26583 100644 --- a/alm/src/themes/Dark.tsx +++ b/alm/src/themes/Dark.tsx @@ -1,6 +1,7 @@ const theme = { backgroundColor: '#121212', fontColor: '#f5f5f5', + buttonColor: '#f5f5f5', colorPrimary: '#272727', colorGrey: '#272727', colorLightGrey: '#272727', diff --git a/alm/src/themes/GlobalStyle.tsx b/alm/src/themes/GlobalStyle.tsx index 04485df09..a8e62da9a 100644 --- a/alm/src/themes/GlobalStyle.tsx +++ b/alm/src/themes/GlobalStyle.tsx @@ -1,6 +1,6 @@ import { createGlobalStyle } from 'styled-components' -import theme from './Dark' +import theme from './Light' type ThemeType = typeof theme @@ -17,6 +17,7 @@ export const GlobalStyle = createGlobalStyle<{ theme: ThemeType }>` :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}; diff --git a/alm/src/themes/Light.ts b/alm/src/themes/Light.ts new file mode 100644 index 000000000..0c972e841 --- /dev/null +++ b/alm/src/themes/Light.ts @@ -0,0 +1,22 @@ +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)' + } +} +export default theme diff --git a/alm/src/utils/getConfirmationsForTx.ts b/alm/src/utils/getConfirmationsForTx.ts index 1fa2d7c2e..58e1ce626 100644 --- a/alm/src/utils/getConfirmationsForTx.ts +++ b/alm/src/utils/getConfirmationsForTx.ts @@ -190,11 +190,10 @@ export const getConfirmationsForTx = async ( setPendingConfirmations: Function, getSuccessTransactions: (args: GetFailedTransactionParams) => Promise ) => { - if (!web3 || !validatorList || !bridgeContract || !waitingBlocksResolved) return + if (!web3 || !validatorList || !validatorList.length || !bridgeContract || !waitingBlocksResolved) return // If all the information was not collected, then it should retry let shouldRetry = false - const hashMsg = web3.utils.soliditySha3Raw(messageData) let validatorConfirmations = await Promise.all( validatorList.map(getValidatorConfirmation(web3, hashMsg, bridgeContract, confirmationContractMethod)) diff --git a/alm/src/utils/web3.ts b/alm/src/utils/web3.ts index db91d59ec..0ed56a0a0 100644 --- a/alm/src/utils/web3.ts +++ b/alm/src/utils/web3.ts @@ -4,7 +4,7 @@ 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 '../../../commons' +import { HOME_AMB_ABI, FOREIGN_AMB_ABI } from '../abis' export interface MessageObject { id: string From 691e4294aeac84f329dc37196f8c7cdde061c49c Mon Sep 17 00:00:00 2001 From: Gerardo Nardelli Date: Mon, 29 Jun 2020 09:39:31 -0300 Subject: [PATCH 09/13] Automatically detect network by searching the transaction in both chains (#377) --- alm/src/components/Form.tsx | 67 ++++--------------- alm/src/components/MainPage.tsx | 19 ++++-- .../components/NetworkTransactionSelector.tsx | 47 +++++++++++++ alm/src/components/StatusContainer.tsx | 7 +- alm/src/components/TransactionSelector.tsx | 47 +++++++++++++ alm/src/config/constants.ts | 4 +- alm/src/hooks/useTransactionFinder.ts | 56 ++++++++++++++++ alm/src/hooks/useTransactionStatus.ts | 31 ++++++++- alm/src/utils/getConfirmationsForTx.ts | 4 +- alm/src/utils/getFinalizationEvent.ts | 2 +- 10 files changed, 216 insertions(+), 68 deletions(-) create mode 100644 alm/src/components/NetworkTransactionSelector.tsx create mode 100644 alm/src/components/TransactionSelector.tsx create mode 100644 alm/src/hooks/useTransactionFinder.ts diff --git a/alm/src/components/Form.tsx b/alm/src/components/Form.tsx index 222dd4477..c438915b7 100644 --- a/alm/src/components/Form.tsx +++ b/alm/src/components/Form.tsx @@ -1,10 +1,9 @@ -import React, { useState, FormEvent, useEffect } from 'react' +import React, { useState, FormEvent } from 'react' import styled from 'styled-components' import { FormSubmitParams } from './MainPage' -import { useStateProvider } from '../state/StateProvider' -import { useParams } from 'react-router-dom' import { Button } from './commons/Button' -import { RadioButtonLabel, RadioButtonContainer } from './commons/RadioButton' +import { TransactionSelector } from './TransactionSelector' +import { TransactionReceipt } from 'web3-eth' const LabelText = styled.label` line-height: 36px; @@ -23,33 +22,21 @@ const Input = styled.input` } ` -export const Form = ({ - onSubmit, - lastUsedChain -}: { - onSubmit: ({ chainId, txHash }: FormSubmitParams) => void - lastUsedChain: number -}) => { - const { home, foreign, loading } = useStateProvider() - const { chainId: paramChainId, txHash: paramTxHash } = useParams() - const [chainId, setChainId] = useState(lastUsedChain) +export const Form = ({ onSubmit }: { onSubmit: ({ chainId, txHash, receipt }: FormSubmitParams) => void }) => { const [txHash, setTxHash] = useState('') - - useEffect( - () => { - if (!paramChainId) { - setChainId(lastUsedChain > 0 ? lastUsedChain : foreign.chainId) - } else { - setChainId(parseInt(paramChainId)) - setTxHash(paramTxHash) - } - }, - [foreign.chainId, paramChainId, paramTxHash, lastUsedChain] - ) + const [searchTx, setSearchTx] = useState(false) const formSubmit = (e: FormEvent) => { e.preventDefault() - onSubmit({ chainId, txHash }) + setSearchTx(true) + } + + const onSelected = (chainId: number, receipt: TransactionReceipt) => { + onSubmit({ chainId, txHash, receipt }) + } + + if (searchTx) { + return } return ( @@ -72,32 +59,6 @@ export const Form = ({
- {!loading && ( -
- setChainId(foreign.chainId)}> - setChainId(foreign.chainId)} - /> - {foreign.name} - - setChainId(home.chainId)}> - setChainId(home.chainId)} - /> - {home.name} - -
- )} ) } diff --git a/alm/src/components/MainPage.tsx b/alm/src/components/MainPage.tsx index 61867b6e9..d84a85dc3 100644 --- a/alm/src/components/MainPage.tsx +++ b/alm/src/components/MainPage.tsx @@ -4,6 +4,7 @@ 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' const StyledMainPage = styled.div` text-align: center; @@ -34,24 +35,24 @@ const HeaderContainer = styled.header` export interface FormSubmitParams { chainId: number txHash: string + receipt: TransactionReceipt } export const MainPage = () => { const history = useHistory() const { home, foreign } = useStateProvider() - const [selectedChainId, setSelectedChainId] = useState(0) const [networkName, setNetworkName] = useState('') + const [receipt, setReceipt] = useState>(null) const setNetworkData = (chainId: number) => { const network = chainId === home.chainId ? home.name : foreign.name setNetworkName(network) - setSelectedChainId(chainId) } - const onFormSubmit = ({ chainId, txHash }: FormSubmitParams) => { + const onFormSubmit = ({ chainId, txHash, receipt }: FormSubmitParams) => { setNetworkData(chainId) - + setReceipt(receipt) history.push(`/${chainId}/${txHash}`) } @@ -72,10 +73,16 @@ export const MainPage = () => {
- } /> + } /> } + children={ + + } />
diff --git a/alm/src/components/NetworkTransactionSelector.tsx b/alm/src/components/NetworkTransactionSelector.tsx new file mode 100644 index 000000000..5c4fdb9bc --- /dev/null +++ b/alm/src/components/NetworkTransactionSelector.tsx @@ -0,0 +1,47 @@ +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 ( +
+

The transaction was found in both networks, please select one:

+
+
+ {networks.map((network, i) => ( + setChainId(network.chainId)} + > + setChainId(network.chainId)} + /> + {network.name} + + ))} +
+
+ +
+
+
+ ) +} diff --git a/alm/src/components/StatusContainer.tsx b/alm/src/components/StatusContainer.tsx index 99e1d8a28..c2a4b4842 100644 --- a/alm/src/components/StatusContainer.tsx +++ b/alm/src/components/StatusContainer.tsx @@ -10,6 +10,7 @@ import { ExplorerTxLink } from './commons/ExplorerTxLink' import { ConfirmationsContainer } from './ConfirmationsContainer' import { LeftArrow } from './commons/LeftArrow' import styled from 'styled-components' +import { TransactionReceipt } from 'web3-eth' const BackButton = styled.button` color: var(--button-color); @@ -28,9 +29,10 @@ const BackLabel = styled.label` export interface StatusContainerParam { onBackToMain: () => void setNetworkFromParams: (chainId: number) => void + receiptParam: Maybe } -export const StatusContainer = ({ onBackToMain, setNetworkFromParams }: StatusContainerParam) => { +export const StatusContainer = ({ onBackToMain, setNetworkFromParams, receiptParam }: StatusContainerParam) => { const { home, foreign } = useStateProvider() const history = useHistory() const { chainId, txHash, messageIdParam } = useParams() @@ -39,7 +41,8 @@ export const StatusContainer = ({ onBackToMain, setNetworkFromParams }: StatusCo const { messages, receipt, status, description, timestamp, loading } = useTransactionStatus({ txHash: validParameters ? txHash : '', - chainId: validParameters ? parseInt(chainId) : 0 + chainId: validParameters ? parseInt(chainId) : 0, + receiptParam }) const selectedMessageId = messageIdParam === undefined || messages[messageIdParam] === undefined ? -1 : messageIdParam diff --git a/alm/src/components/TransactionSelector.tsx b/alm/src/components/TransactionSelector.tsx new file mode 100644 index 000000000..846ff3c4a --- /dev/null +++ b/alm/src/components/TransactionSelector.tsx @@ -0,0 +1,47 @@ +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' + +export const TransactionSelector = ({ + txHash, + onSelected +}: { + txHash: string + onSelected: (chainId: number, receipt: TransactionReceipt) => 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 + } + + return +} diff --git a/alm/src/config/constants.ts b/alm/src/config/constants.ts index 09fcb5c6f..5543c5370 100644 --- a/alm/src/config/constants.ts +++ b/alm/src/config/constants.ts @@ -32,7 +32,9 @@ export const TRANSACTION_STATUS = { SUCCESS_ONE_MESSAGE: 'SUCCESS_ONE_MESSAGE', SUCCESS_NO_MESSAGES: 'SUCCESS_NO_MESSAGES', FAILED: 'FAILED', - NOT_FOUND: 'NOT_FOUND' + FOUND: 'FOUND', + NOT_FOUND: 'NOT_FOUND', + UNDEFINED: 'UNDEFINED' } export const CONFIRMATIONS_STATUS = { diff --git a/alm/src/hooks/useTransactionFinder.ts b/alm/src/hooks/useTransactionFinder.ts new file mode 100644 index 000000000..80611d623 --- /dev/null +++ b/alm/src/hooks/useTransactionFinder.ts @@ -0,0 +1,56 @@ +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 }) => { + const [status, setStatus] = useState(TRANSACTION_STATUS.UNDEFINED) + const [receipt, setReceipt] = useState>(null) + + useEffect( + () => { + if (!txHash || !web3) return + + const subscriptions: number[] = [] + + const unsubscribe = () => { + subscriptions.forEach(s => { + clearTimeout(s) + }) + } + + const getReceipt = async ( + web3: Web3, + txHash: string, + setReceipt: Function, + setStatus: Function, + subscriptions: number[] + ) => { + const txReceipt = await web3.eth.getTransactionReceipt(txHash) + setReceipt(txReceipt) + + if (!txReceipt) { + setStatus(TRANSACTION_STATUS.NOT_FOUND) + const timeoutId = setTimeout( + () => getReceipt(web3, txHash, setReceipt, setStatus, subscriptions), + HOME_RPC_POLLING_INTERVAL + ) + subscriptions.push(timeoutId) + } else { + setStatus(TRANSACTION_STATUS.FOUND) + } + } + + getReceipt(web3, txHash, setReceipt, setStatus, subscriptions) + return () => { + unsubscribe() + } + }, + [txHash, web3] + ) + + return { + status, + receipt + } +} diff --git a/alm/src/hooks/useTransactionStatus.ts b/alm/src/hooks/useTransactionStatus.ts index 4c20d5def..44763f541 100644 --- a/alm/src/hooks/useTransactionStatus.ts +++ b/alm/src/hooks/useTransactionStatus.ts @@ -6,7 +6,15 @@ import { useStateProvider } from '../state/StateProvider' import { getHomeMessagesFromReceipt, getForeignMessagesFromReceipt, MessageObject, getBlock } from '../utils/web3' import useInterval from '@use-it/interval' -export const useTransactionStatus = ({ txHash, chainId }: { txHash: string; chainId: number }) => { +export const useTransactionStatus = ({ + txHash, + chainId, + receiptParam +}: { + txHash: string + chainId: number + receiptParam: Maybe +}) => { const { home, foreign } = useStateProvider() const [messages, setMessages] = useState>([]) const [status, setStatus] = useState('') @@ -37,7 +45,14 @@ export const useTransactionStatus = ({ txHash, chainId }: { txHash: string; chai const isHome = chainId === home.chainId const web3 = isHome ? home.web3 : foreign.web3 - const txReceipt = await web3.eth.getTransactionReceipt(txHash) + let txReceipt + + if (receiptParam) { + txReceipt = receiptParam + } else { + txReceipt = await web3.eth.getTransactionReceipt(txHash) + } + setReceipt(txReceipt) if (!txReceipt) { @@ -92,7 +107,17 @@ export const useTransactionStatus = ({ txHash, chainId }: { txHash: string; chai unsubscribe() } }, - [txHash, chainId, home.chainId, foreign.chainId, home.web3, foreign.web3, home.bridgeAddress, foreign.bridgeAddress] + [ + txHash, + chainId, + home.chainId, + foreign.chainId, + home.web3, + foreign.web3, + home.bridgeAddress, + foreign.bridgeAddress, + receiptParam + ] ) return { diff --git a/alm/src/utils/getConfirmationsForTx.ts b/alm/src/utils/getConfirmationsForTx.ts index 58e1ce626..d9cc3d62f 100644 --- a/alm/src/utils/getConfirmationsForTx.ts +++ b/alm/src/utils/getConfirmationsForTx.ts @@ -53,7 +53,7 @@ export const getValidatorSuccessTransaction = ( getSuccessTransactions: (args: GetFailedTransactionParams) => Promise ) => async (validatorData: BasicConfirmationParam): Promise => { const { validator } = validatorData - const validatorCacheKey = `${CACHE_KEY_SUCCESS}${validatorData.validator}` + const validatorCacheKey = `${CACHE_KEY_SUCCESS}${validatorData.validator}-${messageData}` const fromCache = validatorsCache.getData(validatorCacheKey) if (fromCache && fromCache.txHash) { @@ -100,7 +100,7 @@ export const getValidatorFailedTransaction = ( timestamp: number, getFailedTransactions: (args: GetFailedTransactionParams) => Promise ) => async (validatorData: BasicConfirmationParam): Promise => { - const validatorCacheKey = `${CACHE_KEY_FAILED}${validatorData.validator}` + const validatorCacheKey = `${CACHE_KEY_FAILED}${validatorData.validator}-${messageData}` const failedFromCache = validatorsCache.getData(validatorCacheKey) if (failedFromCache && failedFromCache.txHash) { diff --git a/alm/src/utils/getFinalizationEvent.ts b/alm/src/utils/getFinalizationEvent.ts index 1407b2acc..72bca3e9e 100644 --- a/alm/src/utils/getFinalizationEvent.ts +++ b/alm/src/utils/getFinalizationEvent.ts @@ -80,7 +80,7 @@ export const getFinalizationEvent = async ( }) setPendingExecution(true) } else { - const validatorExecutionCacheKey = `${CACHE_KEY_EXECUTION_FAILED}${validator}` + const validatorExecutionCacheKey = `${CACHE_KEY_EXECUTION_FAILED}${validator}-${message.id}` const failedFromCache = validatorsCache.get(validatorExecutionCacheKey) if (!failedFromCache) { From ab814f831c73aa65dd866cef41338e805154b470 Mon Sep 17 00:00:00 2001 From: Gerardo Nardelli Date: Tue, 30 Jun 2020 17:41:19 -0300 Subject: [PATCH 10/13] Get required block confirmation at the moment of the transaction (#379) --- alm/src/components/ConfirmationsContainer.tsx | 5 ++- alm/src/hooks/useBlockConfirmations.ts | 38 +++++++++++++++++++ alm/src/hooks/useBridgeContracts.ts | 15 +------- alm/src/hooks/useMessageConfirmations.ts | 17 +++++---- alm/src/state/StateProvider.tsx | 11 ++---- alm/src/utils/contract.ts | 18 ++++++++- 6 files changed, 71 insertions(+), 33 deletions(-) create mode 100644 alm/src/hooks/useBlockConfirmations.ts diff --git a/alm/src/components/ConfirmationsContainer.tsx b/alm/src/components/ConfirmationsContainer.tsx index ead23412d..f4995c446 100644 --- a/alm/src/components/ConfirmationsContainer.tsx +++ b/alm/src/components/ConfirmationsContainer.tsx @@ -11,6 +11,7 @@ import { getConfirmationsStatusDescription } from '../utils/networks' import { useStateProvider } from '../state/StateProvider' import { ExecutionConfirmation } from './ExecutionConfirmation' import { useValidatorContract } from '../hooks/useValidatorContract' +import { useBlockConfirmations } from '../hooks/useBlockConfirmations' const StatusLabel = styled.label` font-weight: bold; @@ -45,13 +46,15 @@ export const ConfirmationsContainer = ({ message, receipt, fromHome, timestamp } foreign: { name: foreignName } } = useStateProvider() const { requiredSignatures, validatorList } = useValidatorContract({ fromHome, receipt }) + const { blockConfirmations } = useBlockConfirmations({ fromHome, receipt }) const { confirmations, status, executionData, signatureCollected } = useMessageConfirmations({ message, receipt, fromHome, timestamp, requiredSignatures, - validatorList + validatorList, + blockConfirmations }) return ( diff --git a/alm/src/hooks/useBlockConfirmations.ts b/alm/src/hooks/useBlockConfirmations.ts new file mode 100644 index 000000000..438e9e5af --- /dev/null +++ b/alm/src/hooks/useBlockConfirmations.ts @@ -0,0 +1,38 @@ +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' + +export interface UseBlockConfirmationsParams { + fromHome: boolean + receipt: Maybe +} + +export const useBlockConfirmations = ({ receipt, fromHome }: UseBlockConfirmationsParams) => { + const [blockConfirmations, setBlockConfirmations] = useState(0) + + const { home, foreign } = useStateProvider() + + const callRequireBlockConfirmations = async ( + contract: Contract, + receipt: TransactionReceipt, + setResult: Function + ) => { + const result = await getRequiredBlockConfirmations(contract, receipt.blockNumber) + setResult(result) + } + + useEffect( + () => { + const bridgeContract = fromHome ? home.bridgeContract : foreign.bridgeContract + if (!bridgeContract || !receipt) return + callRequireBlockConfirmations(bridgeContract, receipt, setBlockConfirmations) + }, + [home.bridgeContract, foreign.bridgeContract, receipt, fromHome] + ) + + return { + blockConfirmations + } +} diff --git a/alm/src/hooks/useBridgeContracts.ts b/alm/src/hooks/useBridgeContracts.ts index 02baeeea3..c27bd980d 100644 --- a/alm/src/hooks/useBridgeContracts.ts +++ b/alm/src/hooks/useBridgeContracts.ts @@ -3,7 +3,6 @@ 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' -import { getRequiredBlockConfirmations } from '../utils/contract' export interface useBridgeContractsParams { homeWeb3: Web3 @@ -13,20 +12,11 @@ export interface useBridgeContractsParams { export const useBridgeContracts = ({ homeWeb3, foreignWeb3 }: useBridgeContractsParams) => { const [homeBridge, setHomeBridge] = useState>(null) const [foreignBridge, setForeignBridge] = useState>(null) - const [homeBlockConfirmations, setHomeBlockConfirmations] = useState(0) - const [foreignBlockConfirmations, setForeignBlockConfirmations] = useState(0) - - const callRequireBlockConfirmations = async (contract: Maybe, setResult: Function) => { - if (!contract) return - const result = await getRequiredBlockConfirmations(contract) - setResult(result) - } useEffect( () => { if (!homeWeb3) return const homeContract = new homeWeb3.eth.Contract(HOME_AMB_ABI, HOME_BRIDGE_ADDRESS) - callRequireBlockConfirmations(homeContract, setHomeBlockConfirmations) setHomeBridge(homeContract) }, [homeWeb3] @@ -36,7 +26,6 @@ export const useBridgeContracts = ({ homeWeb3, foreignWeb3 }: useBridgeContracts () => { if (!foreignWeb3) return const foreignContract = new foreignWeb3.eth.Contract(FOREIGN_AMB_ABI, FOREIGN_BRIDGE_ADDRESS) - callRequireBlockConfirmations(foreignContract, setForeignBlockConfirmations) setForeignBridge(foreignContract) }, [foreignWeb3] @@ -44,8 +33,6 @@ export const useBridgeContracts = ({ homeWeb3, foreignWeb3 }: useBridgeContracts return { homeBridge, - foreignBridge, - homeBlockConfirmations, - foreignBlockConfirmations + foreignBridge } } diff --git a/alm/src/hooks/useMessageConfirmations.ts b/alm/src/hooks/useMessageConfirmations.ts index 299bb3876..9b27f010e 100644 --- a/alm/src/hooks/useMessageConfirmations.ts +++ b/alm/src/hooks/useMessageConfirmations.ts @@ -32,6 +32,7 @@ export interface useMessageConfirmationsParams { timestamp: number requiredSignatures: number validatorList: string[] + blockConfirmations: number } export interface BasicConfirmationParam { @@ -58,7 +59,8 @@ export const useMessageConfirmations = ({ fromHome, timestamp, requiredSignatures, - validatorList + validatorList, + blockConfirmations }: useMessageConfirmationsParams) => { const { home, foreign } = useStateProvider() const [confirmations, setConfirmations] = useState>([]) @@ -84,7 +86,7 @@ export const useMessageConfirmations = ({ // Check if the validators are waiting for block confirmations to verify the message useEffect( () => { - if (!receipt) return + if (!receipt || !blockConfirmations) return const subscriptions: Array = [] @@ -99,8 +101,7 @@ export const useMessageConfirmations = ({ const web3 = fromHome ? home.web3 : foreign.web3 blockProvider.start(web3) - const requiredBlockConfirmations = fromHome ? home.blockConfirmations : foreign.blockConfirmations - const targetBlock = receipt.blockNumber + requiredBlockConfirmations + const targetBlock = receipt.blockNumber + blockConfirmations checkSignaturesWaitingForBLocks( targetBlock, @@ -118,7 +119,7 @@ export const useMessageConfirmations = ({ blockProvider.stop() } }, - [foreign.blockConfirmations, foreign.web3, fromHome, home.blockConfirmations, validatorList, home.web3, receipt] + [blockConfirmations, foreign.web3, fromHome, validatorList, home.web3, receipt] ) // The collected signature event is only fetched once the signatures are collected on tx from home to foreign, to calculate if @@ -164,7 +165,7 @@ export const useMessageConfirmations = ({ // This is executed if the message is in Home to Foreign direction only useEffect( () => { - if (!fromHome || !home.web3 || !receipt || !collectedSignaturesEvent) return + if (!fromHome || !home.web3 || !receipt || !collectedSignaturesEvent || !blockConfirmations) return const subscriptions: Array = [] @@ -175,7 +176,7 @@ export const useMessageConfirmations = ({ } homeBlockNumberProvider.start(home.web3) - const targetBlock = collectedSignaturesEvent.blockNumber + home.blockConfirmations + const targetBlock = collectedSignaturesEvent.blockNumber + blockConfirmations checkWaitingBlocksForExecution( homeBlockNumberProvider, @@ -193,7 +194,7 @@ export const useMessageConfirmations = ({ homeBlockNumberProvider.stop() } }, - [collectedSignaturesEvent, fromHome, home.blockConfirmations, home.web3, receipt] + [collectedSignaturesEvent, fromHome, blockConfirmations, home.web3, receipt] ) // Checks if validators verified the message diff --git a/alm/src/state/StateProvider.tsx b/alm/src/state/StateProvider.tsx index 3920c0953..16762011d 100644 --- a/alm/src/state/StateProvider.tsx +++ b/alm/src/state/StateProvider.tsx @@ -18,7 +18,6 @@ export interface BaseNetworkParams { web3: Maybe bridgeAddress: string bridgeContract: Maybe - blockConfirmations: number } export interface StateContext { @@ -33,16 +32,14 @@ const initialState = { name: '', web3: null, bridgeAddress: HOME_BRIDGE_ADDRESS, - bridgeContract: null, - blockConfirmations: 0 + bridgeContract: null }, foreign: { chainId: 0, name: '', web3: null, bridgeAddress: FOREIGN_BRIDGE_ADDRESS, - bridgeContract: null, - blockConfirmations: 0 + bridgeContract: null }, loading: true } @@ -52,7 +49,7 @@ const StateContext = createContext(initialState) export const StateProvider = ({ children }: { children: ReactNode }) => { const homeNetwork = useNetwork(HOME_RPC_URL) const foreignNetwork = useNetwork(FOREIGN_RPC_URL) - const { homeBridge, foreignBridge, homeBlockConfirmations, foreignBlockConfirmations } = useBridgeContracts({ + const { homeBridge, foreignBridge } = useBridgeContracts({ homeWeb3: homeNetwork.web3, foreignWeb3: foreignNetwork.web3 }) @@ -62,14 +59,12 @@ export const StateProvider = ({ children }: { children: ReactNode }) => { bridgeAddress: HOME_BRIDGE_ADDRESS, name: HOME_NETWORK_NAME, bridgeContract: homeBridge, - blockConfirmations: homeBlockConfirmations, ...homeNetwork }, foreign: { bridgeAddress: FOREIGN_BRIDGE_ADDRESS, name: FOREIGN_NETWORK_NAME, bridgeContract: foreignBridge, - blockConfirmations: foreignBlockConfirmations, ...foreignNetwork }, loading: homeNetwork.loading || foreignNetwork.loading diff --git a/alm/src/utils/contract.ts b/alm/src/utils/contract.ts index f1e6bd98f..2b81c2900 100644 --- a/alm/src/utils/contract.ts +++ b/alm/src/utils/contract.ts @@ -1,7 +1,21 @@ import { Contract } from 'web3-eth-contract' -export const getRequiredBlockConfirmations = async (contract: Contract) => { - const blockConfirmations = await contract.methods.requiredBlockConfirmations().call() +export const getRequiredBlockConfirmations = async (contract: Contract, blockNumber: number) => { + const events = await contract.getPastEvents('RequiredBlockConfirmationChanged', { + fromBlock: 0, + toBlock: blockNumber + }) + + let blockConfirmations + if (events.length > 0) { + // Use the value from last event before the transaction + const event = events[events.length - 1] + blockConfirmations = event.returnValues.requiredBlockConfirmations + } else { + // This is a special case where RequiredBlockConfirmationChanged was not emitted during initialization in early versions of AMB + // of Sokol - Kovan. In this case the current value is used. + blockConfirmations = await contract.methods.requiredBlockConfirmations().call() + } return parseInt(blockConfirmations) } From dc27bd6caa0ce56e98c26f262fe0487ada86d4e9 Mon Sep 17 00:00:00 2001 From: Gerardo Nardelli Date: Wed, 1 Jul 2020 18:34:27 -0300 Subject: [PATCH 11/13] ALM docker improvements (#380) --- alm/.env.example | 3 ++- alm/Dockerfile | 12 +++++++++--- alm/docker-compose.yml | 4 +++- alm/package.json | 3 ++- alm/src/utils/explorer.ts | 4 ++-- 5 files changed, 18 insertions(+), 8 deletions(-) diff --git a/alm/.env.example b/alm/.env.example index 16ae54928..2110b1f89 100644 --- a/alm/.env.example +++ b/alm/.env.example @@ -11,4 +11,5 @@ 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 +ALM_FOREIGN_EXPLORER_API=https://kovan.etherscan.io/api?apikey=YourApiKeyToken +PORT=8080 diff --git a/alm/Dockerfile b/alm/Dockerfile index 22e65fe8b..bcd293f06 100644 --- a/alm/Dockerfile +++ b/alm/Dockerfile @@ -1,4 +1,4 @@ -FROM node:12 +FROM node:12 as alm-builder WORKDIR /mono COPY package.json . @@ -19,5 +19,11 @@ ARG DOT_ENV_PATH=./alm/.env COPY ${DOT_ENV_PATH} ./alm/.env WORKDIR /mono/alm -CMD echo "To start the application run:" \ - "yarn start" +RUN yarn run build + + +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 . diff --git a/alm/docker-compose.yml b/alm/docker-compose.yml index 22c9d8c41..cc76c9ac8 100644 --- a/alm/docker-compose.yml +++ b/alm/docker-compose.yml @@ -5,8 +5,10 @@ services: build: context: .. dockerfile: alm/Dockerfile + ports: + - "${PORT}:${PORT}" env_file: ./.env environment: - NODE_ENV=production restart: unless-stopped - entrypoint: yarn start + entrypoint: serve -p ${PORT} -s . diff --git a/alm/package.json b/alm/package.json index 651193518..16b6d3297 100644 --- a/alm/package.json +++ b/alm/package.json @@ -26,7 +26,8 @@ "react-scripts": "3.0.1", "styled-components": "^5.1.1", "typescript": "^3.5.2", - "web3": "1.2.7" + "web3": "1.2.7", + "web3-eth-contract": "1.2.7" }, "scripts": { "start": "./load-env.sh react-app-rewired start", diff --git a/alm/src/utils/explorer.ts b/alm/src/utils/explorer.ts index 8561a6ab5..d57bf926c 100644 --- a/alm/src/utils/explorer.ts +++ b/alm/src/utils/explorer.ts @@ -70,7 +70,7 @@ export const fetchAccountTransactionsFromBlockscout = async ({ } export const getBlockByTimestampUrl = (api: string, timestamp: number) => - `${api}?module=block&action=getblocknobytime×tamp=${timestamp}&closest=before` + `${api}&module=block&action=getblocknobytime×tamp=${timestamp}&closest=before` export const fetchAccountTransactionsFromEtherscan = async ({ account, @@ -101,7 +101,7 @@ export const fetchAccountTransactionsFromEtherscan = async ({ return [] } - const url = `${api}?module=account&action=txlist&address=${account}&startblock=${fromBlock}&endblock=${toBlock}` + const url = `${api}&module=account&action=txlist&address=${account}&startblock=${fromBlock}&endblock=${toBlock}` try { const result = await fetch(url).then(res => res.json()) From d5d0c8f56ab2d6e6804e950b7a8ffe623ab36c19 Mon Sep 17 00:00:00 2001 From: Kirill Fedoseev Date: Thu, 2 Jul 2020 02:37:19 +0500 Subject: [PATCH 12/13] Extend burner wallet plugin with support of erc-to-native mediator (#378) --- burner-wallet-plugin/testing/package.json | 3 +- .../testing/src/LocalhostGateway.ts | 53 ++ burner-wallet-plugin/testing/src/index.tsx | 78 ++- burner-wallet-plugin/testing/tsconfig.json | 1 + .../assets/BridgeableERC20Asset.ts | 51 ++ .../src/burner-wallet/assets/ERC677Asset.ts | 5 +- .../src/burner-wallet/index.ts | 2 + .../pairs/MediatorErcToNative.ts | 48 ++ .../tokenbridge-bw-exchange/src/index.ts | 12 +- .../tokenbridge-bw-exchange/src/utils/abis.ts | 1 + .../src/utils/abis/Mediator.ts | 18 + .../src/utils/abis/MediatorErcToNative.ts | 44 ++ .../src/utils/index.ts | 3 +- .../src/utils/utils.ts | 6 +- burner-wallet-plugin/yarn.lock | 460 +----------------- 15 files changed, 310 insertions(+), 475 deletions(-) create mode 100644 burner-wallet-plugin/testing/src/LocalhostGateway.ts create mode 100644 burner-wallet-plugin/tokenbridge-bw-exchange/src/burner-wallet/assets/BridgeableERC20Asset.ts create mode 100644 burner-wallet-plugin/tokenbridge-bw-exchange/src/burner-wallet/pairs/MediatorErcToNative.ts create mode 100644 burner-wallet-plugin/tokenbridge-bw-exchange/src/utils/abis/MediatorErcToNative.ts diff --git a/burner-wallet-plugin/testing/package.json b/burner-wallet-plugin/testing/package.json index 8c2364cf4..23cefda49 100644 --- a/burner-wallet-plugin/testing/package.json +++ b/burner-wallet-plugin/testing/package.json @@ -16,8 +16,7 @@ "react": "^16.8.6", "react-dom": "^16.8.6", "react-scripts": "3.0.1", - "typescript": "3.5.1", - "web3": "1.0.0-beta.55" + "typescript": "3.5.1" }, "scripts": { "start": "react-scripts start", diff --git a/burner-wallet-plugin/testing/src/LocalhostGateway.ts b/burner-wallet-plugin/testing/src/LocalhostGateway.ts new file mode 100644 index 000000000..43ff964f4 --- /dev/null +++ b/burner-wallet-plugin/testing/src/LocalhostGateway.ts @@ -0,0 +1,53 @@ +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) + }) + }) + } +} diff --git a/burner-wallet-plugin/testing/src/index.tsx b/burner-wallet-plugin/testing/src/index.tsx index 02754f25b..11ef314bc 100644 --- a/burner-wallet-plugin/testing/src/index.tsx +++ b/burner-wallet-plugin/testing/src/index.tsx @@ -6,13 +6,23 @@ import { InjectedSigner, LocalSigner } from '@burner-wallet/core/signers' import { InfuraGateway, InjectedGateway } from '@burner-wallet/core/gateways' import ModernUI from '@burner-wallet/modern-ui' import Exchange from '@burner-wallet/exchange' -import { Mediator, sPOA, ERC677Asset, TokenBridgeGateway } from '@poanet/tokenbridge-bw-exchange' +import { + Mediator, + sPOA, + ERC677Asset, + TokenBridgeGateway, + NativeMediatorAsset, + MediatorErcToNative, + BridgeableERC20Asset +} from '@poanet/tokenbridge-bw-exchange' import MetamaskPlugin from '@burner-wallet/metamask-plugin' +import LocalhostGateway from './LocalhostGateway' let assetIdAtHome = 'assetAtHome' const assetIdAtForeign = 'assetAtForeign' let assetAtHome: Asset let assetAtForeign: Asset +let testBridge: Mediator if (process.env.REACT_APP_MODE === 'AMB_NATIVE_TO_ERC677') { sPOA.setMediatorAddress(process.env.REACT_APP_HOME_MEDIATOR_ADDRESS) @@ -28,8 +38,16 @@ if (process.env.REACT_APP_MODE === 'AMB_NATIVE_TO_ERC677') { // @ts-ignore address: process.env.REACT_APP_FOREIGN_TOKEN_ADDRESS }) -} else { - // process.env.REACT_APP_MODE === 'AMB_ERC677_TO_ERC677' + + 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 if (process.env.REACT_APP_MODE === 'AMB_ERC677_TO_ERC677') { assetAtHome = new ERC677Asset({ id: 'assetAtHome', // @ts-ignore @@ -49,20 +67,54 @@ if (process.env.REACT_APP_MODE === 'AMB_NATIVE_TO_ERC677') { // @ts-ignore address: process.env.REACT_APP_FOREIGN_TOKEN_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 -}) + 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, + // @ts-ignore + bridgeAddress: process.env.REACT_APP_FOREIGN_MEDIATOR_ADDRESS + }) + + 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 core = new BurnerCore({ signers: [new InjectedSigner(), new LocalSigner({ privateKey: process.env.REACT_APP_PK, saveKey: false })], - gateways: [new InjectedGateway(), new TokenBridgeGateway(), new InfuraGateway(process.env.REACT_APP_INFURA_KEY)], + gateways: [ + new InjectedGateway(), + new LocalhostGateway(), + new TokenBridgeGateway(), + new InfuraGateway(process.env.REACT_APP_INFURA_KEY) + ], assets: [assetAtHome, assetAtForeign] }) diff --git a/burner-wallet-plugin/testing/tsconfig.json b/burner-wallet-plugin/testing/tsconfig.json index 0980b23fa..0aab7c650 100644 --- a/burner-wallet-plugin/testing/tsconfig.json +++ b/burner-wallet-plugin/testing/tsconfig.json @@ -17,6 +17,7 @@ "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, + "noImplicitAny": false, "jsx": "preserve" }, "include": [ diff --git a/burner-wallet-plugin/tokenbridge-bw-exchange/src/burner-wallet/assets/BridgeableERC20Asset.ts b/burner-wallet-plugin/tokenbridge-bw-exchange/src/burner-wallet/assets/BridgeableERC20Asset.ts new file mode 100644 index 000000000..e1175cef0 --- /dev/null +++ b/burner-wallet-plugin/tokenbridge-bw-exchange/src/burner-wallet/assets/BridgeableERC20Asset.ts @@ -0,0 +1,51 @@ +import { ERC20Asset } from '@burner-wallet/assets' +import { MEDIATOR_ABI, constants } from '../../utils' +import { toBN } from 'web3-utils' + +interface BridgeableERC20Constructor { + abi?: object + address: string + id: string + name: string + network: string + bridgeAddress: string +} + +export default class BridgeableERC20Asset extends ERC20Asset { + protected bridgeAddress: string + private _bridge + + constructor({ bridgeAddress, ...params }: BridgeableERC20Constructor) { + super({ ...params }) + this.bridgeAddress = bridgeAddress.toLowerCase() + } + + getBridgeContract() { + if (!this._bridge) { + const Contract = this.getWeb3().eth.Contract + this._bridge = new Contract(MEDIATOR_ABI, this.bridgeAddress) + } + return this._bridge + } + + async _send({ from, to, value }) { + if (to.toLowerCase() === this.bridgeAddress) { + const allowance = await this.allowance(from, to) + if (toBN(allowance).lt(toBN(value))) { + await this.approve(from, to, value) + } + const receipt = await this.getBridgeContract() + .methods.relayTokens(from, value) + .send({ from }) + const transferLog = Object.values(receipt.events as object).find( + e => e.raw.topics[0] === constants.TRANSFER_TOPIC + ) + return { + ...receipt, + txHash: receipt.transactionHash, + id: `${receipt.transactionHash}-${transferLog.logIndex}` + } + } + return super._send({ from, to, value }) + } +} diff --git a/burner-wallet-plugin/tokenbridge-bw-exchange/src/burner-wallet/assets/ERC677Asset.ts b/burner-wallet-plugin/tokenbridge-bw-exchange/src/burner-wallet/assets/ERC677Asset.ts index 3f4ad00d1..ecd5364bb 100644 --- a/burner-wallet-plugin/tokenbridge-bw-exchange/src/burner-wallet/assets/ERC677Asset.ts +++ b/burner-wallet-plugin/tokenbridge-bw-exchange/src/burner-wallet/assets/ERC677Asset.ts @@ -1,7 +1,6 @@ import { ERC20Asset } from '@burner-wallet/assets' -import { ERC677_ABI } from '../../utils' +import { ERC677_ABI, constants } from '../../utils' -const TRANSFER_TOPIC = '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef' const BLOCK_LOOKBACK = 250 interface ERC677Constructor { @@ -45,7 +44,7 @@ export default class ERC677Asset extends ERC20Asset { const allTransferEvents = await this.getContract().getPastEvents('allEvents', { fromBlock: block, toBlock: currentBlock, - topics: [TRANSFER_TOPIC] + topics: [constants.TRANSFER_TOPIC] }) // Manually filter `to` parameter because `filter` option does not work with allEvents const events = allTransferEvents.filter(e => e.returnValues.to.toLowerCase() === address.toLowerCase()) diff --git a/burner-wallet-plugin/tokenbridge-bw-exchange/src/burner-wallet/index.ts b/burner-wallet-plugin/tokenbridge-bw-exchange/src/burner-wallet/index.ts index 4b3c3222f..90af23751 100644 --- a/burner-wallet-plugin/tokenbridge-bw-exchange/src/burner-wallet/index.ts +++ b/burner-wallet-plugin/tokenbridge-bw-exchange/src/burner-wallet/index.ts @@ -2,6 +2,8 @@ export { default as sPOA } from './assets/sPOA' export { default as Etc } from './assets/Etc' export { default as Wetc } from './assets/Wetc' export { default as ERC677Asset } from './assets/ERC677Asset' +export { default as BridgeableERC20Asset } from './assets/BridgeableERC20Asset' export { default as NativeMediatorAsset } from './assets/NativeMediatorAsset' export { default as TokenBridgeGateway } from './gateways/TokenBridgeGateway' export { default as Mediator } from './pairs/Mediator' +export { default as MediatorErcToNative } from './pairs/MediatorErcToNative' diff --git a/burner-wallet-plugin/tokenbridge-bw-exchange/src/burner-wallet/pairs/MediatorErcToNative.ts b/burner-wallet-plugin/tokenbridge-bw-exchange/src/burner-wallet/pairs/MediatorErcToNative.ts new file mode 100644 index 000000000..7ee007ca2 --- /dev/null +++ b/burner-wallet-plugin/tokenbridge-bw-exchange/src/burner-wallet/pairs/MediatorErcToNative.ts @@ -0,0 +1,48 @@ +import BN from 'bn.js' +import { EstimateReturn, ValueTypes } from '@burner-wallet/exchange' +import { constants } from '../../utils' +import { MEDIATOR_ERC_TO_NATIVE_ABI } from '../../utils' +import { default as Mediator } from './Mediator' +import { fromWei, toBN } from 'web3-utils' + +export default class MediatorErcToNative extends Mediator { + constructor(params) { + super(params) + } + + async estimateAtoB(value: ValueTypes): Promise { + return this.estimateWithFee(constants.HOME_TO_FOREIGN_FEE_TYPE, value) + } + + async estimateBtoA(value: ValueTypes): Promise { + return this.estimateWithFee(constants.FOREIGN_TO_HOME_FEE_TYPE, value) + } + + async estimateWithFee(feeType: string, value: ValueTypes): Promise { + const web3 = this.getExchange() + .getAsset(this.assetA) + .getWeb3() + + const userAmount = this._getValue(value) + + const contract = new web3.eth.Contract(MEDIATOR_ERC_TO_NATIVE_ABI, this.assetABridge) + const { feeAmount, feePercentage } = await this.getFee(feeType, contract, userAmount) + const finalAmount = toBN(userAmount).sub(feeAmount) + const estimateInfo = feeAmount.isZero() ? null : `${constants.ESTIMATE_FEE_MESSAGE} Fee: ${feePercentage}%` + + return { + estimate: finalAmount.toString(), + estimateInfo + } + } + + async getFee(feeType, contract, value): Promise<{ feeAmount: BN; feePercentage: number }> { + const fee = toBN(await contract.methods.getFee(feeType).call()) + const feePercentage = Number(fromWei(fee, 'ether')) * 100 + const feeAmount = toBN(await contract.methods.calculateFee(feeType, value).call()) + return { + feeAmount, + feePercentage + } + } +} diff --git a/burner-wallet-plugin/tokenbridge-bw-exchange/src/index.ts b/burner-wallet-plugin/tokenbridge-bw-exchange/src/index.ts index 29df51fbf..f43b67b1d 100644 --- a/burner-wallet-plugin/tokenbridge-bw-exchange/src/index.ts +++ b/burner-wallet-plugin/tokenbridge-bw-exchange/src/index.ts @@ -1,2 +1,12 @@ -export { ERC677Asset, NativeMediatorAsset, sPOA, Etc, Wetc, TokenBridgeGateway, Mediator } from './burner-wallet' +export { + ERC677Asset, + BridgeableERC20Asset, + NativeMediatorAsset, + sPOA, + Etc, + Wetc, + TokenBridgeGateway, + Mediator, + MediatorErcToNative +} from './burner-wallet' export { WETCBridge } from './wetc-bridge' diff --git a/burner-wallet-plugin/tokenbridge-bw-exchange/src/utils/abis.ts b/burner-wallet-plugin/tokenbridge-bw-exchange/src/utils/abis.ts index c7c5a4122..dceeb2e2e 100644 --- a/burner-wallet-plugin/tokenbridge-bw-exchange/src/utils/abis.ts +++ b/burner-wallet-plugin/tokenbridge-bw-exchange/src/utils/abis.ts @@ -3,3 +3,4 @@ export { default as FOREIGN_NATIVE_TO_ERC_ABI } from './abis/ForeignBridgeNative export { default as HOME_NATIVE_TO_ERC_ABI } from './abis/HomeBridgeNativeToErc' export { default as MEDIATOR_ABI } from './abis/Mediator' export { default as MEDIATOR_FEE_MANAGER_ABI } from './abis/MediatorFeeManager' +export { default as MEDIATOR_ERC_TO_NATIVE_ABI } from './abis/MediatorErcToNative' diff --git a/burner-wallet-plugin/tokenbridge-bw-exchange/src/utils/abis/Mediator.ts b/burner-wallet-plugin/tokenbridge-bw-exchange/src/utils/abis/Mediator.ts index 7c391a0e7..f52a578e2 100644 --- a/burner-wallet-plugin/tokenbridge-bw-exchange/src/utils/abis/Mediator.ts +++ b/burner-wallet-plugin/tokenbridge-bw-exchange/src/utils/abis/Mediator.ts @@ -34,5 +34,23 @@ export default [ payable: false, stateMutability: 'view', type: 'function' + }, + { + constant: false, + inputs: [ + { + name: '', + type: 'address' + }, + { + name: '', + type: 'uint256' + } + ], + name: 'relayTokens', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function' } ] diff --git a/burner-wallet-plugin/tokenbridge-bw-exchange/src/utils/abis/MediatorErcToNative.ts b/burner-wallet-plugin/tokenbridge-bw-exchange/src/utils/abis/MediatorErcToNative.ts new file mode 100644 index 000000000..6954f959d --- /dev/null +++ b/burner-wallet-plugin/tokenbridge-bw-exchange/src/utils/abis/MediatorErcToNative.ts @@ -0,0 +1,44 @@ +export default [ + { + constant: true, + inputs: [ + { + name: '', + type: 'bytes32' + } + ], + name: 'getFee', + outputs: [ + { + name: '', + type: 'uint256' + } + ], + payable: false, + stateMutability: 'view', + type: 'function' + }, + { + constant: true, + inputs: [ + { + name: '', + type: 'bytes32' + }, + { + name: '', + type: 'uint256' + } + ], + name: 'calculateFee', + outputs: [ + { + name: '', + type: 'uint256' + } + ], + payable: false, + stateMutability: 'view', + type: 'function' + } +] diff --git a/burner-wallet-plugin/tokenbridge-bw-exchange/src/utils/index.ts b/burner-wallet-plugin/tokenbridge-bw-exchange/src/utils/index.ts index 52d3593c1..dad1b86cb 100644 --- a/burner-wallet-plugin/tokenbridge-bw-exchange/src/utils/index.ts +++ b/burner-wallet-plugin/tokenbridge-bw-exchange/src/utils/index.ts @@ -4,5 +4,6 @@ export { FOREIGN_NATIVE_TO_ERC_ABI, HOME_NATIVE_TO_ERC_ABI, MEDIATOR_ABI, - MEDIATOR_FEE_MANAGER_ABI + MEDIATOR_FEE_MANAGER_ABI, + MEDIATOR_ERC_TO_NATIVE_ABI } from './abis' diff --git a/burner-wallet-plugin/tokenbridge-bw-exchange/src/utils/utils.ts b/burner-wallet-plugin/tokenbridge-bw-exchange/src/utils/utils.ts index 2bf0aa343..766087da1 100644 --- a/burner-wallet-plugin/tokenbridge-bw-exchange/src/utils/utils.ts +++ b/burner-wallet-plugin/tokenbridge-bw-exchange/src/utils/utils.ts @@ -7,7 +7,11 @@ export const constants = { EXCHANGE_TIMEOUT: 300000, MAX_FEE: toWei('1', 'ether'), ESTIMATE_FEE_MESSAGE: 'Estimation takes fee charges into consideration.', - ZERO_ADDRESS: '0x0000000000000000000000000000000000000000' + ZERO_ADDRESS: '0x0000000000000000000000000000000000000000', + TRANSFER_TOPIC: '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', + // fee types are taken from contracts/upgradeable_contracts/amb_erc20_to_native/HomeFeeManagerAMBErc20ToNative.sol + HOME_TO_FOREIGN_FEE_TYPE: '0x741ede137d0537e88e0ea0ff25b1f22d837903dbbee8980b4a06e8523247ee26', + FOREIGN_TO_HOME_FEE_TYPE: '0x03be2b2875cb41e0e77355e802a16769bb8dfcf825061cde185c73bf94f12625' } export const waitForEvent = async (web3, contract: Contract, event: string, callback: Function) => { diff --git a/burner-wallet-plugin/yarn.lock b/burner-wallet-plugin/yarn.lock index 06d25d262..ed7454802 100644 --- a/burner-wallet-plugin/yarn.lock +++ b/burner-wallet-plugin/yarn.lock @@ -2600,11 +2600,6 @@ resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== -"@types/mocha@^7.0.2": - version "7.0.2" - resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-7.0.2.tgz#b17f16cf933597e10d6d78eae3251e692ce8b0ce" - integrity sha512-ZvO2tAcjmMi8V/5Z3JsyofMe3hasRcaw88cto5etSVMwVQfeivGAlEYmaQgceUSVYFofVjT+ioHsATjdWcFt1w== - "@types/node@*", "@types/node@>= 8": version "13.11.0" resolved "https://registry.yarnpkg.com/@types/node/-/node-13.11.0.tgz#390ea202539c61c8fa6ba4428b57e05bc36dc47b" @@ -3220,11 +3215,6 @@ are-we-there-yet@~1.1.2: delegates "^1.0.0" readable-stream "^2.0.6" -arg@^4.1.0: - version "4.1.3" - resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" - integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== - argparse@^1.0.7: version "1.0.10" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" @@ -3350,11 +3340,6 @@ assert@^1.1.1: object-assign "^4.1.1" util "0.10.3" -assertion-error@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" - integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw== - assign-symbols@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" @@ -3450,13 +3435,6 @@ axios@^0.18.0: follow-redirects "1.5.10" is-buffer "^2.0.2" -axios@^0.19.0: - version "0.19.2" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.2.tgz#3ea36c5d8818d0d5f8a8a97a6d36b86cdc00cb27" - integrity sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA== - dependencies: - follow-redirects "1.5.10" - axobject-query@^2.0.2: version "2.1.2" resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.1.2.tgz#2bdffc0371e643e5f03ba99065d5179b9ca79799" @@ -3821,11 +3799,6 @@ browser-resolve@^1.11.3: dependencies: resolve "1.1.7" -browser-stdout@1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" - integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== - browserify-aes@^1.0.0, browserify-aes@^1.0.4, browserify-aes@^1.0.6: version "1.2.0" resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" @@ -3838,7 +3811,7 @@ browserify-aes@^1.0.0, browserify-aes@^1.0.4, browserify-aes@^1.0.6: inherits "^2.0.1" safe-buffer "^5.0.1" -browserify-cipher@^1.0.0, browserify-cipher@^1.0.1: +browserify-cipher@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.1.tgz#8d6474c1b870bfdabcd3bcfcc1934a10e94f15f0" integrity sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w== @@ -4215,18 +4188,6 @@ caseless@~0.12.0: resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= -chai@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/chai/-/chai-4.2.0.tgz#760aa72cf20e3795e84b12877ce0e83737aa29e5" - integrity sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw== - dependencies: - assertion-error "^1.1.0" - check-error "^1.0.2" - deep-eql "^3.0.1" - get-func-name "^2.0.0" - pathval "^1.1.0" - type-detect "^4.0.5" - chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.3.1, chalk@^2.4.1, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" @@ -4252,11 +4213,6 @@ chardet@^0.7.0: resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== -check-error@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" - integrity sha1-V00xLt2Iu13YkS6Sht1sCu1KrII= - check-types@^8.0.3: version "8.0.3" resolved "https://registry.yarnpkg.com/check-types/-/check-types-8.0.3.tgz#3356cca19c889544f2d7a95ed49ce508a0ecf552" @@ -4490,11 +4446,6 @@ combined-stream@^1.0.6, combined-stream@~1.0.6: dependencies: delayed-stream "~1.0.0" -commander@2.15.1: - version "2.15.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f" - integrity sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag== - commander@2.17.x: version "2.17.1" resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf" @@ -5290,13 +5241,6 @@ dedent@^0.7.0: resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw= -deep-eql@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-3.0.1.tgz#dfc9404400ad1c8fe023e7da1df1c147c4b444df" - integrity sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw== - dependencies: - type-detect "^4.0.0" - deep-equal@^1.0.1: version "1.1.1" resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.1.tgz#b5c98c942ceffaf7cb051e24e1434a25a2e6076a" @@ -5469,16 +5413,6 @@ diff-sequences@^24.9.0: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.9.0.tgz#5715d6244e2aa65f48bba0bc972db0b0b11e95b5" integrity sha512-Dj6Wk3tWyTE+Fo1rW8v0Xhwk80um6yFYKbuAxc9c3EZxIHFDYwbi34Uk42u1CdnIiVorvt4RmlSDjIPyzGC2ew== -diff@3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" - integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== - -diff@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" - integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== - diffie-hellman@^5.0.0: version "5.0.3" resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" @@ -6531,7 +6465,7 @@ ethers@4.0.44: uuid "2.0.1" xmlhttprequest "1.8.0" -ethers@^4.0.27, ethers@~4.0.4: +ethers@~4.0.4: version "4.0.46" resolved "https://registry.yarnpkg.com/ethers/-/ethers-4.0.46.tgz#13cd3ed099487f43ece00194b89a8a8781f71507" integrity sha512-/dPMzzpInhtiip4hKFvsDiJKeRk64IhyA+Po7CtNXneQFSOCYXg8eBFt+jXbxUQyApgWnWOtYxWdfn9+CvvxDA== @@ -6546,7 +6480,7 @@ ethers@^4.0.27, ethers@~4.0.4: uuid "2.0.1" xmlhttprequest "1.8.0" -ethjs-unit@0.1.6, ethjs-unit@^0.1.6: +ethjs-unit@0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/ethjs-unit/-/ethjs-unit-0.1.6.tgz#c665921e476e87bce2a9d588a6fe0405b2c41699" integrity sha1-xmWSHkduh7ziqdWIpv4EBbLEFpk= @@ -6562,11 +6496,6 @@ ethjs-util@0.1.6, ethjs-util@^0.1.3: is-hex-prefixed "1.0.0" strip-hex-prefix "1.0.0" -eventemitter3@3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.0.tgz#090b4d6cdbd645ed10bf750d4b5407942d7ba163" - integrity sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA== - eventemitter3@3.1.2, eventemitter3@^3.1.0: version "3.1.2" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.2.tgz#2d3d48f9c346698fce83a85d7d664e98535df6e7" @@ -7164,11 +7093,6 @@ get-caller-file@^2.0.1: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-func-name@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" - integrity sha1-6td0q+5y4gQJQzoGY2YCPdaIekE= - get-own-enumerable-property-symbols@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz#b5fde77f22cbe35f390b4e089922c50bce6ef664" @@ -7303,18 +7227,6 @@ glob-to-regexp@^0.3.0: resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab" integrity sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs= -glob@7.1.2: - version "7.1.2" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" - integrity sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4: version "7.1.6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" @@ -7448,11 +7360,6 @@ graceful-fs@^4.1.10, graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1. resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" integrity sha1-TK+tdrxi8C+gObL5Tpo906ORpyU= -growl@1.10.5: - version "1.10.5" - resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" - integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA== - growly@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" @@ -7611,11 +7518,6 @@ hdkey@^1.1.1: safe-buffer "^5.1.1" secp256k1 "^3.0.1" -he@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" - integrity sha1-k0EP0hsAlzUVH4howvJx80J+I/0= - he@1.2.x: version "1.2.0" resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" @@ -9690,11 +9592,6 @@ make-dir@^2.0.0, make-dir@^2.1.0: pify "^4.0.1" semver "^5.6.0" -make-error@^1.1.1: - version "1.3.6" - resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" - integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== - make-fetch-happen@^5.0.0: version "5.0.2" resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-5.0.2.tgz#aa8387104f2687edca01c8687ee45013d02d19bd" @@ -10046,11 +9943,6 @@ minimist-options@^3.0.1: arrify "^1.0.1" is-plain-obj "^1.1.0" -minimist@0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" - integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= - minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" @@ -10115,13 +10007,6 @@ mkdirp@*: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== -mkdirp@0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" - integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= - dependencies: - minimist "0.0.8" - mkdirp@^0.5.0, mkdirp@^0.5.1: version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" @@ -10136,23 +10021,6 @@ mkdirp@~0.5.0, mkdirp@~0.5.1: dependencies: minimist "^1.2.5" -mocha@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-5.2.0.tgz#6d8ae508f59167f940f2b5b3c4a612ae50c90ae6" - integrity sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ== - dependencies: - browser-stdout "1.3.1" - commander "2.15.1" - debug "3.1.0" - diff "3.5.0" - escape-string-regexp "1.0.5" - glob "7.1.2" - growl "1.10.5" - he "1.1.1" - minimatch "3.0.4" - mkdirp "0.5.1" - supports-color "5.4.0" - mock-fs@^4.1.0: version "4.11.0" resolved "https://registry.yarnpkg.com/mock-fs/-/mock-fs-4.11.0.tgz#0828107e4b843a6ba855ecebfe3c6e073b69db92" @@ -10237,7 +10105,7 @@ nan@2.13.2: resolved "https://registry.yarnpkg.com/nan/-/nan-2.13.2.tgz#f51dc7ae66ba7d5d55e1e6d4d8092e802c9aefe7" integrity sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw== -nan@2.14.0, nan@^2.0.8, nan@^2.12.1, nan@^2.14.0, nan@^2.2.1: +nan@2.14.0, nan@^2.12.1, nan@^2.14.0, nan@^2.2.1: version "2.14.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== @@ -11099,12 +10967,7 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== -pathval@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.0.tgz#b942e6d4bde653005ef6b71361def8727d0645e0" - integrity sha1-uULm1L3mUwBe9rcTYd74cn0GReA= - -pbkdf2@^3.0.17, pbkdf2@^3.0.3: +pbkdf2@^3.0.3: version "3.0.17" resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.17.tgz#976c206530617b14ebb32114239f7b09336e93a6" integrity sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA== @@ -12127,7 +11990,7 @@ querystring@0.2.0: resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= -querystringify@^2.0.0, querystringify@^2.1.1: +querystringify@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.1.tgz#60e5a5fd64a7f8bfa4d2ab2ed6fdf4c85bad154e" integrity sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA== @@ -12970,29 +12833,6 @@ scrypt-js@2.0.4: resolved "https://registry.yarnpkg.com/scrypt-js/-/scrypt-js-2.0.4.tgz#32f8c5149f0797672e551c07e230f834b6af5f16" integrity sha512-4KsaGcPnuhtCZQCxFxN3GVYIhKFPTdLd8PLC552XwbMndtD0cjRFAhDuuydXQ0h08ZfPgzqe6EKHozpuH74iDw== -scrypt.js@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/scrypt.js/-/scrypt.js-0.3.0.tgz#6c62d61728ad533c8c376a2e5e3e86d41a95c4c0" - integrity sha512-42LTc1nyFsyv/o0gcHtDztrn+aqpkaCNt5Qh7ATBZfhEZU7IC/0oT/qbBH+uRNoAPvs2fwiOId68FDEoSRA8/A== - dependencies: - scryptsy "^1.2.1" - optionalDependencies: - scrypt "^6.0.2" - -scrypt@^6.0.2: - version "6.0.3" - resolved "https://registry.yarnpkg.com/scrypt/-/scrypt-6.0.3.tgz#04e014a5682b53fa50c2d5cce167d719c06d870d" - integrity sha1-BOAUpWgrU/pQwtXM4WfXGcBthw0= - dependencies: - nan "^2.0.8" - -scryptsy@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/scryptsy/-/scryptsy-1.2.1.tgz#a3225fa4b2524f802700761e2855bdf3b2d92163" - integrity sha1-oyJfpLJST4AnAHYeKFW987LZIWM= - dependencies: - pbkdf2 "^3.0.3" - scryptsy@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/scryptsy/-/scryptsy-2.1.0.tgz#8d1e8d0c025b58fdd25b6fa9a0dc905ee8faa790" @@ -13872,13 +13712,6 @@ stylis@^3.5.0: resolved "https://registry.yarnpkg.com/stylis/-/stylis-3.5.4.tgz#f665f25f5e299cf3d64654ab949a57c768b73fbe" integrity sha512-8/3pSmthWM7lsPBKv7NXkzn2Uc9W7NotcwGNpJaa3k7WMM1XDCA4MgT5k/8BIexd5ydZdboXtU90XH9Ec4Bv/Q== -supports-color@5.4.0: - version "5.4.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.4.0.tgz#1c6b337402c2137605efe19f10fec390f6faab54" - integrity sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w== - dependencies: - has-flag "^3.0.0" - supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" @@ -14243,17 +14076,6 @@ tryer@^1.0.1: resolved "https://registry.yarnpkg.com/tryer/-/tryer-1.0.1.tgz#f2c85406800b9b0f74c9f7465b81eaad241252f8" integrity sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA== -ts-node@^8.8.2: - version "8.8.2" - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.8.2.tgz#0b39e690bee39ea5111513a9d2bcdc0bc121755f" - integrity sha512-duVj6BpSpUpD/oM4MfhO98ozgkp3Gt9qIp3jGxwU2DFvl/3IRaEAvbLa8G60uS7C77457e/m5TMowjedeRxI1Q== - dependencies: - arg "^4.1.0" - diff "^4.0.1" - make-error "^1.1.1" - source-map-support "^0.5.6" - yn "3.1.1" - ts-pnp@1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.1.2.tgz#be8e4bfce5d00f0f58e0666a82260c34a57af552" @@ -14300,11 +14122,6 @@ type-check@~0.3.2: dependencies: prelude-ls "~1.1.2" -type-detect@^4.0.0, type-detect@^4.0.5: - version "4.0.8" - resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" - integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== - type-fest@^0.3.0: version "0.3.1" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.3.1.tgz#63d00d204e059474fe5e1b7c011112bbd1dc29e1" @@ -14350,11 +14167,6 @@ typescript@3.5.3: resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.3.tgz#c830f657f93f1ea846819e929092f5fe5983e977" integrity sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g== -typescript@^3.5.2: - version "3.8.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.3.tgz#409eb8544ea0335711205869ec458ab109ee1061" - integrity sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w== - uglify-js@3.4.x: version "3.4.10" resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.4.10.tgz#9ad9563d8eb3acdfb8d38597d2af1d815f6a755f" @@ -14538,14 +14350,6 @@ url-parse-lax@^3.0.0: dependencies: prepend-http "^2.0.0" -url-parse@1.4.4: - version "1.4.4" - resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.4.tgz#cac1556e95faa0303691fec5cf9d5a1bc34648f8" - integrity sha512-/92DTTorg4JjktLNLe6GPS2/RvAd/RGr6LuktmWSMLEOa6rjnlrFXNgSbSmkNvCoL2T028A0a1JaJLzRMlFoHg== - dependencies: - querystringify "^2.0.0" - requires-port "^1.0.0" - url-parse@^1.4.3: version "1.4.7" resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278" @@ -14577,11 +14381,6 @@ use@^3.1.0: resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== -utf8@2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/utf8/-/utf8-2.1.1.tgz#2e01db02f7d8d0944f77104f1609eb0c304cf768" - integrity sha1-LgHbAvfY0JRPdxBPFgnrDDBM92g= - utf8@3.0.0, utf8@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/utf8/-/utf8-3.0.0.tgz#f052eed1364d696e769ef058b183df88c87f69d1" @@ -14771,17 +14570,6 @@ web3-bzz@1.2.6: swarm-js "0.1.39" underscore "1.9.1" -web3-core-helpers@1.0.0-beta.55: - version "1.0.0-beta.55" - resolved "https://registry.yarnpkg.com/web3-core-helpers/-/web3-core-helpers-1.0.0-beta.55.tgz#832b8499889f9f514b1d174f00172fd3683d63de" - integrity sha512-suj9Xy/lIqajaYLJTEjr2rlFgu6hGYwChHmf8+qNrC2luZA6kirTamtB9VThWMxbywx7p0bqQFjW6zXogAgWhg== - dependencies: - "@babel/runtime" "^7.3.1" - lodash "^4.17.11" - web3-core "1.0.0-beta.55" - web3-eth-iban "1.0.0-beta.55" - web3-utils "1.0.0-beta.55" - web3-core-helpers@1.2.6: version "1.2.6" resolved "https://registry.yarnpkg.com/web3-core-helpers/-/web3-core-helpers-1.2.6.tgz#7aacd25bf8015adcdfc0f3243d0dcfdff0373f7d" @@ -14791,20 +14579,6 @@ web3-core-helpers@1.2.6: web3-eth-iban "1.2.6" web3-utils "1.2.6" -web3-core-method@1.0.0-beta.55: - version "1.0.0-beta.55" - resolved "https://registry.yarnpkg.com/web3-core-method/-/web3-core-method-1.0.0-beta.55.tgz#0af994295ac2dd64ccd53305b7df8da76e11da49" - integrity sha512-w1cW/s2ji9qGELHk2uMJCn1ooay0JJLVoPD1nvmsW6OTRWcVjxa62nJrFQhe6P5lEb83Xk9oHgmCxZoVUHibOw== - dependencies: - "@babel/runtime" "^7.3.1" - eventemitter3 "3.1.0" - lodash "^4.17.11" - rxjs "^6.4.0" - web3-core "1.0.0-beta.55" - web3-core-helpers "1.0.0-beta.55" - web3-core-subscriptions "1.0.0-beta.55" - web3-utils "1.0.0-beta.55" - web3-core-method@1.2.6: version "1.2.6" resolved "https://registry.yarnpkg.com/web3-core-method/-/web3-core-method-1.2.6.tgz#f5a3e4d304abaf382923c8ab88ec8eeef45c1b3b" @@ -14835,15 +14609,6 @@ web3-core-requestmanager@1.2.6: web3-providers-ipc "1.2.6" web3-providers-ws "1.2.6" -web3-core-subscriptions@1.0.0-beta.55: - version "1.0.0-beta.55" - resolved "https://registry.yarnpkg.com/web3-core-subscriptions/-/web3-core-subscriptions-1.0.0-beta.55.tgz#105902c13db53466fc17d07a981ad3d41c700f76" - integrity sha512-pb3oQbUzK7IoyXwag8TYInQddg0rr7BHxKc+Pbs/92hVNQ5ps4iGMVJKezdrjlQ1IJEEUiDIglXl4LZ1hIuMkw== - dependencies: - "@babel/runtime" "^7.3.1" - eventemitter3 "^3.1.0" - lodash "^4.17.11" - web3-core-subscriptions@1.2.6: version "1.2.6" resolved "https://registry.yarnpkg.com/web3-core-subscriptions/-/web3-core-subscriptions-1.2.6.tgz#9d44189e2321f8f1abc31f6c09103b5283461b57" @@ -14853,19 +14618,6 @@ web3-core-subscriptions@1.2.6: underscore "1.9.1" web3-core-helpers "1.2.6" -web3-core@1.0.0-beta.55: - version "1.0.0-beta.55" - resolved "https://registry.yarnpkg.com/web3-core/-/web3-core-1.0.0-beta.55.tgz#26b9abbf1bc1837c9cc90f06ecbc4ed714f89b53" - integrity sha512-AMMp7TLEtE7u8IJAu/THrRhBTZyZzeo7Y6GiWYNwb5+KStC9hIGLr9cI1KX9R6ZioTOLRHrqT7awDhnJ1ku2mg== - dependencies: - "@babel/runtime" "^7.3.1" - "@types/bn.js" "^4.11.4" - "@types/node" "^10.12.18" - lodash "^4.17.11" - web3-core-method "1.0.0-beta.55" - web3-providers "1.0.0-beta.55" - web3-utils "1.0.0-beta.55" - web3-core@1.2.6: version "1.2.6" resolved "https://registry.yarnpkg.com/web3-core/-/web3-core-1.2.6.tgz#bb42a1d7ae49a7258460f0d95ddb00906f59ef92" @@ -14878,16 +14630,6 @@ web3-core@1.2.6: web3-core-requestmanager "1.2.6" web3-utils "1.2.6" -web3-eth-abi@1.0.0-beta.55: - version "1.0.0-beta.55" - resolved "https://registry.yarnpkg.com/web3-eth-abi/-/web3-eth-abi-1.0.0-beta.55.tgz#69250420039346105a3d0f899c0a8a53be926f97" - integrity sha512-3h1xnm/vYmKUXTOYAOP0OsB5uijQV76pNNRGKOB6Dq6GR1pbcbD3WrB/4I643YA8l91t5FRzFzUiA3S77R2iqw== - dependencies: - "@babel/runtime" "^7.3.1" - ethers "^4.0.27" - lodash "^4.17.11" - web3-utils "1.0.0-beta.55" - web3-eth-abi@1.2.6, web3-eth-abi@^1.2.1: version "1.2.6" resolved "https://registry.yarnpkg.com/web3-eth-abi/-/web3-eth-abi-1.2.6.tgz#b495383cc5c0d8e2857b26e7fe25606685983b25" @@ -14897,25 +14639,6 @@ web3-eth-abi@1.2.6, web3-eth-abi@^1.2.1: underscore "1.9.1" web3-utils "1.2.6" -web3-eth-accounts@1.0.0-beta.55: - version "1.0.0-beta.55" - resolved "https://registry.yarnpkg.com/web3-eth-accounts/-/web3-eth-accounts-1.0.0-beta.55.tgz#ba734ffdc1e3cc8ac0ea01de5241323a0c2f69f3" - integrity sha512-VfzvwpSDHXqRVelIxsBVhgbV9BkFvhJ/q+bKhnVUUXV0JAhMK/7uC92TsqKk4EBYuqpHyZ1jjqrL4n03fMU7zw== - dependencies: - "@babel/runtime" "^7.3.1" - browserify-cipher "^1.0.1" - eth-lib "0.2.8" - lodash "^4.17.11" - pbkdf2 "^3.0.17" - randombytes "^2.1.0" - scrypt.js "0.3.0" - uuid "3.3.2" - web3-core "1.0.0-beta.55" - web3-core-helpers "1.0.0-beta.55" - web3-core-method "1.0.0-beta.55" - web3-providers "1.0.0-beta.55" - web3-utils "1.0.0-beta.55" - web3-eth-accounts@1.2.6: version "1.2.6" resolved "https://registry.yarnpkg.com/web3-eth-accounts/-/web3-eth-accounts-1.2.6.tgz#a1ba4bf75fa8102a3ec6cddd0eccd72462262720" @@ -14934,23 +14657,6 @@ web3-eth-accounts@1.2.6: web3-core-method "1.2.6" web3-utils "1.2.6" -web3-eth-contract@1.0.0-beta.55: - version "1.0.0-beta.55" - resolved "https://registry.yarnpkg.com/web3-eth-contract/-/web3-eth-contract-1.0.0-beta.55.tgz#cd9e6727ff73d648ebe7cae17516e8aec5873c65" - integrity sha512-v6oB1wfH039/A5sTb4ZTKX++fcBTHEkuQGpq50ATIDoxP/UTz2+6S+iL+3sCJTsByPw2/Bni/HM7NmLkXqzg/Q== - dependencies: - "@babel/runtime" "^7.3.1" - "@types/bn.js" "^4.11.4" - lodash "^4.17.11" - web3-core "1.0.0-beta.55" - web3-core-helpers "1.0.0-beta.55" - web3-core-method "1.0.0-beta.55" - web3-core-subscriptions "1.0.0-beta.55" - web3-eth-abi "1.0.0-beta.55" - web3-eth-accounts "1.0.0-beta.55" - web3-providers "1.0.0-beta.55" - web3-utils "1.0.0-beta.55" - web3-eth-contract@1.2.6: version "1.2.6" resolved "https://registry.yarnpkg.com/web3-eth-contract/-/web3-eth-contract-1.2.6.tgz#39111543960035ed94c597a239cf5aa1da796741" @@ -14966,24 +14672,6 @@ web3-eth-contract@1.2.6: web3-eth-abi "1.2.6" web3-utils "1.2.6" -web3-eth-ens@1.0.0-beta.55: - version "1.0.0-beta.55" - resolved "https://registry.yarnpkg.com/web3-eth-ens/-/web3-eth-ens-1.0.0-beta.55.tgz#4341434a3406728212d411ae7f22d4cf5b8642fe" - integrity sha512-jEL17coO0FJXb7KYq4+7DhVXj0Rh+wHfZ86jOvFUvJsRaUHfqK2TlMatuhD2mbrmxpBYb6oMPnXVnNK9bnD5Rg== - dependencies: - "@babel/runtime" "^7.3.1" - eth-ens-namehash "2.0.8" - lodash "^4.17.11" - web3-core "1.0.0-beta.55" - web3-core-helpers "1.0.0-beta.55" - web3-core-method "1.0.0-beta.55" - web3-eth-abi "1.0.0-beta.55" - web3-eth-accounts "1.0.0-beta.55" - web3-eth-contract "1.0.0-beta.55" - web3-net "1.0.0-beta.55" - web3-providers "1.0.0-beta.55" - web3-utils "1.0.0-beta.55" - web3-eth-ens@1.2.6: version "1.2.6" resolved "https://registry.yarnpkg.com/web3-eth-ens/-/web3-eth-ens-1.2.6.tgz#bf86a624c4c72bc59913c2345180d3ea947e110d" @@ -14998,15 +14686,6 @@ web3-eth-ens@1.2.6: web3-eth-contract "1.2.6" web3-utils "1.2.6" -web3-eth-iban@1.0.0-beta.55: - version "1.0.0-beta.55" - resolved "https://registry.yarnpkg.com/web3-eth-iban/-/web3-eth-iban-1.0.0-beta.55.tgz#15146a69de21addc99e7dbfb2920555b1e729637" - integrity sha512-a2Fxsb5Mssa+jiXgjUdIzJipE0175IcQXJbZLpKft2+zeSJWNTbaa3PQD2vPPpIM4W789q06N+f9Zc0Fyls+1g== - dependencies: - "@babel/runtime" "^7.3.1" - bn.js "4.11.8" - web3-utils "1.0.0-beta.55" - web3-eth-iban@1.2.6: version "1.2.6" resolved "https://registry.yarnpkg.com/web3-eth-iban/-/web3-eth-iban-1.2.6.tgz#0b22191fd1aa6e27f7ef0820df75820bfb4ed46b" @@ -15015,20 +14694,6 @@ web3-eth-iban@1.2.6: bn.js "4.11.8" web3-utils "1.2.6" -web3-eth-personal@1.0.0-beta.55: - version "1.0.0-beta.55" - resolved "https://registry.yarnpkg.com/web3-eth-personal/-/web3-eth-personal-1.0.0-beta.55.tgz#76e9d2da1501ee3c686751e7c7df63cc11793a1d" - integrity sha512-H0mahLQx6Oj7lpgTamKAswr3rHChRUZijeWAar2Hj7BABQlLRKwx8n09nYhxggvvLYQNQS90JjvQue7rAo2LQQ== - dependencies: - "@babel/runtime" "^7.3.1" - web3-core "1.0.0-beta.55" - web3-core-helpers "1.0.0-beta.55" - web3-core-method "1.0.0-beta.55" - web3-eth-accounts "1.0.0-beta.55" - web3-net "1.0.0-beta.55" - web3-providers "1.0.0-beta.55" - web3-utils "1.0.0-beta.55" - web3-eth-personal@1.2.6: version "1.2.6" resolved "https://registry.yarnpkg.com/web3-eth-personal/-/web3-eth-personal-1.2.6.tgz#47a0a0657ec04dd77f95451a6869d4751d324b6b" @@ -15041,28 +14706,6 @@ web3-eth-personal@1.2.6: web3-net "1.2.6" web3-utils "1.2.6" -web3-eth@1.0.0-beta.55: - version "1.0.0-beta.55" - resolved "https://registry.yarnpkg.com/web3-eth/-/web3-eth-1.0.0-beta.55.tgz#bb52150df0a77bd13511449a53793d4eb23ade6e" - integrity sha512-F3zJ9I1gOgQdNGfi2Dy2lmj6OqCMJoRN01XHhQZagq0HY1JYMfObtfMi5E3L+qsegsSddHbqp4YY57tKx6uxpA== - dependencies: - "@babel/runtime" "^7.3.1" - ethereumjs-tx "^1.3.7" - rxjs "^6.4.0" - web3-core "1.0.0-beta.55" - web3-core-helpers "1.0.0-beta.55" - web3-core-method "1.0.0-beta.55" - web3-core-subscriptions "1.0.0-beta.55" - web3-eth-abi "1.0.0-beta.55" - web3-eth-accounts "1.0.0-beta.55" - web3-eth-contract "1.0.0-beta.55" - web3-eth-ens "1.0.0-beta.55" - web3-eth-iban "1.0.0-beta.55" - web3-eth-personal "1.0.0-beta.55" - web3-net "1.0.0-beta.55" - web3-providers "1.0.0-beta.55" - web3-utils "1.0.0-beta.55" - web3-eth@1.2.6: version "1.2.6" resolved "https://registry.yarnpkg.com/web3-eth/-/web3-eth-1.2.6.tgz#15a8c65fdde0727872848cae506758d302d8d046" @@ -15082,19 +14725,6 @@ web3-eth@1.2.6: web3-net "1.2.6" web3-utils "1.2.6" -web3-net@1.0.0-beta.55: - version "1.0.0-beta.55" - resolved "https://registry.yarnpkg.com/web3-net/-/web3-net-1.0.0-beta.55.tgz#daf24323df16a890a0bac6c6eda48b6e8c7e96ef" - integrity sha512-do2WY8+/GArJSWX7k/zZ7nBnV9Y3n6LhPYkwT3LeFqDzD515bKwlomaNC8hOaTc6UQyXIoPprYTK2FevL7jrZw== - dependencies: - "@babel/runtime" "^7.3.1" - lodash "^4.17.11" - web3-core "1.0.0-beta.55" - web3-core-helpers "1.0.0-beta.55" - web3-core-method "1.0.0-beta.55" - web3-providers "1.0.0-beta.55" - web3-utils "1.0.0-beta.55" - web3-net@1.2.6: version "1.2.6" resolved "https://registry.yarnpkg.com/web3-net/-/web3-net-1.2.6.tgz#035ca0fbe55282fda848ca17ebb4c8966147e5ea" @@ -15158,37 +14788,6 @@ web3-providers-ws@1.2.6: underscore "1.9.1" web3-core-helpers "1.2.6" -web3-providers@1.0.0-beta.55: - version "1.0.0-beta.55" - resolved "https://registry.yarnpkg.com/web3-providers/-/web3-providers-1.0.0-beta.55.tgz#639503517741b69baaa82f1f940630df6a25992b" - integrity sha512-MNifc7W+iF6rykpbDR1MuX152jshWdZXHAU9Dk0Ja2/23elhIs4nCWs7wOX9FHrKgdrQbscPoq0uy+0aGzyWVQ== - dependencies: - "@babel/runtime" "^7.3.1" - "@types/node" "^10.12.18" - eventemitter3 "3.1.0" - lodash "^4.17.11" - url-parse "1.4.4" - web3-core "1.0.0-beta.55" - web3-core-helpers "1.0.0-beta.55" - web3-core-method "1.0.0-beta.55" - web3-utils "1.0.0-beta.55" - websocket "^1.0.28" - xhr2-cookies "1.1.0" - -web3-shh@1.0.0-beta.55: - version "1.0.0-beta.55" - resolved "https://registry.yarnpkg.com/web3-shh/-/web3-shh-1.0.0-beta.55.tgz#56f152ebcefb791dab86d2e6f1c296f8c1553644" - integrity sha512-lGP2HQ/1ThNnfoU8677aL48KsTx4Ht+2KQIn39dGpxVZqysQmovQIltbymVnAr4h8wofwcEz46iNHGa+PAyNzA== - dependencies: - "@babel/runtime" "^7.3.1" - web3-core "1.0.0-beta.55" - web3-core-helpers "1.0.0-beta.55" - web3-core-method "1.0.0-beta.55" - web3-core-subscriptions "1.0.0-beta.55" - web3-net "1.0.0-beta.55" - web3-providers "1.0.0-beta.55" - web3-utils "1.0.0-beta.55" - web3-shh@1.2.6: version "1.2.6" resolved "https://registry.yarnpkg.com/web3-shh/-/web3-shh-1.2.6.tgz#2492616da4cac32d4c7534b890f43bac63190c14" @@ -15199,22 +14798,6 @@ web3-shh@1.2.6: web3-core-subscriptions "1.2.6" web3-net "1.2.6" -web3-utils@1.0.0-beta.55: - version "1.0.0-beta.55" - resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-1.0.0-beta.55.tgz#beb40926b7c04208b752d36a9bc959d27a04b308" - integrity sha512-ASWqUi8gtWK02Tp8ZtcoAbHenMpQXNvHrakgzvqTNNZn26wgpv+Q4mdPi0KOR6ZgHFL8R/9b5BBoUTglS1WPpg== - dependencies: - "@babel/runtime" "^7.3.1" - "@types/bn.js" "^4.11.4" - "@types/node" "^10.12.18" - bn.js "4.11.8" - eth-lib "0.2.8" - ethjs-unit "^0.1.6" - lodash "^4.17.11" - number-to-bn "1.7.0" - randombytes "^2.1.0" - utf8 "2.1.1" - web3-utils@1.2.6, web3-utils@^1.2.1, web3-utils@^1.2.2: version "1.2.6" resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-1.2.6.tgz#b9a25432da00976457fcc1094c4af8ac6d486db9" @@ -15229,21 +14812,6 @@ web3-utils@1.2.6, web3-utils@^1.2.1, web3-utils@^1.2.2: underscore "1.9.1" utf8 "3.0.0" -web3@1.0.0-beta.55: - version "1.0.0-beta.55" - resolved "https://registry.yarnpkg.com/web3/-/web3-1.0.0-beta.55.tgz#8845075129299da172c2eb41a748c8a87c2a2b5a" - integrity sha512-yJpwy4IUA3T/F9hWzYQVn0GbJCrAaZ0KTIO3iuqkhaYH0Y09KV7k4GzFi4hN7hT4cFTj4yIKaeVCwQ5kzvi2Vg== - dependencies: - "@babel/runtime" "^7.3.1" - "@types/node" "^10.12.18" - web3-core "1.0.0-beta.55" - web3-eth "1.0.0-beta.55" - web3-eth-personal "1.0.0-beta.55" - web3-net "1.0.0-beta.55" - web3-providers "1.0.0-beta.55" - web3-shh "1.0.0-beta.55" - web3-utils "1.0.0-beta.55" - web3@^1.2.1, web3@^1.2.2: version "1.2.6" resolved "https://registry.yarnpkg.com/web3/-/web3-1.2.6.tgz#c497dcb14cdd8d6d9fb6b445b3b68ff83f8ccf68" @@ -15406,17 +14974,6 @@ websocket-extensions@>=0.1.1: resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.3.tgz#5d2ff22977003ec687a4b87073dfbbac146ccf29" integrity sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg== -websocket@^1.0.28: - version "1.0.31" - resolved "https://registry.yarnpkg.com/websocket/-/websocket-1.0.31.tgz#e5d0f16c3340ed87670e489ecae6144c79358730" - integrity sha512-VAouplvGKPiKFDTeCCO65vYHsyay8DqoBSlzIO3fayrfOgU94lQN5a1uWVnFrMLceTJw/+fQXR5PGbUVRaHshQ== - dependencies: - debug "^2.2.0" - es5-ext "^0.10.50" - nan "^2.14.0" - typedarray-to-buffer "^3.1.5" - yaeti "^0.0.6" - whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3, whatwg-encoding@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0" @@ -15924,8 +15481,3 @@ yauzl@^2.4.2: dependencies: buffer-crc32 "~0.2.3" fd-slicer "~1.1.0" - -yn@3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" - integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== From caf2e2b4d316777f3c7bcc373b6d486bc9afb731 Mon Sep 17 00:00:00 2001 From: Alexander Kolotov Date: Sun, 5 Jul 2020 00:01:42 +0300 Subject: [PATCH 13/13] Update the contract's submodule to the release 5.1.0 (#381) --- contracts | 2 +- e2e-commons/scripts/setupStakeTokens.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts b/contracts index 56c2c3972..d67761d93 160000 --- a/contracts +++ b/contracts @@ -1 +1 @@ -Subproject commit 56c2c39722be96381084834db68f8d7acc4805fa +Subproject commit d67761d93855a2693b719dd619a400b9302b8952 diff --git a/e2e-commons/scripts/setupStakeTokens.js b/e2e-commons/scripts/setupStakeTokens.js index 07844bbe5..7c878e501 100644 --- a/e2e-commons/scripts/setupStakeTokens.js +++ b/e2e-commons/scripts/setupStakeTokens.js @@ -6,7 +6,7 @@ require('dotenv').config({ }) const { sendRawTxHome, sendRawTxForeign, privateKeyToAddress } = require(`${contractsPath}/deploy/src/deploymentUtils`) const { web3Home, web3Foreign, deploymentPrivateKey } = require(`${contractsPath}/deploy/src/web3`) -const BlockReward = require(`${contractsPath}/build/contracts/BlockReward.json`) +const BlockReward = require(`${contractsPath}/build/contracts/BlockRewardMock.json`) const ERC677BridgeTokenRewardable = require(`${contractsPath}/build/contracts/ERC677BridgeTokenRewardable.json`) const ERC677MultiBridgeToken = require(`${contractsPath}/build/contracts/ERC677MultiBridgeToken.json`)