diff --git a/engine/access/state_stream/filter/address.go b/engine/access/state_stream/filter/address.go new file mode 100644 index 00000000000..f9bf5faf9b3 --- /dev/null +++ b/engine/access/state_stream/filter/address.go @@ -0,0 +1,49 @@ +package filter + +import ( + "fmt" + + "github.com/onflow/flow-go/model/events" + "github.com/onflow/flow-go/model/flow" +) + +type AddressFilter struct { + Addresses map[string]struct{} +} + +var _ Matcher = (*AddressFilter)(nil) + +func NewAddressFilter(addresses []string, chain flow.Chain) (*AddressFilter, error) { + addressesMap := make(map[string]struct{}, len(addresses)) + for _, address := range addresses { + addr := flow.HexToAddress(address) + if err := validateAddress(addr, chain); err != nil { + return nil, err + } + // use the parsed address to make sure it will match the event address string exactly + addressesMap[addr.String()] = struct{}{} + } + return &AddressFilter{Addresses: addressesMap}, nil +} + +func (f *AddressFilter) Match(event *flow.Event) (bool, error) { + parsed, err := events.ParseEvent(event.Type) + if err != nil { + return false, fmt.Errorf("error parsing event type: %w", err) + } + + if parsed.Type != events.AccountEventType { + return false, nil + } + + _, ok := f.Addresses[parsed.Address] + return ok, nil +} + +// validateAddress ensures that the address is valid for the given chain +func validateAddress(address flow.Address, chain flow.Chain) error { + if !chain.IsValid(address) { + return fmt.Errorf("invalid address for chain: %s", address) + } + return nil +} diff --git a/engine/access/state_stream/filter/contract.go b/engine/access/state_stream/filter/contract.go new file mode 100644 index 00000000000..86fc98bb983 --- /dev/null +++ b/engine/access/state_stream/filter/contract.go @@ -0,0 +1,52 @@ +package filter + +import ( + "fmt" + "strings" + + "github.com/onflow/flow-go/model/events" + "github.com/onflow/flow-go/model/flow" +) + +type ContractFilter struct { + Contracts map[string]struct{} +} + +var _ Matcher = (*ContractFilter)(nil) + +func NewContractFilter(contracts []string) (*ContractFilter, error) { + contractsMap := make(map[string]struct{}, len(contracts)) + for _, contract := range contracts { + if err := validateContract(contract); err != nil { + return nil, err + } + contractsMap[contract] = struct{}{} + } + return &ContractFilter{Contracts: contractsMap}, nil +} + +func (f *ContractFilter) Match(event *flow.Event) (bool, error) { + parsed, err := events.ParseEvent(event.Type) + if err != nil { + return false, fmt.Errorf("error parsing event type: %w", err) + } + + if _, ok := f.Contracts[parsed.Contract]; ok { + return true, nil + } + + return false, nil +} + +// validateContract ensures that the contract is in the correct format +func validateContract(contract string) error { + if contract == "flow" { + return nil + } + + parts := strings.Split(contract, ".") + if len(parts) != 3 || parts[0] != "A" { + return fmt.Errorf("invalid contract: %s", contract) + } + return nil +} diff --git a/engine/access/state_stream/filter/fields.go b/engine/access/state_stream/filter/fields.go new file mode 100644 index 00000000000..87f7757560f --- /dev/null +++ b/engine/access/state_stream/filter/fields.go @@ -0,0 +1,174 @@ +package filter + +import ( + "fmt" + + "github.com/onflow/cadence" + "github.com/onflow/flow-go/model/flow" +) + +type MatchOperation string + +const ( + MatchOperationEqual MatchOperation = "EQUAL" // any type + MatchOperationNotEqual MatchOperation = "NOT_EQUAL" // any type + MatchOperationGreaterThan MatchOperation = "GREATER_THAN" // any numeric type + MatchOperationLessThan MatchOperation = "LESS_THAN" // any numeric type + MatchOperationGreaterThanOrEqual MatchOperation = "GREATER_THAN_OR_EQUAL" // any numeric type + MatchOperationLessThanOrEqual MatchOperation = "LESS_THAN_OR_EQUAL" // any numeric type + + // TODO: complete implementation and testing + MatchOperationContains MatchOperation = "CONTAINS" // string, array, dictionary. allow string/value/array of values + MatchOperationDoesNotContain MatchOperation = "DOES_NOT_CONTAIN" // string, array, dictionary. allow string/value/array of values + MatchOperationIn MatchOperation = "IN" // any type. allow array of values + MatchOperationNotIn MatchOperation = "NOT_IN" // any type. allow array of values +) + +var ( + allowedMatchOperators = map[MatchOperation]struct{}{ + MatchOperationEqual: {}, + MatchOperationNotEqual: {}, + MatchOperationGreaterThan: {}, + MatchOperationLessThan: {}, + MatchOperationGreaterThanOrEqual: {}, + MatchOperationLessThanOrEqual: {}, + } +) + +// Only allow filtering fields numeric, string, array, optional + +type TypedFieldFilter struct { + FieldName string + Operation MatchOperation + Value cadence.Value + + // TODO: is this the best way to handle this? + AllowedValues []cadence.Value +} + +var _ Matcher = (*TypedFieldFilter)(nil) + +func NewTypedFieldFilter(fieldName string, operation MatchOperation, value cadence.Value, allowedValues []cadence.Value) (*TypedFieldFilter, error) { + if fieldName == "" { + return nil, fmt.Errorf("field name must not be empty") + } + if _, ok := allowedMatchOperators[operation]; !ok { + return nil, fmt.Errorf("field name must not be empty") + } + if !isAllowedFilterType(value) { + return nil, fmt.Errorf("unsupported filter by type %s", value.Type().ID()) + } + + // make sure all values have the same type + var valueType cadence.Type + for _, allowedValue := range allowedValues { + if !isAllowedFilterType(allowedValue) { + return nil, fmt.Errorf("unsupported filter by type %s", allowedValue.Type().ID()) + } + + if valueType == nil { // TODO: not sure this will work since valueType is an interface + valueType = allowedValue.Type() + } + ok, err := typesEqual(allowedValue, valueType) + if err != nil { + return nil, err + } + if !ok { + return nil, fmt.Errorf("type of values in allowedValues must match filter type %s", value.Type().ID()) + } + } + + return &TypedFieldFilter{ + FieldName: fieldName, + Operation: operation, + Value: value, + AllowedValues: allowedValues, + }, nil +} + +func (f *TypedFieldFilter) Match(event *flow.Event) (bool, error) { + fields, err := getEventFields(event) + if err != nil { + return false, fmt.Errorf("error getting event fields: %w", err) + } + + fieldValue, ok := fields[f.FieldName] + if !ok { + return false, nil + } + + ok, err = typesEqual(fieldValue, f.Value.Type()) + if err != nil { + return false, err + } + if !ok { + return false, nil + } + + switch f.Operation { + case MatchOperationEqual: + return equal(fieldValue, f.Value) + + case MatchOperationNotEqual: + equal, err := equal(fieldValue, f.Value) + if err != nil { + return false, err + } + return !equal, nil + + case MatchOperationGreaterThan: + cmp, err := cmp(fieldValue, f.Value) + if err != nil { + return false, err + } + return cmp > 0, nil + + case MatchOperationLessThan: + cmp, err := cmp(fieldValue, f.Value) + if err != nil { + return false, err + } + return cmp < 0, nil + + case MatchOperationGreaterThanOrEqual: + cmp, err := cmp(fieldValue, f.Value) + if err != nil { + return false, err + } + return cmp >= 0, nil + + case MatchOperationLessThanOrEqual: + cmp, err := cmp(fieldValue, f.Value) + if err != nil { + return false, err + } + return cmp <= 0, nil + + case MatchOperationContains: + return contains(fieldValue, f.Value) + + case MatchOperationDoesNotContain: + contains, err := contains(fieldValue, f.Value) + if err != nil { + return false, err + } + return !contains, nil + + case MatchOperationIn: + if len(f.AllowedValues) == 0 { + return false, nil + } + + return in(fieldValue, f.AllowedValues) + + case MatchOperationNotIn: + in, err := in(fieldValue, f.AllowedValues) + if err != nil { + return false, err + } + return !in, nil + + default: + return false, fmt.Errorf("unsupported filter operation: %s", f.Operation) + } +} diff --git a/engine/access/state_stream/filter/interface.go b/engine/access/state_stream/filter/interface.go new file mode 100644 index 00000000000..703d1c68d75 --- /dev/null +++ b/engine/access/state_stream/filter/interface.go @@ -0,0 +1,7 @@ +package filter + +import "github.com/onflow/flow-go/model/flow" + +type Matcher interface { + Match(event *flow.Event) (bool, error) +} diff --git a/engine/access/state_stream/filter/set.go b/engine/access/state_stream/filter/set.go new file mode 100644 index 00000000000..295842a26c9 --- /dev/null +++ b/engine/access/state_stream/filter/set.go @@ -0,0 +1,68 @@ +package filter + +import ( + "fmt" + + "github.com/onflow/flow-go/model/flow" +) + +type GroupingOperation string + +const ( + GroupingOperationAnd GroupingOperation = "AND" + GroupingOperationOr GroupingOperation = "OR" + GroupingOperationNot GroupingOperation = "NOT" +) + +type FieldFilterSet struct { + GroupingOperation GroupingOperation + Filters []Matcher +} + +var _ Matcher = (*FieldFilterSet)(nil) + +func NewFieldFilterSet(groupingOperation GroupingOperation, filters []Matcher) (*FieldFilterSet, error) { + if len(filters) == 0 { + return nil, fmt.Errorf("filter set cannot be empty") + } + + return &FieldFilterSet{ + GroupingOperation: groupingOperation, + Filters: filters, + }, nil +} + +func (f *FieldFilterSet) Match(event *flow.Event) (bool, error) { + matchCount := 0 + for i, filter := range f.Filters { + matched, err := filter.Match(event) + if err != nil { + return false, fmt.Errorf("error matching filter %d: %w", i, err) + } + + switch f.GroupingOperation { + case GroupingOperationOr: + if matched { + return true, nil + } + case GroupingOperationAnd: + if !matched { + return false, nil + } + case GroupingOperationNot: + if matched { + return false, nil + } + } + } + + switch f.GroupingOperation { + case GroupingOperationAnd: + return matchCount == len(f.Filters), nil + case GroupingOperationOr: + return matchCount > 0, nil + case GroupingOperationNot: + return matchCount == 0, nil + } + return false, nil +} diff --git a/engine/access/state_stream/filter/util.go b/engine/access/state_stream/filter/util.go new file mode 100644 index 00000000000..cd08ebd22f3 --- /dev/null +++ b/engine/access/state_stream/filter/util.go @@ -0,0 +1,248 @@ +package filter + +import ( + "bytes" + "fmt" + "math/big" + "strings" + + "github.com/onflow/cadence" + "github.com/onflow/cadence/encoding/ccf" + "github.com/onflow/flow-go/model/flow" +) + +var ErrComparisonNotSupported = fmt.Errorf("comparison not supported") + +// TODO: next steps are to setup some tests to verify the behavior is correct +// and benchmark the performance is acceptable. + +// numericBig is a cadence type that implements the Big() method. +type numericBig interface { + Big() *big.Int +} + +// typesEqual returns true if `actualValue` is of the provided `userType`. +// If the field value is an optional, `userType` must match the inner type of the optional type. +// If the field value is an array or dictionary, `userType` must match the element type of the array or dictionary. +func typesEqual(actualValue cadence.Value, userType cadence.Type) (bool, error) { + if optional, ok := actualValue.(cadence.Optional); ok { + return typesEqual(optional.Value, userType) + } + + if array, ok := actualValue.(cadence.Array); ok { + if array.ArrayType != nil { + return array.ArrayType.Element().Equal(userType), nil + } + + if len(array.Values) > 0 { + // only traverse a single level + return array.Values[0].Type().Equal(userType), nil + } + + // TODO: should this return an error? + return false, nil // cannot resolve type from array + } + + if actualValue.Type().Equal(userType) { + return true, nil + } + + return false, nil +} + +// compareBig compares two numericBig values. +func compareBig[T numericBig](a, b T) int { + return a.Big().Cmp(b.Big()) +} + +// compare compares two values of the same type. +func compare[T ~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~float32 | ~float64 | ~string](a, b T) int { + if a == b { + return 0 + } + if a < b { + return -1 + } + return 1 +} + +// cmp returns the comparison result of two values. +// If the first value is an optional, the comparison is performed on the inner value. If the inner +// value is nil, the +func cmp(a, b cadence.Value) (int, error) { + if optional, ok := a.(*cadence.Optional); ok { + if optional.Value == nil { + if b == nil { + return 0, nil + } + return -1, nil // TODO: document this behavior + } + a = optional.Value + + // TODO: can I just do this? + // return cmp(optional.Value, b) + } + + switch aValue := a.(type) { + case cadence.UInt: + return compareBig(aValue, b.(cadence.UInt)), nil + case cadence.UInt8: + return compare(aValue, b.(cadence.UInt8)), nil + case cadence.UInt16: + return compare(aValue, b.(cadence.UInt16)), nil + case cadence.UInt32: + return compare(aValue, b.(cadence.UInt32)), nil + case cadence.UInt64: + return compare(aValue, b.(cadence.UInt64)), nil + case cadence.UInt128: + return compareBig(aValue, b.(cadence.UInt128)), nil + case cadence.UInt256: + return compareBig(aValue, b.(cadence.UInt256)), nil + case cadence.Int: + return compareBig(aValue, b.(cadence.Int)), nil + case cadence.Int8: + return compare(aValue, b.(cadence.Int8)), nil + case cadence.Int16: + return compare(aValue, b.(cadence.Int16)), nil + case cadence.Int32: + return compare(aValue, b.(cadence.Int32)), nil + case cadence.Int64: + return compare(aValue, b.(cadence.Int64)), nil + case cadence.Int128: + return compareBig(aValue, b.(cadence.Int128)), nil + case cadence.Int256: + return compareBig(aValue, b.(cadence.Int256)), nil + case cadence.Word8: + return compare(aValue, b.(cadence.Word8)), nil + case cadence.Word16: + return compare(aValue, b.(cadence.Word16)), nil + case cadence.Word32: + return compare(aValue, b.(cadence.Word32)), nil + case cadence.Word64: + return compare(aValue, b.(cadence.Word64)), nil + case cadence.Word128: + return compareBig(aValue, b.(cadence.Word128)), nil + case cadence.Word256: + return compareBig(aValue, b.(cadence.Word256)), nil + case cadence.Fix64: + return compare(aValue, b.(cadence.Fix64)), nil + case cadence.Fix128: + aBig := new(big.Int).SetBytes(aValue.ToBigEndianBytes()) + bBig := new(big.Int).SetBytes(b.(cadence.Fix128).ToBigEndianBytes()) + return aBig.Cmp(bBig), nil + case cadence.UFix64: + return compare(aValue, b.(cadence.UFix64)), nil + case cadence.UFix128: + aBig := new(big.Int).SetBytes(aValue.ToBigEndianBytes()) + bBig := new(big.Int).SetBytes(b.(cadence.UFix128).ToBigEndianBytes()) + return aBig.Cmp(bBig), nil + case cadence.String: + // note: for strings, A < B + return strings.Compare(string(aValue), string(b.(cadence.String))), nil + case cadence.Character: + return strings.Compare(string(aValue), string(b.(cadence.Character))), nil + case cadence.Address: + return bytes.Compare(aValue.Bytes(), b.(cadence.Address).Bytes()), nil + case cadence.Bytes: + return bytes.Compare(aValue, b.(cadence.Bytes)), nil + default: + return 0, fmt.Errorf("%w: compare not supported for type %T", ErrComparisonNotSupported, aValue) + } +} + +// equal returns true if the two values are equal. +func equal(a, b cadence.Value) (bool, error) { + if optional, ok := a.(cadence.Optional); ok { + return equal(optional.Value, b) + } + + switch aValue := a.(type) { + case cadence.Array: + return false, fmt.Errorf("%w: equal comparison not supported for array type", ErrComparisonNotSupported) + case cadence.Bytes: + return bytes.Equal([]byte(aValue), []byte(b.(cadence.Bytes))), nil + default: + return a == b, nil + } +} + +// contains returns true if the provided `set` contains the provided `value`. +func contains(set cadence.Value, value cadence.Value) (bool, error) { + if optional, ok := set.(cadence.Optional); ok { + set = optional.Value + } + + switch v := set.(type) { + case cadence.String: + return strings.Contains(string(v), string(value.(cadence.String))), nil + + case cadence.Array: + for _, item := range v.Values { + if item == value { + return true, nil + } + } + return false, nil + + default: + return false, fmt.Errorf("contains not supported for type %T", v) + } +} + +// in returns true if `eventValue` is contained in the list `allowedValues`. +func in(eventValue cadence.Value, allowedValues []cadence.Value) (bool, error) { + if optional, ok := eventValue.(cadence.Optional); ok { + eventValue = optional.Value + } + + for _, allowedValue := range allowedValues { + ok, err := equal(eventValue, allowedValue) + if err != nil { + return false, err + } + if ok { + return true, nil + } + } + + return false, nil +} + +// getEventFields extracts field values and field names from the payload of a flow event. +// It decodes the event payload into a Cadence event, retrieves the field values and fields, and returns them. +// Parameters: +// - event: The Flow event to extract field values and field names from. +// Returns: +// - map[string]cadence.Value: A map containing name and value for each field extracted from the event payload. +// - error: An error, if any, encountered during event decoding or if the fields are empty. +func getEventFields(event *flow.Event) (map[string]cadence.Value, error) { + data, err := ccf.Decode(nil, event.Payload) + if err != nil { + return nil, err + } + + cdcEvent, ok := data.(cadence.Event) + if !ok { + return nil, err + } + + fields := cadence.FieldsMappedByName(cdcEvent) + if fields == nil { + return nil, fmt.Errorf("fields are empty") + } + return fields, nil +} + +func isAllowedFilterType(value cadence.Value) bool { + switch value.(type) { + case cadence.UInt, cadence.UInt8, cadence.UInt16, cadence.UInt32, cadence.UInt64, cadence.UInt128, cadence.UInt256, + cadence.Int, cadence.Int8, cadence.Int16, cadence.Int32, cadence.Int64, cadence.Int128, cadence.Int256, + cadence.Word8, cadence.Word16, cadence.Word32, cadence.Word64, cadence.Word128, cadence.Word256, + cadence.Fix64, cadence.Fix128, cadence.UFix64, cadence.UFix128, + cadence.String, cadence.Character, cadence.Bytes, cadence.Bool, + cadence.Address, cadence.Path: + return true + default: + return false + } +} diff --git a/engine/access/state_stream/filter/util_test.go b/engine/access/state_stream/filter/util_test.go new file mode 100644 index 00000000000..5bcf74879c9 --- /dev/null +++ b/engine/access/state_stream/filter/util_test.go @@ -0,0 +1,967 @@ +package filter + +import ( + "fmt" + "testing" + + "github.com/onflow/cadence" + cadencecommon "github.com/onflow/cadence/common" + fixedPoint "github.com/onflow/fixed-point" + "github.com/onflow/flow-go/utils/unittest/fixtures" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +var allCadenceTypes = map[cadence.Type]struct{}{ + cadence.IntType: {}, + cadence.Int8Type: {}, + cadence.Int16Type: {}, + cadence.Int32Type: {}, + cadence.Int64Type: {}, + cadence.Int128Type: {}, + cadence.Int256Type: {}, + cadence.UIntType: {}, + cadence.UInt8Type: {}, + cadence.UInt16Type: {}, + cadence.UInt32Type: {}, + cadence.UInt64Type: {}, + cadence.UInt128Type: {}, + cadence.UInt256Type: {}, + cadence.Word8Type: {}, + cadence.Word16Type: {}, + cadence.Word32Type: {}, + cadence.Word64Type: {}, + cadence.Word128Type: {}, + cadence.Word256Type: {}, + cadence.Fix64Type: {}, + cadence.Fix128Type: {}, + cadence.UFix64Type: {}, + cadence.UFix128Type: {}, + cadence.BoolType: {}, + cadence.StringType: {}, + cadence.TheBytesType: {}, + cadence.CharacterType: {}, + cadence.AddressType: {}, + + // TODO: add test for generic path type + cadence.StoragePathType: {}, + cadence.PublicPathType: {}, + + // &cadence.OptionalType{}: {}, + // &cadence.VariableSizedArrayType{}: {}, + // &cadence.ConstantSizedArrayType{}: {}, + // &cadence.DictionaryType{}: {}, +} + +func TestTypesEqual(t *testing.T) { + g := fixtures.NewGeneratorSuite() + for cadenceType := range allCadenceTypes { + value := valueFactory(t, g, cadenceType) + + // base types + t.Run(fmt.Sprintf("%T equal", value), func(t *testing.T) { + got, err := typesEqual(value, cadenceType) + require.NoError(t, err) + require.True(t, got) + }) + t.Run(fmt.Sprintf("%T not equal", value), func(t *testing.T) { + for otherType := range allCadenceTypes { + if otherType == cadenceType { + continue // skip same type + } + got, err := typesEqual(value, otherType) + require.NoError(t, err) + require.Falsef(t, got, fmt.Sprintf("%T should not be equal to %T", cadenceType, otherType)) + } + }) + + // optional + t.Run(fmt.Sprintf("%T optional equal", value), func(t *testing.T) { + got, err := typesEqual(cadence.NewOptional(value), cadenceType) + require.NoError(t, err) + require.True(t, got) + }) + t.Run(fmt.Sprintf("%T optional not equal", value), func(t *testing.T) { + for otherType := range allCadenceTypes { + if otherType == cadenceType { + continue // skip same type + } + got, err := typesEqual(cadence.NewOptional(value), otherType) + require.NoError(t, err) + require.Falsef(t, got, fmt.Sprintf("%T should not be equal to %T", cadenceType, otherType)) + } + }) + + // array + t.Run(fmt.Sprintf("%T array equal with ArrayType", value), func(t *testing.T) { + array := cadence.NewArray([]cadence.Value{value}) + array.ArrayType = cadence.NewVariableSizedArrayType(value.Type()) + + got, err := typesEqual(array, cadenceType) + require.NoError(t, err) + require.True(t, got) + }) + t.Run(fmt.Sprintf("%T array equal without ArrayType", value), func(t *testing.T) { + array := cadence.NewArray([]cadence.Value{value}) + got, err := typesEqual(array, cadenceType) + require.NoError(t, err) + require.True(t, got) + }) + t.Run(fmt.Sprintf("%T array not equal with ArrayType", value), func(t *testing.T) { + array := cadence.NewArray([]cadence.Value{value}) + array.ArrayType = cadence.NewVariableSizedArrayType(value.Type()) + for otherType := range allCadenceTypes { + if otherType == cadenceType { + continue // skip same type + } + got, err := typesEqual(array, otherType) + require.NoError(t, err) + require.Falsef(t, got, fmt.Sprintf("%T should not be equal to %T", cadenceType, otherType)) + } + }) + t.Run(fmt.Sprintf("%T array not equal without ArrayType", value), func(t *testing.T) { + array := cadence.NewArray([]cadence.Value{value}) + for otherType := range allCadenceTypes { + if otherType == cadenceType { + continue // skip same type + } + got, err := typesEqual(array, otherType) + require.NoError(t, err) + require.Falsef(t, got, fmt.Sprintf("%T should not be equal to %T", cadenceType, otherType)) + } + }) + } +} + +func TestEqual(t *testing.T) { + g := fixtures.NewGeneratorSuite() + for cadenceType := range allCadenceTypes { + value1 := valueFactory(t, g, cadenceType) + value2 := valueFactory(t, g, cadenceType) + + t.Run(fmt.Sprintf("%T equal", value1), func(t *testing.T) { + got, err := equal(value1, value1) + require.NoError(t, err) + require.True(t, got) + }) + + t.Run(fmt.Sprintf("%T not equal", value1), func(t *testing.T) { + got, err := equal(value1, value2) + require.NoError(t, err) + require.False(t, got) + }) + + t.Run(fmt.Sprintf("%T optional equal", value1), func(t *testing.T) { + got, err := equal(cadence.NewOptional(value1), value1) + require.NoError(t, err) + require.True(t, got) + }) + + t.Run(fmt.Sprintf("%T optional not equal", value1), func(t *testing.T) { + got, err := equal(cadence.NewOptional(value1), value2) + require.NoError(t, err) + require.False(t, got) + }) + + t.Run(fmt.Sprintf("%T array equal", value1), func(t *testing.T) { + got, err := equal(cadence.NewArray([]cadence.Value{value1}), cadence.NewArray([]cadence.Value{value1})) + require.ErrorIs(t, err, ErrComparisonNotSupported) + require.False(t, got) + }) + } +} + +func TestCmp(t *testing.T) { + // g := fixtures.NewGeneratorSuite() + + t.Run("Int", func(t *testing.T) { + actual, err := cmp( + cadence.NewInt(1), + cadence.NewInt(1), + ) + require.NoError(t, err) + assert.Equal(t, 0, actual) + + actual, err = cmp( + cadence.NewInt(1), + cadence.NewInt(2), + ) + require.NoError(t, err) + assert.Equal(t, -1, actual) + + actual, err = cmp( + cadence.NewInt(1), + cadence.NewInt(-1), + ) + require.NoError(t, err) + assert.Equal(t, 1, actual) + }) + + t.Run("Int8", func(t *testing.T) { + actual, err := cmp( + cadence.NewInt8(1), + cadence.NewInt8(1), + ) + require.NoError(t, err) + assert.Equal(t, 0, actual) + + actual, err = cmp( + cadence.NewInt8(1), + cadence.NewInt8(2), + ) + require.NoError(t, err) + assert.Equal(t, -1, actual) + + actual, err = cmp( + cadence.NewInt8(1), + cadence.NewInt8(-1), + ) + require.NoError(t, err) + assert.Equal(t, 1, actual) + }) + + t.Run("Int16", func(t *testing.T) { + actual, err := cmp( + cadence.NewInt16(1), + cadence.NewInt16(1), + ) + require.NoError(t, err) + assert.Equal(t, 0, actual) + + actual, err = cmp( + cadence.NewInt16(1), + cadence.NewInt16(2), + ) + require.NoError(t, err) + assert.Equal(t, -1, actual) + + actual, err = cmp( + cadence.NewInt16(1), + cadence.NewInt16(-1), + ) + require.NoError(t, err) + assert.Equal(t, 1, actual) + }) + + t.Run("Int32", func(t *testing.T) { + actual, err := cmp( + cadence.NewInt32(1), + cadence.NewInt32(1), + ) + require.NoError(t, err) + assert.Equal(t, 0, actual) + + actual, err = cmp( + cadence.NewInt32(1), + cadence.NewInt32(2), + ) + require.NoError(t, err) + assert.Equal(t, -1, actual) + + actual, err = cmp( + cadence.NewInt32(1), + cadence.NewInt32(-1), + ) + require.NoError(t, err) + assert.Equal(t, 1, actual) + }) + + t.Run("Int64", func(t *testing.T) { + actual, err := cmp( + cadence.NewInt64(1), + cadence.NewInt64(1), + ) + require.NoError(t, err) + assert.Equal(t, 0, actual) + + actual, err = cmp( + cadence.NewInt64(1), + cadence.NewInt64(2), + ) + require.NoError(t, err) + assert.Equal(t, -1, actual) + + actual, err = cmp( + cadence.NewInt64(1), + cadence.NewInt64(-1), + ) + require.NoError(t, err) + assert.Equal(t, 1, actual) + }) + + t.Run("Int128", func(t *testing.T) { + actual, err := cmp( + cadence.NewInt128(1), + cadence.NewInt128(1), + ) + require.NoError(t, err) + assert.Equal(t, 0, actual) + + actual, err = cmp( + cadence.NewInt128(1), + cadence.NewInt128(2), + ) + require.NoError(t, err) + assert.Equal(t, -1, actual) + + actual, err = cmp( + cadence.NewInt128(1), + cadence.NewInt128(-1), + ) + require.NoError(t, err) + assert.Equal(t, 1, actual) + }) + + t.Run("Int256", func(t *testing.T) { + actual, err := cmp( + cadence.NewInt256(1), + cadence.NewInt256(1), + ) + require.NoError(t, err) + assert.Equal(t, 0, actual) + + actual, err = cmp( + cadence.NewInt256(1), + cadence.NewInt256(2), + ) + require.NoError(t, err) + assert.Equal(t, -1, actual) + + actual, err = cmp( + cadence.NewInt256(1), + cadence.NewInt256(-1), + ) + require.NoError(t, err) + assert.Equal(t, 1, actual) + }) + + t.Run("UInt", func(t *testing.T) { + actual, err := cmp( + cadence.NewUInt(1), + cadence.NewUInt(1), + ) + require.NoError(t, err) + assert.Equal(t, 0, actual) + + actual, err = cmp( + cadence.NewUInt(1), + cadence.NewUInt(2), + ) + require.NoError(t, err) + assert.Equal(t, -1, actual) + + actual, err = cmp( + cadence.NewUInt(2), + cadence.NewUInt(1), + ) + require.NoError(t, err) + assert.Equal(t, 1, actual) + }) + + t.Run("UInt8", func(t *testing.T) { + actual, err := cmp( + cadence.NewUInt8(1), + cadence.NewUInt8(1), + ) + require.NoError(t, err) + assert.Equal(t, 0, actual) + + actual, err = cmp( + cadence.NewUInt8(1), + cadence.NewUInt8(2), + ) + require.NoError(t, err) + assert.Equal(t, -1, actual) + + actual, err = cmp( + cadence.NewUInt8(2), + cadence.NewUInt8(1), + ) + require.NoError(t, err) + assert.Equal(t, 1, actual) + }) + + t.Run("UInt16", func(t *testing.T) { + actual, err := cmp( + cadence.NewUInt16(1), + cadence.NewUInt16(1), + ) + require.NoError(t, err) + assert.Equal(t, 0, actual) + + actual, err = cmp( + cadence.NewUInt16(1), + cadence.NewUInt16(2), + ) + require.NoError(t, err) + assert.Equal(t, -1, actual) + + actual, err = cmp( + cadence.NewUInt16(2), + cadence.NewUInt16(1), + ) + require.NoError(t, err) + assert.Equal(t, 1, actual) + }) + + t.Run("UInt32", func(t *testing.T) { + actual, err := cmp( + cadence.NewUInt32(1), + cadence.NewUInt32(1), + ) + require.NoError(t, err) + assert.Equal(t, 0, actual) + + actual, err = cmp( + cadence.NewUInt32(1), + cadence.NewUInt32(2), + ) + require.NoError(t, err) + assert.Equal(t, -1, actual) + + actual, err = cmp( + cadence.NewUInt32(2), + cadence.NewUInt32(1), + ) + require.NoError(t, err) + assert.Equal(t, 1, actual) + }) + + t.Run("UInt64", func(t *testing.T) { + actual, err := cmp( + cadence.NewUInt64(1), + cadence.NewUInt64(1), + ) + require.NoError(t, err) + assert.Equal(t, 0, actual) + + actual, err = cmp( + cadence.NewUInt64(1), + cadence.NewUInt64(2), + ) + require.NoError(t, err) + assert.Equal(t, -1, actual) + + actual, err = cmp( + cadence.NewUInt64(2), + cadence.NewUInt64(1), + ) + require.NoError(t, err) + assert.Equal(t, 1, actual) + }) + + t.Run("UInt128", func(t *testing.T) { + actual, err := cmp( + cadence.NewUInt128(1), + cadence.NewUInt128(1), + ) + require.NoError(t, err) + assert.Equal(t, 0, actual) + + actual, err = cmp( + cadence.NewUInt128(1), + cadence.NewUInt128(2), + ) + require.NoError(t, err) + assert.Equal(t, -1, actual) + + actual, err = cmp( + cadence.NewUInt128(2), + cadence.NewUInt128(1), + ) + require.NoError(t, err) + assert.Equal(t, 1, actual) + }) + + t.Run("UInt256", func(t *testing.T) { + actual, err := cmp( + cadence.NewUInt256(1), + cadence.NewUInt256(1), + ) + require.NoError(t, err) + assert.Equal(t, 0, actual) + + actual, err = cmp( + cadence.NewUInt256(1), + cadence.NewUInt256(2), + ) + require.NoError(t, err) + assert.Equal(t, -1, actual) + + actual, err = cmp( + cadence.NewUInt256(2), + cadence.NewUInt256(1), + ) + require.NoError(t, err) + assert.Equal(t, 1, actual) + }) + + t.Run("Word8", func(t *testing.T) { + actual, err := cmp( + cadence.NewWord8(1), + cadence.NewWord8(1), + ) + require.NoError(t, err) + assert.Equal(t, 0, actual) + + actual, err = cmp( + cadence.NewWord8(1), + cadence.NewWord8(2), + ) + require.NoError(t, err) + assert.Equal(t, -1, actual) + + actual, err = cmp( + cadence.NewWord8(2), + cadence.NewWord8(1), + ) + require.NoError(t, err) + assert.Equal(t, 1, actual) + }) + + t.Run("Word16", func(t *testing.T) { + actual, err := cmp( + cadence.NewWord16(1), + cadence.NewWord16(1), + ) + require.NoError(t, err) + assert.Equal(t, 0, actual) + + actual, err = cmp( + cadence.NewWord16(1), + cadence.NewWord16(2), + ) + require.NoError(t, err) + assert.Equal(t, -1, actual) + + actual, err = cmp( + cadence.NewWord16(2), + cadence.NewWord16(1), + ) + require.NoError(t, err) + assert.Equal(t, 1, actual) + }) + + t.Run("Word32", func(t *testing.T) { + actual, err := cmp( + cadence.NewWord32(1), + cadence.NewWord32(1), + ) + require.NoError(t, err) + assert.Equal(t, 0, actual) + + actual, err = cmp( + cadence.NewWord32(1), + cadence.NewWord32(2), + ) + require.NoError(t, err) + assert.Equal(t, -1, actual) + + actual, err = cmp( + cadence.NewWord32(2), + cadence.NewWord32(1), + ) + require.NoError(t, err) + assert.Equal(t, 1, actual) + }) + + t.Run("Word64", func(t *testing.T) { + actual, err := cmp( + cadence.NewWord64(1), + cadence.NewWord64(1), + ) + require.NoError(t, err) + assert.Equal(t, 0, actual) + + actual, err = cmp( + cadence.NewWord64(1), + cadence.NewWord64(2), + ) + require.NoError(t, err) + assert.Equal(t, -1, actual) + + actual, err = cmp( + cadence.NewWord64(2), + cadence.NewWord64(1), + ) + require.NoError(t, err) + assert.Equal(t, 1, actual) + }) + + t.Run("Word128", func(t *testing.T) { + actual, err := cmp( + cadence.NewWord128(1), + cadence.NewWord128(1), + ) + require.NoError(t, err) + assert.Equal(t, 0, actual) + + actual, err = cmp( + cadence.NewWord128(1), + cadence.NewWord128(2), + ) + require.NoError(t, err) + assert.Equal(t, -1, actual) + + actual, err = cmp( + cadence.NewWord128(2), + cadence.NewWord128(1), + ) + require.NoError(t, err) + assert.Equal(t, 1, actual) + }) + + t.Run("Word256", func(t *testing.T) { + actual, err := cmp( + cadence.NewWord256(1), + cadence.NewWord256(1), + ) + require.NoError(t, err) + assert.Equal(t, 0, actual) + + actual, err = cmp( + cadence.NewWord256(1), + cadence.NewWord256(2), + ) + require.NoError(t, err) + assert.Equal(t, -1, actual) + + actual, err = cmp( + cadence.NewWord256(2), + cadence.NewWord256(1), + ) + require.NoError(t, err) + assert.Equal(t, 1, actual) + }) + + t.Run("Fix64", func(t *testing.T) { + mustFix64 := func(v string) cadence.Fix64 { + out, err := cadence.NewFix64(v) + require.NoError(t, err) + return out + } + + actual, err := cmp( + mustFix64("1.0"), + mustFix64("1.0"), + ) + require.NoError(t, err) + assert.Equal(t, 0, actual) + + actual, err = cmp( + mustFix64("1.0"), + mustFix64("2.0"), + ) + require.NoError(t, err) + assert.Equal(t, -1, actual) + + actual, err = cmp( + mustFix64("2.0"), + mustFix64("1.0"), + ) + require.NoError(t, err) + assert.Equal(t, 1, actual) + }) + + t.Run("Fix128", func(t *testing.T) { + mustFix128 := func(v uint64) cadence.Fix128 { + out, err := cadence.NewFix128(fixedPoint.NewFix128(0, v)) + require.NoError(t, err) + return out + } + + actual, err := cmp( + mustFix128(1), + mustFix128(1), + ) + require.NoError(t, err) + assert.Equal(t, 0, actual) + + actual, err = cmp( + mustFix128(1), + mustFix128(2), + ) + require.NoError(t, err) + assert.Equal(t, -1, actual) + + actual, err = cmp( + mustFix128(2), + mustFix128(1), + ) + require.NoError(t, err) + assert.Equal(t, 1, actual) + }) + + t.Run("UFix64", func(t *testing.T) { + mustUFix64 := func(v string) cadence.UFix64 { + out, err := cadence.NewUFix64(v) + require.NoError(t, err) + return out + } + + actual, err := cmp( + mustUFix64("1.0"), + mustUFix64("1.0"), + ) + require.NoError(t, err) + assert.Equal(t, 0, actual) + + actual, err = cmp( + mustUFix64("1.0"), + mustUFix64("2.0"), + ) + require.NoError(t, err) + assert.Equal(t, -1, actual) + + actual, err = cmp( + mustUFix64("2.0"), + mustUFix64("1.0"), + ) + require.NoError(t, err) + assert.Equal(t, 1, actual) + }) + + t.Run("UFix128", func(t *testing.T) { + mustUFix128 := func(v uint64) cadence.UFix128 { + out, err := cadence.NewUFix128(fixedPoint.NewUFix128(0, v)) + require.NoError(t, err) + return out + } + + actual, err := cmp( + mustUFix128(1), + mustUFix128(1), + ) + require.NoError(t, err) + assert.Equal(t, 0, actual) + + actual, err = cmp( + mustUFix128(1), + mustUFix128(2), + ) + require.NoError(t, err) + assert.Equal(t, -1, actual) + + actual, err = cmp( + mustUFix128(2), + mustUFix128(1), + ) + require.NoError(t, err) + assert.Equal(t, 1, actual) + }) + + t.Run("String", func(t *testing.T) { + mustString := func(v string) cadence.String { + out, err := cadence.NewString(v) + require.NoError(t, err) + return out + } + + actual, err := cmp( + mustString("abc"), + mustString("abc"), + ) + require.NoError(t, err) + assert.Equal(t, 0, actual) + + actual, err = cmp( + mustString("abc"), + mustString("xyz"), + ) + require.NoError(t, err) + assert.Equal(t, -1, actual) + + actual, err = cmp( + mustString("xyz"), + mustString("abc"), + ) + require.NoError(t, err) + assert.Equal(t, 1, actual) + + // note: lowercase are "larger" due to UTF8 encoding + actual, err = cmp( + mustString("abc"), + mustString("ABC"), + ) + require.NoError(t, err) + assert.Equal(t, 1, actual) + }) + + t.Run("Character", func(t *testing.T) { + mustCharacter := func(v string) cadence.Character { + out, err := cadence.NewCharacter(v) + require.NoError(t, err) + return out + } + + actual, err := cmp( + mustCharacter("a"), + mustCharacter("a"), + ) + require.NoError(t, err) + assert.Equal(t, 0, actual) + + actual, err = cmp( + mustCharacter("a"), + mustCharacter("b"), + ) + require.NoError(t, err) + assert.Equal(t, -1, actual) + + actual, err = cmp( + mustCharacter("z"), + mustCharacter("a"), + ) + require.NoError(t, err) + assert.Equal(t, 1, actual) + + // note: lowercase are "larger" due to UTF8 encoding + actual, err = cmp( + mustCharacter("a"), + mustCharacter("A"), + ) + require.NoError(t, err) + assert.Equal(t, 1, actual) + }) + + t.Run("Address", func(t *testing.T) { + mustAddress := func(v string) cadence.Address { + addr, err := cadencecommon.HexToAddress(v) + require.NoError(t, err) + return cadence.NewAddress(addr) + } + + actual, err := cmp( + mustAddress("0x1"), + mustAddress("0x1"), + ) + require.NoError(t, err) + assert.Equal(t, 0, actual) + + actual, err = cmp( + mustAddress("0x1"), + mustAddress("0x2"), + ) + require.NoError(t, err) + assert.Equal(t, -1, actual) + + actual, err = cmp( + mustAddress("0x2"), + mustAddress("0x1"), + ) + require.NoError(t, err) + assert.Equal(t, 1, actual) + }) + + t.Run("Bytes", func(t *testing.T) { + actual, err := cmp( + cadence.NewBytes([]byte{0x1}), + cadence.NewBytes([]byte{0x1}), + ) + require.NoError(t, err) + assert.Equal(t, 0, actual) + + actual, err = cmp( + cadence.NewBytes([]byte{0x1}), + cadence.NewBytes([]byte{0x2}), + ) + require.NoError(t, err) + assert.Equal(t, -1, actual) + + actual, err = cmp( + cadence.NewBytes([]byte{0x2}), + cadence.NewBytes([]byte{0x1}), + ) + require.NoError(t, err) + assert.Equal(t, 1, actual) + }) +} + +func valueFactory(t *testing.T, g *fixtures.GeneratorSuite, cadenceType cadence.Type) cadence.Value { + switch cadenceType { + case cadence.IntType: + return cadence.NewInt(g.Random().Int()) + case cadence.Int8Type: + return cadence.NewInt8(int8(g.Random().Int())) + case cadence.Int16Type: + return cadence.NewInt16(int16(g.Random().Int())) + case cadence.Int32Type: + return cadence.NewInt32(int32(g.Random().Int())) + case cadence.Int64Type: + return cadence.NewInt64(int64(g.Random().Int())) + case cadence.Int128Type: + return cadence.NewInt128(g.Random().Int()) + case cadence.Int256Type: + return cadence.NewInt256(g.Random().Int()) + case cadence.UIntType: + return cadence.NewUInt(uint(g.Random().Uint64())) + case cadence.UInt8Type: + return cadence.NewUInt8(uint8(g.Random().Uint64())) + case cadence.UInt16Type: + return cadence.NewUInt16(uint16(g.Random().Uint64())) + case cadence.UInt32Type: + return cadence.NewUInt32(uint32(g.Random().Uint64())) + case cadence.UInt64Type: + return cadence.NewUInt64(g.Random().Uint64()) + case cadence.UInt128Type: + return cadence.NewUInt128(uint(g.Random().Uint64())) + case cadence.UInt256Type: + return cadence.NewUInt256(uint(g.Random().Uint64())) + case cadence.Word8Type: + return cadence.NewWord8(uint8(g.Random().Uint64())) + case cadence.Word16Type: + return cadence.NewWord16(uint16(g.Random().Uint64())) + case cadence.Word32Type: + return cadence.NewWord32(uint32(g.Random().Uint64())) + case cadence.Word64Type: + return cadence.NewWord64(g.Random().Uint64()) + case cadence.Word128Type: + return cadence.NewWord128(uint(g.Random().Uint64())) + case cadence.Word256Type: + return cadence.NewWord256(uint(g.Random().Uint64())) + case cadence.Fix64Type: + v, err := cadence.NewFix64(fmt.Sprintf("%.2f", g.Random().Float64())) + require.NoError(t, err) + return v + case cadence.Fix128Type: + v, err := cadence.NewFix128(fixedPoint.NewFix128(0, g.Random().Uint64())) + require.NoError(t, err) + return v + case cadence.UFix64Type: + v, err := cadence.NewUFix64(fmt.Sprintf("%.2f", g.Random().Float64())) + require.NoError(t, err) + return v + case cadence.UFix128Type: + v, err := cadence.NewUFix128(fixedPoint.NewUFix128(0, g.Random().Uint64())) + require.NoError(t, err) + return v + case cadence.BoolType: + return cadence.NewBool(g.Random().Bool()) + case cadence.StringType: + v, err := cadence.NewString(g.Random().RandomString(16)) + require.NoError(t, err) + return v + case cadence.TheBytesType: + return cadence.NewBytes(g.Random().RandomBytes(16)) + case cadence.CharacterType: + v, err := cadence.NewCharacter(g.Random().RandomString(1)) + require.NoError(t, err) + return v + case cadence.AddressType: + data := g.Random().RandomBytes(cadencecommon.AddressLength) + address, err := cadencecommon.BytesToAddress(data) + require.NoError(t, err) + return cadence.NewAddress(address) + case cadence.StoragePathType: + v, err := cadence.NewPath(cadencecommon.PathDomainStorage, g.Random().RandomString(16)) + require.NoError(t, err) + return v + case cadence.PublicPathType: + v, err := cadence.NewPath(cadencecommon.PathDomainPublic, g.Random().RandomString(16)) + require.NoError(t, err) + return v + default: + t.Fatalf("unsupported type: %T", cadenceType) + return nil + } +}