Skip to content

ENSv2 plugin silently overrides RegistrarController address per chain #2048

@shrugs

Description

@shrugs

Problem

apps/ensindexer/src/plugins/ensv2/plugin.ts defines a single Ponder contract entry RegistrarController that aggregates Legacy / Wrapped / Unwrapped (ENSRoot), EA / RegistrarController / UpgradeableRegistrarController (Basenames), and EthRegistrarController (Lineanames) under one chain: { … } map.

Each chainConfigForContract(...) returns an object keyed by chainId.toString(), e.g. { \"1\": { address, startBlock, endBlock } }. Spreading multiple such objects for the same chain id under one chain: {} overwrites the prior entry — only the last spread wins per chain id.

Effect

chain controllers spread what actually gets indexed
1 (mainnet) Legacy → Wrapped → Unwrapped only Unwrapped
8453 (base) EA → RegistrarController → Upgradeable only Upgradeable
59144 (linea) EthRegistrarController EthRegistrarController (single, fine)

So on mainnet, ENSv2's `RegistrarController:NameRegistered` / `NameRenewed` handlers only fire for events emitted by `UnwrappedEthRegistrarController` (`0x59e16fccd424cc24e280be16e11bcd56fb0ce547`, deployed at block 22764821). All Legacy and Wrapped historical registrations (going back to 2020) are silently missed by the ensv2 plugin's controller logic on mainnet. Same data loss on Basenames for EA + RegistrarController.

Where it manifests

`apps/ensindexer/src/plugins/ensv2/plugin.ts:218–272` — the `RegistrarController` contract entry's `chain` block.

Fix

Pass `address` as an array per chain id (Ponder accepts `Address | Address[]`). Build a single `{ [chainId]: { address: [...], startBlock: min, endBlock: max-or-undefined } }` entry per chain instead of spreading multiple chain-id-keyed objects.

Sketch:

```ts
chain: {
[ensroot.chain.id]: {
address: [
"LegacyEthRegistrarController" in ensroot.contracts && ensroot.contracts.LegacyEthRegistrarController.address,
"WrappedEthRegistrarController" in ensroot.contracts && ensroot.contracts.WrappedEthRegistrarController.address,
ensroot.contracts.UnwrappedEthRegistrarController.address,
].filter((a): a is Address => !!a),
startBlock: Math.min(/* defined startBlocks /),
},
...(basenames && {
[basenames.chain.id]: {
address: [
basenames.contracts.EARegistrarController.address,
basenames.contracts.RegistrarController.address,
basenames.contracts.UpgradeableRegistrarController.address,
],
startBlock: Math.min(/
… */),
},
}),
...(lineanames && {
[lineanames.chain.id]: {
address: lineanames.contracts.EthRegistrarController.address,
startBlock: lineanames.contracts.EthRegistrarController.startBlock,
},
}),
},
```

A small helper (e.g. `mergedChainConfig(chainId, contracts: ContractConfig[])`) would clean up the call sites in any plugin doing this kind of merge.

Adjacent: subgraph and registrars plugins

Both currently keep one Ponder contract entry per controller (`Ethnames_LegacyEthRegistrarController`, `Ethnames_WrappedEthRegistrarController`, `Ethnames_UnwrappedEthRegistrarController`, `Ethnames_UniversalRegistrarRenewalWithReferrer`) — they do not have this override bug. But moving them to the same merged `Ethnames_RegistrarController` shape would:

  • eliminate the optional contract-name typesystem problem (the merged entry is always present because at least one controller is)
  • match `apps/ensindexer/src/plugins/ensv2/handlers/ensv1/RegistrarController.ts`, which already dispatches by long-form event signature
  • require including `UniversalRegistrarRenewalWithReferrer` ABI in `AnyRegistrarControllerABI` so its `RenewalReferred` event is part of the merged set, OR keeping URRWR as a separate optional entry

Discovery

Surfaced while reviewing the placeholder-elimination PR (followup to #2045). Independent of the placeholder fix; predates it.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status

    In Progress

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions