ALM: warning for auto-relayed messages (#644)

This commit is contained in:
Kirill Fedoseev 2022-03-17 15:11:14 +04:00 committed by GitHub
parent 9c2d2f404c
commit 910c3759c1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 131 additions and 14 deletions

@ -121,7 +121,7 @@ export const ConfirmationsContainer = ({
/> />
{signatureCollected && ( {signatureCollected && (
<ExecutionConfirmation <ExecutionConfirmation
messageData={message.data} message={message}
executionData={executionData} executionData={executionData}
isHome={!fromHome} isHome={!fromHome}
signatureCollected={signatureCollected} signatureCollected={signatureCollected}

@ -10,13 +10,14 @@ import { ExplorerTxLink } from './commons/ExplorerTxLink'
import { Thead, AgeTd, StatusTd } from './commons/Table' import { Thead, AgeTd, StatusTd } from './commons/Table'
import { ManualExecutionButton } from './ManualExecutionButton' import { ManualExecutionButton } from './ManualExecutionButton'
import { useStateProvider } from '../state/StateProvider' import { useStateProvider } from '../state/StateProvider'
import { matchesRule, MessageObject, WarnRule } from '../utils/web3'
const StyledExecutionConfirmation = styled.div` const StyledExecutionConfirmation = styled.div`
margin-top: 30px; margin-top: 30px;
` `
export interface ExecutionConfirmationParams { export interface ExecutionConfirmationParams {
messageData: string message: MessageObject
executionData: ExecutionData executionData: ExecutionData
setExecutionData: Function setExecutionData: Function
signatureCollected: boolean | string[] signatureCollected: boolean | string[]
@ -26,7 +27,7 @@ export interface ExecutionConfirmationParams {
} }
export const ExecutionConfirmation = ({ export const ExecutionConfirmation = ({
messageData, message,
executionData, executionData,
setExecutionData, setExecutionData,
signatureCollected, signatureCollected,
@ -34,7 +35,7 @@ export const ExecutionConfirmation = ({
executionEventsFetched, executionEventsFetched,
setPendingExecution setPendingExecution
}: ExecutionConfirmationParams) => { }: ExecutionConfirmationParams) => {
const { foreign } = useStateProvider() const { foreign, setWarning } = useStateProvider()
const [safeExecutionAvailable, setSafeExecutionAvailable] = useState(false) const [safeExecutionAvailable, setSafeExecutionAvailable] = useState(false)
const availableManualExecution = const availableManualExecution =
!isHome && !isHome &&
@ -67,6 +68,24 @@ export const ExecutionConfirmation = ({
[availableManualExecution, foreign.bridgeContract] [availableManualExecution, foreign.bridgeContract]
) )
useEffect(
() => {
if (!message.data || !executionData || !availableManualExecution) return
try {
const fileName = 'warnRules'
const rules: WarnRule[] = require(`../snapshots/${fileName}.json`)
for (let rule of rules) {
if (matchesRule(rule, message)) {
setWarning(rule.message)
return
}
}
} catch (e) {}
},
[availableManualExecution, executionData, message, message.data, setWarning]
)
const getExecutionStatusElement = (validatorStatus = '') => { const getExecutionStatusElement = (validatorStatus = '') => {
switch (validatorStatus) { switch (validatorStatus) {
case VALIDATOR_CONFIRMATION_STATUS.EXECUTION_SUCCESS: case VALIDATOR_CONFIRMATION_STATUS.EXECUTION_SUCCESS:
@ -125,7 +144,7 @@ export const ExecutionConfirmation = ({
<td> <td>
<ManualExecutionButton <ManualExecutionButton
safeExecutionAvailable={safeExecutionAvailable} safeExecutionAvailable={safeExecutionAvailable}
messageData={messageData} messageData={message.data}
setExecutionData={setExecutionData} setExecutionData={setExecutionData}
signatureCollected={signatureCollected as string[]} signatureCollected={signatureCollected as string[]}
setPendingExecution={setPendingExecution} setPendingExecution={setPendingExecution}

@ -9,6 +9,7 @@ import { InfoAlert } from './commons/InfoAlert'
import { ExplorerTxLink } from './commons/ExplorerTxLink' import { ExplorerTxLink } from './commons/ExplorerTxLink'
import { FOREIGN_NETWORK_NAME, HOME_NETWORK_NAME } from '../config/constants' import { FOREIGN_NETWORK_NAME, HOME_NETWORK_NAME } from '../config/constants'
import { ErrorAlert } from './commons/ErrorAlert' import { ErrorAlert } from './commons/ErrorAlert'
import { WarningAlert } from './commons/WarningAlert'
const StyledMainPage = styled.div` const StyledMainPage = styled.div`
text-align: center; text-align: center;
@ -52,7 +53,7 @@ export interface FormSubmitParams {
export const MainPage = () => { export const MainPage = () => {
const history = useHistory() const history = useHistory()
const { home, foreign, error, setError } = useStateProvider() const { home, foreign, error, setError, warning, setWarning } = useStateProvider()
const [networkName, setNetworkName] = useState('') const [networkName, setNetworkName] = useState('')
const [receipt, setReceipt] = useState<Maybe<TransactionReceipt>>(null) const [receipt, setReceipt] = useState<Maybe<TransactionReceipt>>(null)
const [showInfoAlert, setShowInfoAlert] = useState(false) const [showInfoAlert, setShowInfoAlert] = useState(false)
@ -133,6 +134,7 @@ export const MainPage = () => {
</InfoAlert> </InfoAlert>
)} )}
{error && <ErrorAlert onClick={() => setError('')} error={error} />} {error && <ErrorAlert onClick={() => setError('')} error={error} />}
{warning && <WarningAlert onClick={() => setWarning('')} error={warning} />}
<Route exact path={['/']} children={<Form onSubmit={onFormSubmit} />} /> <Route exact path={['/']} children={<Form onSubmit={onFormSubmit} />} />
<Route <Route
path={['/:chainId/:txHash/:messageIdParam', '/:chainId/:txHash']} path={['/:chainId/:txHash/:messageIdParam', '/:chainId/:txHash']}

@ -0,0 +1,34 @@
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(--warning-color);
border-radius: 4px;
margin-bottom: 20px;
padding-top: 10px;
`
const CloseIconContainer = styled.div`
cursor: pointer;
`
const TextContainer = styled.div`
white-space: pre-wrap;
flex-direction: column;
`
export const WarningAlert = ({ onClick, error }: { onClick: () => void; error: string }) => {
return (
<div className="row is-center">
<StyledErrorAlert className="col-10 is-vertical-align row">
<InfoIcon color="var(--warning-color)" />
<TextContainer className="col-10">{error}</TextContainer>
<CloseIconContainer className="col-1 is-vertical-align is-center" onClick={onClick}>
<CloseIcon color="var(--warning-color)" />
</CloseIconContainer>
</StyledErrorAlert>
</div>
)
}

@ -27,6 +27,8 @@ export interface StateContext {
loading: boolean loading: boolean
error: string error: string
setError: Function setError: Function
warning: string
setWarning: Function
} }
const initialState = { const initialState = {
@ -46,7 +48,9 @@ const initialState = {
}, },
loading: true, loading: true,
error: '', error: '',
setError: () => {} setError: () => {},
warning: '',
setWarning: () => {}
} }
const StateContext = createContext<StateContext>(initialState) const StateContext = createContext<StateContext>(initialState)
@ -59,6 +63,7 @@ export const StateProvider = ({ children }: { children: ReactNode }) => {
foreignWeb3: foreignNetwork.web3 foreignWeb3: foreignNetwork.web3
}) })
const [error, setError] = useState('') const [error, setError] = useState('')
const [warning, setWarning] = useState('')
const value = { const value = {
home: { home: {
@ -75,7 +80,9 @@ export const StateProvider = ({ children }: { children: ReactNode }) => {
}, },
loading: homeNetwork.loading || foreignNetwork.loading, loading: homeNetwork.loading || foreignNetwork.loading,
error, error,
setError setError,
warning,
setWarning
} }
return <StateContext.Provider value={value}>{children}</StateContext.Provider> return <StateContext.Provider value={value}>{children}</StateContext.Provider>

@ -28,5 +28,7 @@ export const GlobalStyle = createGlobalStyle<{ theme: ThemeType }>`
--not-required-bg-color: ${props => props.theme.notRequired.backgroundColor}; --not-required-bg-color: ${props => props.theme.notRequired.backgroundColor};
--failed-color: ${props => props.theme.failed.textColor}; --failed-color: ${props => props.theme.failed.textColor};
--failed-bg-color: ${props => props.theme.failed.backgroundColor}; --failed-bg-color: ${props => props.theme.failed.backgroundColor};
--warning-color: ${props => props.theme.warning.textColor};
--warning-bg-color: ${props => props.theme.warning.backgroundColor};
} }
` `

@ -17,6 +17,10 @@ const theme = {
failed: { failed: {
textColor: '#de4437', textColor: '#de4437',
backgroundColor: 'rgba(222,68,55,.1)' backgroundColor: 'rgba(222,68,55,.1)'
},
warning: {
textColor: '#ffa758',
backgroundColor: 'rgba(222,68,55,.1)'
} }
} }
export default theme export default theme

@ -10,6 +10,37 @@ import { SnapshotProvider } from '../services/SnapshotProvider'
export interface MessageObject { export interface MessageObject {
id: string id: string
data: string data: string
sender?: string
executor?: string
obToken?: string
obReceiver?: string
}
export interface WarnRule {
message: string
sender?: string
executor?: string
obToken?: string
obReceiver?: string
}
export const matchesRule = (rule: WarnRule, msg: MessageObject) => {
if (!msg.executor || !msg.sender) {
return false
}
if (!!rule.executor && rule.executor.toLowerCase() !== msg.executor.toLowerCase()) {
return false
}
if (!!rule.sender && rule.sender.toLowerCase() !== msg.sender.toLowerCase()) {
return false
}
if (!!rule.obToken && (!msg.obToken || rule.obToken.toLowerCase() !== msg.obToken.toLowerCase())) {
return false
}
if (!!rule.obReceiver && (!msg.obReceiver || rule.obReceiver.toLowerCase() !== msg.obReceiver.toLowerCase())) {
return false
}
return true
} }
const rawGetWeb3 = (url: string) => new Web3(new Web3.providers.HttpProvider(url)) const rawGetWeb3 = (url: string) => new Web3(new Web3.providers.HttpProvider(url))
@ -26,15 +57,33 @@ export const filterEventsByAbi = (
const eventHash = web3.eth.abi.encodeEventSignature(eventAbi) const eventHash = web3.eth.abi.encodeEventSignature(eventAbi)
const events = txReceipt.logs.filter(e => e.address === bridgeAddress && e.topics[0] === eventHash) const events = txReceipt.logs.filter(e => e.address === bridgeAddress && e.topics[0] === eventHash)
if (!eventAbi || !eventAbi.inputs || !eventAbi.inputs.length) {
return []
}
const inputs = eventAbi.inputs
return events.map(e => { return events.map(e => {
let decodedLogs: { [p: string]: string } = { const { messageId, encodedData } = web3.eth.abi.decodeLog(inputs, e.data, [e.topics[1]])
messageId: '', let sender, executor, obToken, obReceiver
encodedData: '' if (encodedData.length >= 160) {
sender = `0x${encodedData.slice(66, 106)}`
executor = `0x${encodedData.slice(106, 146)}`
const dataOffset =
160 + (parseInt(encodedData.slice(154, 156), 16) + parseInt(encodedData.slice(156, 158), 16)) * 2 + 8
if (encodedData.length >= dataOffset + 64) {
obToken = `0x${encodedData.slice(dataOffset + 24, dataOffset + 64)}`
} }
if (eventAbi && eventAbi.inputs && eventAbi.inputs.length) { if (encodedData.length >= dataOffset + 128) {
decodedLogs = web3.eth.abi.decodeLog(eventAbi.inputs, e.data, [e.topics[1]]) obReceiver = `0x${encodedData.slice(dataOffset + 88, dataOffset + 128)}`
}
}
return {
id: messageId || '',
data: encodedData || '',
sender,
executor,
obToken,
obReceiver
} }
return { id: decodedLogs.messageId, data: decodedLogs.encodedData }
}) })
} }