From a123b75fd9a41f600d8e67b15617f9332e5d03e8 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Fri, 15 Nov 2024 22:28:53 +0300 Subject: [PATCH 1/3] io: no need to do ValueOf() again, it's known Signed-off-by: Roman Khimov --- pkg/io/size.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/io/size.go b/pkg/io/size.go index 8bfabb5fa5..f4636e9013 100644 --- a/pkg/io/size.go +++ b/pkg/io/size.go @@ -73,7 +73,7 @@ func GetVarSize(value any) int { valueSize := 0 if valueLength != 0 { - switch reflect.ValueOf(value).Index(0).Interface().(type) { + switch v.Index(0).Interface().(type) { case Serializable: for i := range valueLength { valueSize += GetVarSize(v.Index(i).Interface()) From 22c6ab4de99181ab72b2c99f51fefab6abb8f966 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Fri, 15 Nov 2024 22:29:09 +0300 Subject: [PATCH 2/3] smartcontract: process slices via reflection in NewParameterFromValue Pros: * less code * handles more types Cons: * slow This code is not likely to be on the hot path and it is exactly the one used by actors for making calls of various kinds. Supporting more types is more important here than raw speed. Signed-off-by: Roman Khimov --- pkg/smartcontract/parameter.go | 74 +++++++++------------------------- 1 file changed, 18 insertions(+), 56 deletions(-) diff --git a/pkg/smartcontract/parameter.go b/pkg/smartcontract/parameter.go index a7a61e906a..f5bad5277a 100644 --- a/pkg/smartcontract/parameter.go +++ b/pkg/smartcontract/parameter.go @@ -9,6 +9,7 @@ import ( "fmt" "math/big" "os" + "reflect" "slices" "strings" "unicode/utf8" @@ -265,7 +266,7 @@ func NewParameterFromString(in string) (*Parameter, error) { // NewParameterFromValue infers Parameter type from the value given and adjusts // the value if needed. It does not copy the value if it can avoid doing so. All // regular integers, util.*, keys.PublicKey*, string and bool types are supported, -// slice of byte slices is accepted and converted as well. [errors.ErrUnsupported] +// slices of various kinds are converted as well. [errors.ErrUnsupported] // will be returned for types that can't be used now. func NewParameterFromValue(value any) (Parameter, error) { var result = Parameter{ @@ -345,69 +346,30 @@ func NewParameterFromValue(value any) (Parameter, error) { case []Parameter: result.Type = ArrayType result.Value = slices.Clone(v) - case [][]byte: - return newArrayParameter(v) - case []string: - return newArrayParameter(v) - case []bool: - return newArrayParameter(v) - case []*big.Int: - return newArrayParameter(v) - case []int8: - return newArrayParameter(v) - case []int16: - return newArrayParameter(v) - case []uint16: - return newArrayParameter(v) - case []int32: - return newArrayParameter(v) - case []uint32: - return newArrayParameter(v) - case []int: - return newArrayParameter(v) - case []uint: - return newArrayParameter(v) - case []int64: - return newArrayParameter(v) - case []uint64: - return newArrayParameter(v) - case []*Parameter: - return newArrayParameter(v) - case []Convertible: - return newArrayParameter(v) - case []util.Uint160: - return newArrayParameter(v) - case []util.Uint256: - return newArrayParameter(v) - case []*util.Uint160: - return newArrayParameter(v) - case []*util.Uint256: - return newArrayParameter(v) - case []keys.PublicKey: - return newArrayParameter(v) - case []*keys.PublicKey: - return newArrayParameter(v) - case keys.PublicKeys: - return newArrayParameter(v) - case []any: - return newArrayParameter(v) case nil: result.Type = AnyType default: - return result, fmt.Errorf("%w: %T type", errors.ErrUnsupported, value) + rv := reflect.ValueOf(value) + switch rv.Kind() { + case reflect.Slice, reflect.Array: + res := make([]Parameter, 0, rv.Len()) + for i := range rv.Len() { + elem, err := NewParameterFromValue(rv.Index(i).Interface()) + if err != nil { + return result, fmt.Errorf("array index %d: %w", i, err) + } + res = append(res, elem) + } + result.Type = ArrayType + result.Value = res + default: + return result, fmt.Errorf("%w: %T type", errors.ErrUnsupported, value) + } } return result, nil } -func newArrayParameter[E any, S ~[]E](values S) (Parameter, error) { - arr, err := newArrayOfParameters(values) - if err != nil { - return Parameter{}, err - } - return Parameter{Type: ArrayType, Value: arr}, nil -} - func newArrayOfParameters[E any, S ~[]E](values S) ([]Parameter, error) { res := make([]Parameter, 0, len(values)) for i := range values { From db2956f1af33eeb6f0482b8491e2e486ec46c227 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Fri, 15 Nov 2024 22:51:35 +0300 Subject: [PATCH 3/3] smartcontract: add support for maps in NewParameterFromValue It's just not possible to use maps in invokers/actors without this. And maps have too many combinations to try pushing them into a type switch, that's where reflection kicks in and solves it easily. Signed-off-by: Roman Khimov --- pkg/rpcclient/invoker/invoker_test.go | 6 +-- pkg/smartcontract/parameter.go | 16 ++++++++ pkg/smartcontract/parameter_test.go | 56 ++++++++++++++++++++++++--- 3 files changed, 70 insertions(+), 8 deletions(-) diff --git a/pkg/rpcclient/invoker/invoker_test.go b/pkg/rpcclient/invoker/invoker_test.go index 39b21bcafa..699a99fabc 100644 --- a/pkg/rpcclient/invoker/invoker_test.go +++ b/pkg/rpcclient/invoker/invoker_test.go @@ -83,17 +83,17 @@ func TestInvoker(t *testing.T) { require.NoError(t, err) require.Equal(t, resExp, res) - _, err = inv.Verify(util.Uint160{}, nil, make(map[int]int)) + _, err = inv.Verify(util.Uint160{}, nil, make(chan struct{})) require.Error(t, err) - _, err = inv.Call(util.Uint160{}, "method", make(map[int]int)) + _, err = inv.Call(util.Uint160{}, "method", make(chan struct{})) require.Error(t, err) res, err = inv.CallAndExpandIterator(util.Uint160{}, "method", 10, 42) require.NoError(t, err) require.Equal(t, resExp, res) - _, err = inv.CallAndExpandIterator(util.Uint160{}, "method", 10, make(map[int]int)) + _, err = inv.CallAndExpandIterator(util.Uint160{}, "method", 10, make(chan struct{})) require.Error(t, err) } t.Run("standard", func(t *testing.T) { diff --git a/pkg/smartcontract/parameter.go b/pkg/smartcontract/parameter.go index f5bad5277a..c24c09c759 100644 --- a/pkg/smartcontract/parameter.go +++ b/pkg/smartcontract/parameter.go @@ -362,6 +362,22 @@ func NewParameterFromValue(value any) (Parameter, error) { } result.Type = ArrayType result.Value = res + case reflect.Map: + res := make([]ParameterPair, 0, rv.Len()) + iter := rv.MapRange() + for iter.Next() { + k, err := NewParameterFromValue(iter.Key().Interface()) + if err != nil { + return result, fmt.Errorf("map key: %w", err) + } + v, err := NewParameterFromValue(iter.Value().Interface()) + if err != nil { + return result, fmt.Errorf("map value: %w", err) + } + res = append(res, ParameterPair{Key: k, Value: v}) + } + result.Type = MapType + result.Value = res default: return result, fmt.Errorf("%w: %T type", errors.ErrUnsupported, value) } diff --git a/pkg/smartcontract/parameter_test.go b/pkg/smartcontract/parameter_test.go index 865dd79190..7afb7b3f4c 100644 --- a/pkg/smartcontract/parameter_test.go +++ b/pkg/smartcontract/parameter_test.go @@ -887,12 +887,58 @@ func TestParameterFromValue(t *testing.T) { err: "invalid i value", }, { - value: make(map[string]int), - err: "unsupported operation: map[string]int type", + value: make(map[string]int), + expType: MapType, + expVal: []ParameterPair{}, }, { - value: []any{1, 2, make(map[string]int)}, - err: "unsupported operation: map[string]int type", + value: make(map[string]int), + expType: MapType, + expVal: []ParameterPair{}, + }, + { + value: map[string]string{"key": "value"}, + expType: MapType, + expVal: []ParameterPair{{ + Key: Parameter{ + Type: StringType, + Value: "key", + }, + Value: Parameter{ + Type: StringType, + Value: "value", + }, + }}, + }, + { + value: map[int]int64{42: 100500}, + expType: MapType, + expVal: []ParameterPair{{ + Key: Parameter{ + Type: IntegerType, + Value: big.NewInt(42), + }, + Value: Parameter{ + Type: IntegerType, + Value: big.NewInt(100500), + }, + }}, + }, + { + value: make(chan int), + err: "unsupported operation: chan int type", + }, + { + value: []any{1, 2, make(chan int)}, + err: "unsupported operation: chan int type", + }, + { + value: map[string]chan int{"aaa": make(chan int)}, + err: "unsupported operation: chan int type", + }, + { + value: map[error]string{errors.New("some"): "value"}, + err: "unsupported operation: *errors.errorString type", }, } @@ -924,6 +970,6 @@ func TestParametersFromValues(t *testing.T) { Type: ByteArrayType, Value: []byte{3, 2, 1}, }}, res) - _, err = NewParametersFromValues(42, make(map[int]int), []byte{3, 2, 1}) + _, err = NewParametersFromValues(42, make(chan int), []byte{3, 2, 1}) require.Error(t, err) }