Skip to content

Commit

Permalink
ALM manual signatures execution (#471)
Browse files Browse the repository at this point in the history
  • Loading branch information
k1rill-fedoseev authored Nov 25, 2020
1 parent 5327688 commit bbc68f9
Show file tree
Hide file tree
Showing 22 changed files with 576 additions and 515 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,6 @@ monitor/responses/*
monitor/cache/*
!monitor/cache/.gitkeep
!monitor/.gitkeep

# Local Netlify folder
.netlify
8 changes: 6 additions & 2 deletions alm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@ethersproject/bignumber": ">=5.0.0-beta.130",
"@react-hook/window-size": "^3.0.6",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
Expand All @@ -15,6 +16,8 @@
"@types/react-router-dom": "^5.1.5",
"@types/styled-components": "^5.1.0",
"@use-it/interval": "^0.1.3",
"@web3-react/core": "^6.1.1",
"@web3-react/injected-connector": "^6.0.7",
"customize-cra": "^1.0.0",
"date-fns": "^2.14.0",
"dotenv": "^8.2.0",
Expand All @@ -27,8 +30,9 @@
"react-scripts": "3.0.1",
"styled-components": "^5.1.1",
"typescript": "^3.5.2",
"web3": "1.2.7",
"web3-eth-contract": "1.2.7"
"web3": "1.2.11",
"web3-eth-contract": "1.2.11",
"web3-utils": "1.2.11"
},
"scripts": {
"start": "yarn createSnapshots && ./load-env.sh react-app-rewired start",
Expand Down
1 change: 1 addition & 0 deletions alm/public/_redirects
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/* /index.html 200
10 changes: 7 additions & 3 deletions alm/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import React from 'react'
import { BrowserRouter } from 'react-router-dom'
import { Web3ReactProvider } from '@web3-react/core'
import Web3 from 'web3'
import { MainPage } from './components/MainPage'
import { StateProvider } from './state/StateProvider'

function App() {
return (
<BrowserRouter>
<StateProvider>
<MainPage />
</StateProvider>
<Web3ReactProvider getLibrary={provider => new Web3(provider)}>
<StateProvider>
<MainPage />
</StateProvider>
</Web3ReactProvider>
</BrowserRouter>
)
}
Expand Down
22 changes: 19 additions & 3 deletions alm/src/components/ConfirmationsContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,19 @@ export interface ConfirmationsContainerParams {

export const ConfirmationsContainer = ({ message, receipt, fromHome, timestamp }: ConfirmationsContainerParams) => {
const {
home: { name: homeName },
home: { name: homeName, confirmations },
foreign: { name: foreignName }
} = useStateProvider()
const { requiredSignatures, validatorList } = useValidatorContract({ fromHome, receipt })
const { blockConfirmations } = useBlockConfirmations({ fromHome, receipt })
const { confirmations, status, executionData, signatureCollected, waitingBlocksResolved } = useMessageConfirmations({
const {
status,
executionData,
signatureCollected,
waitingBlocksResolved,
setExecutionData,
executionEventsFetched
} = useMessageConfirmations({
message,
receipt,
fromHome,
Expand Down Expand Up @@ -102,7 +109,16 @@ export const ConfirmationsContainer = ({ message, receipt, fromHome, timestamp }
validatorList={validatorList}
waitingBlocksResolved={waitingBlocksResolved}
/>
{signatureCollected && <ExecutionConfirmation executionData={executionData} isHome={!fromHome} />}
{signatureCollected && (
<ExecutionConfirmation
messageData={message.data}
executionData={executionData}
isHome={!fromHome}
requiredSignatures={requiredSignatures}
setExecutionData={setExecutionData}
executionEventsFetched={executionEventsFetched}
/>
)}
</StyledConfirmationContainer>
</div>
)
Expand Down
69 changes: 54 additions & 15 deletions alm/src/components/ExecutionConfirmation.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,43 @@
import React from 'react'
import { formatTimestamp, formatTxHash, getExplorerTxUrl } from '../utils/networks'
import { useWindowWidth } from '@react-hook/window-size'
import { SEARCHING_TX, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants'
import { SEARCHING_TX, VALIDATOR_CONFIRMATION_STATUS, ALM_HOME_TO_FOREIGN_MANUAL_EXECUTION } from '../config/constants'
import { SimpleLoading } from './commons/Loading'
import styled from 'styled-components'
import { ExecutionData } from '../hooks/useMessageConfirmations'
import { GreyLabel, RedLabel, SuccessLabel } from './commons/Labels'
import { ExplorerTxLink } from './commons/ExplorerTxLink'
import { Thead, AgeTd, StatusTd } from './commons/Table'
import { ManualExecutionButton } from './ManualExecutionButton'

const StyledExecutionConfirmation = styled.div`
margin-top: 30px;
`

export interface ExecutionConfirmationParams {
messageData: string
executionData: ExecutionData
setExecutionData: Function
requiredSignatures: number
isHome: boolean
executionEventsFetched: boolean
}

export const ExecutionConfirmation = ({ executionData, isHome }: ExecutionConfirmationParams) => {
export const ExecutionConfirmation = ({
messageData,
executionData,
setExecutionData,
requiredSignatures,
isHome,
executionEventsFetched
}: ExecutionConfirmationParams) => {
const availableManualExecution =
!isHome &&
(executionData.status === VALIDATOR_CONFIRMATION_STATUS.WAITING ||
(executionData.status === VALIDATOR_CONFIRMATION_STATUS.UNDEFINED &&
executionEventsFetched &&
!!executionData.validator))
const requiredManualExecution = availableManualExecution && ALM_HOME_TO_FOREIGN_MANUAL_EXECUTION
const windowWidth = useWindowWidth()

const txExplorerLink = getExplorerTxUrl(executionData.txHash, isHome)
Expand Down Expand Up @@ -48,26 +67,46 @@ export const ExecutionConfirmation = ({ executionData, isHome }: ExecutionConfir
<table>
<Thead>
<tr>
<th>Executed by</th>
<th>{requiredManualExecution ? 'Execution info' : 'Executed by'}</th>
<th className="text-center">Status</th>
<th className="text-center">Age</th>
{!requiredManualExecution && <th className="text-center">Age</th>}
{availableManualExecution && <th className="text-center">Actions</th>}
</tr>
</Thead>
<tbody>
<tr>
<td>{formattedValidator ? formattedValidator : <SimpleLoading />}</td>
<StatusTd className="text-center">{getExecutionStatusElement(executionData.status)}</StatusTd>
<AgeTd className="text-center">
{executionData.timestamp > 0 ? (
<ExplorerTxLink href={txExplorerLink} target="_blank">
{formatTimestamp(executionData.timestamp)}
</ExplorerTxLink>
) : executionData.status === VALIDATOR_CONFIRMATION_STATUS.WAITING ? (
''
<td>
{requiredManualExecution ? (
'Manual user action is required to complete the operation'
) : formattedValidator ? (
formattedValidator
) : (
SEARCHING_TX
<SimpleLoading />
)}
</AgeTd>
</td>
<StatusTd className="text-center">{getExecutionStatusElement(executionData.status)}</StatusTd>
{!requiredManualExecution && (
<AgeTd className="text-center">
{executionData.timestamp > 0 ? (
<ExplorerTxLink href={txExplorerLink} target="_blank">
{formatTimestamp(executionData.timestamp)}
</ExplorerTxLink>
) : executionData.status === VALIDATOR_CONFIRMATION_STATUS.WAITING ? (
''
) : (
SEARCHING_TX
)}
</AgeTd>
)}
{availableManualExecution && (
<td>
<ManualExecutionButton
messageData={messageData}
setExecutionData={setExecutionData}
requiredSignatures={requiredSignatures}
/>
</td>
)}
</tr>
</tbody>
</table>
Expand Down
4 changes: 3 additions & 1 deletion alm/src/components/MainPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { TransactionReceipt } from 'web3-eth'
import { InfoAlert } from './commons/InfoAlert'
import { ExplorerTxLink } from './commons/ExplorerTxLink'
import { FOREIGN_NETWORK_NAME, HOME_NETWORK_NAME } from '../config/constants'
import { ErrorAlert } from './commons/ErrorAlert'

const StyledMainPage = styled.div`
text-align: center;
Expand Down Expand Up @@ -51,7 +52,7 @@ export interface FormSubmitParams {

export const MainPage = () => {
const history = useHistory()
const { home, foreign } = useStateProvider()
const { home, foreign, error, setError } = useStateProvider()
const [networkName, setNetworkName] = useState('')
const [receipt, setReceipt] = useState<Maybe<TransactionReceipt>>(null)
const [showInfoAlert, setShowInfoAlert] = useState(false)
Expand Down Expand Up @@ -131,6 +132,7 @@ export const MainPage = () => {
</AlertP>
</InfoAlert>
)}
{error && <ErrorAlert onClick={() => setError('')} error={error} />}
<Route exact path={['/']} children={<Form onSubmit={onFormSubmit} />} />
<Route
path={['/:chainId/:txHash/:messageIdParam', '/:chainId/:txHash']}
Expand Down
110 changes: 110 additions & 0 deletions alm/src/components/ManualExecutionButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import React, { useState, useEffect } from 'react'
import styled from 'styled-components'
import { InjectedConnector } from '@web3-react/injected-connector'
import { useWeb3React } from '@web3-react/core'
import { INCORRECT_CHAIN_ERROR, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants'
import { useStateProvider } from '../state/StateProvider'
import { signatureToVRS, packSignatures } from '../utils/signatures'

const StyledButton = styled.button`
color: var(--button-color);
border-color: var(--font-color);
margin-top: 10px;
&:focus {
outline: var(--button-color);
}
`

interface ManualExecutionButtonParams {
messageData: string
setExecutionData: Function
requiredSignatures: number
}

export const ManualExecutionButton = ({
messageData,
setExecutionData,
requiredSignatures
}: ManualExecutionButtonParams) => {
const { home, foreign, setError } = useStateProvider()
const { library, activate, account, active } = useWeb3React()
const [manualExecution, setManualExecution] = useState(false)
const disabled =
home.confirmations.filter(({ signature }) => signature && signature.startsWith('0x')).length < requiredSignatures

useEffect(
() => {
if (!manualExecution || !foreign.chainId) return

if (!active) {
activate(new InjectedConnector({ supportedChainIds: [foreign.chainId] }), e => {
if (e.message.includes('Unsupported chain id')) {
setError(INCORRECT_CHAIN_ERROR)
const { ethereum } = window as any

// remove the error message after chain is correctly changed to the foreign one
const listener = (chainId: string) => {
if (parseInt(chainId.slice(2), 16) === foreign.chainId) {
ethereum.removeListener('chainChanged', listener)
setError((error: string) => (error === INCORRECT_CHAIN_ERROR ? '' : error))
}
}
ethereum.on('chainChanged', listener)
} else {
setError(e.message)
}
setManualExecution(false)
})
return
}

if (!library || !foreign.bridgeContract || !home.confirmations) return

const collectedSignatures = home.confirmations
.map(confirmation => confirmation.signature!)
.filter(signature => signature && signature.startsWith('0x'))
const signatures = packSignatures(collectedSignatures.map(signatureToVRS))
const data = foreign.bridgeContract.methods.executeSignatures(messageData, signatures).encodeABI()
setManualExecution(false)

library.eth
.sendTransaction({
from: account,
to: foreign.bridgeAddress,
data
})
.on('transactionHash', (txHash: string) =>
setExecutionData({
status: VALIDATOR_CONFIRMATION_STATUS.PENDING,
validator: account,
txHash,
timestamp: Math.floor(new Date().getTime() / 1000.0),
executionResult: false
})
)
.on('error', (e: Error) => setError(e.message))
},
[
manualExecution,
library,
activate,
active,
account,
foreign.chainId,
foreign.bridgeAddress,
foreign.bridgeContract,
setError,
messageData,
home.confirmations,
setExecutionData
]
)

return (
<div className="is-center">
<StyledButton disabled={disabled} className="button outline" onClick={() => setManualExecution(true)}>
Execute
</StyledButton>
</div>
)
}
9 changes: 3 additions & 6 deletions alm/src/components/commons/CloseIcon.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react'

export const CloseIcon = () => (
export const CloseIcon = ({ color }: { color?: string }) => (
<svg
aria-hidden="true"
focusable="false"
Expand All @@ -10,12 +10,9 @@ export const CloseIcon = () => (
role="img"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 352 512"
fill="#1890ff"
fill={color || '#1890ff'}
height="1em"
>
<path
fill="#1890ff"
d="M242.72 256l100.07-100.07c12.28-12.28 12.28-32.19 0-44.48l-22.24-22.24c-12.28-12.28-32.19-12.28-44.48 0L176 189.28 75.93 89.21c-12.28-12.28-32.19-12.28-44.48 0L9.21 111.45c-12.28 12.28-12.28 32.19 0 44.48L109.28 256 9.21 356.07c-12.28 12.28-12.28 32.19 0 44.48l22.24 22.24c12.28 12.28 32.2 12.28 44.48 0L176 322.72l100.07 100.07c12.28 12.28 32.2 12.28 44.48 0l22.24-22.24c12.28-12.28 12.28-32.19 0-44.48L242.72 256z"
/>
<path d="M242.72 256l100.07-100.07c12.28-12.28 12.28-32.19 0-44.48l-22.24-22.24c-12.28-12.28-32.19-12.28-44.48 0L176 189.28 75.93 89.21c-12.28-12.28-32.19-12.28-44.48 0L9.21 111.45c-12.28 12.28-12.28 32.19 0 44.48L109.28 256 9.21 356.07c-12.28 12.28-12.28 32.19 0 44.48l22.24 22.24c12.28 12.28 32.2 12.28 44.48 0L176 322.72l100.07 100.07c12.28 12.28 32.2 12.28 44.48 0l22.24-22.24c12.28-12.28 12.28-32.19 0-44.48L242.72 256z" />
</svg>
)
31 changes: 31 additions & 0 deletions alm/src/components/commons/ErrorAlert.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React from 'react'
import styled from 'styled-components'
import { InfoIcon } from './InfoIcon'
import { CloseIcon } from './CloseIcon'

const StyledErrorAlert = styled.div`
border: 1px solid var(--failed-color);
border-radius: 4px;
margin-bottom: 20px;
padding-top: 10px;
`

const CloseIconContainer = styled.div`
cursor: pointer;
`

const TextContainer = styled.div`
flex-direction: column;
`

export const ErrorAlert = ({ onClick, error }: { onClick: () => void; error: string }) => (
<div className="row is-center">
<StyledErrorAlert className="col-10 is-vertical-align row">
<InfoIcon color="var(--failed-color)" />
<TextContainer className="col-10">{error}</TextContainer>
<CloseIconContainer className="col-1 is-vertical-align is-center" onClick={onClick}>
<CloseIcon color="var(--failed-color)" />
</CloseIconContainer>
</StyledErrorAlert>
</div>
)
Loading

0 comments on commit bbc68f9

Please sign in to comment.