diff --git a/.changeset/gorgeous-dryers-act.md b/.changeset/gorgeous-dryers-act.md new file mode 100644 index 0000000000..af03d2d2d8 --- /dev/null +++ b/.changeset/gorgeous-dryers-act.md @@ -0,0 +1,5 @@ +--- +"viem": patch +--- + +Improved performance of `parseEventLogs` diff --git a/src/utils/abi/decodeEventLog.test.ts b/src/utils/abi/decodeEventLog.test.ts index 34f56056e6..2c9458f4b8 100644 --- a/src/utils/abi/decodeEventLog.test.ts +++ b/src/utils/abi/decodeEventLog.test.ts @@ -882,6 +882,17 @@ test("errors: event doesn't exist", () => { name: 'Bar', type: 'event', }, + { + inputs: [ + { + indexed: true, + name: 'message', + type: 'string', + }, + ], + name: 'Baz', + type: 'event', + }, ], topics: [ '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', diff --git a/src/utils/abi/decodeEventLog.ts b/src/utils/abi/decodeEventLog.ts index c8b4a51368..a509f20a3c 100644 --- a/src/utils/abi/decodeEventLog.ts +++ b/src/utils/abi/decodeEventLog.ts @@ -116,11 +116,15 @@ export function decodeEventLog< const [signature, ...argTopics] = topics if (!signature) throw new AbiEventSignatureEmptyTopicsError({ docsPath }) - const abiItem = abi.find( - (x) => - x.type === 'event' && - signature === toEventSelector(formatAbiItem(x) as EventDefinition), - ) + const abiItem = (() => { + if (abi.length === 1) return abi[0] + return abi.find( + (x) => + x.type === 'event' && + signature === toEventSelector(formatAbiItem(x) as EventDefinition), + ) + })() + if (!(abiItem && 'name' in abiItem) || abiItem.type !== 'event') throw new AbiEventSignatureNotFoundError(signature, { docsPath }) diff --git a/src/utils/abi/parseEventLogs.bench.ts b/src/utils/abi/parseEventLogs.bench.ts new file mode 100644 index 0000000000..9c342c901c --- /dev/null +++ b/src/utils/abi/parseEventLogs.bench.ts @@ -0,0 +1,81 @@ +import { bench } from 'vitest' + +import { anvilMainnet } from '../../../test/src/anvil.js' +import { getLogs } from '../../actions/public/getLogs.js' +import { mainnet } from '../../chains/index.js' +import { createClient } from '../../clients/createClient.js' +import { http } from '../../clients/transports/http.js' +import { parseEventLogs } from './parseEventLogs.js' + +const client = createClient({ + chain: mainnet, + transport: http(), +}) + +const abi = [ + { + name: 'Transfer', + type: 'event', + inputs: [ + { + indexed: true, + name: 'from', + type: 'address', + }, + { + indexed: true, + name: 'to', + type: 'address', + }, + { + indexed: false, + name: 'value', + type: 'uint256', + }, + ], + }, + { + type: 'event', + name: 'Approval', + inputs: [ + { + indexed: true, + name: 'owner', + type: 'address', + }, + { + indexed: true, + name: 'spender', + type: 'address', + }, + { + indexed: false, + name: 'value', + type: 'uint256', + }, + ], + }, + { + inputs: [ + { + indexed: true, + name: 'message', + type: 'string', + }, + ], + name: 'Foo', + type: 'event', + }, +] as const + +const logs = await getLogs(client, { + fromBlock: anvilMainnet.forkBlockNumber - 5n, + toBlock: anvilMainnet.forkBlockNumber, +}) + +bench('parseEventLogs', async () => { + parseEventLogs({ + abi, + logs, + }) +}) diff --git a/src/utils/abi/parseEventLogs.ts b/src/utils/abi/parseEventLogs.ts index 8aeee331cf..95721fe917 100644 --- a/src/utils/abi/parseEventLogs.ts +++ b/src/utils/abi/parseEventLogs.ts @@ -13,11 +13,11 @@ import type { RpcLog } from '../../types/rpc.js' import { isAddressEqual } from '../address/isAddressEqual.js' import { toBytes } from '../encoding/toBytes.js' import { keccak256 } from '../hash/keccak256.js' +import { toEventSelector } from '../hash/toEventSelector.js' import { type DecodeEventLogErrorType, decodeEventLog, } from './decodeEventLog.js' -import { getAbiItem } from './getAbiItem.js' export type ParseEventLogsParameters< abi extends Abi | readonly unknown[] = Abi, @@ -116,10 +116,11 @@ export function parseEventLogs< return logs .map((log) => { try { - const abiItem = getAbiItem({ - abi: abi as Abi, - name: log.topics[0] as string, - }) as AbiEvent + const abiItem = (abi as Abi).find( + (abiItem) => + abiItem.type === 'event' && + log.topics[0] === toEventSelector(abiItem), + ) as AbiEvent if (!abiItem) return null const event = decodeEventLog({