Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
5 changes: 5 additions & 0 deletions .changeset/eliminate-zero-address-placeholders.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@ensnode/datasources": patch
---

Removed `LegacyEthRegistrarController`, `WrappedEthRegistrarController`, and `UniversalRegistrarRenewalWithReferrer` placeholder entries from the `sepolia-v2` namespace, and `UniversalRegistrarRenewalWithReferrer` from `ens-test-env`. `AnyRegistrarControllerABI` now also includes the `UniversalRegistrarRenewalWithReferrer` ABI.
5 changes: 5 additions & 0 deletions .changeset/ensv2-registrar-controller-bug.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"ensindexer": patch
---

Fixed a bug in `ensv2` plugin where only `UnwrappedEthRegistrarController` was indexed; `LegacyEthRegistrarController` and `WrappedEthRegistrarController` were silently dropped. Fixes #2048.
5 changes: 0 additions & 5 deletions .changeset/skip-zero-address-placeholders-blockrange.md

This file was deleted.

69 changes: 69 additions & 0 deletions apps/ensindexer/src/lib/ponder-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,75 @@ export function chainConfigForContract<CONTRACT_CONFIG extends ContractConfig>(
};
}

/**
* Picks contracts from a datasource's `contracts` map by name, dropping any that are absent
* at runtime — e.g. namespace-conditional contracts that don't exist in the active namespace.
*
* Useful for collecting contracts to pass to {@link mergedChainConfigForContracts}.
*/
export function pickContracts(
contracts: Record<string, ContractConfig>,
names: readonly string[],
Comment on lines +159 to +161
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had an idea that maybe it's possible to make something like

contracts: Record<T, ContractConfig>,
names: T[]

But I lost to compiler, I just dont understand how typescript works...

): ContractConfig[] {
return names
.map((name) => contracts[name] as ContractConfig | undefined)
.filter((c): c is ContractConfig => !!c);
}

/**
* Builds a single Ponder `chain: { [chainId]: { address, startBlock, endBlock } }` entry that
* spans multiple contracts on the same chain (e.g. all of the .eth RegistrarControllers).
*
* Use this when one Ponder contract entry should index events from multiple contracts with addresses
* that share an ABI on the same chain.
*
* - `address` is the union of all defined contract addresses on this chain.
* - `startBlock` is the earliest contract `startBlock`.
* - `endBlock` is the latest contract `endBlock` if every contract specifies one, otherwise undefined.
*
* The result is then constrained against `globalBlockrange` like {@link chainConfigForContract}.
* Pass `contracts` as an array; callers can use `.filter(...)` to drop namespace-conditional ones.
*/
export function mergedChainConfigForContracts(
globalBlockrange: BlockNumberRange,
chainId: number,
contracts: readonly ContractConfig[],
) {
if (contracts.length === 0) {
throw new Error("mergedChainConfigForContracts: contracts must not be empty");
}

const addresses = contracts.flatMap((c) =>
Array.isArray(c.address) ? c.address : c.address ? [c.address] : [],
);

const minStartBlock = contracts.reduce(
(memo, c) => Math.min(memo, c.startBlock),
Number.POSITIVE_INFINITY,
);
Comment thread
shrugs marked this conversation as resolved.
Comment thread
coderabbitai[bot] marked this conversation as resolved.

const allHaveEnd = contracts.every((c) => c.endBlock !== undefined);
const maxEndBlock = allHaveEnd
? contracts.reduce((memo, c) => Math.max(memo, c.endBlock as number), 0)
: undefined;

const { startBlock, endBlock } = constrainBlockrange(
globalBlockrange,
buildBlockNumberRange(minStartBlock, maxEndBlock),
);

return {
[chainId.toString()]: {
// when no contract supplies an address, leave `address` undefined so Ponder treats this as
// factory-mode ("index any address matching the ABI") rather than an explicit empty list,
// which Ponder treats as "index nothing".
address: addresses.length > 0 ? addresses : undefined,
startBlock,
endBlock,
},
};
}
Comment thread
shrugs marked this conversation as resolved.

/**
* TODO
*/
Expand Down
47 changes: 18 additions & 29 deletions apps/ensindexer/src/plugins/ensv2/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import {
constrainBlockrange,
getRequiredDatasources,
maybeGetDatasources,
mergedChainConfigForContracts,
pickContracts,
} from "@/lib/ponder-helpers";

export const pluginName = PluginName.ENSv2;
Expand Down Expand Up @@ -221,51 +223,38 @@ export default createPlugin({
///////////////////////////////////
// Ethnames Registrar Controllers
///////////////////////////////////
...chainConfigForContract(
config.globalBlockrange,
ensroot.chain.id,
ensroot.contracts.LegacyEthRegistrarController,
),
...chainConfigForContract(
config.globalBlockrange,
ensroot.chain.id,
ensroot.contracts.WrappedEthRegistrarController,
),
...chainConfigForContract(
...mergedChainConfigForContracts(
config.globalBlockrange,
ensroot.chain.id,
ensroot.contracts.UnwrappedEthRegistrarController,
pickContracts(ensroot.contracts, [
"LegacyEthRegistrarController",
"WrappedEthRegistrarController",
"UnwrappedEthRegistrarController",
]),
),

///////////////////////////////////
// Basenames Registrar Controllers
///////////////////////////////////
...(basenames && {
...chainConfigForContract(
config.globalBlockrange,
basenames.chain.id,
basenames.contracts.EARegistrarController,
),
...chainConfigForContract(
config.globalBlockrange,
basenames.chain.id,
basenames.contracts.RegistrarController,
),
...chainConfigForContract(
...(basenames &&
mergedChainConfigForContracts(
config.globalBlockrange,
basenames.chain.id,
basenames.contracts.UpgradeableRegistrarController,
),
}),
pickContracts(basenames.contracts, [
"EARegistrarController",
"RegistrarController",
"UpgradeableRegistrarController",
]),
)),

////////////////////////////////////
// Lineanames Registrar Controllers
////////////////////////////////////
...(lineanames &&
chainConfigForContract(
mergedChainConfigForContracts(
config.globalBlockrange,
lineanames.chain.id,
lineanames.contracts.EthRegistrarController,
pickContracts(lineanames.contracts, ["EthRegistrarController"]),
)),
},
},
Expand Down
Loading
Loading