From c7141bb4f3217b03798120162fefb25a2bcbd2ce Mon Sep 17 00:00:00 2001 From: skypper Date: Wed, 18 Feb 2026 17:43:03 +0200 Subject: [PATCH] Add NOT operator and event parameter conditions to action triggers Add `not` field to function, eventEmitted, logEmitted, and comparable int/str filters. Replace the old unsupported `parameter` map on eventEmitted with typed `parameters` supporting string and int comparisons. Update generated Conjure structs to match. --- model/actions/const.go | 1 - model/actions/trigger_base.go | 2 + model/actions/trigger_transaction.go | 49 ++++++++++++---- model/actions/trigger_transaction_test.go | 56 +++++++++++++++++++ .../actions/yaml/trigger_transaction_not.yaml | 30 ++++++++++ .../generated/actions/structs.conjure.go | 18 +++++- 6 files changed, 141 insertions(+), 15 deletions(-) create mode 100644 model/actions/yaml/trigger_transaction_not.yaml diff --git a/model/actions/const.go b/model/actions/const.go index 7584b9d..958263e 100644 --- a/model/actions/const.go +++ b/model/actions/const.go @@ -63,7 +63,6 @@ var ( MsgSignatureAndParameterForbidden = "'parameter' can not be used with 'signature'" MsgIdOrNameRequired = "one of 'id' or 'name' is required" MsgIdAndNameForbidden = "both 'id' and 'name' is forbidden" - MsgIdAndParameterForbidden = "'parameter' can not be used with 'id'" MsgKeyOrFieldRequired = "one of 'key' or 'field' is required" MsgKeyAndFieldForbidden = "both 'key' and 'field' is forbidden" MsgKeyAndValueOrPreviousValueForbidden = "'value' or 'previousValue' can not be used with 'key'" diff --git a/model/actions/trigger_base.go b/model/actions/trigger_base.go index 3685831..21e4dc9 100644 --- a/model/actions/trigger_base.go +++ b/model/actions/trigger_base.go @@ -52,6 +52,7 @@ type IntValue struct { EQ *int `yaml:"eq" json:"eq"` GT *int `yaml:"gt" json:"gt"` LT *int `yaml:"lt" json:"lt"` + Not bool `yaml:"not" json:"not,omitempty"` } func (v IntValue) ToRequest() actions.ComparableInt { @@ -61,6 +62,7 @@ func (v IntValue) ToRequest() actions.ComparableInt { Eq: v.EQ, Gt: v.GT, Lt: v.LT, + Not: v.Not, } } diff --git a/model/actions/trigger_transaction.go b/model/actions/trigger_transaction.go index 111fe42..fd3c3c2 100644 --- a/model/actions/trigger_transaction.go +++ b/model/actions/trigger_transaction.go @@ -155,6 +155,7 @@ type FunctionValue struct { Name *string `yaml:"name" json:"name"` // Optional, only with Name Parameter *MapValue `yaml:"parameter" json:"parameter"` + Not bool `yaml:"not" json:"not,omitempty"` } func (f *FunctionValue) ToRequest() actions.FunctionFilter { @@ -162,6 +163,7 @@ func (f *FunctionValue) ToRequest() actions.FunctionFilter { return actions.FunctionFilter{ Contract: f.Contract.ToRequest(), Name: f.Name, + Not: f.Not, } } @@ -233,22 +235,46 @@ func (f *FunctionField) UnmarshalJSON(bytes []byte) error { return errors.New("Failed to unmarshal 'function' field") } +type ParameterCondValue struct { + Name string `yaml:"name" json:"name"` + String *string `yaml:"string" json:"string,omitempty"` + Int *IntValue `yaml:"int" json:"int,omitempty"` +} + +func (p *ParameterCondValue) ToRequest() actions.ParameterCondition { + pc := actions.ParameterCondition{ + Name: p.Name, + } + if p.String != nil { + pc.StringCmp = &actions.ComparableStr{Exact: p.String} + } + if p.Int != nil { + cmp := p.Int.ToRequest() + pc.IntCmp = &cmp + } + return pc +} + type EventEmittedValue struct { Contract *ContractValue `yaml:"contract" json:"contract"` // Exactly one of Id *string `yaml:"id" json:"id"` Name *string `yaml:"name" json:"name"` - // Optional, only with Name - Parameter *MapValue `yaml:"parameter" json:"parameter"` + Parameters []ParameterCondValue `yaml:"parameters" json:"parameters,omitempty"` + Not bool `yaml:"not" json:"not,omitempty"` } func (r *EventEmittedValue) ToRequest() actions.EventEmittedFilter { - // TODO(marko): Set parameter here when supported - return actions.EventEmittedFilter{ + f := actions.EventEmittedFilter{ Contract: r.Contract.ToRequest(), Id: r.Id, Name: r.Name, + Not: r.Not, } + for _, p := range r.Parameters { + f.Parameters = append(f.Parameters, p.ToRequest()) + } + return f } func (r *EventEmittedValue) Validate(ctx ValidatorContext) (response ValidateResponse) { @@ -270,13 +296,10 @@ func (r *EventEmittedValue) Validate(ctx ValidatorContext) (response ValidateRes if r.Id != nil && r.Name != nil { response.Error(ctx, MsgIdAndNameForbidden) } - if r.Id != nil && r.Parameter != nil { - response.Error(ctx, MsgIdAndParameterForbidden) - } - - // TODO(marko): Support parameter for event emitted - if r.Parameter != nil { - response.Error(ctx, "Parameter not yet supported in event emitted filter") + for i, p := range r.Parameters { + if strings.TrimSpace(p.Name) == "" { + response.Error(ctx.With("parameters").With(strconv.Itoa(i)), "Parameter condition name is required") + } } return response @@ -326,6 +349,7 @@ type LogEmittedValue struct { StartsWith []Hex64 `yaml:"startsWith" json:"startsWith"` Contract *ContractValue `yaml:"contract" json:"contract"` MatchAny bool `yaml:"matchAny" json:"matchAny,omitempty"` + Not bool `yaml:"not" json:"not,omitempty"` } func (l *LogEmittedValue) Validate(ctx ValidatorContext) (response ValidateResponse) { @@ -359,6 +383,9 @@ func (l *LogEmittedValue) ToRequest() actions.LogEmittedFilter { if l.MatchAny { lef.MatchAny = true } + if l.Not { + lef.Not = true + } return lef } diff --git a/model/actions/trigger_transaction_test.go b/model/actions/trigger_transaction_test.go index bbb1e07..53cf385 100644 --- a/model/actions/trigger_transaction_test.go +++ b/model/actions/trigger_transaction_test.go @@ -7,3 +7,59 @@ import ( func TestFull(t *testing.T) { _ = MustReadTriggerAndValidate("trigger_transaction_full") } + +func TestTransactionNot(t *testing.T) { + trigger := MustReadTriggerAndValidate("trigger_transaction_not") + + tx := trigger.Transaction + filter := tx.Filters[0] + req := filter.ToRequest() + + // Function not + if len(req.Function) != 1 { + t.Fatalf("expected 1 function filter, got %d", len(req.Function)) + } + if !req.Function[0].Not { + t.Error("expected function filter Not to be true") + } + + // EventEmitted not + parameters + if len(req.EventEmitted) != 1 { + t.Fatalf("expected 1 eventEmitted filter, got %d", len(req.EventEmitted)) + } + ee := req.EventEmitted[0] + if !ee.Not { + t.Error("expected eventEmitted filter Not to be true") + } + if len(ee.Parameters) != 2 { + t.Fatalf("expected 2 parameters, got %d", len(ee.Parameters)) + } + if ee.Parameters[0].Name != "from" { + t.Errorf("expected parameter name 'from', got %q", ee.Parameters[0].Name) + } + if ee.Parameters[0].StringCmp == nil || *ee.Parameters[0].StringCmp.Exact != "0x0000000000000000000000000000000000000000" { + t.Error("expected parameter 'from' to have string exact match") + } + if ee.Parameters[1].Name != "value" { + t.Errorf("expected parameter name 'value', got %q", ee.Parameters[1].Name) + } + if ee.Parameters[1].IntCmp == nil || ee.Parameters[1].IntCmp.Gte == nil || *ee.Parameters[1].IntCmp.Gte != 1000 { + t.Error("expected parameter 'value' to have int gte=1000") + } + + // LogEmitted not + if len(req.LogEmmitted) != 1 { + t.Fatalf("expected 1 logEmitted filter, got %d", len(req.LogEmmitted)) + } + if !req.LogEmmitted[0].Not { + t.Error("expected logEmitted filter Not to be true") + } + + // Value not + if len(req.Value) != 1 { + t.Fatalf("expected 1 value filter, got %d", len(req.Value)) + } + if !req.Value[0].Not { + t.Error("expected value filter Not to be true") + } +} diff --git a/model/actions/yaml/trigger_transaction_not.yaml b/model/actions/yaml/trigger_transaction_not.yaml new file mode 100644 index 0000000..96f9d09 --- /dev/null +++ b/model/actions/yaml/trigger_transaction_not.yaml @@ -0,0 +1,30 @@ +type: transaction +transaction: + status: + - mined + filters: + - network: 1 + from: 0xf63c48626f874bf5604D3Ba9f4A85d5cE58f8019 + function: + contract: + address: 0x13253c152f4D724D15D7B064DE106A739551dA5F + name: myFunction + not: true + eventEmitted: + contract: + address: 0xFc4c08972fa997C447982D634b0B48C554d92CEe + name: Transfer + not: true + parameters: + - name: from + string: "0x0000000000000000000000000000000000000000" + - name: value + int: + gte: 1000 + logEmitted: + startsWith: + - 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef + not: true + value: + eq: 100 + not: true diff --git a/rest/payloads/generated/actions/structs.conjure.go b/rest/payloads/generated/actions/structs.conjure.go index d0c6036..4c4351f 100644 --- a/rest/payloads/generated/actions/structs.conjure.go +++ b/rest/payloads/generated/actions/structs.conjure.go @@ -535,6 +535,7 @@ type ComparableInt struct { Eq *int `json:"eq"` Gt *int `json:"gt"` Lt *int `json:"lt"` + Not bool `json:"not,omitempty"` } func (o ComparableInt) MarshalYAML() (interface{}, error) { @@ -576,6 +577,7 @@ func (o *ComparableMap) UnmarshalYAML(unmarshal func(interface{}) error) error { type ComparableStr struct { Exact *string `json:"exact"` + Not bool `json:"not,omitempty"` } func (o ComparableStr) MarshalYAML() (interface{}, error) { @@ -698,9 +700,17 @@ func (o *EthBalanceFilter) UnmarshalYAML(unmarshal func(interface{}) error) erro } type EventEmittedFilter struct { - Contract ContractReference `json:"contract"` - Id *string `json:"id"` - Name *string `json:"name"` + Contract ContractReference `json:"contract"` + Id *string `json:"id"` + Name *string `json:"name"` + Not bool `json:"not,omitempty"` + Parameters []ParameterCondition `json:"parameters,omitempty"` +} + +type ParameterCondition struct { + Name string `json:"name"` + StringCmp *ComparableStr `json:"string,omitempty"` + IntCmp *ComparableInt `json:"int,omitempty"` } func (o EventEmittedFilter) MarshalYAML() (interface{}, error) { @@ -856,6 +866,7 @@ func (o *Filter) UnmarshalYAML(unmarshal func(interface{}) error) error { type FunctionFilter struct { Contract ContractReference `json:"contract"` Name *string `json:"name"` + Not bool `json:"not,omitempty"` } func (o FunctionFilter) MarshalYAML() (interface{}, error) { @@ -878,6 +889,7 @@ type LogEmittedFilter struct { TopicsStartsWith []string `json:"topicsStartsWith"` Contract *ContractReference `json:"contract"` MatchAny bool `json:"matchAny,omitempty"` + Not bool `json:"not,omitempty"` } func (o LogEmittedFilter) MarshalJSON() ([]byte, error) {