diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..25fa621 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "typescript.tsdk": "node_modules/typescript/lib" +} diff --git a/package.json b/package.json index e074189..dd6bd8c 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "test": "graph test", "setup": "yarn codegen && yarn create-local && yarn deploy-local", "create-local": "graph create graphprotocol/ens --node http://127.0.0.1:8020", - "deploy-local": "graph deploy graphprotocol/ens --debug --ipfs http://localhost:5001 --node http://127.0.0.1:8020/ --version-label 0.0.1", + "deploy-local": "graph deploy graphprotocol/ens --ipfs http://localhost:5001 --node http://127.0.0.1:8020/ --version-label 0.0.1", "remove-local": "graph remove graphprotocol/ens --node http://127.0.0.1:8020", "docker:setup": "yarn codegen && yarn docker:create-local && yarn docker:deploy-local", "docker:create-local": "graph create graphprotocol/ens --node http://ens-app_graph-node_1:8020", diff --git a/schema.graphql b/schema.graphql index 180392e..90268cf 100644 --- a/schema.graphql +++ b/schema.graphql @@ -15,22 +15,34 @@ type Domain @entity { subdomainCount: Int! "Address logged from current resolver, if any" resolvedAddress: Account - "The account that owns the domain" - owner: Account! + "The resolver that controls the domain's settings" resolver: Resolver "The time-to-live (TTL) value of the domain's records" ttl: BigInt + "Indicates whether the domain has been migrated to a new registrar" isMigrated: Boolean! "The time when the domain was created" createdAt: BigInt! - "The events associated with the domain" - events: [DomainEvent!]! @derivedFrom(field: "domain") + + "The account that owns the domain" + owner: Account! + "The account that owns the ERC721 NFT for the domain" + registrant: Account + "The account that owns the wrapped domain" + wrappedOwner: Account + + "The expiry date for the domain, from either the registration, or the wrapped domain if PCC is burned" + expiryDate: BigInt + "The registration associated with the domain" registration: Registration @derivedFrom(field: "domain") "The wrapped domain associated with the domain" wrappedDomain: WrappedDomain @derivedFrom(field: "domain") + + "The events associated with the domain" + events: [DomainEvent!]! @derivedFrom(field: "domain") } interface DomainEvent { diff --git a/src/ethRegistrar.ts b/src/ethRegistrar.ts index e18e27d..51f9f6e 100644 --- a/src/ethRegistrar.ts +++ b/src/ethRegistrar.ts @@ -6,6 +6,7 @@ import { checkValidLabel, concat, createEventID, + ETH_NODE, uint256ToByteArray, } from "./utils"; @@ -33,9 +34,9 @@ import { Registration, } from "./types/schema"; -var rootNode: ByteArray = byteArrayFromHex( - "93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae" -); +const GRACE_PERIOD_SECONDS = BigInt.fromI32(7776000); // 90 days + +var rootNode: ByteArray = byteArrayFromHex(ETH_NODE); export function handleNameRegistered(event: NameRegisteredEvent): void { let account = new Account(event.params.owner.toHex()); @@ -50,6 +51,9 @@ export function handleNameRegistered(event: NameRegisteredEvent): void { registration.expiryDate = event.params.expires; registration.registrant = account.id; + domain.registrant = account.id; + domain.expiryDate = event.params.expires.plus(GRACE_PERIOD_SECONDS); + let labelName = ens.nameByHash(label.toHexString()); if (labelName != null) { domain.labelName = labelName; @@ -112,8 +116,13 @@ function setNamePreimage(name: string, label: Bytes, cost: BigInt): void { export function handleNameRenewed(event: NameRenewedEvent): void { let label = uint256ToByteArray(event.params.id); let registration = Registration.load(label.toHex())!; + let domain = Domain.load(crypto.keccak256(concat(rootNode, label)).toHex())!; + registration.expiryDate = event.params.expires; + domain.expiryDate = event.params.expires.plus(GRACE_PERIOD_SECONDS); + registration.save(); + domain.save(); let registrationEvent = new NameRenewed(createEventID(event)); registrationEvent.registration = registration.id; @@ -131,7 +140,12 @@ export function handleNameTransferred(event: TransferEvent): void { let registration = Registration.load(label.toHex()); if (registration == null) return; + let domain = Domain.load(crypto.keccak256(concat(rootNode, label)).toHex())!; + registration.registrant = account.id; + domain.registrant = account.id; + + domain.save(); registration.save(); let transferEvent = new NameTransferred(createEventID(event)); diff --git a/src/nameWrapper.ts b/src/nameWrapper.ts index d2c4e25..4f038fb 100644 --- a/src/nameWrapper.ts +++ b/src/nameWrapper.ts @@ -24,6 +24,7 @@ import { createEventID, createOrLoadAccount, createOrLoadDomain, + ETH_NODE, } from "./utils"; function decodeName(buf: Bytes): Array | null { @@ -57,6 +58,12 @@ function decodeName(buf: Bytes): Array | null { return [firstLabel, list.toString()]; } +const PARENT_CANNOT_CONTROL: i32 = 65536; + +function checkPccBurned(fuses: i32): boolean { + return (fuses & PARENT_CANNOT_CONTROL) == PARENT_CANNOT_CONTROL; +} + export function handleNameWrapped(event: NameWrappedEvent): void { let decoded = decodeName(event.params.name); let label: string | null = null; @@ -66,7 +73,8 @@ export function handleNameWrapped(event: NameWrappedEvent): void { name = decoded[1]; } let node = event.params.node; - let fuses = event.params.fuses; + let expiryDate = event.params.expiry; + let fuses = event.params.fuses.toI32(); let blockNumber = event.block.number.toI32(); let transactionID = event.transaction.hash; let owner = createOrLoadAccount(event.params.owner.toHex()); @@ -76,12 +84,19 @@ export function handleNameWrapped(event: NameWrappedEvent): void { domain.labelName = label; domain.name = name; } + if ( + checkPccBurned(fuses) && + (!domain.expiryDate || expiryDate > domain.expiryDate!) + ) { + domain.expiryDate = expiryDate; + } + domain.wrappedOwner = owner.id; domain.save(); let wrappedDomain = new WrappedDomain(node.toHex()); wrappedDomain.domain = domain.id; - wrappedDomain.expiryDate = event.params.expiry; - wrappedDomain.fuses = fuses.toI32(); + wrappedDomain.expiryDate = expiryDate; + wrappedDomain.fuses = fuses; wrappedDomain.owner = owner.id; wrappedDomain.name = name; wrappedDomain.save(); @@ -89,8 +104,8 @@ export function handleNameWrapped(event: NameWrappedEvent): void { let nameWrappedEvent = new NameWrapped(createEventID(event)); nameWrappedEvent.domain = domain.id; nameWrappedEvent.name = name; - nameWrappedEvent.fuses = fuses.toI32(); - nameWrappedEvent.expiryDate = event.params.expiry; + nameWrappedEvent.fuses = fuses; + nameWrappedEvent.expiryDate = expiryDate; nameWrappedEvent.owner = owner.id; nameWrappedEvent.blockNumber = blockNumber; nameWrappedEvent.transactionID = transactionID; @@ -103,6 +118,13 @@ export function handleNameUnwrapped(event: NameUnwrappedEvent): void { let transactionID = event.transaction.hash; let owner = createOrLoadAccount(event.params.owner.toHex()); + let domain = createOrLoadDomain(node.toHex()); + domain.wrappedOwner = null; + if (domain.expiryDate && domain.parent !== ETH_NODE) { + domain.expiryDate = null; + } + domain.save(); + let nameUnwrappedEvent = new NameUnwrapped(createEventID(event)); nameUnwrappedEvent.domain = node.toHex(); nameUnwrappedEvent.owner = owner.id; @@ -122,6 +144,13 @@ export function handleFusesSet(event: FusesSetEvent): void { if (wrappedDomain) { wrappedDomain.fuses = fuses.toI32(); wrappedDomain.save(); + if (wrappedDomain.expiryDate && checkPccBurned(wrappedDomain.fuses)) { + let domain = createOrLoadDomain(node.toHex()); + if (!domain.expiryDate || wrappedDomain.expiryDate > domain.expiryDate!) { + domain.expiryDate = wrappedDomain.expiryDate; + domain.save(); + } + } } let fusesBurnedEvent = new FusesSet(createEventID(event)); fusesBurnedEvent.domain = node.toHex(); @@ -140,6 +169,13 @@ export function handleExpiryExtended(event: ExpiryExtendedEvent): void { if (wrappedDomain) { wrappedDomain.expiryDate = expiry; wrappedDomain.save(); + if (checkPccBurned(wrappedDomain.fuses)) { + let domain = createOrLoadDomain(node.toHex()); + if (!domain.expiryDate || expiry > domain.expiryDate!) { + domain.expiryDate = expiry; + domain.save(); + } + } } let expiryExtendedEvent = new ExpiryExtended(createEventID(event)); expiryExtendedEvent.domain = node.toHex(); @@ -169,9 +205,16 @@ function makeWrappedTransfer( // so we need to create the WrappedDomain entity here if (wrappedDomain == null) { wrappedDomain = new WrappedDomain(namehash); + wrappedDomain.domain = domain.id; + + // placeholders until we get the NameWrapped event + wrappedDomain.expiryDate = BigInt.fromI32(0); + wrappedDomain.fuses = 0; } wrappedDomain.owner = _to.id; wrappedDomain.save(); + domain.wrappedOwner = _to.id; + domain.save(); const wrappedTransfer = new WrappedTransfer(eventID); wrappedTransfer.domain = domain.id; wrappedTransfer.blockNumber = blockNumber; diff --git a/src/utils.ts b/src/utils.ts index e4d287a..2115500 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -9,6 +9,8 @@ export function createEventID(event: ethereum.Event): string { .concat(event.logIndex.toString()); } +export const ETH_NODE = + "93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae"; export const ROOT_NODE = "0x0000000000000000000000000000000000000000000000000000000000000000"; export const EMPTY_ADDRESS = "0x0000000000000000000000000000000000000000"; diff --git a/yarn.lock b/yarn.lock index c324e0e..4537d11 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1782,9 +1782,9 @@ globby@^11.1.0: merge2 "^1.4.1" slash "^3.0.0" -"gluegun@git+https://github.com/edgeandnode/gluegun.git#v4.3.1-pin-colors-dep": +"gluegun@https://github.com/edgeandnode/gluegun#v4.3.1-pin-colors-dep": version "4.3.1" - resolved "git+https://github.com/edgeandnode/gluegun.git#b34b9003d7bf556836da41b57ef36eb21570620a" + resolved "https://github.com/edgeandnode/gluegun#b34b9003d7bf556836da41b57ef36eb21570620a" dependencies: apisauce "^1.0.1" app-module-path "^2.2.0"