From 7a13749f5ecb3dcc15268b8bd444b6ef0ab0a5ca Mon Sep 17 00:00:00 2001 From: Vladimir Shchukin Date: Thu, 30 Jul 2026 14:33:02 -0400 Subject: [PATCH] Reject multiple rows when the protobuf cannot represent groups --- cmd/generate-bindings/solana/README.md | 2 +- cmd/generate-bindings/solana/sourcecre.ts.tpl | 15 ++++++++-- .../testdata/data_storage_ts/DataStorage.ts | 28 ++++++++++++++----- .../solana/tsbindgen_test.go | 4 ++- 4 files changed, 37 insertions(+), 12 deletions(-) diff --git a/cmd/generate-bindings/solana/README.md b/cmd/generate-bindings/solana/README.md index 72f2ba62..2d9a5f41 100644 --- a/cmd/generate-bindings/solana/README.md +++ b/cmd/generate-bindings/solana/README.md @@ -41,7 +41,7 @@ generators emit: - pure account/event **decoders** (discriminator-checked) — there is no read/simulate capability, so these only decode bytes obtained elsewhere, - per-event **log-trigger bindings**: an `Filters` type, - `encodeSubkeys` (EQ comparers, OR across filter rows), and a typed + `encodeSubkeys` (EQ comparers; single-row input only), and a typed `logTriggerLog(filterName, filters, opts)` method whose output adapts the raw log into decoded event data (Go: `bindings.DecodedLog[T]`, TS: `SolanaDecodedLog`). `opts.cpi` targets Anchor `emit_cpi!` events. Only diff --git a/cmd/generate-bindings/solana/sourcecre.ts.tpl b/cmd/generate-bindings/solana/sourcecre.ts.tpl index 43add2a9..20630a7e 100644 --- a/cmd/generate-bindings/solana/sourcecre.ts.tpl +++ b/cmd/generate-bindings/solana/sourcecre.ts.tpl @@ -139,8 +139,9 @@ export const parseAnyEvent = (data: Uint8Array): {{range $i, $e := .Events}}{{if {{- range .Triggers}} /** - * Optional filter values for {{.Name}} log triggers. Set a field to filter on - * that value (OR across filter rows). Leave unset for wildcard. Only top-level + * Optional filter values for {{.Name}} log triggers. Set fields in one row to + * AND those predicates. Multiple rows are OR alternatives, but current trigger + * configuration supports only a single row. Leave unset for wildcard. Only top-level * scalar fields with supported subkey encodings are auto-filterable — nested * structs, vecs, arrays, bool, u128, and i128 need a manual SubkeyConfig. */ @@ -152,6 +153,9 @@ export type {{.Name}}Filters = { } export const encode{{.Name}}Subkeys = (filters: {{.Name}}Filters[]): SolanaSubkeyConfigJson[] => { + if (filters.length > 1) { + throw new Error('multiple filter rows are not supported for {{.Name}}; provide a single filter row') + } {{- range .FilterFields}} const {{.Name}}Comparers: SolanaValueComparatorJson[] = [] {{- end}} @@ -176,7 +180,12 @@ export const encode{{.Name}}Subkeys = (filters: {{.Name}}Filters[]): SolanaSubke {{- else}} export type {{.Name}}Filters = Record -export const encode{{.Name}}Subkeys = (_filters: {{.Name}}Filters[]): SolanaSubkeyConfigJson[] => [] +export const encode{{.Name}}Subkeys = (filters: {{.Name}}Filters[]): SolanaSubkeyConfigJson[] => { + if (filters.length > 1) { + throw new Error('multiple filter rows are not supported for {{.Name}}; provide a single filter row') + } + return [] +} {{- end}} {{- end}} {{- if or .Accounts .Events}} diff --git a/cmd/generate-bindings/solana/testdata/data_storage_ts/DataStorage.ts b/cmd/generate-bindings/solana/testdata/data_storage_ts/DataStorage.ts index e6338638..67ea791e 100644 --- a/cmd/generate-bindings/solana/testdata/data_storage_ts/DataStorage.ts +++ b/cmd/generate-bindings/solana/testdata/data_storage_ts/DataStorage.ts @@ -167,8 +167,9 @@ export const parseAnyEvent = (data: Uint8Array): AccessLogged | DynamicEvent | N } /** - * Optional filter values for AccessLogged log triggers. Set a field to filter on - * that value (OR across filter rows). Leave unset for wildcard. Only top-level + * Optional filter values for AccessLogged log triggers. Set fields in one row to + * AND those predicates. Multiple rows are OR alternatives, but current trigger + * configuration supports only a single row. Leave unset for wildcard. Only top-level * scalar fields with supported subkey encodings are auto-filterable — nested * structs, vecs, arrays, bool, u128, and i128 need a manual SubkeyConfig. */ @@ -178,6 +179,9 @@ export type AccessLoggedFilters = { } export const encodeAccessLoggedSubkeys = (filters: AccessLoggedFilters[]): SolanaSubkeyConfigJson[] => { + if (filters.length > 1) { + throw new Error('multiple filter rows are not supported for AccessLogged; provide a single filter row') + } const callerComparers: SolanaValueComparatorJson[] = [] const messageComparers: SolanaValueComparatorJson[] = [] for (const f of filters) { @@ -205,8 +209,9 @@ export const encodeAccessLoggedSubkeys = (filters: AccessLoggedFilters[]): Solan } /** - * Optional filter values for DynamicEvent log triggers. Set a field to filter on - * that value (OR across filter rows). Leave unset for wildcard. Only top-level + * Optional filter values for DynamicEvent log triggers. Set fields in one row to + * AND those predicates. Multiple rows are OR alternatives, but current trigger + * configuration supports only a single row. Leave unset for wildcard. Only top-level * scalar fields with supported subkey encodings are auto-filterable — nested * structs, vecs, arrays, bool, u128, and i128 need a manual SubkeyConfig. */ @@ -217,6 +222,9 @@ export type DynamicEventFilters = { } export const encodeDynamicEventSubkeys = (filters: DynamicEventFilters[]): SolanaSubkeyConfigJson[] => { + if (filters.length > 1) { + throw new Error('multiple filter rows are not supported for DynamicEvent; provide a single filter row') + } const keyComparers: SolanaValueComparatorJson[] = [] const senderComparers: SolanaValueComparatorJson[] = [] const metadataComparers: SolanaValueComparatorJson[] = [] @@ -254,14 +262,20 @@ export const encodeDynamicEventSubkeys = (filters: DynamicEventFilters[]): Solan } /** - * Optional filter values for NoFields log triggers. Set a field to filter on - * that value (OR across filter rows). Leave unset for wildcard. Only top-level + * Optional filter values for NoFields log triggers. Set fields in one row to + * AND those predicates. Multiple rows are OR alternatives, but current trigger + * configuration supports only a single row. Leave unset for wildcard. Only top-level * scalar fields with supported subkey encodings are auto-filterable — nested * structs, vecs, arrays, bool, u128, and i128 need a manual SubkeyConfig. */ export type NoFieldsFilters = Record -export const encodeNoFieldsSubkeys = (_filters: NoFieldsFilters[]): SolanaSubkeyConfigJson[] => [] +export const encodeNoFieldsSubkeys = (filters: NoFieldsFilters[]): SolanaSubkeyConfigJson[] => { + if (filters.length > 1) { + throw new Error('multiple filter rows are not supported for NoFields; provide a single filter row') + } + return [] +} export class DataStorage { readonly programId: Uint8Array diff --git a/cmd/generate-bindings/solana/tsbindgen_test.go b/cmd/generate-bindings/solana/tsbindgen_test.go index 6b1d2689..ed2420cd 100644 --- a/cmd/generate-bindings/solana/tsbindgen_test.go +++ b/cmd/generate-bindings/solana/tsbindgen_test.go @@ -158,6 +158,7 @@ func TestGenerateBindingsTS_LogTriggers(t *testing.T) { assert.Contains(t, source, "export type AccessLoggedFilters = {") assert.Contains(t, source, "caller?: Address | null") assert.Contains(t, source, "export const encodeAccessLoggedSubkeys = (filters: AccessLoggedFilters[]): SolanaSubkeyConfigJson[] =>") + assert.Contains(t, source, "multiple filter rows are not supported for AccessLogged; provide a single filter row") assert.Contains(t, source, "logTriggerAccessLoggedLog(") assert.Contains(t, source, "): Trigger> {") // Subkey paths use the Go bindings' PascalCase names. @@ -176,7 +177,8 @@ func TestGenerateBindingsTS_LogTriggers(t *testing.T) { assert.NotContains(t, source, "metadataArray?:") // An event with no filterable fields still gets a trigger with empty filters. assert.Contains(t, source, "export type NoFieldsFilters = Record") - assert.Contains(t, source, "export const encodeNoFieldsSubkeys = (_filters: NoFieldsFilters[]): SolanaSubkeyConfigJson[] => []") + assert.Contains(t, source, "export const encodeNoFieldsSubkeys = (filters: NoFieldsFilters[]): SolanaSubkeyConfigJson[] => {") + assert.Contains(t, source, "multiple filter rows are not supported for NoFields; provide a single filter row") assert.Contains(t, source, "logTriggerNoFieldsLog(") // An IDL without events must not emit trigger code or its imports.