Merge pull request #3297 from nspcc-dev/sc-convertible

smartcontract: introduce Convertible interface
This commit is contained in:
Roman Khimov 2024-01-25 17:16:47 +03:00 committed by GitHub
commit a19f85ba34
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 85 additions and 7 deletions

View file

@ -3,7 +3,9 @@ package nns
import ( import (
"errors" "errors"
"fmt" "fmt"
"math/big"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
) )
@ -17,6 +19,10 @@ type RecordState struct {
// RecordType is domain name service record types. // RecordType is domain name service record types.
type RecordType byte type RecordType byte
// Ensure RecordType implements smartcontract.Convertible for proper handling as
// a parameter to invoker.Invoker methods.
var _ = smartcontract.Convertible(RecordType(0))
// Record types are defined in [RFC 1035](https://tools.ietf.org/html/rfc1035) // Record types are defined in [RFC 1035](https://tools.ietf.org/html/rfc1035)
const ( const (
// A represents address record type. // A represents address record type.
@ -33,6 +39,14 @@ const (
AAAA RecordType = 28 AAAA RecordType = 28
) )
// ToSCParameter implements smartcontract.Convertible interface.
func (r RecordType) ToSCParameter() (smartcontract.Parameter, error) {
return smartcontract.Parameter{
Type: smartcontract.IntegerType,
Value: big.NewInt(int64(r)),
}, nil
}
// FromStackItem fills RecordState with data from the given stack item if it can // FromStackItem fills RecordState with data from the given stack item if it can
// be correctly converted to RecordState. // be correctly converted to RecordState.
func (r *RecordState) FromStackItem(itm stackitem.Item) error { func (r *RecordState) FromStackItem(itm stackitem.Item) error {

View file

@ -25,6 +25,11 @@ type Parameter struct {
Value any `json:"value"` Value any `json:"value"`
} }
// Convertible is something that can be converted to Parameter.
type Convertible interface {
ToSCParameter() (Parameter, error)
}
// ParameterPair represents a key-value pair, a slice of which is stored in // ParameterPair represents a key-value pair, a slice of which is stored in
// MapType Parameter. // MapType Parameter.
type ParameterPair struct { type ParameterPair struct {
@ -307,6 +312,12 @@ func NewParameterFromValue(value any) (Parameter, error) {
result = *v result = *v
case Parameter: case Parameter:
result = v result = v
case Convertible:
var err error
result, err = v.ToSCParameter()
if err != nil {
return result, fmt.Errorf("failed to convert smartcontract.Convertible (%T) to Parameter: %w", v, err)
}
case util.Uint160: case util.Uint160:
result.Type = Hash160Type result.Type = Hash160Type
case util.Uint256: case util.Uint256:

View file

@ -4,6 +4,7 @@ import (
"encoding/base64" "encoding/base64"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"errors"
"math" "math"
"math/big" "math/big"
"strings" "strings"
@ -554,6 +555,25 @@ func TestExpandParameterToEmitableToStackitem(t *testing.T) {
} }
} }
// testConvertible implements Convertible interface and needed for NewParameterFromValue
// test.
type testConvertible struct {
i int
err string
}
var _ = Convertible(testConvertible{})
func (c testConvertible) ToSCParameter() (Parameter, error) {
if c.err != "" {
return Parameter{}, errors.New(c.err)
}
return Parameter{
Type: IntegerType,
Value: c.i,
}, nil
}
func TestParameterFromValue(t *testing.T) { func TestParameterFromValue(t *testing.T) {
pk1, _ := keys.NewPrivateKey() pk1, _ := keys.NewPrivateKey()
pk2, _ := keys.NewPrivateKey() pk2, _ := keys.NewPrivateKey()
@ -561,6 +581,7 @@ func TestParameterFromValue(t *testing.T) {
value any value any
expType ParamType expType ParamType
expVal any expVal any
err string // expected error substring
}{ }{
{ {
value: []byte{1, 2, 3}, value: []byte{1, 2, 3},
@ -741,20 +762,52 @@ func TestParameterFromValue(t *testing.T) {
Value: []byte{1, 2, 3}, Value: []byte{1, 2, 3},
}}, }},
}, },
{
value: testConvertible{i: 123},
expType: IntegerType,
expVal: 123,
},
{
value: []any{1, testConvertible{i: 123}},
expType: ArrayType,
expVal: []Parameter{
{
Type: IntegerType,
Value: big.NewInt(1),
},
{
Type: IntegerType,
Value: 123,
},
},
},
{
value: testConvertible{err: "invalid i value"},
err: "invalid i value",
},
{
value: make(map[string]int),
err: "unsupported parameter map[string]int",
},
{
value: []any{1, 2, make(map[string]int)},
err: "unsupported parameter map[string]int",
},
} }
for _, item := range items { for _, item := range items {
t.Run(item.expType.String()+" to stack parameter", func(t *testing.T) { t.Run(item.expType.String()+" to stack parameter", func(t *testing.T) {
res, err := NewParameterFromValue(item.value) res, err := NewParameterFromValue(item.value)
require.NoError(t, err) if item.err != "" {
require.Equal(t, item.expType, res.Type) require.Error(t, err)
require.Equal(t, item.expVal, res.Value) require.Contains(t, err.Error(), item.err)
} else {
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([]any{1, 2, make(map[string]int)})
require.Error(t, err)
} }
func TestParametersFromValues(t *testing.T) { func TestParametersFromValues(t *testing.T) {