Skip to content

Commit

Permalink
Fix: show an error if risk isn't acknowledged (#2216)
Browse files Browse the repository at this point in the history
* Fix: show an error if risk isn't acknowledged

* Highlight risk checkbox on submit

* Don't reset error

* Rm timeout
  • Loading branch information
katspaugh authored Jul 4, 2023
1 parent fc23c8b commit 7575033
Show file tree
Hide file tree
Showing 9 changed files with 87 additions and 9 deletions.
2 changes: 1 addition & 1 deletion src/components/tx/ErrorMessage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const ErrorMessage = ({
children: ReactNode
error?: Error & { reason?: string }
className?: string
level?: 'error' | 'info'
level?: 'error' | 'warning' | 'info'
}): ReactElement => {
const [showDetails, setShowDetails] = useState<boolean>(false)

Expand Down
5 changes: 5 additions & 0 deletions src/components/tx/ErrorMessage/styles.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@
color: var(--color-error-dark);
}

.container.warning {
background-color: var(--color-warning-background);
color: var(--color-warning-dark);
}

.container.info {
background-color: var(--color-info-background);
color: var(--color-primary-main);
Expand Down
15 changes: 11 additions & 4 deletions src/components/tx/SignOrExecuteForm/ExecuteForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import { getTxOptions } from '@/utils/transactions'
import useIsValidExecution from '@/hooks/useIsValidExecution'
import CheckWallet from '@/components/common/CheckWallet'
import { useImmediatelyExecutable, useIsExecutionLoop, useTxActions } from './hooks'
import UnknownContractError from './UnknownContractError'
import { useRelaysBySafe } from '@/hooks/useRemainingRelays'
import useWalletCanRelay from '@/hooks/useWalletCanRelay'
import { ExecutionMethod, ExecutionMethodSelector } from '../ExecutionMethodSelector'
Expand All @@ -24,6 +23,7 @@ import { asError } from '@/services/exceptions/utils'

import css from './styles.module.css'
import commonCss from '@/components/tx-flow/common/styles.module.css'
import { TxSecurityContext } from '../security/shared/TxSecurityContext'

const ExecuteForm = ({
safeTx,
Expand All @@ -43,6 +43,7 @@ const ExecuteForm = ({
const { executeTx } = useTxActions()
const [relays] = useRelaysBySafe()
const { setTxFlow } = useContext(TxModalContext)
const { needsRiskConfirmation, isRiskConfirmed, setIsRiskIgnored } = useContext(TxSecurityContext)

// Check that the transaction is executable
const isCreation = !txId
Expand All @@ -69,6 +70,12 @@ const ExecuteForm = ({
// On modal submit
const handleSubmit = async (e: SyntheticEvent) => {
e.preventDefault()

if (needsRiskConfirmation && !isRiskConfirmed) {
setIsRiskIgnored(true)
return
}

setIsSubmittable(false)
setSubmitError(undefined)

Expand Down Expand Up @@ -126,10 +133,10 @@ const ExecuteForm = ({
? 'To save gas costs, avoid creating the transaction.'
: 'To save gas costs, reject this transaction.'}
</ErrorMessage>
) : submitError ? (
<ErrorMessage error={submitError}>Error submitting the transaction. Please try again.</ErrorMessage>
) : (
<UnknownContractError />
submitError && (
<ErrorMessage error={submitError}>Error submitting the transaction. Please try again.</ErrorMessage>
)
)}

<Divider className={commonCss.nestedDivider} sx={{ pt: 3 }} />
Expand Down
15 changes: 15 additions & 0 deletions src/components/tx/SignOrExecuteForm/RiskConfirmationError.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { useContext } from 'react'
import ErrorMessage from '../ErrorMessage'
import { TxSecurityContext } from '../security/shared/TxSecurityContext'

const RiskConfirmationError = () => {
const { isRiskConfirmed, isRiskIgnored } = useContext(TxSecurityContext)

if (isRiskConfirmed || !isRiskIgnored) {
return null
}

return <ErrorMessage level="warning">Please acknowledge the risk before proceeding.</ErrorMessage>
}

export default RiskConfirmationError
8 changes: 8 additions & 0 deletions src/components/tx/SignOrExecuteForm/SignForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type { SafeTransaction } from '@safe-global/safe-core-sdk-types'
import { TxModalContext } from '@/components/tx-flow'
import { asError } from '@/services/exceptions/utils'
import commonCss from '@/components/tx-flow/common/styles.module.css'
import { TxSecurityContext } from '../security/shared/TxSecurityContext'

const SignForm = ({
safeTx,
Expand All @@ -29,10 +30,17 @@ const SignForm = ({
const isOwner = useIsSafeOwner()
const { signTx } = useTxActions()
const { setTxFlow } = useContext(TxModalContext)
const { needsRiskConfirmation, isRiskConfirmed, setIsRiskIgnored } = useContext(TxSecurityContext)

// On modal submit
const handleSubmit = async (e: SyntheticEvent) => {
e.preventDefault()

if (needsRiskConfirmation && !isRiskConfirmed) {
setIsRiskIgnored(true)
return
}

setIsSubmittable(false)
setSubmitError(undefined)

Expand Down
6 changes: 6 additions & 0 deletions src/components/tx/SignOrExecuteForm/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import ConfirmationTitle, { ConfirmationTitleTypes } from '@/components/tx/SignO
import { useAppSelector } from '@/store'
import { selectSettings } from '@/store/settingsSlice'
import { RedefineBalanceChanges } from '../security/redefine/RedefineBalanceChange'
import UnknownContractError from './UnknownContractError'
import RiskConfirmationError from './RiskConfirmationError'

export type SignOrExecuteProps = {
txId?: string
Expand Down Expand Up @@ -67,6 +69,10 @@ const SignOrExecuteForm = (props: SignOrExecuteProps): ReactElement => {

<WrongChainWarning />

<UnknownContractError />

<RiskConfirmationError />

{willExecute ? <ExecuteForm {...props} safeTx={safeTx} /> : <SignForm {...props} safeTx={safeTx} />}
</TxCard>
</>
Expand Down
15 changes: 12 additions & 3 deletions src/components/tx/security/redefine/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useContext } from 'react'
import { useContext, useEffect, useRef } from 'react'
import { mapRedefineSeverity } from '@/components/tx/security/redefine/useRedefine'
import { TxSecurityContext } from '@/components/tx/security/shared/TxSecurityContext'
import { SecuritySeverity } from '@/services/security/modules/types'
Expand All @@ -22,8 +22,9 @@ import { RedefineHint } from '@/components/tx/security/redefine/RedefineHint'
const MAX_SHOWN_WARNINGS = 3

const RedefineBlock = () => {
const { severity, isLoading, error, needsRiskConfirmation, isRiskConfirmed, setIsRiskConfirmed } =
const { severity, isLoading, error, needsRiskConfirmation, isRiskConfirmed, setIsRiskConfirmed, isRiskIgnored } =
useContext(TxSecurityContext)
const checkboxRef = useRef<HTMLElement>(null)

const isDarkMode = useDarkMode()
const severityProps = severity !== undefined ? mapRedefineSeverity[severity] : undefined
Expand All @@ -32,6 +33,13 @@ const RedefineBlock = () => {
setIsRiskConfirmed((prev) => !prev)
}

// Highlight checkbox if user tries to submit transaction without confirming risks
useEffect(() => {
if (isRiskIgnored) {
checkboxRef.current?.scrollIntoView({ behavior: 'smooth', block: 'center' })
}
}, [isRiskIgnored, checkboxRef])

return (
<div className={css.wrapperBox}>
<Paper
Expand Down Expand Up @@ -81,11 +89,12 @@ const RedefineBlock = () => {
</Paper>
<div>
{needsRiskConfirmation && (
<Box pl={2}>
<Box pl={2} ref={checkboxRef}>
<Track {...MODALS_EVENTS.ACCEPT_RISK}>
<FormControlLabel
label="I understand the risks and would like to continue this transaction"
control={<Checkbox checked={isRiskConfirmed} onChange={toggleConfirmation} />}
className={isRiskIgnored ? css.checkboxError : ''}
/>
</Track>
</Box>
Expand Down
21 changes: 21 additions & 0 deletions src/components/tx/security/redefine/styles.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,24 @@
grid-template-columns: 35% auto;
padding: var(--space-2) 12px;
}

@keyframes popup {
0% {
transform: scale(1);
}
50% {
transform: scale(1.05);
}
100% {
transform: scale(1);
}
}

.checkboxError {
color: var(--color-error-main);
animation: popup 0.5s ease-in-out;
}

.checkboxError svg {
color: var(--color-error-main) !important;
}
9 changes: 8 additions & 1 deletion src/components/tx/security/shared/TxSecurityContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ export const TxSecurityContext = createContext<{
needsRiskConfirmation: boolean
isRiskConfirmed: boolean
setIsRiskConfirmed: Dispatch<SetStateAction<boolean>>
isRiskIgnored: boolean
setIsRiskIgnored: Dispatch<SetStateAction<boolean>>
}>({
warnings: [],
simulationUuid: undefined,
Expand All @@ -24,12 +26,15 @@ export const TxSecurityContext = createContext<{
needsRiskConfirmation: false,
isRiskConfirmed: false,
setIsRiskConfirmed: () => {},
isRiskIgnored: false,
setIsRiskIgnored: () => {},
})

export const TxSecurityProvider = ({ children }: { children: JSX.Element }) => {
const { safeTx } = useContext(SafeTxContext)
const [redefineResponse, redefineError, redefineLoading] = useRedefine(safeTx)
const [isRiskConfirmed, setIsRiskConfirmed] = useState(false)
const [isRiskIgnored, setIsRiskIgnored] = useState(false)

const providedValue = useMemo(
() => ({
Expand All @@ -42,8 +47,10 @@ export const TxSecurityProvider = ({ children }: { children: JSX.Element }) => {
needsRiskConfirmation: !!redefineResponse && redefineResponse.severity >= SecuritySeverity.HIGH,
isRiskConfirmed,
setIsRiskConfirmed,
isRiskIgnored: isRiskIgnored && !isRiskConfirmed,
setIsRiskIgnored,
}),
[isRiskConfirmed, redefineError, redefineLoading, redefineResponse],
[isRiskConfirmed, isRiskIgnored, redefineError, redefineLoading, redefineResponse],
)

return <TxSecurityContext.Provider value={providedValue}>{children}</TxSecurityContext.Provider>
Expand Down

0 comments on commit 7575033

Please sign in to comment.