From 5d7fd35e98cf0875888990f6e7577f4fa88641d7 Mon Sep 17 00:00:00 2001 From: storywithoutend Date: Tue, 7 Feb 2023 00:31:37 +0800 Subject: [PATCH 1/4] update deleteSubname and transferSubname --- packages/ensjs/deploy/00_register_wrapped.ts | 7 +- .../ensjs/src/functions/deleteSubname.test.ts | 116 +- packages/ensjs/src/functions/deleteSubname.ts | 62 +- .../ensjs/src/functions/getSubnames.test.ts | 1622 ++++++++++++----- .../src/functions/transferSubname.test.ts | 102 +- .../ensjs/src/functions/transferSubname.ts | 64 +- packages/ensjs/src/index.ts | 4 +- packages/ensjs/src/utils/registry.ts | 14 + 8 files changed, 1510 insertions(+), 481 deletions(-) create mode 100644 packages/ensjs/src/utils/registry.ts diff --git a/packages/ensjs/deploy/00_register_wrapped.ts b/packages/ensjs/deploy/00_register_wrapped.ts index 4a86e67c..70cf3740 100644 --- a/packages/ensjs/deploy/00_register_wrapped.ts +++ b/packages/ensjs/deploy/00_register_wrapped.ts @@ -26,7 +26,12 @@ const names: { { label: 'wrapped-with-subnames', namedOwner: 'owner', - subnames: [{ label: 'test', namedOwner: 'owner2' }], + subnames: [ + { label: 'test', namedOwner: 'owner2' }, + { label: 'legacy', namedOwner: 'owner2' }, + { label: 'xyz', namedOwner: 'owner2' }, + { label: 'addr', namedOwner: 'owner2' }, + ], }, { label: 'expired-wrapped', diff --git a/packages/ensjs/src/functions/deleteSubname.test.ts b/packages/ensjs/src/functions/deleteSubname.test.ts index 701f0ecc..f500d5dd 100644 --- a/packages/ensjs/src/functions/deleteSubname.test.ts +++ b/packages/ensjs/src/functions/deleteSubname.test.ts @@ -1,12 +1,16 @@ +import { ethers } from 'ethers' import { ENS } from '..' import setup from '../tests/setup' import { namehash } from '../utils/normalise' let ensInstance: ENS let revert: Awaited>['revert'] +let provider: ethers.providers.JsonRpcProvider +let accounts: string[] beforeAll(async () => { - ;({ ensInstance, revert } = await setup()) + ;({ ensInstance, revert, provider } = await setup()) + accounts = await provider.listAccounts() }) afterAll(async () => { @@ -30,7 +34,7 @@ describe('deleteSubname', () => { expect(result).toBe('0x0000000000000000000000000000000000000000') }) - it('should allow deleting a subname on the nameWrapper', async () => { + it('should allow deleting a subname on the nameWrapper by parent owner', async () => { const nameWrapper = await ensInstance.contracts!.getNameWrapper()! const tx = await ensInstance.deleteSubname( @@ -49,6 +53,114 @@ describe('deleteSubname', () => { expect(result).toBe('0x0000000000000000000000000000000000000000') }) + it('should NOT allow deleting a subname on the nameWrapper by name owner', async () => { + const nameWrapper = await ensInstance.contracts!.getNameWrapper()! + + const checkOwner = await nameWrapper.ownerOf( + namehash('addr.wrapped-with-subnames.eth'), + ) + expect(checkOwner).toBe(accounts[2]) + + expect( + ensInstance.deleteSubname('addr.wrapped-with-subnames.eth', { + contract: 'nameWrapper', + addressOrIndex: 2, + }), + ).rejects.toThrow() + + const result = await nameWrapper.ownerOf( + namehash('addr.wrapped-with-subnames.eth'), + ) + expect(result).toBe(accounts[2]) + }) + + it('should allow deleting a subname on the nameWrapper with PCC burned by name owner', async () => { + const nameWrapper = await ensInstance.contracts!.getNameWrapper()! + + const tx0 = await ensInstance.setFuses('wrapped-with-subnames.eth', { + named: ['CANNOT_UNWRAP'], + addressOrIndex: 1, + }) + expect(tx0).toBeTruthy() + await tx0.wait() + + const tx1 = await ensInstance.setChildFuses( + 'xyz.wrapped-with-subnames.eth', + { + fuses: { + parent: { + named: ['PARENT_CANNOT_CONTROL'], + }, + }, + addressOrIndex: 1, + }, + ) + expect(tx1).toBeTruthy() + await tx1.wait() + + const checkOwner = await nameWrapper.ownerOf( + namehash('xyz.wrapped-with-subnames.eth'), + ) + expect(checkOwner).toBe(accounts[2]) + + const tx = await ensInstance.deleteSubname( + 'xyz.wrapped-with-subnames.eth', + { + contract: 'nameWrapper', + addressOrIndex: 2, + }, + ) + expect(tx).toBeTruthy() + await tx.wait() + + const result = await nameWrapper.ownerOf( + namehash('xyz.wrapped-with-subnames.eth'), + ) + expect(result).toBe('0x0000000000000000000000000000000000000000') + }) + + it('should NOT allow deleting a subname on the nameWrapper with PCC burned by parent owner', async () => { + const nameWrapper = await ensInstance.contracts!.getNameWrapper()! + + const tx0 = await ensInstance.setFuses('wrapped-with-subnames.eth', { + named: ['CANNOT_UNWRAP'], + addressOrIndex: 1, + }) + expect(tx0).toBeTruthy() + await tx0.wait() + + const tx1 = await ensInstance.setChildFuses( + 'legacy.wrapped-with-subnames.eth', + { + fuses: { + parent: { + named: ['PARENT_CANNOT_CONTROL'], + }, + }, + addressOrIndex: 1, + }, + ) + expect(tx1).toBeTruthy() + await tx1.wait() + + const checkOwner = await nameWrapper.ownerOf( + namehash('legacy.wrapped-with-subnames.eth'), + ) + expect(checkOwner).toBe(accounts[2]) + + await expect( + ensInstance.deleteSubname('legacy.wrapped-with-subnames.eth', { + contract: 'nameWrapper', + addressOrIndex: 1, + }), + ).rejects.toThrow() + + const result = await nameWrapper.ownerOf( + namehash('legacy.wrapped-with-subnames.eth'), + ) + expect(result).toBe(accounts[2]) + }) + it('should not allow deleting 1LD', async () => { await expect( ensInstance.deleteSubname('eth', { diff --git a/packages/ensjs/src/functions/deleteSubname.ts b/packages/ensjs/src/functions/deleteSubname.ts index 8cb09f53..4a754c72 100644 --- a/packages/ensjs/src/functions/deleteSubname.ts +++ b/packages/ensjs/src/functions/deleteSubname.ts @@ -6,24 +6,62 @@ type Args = { contract: 'registry' | 'nameWrapper' } +const deleteWrappedSubname = async ( + { + contracts, + signer, + getWrapperData, + }: ENSArgs<'contracts' | 'signer' | 'getWrapperData'>, + name: string, +) => { + const nameWrapper = (await contracts!.getNameWrapper()!).connect(signer) + const data = await getWrapperData(name) + const isPCCBurned = !!data?.parent?.PARENT_CANNOT_CONTROL + + if (isPCCBurned) { + const node = namehash(name) + return nameWrapper.populateTransaction.setRecord( + node, + '0x0000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000', + 0, + ) + } + + const labels = name.split('.') + const label = labels.shift() as string + const parentNodehash = namehash(labels.join('.')) + return nameWrapper.populateTransaction.setSubnodeRecord( + parentNodehash, + label, + '0x0000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000', + 0, + 0, + 0, + ) +} + export default async function ( - { contracts, signer }: ENSArgs<'contracts' | 'signer' | 'getExpiry'>, + { + contracts, + signer, + getWrapperData, + }: ENSArgs<'contracts' | 'signer' | 'getExpiry' | 'getWrapperData'>, name: string, { contract }: Args, ) { const labels = name.split('.') - if (labels.length < 3) { throw new Error(`${name} is not a valid subname`) } - const label = labels.shift() as string - const labelhash = solidityKeccak256(['string'], [label]) - const parentNodehash = namehash(labels.join('.')) - switch (contract) { case 'registry': { const registry = (await contracts!.getRegistry()!).connect(signer) + const label = labels.shift() as string + const labelhash = solidityKeccak256(['string'], [label]) + const parentNodehash = namehash(labels.join('.')) return registry.populateTransaction.setSubnodeRecord( parentNodehash, @@ -34,17 +72,7 @@ export default async function ( ) } case 'nameWrapper': { - const nameWrapper = (await contracts!.getNameWrapper()!).connect(signer) - - return nameWrapper.populateTransaction.setSubnodeRecord( - parentNodehash, - label, - '0x0000000000000000000000000000000000000000', - '0x0000000000000000000000000000000000000000', - 0, - 0, - 0, - ) + return deleteWrappedSubname({ contracts, signer, getWrapperData }, name) } default: { throw new Error(`Unknown contract: ${contract}`) diff --git a/packages/ensjs/src/functions/getSubnames.test.ts b/packages/ensjs/src/functions/getSubnames.test.ts index 6de809d7..1b9b08cc 100644 --- a/packages/ensjs/src/functions/getSubnames.test.ts +++ b/packages/ensjs/src/functions/getSubnames.test.ts @@ -11,139 +11,21 @@ const testProperties = (obj: object, ...properties: string[]) => properties.map((property) => expect(obj).toHaveProperty(property)) describe('getSubnames', () => { - it('should get the subnames for a name ordered by createdAt in desc order', async () => { - const result = await ensInstance.getSubnames({ - name: 'with-subnames.eth', - pageSize: 10, - orderBy: 'createdAt', - orderDirection: 'desc', - }) - expect(result).toBeTruthy() - expect(result.subnames.length).toBe(4) - expect(result.subnameCount).toBe(4) - expect(result.subnames[0].name).toEqual('addr.with-subnames.eth') - expect(result.subnames[1].name).toEqual('xyz.with-subnames.eth') - expect(result.subnames[2].name).toEqual('legacy.with-subnames.eth') - expect(result.subnames[3].name).toEqual('test.with-subnames.eth') - expect( - result.subnames.every((subname, i, arr) => { - if (arr[i + 1]) { - return (subname as any).createdAt >= (arr[i + 1] as any).createdAt - } - return true - }), - ).toBe(true) - testProperties( - result.subnames[0], - 'id', - 'labelName', - 'labelhash', - 'name', - 'isMigrated', - 'owner', - 'truncatedName', - ) - }) - - it('should get the subnames for a name ordered by createdAt in asc order', async () => { - const result = await ensInstance.getSubnames({ - name: 'with-subnames.eth', - pageSize: 10, - orderBy: 'createdAt', - orderDirection: 'asc', - }) - expect(result).toBeTruthy() - expect(result.subnames.length).toBe(4) - expect(result.subnameCount).toBe(4) - expect(result.subnames[0].name).toEqual('test.with-subnames.eth') - expect(result.subnames[1].name).toEqual('legacy.with-subnames.eth') - expect(result.subnames[2].name).toEqual('xyz.with-subnames.eth') - expect(result.subnames[3].name).toEqual('addr.with-subnames.eth') - expect( - result.subnames.every((subname, i, arr) => { - if (arr[i + 1]) { - return (subname as any).createdAt <= (arr[i + 1] as any).createdAt - } - return true - }), - ).toBe(true) - testProperties( - result.subnames[0], - 'id', - 'labelName', - 'labelhash', - 'name', - 'isMigrated', - 'owner', - 'truncatedName', - ) - }) - - it('should get the subnames for a name by labelName in desc order', async () => { - const result = await ensInstance.getSubnames({ - name: 'with-subnames.eth', - pageSize: 10, - orderBy: 'labelName', - orderDirection: 'desc', - }) - expect(result).toBeTruthy() - expect(result.subnames.length).toBe(4) - expect(result.subnameCount).toBe(4) - expect(result.subnames[0].name).toEqual('xyz.with-subnames.eth') - expect(result.subnames[1].name).toEqual('test.with-subnames.eth') - expect(result.subnames[2].name).toEqual('legacy.with-subnames.eth') - expect(result.subnames[3].name).toEqual('addr.with-subnames.eth') - testProperties( - result.subnames[0], - 'id', - 'labelName', - 'labelhash', - 'name', - 'isMigrated', - 'owner', - 'truncatedName', - ) - }) - - it('should get the subnames for a name by labelName in asc order', async () => { - const result = await ensInstance.getSubnames({ - name: 'with-subnames.eth', - pageSize: 10, - orderBy: 'labelName', - orderDirection: 'asc', - }) - expect(result).toBeTruthy() - expect(result.subnames.length).toBe(4) - expect(result.subnameCount).toBe(4) - expect(result.subnames[0].name).toEqual('addr.with-subnames.eth') - expect(result.subnames[1].name).toEqual('legacy.with-subnames.eth') - expect(result.subnames[2].name).toEqual('test.with-subnames.eth') - expect(result.subnames[3].name).toEqual('xyz.with-subnames.eth') - testProperties( - result.subnames[0], - 'id', - 'labelName', - 'labelhash', - 'name', - 'isMigrated', - 'owner', - 'truncatedName', - ) - }) - - describe('with pagination', () => { - it('should get paginated subnames for a name ordered by createdAt in desc order', async () => { + describe('unwrapped', () => { + it('should get the subnames for a name ordered by createdAt in desc order', async () => { const result = await ensInstance.getSubnames({ name: 'with-subnames.eth', - pageSize: 2, + pageSize: 10, orderBy: 'createdAt', orderDirection: 'desc', }) expect(result).toBeTruthy() - expect(result.subnames.length).toBe(2) + expect(result.subnames.length).toBe(4) expect(result.subnameCount).toBe(4) expect(result.subnames[0].name).toEqual('addr.with-subnames.eth') expect(result.subnames[1].name).toEqual('xyz.with-subnames.eth') + expect(result.subnames[2].name).toEqual('legacy.with-subnames.eth') + expect(result.subnames[3].name).toEqual('test.with-subnames.eth') expect( result.subnames.every((subname, i, arr) => { if (arr[i + 1]) { @@ -162,50 +44,22 @@ describe('getSubnames', () => { 'owner', 'truncatedName', ) - const result2 = await ensInstance.getSubnames({ - name: 'with-subnames.eth', - pageSize: 2, - orderBy: 'createdAt', - orderDirection: 'desc', - lastSubnames: result.subnames, - }) - expect(result2).toBeTruthy() - expect(result2.subnames.length).toBe(2) - expect(result2.subnameCount).toBe(4) - expect(result2.subnames[0].name).toEqual('legacy.with-subnames.eth') - expect(result2.subnames[1].name).toEqual('test.with-subnames.eth') - expect( - result2.subnames.every((subname, i, arr) => { - if (arr[i + 1]) { - return (subname as any).createdAt >= (arr[i + 1] as any).createdAt - } - return true - }), - ).toBe(true) - testProperties( - result2.subnames[0], - 'id', - 'labelName', - 'labelhash', - 'name', - 'isMigrated', - 'owner', - 'truncatedName', - ) }) - it('should get paginated subnames for a name ordered by createdAt in asc order', async () => { + it('should get the subnames for a name ordered by createdAt in asc order', async () => { const result = await ensInstance.getSubnames({ name: 'with-subnames.eth', - pageSize: 2, + pageSize: 10, orderBy: 'createdAt', orderDirection: 'asc', }) expect(result).toBeTruthy() - expect(result.subnames.length).toBe(2) + expect(result.subnames.length).toBe(4) expect(result.subnameCount).toBe(4) expect(result.subnames[0].name).toEqual('test.with-subnames.eth') expect(result.subnames[1].name).toEqual('legacy.with-subnames.eth') + expect(result.subnames[2].name).toEqual('xyz.with-subnames.eth') + expect(result.subnames[3].name).toEqual('addr.with-subnames.eth') expect( result.subnames.every((subname, i, arr) => { if (arr[i + 1]) { @@ -224,74 +78,22 @@ describe('getSubnames', () => { 'owner', 'truncatedName', ) - const result2 = await ensInstance.getSubnames({ - name: 'with-subnames.eth', - page: 0, - pageSize: 2, - orderBy: 'createdAt', - orderDirection: 'asc', - lastSubnames: result.subnames, - }) - - expect(result2).toBeTruthy() - expect(result2.subnames.length).toBe(2) - expect(result2.subnameCount).toBe(4) - expect(result2.subnames[0].name).toEqual('xyz.with-subnames.eth') - expect(result2.subnames[1].name).toEqual('addr.with-subnames.eth') - expect( - result2.subnames.every((subname, i, arr) => { - if (arr[i + 1]) { - return (subname as any).createdAt <= (arr[i + 1] as any).createdAt - } - return true - }), - ).toBe(true) - testProperties( - result2.subnames[0], - 'id', - 'labelName', - 'labelhash', - 'name', - 'isMigrated', - 'owner', - 'truncatedName', - ) }) - it('should get paginated subnames for a name by labelName in desc order', async () => { + it('should get the subnames for a name by labelName in desc order', async () => { const result = await ensInstance.getSubnames({ name: 'with-subnames.eth', - pageSize: 2, + pageSize: 10, orderBy: 'labelName', orderDirection: 'desc', }) expect(result).toBeTruthy() - expect(result.subnames.length).toBe(2) + expect(result.subnames.length).toBe(4) expect(result.subnameCount).toBe(4) expect(result.subnames[0].name).toEqual('xyz.with-subnames.eth') expect(result.subnames[1].name).toEqual('test.with-subnames.eth') - testProperties( - result.subnames[0], - 'id', - 'labelName', - 'labelhash', - 'name', - 'isMigrated', - 'owner', - 'truncatedName', - ) - const result2 = await ensInstance.getSubnames({ - name: 'with-subnames.eth', - pageSize: 2, - orderBy: 'labelName', - orderDirection: 'desc', - lastSubnames: result.subnames, - }) - expect(result2).toBeTruthy() - expect(result2.subnames.length).toBe(2) - expect(result2.subnameCount).toBe(4) - expect(result2.subnames[0].name).toEqual('legacy.with-subnames.eth') - expect(result2.subnames[1].name).toEqual('addr.with-subnames.eth') + expect(result.subnames[2].name).toEqual('legacy.with-subnames.eth') + expect(result.subnames[3].name).toEqual('addr.with-subnames.eth') testProperties( result.subnames[0], 'id', @@ -304,19 +106,20 @@ describe('getSubnames', () => { ) }) - it('should get paginated subnames for a name by labelName in asc order', async () => { + it('should get the subnames for a name by labelName in asc order', async () => { const result = await ensInstance.getSubnames({ name: 'with-subnames.eth', - pageSize: 2, + pageSize: 10, orderBy: 'labelName', orderDirection: 'asc', }) expect(result).toBeTruthy() - expect(result.subnames.length).toBe(2) + expect(result.subnames.length).toBe(4) expect(result.subnameCount).toBe(4) expect(result.subnames[0].name).toEqual('addr.with-subnames.eth') expect(result.subnames[1].name).toEqual('legacy.with-subnames.eth') - + expect(result.subnames[2].name).toEqual('test.with-subnames.eth') + expect(result.subnames[3].name).toEqual('xyz.with-subnames.eth') testProperties( result.subnames[0], 'id', @@ -327,47 +130,568 @@ describe('getSubnames', () => { 'owner', 'truncatedName', ) + }) - const result2 = await ensInstance.getSubnames({ - name: 'with-subnames.eth', - pageSize: 2, - orderBy: 'labelName', - orderDirection: 'asc', - lastSubnames: result.subnames, + describe('with pagination', () => { + it('should get paginated subnames for a name ordered by createdAt in desc order', async () => { + const result = await ensInstance.getSubnames({ + name: 'with-subnames.eth', + pageSize: 2, + orderBy: 'createdAt', + orderDirection: 'desc', + }) + expect(result).toBeTruthy() + expect(result.subnames.length).toBe(2) + expect(result.subnameCount).toBe(4) + expect(result.subnames[0].name).toEqual('addr.with-subnames.eth') + expect(result.subnames[1].name).toEqual('xyz.with-subnames.eth') + expect( + result.subnames.every((subname, i, arr) => { + if (arr[i + 1]) { + return (subname as any).createdAt >= (arr[i + 1] as any).createdAt + } + return true + }), + ).toBe(true) + testProperties( + result.subnames[0], + 'id', + 'labelName', + 'labelhash', + 'name', + 'isMigrated', + 'owner', + 'truncatedName', + ) + const result2 = await ensInstance.getSubnames({ + name: 'with-subnames.eth', + pageSize: 2, + orderBy: 'createdAt', + orderDirection: 'desc', + lastSubnames: result.subnames, + }) + expect(result2).toBeTruthy() + expect(result2.subnames.length).toBe(2) + expect(result2.subnameCount).toBe(4) + expect(result2.subnames[0].name).toEqual('legacy.with-subnames.eth') + expect(result2.subnames[1].name).toEqual('test.with-subnames.eth') + expect( + result2.subnames.every((subname, i, arr) => { + if (arr[i + 1]) { + return (subname as any).createdAt >= (arr[i + 1] as any).createdAt + } + return true + }), + ).toBe(true) + testProperties( + result2.subnames[0], + 'id', + 'labelName', + 'labelhash', + 'name', + 'isMigrated', + 'owner', + 'truncatedName', + ) }) - expect(result2).toBeTruthy() - expect(result2.subnames.length).toBe(2) - expect(result2.subnameCount).toBe(4) - expect(result2.subnames[0].name).toEqual('test.with-subnames.eth') - expect(result2.subnames[1].name).toEqual('xyz.with-subnames.eth') - testProperties( - result2.subnames[0], - 'id', - 'labelName', - 'labelhash', - 'name', - 'isMigrated', - 'owner', - 'truncatedName', - ) + it('should get paginated subnames for a name ordered by createdAt in asc order', async () => { + const result = await ensInstance.getSubnames({ + name: 'with-subnames.eth', + pageSize: 2, + orderBy: 'createdAt', + orderDirection: 'asc', + }) + expect(result).toBeTruthy() + expect(result.subnames.length).toBe(2) + expect(result.subnameCount).toBe(4) + expect(result.subnames[0].name).toEqual('test.with-subnames.eth') + expect(result.subnames[1].name).toEqual('legacy.with-subnames.eth') + expect( + result.subnames.every((subname, i, arr) => { + if (arr[i + 1]) { + return (subname as any).createdAt <= (arr[i + 1] as any).createdAt + } + return true + }), + ).toBe(true) + testProperties( + result.subnames[0], + 'id', + 'labelName', + 'labelhash', + 'name', + 'isMigrated', + 'owner', + 'truncatedName', + ) + const result2 = await ensInstance.getSubnames({ + name: 'with-subnames.eth', + page: 0, + pageSize: 2, + orderBy: 'createdAt', + orderDirection: 'asc', + lastSubnames: result.subnames, + }) + + expect(result2).toBeTruthy() + expect(result2.subnames.length).toBe(2) + expect(result2.subnameCount).toBe(4) + expect(result2.subnames[0].name).toEqual('xyz.with-subnames.eth') + expect(result2.subnames[1].name).toEqual('addr.with-subnames.eth') + expect( + result2.subnames.every((subname, i, arr) => { + if (arr[i + 1]) { + return (subname as any).createdAt <= (arr[i + 1] as any).createdAt + } + return true + }), + ).toBe(true) + testProperties( + result2.subnames[0], + 'id', + 'labelName', + 'labelhash', + 'name', + 'isMigrated', + 'owner', + 'truncatedName', + ) + }) + + it('should get paginated subnames for a name by labelName in desc order', async () => { + const result = await ensInstance.getSubnames({ + name: 'with-subnames.eth', + pageSize: 2, + orderBy: 'labelName', + orderDirection: 'desc', + }) + expect(result).toBeTruthy() + expect(result.subnames.length).toBe(2) + expect(result.subnameCount).toBe(4) + expect(result.subnames[0].name).toEqual('xyz.with-subnames.eth') + expect(result.subnames[1].name).toEqual('test.with-subnames.eth') + testProperties( + result.subnames[0], + 'id', + 'labelName', + 'labelhash', + 'name', + 'isMigrated', + 'owner', + 'truncatedName', + ) + const result2 = await ensInstance.getSubnames({ + name: 'with-subnames.eth', + pageSize: 2, + orderBy: 'labelName', + orderDirection: 'desc', + lastSubnames: result.subnames, + }) + expect(result2).toBeTruthy() + expect(result2.subnames.length).toBe(2) + expect(result2.subnameCount).toBe(4) + expect(result2.subnames[0].name).toEqual('legacy.with-subnames.eth') + expect(result2.subnames[1].name).toEqual('addr.with-subnames.eth') + testProperties( + result.subnames[0], + 'id', + 'labelName', + 'labelhash', + 'name', + 'isMigrated', + 'owner', + 'truncatedName', + ) + }) + + it('should get paginated subnames for a name by labelName in asc order', async () => { + const result = await ensInstance.getSubnames({ + name: 'with-subnames.eth', + pageSize: 2, + orderBy: 'labelName', + orderDirection: 'asc', + }) + expect(result).toBeTruthy() + expect(result.subnames.length).toBe(2) + expect(result.subnameCount).toBe(4) + expect(result.subnames[0].name).toEqual('addr.with-subnames.eth') + expect(result.subnames[1].name).toEqual('legacy.with-subnames.eth') + + testProperties( + result.subnames[0], + 'id', + 'labelName', + 'labelhash', + 'name', + 'isMigrated', + 'owner', + 'truncatedName', + ) + + const result2 = await ensInstance.getSubnames({ + name: 'with-subnames.eth', + pageSize: 2, + orderBy: 'labelName', + orderDirection: 'asc', + lastSubnames: result.subnames, + }) + expect(result2).toBeTruthy() + expect(result2.subnames.length).toBe(2) + expect(result2.subnameCount).toBe(4) + expect(result2.subnames[0].name).toEqual('test.with-subnames.eth') + expect(result2.subnames[1].name).toEqual('xyz.with-subnames.eth') + + testProperties( + result2.subnames[0], + 'id', + 'labelName', + 'labelhash', + 'name', + 'isMigrated', + 'owner', + 'truncatedName', + ) + }) + }) + + describe('With search query', () => { + it('should get the searched subnames for a name ordered by createdAt in desc order', async () => { + const result = await ensInstance.getSubnames({ + name: 'with-subnames.eth', + pageSize: 10, + orderBy: 'createdAt', + orderDirection: 'desc', + search: 'a', + }) + expect(result).toBeTruthy() + expect(result.subnames.length).toBe(2) + expect(result.subnameCount).toBe(4) + expect(result.subnames[0].name).toEqual('addr.with-subnames.eth') + expect(result.subnames[1].name).toEqual('legacy.with-subnames.eth') + expect( + result.subnames.every((subname, i, arr) => { + if (arr[i + 1]) { + return (subname as any).createdAt >= (arr[i + 1] as any).createdAt + } + return true + }), + ).toBe(true) + testProperties( + result.subnames[0], + 'id', + 'labelName', + 'labelhash', + 'name', + 'isMigrated', + 'owner', + 'truncatedName', + ) + }) + + it('should get the searched subnames for a name ordered by createdAt in asc order', async () => { + const result = await ensInstance.getSubnames({ + name: 'with-subnames.eth', + page: 0, + pageSize: 10, + orderBy: 'createdAt', + orderDirection: 'asc', + search: 'a', + }) + expect(result).toBeTruthy() + expect(result.subnames.length).toBe(2) + expect(result.subnameCount).toBe(4) + expect(result.subnames[0].name).toEqual('legacy.with-subnames.eth') + expect(result.subnames[1].name).toEqual('addr.with-subnames.eth') + expect( + result.subnames.every((subname, i, arr) => { + if (arr[i + 1]) { + return (subname as any).createdAt <= (arr[i + 1] as any).createdAt + } + return true + }), + ).toBe(true) + testProperties( + result.subnames[0], + 'id', + 'labelName', + 'labelhash', + 'name', + 'isMigrated', + 'owner', + 'truncatedName', + ) + }) + + it('should get the subnames for a name by labelName in desc order', async () => { + const result = await ensInstance.getSubnames({ + name: 'with-subnames.eth', + pageSize: 10, + orderBy: 'labelName', + orderDirection: 'desc', + search: 'a', + }) + expect(result).toBeTruthy() + expect(result.subnames.length).toBe(2) + expect(result.subnameCount).toBe(4) + expect(result.subnames[0].name).toEqual('legacy.with-subnames.eth') + expect(result.subnames[1].name).toEqual('addr.with-subnames.eth') + testProperties( + result.subnames[0], + 'id', + 'labelName', + 'labelhash', + 'name', + 'isMigrated', + 'owner', + 'truncatedName', + ) + }) + + it('should get the subnames for a name by labelName in asc order', async () => { + const result = await ensInstance.getSubnames({ + name: 'with-subnames.eth', + pageSize: 10, + orderBy: 'labelName', + orderDirection: 'asc', + search: 'a', + }) + expect(result).toBeTruthy() + expect(result.subnames.length).toBe(2) + expect(result.subnameCount).toBe(4) + expect(result.subnames[0].name).toEqual('addr.with-subnames.eth') + expect(result.subnames[1].name).toEqual('legacy.with-subnames.eth') + testProperties( + result.subnames[0], + 'id', + 'labelName', + 'labelhash', + 'name', + 'isMigrated', + 'owner', + 'truncatedName', + ) + }) + }) + + describe('with search query and pagination', () => { + it('should get paginated subnames for a name ordered by createdAt in desc order', async () => { + const result = await ensInstance.getSubnames({ + name: 'with-subnames.eth', + pageSize: 1, + orderBy: 'createdAt', + orderDirection: 'desc', + search: 'a', + }) + expect(result).toBeTruthy() + expect(result.subnames.length).toBe(1) + expect(result.subnameCount).toBe(4) + expect(result.subnames[0].name).toEqual('addr.with-subnames.eth') + testProperties( + result.subnames[0], + 'id', + 'labelName', + 'labelhash', + 'name', + 'isMigrated', + 'owner', + 'truncatedName', + ) + const result2 = await ensInstance.getSubnames({ + name: 'with-subnames.eth', + pageSize: 1, + orderBy: 'createdAt', + orderDirection: 'desc', + lastSubnames: result.subnames, + search: 'a', + }) + expect(result2).toBeTruthy() + expect(result2.subnames.length).toBe(1) + expect(result2.subnameCount).toBe(4) + expect(result2.subnames[0].name).toEqual('legacy.with-subnames.eth') + testProperties( + result2.subnames[0], + 'id', + 'labelName', + 'labelhash', + 'name', + 'isMigrated', + 'owner', + 'truncatedName', + ) + }) + + it('should get paginated subnames for a name ordered by createdAt in asc order', async () => { + const result = await ensInstance.getSubnames({ + name: 'with-subnames.eth', + pageSize: 1, + orderBy: 'createdAt', + orderDirection: 'asc', + search: 'a', + }) + expect(result).toBeTruthy() + expect(result.subnames.length).toBe(1) + expect(result.subnameCount).toBe(4) + expect(result.subnames[0].name).toEqual('legacy.with-subnames.eth') + expect( + result.subnames.every((subname, i, arr) => { + if (arr[i + 1]) { + return (subname as any).createdAt <= (arr[i + 1] as any).createdAt + } + return true + }), + ).toBe(true) + testProperties( + result.subnames[0], + 'id', + 'labelName', + 'labelhash', + 'name', + 'isMigrated', + 'owner', + 'truncatedName', + ) + const result2 = await ensInstance.getSubnames({ + name: 'with-subnames.eth', + page: 0, + pageSize: 2, + orderBy: 'createdAt', + orderDirection: 'asc', + lastSubnames: result.subnames, + search: 'a', + }) + + expect(result2).toBeTruthy() + expect(result2.subnames.length).toBe(1) + expect(result2.subnameCount).toBe(4) + expect(result2.subnames[0].name).toEqual('addr.with-subnames.eth') + + testProperties( + result2.subnames[0], + 'id', + 'labelName', + 'labelhash', + 'name', + 'isMigrated', + 'owner', + 'truncatedName', + ) + }) + + it('should get paginated subnames for a name by labelName in desc order', async () => { + const result = await ensInstance.getSubnames({ + name: 'with-subnames.eth', + pageSize: 1, + orderBy: 'labelName', + orderDirection: 'desc', + search: 'a', + }) + expect(result).toBeTruthy() + expect(result.subnames.length).toBe(1) + expect(result.subnameCount).toBe(4) + expect(result.subnames[0].name).toEqual('legacy.with-subnames.eth') + testProperties( + result.subnames[0], + 'id', + 'labelName', + 'labelhash', + 'name', + 'isMigrated', + 'owner', + 'truncatedName', + ) + const result2 = await ensInstance.getSubnames({ + name: 'with-subnames.eth', + pageSize: 1, + orderBy: 'labelName', + orderDirection: 'desc', + lastSubnames: result.subnames, + search: 'a', + }) + expect(result2).toBeTruthy() + expect(result2.subnames.length).toBe(1) + expect(result2.subnameCount).toBe(4) + expect(result2.subnames[0].name).toEqual('addr.with-subnames.eth') + testProperties( + result.subnames[0], + 'id', + 'labelName', + 'labelhash', + 'name', + 'isMigrated', + 'owner', + 'truncatedName', + ) + }) + + it('should get paginated subnames for a name by labelName in asc order', async () => { + const result = await ensInstance.getSubnames({ + name: 'with-subnames.eth', + pageSize: 1, + orderBy: 'labelName', + orderDirection: 'asc', + search: 'a', + }) + expect(result).toBeTruthy() + expect(result.subnames.length).toBe(1) + expect(result.subnameCount).toBe(4) + expect(result.subnames[0].name).toEqual('addr.with-subnames.eth') + + testProperties( + result.subnames[0], + 'id', + 'labelName', + 'labelhash', + 'name', + 'isMigrated', + 'owner', + 'truncatedName', + ) + + const result2 = await ensInstance.getSubnames({ + name: 'with-subnames.eth', + pageSize: 1, + orderBy: 'labelName', + orderDirection: 'asc', + lastSubnames: result.subnames, + search: 'a', + }) + expect(result2).toBeTruthy() + expect(result2.subnames.length).toBe(1) + expect(result2.subnameCount).toBe(4) + expect(result2.subnames[0].name).toEqual('legacy.with-subnames.eth') + + testProperties( + result2.subnames[0], + 'id', + 'labelName', + 'labelhash', + 'name', + 'isMigrated', + 'owner', + 'truncatedName', + ) + }) }) }) - describe('With search query', () => { - it('should get the searched subnames for a name ordered by createdAt in desc order', async () => { + describe('wrapped', () => { + it('should get the subnames for a name ordered by createdAt in desc order', async () => { const result = await ensInstance.getSubnames({ - name: 'with-subnames.eth', + name: 'wrapped-with-subnames.eth', pageSize: 10, orderBy: 'createdAt', orderDirection: 'desc', - search: 'a', }) expect(result).toBeTruthy() - expect(result.subnames.length).toBe(2) + expect(result.subnames.length).toBe(4) expect(result.subnameCount).toBe(4) - expect(result.subnames[0].name).toEqual('addr.with-subnames.eth') - expect(result.subnames[1].name).toEqual('legacy.with-subnames.eth') + expect(result.subnames[0].name).toEqual('addr.wrapped-with-subnames.eth') + expect(result.subnames[1].name).toEqual('xyz.wrapped-with-subnames.eth') + expect(result.subnames[2].name).toEqual( + 'legacy.wrapped-with-subnames.eth', + ) + expect(result.subnames[3].name).toEqual('test.wrapped-with-subnames.eth') expect( result.subnames.every((subname, i, arr) => { if (arr[i + 1]) { @@ -388,20 +712,22 @@ describe('getSubnames', () => { ) }) - it('should get the searched subnames for a name ordered by createdAt in asc order', async () => { + it('should get the subnames for a name ordered by createdAt in asc order', async () => { const result = await ensInstance.getSubnames({ - name: 'with-subnames.eth', - page: 0, + name: 'wrapped-with-subnames.eth', pageSize: 10, orderBy: 'createdAt', orderDirection: 'asc', - search: 'a', }) expect(result).toBeTruthy() - expect(result.subnames.length).toBe(2) + expect(result.subnames.length).toBe(4) expect(result.subnameCount).toBe(4) - expect(result.subnames[0].name).toEqual('legacy.with-subnames.eth') - expect(result.subnames[1].name).toEqual('addr.with-subnames.eth') + expect(result.subnames[0].name).toEqual('test.wrapped-with-subnames.eth') + expect(result.subnames[1].name).toEqual( + 'legacy.wrapped-with-subnames.eth', + ) + expect(result.subnames[2].name).toEqual('xyz.wrapped-with-subnames.eth') + expect(result.subnames[3].name).toEqual('addr.wrapped-with-subnames.eth') expect( result.subnames.every((subname, i, arr) => { if (arr[i + 1]) { @@ -424,17 +750,20 @@ describe('getSubnames', () => { it('should get the subnames for a name by labelName in desc order', async () => { const result = await ensInstance.getSubnames({ - name: 'with-subnames.eth', + name: 'wrapped-with-subnames.eth', pageSize: 10, orderBy: 'labelName', orderDirection: 'desc', - search: 'a', }) expect(result).toBeTruthy() - expect(result.subnames.length).toBe(2) + expect(result.subnames.length).toBe(4) expect(result.subnameCount).toBe(4) - expect(result.subnames[0].name).toEqual('legacy.with-subnames.eth') - expect(result.subnames[1].name).toEqual('addr.with-subnames.eth') + expect(result.subnames[0].name).toEqual('xyz.wrapped-with-subnames.eth') + expect(result.subnames[1].name).toEqual('test.wrapped-with-subnames.eth') + expect(result.subnames[2].name).toEqual( + 'legacy.wrapped-with-subnames.eth', + ) + expect(result.subnames[3].name).toEqual('addr.wrapped-with-subnames.eth') testProperties( result.subnames[0], 'id', @@ -449,17 +778,20 @@ describe('getSubnames', () => { it('should get the subnames for a name by labelName in asc order', async () => { const result = await ensInstance.getSubnames({ - name: 'with-subnames.eth', + name: 'wrapped-with-subnames.eth', pageSize: 10, orderBy: 'labelName', orderDirection: 'asc', - search: 'a', }) expect(result).toBeTruthy() - expect(result.subnames.length).toBe(2) + expect(result.subnames.length).toBe(4) expect(result.subnameCount).toBe(4) - expect(result.subnames[0].name).toEqual('addr.with-subnames.eth') - expect(result.subnames[1].name).toEqual('legacy.with-subnames.eth') + expect(result.subnames[0].name).toEqual('addr.wrapped-with-subnames.eth') + expect(result.subnames[1].name).toEqual( + 'legacy.wrapped-with-subnames.eth', + ) + expect(result.subnames[2].name).toEqual('test.wrapped-with-subnames.eth') + expect(result.subnames[3].name).toEqual('xyz.wrapped-with-subnames.eth') testProperties( result.subnames[0], 'id', @@ -471,205 +803,607 @@ describe('getSubnames', () => { 'truncatedName', ) }) - }) - describe('with search query and pagination', () => { - it('should get paginated subnames for a name ordered by createdAt in desc order', async () => { - const result = await ensInstance.getSubnames({ - name: 'with-subnames.eth', - pageSize: 1, - orderBy: 'createdAt', - orderDirection: 'desc', - search: 'a', - }) - expect(result).toBeTruthy() - expect(result.subnames.length).toBe(1) - expect(result.subnameCount).toBe(4) - expect(result.subnames[0].name).toEqual('addr.with-subnames.eth') - testProperties( - result.subnames[0], - 'id', - 'labelName', - 'labelhash', - 'name', - 'isMigrated', - 'owner', - 'truncatedName', - ) - const result2 = await ensInstance.getSubnames({ - name: 'with-subnames.eth', - pageSize: 1, - orderBy: 'createdAt', - orderDirection: 'desc', - lastSubnames: result.subnames, - search: 'a', + describe('with pagination', () => { + it('should get paginated subnames for a name ordered by createdAt in desc order', async () => { + const result = await ensInstance.getSubnames({ + name: 'wrapped-with-subnames.eth', + pageSize: 2, + orderBy: 'createdAt', + orderDirection: 'desc', + }) + expect(result).toBeTruthy() + expect(result.subnames.length).toBe(2) + expect(result.subnameCount).toBe(4) + expect(result.subnames[0].name).toEqual( + 'addr.wrapped-with-subnames.eth', + ) + expect(result.subnames[1].name).toEqual('xyz.wrapped-with-subnames.eth') + expect( + result.subnames.every((subname, i, arr) => { + if (arr[i + 1]) { + return (subname as any).createdAt >= (arr[i + 1] as any).createdAt + } + return true + }), + ).toBe(true) + testProperties( + result.subnames[0], + 'id', + 'labelName', + 'labelhash', + 'name', + 'isMigrated', + 'owner', + 'truncatedName', + ) + const result2 = await ensInstance.getSubnames({ + name: 'wrapped-with-subnames.eth', + pageSize: 2, + orderBy: 'createdAt', + orderDirection: 'desc', + lastSubnames: result.subnames, + }) + expect(result2).toBeTruthy() + expect(result2.subnames.length).toBe(2) + expect(result2.subnameCount).toBe(4) + expect(result2.subnames[0].name).toEqual( + 'legacy.wrapped-with-subnames.eth', + ) + expect(result2.subnames[1].name).toEqual( + 'test.wrapped-with-subnames.eth', + ) + expect( + result2.subnames.every((subname, i, arr) => { + if (arr[i + 1]) { + return (subname as any).createdAt >= (arr[i + 1] as any).createdAt + } + return true + }), + ).toBe(true) + testProperties( + result2.subnames[0], + 'id', + 'labelName', + 'labelhash', + 'name', + 'isMigrated', + 'owner', + 'truncatedName', + ) }) - expect(result2).toBeTruthy() - expect(result2.subnames.length).toBe(1) - expect(result2.subnameCount).toBe(4) - expect(result2.subnames[0].name).toEqual('legacy.with-subnames.eth') - testProperties( - result2.subnames[0], - 'id', - 'labelName', - 'labelhash', - 'name', - 'isMigrated', - 'owner', - 'truncatedName', - ) - }) - it('should get paginated subnames for a name ordered by createdAt in asc order', async () => { - const result = await ensInstance.getSubnames({ - name: 'with-subnames.eth', - pageSize: 1, - orderBy: 'createdAt', - orderDirection: 'asc', - search: 'a', + it('should get paginated subnames for a name ordered by createdAt in asc order', async () => { + const result = await ensInstance.getSubnames({ + name: 'wrapped-with-subnames.eth', + pageSize: 2, + orderBy: 'createdAt', + orderDirection: 'asc', + }) + expect(result).toBeTruthy() + expect(result.subnames.length).toBe(2) + expect(result.subnameCount).toBe(4) + expect(result.subnames[0].name).toEqual( + 'test.wrapped-with-subnames.eth', + ) + expect(result.subnames[1].name).toEqual( + 'legacy.wrapped-with-subnames.eth', + ) + expect( + result.subnames.every((subname, i, arr) => { + if (arr[i + 1]) { + return (subname as any).createdAt <= (arr[i + 1] as any).createdAt + } + return true + }), + ).toBe(true) + testProperties( + result.subnames[0], + 'id', + 'labelName', + 'labelhash', + 'name', + 'isMigrated', + 'owner', + 'truncatedName', + ) + const result2 = await ensInstance.getSubnames({ + name: 'wrapped-with-subnames.eth', + page: 0, + pageSize: 2, + orderBy: 'createdAt', + orderDirection: 'asc', + lastSubnames: result.subnames, + }) + + expect(result2).toBeTruthy() + expect(result2.subnames.length).toBe(2) + expect(result2.subnameCount).toBe(4) + expect(result2.subnames[0].name).toEqual( + 'xyz.wrapped-with-subnames.eth', + ) + expect(result2.subnames[1].name).toEqual( + 'addr.wrapped-with-subnames.eth', + ) + expect( + result2.subnames.every((subname, i, arr) => { + if (arr[i + 1]) { + return (subname as any).createdAt <= (arr[i + 1] as any).createdAt + } + return true + }), + ).toBe(true) + testProperties( + result2.subnames[0], + 'id', + 'labelName', + 'labelhash', + 'name', + 'isMigrated', + 'owner', + 'truncatedName', + ) }) - expect(result).toBeTruthy() - expect(result.subnames.length).toBe(1) - expect(result.subnameCount).toBe(4) - expect(result.subnames[0].name).toEqual('legacy.with-subnames.eth') - expect( - result.subnames.every((subname, i, arr) => { - if (arr[i + 1]) { - return (subname as any).createdAt <= (arr[i + 1] as any).createdAt - } - return true - }), - ).toBe(true) - testProperties( - result.subnames[0], - 'id', - 'labelName', - 'labelhash', - 'name', - 'isMigrated', - 'owner', - 'truncatedName', - ) - const result2 = await ensInstance.getSubnames({ - name: 'with-subnames.eth', - page: 0, - pageSize: 2, - orderBy: 'createdAt', - orderDirection: 'asc', - lastSubnames: result.subnames, - search: 'a', + + it('should get paginated subnames for a name by labelName in desc order', async () => { + const result = await ensInstance.getSubnames({ + name: 'wrapped-with-subnames.eth', + pageSize: 2, + orderBy: 'labelName', + orderDirection: 'desc', + }) + expect(result).toBeTruthy() + expect(result.subnames.length).toBe(2) + expect(result.subnameCount).toBe(4) + expect(result.subnames[0].name).toEqual('xyz.wrapped-with-subnames.eth') + expect(result.subnames[1].name).toEqual( + 'test.wrapped-with-subnames.eth', + ) + testProperties( + result.subnames[0], + 'id', + 'labelName', + 'labelhash', + 'name', + 'isMigrated', + 'owner', + 'truncatedName', + ) + const result2 = await ensInstance.getSubnames({ + name: 'wrapped-with-subnames.eth', + pageSize: 2, + orderBy: 'labelName', + orderDirection: 'desc', + lastSubnames: result.subnames, + }) + expect(result2).toBeTruthy() + expect(result2.subnames.length).toBe(2) + expect(result2.subnameCount).toBe(4) + expect(result2.subnames[0].name).toEqual( + 'legacy.wrapped-with-subnames.eth', + ) + expect(result2.subnames[1].name).toEqual( + 'addr.wrapped-with-subnames.eth', + ) + testProperties( + result.subnames[0], + 'id', + 'labelName', + 'labelhash', + 'name', + 'isMigrated', + 'owner', + 'truncatedName', + ) }) - expect(result2).toBeTruthy() - expect(result2.subnames.length).toBe(1) - expect(result2.subnameCount).toBe(4) - expect(result2.subnames[0].name).toEqual('addr.with-subnames.eth') + it('should get paginated subnames for a name by labelName in asc order', async () => { + const result = await ensInstance.getSubnames({ + name: 'wrapped-with-subnames.eth', + pageSize: 2, + orderBy: 'labelName', + orderDirection: 'asc', + }) + expect(result).toBeTruthy() + expect(result.subnames.length).toBe(2) + expect(result.subnameCount).toBe(4) + expect(result.subnames[0].name).toEqual( + 'addr.wrapped-with-subnames.eth', + ) + expect(result.subnames[1].name).toEqual( + 'legacy.wrapped-with-subnames.eth', + ) - testProperties( - result2.subnames[0], - 'id', - 'labelName', - 'labelhash', - 'name', - 'isMigrated', - 'owner', - 'truncatedName', - ) + testProperties( + result.subnames[0], + 'id', + 'labelName', + 'labelhash', + 'name', + 'isMigrated', + 'owner', + 'truncatedName', + ) + + const result2 = await ensInstance.getSubnames({ + name: 'wrapped-with-subnames.eth', + pageSize: 2, + orderBy: 'labelName', + orderDirection: 'asc', + lastSubnames: result.subnames, + }) + expect(result2).toBeTruthy() + expect(result2.subnames.length).toBe(2) + expect(result2.subnameCount).toBe(4) + expect(result2.subnames[0].name).toEqual( + 'test.wrapped-with-subnames.eth', + ) + expect(result2.subnames[1].name).toEqual( + 'xyz.wrapped-with-subnames.eth', + ) + + testProperties( + result2.subnames[0], + 'id', + 'labelName', + 'labelhash', + 'name', + 'isMigrated', + 'owner', + 'truncatedName', + ) + }) }) - it('should get paginated subnames for a name by labelName in desc order', async () => { - const result = await ensInstance.getSubnames({ - name: 'with-subnames.eth', - pageSize: 1, - orderBy: 'labelName', - orderDirection: 'desc', - search: 'a', + describe('With search query', () => { + it('should get the searched subnames for a name ordered by createdAt in desc order', async () => { + const result = await ensInstance.getSubnames({ + name: 'wrapped-with-subnames.eth', + pageSize: 10, + orderBy: 'createdAt', + orderDirection: 'desc', + search: 'a', + }) + expect(result).toBeTruthy() + expect(result.subnames.length).toBe(2) + expect(result.subnameCount).toBe(4) + expect(result.subnames[0].name).toEqual( + 'addr.wrapped-with-subnames.eth', + ) + expect(result.subnames[1].name).toEqual( + 'legacy.wrapped-with-subnames.eth', + ) + expect( + result.subnames.every((subname, i, arr) => { + if (arr[i + 1]) { + return (subname as any).createdAt >= (arr[i + 1] as any).createdAt + } + return true + }), + ).toBe(true) + testProperties( + result.subnames[0], + 'id', + 'labelName', + 'labelhash', + 'name', + 'isMigrated', + 'owner', + 'truncatedName', + ) }) - expect(result).toBeTruthy() - expect(result.subnames.length).toBe(1) - expect(result.subnameCount).toBe(4) - expect(result.subnames[0].name).toEqual('legacy.with-subnames.eth') - testProperties( - result.subnames[0], - 'id', - 'labelName', - 'labelhash', - 'name', - 'isMigrated', - 'owner', - 'truncatedName', - ) - const result2 = await ensInstance.getSubnames({ - name: 'with-subnames.eth', - pageSize: 1, - orderBy: 'labelName', - orderDirection: 'desc', - lastSubnames: result.subnames, - search: 'a', + + it('should get the searched subnames for a name ordered by createdAt in asc order', async () => { + const result = await ensInstance.getSubnames({ + name: 'wrapped-with-subnames.eth', + page: 0, + pageSize: 10, + orderBy: 'createdAt', + orderDirection: 'asc', + search: 'a', + }) + expect(result).toBeTruthy() + expect(result.subnames.length).toBe(2) + expect(result.subnameCount).toBe(4) + expect(result.subnames[0].name).toEqual( + 'legacy.wrapped-with-subnames.eth', + ) + expect(result.subnames[1].name).toEqual( + 'addr.wrapped-with-subnames.eth', + ) + expect( + result.subnames.every((subname, i, arr) => { + if (arr[i + 1]) { + return (subname as any).createdAt <= (arr[i + 1] as any).createdAt + } + return true + }), + ).toBe(true) + testProperties( + result.subnames[0], + 'id', + 'labelName', + 'labelhash', + 'name', + 'isMigrated', + 'owner', + 'truncatedName', + ) + }) + + it('should get the subnames for a name by labelName in desc order', async () => { + const result = await ensInstance.getSubnames({ + name: 'wrapped-with-subnames.eth', + pageSize: 10, + orderBy: 'labelName', + orderDirection: 'desc', + search: 'a', + }) + expect(result).toBeTruthy() + expect(result.subnames.length).toBe(2) + expect(result.subnameCount).toBe(4) + expect(result.subnames[0].name).toEqual( + 'legacy.wrapped-with-subnames.eth', + ) + expect(result.subnames[1].name).toEqual( + 'addr.wrapped-with-subnames.eth', + ) + testProperties( + result.subnames[0], + 'id', + 'labelName', + 'labelhash', + 'name', + 'isMigrated', + 'owner', + 'truncatedName', + ) + }) + + it('should get the subnames for a name by labelName in asc order', async () => { + const result = await ensInstance.getSubnames({ + name: 'wrapped-with-subnames.eth', + pageSize: 10, + orderBy: 'labelName', + orderDirection: 'asc', + search: 'a', + }) + expect(result).toBeTruthy() + expect(result.subnames.length).toBe(2) + expect(result.subnameCount).toBe(4) + expect(result.subnames[0].name).toEqual( + 'addr.wrapped-with-subnames.eth', + ) + expect(result.subnames[1].name).toEqual( + 'legacy.wrapped-with-subnames.eth', + ) + testProperties( + result.subnames[0], + 'id', + 'labelName', + 'labelhash', + 'name', + 'isMigrated', + 'owner', + 'truncatedName', + ) }) - expect(result2).toBeTruthy() - expect(result2.subnames.length).toBe(1) - expect(result2.subnameCount).toBe(4) - expect(result2.subnames[0].name).toEqual('addr.with-subnames.eth') - testProperties( - result.subnames[0], - 'id', - 'labelName', - 'labelhash', - 'name', - 'isMigrated', - 'owner', - 'truncatedName', - ) }) - it('should get paginated subnames for a name by labelName in asc order', async () => { - const result = await ensInstance.getSubnames({ - name: 'with-subnames.eth', - pageSize: 1, - orderBy: 'labelName', - orderDirection: 'asc', - search: 'a', + describe('with search query and pagination', () => { + it('should get paginated subnames for a name ordered by createdAt in desc order', async () => { + const result = await ensInstance.getSubnames({ + name: 'wrapped-with-subnames.eth', + pageSize: 1, + orderBy: 'createdAt', + orderDirection: 'desc', + search: 'a', + }) + expect(result).toBeTruthy() + expect(result.subnames.length).toBe(1) + expect(result.subnameCount).toBe(4) + expect(result.subnames[0].name).toEqual( + 'addr.wrapped-with-subnames.eth', + ) + testProperties( + result.subnames[0], + 'id', + 'labelName', + 'labelhash', + 'name', + 'isMigrated', + 'owner', + 'truncatedName', + ) + const result2 = await ensInstance.getSubnames({ + name: 'wrapped-with-subnames.eth', + pageSize: 1, + orderBy: 'createdAt', + orderDirection: 'desc', + lastSubnames: result.subnames, + search: 'a', + }) + expect(result2).toBeTruthy() + expect(result2.subnames.length).toBe(1) + expect(result2.subnameCount).toBe(4) + expect(result2.subnames[0].name).toEqual( + 'legacy.wrapped-with-subnames.eth', + ) + testProperties( + result2.subnames[0], + 'id', + 'labelName', + 'labelhash', + 'name', + 'isMigrated', + 'owner', + 'truncatedName', + ) }) - expect(result).toBeTruthy() - expect(result.subnames.length).toBe(1) - expect(result.subnameCount).toBe(4) - expect(result.subnames[0].name).toEqual('addr.with-subnames.eth') - testProperties( - result.subnames[0], - 'id', - 'labelName', - 'labelhash', - 'name', - 'isMigrated', - 'owner', - 'truncatedName', - ) + it('should get paginated subnames for a name ordered by createdAt in asc order', async () => { + const result = await ensInstance.getSubnames({ + name: 'wrapped-with-subnames.eth', + pageSize: 1, + orderBy: 'createdAt', + orderDirection: 'asc', + search: 'a', + }) + expect(result).toBeTruthy() + expect(result.subnames.length).toBe(1) + expect(result.subnameCount).toBe(4) + expect(result.subnames[0].name).toEqual( + 'legacy.wrapped-with-subnames.eth', + ) + expect( + result.subnames.every((subname, i, arr) => { + if (arr[i + 1]) { + return (subname as any).createdAt <= (arr[i + 1] as any).createdAt + } + return true + }), + ).toBe(true) + testProperties( + result.subnames[0], + 'id', + 'labelName', + 'labelhash', + 'name', + 'isMigrated', + 'owner', + 'truncatedName', + ) + const result2 = await ensInstance.getSubnames({ + name: 'wrapped-with-subnames.eth', + page: 0, + pageSize: 2, + orderBy: 'createdAt', + orderDirection: 'asc', + lastSubnames: result.subnames, + search: 'a', + }) - const result2 = await ensInstance.getSubnames({ - name: 'with-subnames.eth', - pageSize: 1, - orderBy: 'labelName', - orderDirection: 'asc', - lastSubnames: result.subnames, - search: 'a', + expect(result2).toBeTruthy() + expect(result2.subnames.length).toBe(1) + expect(result2.subnameCount).toBe(4) + expect(result2.subnames[0].name).toEqual( + 'addr.wrapped-with-subnames.eth', + ) + + testProperties( + result2.subnames[0], + 'id', + 'labelName', + 'labelhash', + 'name', + 'isMigrated', + 'owner', + 'truncatedName', + ) }) - expect(result2).toBeTruthy() - expect(result2.subnames.length).toBe(1) - expect(result2.subnameCount).toBe(4) - expect(result2.subnames[0].name).toEqual('legacy.with-subnames.eth') - testProperties( - result2.subnames[0], - 'id', - 'labelName', - 'labelhash', - 'name', - 'isMigrated', - 'owner', - 'truncatedName', - ) + it('should get paginated subnames for a name by labelName in desc order', async () => { + const result = await ensInstance.getSubnames({ + name: 'wrapped-with-subnames.eth', + pageSize: 1, + orderBy: 'labelName', + orderDirection: 'desc', + search: 'a', + }) + expect(result).toBeTruthy() + expect(result.subnames.length).toBe(1) + expect(result.subnameCount).toBe(4) + expect(result.subnames[0].name).toEqual( + 'legacy.wrapped-with-subnames.eth', + ) + testProperties( + result.subnames[0], + 'id', + 'labelName', + 'labelhash', + 'name', + 'isMigrated', + 'owner', + 'truncatedName', + ) + const result2 = await ensInstance.getSubnames({ + name: 'wrapped-with-subnames.eth', + pageSize: 1, + orderBy: 'labelName', + orderDirection: 'desc', + lastSubnames: result.subnames, + search: 'a', + }) + expect(result2).toBeTruthy() + expect(result2.subnames.length).toBe(1) + expect(result2.subnameCount).toBe(4) + expect(result2.subnames[0].name).toEqual( + 'addr.wrapped-with-subnames.eth', + ) + testProperties( + result.subnames[0], + 'id', + 'labelName', + 'labelhash', + 'name', + 'isMigrated', + 'owner', + 'truncatedName', + ) + }) + + it('should get paginated subnames for a name by labelName in asc order', async () => { + const result = await ensInstance.getSubnames({ + name: 'wrapped-with-subnames.eth', + pageSize: 1, + orderBy: 'labelName', + orderDirection: 'asc', + search: 'a', + }) + expect(result).toBeTruthy() + expect(result.subnames.length).toBe(1) + expect(result.subnameCount).toBe(4) + expect(result.subnames[0].name).toEqual( + 'addr.wrapped-with-subnames.eth', + ) + + testProperties( + result.subnames[0], + 'id', + 'labelName', + 'labelhash', + 'name', + 'isMigrated', + 'owner', + 'truncatedName', + ) + + const result2 = await ensInstance.getSubnames({ + name: 'wrapped-with-subnames.eth', + pageSize: 1, + orderBy: 'labelName', + orderDirection: 'asc', + lastSubnames: result.subnames, + search: 'a', + }) + expect(result2).toBeTruthy() + expect(result2.subnames.length).toBe(1) + expect(result2.subnameCount).toBe(4) + expect(result2.subnames[0].name).toEqual( + 'legacy.wrapped-with-subnames.eth', + ) + + testProperties( + result2.subnames[0], + 'id', + 'labelName', + 'labelhash', + 'name', + 'isMigrated', + 'owner', + 'truncatedName', + ) + }) }) }) }) diff --git a/packages/ensjs/src/functions/transferSubname.test.ts b/packages/ensjs/src/functions/transferSubname.test.ts index 9c29bf1b..01c0e10f 100644 --- a/packages/ensjs/src/functions/transferSubname.test.ts +++ b/packages/ensjs/src/functions/transferSubname.test.ts @@ -34,7 +34,18 @@ describe('transferSubname', () => { const result = await registry.owner(namehash('test.with-subnames.eth')) expect(result).toBe(accounts[1]) }) - it('should allow transferring a subname on the nameWrapper', async () => { + + it('should NOT allow transferring a subname on the nameWrapper by name owner', async () => { + expect( + ensInstance.transferSubname('addr.wrapped-with-subnames.eth', { + contract: 'nameWrapper', + owner: accounts[1], + addressOrIndex: 2, + }), + ).rejects.toThrow() + }) + + it('should allow transferring a subname on the nameWrapper by parent owner', async () => { const tx = await ensInstance.transferSubname( 'test.wrapped-with-subnames.eth', { @@ -52,4 +63,93 @@ describe('transferSubname', () => { ) expect(result).toBe(accounts[1]) }) + + it('should NOT allow transferring a subname on the nameWrapper with PCC burned by parent owner', async () => { + const nameWrapper = await ensInstance.contracts!.getNameWrapper()! + + const tx0 = await ensInstance.setFuses('wrapped-with-subnames.eth', { + named: ['CANNOT_UNWRAP'], + addressOrIndex: 1, + }) + expect(tx0).toBeTruthy() + await tx0.wait() + + const tx1 = await ensInstance.setChildFuses( + 'legacy.wrapped-with-subnames.eth', + { + fuses: { + parent: { + named: ['PARENT_CANNOT_CONTROL'], + }, + }, + addressOrIndex: 1, + }, + ) + expect(tx1).toBeTruthy() + await tx1.wait() + + const checkOwner = await nameWrapper.ownerOf( + namehash('legacy.wrapped-with-subnames.eth'), + ) + expect(checkOwner).toBe(accounts[2]) + + expect( + ensInstance.transferSubname('legacy.wrapped-with-subnames.eth', { + contract: 'nameWrapper', + owner: accounts[1], + addressOrIndex: 1, + }), + ).rejects.toThrow() + + const result = await nameWrapper.ownerOf( + namehash('legacy.wrapped-with-subnames.eth'), + ) + expect(result).toBe(accounts[2]) + }) + + it('should allow transferring a subname on the nameWrapper with PCC burned by name owner', async () => { + const nameWrapper = await ensInstance.contracts!.getNameWrapper()! + + const tx0 = await ensInstance.setFuses('wrapped-with-subnames.eth', { + named: ['CANNOT_UNWRAP'], + addressOrIndex: 1, + }) + expect(tx0).toBeTruthy() + await tx0.wait() + + const tx1 = await ensInstance.setChildFuses( + 'xyz.wrapped-with-subnames.eth', + { + fuses: { + parent: { + named: ['PARENT_CANNOT_CONTROL'], + }, + }, + addressOrIndex: 1, + }, + ) + expect(tx1).toBeTruthy() + await tx1.wait() + + const checkOwner = await nameWrapper.ownerOf( + namehash('xyz.wrapped-with-subnames.eth'), + ) + expect(checkOwner).toBe(accounts[2]) + + const tx = await ensInstance.transferSubname( + 'xyz.wrapped-with-subnames.eth', + { + contract: 'nameWrapper', + owner: accounts[1], + addressOrIndex: 2, + }, + ) + expect(tx).toBeTruthy() + await tx.wait() + + const result = await nameWrapper.ownerOf( + namehash('xyz.wrapped-with-subnames.eth'), + ) + expect(result).toBe(accounts[1]) + }) }) diff --git a/packages/ensjs/src/functions/transferSubname.ts b/packages/ensjs/src/functions/transferSubname.ts index c1bbd85a..b1b34c56 100644 --- a/packages/ensjs/src/functions/transferSubname.ts +++ b/packages/ensjs/src/functions/transferSubname.ts @@ -1,6 +1,7 @@ import { keccak256 as solidityKeccak256 } from '@ethersproject/solidity' import { ENSArgs } from '..' import { namehash } from '../utils/normalise' +import { makeResolver } from '../utils/registry' import { Expiry, makeExpiry } from '../utils/wrapper' type BaseArgs = { @@ -16,12 +17,51 @@ type NameWrapperArgs = { type Args = BaseArgs | NameWrapperArgs +const transferWrappedSubname = async ( + { + contracts, + signer, + getWrapperData, + getExpiry, + }: ENSArgs<'contracts' | 'signer' | 'getWrapperData' | 'getExpiry'>, + name: string, + { contract, owner, resolverAddress, ...wrapperArgs }: Args, +) => { + const nameWrapper = (await contracts!.getNameWrapper()!).connect(signer) + const data = await getWrapperData(name) + const isPCCBurned = !!data?.parent?.PARENT_CANNOT_CONTROL + + if (isPCCBurned) { + const node = namehash(name) + const resolver = await makeResolver({ contracts }, name, resolverAddress) + return nameWrapper.populateTransaction.setRecord(node, owner, resolver, 0) + } + + const labels = name.split('.') + const label = labels.shift() as string + const parentNodehash = namehash(labels.join('.')) + const expiry = await makeExpiry( + { getExpiry }, + name, + 'expiry' in wrapperArgs ? wrapperArgs.expiry : undefined, + ) + + return nameWrapper.populateTransaction.setSubnodeOwner( + parentNodehash, + label, + owner, + '0', + expiry, + ) +} + export default async function ( { contracts, signer, getExpiry, - }: ENSArgs<'contracts' | 'signer' | 'getExpiry'>, + getWrapperData, + }: ENSArgs<'contracts' | 'signer' | 'getExpiry' | 'getWrapperData'>, name: string, { contract, owner, resolverAddress, ...wrapperArgs }: Args, ) { @@ -41,19 +81,15 @@ export default async function ( ) } case 'nameWrapper': { - const nameWrapper = (await contracts!.getNameWrapper()!).connect(signer) - const expiry = await makeExpiry( - { getExpiry }, - labels.join('.'), - 'expiry' in wrapperArgs ? wrapperArgs.expiry : undefined, - ) - - return nameWrapper.populateTransaction.setSubnodeOwner( - parentNodehash, - label, - owner, - '0', - expiry, + return transferWrappedSubname( + { contracts, signer, getWrapperData, getExpiry }, + name, + { + contract, + owner, + resolverAddress, + ...wrapperArgs, + }, ) } default: { diff --git a/packages/ensjs/src/index.ts b/packages/ensjs/src/index.ts index cd6a5219..ef74b596 100644 --- a/packages/ensjs/src/index.ts +++ b/packages/ensjs/src/index.ts @@ -621,11 +621,11 @@ export class ENS { public deleteSubname = this.generateWriteFunction< FunctionTypes['deleteSubname'] - >('deleteSubname', ['contracts']) + >('deleteSubname', ['contracts', 'getWrapperData']) public transferSubname = this.generateWriteFunction< FunctionTypes['transferSubname'] - >('transferSubname', ['contracts', 'getExpiry']) + >('transferSubname', ['contracts', 'getExpiry', 'getWrapperData']) public commitName = this.generateWriteFunction( 'commitName', diff --git a/packages/ensjs/src/utils/registry.ts b/packages/ensjs/src/utils/registry.ts new file mode 100644 index 00000000..9177afbc --- /dev/null +++ b/packages/ensjs/src/utils/registry.ts @@ -0,0 +1,14 @@ +import { ENSArgs } from '../index' +import { namehash } from './normalise' + +export const makeResolver = async ( + { contracts }: ENSArgs<'contracts'>, + name: string, + resolver?: string, +) => { + if (resolver) return resolver + const registry = await contracts!.getRegistry() + const node = namehash(name) + const _resolver = await registry.resolver(node) + return _resolver +} From 519aca586b8cae09378ae272cff98c8e0445b7f0 Mon Sep 17 00:00:00 2001 From: storywithoutend Date: Tue, 7 Feb 2023 10:48:11 +0800 Subject: [PATCH 2/4] fix test --- packages/ensjs/src/functions/getNames.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ensjs/src/functions/getNames.test.ts b/packages/ensjs/src/functions/getNames.test.ts index 2e9696ce..032100b1 100644 --- a/packages/ensjs/src/functions/getNames.test.ts +++ b/packages/ensjs/src/functions/getNames.test.ts @@ -172,7 +172,7 @@ describe('getNames', () => { type: 'wrappedOwner', page: 0, }) - expect(pageOne).toHaveLength(2) + expect(pageOne).toHaveLength(5) }) describe('orderBy', () => { describe('registrations', () => { From 3c4c775dd37263b70a3058f0355aaa658b125024 Mon Sep 17 00:00:00 2001 From: storywithoutend Date: Tue, 7 Feb 2023 23:23:31 +0800 Subject: [PATCH 3/4] update transfer subname and delete subname --- .../src/functions/transferSubname.test.ts | 225 +++++++++--------- .../ensjs/src/functions/transferSubname.ts | 15 +- packages/ensjs/src/utils/wrapper.ts | 21 +- 3 files changed, 134 insertions(+), 127 deletions(-) diff --git a/packages/ensjs/src/functions/transferSubname.test.ts b/packages/ensjs/src/functions/transferSubname.test.ts index 01c0e10f..c64ca6c9 100644 --- a/packages/ensjs/src/functions/transferSubname.test.ts +++ b/packages/ensjs/src/functions/transferSubname.test.ts @@ -17,10 +17,11 @@ afterAll(async () => { await revert() }) +beforeEach(async () => { + await revert() +}) + describe('transferSubname', () => { - beforeEach(async () => { - await revert() - }) it('should allow transferring a subname on the registry', async () => { const tx = await ensInstance.transferSubname('test.with-subnames.eth', { contract: 'registry', @@ -35,121 +36,129 @@ describe('transferSubname', () => { expect(result).toBe(accounts[1]) }) - it('should NOT allow transferring a subname on the nameWrapper by name owner', async () => { - expect( - ensInstance.transferSubname('addr.wrapped-with-subnames.eth', { - contract: 'nameWrapper', - owner: accounts[1], - addressOrIndex: 2, - }), - ).rejects.toThrow() + describe('wrapped name with PCC NOT burned', () => { + it('should allow transferring a subname by name owner', async () => { + const nameWrapper = await ensInstance.contracts!.getNameWrapper()! + const tx = await ensInstance.transferSubname( + 'test.wrapped-with-subnames.eth', + { + contract: 'nameWrapper', + owner: accounts[1], + addressOrIndex: 2, + }, + ) + expect(tx).toBeTruthy() + await tx.wait() + + const result = await nameWrapper.ownerOf( + namehash('test.wrapped-with-subnames.eth'), + ) + expect(result).toBe(accounts[1]) + }) + + it('should allow transferring a subname by parent owner', async () => { + const tx = await ensInstance.transferSubname( + 'test.wrapped-with-subnames.eth', + { + contract: 'nameWrapper', + owner: accounts[1], + addressOrIndex: 1, + }, + ) + expect(tx).toBeTruthy() + await tx.wait() + + const nameWrapper = await ensInstance.contracts!.getNameWrapper()! + const result = await nameWrapper.ownerOf( + namehash('test.wrapped-with-subnames.eth'), + ) + expect(result).toBe(accounts[1]) + }) }) - it('should allow transferring a subname on the nameWrapper by parent owner', async () => { - const tx = await ensInstance.transferSubname( - 'test.wrapped-with-subnames.eth', - { - contract: 'nameWrapper', - owner: accounts[1], + describe('wrapped name with PCC burned', () => { + beforeEach(async () => { + const tx0 = await ensInstance.setFuses('wrapped-with-subnames.eth', { + named: ['CANNOT_UNWRAP'], addressOrIndex: 1, - }, - ) - expect(tx).toBeTruthy() - await tx.wait() - - const nameWrapper = await ensInstance.contracts!.getNameWrapper()! - const result = await nameWrapper.ownerOf( - namehash('test.wrapped-with-subnames.eth'), - ) - expect(result).toBe(accounts[1]) - }) + }) + expect(tx0).toBeTruthy() + await tx0.wait() + }) - it('should NOT allow transferring a subname on the nameWrapper with PCC burned by parent owner', async () => { - const nameWrapper = await ensInstance.contracts!.getNameWrapper()! + it('should allow transferring a subname by name owner', async () => { + const nameWrapper = await ensInstance.contracts!.getNameWrapper()! - const tx0 = await ensInstance.setFuses('wrapped-with-subnames.eth', { - named: ['CANNOT_UNWRAP'], - addressOrIndex: 1, - }) - expect(tx0).toBeTruthy() - await tx0.wait() - - const tx1 = await ensInstance.setChildFuses( - 'legacy.wrapped-with-subnames.eth', - { - fuses: { - parent: { - named: ['PARENT_CANNOT_CONTROL'], + const tx1 = await ensInstance.setChildFuses( + 'test.wrapped-with-subnames.eth', + { + fuses: { + parent: { + named: ['PARENT_CANNOT_CONTROL'], + }, }, + addressOrIndex: 1, }, - addressOrIndex: 1, - }, - ) - expect(tx1).toBeTruthy() - await tx1.wait() - - const checkOwner = await nameWrapper.ownerOf( - namehash('legacy.wrapped-with-subnames.eth'), - ) - expect(checkOwner).toBe(accounts[2]) - - expect( - ensInstance.transferSubname('legacy.wrapped-with-subnames.eth', { - contract: 'nameWrapper', - owner: accounts[1], - addressOrIndex: 1, - }), - ).rejects.toThrow() - - const result = await nameWrapper.ownerOf( - namehash('legacy.wrapped-with-subnames.eth'), - ) - expect(result).toBe(accounts[2]) - }) + ) + expect(tx1).toBeTruthy() + await tx1.wait() + + const checkOwner = await nameWrapper.ownerOf( + namehash('test.wrapped-with-subnames.eth'), + ) + expect(checkOwner).toBe(accounts[2]) + + const tx = await ensInstance.transferSubname( + 'test.wrapped-with-subnames.eth', + { + contract: 'nameWrapper', + owner: accounts[1], + addressOrIndex: 2, + }, + ) + expect(tx).toBeTruthy() + await tx.wait() + + const result = await nameWrapper.ownerOf( + namehash('test.wrapped-with-subnames.eth'), + ) + expect(result).toBe(accounts[1]) + }) - it('should allow transferring a subname on the nameWrapper with PCC burned by name owner', async () => { - const nameWrapper = await ensInstance.contracts!.getNameWrapper()! + it('should NOT allow transferring a subname by parent owner', async () => { + const nameWrapper = await ensInstance.contracts!.getNameWrapper()! - const tx0 = await ensInstance.setFuses('wrapped-with-subnames.eth', { - named: ['CANNOT_UNWRAP'], - addressOrIndex: 1, - }) - expect(tx0).toBeTruthy() - await tx0.wait() - - const tx1 = await ensInstance.setChildFuses( - 'xyz.wrapped-with-subnames.eth', - { - fuses: { - parent: { - named: ['PARENT_CANNOT_CONTROL'], + const tx1 = await ensInstance.setChildFuses( + 'test.wrapped-with-subnames.eth', + { + fuses: { + parent: { + named: ['PARENT_CANNOT_CONTROL'], + }, }, + addressOrIndex: 1, }, - addressOrIndex: 1, - }, - ) - expect(tx1).toBeTruthy() - await tx1.wait() - - const checkOwner = await nameWrapper.ownerOf( - namehash('xyz.wrapped-with-subnames.eth'), - ) - expect(checkOwner).toBe(accounts[2]) - - const tx = await ensInstance.transferSubname( - 'xyz.wrapped-with-subnames.eth', - { - contract: 'nameWrapper', - owner: accounts[1], - addressOrIndex: 2, - }, - ) - expect(tx).toBeTruthy() - await tx.wait() - - const result = await nameWrapper.ownerOf( - namehash('xyz.wrapped-with-subnames.eth'), - ) - expect(result).toBe(accounts[1]) + ) + expect(tx1).toBeTruthy() + await tx1.wait() + + const checkOwner = await nameWrapper.ownerOf( + namehash('test.wrapped-with-subnames.eth'), + ) + expect(checkOwner).toBe(accounts[2]) + + await expect( + ensInstance.transferSubname('test.wrapped-with-subnames.eth', { + contract: 'nameWrapper', + owner: accounts[1], + addressOrIndex: 1, + }), + ).rejects.toThrow() + + const result = await nameWrapper.ownerOf( + namehash('test.wrapped-with-subnames.eth'), + ) + expect(result).toBe(accounts[2]) + }) }) }) diff --git a/packages/ensjs/src/functions/transferSubname.ts b/packages/ensjs/src/functions/transferSubname.ts index b1b34c56..010303e1 100644 --- a/packages/ensjs/src/functions/transferSubname.ts +++ b/packages/ensjs/src/functions/transferSubname.ts @@ -2,7 +2,7 @@ import { keccak256 as solidityKeccak256 } from '@ethersproject/solidity' import { ENSArgs } from '..' import { namehash } from '../utils/normalise' import { makeResolver } from '../utils/registry' -import { Expiry, makeExpiry } from '../utils/wrapper' +import { expiryToBigNumber, Expiry } from '../utils/wrapper' type BaseArgs = { owner: string @@ -22,16 +22,15 @@ const transferWrappedSubname = async ( contracts, signer, getWrapperData, - getExpiry, }: ENSArgs<'contracts' | 'signer' | 'getWrapperData' | 'getExpiry'>, name: string, - { contract, owner, resolverAddress, ...wrapperArgs }: Args, + { contract, owner, resolverAddress, ...wrapperArgs }: NameWrapperArgs, ) => { const nameWrapper = (await contracts!.getNameWrapper()!).connect(signer) const data = await getWrapperData(name) - const isPCCBurned = !!data?.parent?.PARENT_CANNOT_CONTROL + const senderAddress = await signer.getAddress() - if (isPCCBurned) { + if (senderAddress === data?.owner) { const node = namehash(name) const resolver = await makeResolver({ contracts }, name, resolverAddress) return nameWrapper.populateTransaction.setRecord(node, owner, resolver, 0) @@ -40,11 +39,7 @@ const transferWrappedSubname = async ( const labels = name.split('.') const label = labels.shift() as string const parentNodehash = namehash(labels.join('.')) - const expiry = await makeExpiry( - { getExpiry }, - name, - 'expiry' in wrapperArgs ? wrapperArgs.expiry : undefined, - ) + const expiry = expiryToBigNumber(wrapperArgs.expiry, 0) return nameWrapper.populateTransaction.setSubnodeOwner( parentNodehash, diff --git a/packages/ensjs/src/utils/wrapper.ts b/packages/ensjs/src/utils/wrapper.ts index 952a2dd4..20b120e5 100644 --- a/packages/ensjs/src/utils/wrapper.ts +++ b/packages/ensjs/src/utils/wrapper.ts @@ -6,20 +6,23 @@ export type Expiry = string | number | Date | BigNumber export const MAX_EXPIRY = BigNumber.from(2).pow(64).sub(1) +export const expiryToBigNumber = (expiry?: Expiry, defaultValue = 0) => { + if (!expiry) return BigNumber.from(defaultValue) + if (expiry instanceof Date) { + return BigNumber.from(expiry.getTime() / 1000) + } + if (expiry instanceof BigNumber) { + return expiry + } + return BigNumber.from(expiry) +} + export const makeExpiry = async ( { getExpiry }: ENSArgs<'getExpiry'>, name: string, expiry?: Expiry, ) => { - if (expiry) { - if (expiry instanceof Date) { - return BigNumber.from(expiry.getTime() / 1000) - } - if (expiry instanceof BigNumber) { - return expiry - } - return BigNumber.from(expiry) - } + if (expiry) return expiryToBigNumber(expiry) if (name.endsWith('.eth')) { const expResponse = await getExpiry(name) if (!expResponse?.expiry) From faa8e434f6a309169f85d0126df5a0d65ba09fe7 Mon Sep 17 00:00:00 2001 From: storywithoutend Date: Fri, 10 Feb 2023 13:20:22 +0800 Subject: [PATCH 4/4] addressed change requests --- .../ensjs/src/functions/deleteSubname.test.ts | 65 +++++++++++------ packages/ensjs/src/functions/deleteSubname.ts | 73 ++++++++----------- .../src/functions/getWrapperData.test.ts | 1 - .../src/functions/transferSubname.test.ts | 31 +++----- .../ensjs/src/functions/transferSubname.ts | 62 ++++------------ packages/ensjs/src/index.ts | 4 +- 6 files changed, 101 insertions(+), 135 deletions(-) diff --git a/packages/ensjs/src/functions/deleteSubname.test.ts b/packages/ensjs/src/functions/deleteSubname.test.ts index f500d5dd..acb7c02a 100644 --- a/packages/ensjs/src/functions/deleteSubname.test.ts +++ b/packages/ensjs/src/functions/deleteSubname.test.ts @@ -13,6 +13,10 @@ beforeAll(async () => { accounts = await provider.listAccounts() }) +beforeEach(async () => { + await revert() +}) + afterAll(async () => { await revert() }) @@ -21,15 +25,17 @@ describe('deleteSubname', () => { beforeEach(async () => { await revert() }) - it('should allow deleting a subname on the registry', async () => { + it('should allow deleting a subname on the registry by parent owner', async () => { + const registry = await ensInstance.contracts!.getRegistry()! + const parentOwner = await registry.owner(namehash('with-subnames.eth')) + const tx = await ensInstance.deleteSubname('test.with-subnames.eth', { contract: 'registry', - addressOrIndex: 1, + addressOrIndex: parentOwner, }) expect(tx).toBeTruthy() await tx.wait() - const registry = await ensInstance.contracts!.getRegistry()! const result = await registry.owner(namehash('test.with-subnames.eth')) expect(result).toBe('0x0000000000000000000000000000000000000000') }) @@ -37,11 +43,16 @@ describe('deleteSubname', () => { it('should allow deleting a subname on the nameWrapper by parent owner', async () => { const nameWrapper = await ensInstance.contracts!.getNameWrapper()! + const parentOwner = await nameWrapper.ownerOf( + namehash('wrapped-with-subnames.eth'), + ) + const tx = await ensInstance.deleteSubname( 'test.wrapped-with-subnames.eth', { contract: 'nameWrapper', - addressOrIndex: 1, + method: 'setSubnodeOwner', + addressOrIndex: parentOwner, }, ) expect(tx).toBeTruthy() @@ -53,33 +64,35 @@ describe('deleteSubname', () => { expect(result).toBe('0x0000000000000000000000000000000000000000') }) - it('should NOT allow deleting a subname on the nameWrapper by name owner', async () => { + it('should allow deleting a subname on the nameWrapper by name owner', async () => { const nameWrapper = await ensInstance.contracts!.getNameWrapper()! - const checkOwner = await nameWrapper.ownerOf( + const nameOwner = await nameWrapper.ownerOf( namehash('addr.wrapped-with-subnames.eth'), ) - expect(checkOwner).toBe(accounts[2]) - expect( - ensInstance.deleteSubname('addr.wrapped-with-subnames.eth', { - contract: 'nameWrapper', - addressOrIndex: 2, - }), - ).rejects.toThrow() + await ensInstance.deleteSubname('addr.wrapped-with-subnames.eth', { + contract: 'nameWrapper', + method: 'setRecord', + addressOrIndex: nameOwner, + }) const result = await nameWrapper.ownerOf( namehash('addr.wrapped-with-subnames.eth'), ) - expect(result).toBe(accounts[2]) + expect(result).toBe('0x0000000000000000000000000000000000000000') }) it('should allow deleting a subname on the nameWrapper with PCC burned by name owner', async () => { const nameWrapper = await ensInstance.contracts!.getNameWrapper()! + const parentOwner = await nameWrapper.ownerOf( + namehash('wrapped-with-subnames.eth'), + ) + const tx0 = await ensInstance.setFuses('wrapped-with-subnames.eth', { named: ['CANNOT_UNWRAP'], - addressOrIndex: 1, + addressOrIndex: parentOwner, }) expect(tx0).toBeTruthy() await tx0.wait() @@ -92,22 +105,24 @@ describe('deleteSubname', () => { named: ['PARENT_CANNOT_CONTROL'], }, }, - addressOrIndex: 1, + addressOrIndex: parentOwner, }, ) expect(tx1).toBeTruthy() await tx1.wait() - const checkOwner = await nameWrapper.ownerOf( + const nameOwner = await nameWrapper.ownerOf( namehash('xyz.wrapped-with-subnames.eth'), ) - expect(checkOwner).toBe(accounts[2]) + + expect(parentOwner === nameOwner).toBe(false) const tx = await ensInstance.deleteSubname( 'xyz.wrapped-with-subnames.eth', { contract: 'nameWrapper', - addressOrIndex: 2, + method: 'setRecord', + addressOrIndex: nameOwner, }, ) expect(tx).toBeTruthy() @@ -122,9 +137,13 @@ describe('deleteSubname', () => { it('should NOT allow deleting a subname on the nameWrapper with PCC burned by parent owner', async () => { const nameWrapper = await ensInstance.contracts!.getNameWrapper()! + const parentOwner = await nameWrapper.ownerOf( + namehash('wrapped-with-subnames.eth'), + ) + const tx0 = await ensInstance.setFuses('wrapped-with-subnames.eth', { named: ['CANNOT_UNWRAP'], - addressOrIndex: 1, + addressOrIndex: parentOwner, }) expect(tx0).toBeTruthy() await tx0.wait() @@ -137,7 +156,7 @@ describe('deleteSubname', () => { named: ['PARENT_CANNOT_CONTROL'], }, }, - addressOrIndex: 1, + addressOrIndex: parentOwner, }, ) expect(tx1).toBeTruthy() @@ -151,7 +170,8 @@ describe('deleteSubname', () => { await expect( ensInstance.deleteSubname('legacy.wrapped-with-subnames.eth', { contract: 'nameWrapper', - addressOrIndex: 1, + method: 'setSubnodeOwner', + addressOrIndex: parentOwner, }), ).rejects.toThrow() @@ -165,6 +185,7 @@ describe('deleteSubname', () => { await expect( ensInstance.deleteSubname('eth', { contract: 'nameWrapper', + method: 'setRecord', addressOrIndex: 1, }), ).rejects.toThrow() diff --git a/packages/ensjs/src/functions/deleteSubname.ts b/packages/ensjs/src/functions/deleteSubname.ts index 4a754c72..be9d4749 100644 --- a/packages/ensjs/src/functions/deleteSubname.ts +++ b/packages/ensjs/src/functions/deleteSubname.ts @@ -2,54 +2,22 @@ import { keccak256 as solidityKeccak256 } from '@ethersproject/solidity' import { ENSArgs } from '..' import { namehash } from '../utils/normalise' -type Args = { +type BaseArgs = { contract: 'registry' | 'nameWrapper' + method?: 'setRecord' | 'setSubnodeOwner' } -const deleteWrappedSubname = async ( - { - contracts, - signer, - getWrapperData, - }: ENSArgs<'contracts' | 'signer' | 'getWrapperData'>, - name: string, -) => { - const nameWrapper = (await contracts!.getNameWrapper()!).connect(signer) - const data = await getWrapperData(name) - const isPCCBurned = !!data?.parent?.PARENT_CANNOT_CONTROL - - if (isPCCBurned) { - const node = namehash(name) - return nameWrapper.populateTransaction.setRecord( - node, - '0x0000000000000000000000000000000000000000', - '0x0000000000000000000000000000000000000000', - 0, - ) - } - - const labels = name.split('.') - const label = labels.shift() as string - const parentNodehash = namehash(labels.join('.')) - return nameWrapper.populateTransaction.setSubnodeRecord( - parentNodehash, - label, - '0x0000000000000000000000000000000000000000', - '0x0000000000000000000000000000000000000000', - 0, - 0, - 0, - ) +type NameWrapperArgs = { + contract: 'nameWrapper' + method: 'setRecord' | 'setSubnodeOwner' } +type Args = BaseArgs | NameWrapperArgs + export default async function ( - { - contracts, - signer, - getWrapperData, - }: ENSArgs<'contracts' | 'signer' | 'getExpiry' | 'getWrapperData'>, + { contracts, signer }: ENSArgs<'contracts' | 'signer'>, name: string, - { contract }: Args, + { contract, ...args }: Args, ) { const labels = name.split('.') if (labels.length < 3) { @@ -72,7 +40,28 @@ export default async function ( ) } case 'nameWrapper': { - return deleteWrappedSubname({ contracts, signer, getWrapperData }, name) + const nameWrapper = (await contracts!.getNameWrapper()!).connect(signer) + const { method } = args as NameWrapperArgs + + if (method === 'setRecord') { + const node = namehash(name) + return nameWrapper.populateTransaction.setRecord( + node, + '0x0000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000', + 0, + ) + } + + const label = labels.shift() as string + const parentNodehash = namehash(labels.join('.')) + return nameWrapper.populateTransaction.setSubnodeOwner( + parentNodehash, + label, + '0x0000000000000000000000000000000000000000', + 0, + 0, + ) } default: { throw new Error(`Unknown contract: ${contract}`) diff --git a/packages/ensjs/src/functions/getWrapperData.test.ts b/packages/ensjs/src/functions/getWrapperData.test.ts index 1dcc1f12..15e2ae86 100644 --- a/packages/ensjs/src/functions/getWrapperData.test.ts +++ b/packages/ensjs/src/functions/getWrapperData.test.ts @@ -59,7 +59,6 @@ describe('getWrapperData', () => { const result = await ensInstance.getWrapperData('wrapped.eth') expect(result).toBeTruthy() if (result) { - console.log(result.expiryDate) expect(result.expiryDate).toBeInstanceOf(Date) expect(Number.isNaN(result.expiryDate?.getTime())).toBe(false) } diff --git a/packages/ensjs/src/functions/transferSubname.test.ts b/packages/ensjs/src/functions/transferSubname.test.ts index c64ca6c9..a4769438 100644 --- a/packages/ensjs/src/functions/transferSubname.test.ts +++ b/packages/ensjs/src/functions/transferSubname.test.ts @@ -37,23 +37,20 @@ describe('transferSubname', () => { }) describe('wrapped name with PCC NOT burned', () => { - it('should allow transferring a subname by name owner', async () => { + it('should NOT allow transferring a subname by name owner', async () => { const nameWrapper = await ensInstance.contracts!.getNameWrapper()! - const tx = await ensInstance.transferSubname( - 'test.wrapped-with-subnames.eth', - { + await expect( + ensInstance.transferSubname('test.wrapped-with-subnames.eth', { contract: 'nameWrapper', owner: accounts[1], addressOrIndex: 2, - }, - ) - expect(tx).toBeTruthy() - await tx.wait() + }), + ).rejects.toThrow() const result = await nameWrapper.ownerOf( namehash('test.wrapped-with-subnames.eth'), ) - expect(result).toBe(accounts[1]) + expect(result).toBe(accounts[2]) }) it('should allow transferring a subname by parent owner', async () => { @@ -86,7 +83,7 @@ describe('transferSubname', () => { await tx0.wait() }) - it('should allow transferring a subname by name owner', async () => { + it('should NOT allow transferring a subname by name owner', async () => { const nameWrapper = await ensInstance.contracts!.getNameWrapper()! const tx1 = await ensInstance.setChildFuses( @@ -108,21 +105,17 @@ describe('transferSubname', () => { ) expect(checkOwner).toBe(accounts[2]) - const tx = await ensInstance.transferSubname( - 'test.wrapped-with-subnames.eth', - { + await expect( + ensInstance.transferSubname('test.wrapped-with-subnames.eth', { contract: 'nameWrapper', owner: accounts[1], addressOrIndex: 2, - }, - ) - expect(tx).toBeTruthy() - await tx.wait() - + }), + ).rejects.toThrow() const result = await nameWrapper.ownerOf( namehash('test.wrapped-with-subnames.eth'), ) - expect(result).toBe(accounts[1]) + expect(result).toBe(accounts[2]) }) it('should NOT allow transferring a subname by parent owner', async () => { diff --git a/packages/ensjs/src/functions/transferSubname.ts b/packages/ensjs/src/functions/transferSubname.ts index 010303e1..a15b1388 100644 --- a/packages/ensjs/src/functions/transferSubname.ts +++ b/packages/ensjs/src/functions/transferSubname.ts @@ -1,7 +1,6 @@ import { keccak256 as solidityKeccak256 } from '@ethersproject/solidity' import { ENSArgs } from '..' import { namehash } from '../utils/normalise' -import { makeResolver } from '../utils/registry' import { expiryToBigNumber, Expiry } from '../utils/wrapper' type BaseArgs = { @@ -17,46 +16,8 @@ type NameWrapperArgs = { type Args = BaseArgs | NameWrapperArgs -const transferWrappedSubname = async ( - { - contracts, - signer, - getWrapperData, - }: ENSArgs<'contracts' | 'signer' | 'getWrapperData' | 'getExpiry'>, - name: string, - { contract, owner, resolverAddress, ...wrapperArgs }: NameWrapperArgs, -) => { - const nameWrapper = (await contracts!.getNameWrapper()!).connect(signer) - const data = await getWrapperData(name) - const senderAddress = await signer.getAddress() - - if (senderAddress === data?.owner) { - const node = namehash(name) - const resolver = await makeResolver({ contracts }, name, resolverAddress) - return nameWrapper.populateTransaction.setRecord(node, owner, resolver, 0) - } - - const labels = name.split('.') - const label = labels.shift() as string - const parentNodehash = namehash(labels.join('.')) - const expiry = expiryToBigNumber(wrapperArgs.expiry, 0) - - return nameWrapper.populateTransaction.setSubnodeOwner( - parentNodehash, - label, - owner, - '0', - expiry, - ) -} - export default async function ( - { - contracts, - signer, - getExpiry, - getWrapperData, - }: ENSArgs<'contracts' | 'signer' | 'getExpiry' | 'getWrapperData'>, + { contracts, signer }: ENSArgs<'contracts' | 'signer'>, name: string, { contract, owner, resolverAddress, ...wrapperArgs }: Args, ) { @@ -76,15 +37,18 @@ export default async function ( ) } case 'nameWrapper': { - return transferWrappedSubname( - { contracts, signer, getWrapperData, getExpiry }, - name, - { - contract, - owner, - resolverAddress, - ...wrapperArgs, - }, + const nameWrapper = (await contracts!.getNameWrapper()!).connect(signer) + const expiry = expiryToBigNumber( + (wrapperArgs as NameWrapperArgs).expiry, + 0, + ) + + return nameWrapper.populateTransaction.setSubnodeOwner( + parentNodehash, + label, + owner, + '0', + expiry, ) } default: { diff --git a/packages/ensjs/src/index.ts b/packages/ensjs/src/index.ts index ef74b596..1e7eff22 100644 --- a/packages/ensjs/src/index.ts +++ b/packages/ensjs/src/index.ts @@ -621,11 +621,11 @@ export class ENS { public deleteSubname = this.generateWriteFunction< FunctionTypes['deleteSubname'] - >('deleteSubname', ['contracts', 'getWrapperData']) + >('deleteSubname', ['contracts']) public transferSubname = this.generateWriteFunction< FunctionTypes['transferSubname'] - >('transferSubname', ['contracts', 'getExpiry', 'getWrapperData']) + >('transferSubname', ['contracts']) public commitName = this.generateWriteFunction( 'commitName',