Skip to content

Commit

Permalink
Merge pull request #61 from DarkFlorist/fuses-and-ownership
Browse files Browse the repository at this point in the history
fuses and ownership
  • Loading branch information
KillariDev authored Oct 24, 2024
2 parents 18b661b + 47c78da commit 777ce67
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 39 deletions.
9 changes: 5 additions & 4 deletions app/ts/components/App.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Signal, useSignal } from '@preact/signals'
import { useEffect, useRef } from 'preact/hooks'
import { requestAccounts, isValidEnsSubDomain, isChildOwnershipOwnedByOpenRenewManager, getAccounts, getDomainInfos, isPetalLockAndOpenRenewalManagerDeployed, getOpenRenewalManagerAddress, areRequiredFusesBurnt } from '../utils/ensUtils.js'
import { requestAccounts, isValidEnsSubDomain, isChildOwnershipOwnedByOpenRenewManager, getAccounts, getDomainInfos, isPetalLockAndOpenRenewalManagerDeployed, getOpenRenewalManagerAddress, areRequiredFusesBurntWithoutApproval, isChildOwnershipGivenAway } from '../utils/ensUtils.js'
import { BigSpinner } from './Spinner.js'
import { ensureError } from '../utils/utilities.js'
import { AccountAddress, CheckBoxes, DomainInfo, FinalChildChecks, ParentChecks } from '../types/types.js'
Expand Down Expand Up @@ -104,19 +104,20 @@ export function App() {
pathInfo.deepValue = newPathInfo
immutable.value = false
checkBoxes.deepValue = newPathInfo.map((currElement, index): FinalChildChecks | ParentChecks => {
const fusesBurned = areRequiredFusesBurnt(index, newPathInfo)
const fusesBurned = areRequiredFusesBurntWithoutApproval(index, newPathInfo)
const base = {
exists: currElement.registered,
isWrapped: currElement.isWrapped,
fusesBurned,
domainInfo: currElement,
}
if (index === newPathInfo.length - 1) {
immutable.value = currElement.isWrapped && fusesBurned && isChildOwnershipOwnedByOpenRenewManager(currElement)
immutable.value = currElement.isWrapped && fusesBurned && isChildOwnershipGivenAway(currElement)
return {
...base,
type: 'finalChild' as const,
ownershipOpenRenewalContract: isChildOwnershipOwnedByOpenRenewManager(currElement),
childOwnershipIsGivenAway: isChildOwnershipGivenAway(currElement),
immutable: immutable.value,
contentHashIsSet: currElement.contentHash !== '0x',
resolutionAddressIsSet: BigInt(currElement.resolutionAddress) !== 0n,
Expand All @@ -125,7 +126,7 @@ export function App() {
return {
...base,
type: 'parent' as const,
openRenewalContractIsApproved: currElement.approved === getOpenRenewalManagerAddress()
openRenewalContractIsApproved: currElement.approved === getOpenRenewalManagerAddress() && currElement.fuses.includes('Cannot Approve')
}
})
errorString.value = undefined
Expand Down
57 changes: 35 additions & 22 deletions app/ts/components/requirements.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { computed, Signal, useSignal } from '@preact/signals'
import { AccountAddress, CheckBoxes, FinalChildChecks, ParentChecks } from '../types/types.js'
import { AccountAddress, CheckBoxes, EnsFuseName, FinalChildChecks, ParentChecks } from '../types/types.js'
import { ENS_TOKEN_WRAPPER } from '../utils/constants.js'
import { callPetalLock, deployPetalLockAndRenewalManager, getOpenRenewalManagerAddress, getRequiredFuses, renewDomainByYear, renewDomainToMax } from '../utils/ensUtils.js'
import { callPetalLock, deployPetalLockAndRenewalManager, getOpenRenewalManagerAddress, getRequiredFusesWithoutApproval, renewDomainByYear, renewDomainToMax } from '../utils/ensUtils.js'
import { isSameAddress } from '../utils/utilities.js'
import { OptionalSignal } from '../utils/OptionalSignal.js'
import { isValidContentHashString } from '../utils/contenthash.js'
Expand All @@ -23,28 +23,39 @@ export const SwitchAddress = ({ maybeSigningAddress, maybeAccountAddress, requir
return <p class = 'paragraph' style = 'color: #b43c42'> { ` - Switch to ${ maybeSigningAddress } to sign` } </p>
}

const arrayToString = (array: readonly string[]) => array.map((n) => `"${ n }"`).join(', ')

export const ChildRequirements = ( { checkBoxes, fuses } : { checkBoxes: FinalChildChecks, fuses: readonly string[] }) => {
const getFuseString = (alreadyBurntBuses: readonly EnsFuseName[], requiredFuses: readonly EnsFuseName[]) => {
const fusesToBurnStill = requiredFuses.filter((fuse) => !alreadyBurntBuses.includes(fuse))
if (alreadyBurntBuses.length === 0) return `The fuses ${ arrayToString(fusesToBurnStill) } need to be burnt`
if (fusesToBurnStill.length === 0) return `Currently the fuses ${ arrayToString(alreadyBurntBuses) } are burnt`
return `Currently the fuses ${ arrayToString(alreadyBurntBuses) } are burnt. It it still needed to burn following fuses: ${ arrayToString(fusesToBurnStill) }`
}

export const ChildRequirements = ( { checkBoxes, fuses } : { checkBoxes: FinalChildChecks, fuses: readonly EnsFuseName[] }) => {
const fuseError = getFuseString(checkBoxes.domainInfo.fuses, fuses)
return <>
<p class = 'subdomain-header'>{ checkBoxes.domainInfo.subDomain } </p>
<div class = 'grid-container'>
<Requirement checked = { checkBoxes.exists } primarytext = { `${ checkBoxes.domainInfo.subDomain } exists` } />
<Requirement checked = { checkBoxes.isWrapped } primarytext = { `${ checkBoxes.domainInfo.subDomain } is wrapped` } />
<Requirement checked = { checkBoxes.fusesBurned } primarytext = { `${ checkBoxes.domainInfo.subDomain } fuses are burnt` } secondaryText = { `The fuses ${ fuses.map((n) => `"${ n }"`).join(', ') } are burnt` } />
<Requirement checked = { checkBoxes.fusesBurned } primarytext = { `${ checkBoxes.domainInfo.subDomain } fuses are burnt` } secondaryText = { fuseError } />
<Requirement checked = { checkBoxes.childOwnershipIsGivenAway } primarytext = { 'The domain control is restricted' } secondaryText = 'The owner of the domain need to be burned or controlled by the Open Renewal Contract'/>
<Requirement checked = { checkBoxes.ownershipOpenRenewalContract } primarytext = { `${ checkBoxes.domainInfo.subDomain } is owned by Open Renewal Contract` } secondaryText = 'The ownership of subdomain is moved to an Open Renewal Contract that allows anyone to renew the domain.'/>
<Requirement checked = { checkBoxes.contentHashIsSet || checkBoxes.resolutionAddressIsSet } primarytext = { 'Content hash or address is set'} secondaryText = 'Content hash or address should be set for the domain to be useful'/>
</div>
</>
}

export const ParentRequirements = ( { checkBoxes, fuses } : { checkBoxes: ParentChecks, fuses: readonly string[] }) => {
export const ParentRequirements = ( { checkBoxes, fuses } : { checkBoxes: ParentChecks, fuses: readonly EnsFuseName[] }) => {
const fuseError = getFuseString(checkBoxes.domainInfo.fuses, fuses)
return <>
<p class = 'subdomain-header'>{ checkBoxes.domainInfo.subDomain } </p>
<p class = 'subdomain-header'>{ checkBoxes.domainInfo.subDomain } </p>
<div class = 'grid-container'>
<Requirement checked = { checkBoxes.exists } primarytext = { `${ checkBoxes.domainInfo.subDomain } exists` } />
<Requirement checked = { checkBoxes.isWrapped } primarytext = { `${ checkBoxes.domainInfo.subDomain } is wrapped` } />
<Requirement checked = { checkBoxes.fusesBurned } primarytext = { `${ checkBoxes.domainInfo.subDomain } fuses are burnt` } secondaryText = { `The fuses ${ fuses.map((n) => `"${ n }"`).join(', ') } are burnt` } />
<Requirement checked = { checkBoxes.openRenewalContractIsApproved } primarytext = { `${ checkBoxes.domainInfo.subDomain } has approved Open Renewal Contract` } secondaryText = { `Contract ${ getOpenRenewalManagerAddress() } needs to be approved in order for anyone to be able to renew the name.` } />
<Requirement checked = { checkBoxes.fusesBurned } primarytext = { `${ checkBoxes.domainInfo.subDomain } fuses are burnt` } secondaryText = { fuseError } />
<Requirement checked = { checkBoxes.openRenewalContractIsApproved } primarytext = { `${ checkBoxes.domainInfo.subDomain } has approved Open Renewal Contract` } secondaryText = { `Contract ${ getOpenRenewalManagerAddress() } needs to be approved and fuses "Cannot Approve" needs to be burned in order for anyone to be able to renew the name.` } />
</div>
</>
}
Expand All @@ -53,7 +64,7 @@ export const Requirements = ({ checkBoxesArray } : { checkBoxesArray: OptionalSi
const allCheckBoxes = checkBoxesArray.deepValue
if (allCheckBoxes === undefined) return <></>
return <div class = 'grid-container-bordered'> { [...allCheckBoxes].reverse().map((check, index) => {
const fuses = getRequiredFuses(index, allCheckBoxes.map((c) => c.domainInfo))
const fuses = getRequiredFusesWithoutApproval(allCheckBoxes.length - index - 1, allCheckBoxes.map((c) => c.domainInfo))
if (check.type === 'parent') return <ParentRequirements checkBoxes = { check } fuses = { fuses }/>
return <ChildRequirements checkBoxes = { check } fuses = { fuses }/>
}) } </div>
Expand Down Expand Up @@ -234,24 +245,26 @@ export const Create = ( { contentHashInput, resolutionAddressInput, loadingInfos
})

if (checkBoxes.deepValue === undefined) return <></>
const subDomain = checkBoxes.deepValue[checkBoxes.deepValue.length - 1]?.domainInfo.subDomain
const finalChild = checkBoxes.deepValue[checkBoxes.deepValue.length - 1]
const subDomain = finalChild?.domainInfo.subDomain
if (subDomain === undefined) throw new Error('missing subdomain')

return <div style = 'padding-top: 10px;'>
{ immutable.value ? <div key = 'dialog' class = 'extend-dialog'>
<p style = 'white-space: nowrap; margin: 0; font-size: 24px; padding-bottom: 10px; justify-self: center;'>{ `Renew ${ subDomain }` }</p>
<div style = 'justify-content: center;'>
<p style = 'font-size: 24px;'> Renew by&nbsp;</p> <YearPicker validYear = { isYearValid } year = { extendYear }/> <p style = 'font-size: 24px;'>&nbsp;years </p>
<button style = 'font-size: 3em;' class = 'button is-primary' disabled = { extending.value || !isYearValid.value } onClick = { renewByYear }> Renew { extending.value ? <Spinner/> : <></> }</button>
</div>
{ !level2DomainExpiryBiggerThanLowestLevelExpiry.value || checkBoxes.deepValue[0] === undefined ? <></> : <>
<div style = 'justify-content: center; font-size: 24px;'>
<p> OR </p>
</div>
{ !(finalChild?.type === 'finalChild' && finalChild.ownershipOpenRenewalContract) ? <div style = 'justify-content: center;'> <p class = 'paragraph' style = 'color: #b43c42'> { `Warning: ${ subDomain } cannot be renewed. It will become mutable when expired.` } </p> </div>: <>
<p style = 'white-space: nowrap; margin: 0; font-size: 24px; padding-bottom: 10px; justify-self: center;'>{ `Renew ${ subDomain }` }</p>
<div style = 'justify-content: center;'>
<p style = 'font-size: 24px;' >{ `Renew without renewing ${ checkBoxes.deepValue[0].domainInfo.subDomain }` }</p>
<button style = 'font-size: 3em;' class = 'button is-primary' disabled = { extending.value } onClick = { renewToMax }> { `Renew to ${ checkBoxes.deepValue[0].domainInfo.expiry.toISOString().substring(0, 10) }` } { extending.value ? <Spinner/> : <></> }</button>
<p style = 'font-size: 24px;'> Renew by&nbsp;</p> <YearPicker validYear = { isYearValid } year = { extendYear }/> <p style = 'font-size: 24px;'>&nbsp;years </p>
<button style = 'font-size: 3em;' class = 'button is-primary' disabled = { extending.value || !isYearValid.value } onClick = { renewByYear }> Renew { extending.value ? <Spinner/> : <></> }</button>
</div>
{ !level2DomainExpiryBiggerThanLowestLevelExpiry.value || checkBoxes.deepValue[0] === undefined ? <></> : <>
<div style = 'justify-content: center; font-size: 24px;'>
<p> OR </p>
</div>
<div style = 'justify-content: center;'>
<p style = 'font-size: 24px;' >{ `Renew without renewing ${ checkBoxes.deepValue[0].domainInfo.subDomain }` }</p>
<button style = 'font-size: 3em;' class = 'button is-primary' disabled = { extending.value } onClick = { renewToMax }> { `Renew to ${ checkBoxes.deepValue[0].domainInfo.expiry.toISOString().substring(0, 10) }` } { extending.value ? <Spinner/> : <></> }</button>
</div>
</> }
</> }
</div> : <div key = 'dialog'>
<div style = 'padding: 10px;'>
Expand Down
1 change: 1 addition & 0 deletions app/ts/types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export type FinalChildChecks = {
isWrapped: boolean,
fusesBurned: boolean,
ownershipOpenRenewalContract: boolean,
childOwnershipIsGivenAway: boolean,
immutable: boolean,
contentHashIsSet: boolean,
domainInfo: DomainInfo,
Expand Down
8 changes: 0 additions & 8 deletions app/ts/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,23 +35,15 @@ export const ENS_FLAGS = [
] as const

export const FINAL_CHILD_FUSES = [
'Cannot Unwrap Name',
'Cannot Burn Fuses',
'Cannot Set Resolver',
'Cannot Set Time To Live',
'Cannot Create Subdomain',
'Cannot Approve',
'Parent Domain Cannot Control',
'Can Extend Expiry'
] as const

export const SINGLE_DOMAIN_FUSES = [
'Cannot Unwrap Name',
'Is .eth domain',
] as const

export const TOP_PARENT_FUSES = [
'Is .eth domain',
'Cannot Unwrap Name',
'Cannot Approve',
] as const
Expand Down
28 changes: 23 additions & 5 deletions app/ts/utils/ensUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,18 +170,24 @@ export const getDomainInfos = async (accountAddress: AccountAddress | undefined,
}))
}

export const getRequiredFuses = (domainIndex: number, domainInfos: readonly DomainInfo[]) => {
if (domainIndex === 0 && domainInfos.length === 1) return SINGLE_DOMAIN_FUSES
export const getRequiredFusesWithoutApproval = (domainIndex: number, domainInfos: readonly DomainInfo[]) => {
return getRequiredFusesWithApproval(domainIndex, domainInfos).filter((fuse) => fuse !== 'Cannot Approve' && fuse !== 'Can Extend Expiry')
}

export const getRequiredFusesWithApproval = (domainIndex: number, domainInfos: readonly DomainInfo[]) => {
console.log('getRequiredFusesWithApproval')
console.log(domainInfos)
if (domainInfos.length === 1) return SINGLE_DOMAIN_FUSES
if (domainIndex === 0 && domainInfos.length > 1) return TOP_PARENT_FUSES
if (domainIndex === domainInfos.length - 1) return FINAL_CHILD_FUSES
return MID_PARENT_FUSES;
return MID_PARENT_FUSES
}

export const areRequiredFusesBurnt = (domainIndex: number, domainInfos: readonly DomainInfo[]) => {
export const areRequiredFusesBurntWithoutApproval = (domainIndex: number, domainInfos: readonly DomainInfo[]) => {
const domainInfo = domainInfos[domainIndex]
if (domainInfo === undefined) throw new Error('wrong index')
if (!domainInfo.isWrapped) return false
const requiredFuses = getRequiredFuses(domainIndex, domainInfos)
const requiredFuses = getRequiredFusesWithoutApproval(domainIndex, domainInfos)
for (const requiredFuse of requiredFuses) {
if (!domainInfo.fuses.includes(requiredFuse)) return false
}
Expand All @@ -198,6 +204,18 @@ export const isChildOwnershipOwnedByOpenRenewManager = (childInfo: DomainInfo) =
return BigInt(getOpenRenewalManagerAddress()) === BigInt(childInfo.owner) && childInfo.isWrapped
}

export const isChildOwnershipGivenAway = (childInfo: DomainInfo) => {
const owner = BigInt(childInfo.owner)
const renewalManager = BigInt(getOpenRenewalManagerAddress())
const burnAddresses = [
0x0000000000000000000000000000000000000000n,
0x000000000000000000000000000000000000deadn,
0xdead000000000000000000000000000000000000n,
0xdeaDDeADDEaDdeaDdEAddEADDEAdDeadDEADDEaDn
]
return (renewalManager === owner || burnAddresses.includes(owner)) && childInfo.isWrapped
}

export const proxyDeployerAddress = `0x7a0d94f55792c434d74a40883c6ed8545e406d12`

export async function ensureProxyDeployerDeployed(accountAddress: AccountAddress): Promise<void> {
Expand Down

0 comments on commit 777ce67

Please sign in to comment.