diff --git a/.changeset/reclassify-extended-on-proxy-upgrade.md b/.changeset/reclassify-extended-on-proxy-upgrade.md new file mode 100644 index 0000000000..d3d409aa60 --- /dev/null +++ b/.changeset/reclassify-extended-on-proxy-upgrade.md @@ -0,0 +1,5 @@ +--- +"ensindexer": patch +--- + +ENSIndexer now re-derives a Resolver's ENSIP-10 `IExtendedResolver` support when a known proxy Resolver emits an EIP-1967 `Upgraded` event, instead of fixing the value once at first visibility. Proxy Resolvers that activate `IExtendedResolver` via a post-assignment upgrade (e.g. the 3DNS Resolver behind `.box`) were stuck `extended = false` forever, silently breaking wildcard resolution for affected names. diff --git a/apps/ensindexer/src/lib/protocol-acceleration/resolver-db-helpers.ts b/apps/ensindexer/src/lib/protocol-acceleration/resolver-db-helpers.ts index 4b4fcd40ed..64b366a42c 100644 --- a/apps/ensindexer/src/lib/protocol-acceleration/resolver-db-helpers.ts +++ b/apps/ensindexer/src/lib/protocol-acceleration/resolver-db-helpers.ts @@ -33,28 +33,54 @@ type ResolverRecordsCompositeKey = Pick< >; /** - * Ensures a Resolver entity exists for `resolver`, capturing additional metadata. - * - * @dev performs a single `supportsInterface` RPC (via Ponder's cached `context.client`) to determine - * `isExtended` support. + * Ensures a Resolver entity exists for `resolver`, deriving its Supported Interfaces on first insert. */ -export async function upsertResolver( - context: IndexingEngineContext, - resolver: AccountId, -): Promise { +export async function upsertResolver(context: IndexingEngineContext, resolver: AccountId) { const id = makeResolverId(resolver); const existing = await context.ensDb.find(ensIndexerSchema.resolver, { id }); - if (existing) return existing; + + // if already exists, no-op + if (existing) return; + + // insert the new Resolver record + await context.ensDb.insert(ensIndexerSchema.resolver).values({ id, ...resolver }); + + // update its Supported Interfaces + await updateResolverInterfaces(context, resolver); +} + +/** + * Updates a Resolver's Supported Interfaces. + */ +async function updateResolverInterfaces(context: IndexingEngineContext, resolver: AccountId) { + const id = makeResolverId(resolver); const isExtended = await isExtendedResolver({ - publicClient: context.client, address: resolver.address, + publicClient: context.client, }); - const row = { id, ...resolver, isExtended }; - await context.ensDb.insert(ensIndexerSchema.resolver).values(row).onConflictDoNothing(); - return row; + await context.ensDb.update(ensIndexerSchema.resolver, { id }).set({ isExtended }); +} + +/** + * Handles a Resolver's implementation changing by updating its Supported Interfaces, if the Resolver + * is already indexed. + * + * @dev intentionally avoids upserting a Resolver entity for contracts not already an indexed Resolver. + * If a contract were to emit Upgraded and then emit a Resolver event, `upsertResolver` above would + * ensure that the Supported Interfaces are correctly indexed. + */ +export async function handleResolverImplementationChange( + context: IndexingEngineContext, + resolver: AccountId, +) { + const id = makeResolverId(resolver); + const existing = await context.ensDb.find(ensIndexerSchema.resolver, { id }); + if (!existing) return; + + await updateResolverInterfaces(context, resolver); } /** diff --git a/apps/ensindexer/src/plugins/protocol-acceleration/event-handlers.ts b/apps/ensindexer/src/plugins/protocol-acceleration/event-handlers.ts index 8c1b91ffd2..7930d58d1c 100644 --- a/apps/ensindexer/src/plugins/protocol-acceleration/event-handlers.ts +++ b/apps/ensindexer/src/plugins/protocol-acceleration/event-handlers.ts @@ -3,11 +3,13 @@ import attach_ENSv2RegistryHandlers from "./handlers/ENSv2Registry"; import attach_ResolverHandlers from "./handlers/Resolver"; import attach_StandaloneReverseRegistrarHandlers from "./handlers/StandaloneReverseRegistrar"; import attach_ThreeDNSTokenHandlers from "./handlers/ThreeDNSToken"; +import attach_UpgradeableProxyResolverHandlers from "./handlers/UpgradeableProxyResolver"; export default function () { attach_ENSv1RegistryHandlers(); attach_ENSv2RegistryHandlers(); attach_ResolverHandlers(); attach_StandaloneReverseRegistrarHandlers(); + attach_UpgradeableProxyResolverHandlers(); attach_ThreeDNSTokenHandlers(); } diff --git a/apps/ensindexer/src/plugins/protocol-acceleration/handlers/UpgradeableProxyResolver.ts b/apps/ensindexer/src/plugins/protocol-acceleration/handlers/UpgradeableProxyResolver.ts new file mode 100644 index 0000000000..abdae25449 --- /dev/null +++ b/apps/ensindexer/src/plugins/protocol-acceleration/handlers/UpgradeableProxyResolver.ts @@ -0,0 +1,21 @@ +import { PluginName } from "@ensnode/ensnode-sdk"; + +import { getThisAccountId } from "@/lib/get-this-account-id"; +import { addOnchainEventListener } from "@/lib/indexing-engines/ponder"; +import { namespaceContract } from "@/lib/plugin-helpers"; +import { handleResolverImplementationChange } from "@/lib/protocol-acceleration/resolver-db-helpers"; + +const pluginName = PluginName.ProtocolAcceleration; + +/** + * Handlers for any Resolvers that are UpgradeableProxies, necessary for tracking implementation updates. + */ +export default function () { + addOnchainEventListener( + namespaceContract(pluginName, "UpgradeableProxyResolver:Upgraded"), + async ({ context, event }) => { + const resolver = getThisAccountId(context, event); + await handleResolverImplementationChange(context, resolver); + }, + ); +} diff --git a/apps/ensindexer/src/plugins/protocol-acceleration/plugin.ts b/apps/ensindexer/src/plugins/protocol-acceleration/plugin.ts index 67291ce4d9..6e36342d9e 100644 --- a/apps/ensindexer/src/plugins/protocol-acceleration/plugin.ts +++ b/apps/ensindexer/src/plugins/protocol-acceleration/plugin.ts @@ -6,6 +6,7 @@ import { ResolverABI, StandaloneReverseRegistrarABI, ThreeDNSTokenABI, + UpgradeableProxyABI, } from "@ensnode/datasources"; import { buildBlockNumberRange, PluginName } from "@ensnode/ensnode-sdk"; import { @@ -101,6 +102,22 @@ export default createPlugin({ ), }, + //////////////////////////// + // UpgradeableProxyResolver + //////////////////////////// + [namespaceContract(pluginName, "UpgradeableProxyResolver")]: { + abi: UpgradeableProxyABI, + chain: { + // the DotBoxL1Resolver is an UpgradeableProxy + ...("DotBoxL1Resolver" in ensroot.contracts && + chainConfigForContract( + config.globalBlockrange, + ensroot.chain.id, + ensroot.contracts.DotBoxL1Resolver, + )), + }, + }, + ///////////////////// // ENSv1 RegistryOld ///////////////////// diff --git a/packages/datasources/src/abis/shared/UpgradeableProxy.ts b/packages/datasources/src/abis/shared/UpgradeableProxy.ts new file mode 100644 index 0000000000..6978ea3d90 --- /dev/null +++ b/packages/datasources/src/abis/shared/UpgradeableProxy.ts @@ -0,0 +1,18 @@ +import type { Abi } from "viem"; + +/** + * Minimal ABI for EIP-1967 upgradeable proxies. + * + * Captures only the `Upgraded(address indexed implementation)` event, emitted by transparent/UUPS + * proxies whenever their implementation slot changes. + * + * @see https://eips.ethereum.org/EIPS/eip-1967 + */ +export const UpgradeableProxy = [ + { + type: "event", + name: "Upgraded", + inputs: [{ name: "implementation", type: "address", indexed: true, internalType: "address" }], + anonymous: false, + }, +] as const satisfies Abi; diff --git a/packages/datasources/src/index.ts b/packages/datasources/src/index.ts index f94714fb49..c0c6a15279 100644 --- a/packages/datasources/src/index.ts +++ b/packages/datasources/src/index.ts @@ -4,6 +4,7 @@ export { Registry as RegistryABI } from "./abis/ensv2/Registry"; export { L2ReverseRegistrar as L2ReverseRegistrarABI } from "./abis/root/L2ReverseRegistrar"; export { StandaloneReverseRegistrar as StandaloneReverseRegistrarABI } from "./abis/shared/StandaloneReverseRegistrar"; export { UniversalResolverABI } from "./abis/shared/UniversalResolver"; +export { UpgradeableProxy as UpgradeableProxyABI } from "./abis/shared/UpgradeableProxy"; export { ThreeDNSToken as ThreeDNSTokenABI } from "./abis/threedns/ThreeDNSToken"; export * from "./identify-contracts"; export { AnyRegistrarABI } from "./lib/AnyRegistrarABI"; diff --git a/packages/datasources/src/mainnet.ts b/packages/datasources/src/mainnet.ts index f3e2a4925d..642ddf3a29 100644 --- a/packages/datasources/src/mainnet.ts +++ b/packages/datasources/src/mainnet.ts @@ -100,6 +100,11 @@ export default { address: "0x1507ce9421232fdbd302f5ebe4590f8d77febbff", startBlock: 24640923, }, + DotBoxL1Resolver: { + abi: ResolverABI, + address: "0xf97aac6c8dbaebcb54ff166d79706e3af7a813c8", + startBlock: 19128555, + }, // the Resolver for *.argent.xyz names ArgentResolver: {