Skip to content

Commit 477befd

Browse files
committed
fix(ensjs): enforce normalization in getName
getName previously coerced the reverse-resolved name via normalise(), silently returning a different name than what was resolved. It now uses viem's normalize() to verify the name is already normalised and returns null otherwise, matching viem's getEnsName (wevm/viem#4756) and v5. Applies to both the match and allowMismatch paths. Preserves the encode/decode batching API and the existing return body. Closes WEB-533
1 parent bc7293e commit 477befd

3 files changed

Lines changed: 92 additions & 5 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@ensdomains/ensjs": patch
3+
---
4+
5+
`getName` now enforces normalization: it only returns a primary name if the value returned by reverse resolution is already in its normalised form, returning `null` otherwise (instead of silently coercing it). This mirrors viem's `getEnsName` behaviour (wevm/viem#4756) and brings v4 in line with v5 (WEB-533). The normalization check is applied to both the matching and `allowMismatch` paths.

packages/ensjs/src/functions/public/getName.test.ts

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,15 @@ import {
44
RawContractError,
55
bytesToHex,
66
encodeErrorResult,
7+
encodeFunctionResult,
8+
zeroAddress,
79
} from 'viem'
810
import { afterEach, beforeAll, beforeEach, describe, expect, it } from 'vitest'
911
import type { ClientWithEns } from '../../contracts/consts.js'
10-
import { universalResolverErrors } from '../../contracts/universalResolver.js'
12+
import {
13+
universalResolverErrors,
14+
universalResolverReverseSnippet,
15+
} from '../../contracts/universalResolver.js'
1116
import {
1217
deploymentAddresses,
1318
publicClient,
@@ -150,6 +155,66 @@ describe('getName', () => {
150155
Version: viem@2.37.12]
151156
`)
152157
})
158+
it('should return null for an unnormalised name in the match path', async () => {
159+
const result = await getName.decode(
160+
{} as ClientWithEns,
161+
encodeFunctionResult({
162+
abi: universalResolverReverseSnippet,
163+
functionName: 'reverse',
164+
result: ['Nick.eth', zeroAddress, zeroAddress],
165+
}),
166+
{
167+
address: '0x1234567890abcdef',
168+
args: ['0x', 60n],
169+
},
170+
{
171+
address: accounts[0],
172+
strict: false,
173+
},
174+
)
175+
expect(result).toBeNull()
176+
})
177+
it('should return a normalised match unchanged', async () => {
178+
const result = await getName.decode(
179+
{} as ClientWithEns,
180+
encodeFunctionResult({
181+
abi: universalResolverReverseSnippet,
182+
functionName: 'reverse',
183+
result: ['nick.eth', zeroAddress, zeroAddress],
184+
}),
185+
{
186+
address: '0x1234567890abcdef',
187+
args: ['0x', 60n],
188+
},
189+
{
190+
address: accounts[0],
191+
strict: false,
192+
},
193+
)
194+
expect(result).toMatchObject({ name: 'nick.eth', match: true })
195+
})
196+
it('should return null for an unnormalised name in the mismatch path', async () => {
197+
const result = await getName.decode(
198+
{} as ClientWithEns,
199+
new RawContractError({
200+
data: encodeErrorResult({
201+
abi: universalResolverErrors,
202+
errorName: 'ReverseAddressMismatch',
203+
args: ['Nick.eth', accounts[0]],
204+
}),
205+
}),
206+
{
207+
address: '0x1234567890abcdef',
208+
args: ['0x', 60n],
209+
},
210+
{
211+
address: accounts[0],
212+
allowMismatch: true,
213+
strict: false,
214+
},
215+
)
216+
expect(result).toBeNull()
217+
})
153218
it('should not return unnormalised name', async () => {
154219
const tx1 = await createSubname(walletClient, {
155220
name: 'suB.with-profile.eth',

packages/ensjs/src/functions/public/getName.ts

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
encodeFunctionData,
99
zeroAddress,
1010
} from 'viem'
11+
import { normalize } from 'viem/ens'
1112
import type { ClientWithEns } from '../../contracts/consts.js'
1213
import { getChainContractAddress } from '../../contracts/getChainContractAddress.js'
1314
import {
@@ -24,7 +25,21 @@ import {
2425
generateFunction,
2526
} from '../../utils/generateFunction.js'
2627
import { getRevertErrorData } from '../../utils/getRevertErrorData.js'
27-
import { normalise } from '../../utils/normalise.js'
28+
29+
/**
30+
* Checks whether a name is already normalised, without coercing it.
31+
*
32+
* Mirrors viem's `getEnsName` normalization enforcement (wevm/viem#4756):
33+
* a name is only returned if it is already in its normalised form. Returns
34+
* `false` if the name cannot be normalised (i.e. `normalize` throws).
35+
*/
36+
const isNormalised = (name: string): boolean => {
37+
try {
38+
return name === normalize(name)
39+
} catch {
40+
return false
41+
}
42+
}
2843

2944
type GetNameCoinTypeParameters = {
3045
coinType: number
@@ -134,8 +149,10 @@ const decode = async (
134149
data: errorData,
135150
})
136151
if (decodedError.errorName !== 'ReverseAddressMismatch') return null
152+
const [name] = decodedError.args
153+
if (!isNormalised(name)) return null
137154
return {
138-
name: decodedError.args[0],
155+
name,
139156
match: false,
140157
reverseResolverAddress: zeroAddress,
141158
resolverAddress: zeroAddress,
@@ -155,9 +172,9 @@ const decode = async (
155172

156173
if (!unnormalisedName) return null
157174

158-
const normalisedName = normalise(unnormalisedName)
175+
if (!isNormalised(unnormalisedName)) return null
159176
return {
160-
name: normalisedName,
177+
name: unnormalisedName,
161178
match: true,
162179
reverseResolverAddress,
163180
resolverAddress,

0 commit comments

Comments
 (0)