From 9386e3cb03ae6c2b89b5bbeb6965b90e9c59f6cf Mon Sep 17 00:00:00 2001 From: Brad Decker Date: Mon, 17 May 2021 14:00:59 -0500 Subject: [PATCH] create safer isValidAddress method (#11089) --- app/scripts/controllers/preferences.js | 4 +- .../controllers/transactions/lib/util.js | 9 ++- app/scripts/lib/typed-message-manager.js | 5 +- shared/modules/hexstring-utils.js | 53 +++++++++++++ shared/modules/hexstring-utils.test.js | 57 ++++++++++++++ .../ui/identicon/identicon.component.js | 13 ++-- .../ui/identicon/identicon.component.test.js | 6 +- ui/helpers/utils/icon-factory.js | 12 ++- ui/helpers/utils/util.js | 34 -------- ui/helpers/utils/util.test.js | 78 +------------------ ui/pages/add-token/add-token.component.js | 10 +-- .../add-recipient/add-recipient.component.js | 10 ++- .../add-recipient/add-recipient.js | 11 ++- .../add-recipient/add-recipient.utils.test.js | 8 +- .../add-recipient/ens-input.component.js | 29 ++++--- ui/pages/send/send.component.js | 4 +- .../add-contact/add-contact.component.js | 13 ++-- .../edit-contact/edit-contact.component.js | 12 ++- ui/pages/settings/settings.container.js | 10 ++- ui/pages/swaps/swaps.util.js | 16 ++-- 20 files changed, 227 insertions(+), 167 deletions(-) create mode 100644 shared/modules/hexstring-utils.js create mode 100644 shared/modules/hexstring-utils.test.js diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index c20dcc6a0..19aef453e 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -2,12 +2,12 @@ import { strict as assert } from 'assert'; import { ObservableStore } from '@metamask/obs-store'; import { ethErrors } from 'eth-rpc-errors'; import { normalize as normalizeAddress } from 'eth-sig-util'; -import { isValidAddress } from 'ethereumjs-util'; import ethers from 'ethers'; import log from 'loglevel'; import { LISTED_CONTRACT_ADDRESSES } from '../../../shared/constants/tokens'; import { NETWORK_TYPE_TO_ID_MAP } from '../../../shared/constants/network'; import { isPrefixedFormattedHexString } from '../../../shared/modules/network.utils'; +import { isValidHexAddress } from '../../../shared/modules/hexstring-utils'; import { NETWORK_EVENTS } from './network'; export default class PreferencesController { @@ -867,7 +867,7 @@ export default class PreferencesController { `Invalid decimals "${decimals}": must be 0 <= 36.`, ); } - if (!isValidAddress(address)) { + if (!isValidHexAddress(address, { allowNonPrefixed: false })) { throw ethErrors.rpc.invalidParams(`Invalid address "${address}".`); } } diff --git a/app/scripts/controllers/transactions/lib/util.js b/app/scripts/controllers/transactions/lib/util.js index 70652a3c1..804f43ec2 100644 --- a/app/scripts/controllers/transactions/lib/util.js +++ b/app/scripts/controllers/transactions/lib/util.js @@ -1,7 +1,7 @@ -import { isValidAddress } from 'ethereumjs-util'; import { ethErrors } from 'eth-rpc-errors'; import { addHexPrefix } from '../../../lib/util'; import { TRANSACTION_STATUSES } from '../../../../../shared/constants/transaction'; +import { isValidHexAddress } from '../../../../../shared/modules/hexstring-utils'; const normalizers = { from: (from) => addHexPrefix(from), @@ -110,7 +110,7 @@ export function validateFrom(txParams) { `Invalid "from" address "${txParams.from}": not a string.`, ); } - if (!isValidAddress(txParams.from)) { + if (!isValidHexAddress(txParams.from, { allowNonPrefixed: false })) { throw ethErrors.rpc.invalidParams('Invalid "from" address.'); } } @@ -128,7 +128,10 @@ export function validateRecipient(txParams) { } else { throw ethErrors.rpc.invalidParams('Invalid "to" address.'); } - } else if (txParams.to !== undefined && !isValidAddress(txParams.to)) { + } else if ( + txParams.to !== undefined && + !isValidHexAddress(txParams.to, { allowNonPrefixed: false }) + ) { throw ethErrors.rpc.invalidParams('Invalid "to" address.'); } return txParams; diff --git a/app/scripts/lib/typed-message-manager.js b/app/scripts/lib/typed-message-manager.js index 3da405cf1..8e205520b 100644 --- a/app/scripts/lib/typed-message-manager.js +++ b/app/scripts/lib/typed-message-manager.js @@ -3,12 +3,12 @@ import { strict as assert } from 'assert'; import { ObservableStore } from '@metamask/obs-store'; import { ethErrors } from 'eth-rpc-errors'; import { typedSignatureHash, TYPED_MESSAGE_SCHEMA } from 'eth-sig-util'; -import { isValidAddress } from 'ethereumjs-util'; import log from 'loglevel'; import jsonschema from 'jsonschema'; import { MESSAGE_TYPE } from '../../../shared/constants/app'; import { METAMASK_CONTROLLER_EVENTS } from '../metamask-controller'; import createId from '../../../shared/modules/random-id'; +import { isValidHexAddress } from '../../../shared/modules/hexstring-utils'; /** * Represents, and contains data about, an 'eth_signTypedData' type signature request. These are created when a @@ -160,7 +160,8 @@ export default class TypedMessageManager extends EventEmitter { assert.ok('data' in params, 'Params must include a "data" field.'); assert.ok('from' in params, 'Params must include a "from" field.'); assert.ok( - typeof params.from === 'string' && isValidAddress(params.from), + typeof params.from === 'string' && + isValidHexAddress(params.from, { allowNonPrefixed: false }), '"from" field must be a valid, lowercase, hexadecimal Ethereum address string.', ); diff --git a/shared/modules/hexstring-utils.js b/shared/modules/hexstring-utils.js new file mode 100644 index 000000000..f40f537d3 --- /dev/null +++ b/shared/modules/hexstring-utils.js @@ -0,0 +1,53 @@ +import { + isHexString, + isValidAddress, + isValidChecksumAddress, + addHexPrefix, +} from 'ethereumjs-util'; + +export const BURN_ADDRESS = '0x0000000000000000000000000000000000000000'; + +export function isBurnAddress(address) { + return address === BURN_ADDRESS; +} + +/** + * Validates that the input is a hex address. This utility method is a thin + * wrapper around ethereumjs-util.isValidAddress, with the exception that it + * does not throw an error when provided values that are not hex strings. In + * addition, and by default, this method will return true for hex strings that + * meet the length requirement of a hex address, but are not prefixed with `0x` + * Finally, if the mixedCaseUseChecksum flag is true and a mixed case string is + * provided this method will validate it has the proper checksum formatting. + * @param {string} possibleAddress - Input parameter to check against + * @param {Object} [options] - options bag + * @param {boolean} [options.allowNonPrefixed] - If true will first ensure '0x' + * is prepended to the string + * @param {boolean} [options.mixedCaseUseChecksum] - If true will treat mixed + * case addresses as checksum addresses and validate that proper checksum + * format is used + * @returns {boolean} whether or not the input is a valid hex address + */ +export function isValidHexAddress( + possibleAddress, + { allowNonPrefixed = true, mixedCaseUseChecksum = false } = {}, +) { + const addressToCheck = allowNonPrefixed + ? addHexPrefix(possibleAddress) + : possibleAddress; + if (!isHexString(addressToCheck)) { + return false; + } + + if (mixedCaseUseChecksum) { + const prefixRemoved = addressToCheck.slice(2); + const lower = prefixRemoved.toLowerCase(); + const upper = prefixRemoved.toUpperCase(); + const allOneCase = prefixRemoved === lower || prefixRemoved === upper; + if (!allOneCase) { + return isValidChecksumAddress(addressToCheck); + } + } + + return isValidAddress(addressToCheck); +} diff --git a/shared/modules/hexstring-utils.test.js b/shared/modules/hexstring-utils.test.js new file mode 100644 index 000000000..3d292451e --- /dev/null +++ b/shared/modules/hexstring-utils.test.js @@ -0,0 +1,57 @@ +import { strict as assert } from 'assert'; +import { toChecksumAddress } from 'ethereumjs-util'; +import { isValidHexAddress } from './hexstring-utils'; + +describe('hexstring utils', function () { + describe('isValidHexAddress', function () { + it('should allow 40-char non-prefixed hex', function () { + const address = 'fdea65c8e26263f6d9a1b5de9555d2931a33b825'; + const result = isValidHexAddress(address); + assert.equal(result, true); + }); + + it('should allow 42-char prefixed hex', function () { + const address = '0xfdea65c8e26263f6d9a1b5de9555d2931a33b825'; + const result = isValidHexAddress(address); + assert.equal(result, true); + }); + + it('should NOT allow 40-char non-prefixed hex when allowNonPrefixed is false', function () { + const address = 'fdea65c8e26263f6d9a1b5de9555d2931a33b825'; + const result = isValidHexAddress(address, { allowNonPrefixed: false }); + assert.equal(result, false); + }); + + it('should NOT allow any length of non hex-prefixed string', function () { + const address = 'fdea65c8e26263f6d9a1b5de9555d2931a33b85'; + const result = isValidHexAddress(address); + assert.equal(result, false); + }); + + it('should NOT allow less than 42 character hex-prefixed string', function () { + const address = '0xfdea65ce26263f6d9a1b5de9555d2931a33b85'; + const result = isValidHexAddress(address); + assert.equal(result, false); + }); + + it('should recognize correct capitalized checksum', function () { + const address = '0xFDEa65C8e26263F6d9A1B5de9555D2931A33b825'; + const result = isValidHexAddress(address, { mixedCaseUseChecksum: true }); + assert.equal(result, true); + }); + + it('should recognize incorrect capitalized checksum', function () { + const address = '0xFDea65C8e26263F6d9A1B5de9555D2931A33b825'; + const result = isValidHexAddress(address, { mixedCaseUseChecksum: true }); + assert.equal(result, false); + }); + + it('should recognize this sample hashed address', function () { + const address = '0x5Fda30Bb72B8Dfe20e48A00dFc108d0915BE9Bb0'; + const result = isValidHexAddress(address, { mixedCaseUseChecksum: true }); + const hashed = toChecksumAddress(address.toLowerCase()); + assert.equal(hashed, address); + assert.equal(result, true); + }); + }); +}); diff --git a/ui/components/ui/identicon/identicon.component.js b/ui/components/ui/identicon/identicon.component.js index d52717d05..ec2cc70cc 100644 --- a/ui/components/ui/identicon/identicon.component.js +++ b/ui/components/ui/identicon/identicon.component.js @@ -2,8 +2,9 @@ import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; import classnames from 'classnames'; import contractMap from '@metamask/contract-metadata'; +import { isHexString, addHexPrefix } from 'ethereumjs-util'; -import { checksumAddress, isHex } from '../../../helpers/utils/util'; +import { checksumAddress } from '../../../helpers/utils/util'; import Jazzicon from '../jazzicon'; import BlockieIdenticon from './blockieIdenticon'; @@ -85,12 +86,12 @@ export default class Identicon extends PureComponent { } if (address) { - if (isHex(address)) { - const checksummedAddress = checksumAddress(address); + const checksummedAddress = + isHexString(addHexPrefix(address)) && + checksumAddress(addHexPrefix(address)); - if (contractMap[checksummedAddress]?.logo) { - return this.renderJazzicon(); - } + if (contractMap[checksummedAddress]?.logo) { + return this.renderJazzicon(); } return ( diff --git a/ui/components/ui/identicon/identicon.component.test.js b/ui/components/ui/identicon/identicon.component.test.js index f78a47b6d..fb9702fe8 100644 --- a/ui/components/ui/identicon/identicon.component.test.js +++ b/ui/components/ui/identicon/identicon.component.test.js @@ -38,7 +38,11 @@ describe('Identicon', () => { it('renders div with address prop', () => { const wrapper = mount( - , + , ); expect(wrapper.find('div.test-address').prop('className')).toStrictEqual( diff --git a/ui/helpers/utils/icon-factory.js b/ui/helpers/utils/icon-factory.js index 4d69b842f..030302a30 100644 --- a/ui/helpers/utils/icon-factory.js +++ b/ui/helpers/utils/icon-factory.js @@ -1,5 +1,7 @@ import contractMap from '@metamask/contract-metadata'; -import { isValidAddress, checksumAddress, isHex } from './util'; +import { isHexString, addHexPrefix } from 'ethereumjs-util'; +import { isValidHexAddress } from '../../../shared/modules/hexstring-utils'; +import { checksumAddress } from './util'; let iconFactory; @@ -18,8 +20,8 @@ function IconFactory(jazzicon) { IconFactory.prototype.iconForAddress = function (address, diameter) { let addr = address; - if (isHex(address)) { - addr = checksumAddress(address); + if (isHexString(addHexPrefix(address))) { + addr = checksumAddress(addHexPrefix(address)); } if (iconExistsFor(addr)) { @@ -52,7 +54,9 @@ IconFactory.prototype.generateNewIdenticon = function (address, diameter) { function iconExistsFor(address) { return ( - contractMap[address] && isValidAddress(address) && contractMap[address].logo + contractMap[address] && + isValidHexAddress(address, { allowNonPrefixed: false }) && + contractMap[address].logo ); } diff --git a/ui/helpers/utils/util.js b/ui/helpers/utils/util.js index f52e824d8..42cfe605c 100644 --- a/ui/helpers/utils/util.js +++ b/ui/helpers/utils/util.js @@ -78,20 +78,6 @@ export function addressSummary( : '...'; } -export function isValidAddress(address) { - if (!address || address === '0x0000000000000000000000000000000000000000') { - return false; - } - const prefixed = addHexPrefix(address); - if (!isHex(prefixed)) { - return false; - } - return ( - (isAllOneCase(prefixed.slice(2)) && ethUtil.isValidAddress(prefixed)) || - ethUtil.isValidChecksumAddress(prefixed) - ); -} - export function isValidDomainName(address) { const match = punycode .toASCII(address) @@ -112,15 +98,6 @@ export function isOriginContractAddress(to, sendTokenAddress) { return to.toLowerCase() === sendTokenAddress.toLowerCase(); } -export function isAllOneCase(address) { - if (!address) { - return true; - } - const lower = address.toLowerCase(); - const upper = address.toUpperCase(); - return address === lower || address === upper; -} - // Takes wei Hex, returns wei BN, even if input is null export function numericBalance(balance) { if (!balance) { @@ -182,10 +159,6 @@ export function formatBalance( return formatted; } -export function isHex(str) { - return Boolean(str.match(/^(0x)?[0-9a-fA-F]+$/u)); -} - export function getContractAtAddress(tokenAddress) { return global.eth.contract(abi).at(tokenAddress); } @@ -253,13 +226,6 @@ export function shortenAddress(address = '') { return `${address.slice(0, 6)}...${address.slice(-4)}`; } -export function isValidAddressHead(address) { - const addressLengthIsLessThanFull = address.length < 42; - const addressIsHex = isHex(address); - - return addressLengthIsLessThanFull && addressIsHex; -} - export function getAccountByAddress(accounts = [], targetAddress) { return accounts.find(({ address }) => address === targetAddress); } diff --git a/ui/helpers/utils/util.test.js b/ui/helpers/utils/util.test.js index 0dfa431f6..7038dd79e 100644 --- a/ui/helpers/utils/util.test.js +++ b/ui/helpers/utils/util.test.js @@ -1,4 +1,4 @@ -import { BN, toChecksumAddress } from 'ethereumjs-util'; +import { BN } from 'ethereumjs-util'; import * as util from './util'; describe('util', () => { @@ -47,52 +47,6 @@ describe('util', () => { }); }); - describe('#isValidAddress', () => { - it('should allow 40-char non-prefixed hex', () => { - const address = 'fdea65c8e26263f6d9a1b5de9555d2931a33b825'; - const result = util.isValidAddress(address); - expect(result).toStrictEqual(true); - }); - - it('should allow 42-char non-prefixed hex', () => { - const address = '0xfdea65c8e26263f6d9a1b5de9555d2931a33b825'; - const result = util.isValidAddress(address); - expect(result).toStrictEqual(true); - }); - - it('should not allow less non hex-prefixed', () => { - const address = 'fdea65c8e26263f6d9a1b5de9555d2931a33b85'; - const result = util.isValidAddress(address); - expect(result).toStrictEqual(false); - }); - - it('should not allow less hex-prefixed', () => { - const address = '0xfdea65ce26263f6d9a1b5de9555d2931a33b85'; - const result = util.isValidAddress(address); - expect(result).toStrictEqual(false); - }); - - it('should recognize correct capitalized checksum', () => { - const address = '0xFDEa65C8e26263F6d9A1B5de9555D2931A33b825'; - const result = util.isValidAddress(address); - expect(result).toStrictEqual(true); - }); - - it('should recognize incorrect capitalized checksum', () => { - const address = '0xFDea65C8e26263F6d9A1B5de9555D2931A33b825'; - const result = util.isValidAddress(address); - expect(result).toStrictEqual(false); - }); - - it('should recognize this sample hashed address', () => { - const address = '0x5Fda30Bb72B8Dfe20e48A00dFc108d0915BE9Bb0'; - const result = util.isValidAddress(address); - const hashed = toChecksumAddress(address.toLowerCase()); - expect(hashed).toStrictEqual(address); - expect(result).toStrictEqual(true); - }); - }); - describe('isValidDomainName', () => { it('should return true when given a valid domain name', () => { expect(util.isValidDomainName('foo.bar')).toStrictEqual(true); @@ -239,36 +193,6 @@ describe('util', () => { }); describe('normalizing values', function () { - describe('#isHex', function () { - it('should return true when given a hex string', function () { - const result = util.isHex( - 'c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2', - ); - expect(result).toStrictEqual(true); - }); - - it('should return false when given a non-hex string', () => { - const result = util.isHex( - 'c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714imnotreal', - ); - expect(result).toStrictEqual(false); - }); - - it('should return false when given a string containing a non letter/number character', () => { - const result = util.isHex( - 'c3ab8ff13720!8ad9047dd39466b3c%8974e592c2fa383d4a396071imnotreal', - ); - expect(result).toStrictEqual(false); - }); - - it('should return true when given a hex string with hex-prefix', () => { - const result = util.isHex( - '0xc3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2', - ); - expect(result).toStrictEqual(true); - }); - }); - describe('#getRandomFileName', () => { it('should only return a string containing alphanumeric characters', () => { const result = util.getRandomFileName(); diff --git a/ui/pages/add-token/add-token.component.js b/ui/pages/add-token/add-token.component.js index 1aad2e2c8..8a6c77f14 100644 --- a/ui/pages/add-token/add-token.component.js +++ b/ui/pages/add-token/add-token.component.js @@ -1,15 +1,13 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import { - checkExistingAddresses, - isValidAddress, -} from '../../helpers/utils/util'; +import { checkExistingAddresses } from '../../helpers/utils/util'; import { tokenInfoGetter } from '../../helpers/utils/token-util'; import { CONFIRM_ADD_TOKEN_ROUTE } from '../../helpers/constants/routes'; import TextField from '../../components/ui/text-field'; import PageContainer from '../../components/ui/page-container'; import { Tabs, Tab } from '../../components/ui/tabs'; import { addHexPrefix } from '../../../app/scripts/lib/util'; +import { isValidHexAddress } from '../../../shared/modules/hexstring-utils'; import TokenList from './token-list'; import TokenSearch from './token-search'; @@ -167,7 +165,9 @@ class AddToken extends Component { autoFilled: false, }); - const addressIsValid = isValidAddress(customAddress); + const addressIsValid = isValidHexAddress(customAddress, { + allowNonPrefixed: false, + }); const standardAddress = addHexPrefix(customAddress).toLowerCase(); switch (true) { diff --git a/ui/pages/send/send-content/add-recipient/add-recipient.component.js b/ui/pages/send/send-content/add-recipient/add-recipient.component.js index 82ccfa1d4..da7999c94 100644 --- a/ui/pages/send/send-content/add-recipient/add-recipient.component.js +++ b/ui/pages/send/send-content/add-recipient/add-recipient.component.js @@ -2,13 +2,16 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import Fuse from 'fuse.js'; import Identicon from '../../../../components/ui/identicon'; -import { isValidAddress } from '../../../../helpers/utils/util'; import Dialog from '../../../../components/ui/dialog'; import ContactList from '../../../../components/app/contact-list'; import RecipientGroup from '../../../../components/app/contact-list/recipient-group/recipient-group.component'; import { ellipsify } from '../../send.utils'; import Button from '../../../../components/ui/button'; import Confusable from '../../../../components/ui/confusable'; +import { + isBurnAddress, + isValidHexAddress, +} from '../../../../../shared/modules/hexstring-utils'; export default class AddRecipient extends Component { static propTypes = { @@ -101,7 +104,10 @@ export default class AddRecipient extends Component { let content; - if (isValidAddress(query)) { + if ( + !isBurnAddress(query) && + isValidHexAddress(query, { mixedCaseUseChecksum: true }) + ) { content = this.renderExplicitAddress(query); } else if (ensResolution) { content = this.renderExplicitAddress( diff --git a/ui/pages/send/send-content/add-recipient/add-recipient.js b/ui/pages/send/send-content/add-recipient/add-recipient.js index 3ba0077c7..c1fee499b 100644 --- a/ui/pages/send/send-content/add-recipient/add-recipient.js +++ b/ui/pages/send/send-content/add-recipient/add-recipient.js @@ -11,18 +11,25 @@ import { } from '../../send.constants'; import { - isValidAddress, checkExistingAddresses, isValidDomainName, isOriginContractAddress, isDefaultMetaMaskChain, } from '../../../../helpers/utils/util'; +import { + isBurnAddress, + isValidHexAddress, +} from '../../../../../shared/modules/hexstring-utils'; export function getToErrorObject(to, sendTokenAddress, chainId) { let toError = null; if (!to) { toError = REQUIRED_ERROR; - } else if (!isValidAddress(to) && !isValidDomainName(to)) { + } else if ( + isBurnAddress(to) || + (!isValidHexAddress(to, { mixedCaseUseChecksum: true }) && + !isValidDomainName(to)) + ) { toError = isDefaultMetaMaskChain(chainId) ? INVALID_RECIPIENT_ADDRESS_ERROR : INVALID_RECIPIENT_ADDRESS_NOT_ETH_NETWORK_ERROR; diff --git a/ui/pages/send/send-content/add-recipient/add-recipient.utils.test.js b/ui/pages/send/send-content/add-recipient/add-recipient.utils.test.js index 954fc6ab8..9e57f781b 100644 --- a/ui/pages/send/send-content/add-recipient/add-recipient.utils.test.js +++ b/ui/pages/send/send-content/add-recipient/add-recipient.utils.test.js @@ -11,13 +11,19 @@ jest.mock('../../../../helpers/utils/util', () => ({ isDefaultMetaMaskChain: jest.fn().mockReturnValue(true), isEthNetwork: jest.fn().mockReturnValue(true), checkExistingAddresses: jest.fn().mockReturnValue(true), - isValidAddress: jest.fn((to) => Boolean(to.match(/^[0xabcdef123456798]+$/u))), isValidDomainName: jest.requireActual('../../../../helpers/utils/util') .isValidDomainName, isOriginContractAddress: jest.requireActual('../../../../helpers/utils/util') .isOriginContractAddress, })); +jest.mock('../../../../../shared/modules/hexstring-utils', () => ({ + isValidHexAddress: jest.fn((to) => + Boolean(to.match(/^[0xabcdef123456798]+$/u)), + ), + isBurnAddress: jest.fn(() => false), +})); + describe('add-recipient utils', () => { describe('getToErrorObject()', () => { it('should return a required error if "to" is falsy', () => { diff --git a/ui/pages/send/send-content/add-recipient/ens-input.component.js b/ui/pages/send/send-content/add-recipient/ens-input.component.js index 9d55d13a7..658ac9bde 100644 --- a/ui/pages/send/send-content/add-recipient/ens-input.component.js +++ b/ui/pages/send/send-content/add-recipient/ens-input.component.js @@ -7,13 +7,14 @@ import copyToClipboard from 'copy-to-clipboard/index'; import ENS from 'ethjs-ens'; import networkMap from 'ethereum-ens-network-map'; import log from 'loglevel'; +import { isHexString } from 'ethereumjs-util'; import { ellipsify } from '../../send.utils'; -import { - isValidDomainName, - isValidAddress, - isValidAddressHead, -} from '../../../../helpers/utils/util'; +import { isValidDomainName } from '../../../../helpers/utils/util'; import { MAINNET_NETWORK_ID } from '../../../../../shared/constants/network'; +import { + isBurnAddress, + isValidHexAddress, +} from '../../../../../shared/modules/hexstring-utils'; // Local Constants const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'; @@ -143,7 +144,10 @@ export default class EnsInput extends Component { onPaste = (event) => { event.clipboardData.items[0].getAsString((text) => { - if (isValidAddress(text)) { + if ( + !isBurnAddress(text) && + isValidHexAddress(text, { mixedCaseUseChecksum: true }) + ) { this.props.onPaste(text); } }); @@ -170,8 +174,11 @@ export default class EnsInput extends Component { if ( !networkHasEnsSupport && - !isValidAddress(input) && - !isValidAddressHead(input) + !( + isBurnAddress(input) === false && + isValidHexAddress(input, { mixedCaseUseChecksum: true }) + ) && + !isHexString(input) ) { updateEnsResolution(''); updateEnsResolutionError( @@ -182,7 +189,11 @@ export default class EnsInput extends Component { if (isValidDomainName(input)) { this.lookupEnsName(input); - } else if (onValidAddressTyped && isValidAddress(input)) { + } else if ( + onValidAddressTyped && + !isBurnAddress(input) && + isValidHexAddress(input, { mixedCaseUseChecksum: true }) + ) { onValidAddressTyped(input); } else { updateEnsResolution(''); diff --git a/ui/pages/send/send.component.js b/ui/pages/send/send.component.js index 56eafb1e7..6954f9cfc 100644 --- a/ui/pages/send/send.component.js +++ b/ui/pages/send/send.component.js @@ -1,7 +1,7 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { debounce } from 'lodash'; -import { isValidAddress } from '../../helpers/utils/util'; +import { isValidHexAddress } from '../../../shared/modules/hexstring-utils'; import { getAmountErrorObject, getGasFeeErrorObject, @@ -171,7 +171,7 @@ export default class SendTransactionScreen extends Component { if (qrCodeData) { if (qrCodeData.type === 'address') { scannedAddress = qrCodeData.values.address.toLowerCase(); - if (isValidAddress(scannedAddress)) { + if (isValidHexAddress(scannedAddress, { allowNonPrefixed: false })) { const currentAddress = prevTo?.toLowerCase(); if (currentAddress !== scannedAddress) { updateSendTo(scannedAddress); diff --git a/ui/pages/settings/contact-list-tab/add-contact/add-contact.component.js b/ui/pages/settings/contact-list-tab/add-contact/add-contact.component.js index 51640edf5..e454838d1 100644 --- a/ui/pages/settings/contact-list-tab/add-contact/add-contact.component.js +++ b/ui/pages/settings/contact-list-tab/add-contact/add-contact.component.js @@ -4,12 +4,13 @@ import { debounce } from 'lodash'; import Identicon from '../../../../components/ui/identicon'; import TextField from '../../../../components/ui/text-field'; import { CONTACT_LIST_ROUTE } from '../../../../helpers/constants/routes'; -import { - isValidAddress, - isValidDomainName, -} from '../../../../helpers/utils/util'; +import { isValidDomainName } from '../../../../helpers/utils/util'; import EnsInput from '../../../send/send-content/add-recipient/ens-input'; import PageContainerFooter from '../../../../components/ui/page-container/page-container-footer'; +import { + isBurnAddress, + isValidHexAddress, +} from '../../../../../shared/modules/hexstring-utils'; export default class AddContact extends PureComponent { static contextTypes = { @@ -53,7 +54,9 @@ export default class AddContact extends PureComponent { } validate = (address) => { - const valid = isValidAddress(address); + const valid = + !isBurnAddress(address) && + isValidHexAddress(address, { mixedCaseUseChecksum: true }); const validEnsAddress = isValidDomainName(address); if (valid || validEnsAddress || address === '') { diff --git a/ui/pages/settings/contact-list-tab/edit-contact/edit-contact.component.js b/ui/pages/settings/contact-list-tab/edit-contact/edit-contact.component.js index 0ccc1f64a..51419b302 100644 --- a/ui/pages/settings/contact-list-tab/edit-contact/edit-contact.component.js +++ b/ui/pages/settings/contact-list-tab/edit-contact/edit-contact.component.js @@ -4,8 +4,11 @@ import { Redirect } from 'react-router-dom'; import Identicon from '../../../../components/ui/identicon'; import Button from '../../../../components/ui/button/button.component'; import TextField from '../../../../components/ui/text-field'; -import { isValidAddress } from '../../../../helpers/utils/util'; import PageContainerFooter from '../../../../components/ui/page-container/page-container-footer'; +import { + isBurnAddress, + isValidHexAddress, +} from '../../../../../shared/modules/hexstring-utils'; export default class EditContact extends PureComponent { static contextTypes = { @@ -135,7 +138,12 @@ export default class EditContact extends PureComponent { this.state.newAddress !== address ) { // if the user makes a valid change to the address field, remove the original address - if (isValidAddress(this.state.newAddress)) { + if ( + !isBurnAddress(this.state.newAddress) && + isValidHexAddress(this.state.newAddress, { + mixedCaseUseChecksum: true, + }) + ) { await removeFromAddressBook(chainId, address); await addToAddressBook( this.state.newAddress, diff --git a/ui/pages/settings/settings.container.js b/ui/pages/settings/settings.container.js index a9aeb9d6f..bf428c4fc 100644 --- a/ui/pages/settings/settings.container.js +++ b/ui/pages/settings/settings.container.js @@ -2,10 +2,13 @@ import { compose } from 'redux'; import { connect } from 'react-redux'; import { withRouter } from 'react-router-dom'; import { getAddressBookEntryName } from '../../selectors'; -import { isValidAddress, isHex } from '../../helpers/utils/util'; import { ENVIRONMENT_TYPE_POPUP } from '../../../shared/constants/app'; import { getEnvironmentType } from '../../../app/scripts/lib/util'; import { getMostRecentOverviewPage } from '../../ducks/history/history'; +import { + isValidHexAddress, + isBurnAddress, +} from '../../../shared/modules/hexstring-utils'; import { ABOUT_US_ROUTE, @@ -64,7 +67,10 @@ const mapStateToProps = (state, ownProps) => { const addressName = getAddressBookEntryName( state, - isHex(pathNameTail) && isValidAddress(pathNameTail) ? pathNameTail : '', + !isBurnAddress(pathNameTail) && + isValidHexAddress(pathNameTail, { mixedCaseUseChecksum: true }) + ? pathNameTail + : '', ); return { diff --git a/ui/pages/swaps/swaps.util.js b/ui/pages/swaps/swaps.util.js index 45f200a2f..e72483f35 100644 --- a/ui/pages/swaps/swaps.util.js +++ b/ui/pages/swaps/swaps.util.js @@ -1,7 +1,6 @@ import log from 'loglevel'; import BigNumber from 'bignumber.js'; import abi from 'human-standard-token-abi'; -import { isValidAddress } from 'ethereumjs-util'; import { SWAPS_CHAINID_DEFAULT_TOKEN_MAP, METASWAP_CHAINID_API_HOST_MAP, @@ -35,6 +34,7 @@ import { formatCurrency } from '../../helpers/utils/confirm-tx.util'; import fetchWithCache from '../../helpers/utils/fetch-with-cache'; import { calcGasTotal } from '../send/send.utils'; +import { isValidHexAddress } from '../../../shared/modules/hexstring-utils'; const TOKEN_TRANSFER_LOG_TOPIC_HASH = '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'; @@ -74,8 +74,8 @@ const QUOTE_VALIDATORS = [ validator: (trade) => trade && validHex(trade.data) && - isValidAddress(trade.to) && - isValidAddress(trade.from) && + isValidHexAddress(trade.to, { allowNonPrefixed: false }) && + isValidHexAddress(trade.from, { allowNonPrefixed: false }) && truthyString(trade.value), }, { @@ -85,8 +85,8 @@ const QUOTE_VALIDATORS = [ approvalTx === null || (approvalTx && validHex(approvalTx.data) && - isValidAddress(approvalTx.to) && - isValidAddress(approvalTx.from)), + isValidHexAddress(approvalTx.to, { allowNonPrefixed: false }) && + isValidHexAddress(approvalTx.from, { allowNonPrefixed: false })), }, { property: 'sourceAmount', @@ -101,12 +101,12 @@ const QUOTE_VALIDATORS = [ { property: 'sourceToken', type: 'string', - validator: isValidAddress, + validator: (input) => isValidHexAddress(input, { allowNonPrefixed: false }), }, { property: 'destinationToken', type: 'string', - validator: isValidAddress, + validator: (input) => isValidHexAddress(input, { allowNonPrefixed: false }), }, { property: 'aggregator', @@ -146,7 +146,7 @@ const TOKEN_VALIDATORS = [ { property: 'address', type: 'string', - validator: isValidAddress, + validator: (input) => isValidHexAddress(input, { allowNonPrefixed: false }), }, { property: 'symbol',