diff --git a/pkg/smartcontract/parameter.go b/pkg/smartcontract/parameter.go index 3cecd259c..170a59028 100644 --- a/pkg/smartcontract/parameter.go +++ b/pkg/smartcontract/parameter.go @@ -255,6 +255,101 @@ func NewParameterFromString(in string) (*Parameter, error) { return res, nil } +// 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. +func NewParameterFromValue(value interface{}) (Parameter, error) { + var result = Parameter{ + Value: value, + } + + switch v := value.(type) { + case []byte: + result.Type = ByteArrayType + case string: + result.Type = StringType + case bool: + result.Type = BoolType + case *big.Int: + result.Type = IntegerType + case int8: + result.Type = IntegerType + result.Value = big.NewInt(int64(v)) + case byte: + result.Type = IntegerType + result.Value = big.NewInt(int64(v)) + case int16: + result.Type = IntegerType + result.Value = big.NewInt(int64(v)) + case uint16: + result.Type = IntegerType + result.Value = big.NewInt(int64(v)) + case int32: + result.Type = IntegerType + result.Value = big.NewInt(int64(v)) + case uint32: + result.Type = IntegerType + result.Value = big.NewInt(int64(v)) + case int: + result.Type = IntegerType + result.Value = big.NewInt(int64(v)) + case uint: + result.Type = IntegerType + result.Value = new(big.Int).SetUint64(uint64(v)) + case int64: + result.Type = IntegerType + result.Value = big.NewInt(v) + case uint64: + result.Type = IntegerType + result.Value = new(big.Int).SetUint64(v) + case util.Uint160: + result.Type = Hash160Type + case util.Uint256: + result.Type = Hash256Type + case keys.PublicKey: + return NewParameterFromValue(&v) + case *keys.PublicKey: + result.Type = PublicKeyType + result.Value = v.Bytes() + case [][]byte: + arr := make([]Parameter, 0, len(v)) + for i := range v { + // We know the type exactly, so error is not possible. + elem, _ := NewParameterFromValue(v[i]) + arr = append(arr, elem) + } + result.Type = ArrayType + result.Value = arr + case []*keys.PublicKey: + return NewParameterFromValue(keys.PublicKeys(v)) + case keys.PublicKeys: + arr := make([]Parameter, 0, len(v)) + for i := range v { + // We know the type exactly, so error is not possible. + elem, _ := NewParameterFromValue(v[i]) + arr = append(arr, elem) + } + result.Type = ArrayType + result.Value = arr + case []interface{}: + arr := make([]Parameter, 0, len(v)) + for i := range v { + elem, err := NewParameterFromValue(v[i]) + if err != nil { + return result, err + } + arr = append(arr, elem) + } + result.Type = ArrayType + result.Value = arr + default: + return result, fmt.Errorf("unsupported parameter %T", value) + } + + return result, nil +} + // ExpandParameterToEmitable converts a parameter to a type which can be handled as // an array item by emit.Array. It correlates with the way an RPC server handles // FuncParams for invoke* calls inside the request.ExpandArrayIntoScript function. diff --git a/pkg/smartcontract/parameter_test.go b/pkg/smartcontract/parameter_test.go index 51b5931c4..281230ffd 100644 --- a/pkg/smartcontract/parameter_test.go +++ b/pkg/smartcontract/parameter_test.go @@ -527,3 +527,168 @@ func TestExpandParameterToEmitable(t *testing.T) { require.Error(t, err) } } + +func TestParameterFromValue(t *testing.T) { + pk1, _ := keys.NewPrivateKey() + pk2, _ := keys.NewPrivateKey() + items := []struct { + value interface{} + expType ParamType + expVal interface{} + }{ + { + value: []byte{1, 2, 3}, + expType: ByteArrayType, + expVal: []byte{1, 2, 3}, + }, + { + value: "hello world", + expType: StringType, + expVal: "hello world", + }, + { + value: false, + expType: BoolType, + expVal: false, + }, + { + value: true, + expType: BoolType, + expVal: true, + }, + { + value: big.NewInt(100), + expType: IntegerType, + expVal: big.NewInt(100), + }, + { + value: byte(100), + expType: IntegerType, + expVal: big.NewInt(100), + }, + { + value: int8(100), + expType: IntegerType, + expVal: big.NewInt(100), + }, + { + value: uint8(100), + expType: IntegerType, + expVal: big.NewInt(100), + }, + { + value: int16(100), + expType: IntegerType, + expVal: big.NewInt(100), + }, + { + value: uint16(100), + expType: IntegerType, + expVal: big.NewInt(100), + }, + { + value: int32(100), + expType: IntegerType, + expVal: big.NewInt(100), + }, + { + value: uint32(100), + expType: IntegerType, + expVal: big.NewInt(100), + }, + { + value: 100, + expType: IntegerType, + expVal: big.NewInt(100), + }, + { + value: uint(100), + expType: IntegerType, + expVal: big.NewInt(100), + }, + { + value: int64(100), + expType: IntegerType, + expVal: big.NewInt(100), + }, + { + value: uint64(100), + expType: IntegerType, + expVal: big.NewInt(100), + }, + { + value: util.Uint160{1, 2, 3}, + expType: Hash160Type, + expVal: util.Uint160{1, 2, 3}, + }, + { + value: util.Uint256{3, 2, 1}, + expType: Hash256Type, + expVal: util.Uint256{3, 2, 1}, + }, + { + value: pk1.PublicKey(), + expType: PublicKeyType, + expVal: pk1.PublicKey().Bytes(), + }, + { + value: *pk2.PublicKey(), + expType: PublicKeyType, + expVal: pk2.PublicKey().Bytes(), + }, + { + value: [][]byte{{1, 2, 3}, {3, 2, 1}}, + expType: ArrayType, + expVal: []Parameter{{ByteArrayType, []byte{1, 2, 3}}, {ByteArrayType, []byte{3, 2, 1}}}, + }, + { + value: []*keys.PublicKey{pk1.PublicKey(), pk2.PublicKey()}, + expType: ArrayType, + expVal: []Parameter{{ + Type: PublicKeyType, + Value: pk1.PublicKey().Bytes(), + }, { + Type: PublicKeyType, + Value: pk2.PublicKey().Bytes(), + }}, + }, + { + value: keys.PublicKeys{pk1.PublicKey(), pk2.PublicKey()}, + expType: ArrayType, + expVal: []Parameter{{ + Type: PublicKeyType, + Value: pk1.PublicKey().Bytes(), + }, { + Type: PublicKeyType, + Value: pk2.PublicKey().Bytes(), + }}, + }, + { + value: []interface{}{-42, "random", []byte{1, 2, 3}}, + expType: ArrayType, + expVal: []Parameter{{ + Type: IntegerType, + Value: big.NewInt(-42), + }, { + Type: StringType, + Value: "random", + }, { + Type: ByteArrayType, + Value: []byte{1, 2, 3}, + }}, + }, + } + + for _, item := range items { + t.Run(item.expType.String()+" to stack parameter", func(t *testing.T) { + res, err := NewParameterFromValue(item.value) + require.NoError(t, err) + require.Equal(t, item.expType, res.Type) + require.Equal(t, item.expVal, res.Value) + }) + } + _, err := NewParameterFromValue(make(map[string]int)) + require.Error(t, err) + _, err = NewParameterFromValue([]interface{}{1, 2, make(map[string]int)}) + require.Error(t, err) +}