smartcontract: provide interface{}->Parameter conversion

Which is almost like a NeoFS's toStackParameter() on steroids (except it
doesn't mess with noderoles package, it can be casted to int). RPC client's
Invoke* functions expect Parameters, so make it easy to create them.
This commit is contained in:
Roman Khimov 2022-07-29 12:24:12 +03:00
parent a8a2f2ed5a
commit 92a931c145
2 changed files with 260 additions and 0 deletions

View file

@ -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.

View file

@ -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)
}