Skip to content

fix: isolate signatures array across QueryBuilder instances - #11

Open
seungjulee wants to merge 1 commit into
wevm:mainfrom
seungjulee:fix/shared-signatures-mutation
Open

seungjulee wants to merge 1 commit into
wevm:mainfrom
seungjulee:fix/shared-signatures-mutation

Conversation

@seungjulee

@seungjulee seungjulee commented Mar 13, 2026

Copy link
Copy Markdown

Problem

QueryBuilder.from() stores signatures in a shared mutable array. Each withSignatures() call pushes to the same array, so signatures accumulate across all derived instances:

const qb = QueryBuilder.from(is)

const a = qb.withSignatures(['event Transfer(address indexed from, address indexed to, uint256 tokens)'])
// signatures = ['event Transfer(... uint256 tokens)']

const b = qb.withSignatures(['event Transfer(address indexed from, address indexed to, uint256 amount)'])
// signatures = ['event Transfer(... uint256 tokens)', 'event Transfer(... uint256 amount)']
// ^^^ instance `a` is now also contaminated with the second signature

This breaks queries for events that share the same event selector but differ in parameter names (e.g. ERC-20 Transfer with uint256 tokens vs uint256 amount). The Driver receives the accumulated list, so the wrong signature may be used for column name resolution, leading to errors like column "amount" does not exist.

Fix

Create an immutable snapshot of the signatures array for each inner() call. Each QueryBuilder instance now gets its own isolated copy of signatures:

- const signatures: string[] = []
+ let signatures: string[] = []

  function inner(o = {}) {
    cursor ??= o.cursor
-   signatures.push(...(o.signatures ?? []))
+   signatures = [...signatures, ...(o.signatures ?? [])]
+
+   const sigs = [...signatures]

    const kysely = new Kysely({
      dialect: {
-       createDriver: () => new Driver({ ...options, cursor, signatures }),
+       createDriver: () => new Driver({ ...options, cursor, signatures: sigs }),
        // ...
      },
    })

Reproduction

  1. Create a QueryBuilder via from()
  2. Call withSignatures() with signature A, use the returned builder for a query
  3. Call withSignatures() with signature B (same event name, different param names)
  4. The query from step 2 now has both signatures — the backend may resolve columns using the wrong one

`QueryBuilder.from()` stored signatures in a shared mutable array.
Each `withSignatures()` call pushed to the same array, so signatures
accumulated across all derived instances.

This broke queries using events with the same name but different
parameter names (e.g. two ERC-20 Transfer variants with `uint256 tokens`
vs `uint256 amount`). The second `withSignatures()` call contaminated
the first instance's Driver, causing the wrong column names in queries.

Fix: create an immutable snapshot of the signatures array for each
`inner()` call, so each QueryBuilder instance gets its own copy.
@seungjulee
seungjulee force-pushed the fix/shared-signatures-mutation branch from c526216 to 23b246a Compare March 13, 2026 02:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant