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
2 changes: 1 addition & 1 deletion cmd/generate-bindings/solana/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<Event>Filters` type,
`encode<Event>Subkeys` (EQ comparers, OR across filter rows), and a typed
`encode<Event>Subkeys` (EQ comparers; single-row input only), and a typed
`logTrigger<Event>Log(filterName, filters, opts)` method whose output adapts
the raw log into decoded event data (Go: `bindings.DecodedLog[T]`, TS:
`SolanaDecodedLog<T>`). `opts.cpi` targets Anchor `emit_cpi!` events. Only
Expand Down
13 changes: 12 additions & 1 deletion cmd/generate-bindings/solana/anchor-go/generator/cre.go
Original file line number Diff line number Diff line change
Expand Up @@ -514,7 +514,8 @@ func creEventFiltersStruct(eventName string, filterFields []eventFilterField) Co
st := Empty()
st.Commentf("%sFilters holds optional filter values for %s log triggers.", exportedName, exportedName)
st.Line()
st.Comment("Set a field to filter on that value (OR across filter rows). Leave nil for wildcard.")
st.Comment("Set fields within one row to AND those predicates. Multiple rows are OR alternatives, but current")
st.Comment("trigger configuration supports only a single row, so encoding rejects multi-row input.")
st.Line()
st.Type().Id(exportedName + "Filters").StructFunc(func(g *Group) {
for _, field := range filterFields {
Expand All @@ -532,6 +533,16 @@ func creEncodeSubkeysForEvent(eventName string, filterFields []eventFilterField)
Params(Id("filters").Index().Id(exportedName+"Filters")).
Params(Index().Op("*").Qual(PkgSolanaCre, "SubkeyConfig"), Error()).
BlockFunc(func(block *Group) {
block.If(Len(Id("filters")).Op(">").Lit(1)).Block(
Return(
Nil(),
Qual("fmt", "Errorf").Call(
Lit("multiple filter rows are not supported for "+exportedName+"; provide a single filter row"),
),
),
)
block.Line()

for _, field := range filterFields {
block.Id(field.goName+"Comparers").Op(":=").Make(
Index().Op("*").Qual(PkgSolanaCre, "ValueComparator"), Lit(0),
Expand Down
28 changes: 21 additions & 7 deletions cmd/generate-bindings/solana/bindings_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,17 +190,33 @@ func TestLogTrigger(t *testing.T) {
testPubKey2, err := solana.NewRandomPrivateKey()
require.NoError(t, err)
caller2 := testPubKey2.PublicKey()
messageFilter := "match me"

filters := []datastorage.AccessLoggedFilters{
multiRowFilters := []datastorage.AccessLoggedFilters{
{Caller: &testPubKey},
{Caller: &caller2},
}

_, err = ds.Codec.EncodeAccessLoggedSubkeys(multiRowFilters)
require.Error(t, err)
require.Contains(t, err.Error(), "multiple filter rows are not supported")

filters := []datastorage.AccessLoggedFilters{
{Caller: &testPubKey, Message: &messageFilter},
}

subkeys, err := ds.Codec.EncodeAccessLoggedSubkeys(filters)
require.NoError(t, err)
require.Len(t, subkeys, 1)
require.Equal(t, []string{"Caller"}, subkeys[0].Path)
require.Len(t, subkeys[0].Comparers, 2)
require.Len(t, subkeys, 2)

paths := map[string]bool{}
for _, subkey := range subkeys {
require.Len(t, subkey.Path, 1)
require.Len(t, subkey.Comparers, 1)
paths[subkey.Path[0]] = true
}
require.True(t, paths["Caller"])
require.True(t, paths["Message"])

trigger, err := ds.LogTriggerAccessLoggedLog(
anyChainSelector,
Expand Down Expand Up @@ -241,9 +257,7 @@ func TestLogTrigger(t *testing.T) {
metadata := []byte{0xde, 0xad, 0xbe, 0xef}

filters := []datastorage.DynamicEventFilters{
{Key: &key},
{Sender: &sender},
{Metadata: &metadata},
{Key: &key, Sender: &sender, Metadata: &metadata},
}

subkeys, err := ds.Codec.EncodeDynamicEventSubkeys(filters)
Expand Down
15 changes: 12 additions & 3 deletions cmd/generate-bindings/solana/sourcecre.ts.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand All @@ -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}}
Expand All @@ -176,7 +180,12 @@ export const encode{{.Name}}Subkeys = (filters: {{.Name}}Filters[]): SolanaSubke
{{- else}}
export type {{.Name}}Filters = Record<string, never>

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}}
Expand Down
18 changes: 15 additions & 3 deletions cmd/generate-bindings/solana/testdata/data_storage/triggers.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand All @@ -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) {
Expand Down Expand Up @@ -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.
*/
Expand All @@ -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[] = []
Expand Down Expand Up @@ -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<string, never>

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
Expand Down
4 changes: 3 additions & 1 deletion cmd/generate-bindings/solana/tsbindgen_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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<SolanaLog, SolanaDecodedLog<AccessLogged>> {")
// Subkey paths use the Go bindings' PascalCase names.
Expand All @@ -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<string, never>")
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.
Expand Down
Loading