Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: fetch fuses in getSubnames() #113

Merged
merged 5 commits into from
Feb 10, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
29 changes: 25 additions & 4 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 @@ -70,6 +70,23 @@ const mapDomain = ({ name, ...domain }: Domain) => {
}

const mapWrappedDomain = (wrappedDomain: WrappedDomain) => {
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) as Omit<
ReturnType<typeof mapDomain>,
'registration'
Expand All @@ -89,8 +106,9 @@ const mapWrappedDomain = (wrappedDomain: WrappedDomain) => {
),
}
}

return {
expiryDate: new Date(parseInt(wrappedDomain.expiryDate) * 1000),
expiryDate,
fuses: decodeFuses(wrappedDomain.fuses),
...domain,
type: 'wrappedDomain',
Expand Down Expand Up @@ -366,7 +384,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 +403,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