Skip to content

Commit

Permalink
Merge pull request #113 from ensdomains/feat/get-subnames-fuses
Browse files Browse the repository at this point in the history
feat: fetch fuses in `getSubnames()`
  • Loading branch information
TateB authored Feb 10, 2023
2 parents 8ce8078 + a21a9fd commit 428bc3b
Show file tree
Hide file tree
Showing 6 changed files with 203 additions and 39 deletions.
38 changes: 36 additions & 2 deletions packages/ensjs/deploy/00_register_wrapped.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { BigNumber } from '@ethersproject/bignumber'
import { ethers } from 'hardhat'
import { DeployFunction } from 'hardhat-deploy/types'
import { HardhatRuntimeEnvironment } from 'hardhat/types'
import { encodeFuses } from '../src/utils/fuses'
import { namehash } from '../src/utils/normalise'

const names: {
Expand All @@ -15,6 +16,8 @@ const names: {
subnames?: {
label: string
namedOwner: string
fuses?: number
expiry?: number
}[]
duration?: number
}[] = [
Expand All @@ -34,6 +37,35 @@ const names: {
subnames: [{ label: 'test', namedOwner: 'owner2' }],
duration: 2419200,
},
{
label: 'wrapped-with-expiring-subnames',
namedOwner: 'owner',
fuses: encodeFuses({
child: {
named: ['CANNOT_UNWRAP'],
},
}),
subnames: [
{
label: 'test',
namedOwner: 'owner2',
expiry: Math.floor(Date.now() / 1000),
},
{
label: 'test1',
namedOwner: 'owner2',
expiry: 0,
},
{
label: 'recent-pcc',
namedOwner: 'owner2',
expiry: Math.floor(Date.now() / 1000),
fuses: encodeFuses({
parent: { named: ['PARENT_CANNOT_CONTROL'] },
}),
},
],
},
]

const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
Expand Down Expand Up @@ -103,6 +135,8 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
for (const {
label: subnameLabel,
namedOwner: namedSubnameOwner,
fuses: subnameFuses = 0,
expiry: subnameExpiry = BigNumber.from(2).pow(64).sub(1),
} of subnames) {
const subnameOwner = allNamedAccts[namedSubnameOwner]
const _nameWrapper = nameWrapper.connect(await ethers.getSigner(owner))
Expand All @@ -112,8 +146,8 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
subnameOwner,
resolver,
'0',
'0',
BigNumber.from(2).pow(64).sub(1),
subnameFuses,
subnameExpiry,
)
console.log(` - ${subnameLabel} (tx: ${setSubnameTx.hash})...`)
await setSubnameTx.wait()
Expand Down
7 changes: 5 additions & 2 deletions packages/ensjs/src/functions/getNames.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,13 +166,16 @@ describe('getNames', () => {
})
expect(pageFive).toHaveLength(totalOwnedNames % 10)
})
it('should get wrapped domains for an address with pagination', async () => {
it('should get wrapped domains for an address with pagination, and filter out pcc expired names', async () => {
const pageOne = await ensInstance.getNames({
address: '0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC',
type: 'wrappedOwner',
page: 0,
})
expect(pageOne).toHaveLength(2)
// length of page one should be all the names on 0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC
// minus 1 for the PCC expired name.
// the result here implies that the PCC expired name is not returned
expect(pageOne).toHaveLength(4)
})
describe('orderBy', () => {
describe('registrations', () => {
Expand Down
68 changes: 46 additions & 22 deletions packages/ensjs/src/functions/getNames.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ENSArgs } from '..'
import { truncateFormat } from '../utils/format'
import { AllCurrentFuses, decodeFuses } from '../utils/fuses'
import { AllCurrentFuses, checkPCCBurned, decodeFuses } from '../utils/fuses'
import { decryptName } from '../utils/labels'
import { Domain, Registration, WrappedDomain } from '../utils/subgraph-types'

Expand Down Expand Up @@ -60,8 +60,21 @@ type Params = BaseParams &

const mapDomain = ({ name, ...domain }: Domain) => {
const decrypted = name ? decryptName(name) : undefined

return {
...domain,
...(domain.registration
? {
registration: {
expiryDate: new Date(
parseInt(domain.registration.expiryDate) * 1000,
),
registrationDate: new Date(
parseInt(domain.registration.registrationDate) * 1000,
),
},
}
: {}),
name: decrypted,
truncatedName: decrypted ? truncateFormat(decrypted) : undefined,
createdAt: new Date(parseInt(domain.createdAt) * 1000),
Expand All @@ -70,27 +83,27 @@ const mapDomain = ({ name, ...domain }: Domain) => {
}

const mapWrappedDomain = (wrappedDomain: WrappedDomain) => {
const domain = mapDomain(wrappedDomain.domain) as Omit<
ReturnType<typeof mapDomain>,
'registration'
> & {
registration?: {
expiryDate: string | Date
registrationDate: string | Date
}
}
if (domain.registration) {
domain.registration = {
expiryDate: new Date(
parseInt(domain.registration.expiryDate as string) * 1000,
),
registrationDate: new Date(
parseInt(domain.registration.registrationDate as string) * 1000,
),
}
const expiryDate =
wrappedDomain.expiryDate && wrappedDomain.expiryDate !== '0'
? new Date(parseInt(wrappedDomain.expiryDate) * 1000)
: undefined
if (
expiryDate &&
expiryDate < new Date() &&
checkPCCBurned(wrappedDomain.fuses)
) {
// PCC was burned previously and now the fuses are expired meaning that the
// owner is now 0x0 so we need to filter this out
// if a user's local time is out of sync with the blockchain, this could potentially
// be incorrect. the likelihood of that happening though is very low, and devs
// shouldn't be relying on this value for anything critical anyway.
return null
}

const domain = mapDomain(wrappedDomain.domain)

return {
expiryDate: new Date(parseInt(wrappedDomain.expiryDate) * 1000),
expiryDate,
fuses: decodeFuses(wrappedDomain.fuses),
...domain,
type: 'wrappedDomain',
Expand Down Expand Up @@ -156,6 +169,10 @@ const getNames = async (
domains(first: 1000) {
${domainQueryData}
createdAt
registration {
registrationDate
expiryDate
}
}
wrappedDomains(first: 1000) {
expiryDate
Expand Down Expand Up @@ -187,6 +204,10 @@ const getNames = async (
domains(orderBy: $orderBy, orderDirection: $orderDirection) {
${domainQueryData}
createdAt
registration {
registrationDate
expiryDate
}
}
}
}
Expand Down Expand Up @@ -366,7 +387,8 @@ const getNames = async (
return [
...(account?.domains.map(mapDomain) || []),
...(account?.registrations.map(mapRegistration) || []),
...(account?.wrappedDomains.map(mapWrappedDomain) || []),
...(account?.wrappedDomains.map(mapWrappedDomain).filter((d: any) => d) ||
[]),
].sort((a, b) => {
if (orderDirection === 'desc') {
if (orderBy === 'labelName') {
Expand All @@ -384,7 +406,9 @@ const getNames = async (
return (account?.domains.map(mapDomain) || []) as Name[]
}
if (type === 'wrappedOwner') {
return (account?.wrappedDomains.map(mapWrappedDomain) || []) as Name[]
return (account?.wrappedDomains
.map(mapWrappedDomain)
.filter((d: any) => d) || []) as Name[]
}
return (account?.registrations.map(mapRegistration) || []) as Name[]
}
Expand Down
51 changes: 51 additions & 0 deletions packages/ensjs/src/functions/getSubnames.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ENS } from '..'
import setup from '../tests/setup'
import { decodeFuses } from '../utils/fuses'

let ensInstance: ENS

Expand Down Expand Up @@ -130,6 +131,56 @@ describe('getSubnames', () => {
'truncatedName',
)
})
describe('wrapped subnames', () => {
it('should return fuses', async () => {
const result = await ensInstance.getSubnames({
name: 'wrapped-with-subnames.eth',
pageSize: 10,
orderBy: 'createdAt',
orderDirection: 'desc',
})

expect(result).toBeTruthy()
expect(result.subnames.length).toBe(1)
expect(result.subnameCount).toBe(1)
expect(result.subnames[0].fuses).toBeDefined()
})
it('should return expiry as undefined if 0', async () => {
const result = await ensInstance.getSubnames({
name: 'wrapped-with-expiring-subnames.eth',
pageSize: 10,
orderBy: 'createdAt',
orderDirection: 'desc',
})

expect(result).toBeTruthy()
expect(result.subnames[1].expiryDate).toBeUndefined()
})
it('should return expiry', async () => {
const result = await ensInstance.getSubnames({
name: 'wrapped-with-expiring-subnames.eth',
pageSize: 10,
orderBy: 'createdAt',
orderDirection: 'desc',
})

expect(result).toBeTruthy()
expect(result.subnames[2].expiryDate).toBeInstanceOf(Date)
})
it('should return owner as undefined, fuses as 0, and pccExpired as true if pcc expired', async () => {
const result = await ensInstance.getSubnames({
name: 'wrapped-with-expiring-subnames.eth',
pageSize: 10,
orderBy: 'createdAt',
orderDirection: 'desc',
})

expect(result).toBeTruthy()
expect(result.subnames[0].owner).toBeUndefined()
expect(result.subnames[0].fuses).toStrictEqual(decodeFuses(0))
expect(result.subnames[0].pccExpired).toBe(true)
})
})

describe('with pagination', () => {
it('should get paginated subnames for a name ordered by createdAt in desc order', async () => {
Expand Down
75 changes: 62 additions & 13 deletions packages/ensjs/src/functions/getSubnames.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,36 @@
import { ENSArgs } from '..'
import { truncateFormat } from '../utils/format'
import { AllCurrentFuses, checkPCCBurned, decodeFuses } from '../utils/fuses'
import { decryptName } from '../utils/labels'
import { namehash } from '../utils/normalise'
import { Domain } from '../utils/subgraph-types'

type Subname = {
type BaseSubname = {
id: string
labelName: string | null
truncatedName?: string
labelhash: string
isMigrated: boolean
name: string
owner: {
id: string
}
owner: string | undefined
}

type UnwrappedSubname = BaseSubname & {
fuses?: never
expiryDate?: never
pccExpired?: never
type: 'domain'
}

type WrappedSubname = BaseSubname & {
fuses: AllCurrentFuses
expiryDate: Date
pccExpired: boolean
type: 'wrappedDomain'
}

type Subname = WrappedSubname | UnwrappedSubname

type Params = {
name: string
page?: number
Expand Down Expand Up @@ -90,6 +106,13 @@ const largeQuery = async (
owner {
id
}
wrappedDomain {
fuses
expiryDate
owner {
id
}
}
}
}
}
Expand All @@ -106,18 +129,44 @@ const largeQuery = async (
}
const response = await client.request(finalQuery, queryVars)
const domain = response?.domain
const subdomains = domain.subdomains.map((subname: any) => {
const decrypted = decryptName(subname.name)
const subdomains = domain.subdomains.map(
({ wrappedDomain, ...subname }: Domain) => {
const decrypted = decryptName(subname.name!)

return {
...subname,
name: decrypted,
truncatedName: truncateFormat(decrypted),
}
})
const obj = {
...subname,
labelName: subname.labelName || null,
labelhash: subname.labelhash || '',
name: decrypted,
truncatedName: truncateFormat(decrypted),
owner: subname.owner.id,
type: 'domain',
} as Subname

if (wrappedDomain) {
obj.type = 'wrappedDomain'
const expiryDateAsDate =
wrappedDomain.expiryDate && wrappedDomain.expiryDate !== '0'
? new Date(parseInt(wrappedDomain.expiryDate) * 1000)
: undefined
// if a user's local time is out of sync with the blockchain, this could potentially
// be incorrect. the likelihood of that happening though is very low, and devs
// shouldn't be relying on this value for anything critical anyway.
const hasExpired = expiryDateAsDate && expiryDateAsDate < new Date()
obj.expiryDate = expiryDateAsDate
obj.fuses = decodeFuses(hasExpired ? 0 : wrappedDomain.fuses)
obj.pccExpired = hasExpired
? checkPCCBurned(wrappedDomain.fuses)
: false
obj.owner = obj.pccExpired ? undefined : wrappedDomain.owner.id
}

return obj
},
)

return {
subnames: subdomains,
subnames: subdomains as Subname[],
subnameCount: domain.subdomainCount,
}
}
Expand Down
3 changes: 3 additions & 0 deletions packages/ensjs/src/utils/fuses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,9 @@ export const decodeFuses = (fuses: number) => {
}
}

export const checkPCCBurned = (fuses: number) =>
(fuses & PARENT_CANNOT_CONTROL) === PARENT_CANNOT_CONTROL

export type AllCurrentFuses = ReturnType<typeof decodeFuses>

export default fullFuseEnum

0 comments on commit 428bc3b

Please sign in to comment.