From a150566822e4885444f501663c4ce36dfe16907f Mon Sep 17 00:00:00 2001 From: Khaidar Kairbek Date: Sat, 28 Dec 2024 09:18:51 +0500 Subject: [PATCH] filters build: args required, filters array, singular event --- .../src/build/configAndIndexingFunctions.ts | 96 ++++++++++--------- packages/core/src/config/eventFilter.ts | 65 ++++++------- packages/core/src/sync/abi.ts | 88 +++-------------- 3 files changed, 97 insertions(+), 152 deletions(-) diff --git a/packages/core/src/build/configAndIndexingFunctions.ts b/packages/core/src/build/configAndIndexingFunctions.ts index b21501bff..4657356ac 100644 --- a/packages/core/src/build/configAndIndexingFunctions.ts +++ b/packages/core/src/build/configAndIndexingFunctions.ts @@ -342,30 +342,18 @@ export async function buildConfigAndIndexingFunctions({ : [source.filter]; for (const filter of source.filter) { - if (Array.isArray(filter.event) && filter.args !== undefined) { + const abiEvent = abiEvents.bySafeName[filter.event]; + if (!abiEvent) { throw new Error( - `Validation failed: Event filter for contract '${source.name}' cannot contain indexed argument values if multiple events are provided.`, + `Validation failed: Invalid filter for contract '${ + source.name + }'. Got event name '${filter.event}', expected one of [${Object.keys( + abiEvents.bySafeName, + ) + .map((n) => `'${n}'`) + .join(", ")}].`, ); } - - const filterSafeEventNames = Array.isArray(filter.event) - ? filter.event - : [filter.event]; - - for (const filterSafeEventName of filterSafeEventNames) { - const abiEvent = abiEvents.bySafeName[filterSafeEventName]; - if (!abiEvent) { - throw new Error( - `Validation failed: Invalid filter for contract '${ - source.name - }'. Got event name '${filterSafeEventName}', expected one of [${Object.keys( - abiEvents.bySafeName, - ) - .map((n) => `'${n}'`) - .join(", ")}].`, - ); - } - } } // TODO: Explicit validation of indexed argument value format (array or object). @@ -373,41 +361,59 @@ export async function buildConfigAndIndexingFunctions({ // is an invariant of the current filter design. // Note: This can throw. - topicsArray = buildTopics(source.abi, source.filter); + const filteredTopicsArray = buildTopics( + source.abi, + source.filter, + ).reduce( + (acc, cur) => { + if (acc.includes(cur) === false) { + acc.push(cur); + } - const filteredEventSelectors = topicsArray.reduce((acc, cur) => { - const topic0 = Array.isArray(cur.topic0) ? cur.topic0 : [cur.topic0]; + return acc; + }, + [] as { + topic0: Hex; + topic1: Hex | Hex[] | null; + topic2: Hex | Hex[] | null; + topic3: Hex | Hex[] | null; + }[], + ); + + const filteredEventSelectors = filteredTopicsArray.reduce( + (acc, cur) => { + const eventSelector = cur.topic0; - for (const eventSelector of topic0) { if ( eventSelector !== null && acc.includes(eventSelector) === false ) { acc.push(eventSelector); } - } - return acc; - }, [] as Hex[]); + return acc; + }, + [] as Hex[], + ); - // Validate that the topic0 value defined by the `eventFilter` is a superset of the - // registered indexing functions. Simply put, confirm that no indexing function is - // defined for a log event that is excluded by the filter. - for (const registeredEventSelector of registeredEventSelectors) { - if (!filteredEventSelectors.includes(registeredEventSelector)) { - const logEventName = - abiEvents.bySelector[registeredEventSelector]!.safeName; + // Merge filtered topics and registered event selectors + const excludedRegisteredEventSelectors = + registeredEventSelectors.filter( + (s) => filteredEventSelectors.includes(s) === false, + ); - throw new Error( - `Validation failed: Event '${logEventName}' is excluded by the event filter defined on the contract '${ - source.name - }'. Got '${logEventName}', expected one of [${filteredEventSelectors - .map((s) => abiEvents.bySelector[s]!.safeName) - .map((eventName) => `'${eventName}'`) - .join(", ")}].`, - ); - } - } + topicsArray = + excludedRegisteredEventSelectors.length > 0 + ? [ + { + topic0: excludedRegisteredEventSelectors, + topic1: null, + topic2: null, + topic3: null, + }, + ...filteredTopicsArray, + ] + : filteredTopicsArray; } const startBlockMaybeNan = source.startBlock; diff --git a/packages/core/src/config/eventFilter.ts b/packages/core/src/config/eventFilter.ts index 8411945a0..19647b90a 100644 --- a/packages/core/src/config/eventFilter.ts +++ b/packages/core/src/config/eventFilter.ts @@ -1,6 +1,8 @@ import type { Abi, GetEventArgs } from "viem"; import type { ParseAbiEvent, SafeEventNames } from "./utilityTypes.js"; +// TODO: Fix this type ( changes: args are required, event is singular for each filter ) +// Filters are only for topic1/2/3 filtering export type GetEventFilter< abi extends Abi, contract, @@ -8,46 +10,41 @@ export type GetEventFilter< safeEventNames extends string = SafeEventNames, > = contract extends { filter: { - event: infer event extends readonly string[] | string; + event: infer event extends string; }; } - ? // 1. Contract has a filter with event - event extends readonly string[] - ? // 1.a Filter event is an array + ? event extends safeEventNames + ? // 1.b.i Filter event is a valid string { filter?: { - event: readonly safeEventNames[]; + event: safeEventNames | event; + args: GetEventArgs< + abi, + string, + { + EnableUnion: true; + IndexedOnly: true; + Required: false; + }, + ParseAbiEvent + >; }; } - : // 1.b Filter event is a string - event extends safeEventNames - ? // 1.b.i Filter event is a valid string - { - filter?: { - event: safeEventNames | event; - args?: GetEventArgs< - abi, - string, - { - EnableUnion: true; - IndexedOnly: true; - Required: false; - }, - ParseAbiEvent - >; - }; - } - : // 1.b.ii Filter event is an invalid string - { - filter?: { + : // 1.b.ii Filter event is an invalid string + { + filter?: { + event: safeEventNames; + args: GetEventArgs; + }; + } + : { + filter?: + | { + event: safeEventNames; + args: GetEventArgs; + }[] + | { event: safeEventNames; - args?: GetEventArgs; + args: GetEventArgs; }; - } - : // 2. Contract doesn't have a filter with event - { - filter?: { - event: safeEventNames | readonly safeEventNames[]; - args?: GetEventArgs; - }; }; diff --git a/packages/core/src/sync/abi.ts b/packages/core/src/sync/abi.ts index 9fef0b401..f48d76abf 100644 --- a/packages/core/src/sync/abi.ts +++ b/packages/core/src/sync/abi.ts @@ -92,85 +92,27 @@ export function buildTopics( abi: Abi, filter: NonNullable, ): { - topic0: Hex | Hex[]; + topic0: Hex; topic1: Hex | Hex[] | null; topic2: Hex | Hex[] | null; topic3: Hex | Hex[] | null; }[] { const filters = Array.isArray(filter) ? filter : [filter]; - const topics = filters - .map((filter) => { - if (Array.isArray(filter.event)) { - // List of event signatures - return { - topic0: filter.event.map((event) => - toEventSelector(findAbiEvent(abi, event)), - ), - topic1: null, - topic2: null, - topic3: null, - }; - } else { - // Single event with args - const topics = encodeEventTopics({ - abi: [findAbiEvent(abi, filter.event)], - args: filter.args as GetEventArgs, - }); - - return { - topic0: topics[0], - topic1: topics[1] ?? null, - topic2: topics[2] ?? null, - topic3: topics[3] ?? null, - }; - } - }) - .reduce( - // Merge similar topics - (acc, cur) => { - // Ignore if topics in array - if (acc.some((t) => t === cur) === true) { - return acc; - } - - // Push as is if args defined - if (cur.topic1 !== null || cur.topic2 !== null || cur.topic3 !== null) { - acc.push(cur); - return acc; - } - - // Merge topic0 with topics with no args defined - const isMerged = acc.some((t, idx) => { - if (t.topic1 === null && t.topic2 === null && t.topic3 === null) { - const topic0 = Array.isArray(t.topic0) ? t.topic0 : [t.topic0]; - const curTopic0 = Array.isArray(cur.topic0) - ? cur.topic0 - : [cur.topic0]; - acc[idx] = { - topic0: Array.from(new Set([...topic0, ...curTopic0])), - topic1: null, - topic2: null, - topic3: null, - }; - return true; - } - return false; - }); - - if (isMerged === false) { - acc.push(cur); - } - - return acc; - }, - [] as { - topic0: Hex | Hex[]; - topic1: Hex | Hex[] | null; - topic2: Hex | Hex[] | null; - topic3: Hex | Hex[] | null; - }[], - ); + const topics = filters.map((filter) => { + // Single event with args + const topics = encodeEventTopics({ + abi: [findAbiEvent(abi, filter.event)], + args: filter.args as GetEventArgs, + }); + + return { + topic0: topics[0], + topic1: topics[1] ?? null, + topic2: topics[2] ?? null, + topic3: topics[3] ?? null, + }; + }); return topics; }