From 33710ffbc4cba2dd8b9e5cce68d385ed22ad9cb9 Mon Sep 17 00:00:00 2001 From: Noah Zinsmeister Date: Fri, 3 Dec 2021 16:00:55 -0500 Subject: [PATCH 1/3] namehash -> safeNamehash where necessary --- src/hooks/useENSAddress.ts | 10 +++------- src/hooks/useENSAvatar.ts | 11 ++++------- src/hooks/useENSContentHash.ts | 10 +++------- src/hooks/useENSName.ts | 8 ++------ src/utils/safeNamehash.test.ts | 18 ++++++++++++++++++ src/utils/safeNamehash.ts | 12 ++++++++++++ 6 files changed, 42 insertions(+), 27 deletions(-) create mode 100644 src/utils/safeNamehash.test.ts create mode 100644 src/utils/safeNamehash.ts diff --git a/src/hooks/useENSAddress.ts b/src/hooks/useENSAddress.ts index a4565d9e95a..5a5eaee7918 100644 --- a/src/hooks/useENSAddress.ts +++ b/src/hooks/useENSAddress.ts @@ -1,5 +1,5 @@ -import { namehash } from '@ethersproject/hash' import { useMemo } from 'react' +import { safeNamehash } from 'utils/safeNamehash' import { useSingleCallResult } from '../state/multicall/hooks' import isZero from '../utils/isZero' @@ -12,12 +12,8 @@ import useDebounce from './useDebounce' export default function useENSAddress(ensName?: string | null): { loading: boolean; address: string | null } { const debouncedName = useDebounce(ensName, 200) const ensNodeArgument = useMemo(() => { - if (!debouncedName) return [undefined] - try { - return debouncedName ? [namehash(debouncedName)] : [undefined] - } catch (error) { - return [undefined] - } + if (debouncedName === null) return [undefined] + return [safeNamehash(debouncedName)] }, [debouncedName]) const registrarContract = useENSRegistrarContract(false) const resolverAddress = useSingleCallResult(registrarContract, 'resolver', ensNodeArgument) diff --git a/src/hooks/useENSAvatar.ts b/src/hooks/useENSAvatar.ts index 8e6d07d16bc..aa8ccece7ae 100644 --- a/src/hooks/useENSAvatar.ts +++ b/src/hooks/useENSAvatar.ts @@ -1,5 +1,5 @@ -import { namehash } from '@ethersproject/hash' import { useEffect, useMemo, useState } from 'react' +import { safeNamehash } from 'utils/safeNamehash' import uriToHttp from 'utils/uriToHttp' import { useSingleCallResult } from '../state/multicall/hooks' @@ -21,15 +21,12 @@ export default function useENSAvatar( const debouncedAddress = useDebounce(address, 200) const node = useMemo(() => { if (!debouncedAddress || !isAddress(debouncedAddress)) return undefined - try { - return debouncedAddress ? namehash(`${debouncedAddress.toLowerCase().substr(2)}.addr.reverse`) : undefined - } catch (error) { - return undefined - } + return safeNamehash(`${debouncedAddress.toLowerCase().substr(2)}.addr.reverse`) }, [debouncedAddress]) const addressAvatar = useAvatarFromNode(node) - const nameAvatar = useAvatarFromNode(namehash(useENSName(address).ENSName ?? '')) + const ENSName = useENSName(address).ENSName + const nameAvatar = useAvatarFromNode(ENSName ? safeNamehash(ENSName) : undefined) let avatar = addressAvatar.avatar || nameAvatar.avatar const nftAvatar = useAvatarFromNFT(avatar, enforceOwnership) diff --git a/src/hooks/useENSContentHash.ts b/src/hooks/useENSContentHash.ts index 9f002d092a5..7171d18a62e 100644 --- a/src/hooks/useENSContentHash.ts +++ b/src/hooks/useENSContentHash.ts @@ -1,5 +1,5 @@ -import { namehash } from '@ethersproject/hash' import { useMemo } from 'react' +import { safeNamehash } from 'utils/safeNamehash' import { useSingleCallResult } from '../state/multicall/hooks' import isZero from '../utils/isZero' @@ -10,12 +10,8 @@ import { useENSRegistrarContract, useENSResolverContract } from './useContract' */ export default function useENSContentHash(ensName?: string | null): { loading: boolean; contenthash: string | null } { const ensNodeArgument = useMemo(() => { - if (!ensName) return [undefined] - try { - return ensName ? [namehash(ensName)] : [undefined] - } catch (error) { - return [undefined] - } + if (ensName === null) return [undefined] + return [safeNamehash(ensName)] }, [ensName]) const registrarContract = useENSRegistrarContract(false) const resolverAddressResult = useSingleCallResult(registrarContract, 'resolver', ensNodeArgument) diff --git a/src/hooks/useENSName.ts b/src/hooks/useENSName.ts index f0b7d1d8bf0..14aa17c1f5a 100644 --- a/src/hooks/useENSName.ts +++ b/src/hooks/useENSName.ts @@ -1,5 +1,5 @@ -import { namehash } from '@ethersproject/hash' import { useMemo } from 'react' +import { safeNamehash } from 'utils/safeNamehash' import { useSingleCallResult } from '../state/multicall/hooks' import { isAddress } from '../utils' @@ -15,11 +15,7 @@ export default function useENSName(address?: string): { ENSName: string | null; const debouncedAddress = useDebounce(address, 200) const ensNodeArgument = useMemo(() => { if (!debouncedAddress || !isAddress(debouncedAddress)) return [undefined] - try { - return debouncedAddress ? [namehash(`${debouncedAddress.toLowerCase().substr(2)}.addr.reverse`)] : [undefined] - } catch (error) { - return [undefined] - } + return [safeNamehash(`${debouncedAddress.toLowerCase().substr(2)}.addr.reverse`)] }, [debouncedAddress]) const registrarContract = useENSRegistrarContract(false) const resolverAddress = useSingleCallResult(registrarContract, 'resolver', ensNodeArgument) diff --git a/src/utils/safeNamehash.test.ts b/src/utils/safeNamehash.test.ts new file mode 100644 index 00000000000..393c68ca7c3 --- /dev/null +++ b/src/utils/safeNamehash.test.ts @@ -0,0 +1,18 @@ +import { namehash } from '@ethersproject/hash' + +import { safeNamehash } from './safeNamehash' + +describe.only('#safeNamehash', () => { + it('#namehash fails', () => { + expect(() => namehash('🤔')).toThrow('STRINGPREP_CONTAINS_UNASSIGNED') + }) + + beforeEach(() => { + // eslint-disable-next-line @typescript-eslint/no-empty-function + jest.spyOn(console, 'debug').mockImplementation(() => {}) + }) + + it('works', () => { + expect(safeNamehash('🤔')).toEqual(undefined) + }) +}) diff --git a/src/utils/safeNamehash.ts b/src/utils/safeNamehash.ts new file mode 100644 index 00000000000..efa6af55954 --- /dev/null +++ b/src/utils/safeNamehash.ts @@ -0,0 +1,12 @@ +import { namehash } from '@ethersproject/hash' + +export function safeNamehash(name?: string): string | undefined { + if (typeof name === 'undefined') return undefined + + try { + return namehash(name) + } catch (error) { + console.debug(error) + return undefined + } +} From 37717d016dacfe9391d1ef7da315edd296d637f8 Mon Sep 17 00:00:00 2001 From: Noah Zinsmeister Date: Fri, 3 Dec 2021 16:12:24 -0500 Subject: [PATCH 2/3] cleanup --- src/hooks/useENSAddress.ts | 8 ++++---- src/hooks/useENSAvatar.ts | 5 +++-- src/hooks/useENSContentHash.ts | 5 +---- src/hooks/useENSName.ts | 4 ++-- src/utils/safeNamehash.test.ts | 9 ++++++--- 5 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/hooks/useENSAddress.ts b/src/hooks/useENSAddress.ts index 5a5eaee7918..8c5496a59ee 100644 --- a/src/hooks/useENSAddress.ts +++ b/src/hooks/useENSAddress.ts @@ -11,10 +11,10 @@ import useDebounce from './useDebounce' */ export default function useENSAddress(ensName?: string | null): { loading: boolean; address: string | null } { const debouncedName = useDebounce(ensName, 200) - const ensNodeArgument = useMemo(() => { - if (debouncedName === null) return [undefined] - return [safeNamehash(debouncedName)] - }, [debouncedName]) + const ensNodeArgument = useMemo( + () => [debouncedName === null ? undefined : safeNamehash(debouncedName)], + [debouncedName] + ) const registrarContract = useENSRegistrarContract(false) const resolverAddress = useSingleCallResult(registrarContract, 'resolver', ensNodeArgument) const resolverAddressResult = resolverAddress.result?.[0] diff --git a/src/hooks/useENSAvatar.ts b/src/hooks/useENSAvatar.ts index aa8ccece7ae..c7b584d24c3 100644 --- a/src/hooks/useENSAvatar.ts +++ b/src/hooks/useENSAvatar.ts @@ -1,3 +1,4 @@ +import { namehash } from '@ethersproject/hash' import { useEffect, useMemo, useState } from 'react' import { safeNamehash } from 'utils/safeNamehash' import uriToHttp from 'utils/uriToHttp' @@ -21,12 +22,12 @@ export default function useENSAvatar( const debouncedAddress = useDebounce(address, 200) const node = useMemo(() => { if (!debouncedAddress || !isAddress(debouncedAddress)) return undefined - return safeNamehash(`${debouncedAddress.toLowerCase().substr(2)}.addr.reverse`) + return namehash(`${debouncedAddress.toLowerCase().substr(2)}.addr.reverse`) }, [debouncedAddress]) const addressAvatar = useAvatarFromNode(node) const ENSName = useENSName(address).ENSName - const nameAvatar = useAvatarFromNode(ENSName ? safeNamehash(ENSName) : undefined) + const nameAvatar = useAvatarFromNode(ENSName === null ? undefined : safeNamehash(ENSName)) let avatar = addressAvatar.avatar || nameAvatar.avatar const nftAvatar = useAvatarFromNFT(avatar, enforceOwnership) diff --git a/src/hooks/useENSContentHash.ts b/src/hooks/useENSContentHash.ts index 7171d18a62e..612e7d7e7a8 100644 --- a/src/hooks/useENSContentHash.ts +++ b/src/hooks/useENSContentHash.ts @@ -9,10 +9,7 @@ import { useENSRegistrarContract, useENSResolverContract } from './useContract' * Does a lookup for an ENS name to find its contenthash. */ export default function useENSContentHash(ensName?: string | null): { loading: boolean; contenthash: string | null } { - const ensNodeArgument = useMemo(() => { - if (ensName === null) return [undefined] - return [safeNamehash(ensName)] - }, [ensName]) + const ensNodeArgument = useMemo(() => [ensName === null ? undefined : safeNamehash(ensName)], [ensName]) const registrarContract = useENSRegistrarContract(false) const resolverAddressResult = useSingleCallResult(registrarContract, 'resolver', ensNodeArgument) const resolverAddress = resolverAddressResult.result?.[0] diff --git a/src/hooks/useENSName.ts b/src/hooks/useENSName.ts index 14aa17c1f5a..414ce9c68f9 100644 --- a/src/hooks/useENSName.ts +++ b/src/hooks/useENSName.ts @@ -1,5 +1,5 @@ +import { namehash } from '@ethersproject/hash' import { useMemo } from 'react' -import { safeNamehash } from 'utils/safeNamehash' import { useSingleCallResult } from '../state/multicall/hooks' import { isAddress } from '../utils' @@ -15,7 +15,7 @@ export default function useENSName(address?: string): { ENSName: string | null; const debouncedAddress = useDebounce(address, 200) const ensNodeArgument = useMemo(() => { if (!debouncedAddress || !isAddress(debouncedAddress)) return [undefined] - return [safeNamehash(`${debouncedAddress.toLowerCase().substr(2)}.addr.reverse`)] + return [namehash(`${debouncedAddress.toLowerCase().substr(2)}.addr.reverse`)] }, [debouncedAddress]) const registrarContract = useENSRegistrarContract(false) const resolverAddress = useSingleCallResult(registrarContract, 'resolver', ensNodeArgument) diff --git a/src/utils/safeNamehash.test.ts b/src/utils/safeNamehash.test.ts index 393c68ca7c3..babcd089cfa 100644 --- a/src/utils/safeNamehash.test.ts +++ b/src/utils/safeNamehash.test.ts @@ -2,17 +2,20 @@ import { namehash } from '@ethersproject/hash' import { safeNamehash } from './safeNamehash' -describe.only('#safeNamehash', () => { +describe('#safeNamehash', () => { + const emoji = '🤔' + it('#namehash fails', () => { - expect(() => namehash('🤔')).toThrow('STRINGPREP_CONTAINS_UNASSIGNED') + expect(() => namehash(emoji)).toThrow('STRINGPREP_CONTAINS_UNASSIGNED') }) + // suppress console.debug for the next test beforeEach(() => { // eslint-disable-next-line @typescript-eslint/no-empty-function jest.spyOn(console, 'debug').mockImplementation(() => {}) }) it('works', () => { - expect(safeNamehash('🤔')).toEqual(undefined) + expect(safeNamehash(emoji)).toEqual(undefined) }) }) From b42fd46eebf61e82759621c6700222245e9fe843 Mon Sep 17 00:00:00 2001 From: Noah Zinsmeister Date: Fri, 3 Dec 2021 16:39:53 -0500 Subject: [PATCH 3/3] address comment --- src/utils/safeNamehash.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/safeNamehash.ts b/src/utils/safeNamehash.ts index efa6af55954..0dc17076a5b 100644 --- a/src/utils/safeNamehash.ts +++ b/src/utils/safeNamehash.ts @@ -1,7 +1,7 @@ import { namehash } from '@ethersproject/hash' export function safeNamehash(name?: string): string | undefined { - if (typeof name === 'undefined') return undefined + if (name === undefined) return undefined try { return namehash(name)