-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
feat: Implement extra validations and alert when sending burn address in EVM transfer transactions #21772
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Implement extra validations and alert when sending burn address in EVM transfer transactions #21772
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,7 +12,9 @@ import Icon, { | |
| } from '../../../../../../../component-library/components/Icons/Icon'; | ||
| import { NameType } from '../../../../../../UI/Name/Name.types'; | ||
| import { useTransferRecipient } from '../../../../hooks/transactions/useTransferRecipient'; | ||
| import { RowAlertKey } from '../../../UI/info-row/alert-row/constants'; | ||
| import InfoSection from '../../../UI/info-row/info-section'; | ||
| import AlertRow from '../../../UI/info-row/alert-row'; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: sorting imports There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am not going to address sortings anymore as lint rules of the repo doesn't dictate it. |
||
| import styleSheet from './from-to-row.styles'; | ||
|
|
||
| const FromToRow = () => { | ||
|
|
@@ -54,12 +56,15 @@ const FromToRow = () => { | |
| </View> | ||
|
|
||
| <View style={[styles.nameContainer, styles.rightNameContainer]}> | ||
| <Name | ||
| type={NameType.EthereumAddress} | ||
| value={toAddress} | ||
| variation={chainId} | ||
| maxCharLength={MAX_CHAR_LENGTH} | ||
| /> | ||
| {/* Intentional empty label to trigger the alert row without a label */} | ||
| <AlertRow alertField={RowAlertKey.BurnAddress}> | ||
| <Name | ||
| type={NameType.EthereumAddress} | ||
| value={toAddress as string} | ||
OGPoyraz marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: Type Assertion Bypasses SafetyThe |
||
| variation={chainId} | ||
| maxCharLength={MAX_CHAR_LENGTH} | ||
| /> | ||
| </AlertRow> | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: Undefined Address HandlingThe component does not guard against |
||
| </View> | ||
| </View> | ||
| </InfoSection> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,142 @@ | ||
| import { useBurnAddressAlert } from './useBurnAddressAlert'; | ||
| import { renderHookWithProvider } from '../../../../../util/test/renderWithProvider'; | ||
| import { | ||
| useNestedTransactionTransferRecipients, | ||
| useTransferRecipient, | ||
| } from '../transactions/useTransferRecipient'; | ||
| import { Severity } from '../../types/alerts'; | ||
| import { AlertKeys } from '../../constants/alerts'; | ||
| import { RowAlertKey } from '../../components/UI/info-row/alert-row/constants'; | ||
|
|
||
| jest.mock('../transactions/useTransferRecipient'); | ||
|
|
||
| describe('useBurnAddressAlert', () => { | ||
| const BURN_ADDRESS_1 = '0x0000000000000000000000000000000000000000'; | ||
| const BURN_ADDRESS_2 = '0x000000000000000000000000000000000000dead'; | ||
| const NORMAL_ADDRESS = '0x1234567890123456789012345678901234567890'; | ||
|
|
||
| beforeEach(() => { | ||
| jest.clearAllMocks(); | ||
| (useTransferRecipient as jest.Mock).mockReturnValue(undefined); | ||
| (useNestedTransactionTransferRecipients as jest.Mock).mockReturnValue([]); | ||
| }); | ||
|
|
||
| it('returns empty array when no recipient addresses are present', () => { | ||
| const { result } = renderHookWithProvider(() => useBurnAddressAlert()); | ||
|
|
||
| expect(result.current).toEqual([]); | ||
| }); | ||
|
|
||
| it('returns empty array when transaction recipient is a normal address', () => { | ||
| (useTransferRecipient as jest.Mock).mockReturnValue(NORMAL_ADDRESS); | ||
|
|
||
| const { result } = renderHookWithProvider(() => useBurnAddressAlert()); | ||
|
|
||
| expect(result.current).toEqual([]); | ||
| }); | ||
|
|
||
| it('returns empty array when nested transaction recipients are normal addresses', () => { | ||
| (useNestedTransactionTransferRecipients as jest.Mock).mockReturnValue([ | ||
| NORMAL_ADDRESS, | ||
| '0xabcdef1234567890abcdef1234567890abcdef12', | ||
| ]); | ||
|
|
||
| const { result } = renderHookWithProvider(() => useBurnAddressAlert()); | ||
|
|
||
| expect(result.current).toEqual([]); | ||
| }); | ||
|
|
||
| it('returns burn address alert when transaction recipient is first burn address', () => { | ||
| (useTransferRecipient as jest.Mock).mockReturnValue(BURN_ADDRESS_1); | ||
|
|
||
| const { result } = renderHookWithProvider(() => useBurnAddressAlert()); | ||
|
|
||
| expect(result.current).toHaveLength(1); | ||
| expect(result.current[0]).toMatchObject({ | ||
| key: AlertKeys.BurnAddress, | ||
| field: RowAlertKey.BurnAddress, | ||
| severity: Severity.Danger, | ||
| isBlocking: false, | ||
| }); | ||
| expect(result.current[0].title).toBeDefined(); | ||
| expect(result.current[0].message).toBeDefined(); | ||
| }); | ||
|
|
||
| it('returns burn address alert when transaction recipient is second burn address', () => { | ||
| (useTransferRecipient as jest.Mock).mockReturnValue(BURN_ADDRESS_2); | ||
|
|
||
| const { result } = renderHookWithProvider(() => useBurnAddressAlert()); | ||
|
|
||
| expect(result.current).toHaveLength(1); | ||
| expect(result.current[0]).toMatchObject({ | ||
| key: AlertKeys.BurnAddress, | ||
| field: RowAlertKey.BurnAddress, | ||
| severity: Severity.Danger, | ||
| isBlocking: false, | ||
| }); | ||
| }); | ||
|
|
||
| it('returns burn address alert when transaction recipient is burn address with uppercase letters', () => { | ||
| (useTransferRecipient as jest.Mock).mockReturnValue( | ||
| '0x0000000000000000000000000000000000000000'.toUpperCase(), | ||
| ); | ||
|
|
||
| const { result } = renderHookWithProvider(() => useBurnAddressAlert()); | ||
|
|
||
| expect(result.current).toHaveLength(1); | ||
| expect(result.current[0].key).toBe(AlertKeys.BurnAddress); | ||
| }); | ||
|
|
||
| it('returns burn address alert when nested transaction contains burn address', () => { | ||
| (useNestedTransactionTransferRecipients as jest.Mock).mockReturnValue([ | ||
| NORMAL_ADDRESS, | ||
| BURN_ADDRESS_1, | ||
| ]); | ||
|
|
||
| const { result } = renderHookWithProvider(() => useBurnAddressAlert()); | ||
|
|
||
| expect(result.current).toHaveLength(1); | ||
| expect(result.current[0]).toMatchObject({ | ||
| key: AlertKeys.BurnAddress, | ||
| field: RowAlertKey.BurnAddress, | ||
| severity: Severity.Danger, | ||
| isBlocking: false, | ||
| }); | ||
| }); | ||
|
|
||
| it('returns burn address alert when nested transaction contains burn address with mixed case', () => { | ||
| (useNestedTransactionTransferRecipients as jest.Mock).mockReturnValue([ | ||
| '0x000000000000000000000000000000000000dEaD', | ||
| ]); | ||
|
|
||
| const { result } = renderHookWithProvider(() => useBurnAddressAlert()); | ||
|
|
||
| expect(result.current).toHaveLength(1); | ||
| expect(result.current[0].key).toBe(AlertKeys.BurnAddress); | ||
| }); | ||
|
|
||
| it('returns single burn address alert when both transaction and nested transactions contain burn addresses', () => { | ||
| (useTransferRecipient as jest.Mock).mockReturnValue(BURN_ADDRESS_1); | ||
| (useNestedTransactionTransferRecipients as jest.Mock).mockReturnValue([ | ||
| BURN_ADDRESS_2, | ||
| ]); | ||
|
|
||
| const { result } = renderHookWithProvider(() => useBurnAddressAlert()); | ||
|
|
||
| expect(result.current).toHaveLength(1); | ||
| expect(result.current[0].key).toBe(AlertKeys.BurnAddress); | ||
| }); | ||
|
|
||
| it('returns single burn address alert when nested transactions contain multiple burn addresses', () => { | ||
| (useNestedTransactionTransferRecipients as jest.Mock).mockReturnValue([ | ||
| BURN_ADDRESS_1, | ||
| NORMAL_ADDRESS, | ||
| BURN_ADDRESS_2, | ||
| ]); | ||
|
|
||
| const { result } = renderHookWithProvider(() => useBurnAddressAlert()); | ||
|
|
||
| expect(result.current).toHaveLength(1); | ||
| expect(result.current[0].key).toBe(AlertKeys.BurnAddress); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| import { useMemo } from 'react'; | ||
| import { Alert, Severity } from '../../types/alerts'; | ||
| import { RowAlertKey } from '../../components/UI/info-row/alert-row/constants'; | ||
| import { AlertKeys } from '../../constants/alerts'; | ||
| import { LOWER_CASED_BURN_ADDRESSES } from '../../utils/send-address-validations'; | ||
| import { strings } from '../../../../../../locales/i18n'; | ||
| import { | ||
| useNestedTransactionTransferRecipients, | ||
| useTransferRecipient, | ||
| } from '../transactions/useTransferRecipient'; | ||
|
|
||
| export function useBurnAddressAlert(): Alert[] { | ||
| const transactionMetaRecipient = useTransferRecipient(); | ||
| const nestedTransactionRecipients = useNestedTransactionTransferRecipients(); | ||
|
|
||
| const hasBurnAddressRecipient = useMemo(() => { | ||
| const hasBurnAddressInTransactionMetaRecipient = | ||
| LOWER_CASED_BURN_ADDRESSES.includes( | ||
| transactionMetaRecipient?.toLowerCase() ?? '', | ||
| ); | ||
| const hasBurnAddressNestedTransactionRecipient = | ||
| nestedTransactionRecipients.some((recipient) => | ||
| LOWER_CASED_BURN_ADDRESSES.includes(recipient.toLowerCase()), | ||
| ); | ||
|
|
||
| return ( | ||
| hasBurnAddressInTransactionMetaRecipient || | ||
| hasBurnAddressNestedTransactionRecipient | ||
| ); | ||
| }, [transactionMetaRecipient, nestedTransactionRecipients]); | ||
|
|
||
| return useMemo(() => { | ||
| if (!hasBurnAddressRecipient) { | ||
| return []; | ||
| } | ||
|
|
||
| return [ | ||
| { | ||
| key: AlertKeys.BurnAddress, | ||
| field: RowAlertKey.BurnAddress, | ||
| message: strings('alert_system.burn_address.message'), | ||
| title: strings('alert_system.burn_address.title'), | ||
| severity: Severity.Danger, | ||
| isBlocking: false, | ||
| }, | ||
| ]; | ||
| }, [hasBurnAddressRecipient]); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The 2 label children created now can be merged by adding condition.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't it look more complicated if we merge conditions here?