rpc/smartcontract: merge contract parameter types
Merged two types: - smartcontract.ParamType - rpc.StackParamType into single one: - smartcontract.ParamType as they duplicated the functionality. NOTE: type smartcontract.MapType was added (as in C# implementation). From now, list of supported smartcontract parameter types: UnknownType SignatureType BoolType IntegerType Hash160Type Hash256Type ByteArrayType PublicKeyType StringType ArrayType MapType InteropInterfaceType VoidType
This commit is contained in:
parent
5d4dfbfa1a
commit
648e0bb242
13 changed files with 979 additions and 1001 deletions
|
@ -296,10 +296,10 @@ func initSmartContract(ctx *cli.Context) error {
|
|||
// TODO: Fix the missing neo-go.yml file with the `init` command when the package manager is in place.
|
||||
if !ctx.Bool("skip-details") {
|
||||
details := parseContractDetails()
|
||||
details.ReturnType = request.ByteArray
|
||||
details.Parameters = make([]request.StackParamType, 2)
|
||||
details.Parameters[0] = request.String
|
||||
details.Parameters[1] = request.Array
|
||||
details.ReturnType = smartcontract.ByteArrayType
|
||||
details.Parameters = make([]smartcontract.ParamType, 2)
|
||||
details.Parameters[0] = smartcontract.StringType
|
||||
details.Parameters[1] = smartcontract.ArrayType
|
||||
|
||||
project := &ProjectConfig{Contract: details}
|
||||
b, err := yaml.Marshal(project)
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"fmt"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/encoding/address"
|
||||
"github.com/CityOfZion/neo-go/pkg/smartcontract"
|
||||
"github.com/CityOfZion/neo-go/pkg/util"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
@ -24,7 +25,7 @@ type (
|
|||
// FuncParam represents a function argument parameter used in the
|
||||
// invokefunction RPC method.
|
||||
FuncParam struct {
|
||||
Type StackParamType `json:"type"`
|
||||
Type smartcontract.ParamType `json:"type"`
|
||||
Value Param `json:"value"`
|
||||
}
|
||||
)
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/encoding/address"
|
||||
"github.com/CityOfZion/neo-go/pkg/smartcontract"
|
||||
"github.com/CityOfZion/neo-go/pkg/util"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
@ -41,7 +42,7 @@ func TestParam_UnmarshalJSON(t *testing.T) {
|
|||
{
|
||||
Type: FuncParamT,
|
||||
Value: FuncParam{
|
||||
Type: String,
|
||||
Type: smartcontract.StringType,
|
||||
Value: Param{
|
||||
Type: StringT,
|
||||
Value: "jajaja",
|
||||
|
@ -146,7 +147,7 @@ func TestParamGetUint160FromAddress(t *testing.T) {
|
|||
|
||||
func TestParamGetFuncParam(t *testing.T) {
|
||||
fp := FuncParam{
|
||||
Type: String,
|
||||
Type: smartcontract.StringType,
|
||||
Value: Param{
|
||||
Type: StringT,
|
||||
Value: "jajaja",
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package request
|
||||
|
||||
import "github.com/CityOfZion/neo-go/pkg/smartcontract"
|
||||
|
||||
// ContractDetails contains contract metadata.
|
||||
type ContractDetails struct {
|
||||
Author string
|
||||
|
@ -10,6 +12,6 @@ type ContractDetails struct {
|
|||
HasStorage bool
|
||||
HasDynamicInvocation bool
|
||||
IsPayable bool
|
||||
ReturnType StackParamType
|
||||
Parameters []StackParamType
|
||||
ReturnType smartcontract.ParamType
|
||||
Parameters []smartcontract.ParamType
|
||||
}
|
||||
|
|
|
@ -1,291 +0,0 @@
|
|||
package request
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/util"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// StackParamType represents different types of stack values.
|
||||
type StackParamType int
|
||||
|
||||
// All possible StackParamType values are listed here.
|
||||
const (
|
||||
Unknown StackParamType = -1
|
||||
Signature StackParamType = 0x00
|
||||
Boolean StackParamType = 0x01
|
||||
Integer StackParamType = 0x02
|
||||
Hash160 StackParamType = 0x03
|
||||
Hash256 StackParamType = 0x04
|
||||
ByteArray StackParamType = 0x05
|
||||
PublicKey StackParamType = 0x06
|
||||
String StackParamType = 0x07
|
||||
Array StackParamType = 0x10
|
||||
InteropInterface StackParamType = 0xf0
|
||||
Void StackParamType = 0xff
|
||||
)
|
||||
|
||||
// String implements the stringer interface.
|
||||
func (t StackParamType) String() string {
|
||||
switch t {
|
||||
case Signature:
|
||||
return "Signature"
|
||||
case Boolean:
|
||||
return "Boolean"
|
||||
case Integer:
|
||||
return "Integer"
|
||||
case Hash160:
|
||||
return "Hash160"
|
||||
case Hash256:
|
||||
return "Hash256"
|
||||
case ByteArray:
|
||||
return "ByteArray"
|
||||
case PublicKey:
|
||||
return "PublicKey"
|
||||
case String:
|
||||
return "String"
|
||||
case Array:
|
||||
return "Array"
|
||||
case InteropInterface:
|
||||
return "InteropInterface"
|
||||
case Void:
|
||||
return "Void"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
}
|
||||
|
||||
// StackParamTypeFromString converts string into the StackParamType.
|
||||
func StackParamTypeFromString(s string) (StackParamType, error) {
|
||||
switch s {
|
||||
case "Signature":
|
||||
return Signature, nil
|
||||
case "Boolean":
|
||||
return Boolean, nil
|
||||
case "Integer":
|
||||
return Integer, nil
|
||||
case "Hash160":
|
||||
return Hash160, nil
|
||||
case "Hash256":
|
||||
return Hash256, nil
|
||||
case "ByteArray":
|
||||
return ByteArray, nil
|
||||
case "PublicKey":
|
||||
return PublicKey, nil
|
||||
case "String":
|
||||
return String, nil
|
||||
case "Array":
|
||||
return Array, nil
|
||||
case "InteropInterface":
|
||||
return InteropInterface, nil
|
||||
case "Void":
|
||||
return Void, nil
|
||||
default:
|
||||
return Unknown, errors.Errorf("unknown stack parameter type: %s", s)
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface.
|
||||
func (t *StackParamType) MarshalJSON() ([]byte, error) {
|
||||
return []byte(`"` + t.String() + `"`), nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON sets StackParamType from JSON-encoded data.
|
||||
func (t *StackParamType) UnmarshalJSON(data []byte) (err error) {
|
||||
var (
|
||||
s = string(data)
|
||||
l = len(s)
|
||||
)
|
||||
if l < 2 || s[0] != '"' || s[l-1] != '"' {
|
||||
*t = Unknown
|
||||
return errors.Errorf("invalid type: %s", s)
|
||||
}
|
||||
*t, err = StackParamTypeFromString(s[1 : l-1])
|
||||
return
|
||||
}
|
||||
|
||||
// MarshalYAML implements the YAML Marshaler interface.
|
||||
func (t *StackParamType) MarshalYAML() (interface{}, error) {
|
||||
return t.String(), nil
|
||||
}
|
||||
|
||||
// UnmarshalYAML implements the YAML Unmarshaler interface.
|
||||
func (t *StackParamType) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
var name string
|
||||
|
||||
err := unmarshal(&name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*t, err = StackParamTypeFromString(name)
|
||||
return err
|
||||
}
|
||||
|
||||
// StackParam represent a stack parameter.
|
||||
type StackParam struct {
|
||||
Type StackParamType `json:"type"`
|
||||
Value interface{} `json:"value"`
|
||||
}
|
||||
|
||||
type rawStackParam struct {
|
||||
Type StackParamType `json:"type"`
|
||||
Value json.RawMessage `json:"value"`
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements Unmarshaler interface.
|
||||
func (p *StackParam) UnmarshalJSON(data []byte) (err error) {
|
||||
var (
|
||||
r rawStackParam
|
||||
i int64
|
||||
s string
|
||||
b []byte
|
||||
)
|
||||
|
||||
if err = json.Unmarshal(data, &r); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch p.Type = r.Type; r.Type {
|
||||
case ByteArray:
|
||||
if err = json.Unmarshal(r.Value, &s); err != nil {
|
||||
return
|
||||
}
|
||||
if b, err = hex.DecodeString(s); err != nil {
|
||||
return
|
||||
}
|
||||
p.Value = b
|
||||
case String:
|
||||
if err = json.Unmarshal(r.Value, &s); err != nil {
|
||||
return
|
||||
}
|
||||
p.Value = s
|
||||
case Integer:
|
||||
if err = json.Unmarshal(r.Value, &i); err == nil {
|
||||
p.Value = i
|
||||
return
|
||||
}
|
||||
// sometimes integer comes as string
|
||||
if err = json.Unmarshal(r.Value, &s); err != nil {
|
||||
return
|
||||
}
|
||||
if i, err = strconv.ParseInt(s, 10, 64); err != nil {
|
||||
return
|
||||
}
|
||||
p.Value = i
|
||||
case Array:
|
||||
// https://github.com/neo-project/neo/blob/3d59ecca5a8deb057bdad94b3028a6d5e25ac088/neo/Network/RPC/RpcServer.cs#L67
|
||||
var rs []StackParam
|
||||
if err = json.Unmarshal(r.Value, &rs); err != nil {
|
||||
return
|
||||
}
|
||||
p.Value = rs
|
||||
case Hash160:
|
||||
var h util.Uint160
|
||||
if err = json.Unmarshal(r.Value, &h); err != nil {
|
||||
return
|
||||
}
|
||||
p.Value = h
|
||||
case Hash256:
|
||||
var h util.Uint256
|
||||
if err = json.Unmarshal(r.Value, &h); err != nil {
|
||||
return
|
||||
}
|
||||
p.Value = h
|
||||
default:
|
||||
return errors.New("not implemented")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// StackParams is an array of StackParam (TODO: drop it?).
|
||||
type StackParams []StackParam
|
||||
|
||||
// TryParseArray converts an array of StackParam into an array of more appropriate things.
|
||||
func (p StackParams) TryParseArray(vals ...interface{}) error {
|
||||
var (
|
||||
err error
|
||||
i int
|
||||
par StackParam
|
||||
)
|
||||
if len(p) != len(vals) {
|
||||
return errors.New("receiver array doesn't fit the StackParams length")
|
||||
}
|
||||
for i, par = range p {
|
||||
if err = par.TryParse(vals[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TryParse converts one StackParam into something more appropriate.
|
||||
func (p StackParam) TryParse(dest interface{}) error {
|
||||
var (
|
||||
err error
|
||||
ok bool
|
||||
data []byte
|
||||
)
|
||||
switch p.Type {
|
||||
case ByteArray:
|
||||
if data, ok = p.Value.([]byte); !ok {
|
||||
return errors.Errorf("failed to cast %s to []byte", p.Value)
|
||||
}
|
||||
switch dest := dest.(type) {
|
||||
case *util.Uint160:
|
||||
if *dest, err = util.Uint160DecodeBytesBE(data); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
case *[]byte:
|
||||
*dest = data
|
||||
return nil
|
||||
case *util.Uint256:
|
||||
if *dest, err = util.Uint256DecodeBytesLE(data); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
case *int64, *int32, *int16, *int8, *int, *uint64, *uint32, *uint16, *uint8, *uint:
|
||||
i := bytesToUint64(data)
|
||||
switch dest := dest.(type) {
|
||||
case *int64:
|
||||
*dest = int64(i)
|
||||
case *int32:
|
||||
*dest = int32(i)
|
||||
case *int16:
|
||||
*dest = int16(i)
|
||||
case *int8:
|
||||
*dest = int8(i)
|
||||
case *int:
|
||||
*dest = int(i)
|
||||
case *uint64:
|
||||
*dest = i
|
||||
case *uint32:
|
||||
*dest = uint32(i)
|
||||
case *uint16:
|
||||
*dest = uint16(i)
|
||||
case *uint8:
|
||||
*dest = uint8(i)
|
||||
case *uint:
|
||||
*dest = uint(i)
|
||||
}
|
||||
case *string:
|
||||
*dest = string(data)
|
||||
return nil
|
||||
default:
|
||||
return errors.Errorf("cannot cast stackparam of type %s to type %s", p.Type, dest)
|
||||
}
|
||||
default:
|
||||
return errors.New("cannot define stackparam type")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func bytesToUint64(b []byte) uint64 {
|
||||
data := make([]byte, 8)
|
||||
copy(data[8-len(b):], util.ArrayReverse(b))
|
||||
return binary.BigEndian.Uint64(data)
|
||||
}
|
|
@ -1,200 +0,0 @@
|
|||
package request
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/util"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var testCases = []struct {
|
||||
input string
|
||||
result StackParam
|
||||
}{
|
||||
{
|
||||
input: `{"type":"Integer","value":12345}`,
|
||||
result: StackParam{Type: Integer, Value: int64(12345)},
|
||||
},
|
||||
{
|
||||
input: `{"type":"Integer","value":"12345"}`,
|
||||
result: StackParam{Type: Integer, Value: int64(12345)},
|
||||
},
|
||||
{
|
||||
input: `{"type":"ByteArray","value":"010203"}`,
|
||||
result: StackParam{Type: ByteArray, Value: []byte{0x01, 0x02, 0x03}},
|
||||
},
|
||||
{
|
||||
input: `{"type":"String","value":"Some string"}`,
|
||||
result: StackParam{Type: String, Value: "Some string"},
|
||||
},
|
||||
{
|
||||
input: `{"type":"Array","value":[
|
||||
{"type": "String", "value": "str 1"},
|
||||
{"type": "Integer", "value": 2}]}`,
|
||||
result: StackParam{
|
||||
Type: Array,
|
||||
Value: []StackParam{
|
||||
{Type: String, Value: "str 1"},
|
||||
{Type: Integer, Value: int64(2)},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: `{"type": "Hash160", "value": "0bcd2978634d961c24f5aea0802297ff128724d6"}`,
|
||||
result: StackParam{
|
||||
Type: Hash160,
|
||||
Value: util.Uint160{
|
||||
0x0b, 0xcd, 0x29, 0x78, 0x63, 0x4d, 0x96, 0x1c, 0x24, 0xf5,
|
||||
0xae, 0xa0, 0x80, 0x22, 0x97, 0xff, 0x12, 0x87, 0x24, 0xd6,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: `{"type": "Hash256", "value": "f037308fa0ab18155bccfc08485468c112409ea5064595699e98c545f245f32d"}`,
|
||||
result: StackParam{
|
||||
Type: Hash256,
|
||||
Value: util.Uint256{
|
||||
0x2d, 0xf3, 0x45, 0xf2, 0x45, 0xc5, 0x98, 0x9e,
|
||||
0x69, 0x95, 0x45, 0x06, 0xa5, 0x9e, 0x40, 0x12,
|
||||
0xc1, 0x68, 0x54, 0x48, 0x08, 0xfc, 0xcc, 0x5b,
|
||||
0x15, 0x18, 0xab, 0xa0, 0x8f, 0x30, 0x37, 0xf0,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var errorCases = []string{
|
||||
`{"type": "ByteArray","value":`, // incorrect JSON
|
||||
`{"type": "ByteArray","value":1}`, // incorrect Value
|
||||
`{"type": "ByteArray","value":"12zz"}`, // incorrect ByteArray value
|
||||
`{"type": "String","value":`, // incorrect JSON
|
||||
`{"type": "String","value":1}`, // incorrect Value
|
||||
`{"type": "Integer","value": "nn"}`, // incorrect Integer value
|
||||
`{"type": "Integer","value": []}`, // incorrect Integer value
|
||||
`{"type": "Array","value": 123}`, // incorrect Array value
|
||||
`{"type": "Hash160","value": "0bcd"}`, // incorrect Uint160 value
|
||||
`{"type": "Hash256","value": "0bcd"}`, // incorrect Uint256 value
|
||||
`{"type": "Stringg","value": ""}`, // incorrect type
|
||||
`{"type": {},"value": ""}`, // incorrect value
|
||||
|
||||
`{"type": "InteropInterface","value": ""}`, // ununmarshable type
|
||||
}
|
||||
|
||||
func TestStackParam_UnmarshalJSON(t *testing.T) {
|
||||
var s StackParam
|
||||
for _, tc := range testCases {
|
||||
assert.NoError(t, json.Unmarshal([]byte(tc.input), &s))
|
||||
assert.Equal(t, s, tc.result)
|
||||
}
|
||||
|
||||
for _, input := range errorCases {
|
||||
assert.Error(t, json.Unmarshal([]byte(input), &s))
|
||||
}
|
||||
}
|
||||
|
||||
var tryParseTestCases = []struct {
|
||||
input interface{}
|
||||
expected interface{}
|
||||
}{
|
||||
{
|
||||
input: []byte{
|
||||
0x0b, 0xcd, 0x29, 0x78, 0x63, 0x4d, 0x96, 0x1c, 0x24, 0xf5,
|
||||
0xae, 0xa0, 0x80, 0x22, 0x97, 0xff, 0x12, 0x87, 0x24, 0xd6,
|
||||
},
|
||||
expected: util.Uint160{
|
||||
0x0b, 0xcd, 0x29, 0x78, 0x63, 0x4d, 0x96, 0x1c, 0x24, 0xf5,
|
||||
0xae, 0xa0, 0x80, 0x22, 0x97, 0xff, 0x12, 0x87, 0x24, 0xd6,
|
||||
},
|
||||
},
|
||||
{
|
||||
input: []byte{
|
||||
0xf0, 0x37, 0x30, 0x8f, 0xa0, 0xab, 0x18, 0x15,
|
||||
0x5b, 0xcc, 0xfc, 0x08, 0x48, 0x54, 0x68, 0xc1,
|
||||
0x12, 0x40, 0x9e, 0xa5, 0x06, 0x45, 0x95, 0x69,
|
||||
0x9e, 0x98, 0xc5, 0x45, 0xf2, 0x45, 0xf3, 0x2d,
|
||||
},
|
||||
expected: util.Uint256{
|
||||
0x2d, 0xf3, 0x45, 0xf2, 0x45, 0xc5, 0x98, 0x9e,
|
||||
0x69, 0x95, 0x45, 0x06, 0xa5, 0x9e, 0x40, 0x12,
|
||||
0xc1, 0x68, 0x54, 0x48, 0x08, 0xfc, 0xcc, 0x5b,
|
||||
0x15, 0x18, 0xab, 0xa0, 0x8f, 0x30, 0x37, 0xf0,
|
||||
},
|
||||
},
|
||||
{
|
||||
input: []byte{0, 1, 2, 3, 4, 9, 8, 6},
|
||||
expected: []byte{0, 1, 2, 3, 4, 9, 8, 6},
|
||||
},
|
||||
{
|
||||
input: []byte{0x63, 0x78, 0x29, 0xcd, 0x0b},
|
||||
expected: int64(50686687331),
|
||||
},
|
||||
{
|
||||
input: []byte("this is a test string"),
|
||||
expected: "this is a test string",
|
||||
},
|
||||
}
|
||||
|
||||
func TestStackParam_TryParse(t *testing.T) {
|
||||
for _, tc := range tryParseTestCases {
|
||||
t.Run(reflect.TypeOf(tc.expected).String(), func(t *testing.T) {
|
||||
input := StackParam{
|
||||
Type: ByteArray,
|
||||
Value: tc.input,
|
||||
}
|
||||
|
||||
val := reflect.New(reflect.TypeOf(tc.expected))
|
||||
assert.NoError(t, input.TryParse(val.Interface()))
|
||||
assert.Equal(t, tc.expected, val.Elem().Interface())
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("[]Uint160", func(t *testing.T) {
|
||||
exp1 := util.Uint160{1, 2, 3, 4, 5}
|
||||
exp2 := util.Uint160{9, 8, 7, 6, 5}
|
||||
|
||||
params := StackParams{
|
||||
{
|
||||
Type: ByteArray,
|
||||
Value: exp1.BytesBE(),
|
||||
},
|
||||
{
|
||||
Type: ByteArray,
|
||||
Value: exp2.BytesBE(),
|
||||
},
|
||||
}
|
||||
|
||||
var out1, out2 util.Uint160
|
||||
|
||||
assert.NoError(t, params.TryParseArray(&out1, &out2))
|
||||
assert.Equal(t, exp1, out1)
|
||||
assert.Equal(t, exp2, out2)
|
||||
})
|
||||
}
|
||||
|
||||
func TestStackParamType_String(t *testing.T) {
|
||||
types := []StackParamType{
|
||||
Signature,
|
||||
Boolean,
|
||||
Integer,
|
||||
Hash160,
|
||||
Hash256,
|
||||
ByteArray,
|
||||
PublicKey,
|
||||
String,
|
||||
Array,
|
||||
InteropInterface,
|
||||
Void,
|
||||
}
|
||||
|
||||
for _, exp := range types {
|
||||
actual, err := StackParamTypeFromString(exp.String())
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, exp, actual)
|
||||
}
|
||||
|
||||
actual, err := StackParamTypeFromString(Unknown.String())
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, Unknown, actual)
|
||||
}
|
|
@ -148,31 +148,31 @@ func expandArrayIntoScript(script *io.BinWriter, slice []Param) error {
|
|||
return err
|
||||
}
|
||||
switch fp.Type {
|
||||
case ByteArray, Signature:
|
||||
case smartcontract.ByteArrayType, smartcontract.SignatureType:
|
||||
str, err := fp.Value.GetBytesHex()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
emit.Bytes(script, str)
|
||||
case String:
|
||||
case smartcontract.StringType:
|
||||
str, err := fp.Value.GetString()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
emit.String(script, str)
|
||||
case Hash160:
|
||||
case smartcontract.Hash160Type:
|
||||
hash, err := fp.Value.GetUint160FromHex()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
emit.Bytes(script, hash.BytesBE())
|
||||
case Hash256:
|
||||
case smartcontract.Hash256Type:
|
||||
hash, err := fp.Value.GetUint256()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
emit.Bytes(script, hash.BytesBE())
|
||||
case PublicKey:
|
||||
case smartcontract.PublicKeyType:
|
||||
str, err := fp.Value.GetString()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -182,13 +182,13 @@ func expandArrayIntoScript(script *io.BinWriter, slice []Param) error {
|
|||
return err
|
||||
}
|
||||
emit.Bytes(script, key.Bytes())
|
||||
case Integer:
|
||||
case smartcontract.IntegerType:
|
||||
val, err := fp.Value.GetInt()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
emit.Int(script, int64(val))
|
||||
case Boolean:
|
||||
case smartcontract.BoolType:
|
||||
str, err := fp.Value.GetString()
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"encoding/hex"
|
||||
"testing"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/smartcontract"
|
||||
"github.com/CityOfZion/neo-go/pkg/util"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
@ -29,31 +30,31 @@ func TestInvocationScriptCreationGood(t *testing.T) {
|
|||
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{}}},
|
||||
script: "00c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
||||
}, {
|
||||
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: ByteArray, Value: Param{Type: StringT, Value: "50befd26fdf6e4d957c11e078b24ebce6291456f"}}}}}},
|
||||
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.ByteArrayType, Value: Param{Type: StringT, Value: "50befd26fdf6e4d957c11e078b24ebce6291456f"}}}}}},
|
||||
script: "1450befd26fdf6e4d957c11e078b24ebce6291456f51c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
||||
}, {
|
||||
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: Signature, Value: Param{Type: StringT, Value: "4edf5005771de04619235d5a4c7a9a11bb78e008541f1da7725f654c33380a3c87e2959a025da706d7255cb3a3fa07ebe9c6559d0d9e6213c68049168eb1056f"}}}}}},
|
||||
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.SignatureType, Value: Param{Type: StringT, Value: "4edf5005771de04619235d5a4c7a9a11bb78e008541f1da7725f654c33380a3c87e2959a025da706d7255cb3a3fa07ebe9c6559d0d9e6213c68049168eb1056f"}}}}}},
|
||||
script: "404edf5005771de04619235d5a4c7a9a11bb78e008541f1da7725f654c33380a3c87e2959a025da706d7255cb3a3fa07ebe9c6559d0d9e6213c68049168eb1056f51c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
||||
}, {
|
||||
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: String, Value: Param{Type: StringT, Value: "50befd26fdf6e4d957c11e078b24ebce6291456f"}}}}}},
|
||||
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.StringType, Value: Param{Type: StringT, Value: "50befd26fdf6e4d957c11e078b24ebce6291456f"}}}}}},
|
||||
script: "283530626566643236666466366534643935376331316530373862323465626365363239313435366651c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
||||
}, {
|
||||
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: Hash160, Value: Param{Type: StringT, Value: "50befd26fdf6e4d957c11e078b24ebce6291456f"}}}}}},
|
||||
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.Hash160Type, Value: Param{Type: StringT, Value: "50befd26fdf6e4d957c11e078b24ebce6291456f"}}}}}},
|
||||
script: "146f459162ceeb248b071ec157d9e4f6fd26fdbe5051c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
||||
}, {
|
||||
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: Hash256, Value: Param{Type: StringT, Value: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"}}}}}},
|
||||
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.Hash256Type, Value: Param{Type: StringT, Value: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"}}}}}},
|
||||
script: "20e72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c6051c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
||||
}, {
|
||||
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: PublicKey, Value: Param{Type: StringT, Value: "03c089d7122b840a4935234e82e26ae5efd0c2acb627239dc9f207311337b6f2c1"}}}}}},
|
||||
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.PublicKeyType, Value: Param{Type: StringT, Value: "03c089d7122b840a4935234e82e26ae5efd0c2acb627239dc9f207311337b6f2c1"}}}}}},
|
||||
script: "2103c089d7122b840a4935234e82e26ae5efd0c2acb627239dc9f207311337b6f2c151c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
||||
}, {
|
||||
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: Integer, Value: Param{Type: NumberT, Value: 42}}}}}},
|
||||
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.IntegerType, Value: Param{Type: NumberT, Value: 42}}}}}},
|
||||
script: "012a51c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
||||
}, {
|
||||
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: Boolean, Value: Param{Type: StringT, Value: "true"}}}}}},
|
||||
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.BoolType, Value: Param{Type: StringT, Value: "true"}}}}}},
|
||||
script: "5151c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
||||
}, {
|
||||
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: Boolean, Value: Param{Type: StringT, Value: "false"}}}}}},
|
||||
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.BoolType, Value: Param{Type: StringT, Value: "false"}}}}}},
|
||||
script: "0051c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
||||
}}
|
||||
for _, ps := range paramScripts {
|
||||
|
@ -70,17 +71,17 @@ func TestInvocationScriptCreationBad(t *testing.T) {
|
|||
{{Type: NumberT, Value: "qwerty"}},
|
||||
{{Type: ArrayT, Value: 42}},
|
||||
{{Type: ArrayT, Value: []Param{{Type: NumberT, Value: 42}}}},
|
||||
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: ByteArray, Value: Param{Type: StringT, Value: "qwerty"}}}}}},
|
||||
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: Signature, Value: Param{Type: StringT, Value: "qwerty"}}}}}},
|
||||
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: String, Value: Param{Type: NumberT, Value: 42}}}}}},
|
||||
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: Hash160, Value: Param{Type: StringT, Value: "qwerty"}}}}}},
|
||||
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: Hash256, Value: Param{Type: StringT, Value: "qwerty"}}}}}},
|
||||
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: PublicKey, Value: Param{Type: NumberT, Value: 42}}}}}},
|
||||
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: PublicKey, Value: Param{Type: StringT, Value: "qwerty"}}}}}},
|
||||
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: Integer, Value: Param{Type: StringT, Value: "qwerty"}}}}}},
|
||||
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: Boolean, Value: Param{Type: NumberT, Value: 42}}}}}},
|
||||
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: Boolean, Value: Param{Type: StringT, Value: "qwerty"}}}}}},
|
||||
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: Unknown, Value: Param{}}}}}},
|
||||
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.ByteArrayType, Value: Param{Type: StringT, Value: "qwerty"}}}}}},
|
||||
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.SignatureType, Value: Param{Type: StringT, Value: "qwerty"}}}}}},
|
||||
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.StringType, Value: Param{Type: NumberT, Value: 42}}}}}},
|
||||
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.Hash160Type, Value: Param{Type: StringT, Value: "qwerty"}}}}}},
|
||||
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.Hash256Type, Value: Param{Type: StringT, Value: "qwerty"}}}}}},
|
||||
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.PublicKeyType, Value: Param{Type: NumberT, Value: 42}}}}}},
|
||||
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.PublicKeyType, Value: Param{Type: StringT, Value: "qwerty"}}}}}},
|
||||
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.IntegerType, Value: Param{Type: StringT, Value: "qwerty"}}}}}},
|
||||
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.BoolType, Value: Param{Type: NumberT, Value: 42}}}}}},
|
||||
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.BoolType, Value: Param{Type: StringT, Value: "qwerty"}}}}}},
|
||||
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.UnknownType, Value: Param{}}}}}},
|
||||
}
|
||||
for _, ps := range testParams {
|
||||
_, err := CreateFunctionInvocationScript(contract, ps)
|
||||
|
|
|
@ -3,8 +3,8 @@ package response
|
|||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/rpc/request"
|
||||
"github.com/CityOfZion/neo-go/pkg/rpc/response/result"
|
||||
"github.com/CityOfZion/neo-go/pkg/smartcontract"
|
||||
"github.com/CityOfZion/neo-go/pkg/vm"
|
||||
)
|
||||
|
||||
|
@ -14,7 +14,7 @@ type InvokeResult struct {
|
|||
State vm.State `json:"state"`
|
||||
GasConsumed string `json:"gas_consumed"`
|
||||
Script string `json:"script"`
|
||||
Stack []request.StackParam
|
||||
Stack []smartcontract.Parameter
|
||||
}
|
||||
|
||||
// Header is a generic JSON-RPC 2.0 response header (ID and JSON-RPC version).
|
||||
|
|
|
@ -1,33 +1,16 @@
|
|||
package smartcontract
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"math/bits"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
|
||||
"github.com/CityOfZion/neo-go/pkg/encoding/address"
|
||||
"github.com/CityOfZion/neo-go/pkg/io"
|
||||
"github.com/CityOfZion/neo-go/pkg/util"
|
||||
)
|
||||
|
||||
// ParamType represents the Type of the contract parameter.
|
||||
type ParamType byte
|
||||
|
||||
// A list of supported smart contract parameter types.
|
||||
const (
|
||||
SignatureType ParamType = iota
|
||||
BoolType
|
||||
IntegerType
|
||||
Hash160Type
|
||||
Hash256Type
|
||||
ByteArrayType
|
||||
PublicKeyType
|
||||
StringType
|
||||
ArrayType
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// PropertyState represents contract properties (flags).
|
||||
|
@ -49,62 +32,6 @@ type Parameter struct {
|
|||
Value interface{} `json:"value"`
|
||||
}
|
||||
|
||||
func (pt ParamType) String() string {
|
||||
switch pt {
|
||||
case SignatureType:
|
||||
return "Signature"
|
||||
case BoolType:
|
||||
return "Boolean"
|
||||
case IntegerType:
|
||||
return "Integer"
|
||||
case Hash160Type:
|
||||
return "Hash160"
|
||||
case Hash256Type:
|
||||
return "Hash256"
|
||||
case ByteArrayType:
|
||||
return "ByteArray"
|
||||
case PublicKeyType:
|
||||
return "PublicKey"
|
||||
case StringType:
|
||||
return "String"
|
||||
case ArrayType:
|
||||
return "Array"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface.
|
||||
func (pt ParamType) MarshalJSON() ([]byte, error) {
|
||||
return []byte(`"` + pt.String() + `"`), nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaler interface.
|
||||
func (pt *ParamType) UnmarshalJSON(data []byte) error {
|
||||
var s string
|
||||
if err := json.Unmarshal(data, &s); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p, err := parseParamType(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*pt = p
|
||||
return nil
|
||||
}
|
||||
|
||||
// EncodeBinary implements io.Serializable interface.
|
||||
func (pt ParamType) EncodeBinary(w *io.BinWriter) {
|
||||
w.WriteB(byte(pt))
|
||||
}
|
||||
|
||||
// DecodeBinary implements io.Serializable interface.
|
||||
func (pt *ParamType) DecodeBinary(r *io.BinReader) {
|
||||
*pt = ParamType(r.ReadB())
|
||||
}
|
||||
|
||||
// NewParameter returns a Parameter with proper initialized Value
|
||||
// of the given ParamType.
|
||||
func NewParameter(t ParamType) Parameter {
|
||||
|
@ -114,149 +41,188 @@ func NewParameter(t ParamType) Parameter {
|
|||
}
|
||||
}
|
||||
|
||||
// parseParamType is a user-friendly string to ParamType converter, it's
|
||||
// case-insensitive and makes the following conversions:
|
||||
// signature -> SignatureType
|
||||
// bool -> BoolType
|
||||
// int -> IntegerType
|
||||
// hash160 -> Hash160Type
|
||||
// hash256 -> Hash256Type
|
||||
// bytes -> ByteArrayType
|
||||
// key -> PublicKeyType
|
||||
// string -> StringType
|
||||
// anything else generates an error.
|
||||
func parseParamType(typ string) (ParamType, error) {
|
||||
switch strings.ToLower(typ) {
|
||||
case "signature":
|
||||
return SignatureType, nil
|
||||
case "bool":
|
||||
return BoolType, nil
|
||||
case "int":
|
||||
return IntegerType, nil
|
||||
case "hash160":
|
||||
return Hash160Type, nil
|
||||
case "hash256":
|
||||
return Hash256Type, nil
|
||||
case "bytes", "bytearray":
|
||||
return ByteArrayType, nil
|
||||
case "key":
|
||||
return PublicKeyType, nil
|
||||
case "string":
|
||||
return StringType, nil
|
||||
default:
|
||||
// We deliberately don't support array here.
|
||||
return 0, errors.New("wrong or unsupported parameter type")
|
||||
}
|
||||
type rawParameter struct {
|
||||
Type ParamType `json:"type"`
|
||||
Value json.RawMessage `json:"value"`
|
||||
}
|
||||
|
||||
// adjustValToType is a value type-checker and converter.
|
||||
func adjustValToType(typ ParamType, val string) (interface{}, error) {
|
||||
switch typ {
|
||||
case SignatureType:
|
||||
b, err := hex.DecodeString(val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// UnmarshalJSON implements Unmarshaler interface.
|
||||
func (p *Parameter) UnmarshalJSON(data []byte) (err error) {
|
||||
var (
|
||||
r rawParameter
|
||||
i int64
|
||||
s string
|
||||
b []byte
|
||||
)
|
||||
|
||||
if err = json.Unmarshal(data, &r); err != nil {
|
||||
return
|
||||
}
|
||||
if len(b) != 64 {
|
||||
return nil, errors.New("not a signature")
|
||||
}
|
||||
return val, nil
|
||||
case BoolType:
|
||||
switch val {
|
||||
case "true":
|
||||
return true, nil
|
||||
case "false":
|
||||
return false, nil
|
||||
default:
|
||||
return nil, errors.New("invalid boolean value")
|
||||
}
|
||||
case IntegerType:
|
||||
return strconv.Atoi(val)
|
||||
case Hash160Type:
|
||||
u, err := address.StringToUint160(val)
|
||||
if err == nil {
|
||||
return hex.EncodeToString(u.BytesBE()), nil
|
||||
}
|
||||
b, err := hex.DecodeString(val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(b) != 20 {
|
||||
return nil, errors.New("not a hash160")
|
||||
}
|
||||
return val, nil
|
||||
case Hash256Type:
|
||||
b, err := hex.DecodeString(val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(b) != 32 {
|
||||
return nil, errors.New("not a hash256")
|
||||
}
|
||||
return val, nil
|
||||
|
||||
switch p.Type = r.Type; r.Type {
|
||||
case ByteArrayType:
|
||||
_, err := hex.DecodeString(val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if err = json.Unmarshal(r.Value, &s); err != nil {
|
||||
return
|
||||
}
|
||||
return val, nil
|
||||
case PublicKeyType:
|
||||
_, err := keys.NewPublicKeyFromString(val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if b, err = hex.DecodeString(s); err != nil {
|
||||
return
|
||||
}
|
||||
return val, nil
|
||||
p.Value = b
|
||||
case StringType:
|
||||
return val, nil
|
||||
default:
|
||||
return nil, errors.New("unsupported parameter type")
|
||||
if err = json.Unmarshal(r.Value, &s); err != nil {
|
||||
return
|
||||
}
|
||||
p.Value = s
|
||||
case IntegerType:
|
||||
if err = json.Unmarshal(r.Value, &i); err == nil {
|
||||
p.Value = i
|
||||
return
|
||||
}
|
||||
// sometimes integer comes as string
|
||||
if err = json.Unmarshal(r.Value, &s); err != nil {
|
||||
return
|
||||
}
|
||||
if i, err = strconv.ParseInt(s, 10, 64); err != nil {
|
||||
return
|
||||
}
|
||||
p.Value = i
|
||||
case ArrayType:
|
||||
// https://github.com/neo-project/neo/blob/3d59ecca5a8deb057bdad94b3028a6d5e25ac088/neo/Network/RPC/RpcServer.cs#L67
|
||||
var rs []Parameter
|
||||
if err = json.Unmarshal(r.Value, &rs); err != nil {
|
||||
return
|
||||
}
|
||||
p.Value = rs
|
||||
case Hash160Type:
|
||||
var h util.Uint160
|
||||
if err = json.Unmarshal(r.Value, &h); err != nil {
|
||||
return
|
||||
}
|
||||
p.Value = h
|
||||
case Hash256Type:
|
||||
var h util.Uint256
|
||||
if err = json.Unmarshal(r.Value, &h); err != nil {
|
||||
return
|
||||
}
|
||||
p.Value = h
|
||||
default:
|
||||
return errors.New("not implemented")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// inferParamType tries to infer the value type from its contents. It returns
|
||||
// IntegerType for anything that looks like decimal integer (can be converted
|
||||
// with strconv.Atoi), BoolType for true and false values, Hash160Type for
|
||||
// addresses and hex strings encoding 20 bytes long values, PublicKeyType for
|
||||
// valid hex-encoded public keys, Hash256Type for hex-encoded 32 bytes values,
|
||||
// SignatureType for hex-encoded 64 bytes values, ByteArrayType for any other
|
||||
// valid hex-encoded values and StringType for anything else.
|
||||
func inferParamType(val string) ParamType {
|
||||
var err error
|
||||
// Params is an array of Parameter (TODO: drop it?).
|
||||
type Params []Parameter
|
||||
|
||||
_, err = strconv.Atoi(val)
|
||||
if err == nil {
|
||||
return IntegerType
|
||||
// TryParseArray converts an array of Parameter into an array of more appropriate things.
|
||||
func (p Params) TryParseArray(vals ...interface{}) error {
|
||||
var (
|
||||
err error
|
||||
i int
|
||||
par Parameter
|
||||
)
|
||||
if len(p) != len(vals) {
|
||||
return errors.New("receiver array doesn't fit the Params length")
|
||||
}
|
||||
for i, par = range p {
|
||||
if err = par.TryParse(vals[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TryParse converts one Parameter into something more appropriate.
|
||||
func (p Parameter) TryParse(dest interface{}) error {
|
||||
var (
|
||||
err error
|
||||
ok bool
|
||||
data []byte
|
||||
)
|
||||
switch p.Type {
|
||||
case ByteArrayType:
|
||||
if data, ok = p.Value.([]byte); !ok {
|
||||
return errors.Errorf("failed to cast %s to []byte", p.Value)
|
||||
}
|
||||
switch dest := dest.(type) {
|
||||
case *util.Uint160:
|
||||
if *dest, err = util.Uint160DecodeBytesBE(data); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
case *[]byte:
|
||||
*dest = data
|
||||
return nil
|
||||
case *util.Uint256:
|
||||
if *dest, err = util.Uint256DecodeBytesLE(data); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
case *int64, *int32, *int16, *int8, *int, *uint64, *uint32, *uint16, *uint8, *uint:
|
||||
var size int
|
||||
switch dest.(type) {
|
||||
case *int64, *uint64:
|
||||
size = 64
|
||||
case *int32, *uint32:
|
||||
size = 32
|
||||
case *int16, *uint16:
|
||||
size = 16
|
||||
case *int8, *uint8:
|
||||
size = 8
|
||||
case *int, *uint:
|
||||
size = bits.UintSize
|
||||
}
|
||||
|
||||
if val == "true" || val == "false" {
|
||||
return BoolType
|
||||
i, err := bytesToUint64(data, size)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = address.StringToUint160(val)
|
||||
if err == nil {
|
||||
return Hash160Type
|
||||
switch dest := dest.(type) {
|
||||
case *int64:
|
||||
*dest = int64(i)
|
||||
case *int32:
|
||||
*dest = int32(i)
|
||||
case *int16:
|
||||
*dest = int16(i)
|
||||
case *int8:
|
||||
*dest = int8(i)
|
||||
case *int:
|
||||
*dest = int(i)
|
||||
case *uint64:
|
||||
*dest = i
|
||||
case *uint32:
|
||||
*dest = uint32(i)
|
||||
case *uint16:
|
||||
*dest = uint16(i)
|
||||
case *uint8:
|
||||
*dest = uint8(i)
|
||||
case *uint:
|
||||
*dest = uint(i)
|
||||
}
|
||||
|
||||
_, err = keys.NewPublicKeyFromString(val)
|
||||
if err == nil {
|
||||
return PublicKeyType
|
||||
}
|
||||
|
||||
unhexed, err := hex.DecodeString(val)
|
||||
if err == nil {
|
||||
switch len(unhexed) {
|
||||
case 20:
|
||||
return Hash160Type
|
||||
case 32:
|
||||
return Hash256Type
|
||||
case 64:
|
||||
return SignatureType
|
||||
case *string:
|
||||
*dest = string(data)
|
||||
return nil
|
||||
default:
|
||||
return ByteArrayType
|
||||
return errors.Errorf("cannot cast param of type %s to type %s", p.Type, dest)
|
||||
}
|
||||
default:
|
||||
return errors.New("cannot define param type")
|
||||
}
|
||||
// Anything can be a string.
|
||||
return StringType
|
||||
return nil
|
||||
}
|
||||
|
||||
func bytesToUint64(b []byte, size int) (uint64, error) {
|
||||
var length = size / 8
|
||||
if len(b) > length {
|
||||
return 0, errors.Errorf("input doesn't fit into %d bits", size)
|
||||
}
|
||||
if len(b) < length {
|
||||
data := make([]byte, length)
|
||||
copy(data, b)
|
||||
return binary.LittleEndian.Uint64(data), nil
|
||||
}
|
||||
return binary.LittleEndian.Uint64(b), nil
|
||||
}
|
||||
|
||||
// NewParameterFromString returns a new Parameter initialized from the given
|
||||
|
@ -282,10 +248,14 @@ func NewParameterFromString(in string) (*Parameter, error) {
|
|||
}
|
||||
if char == ':' && !escaped && !hadType {
|
||||
typStr := buf.String()
|
||||
res.Type, err = parseParamType(typStr)
|
||||
res.Type, err = ParseParamType(typStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// We currently do not support following types:
|
||||
if res.Type == ArrayType || res.Type == MapType || res.Type == InteropInterfaceType || res.Type == VoidType {
|
||||
return nil, errors.Errorf("Unsupported contract parameter type: %s", res.Type)
|
||||
}
|
||||
buf.Reset()
|
||||
hadType = true
|
||||
continue
|
||||
|
|
|
@ -1,283 +1,204 @@
|
|||
package smartcontract
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/util"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestParseParamType(t *testing.T) {
|
||||
var inouts = []struct {
|
||||
in string
|
||||
out ParamType
|
||||
err bool
|
||||
}{{
|
||||
in: "signature",
|
||||
out: SignatureType,
|
||||
}, {
|
||||
in: "Signature",
|
||||
out: SignatureType,
|
||||
}, {
|
||||
in: "SiGnAtUrE",
|
||||
out: SignatureType,
|
||||
}, {
|
||||
in: "bool",
|
||||
out: BoolType,
|
||||
}, {
|
||||
in: "int",
|
||||
out: IntegerType,
|
||||
}, {
|
||||
in: "hash160",
|
||||
out: Hash160Type,
|
||||
}, {
|
||||
in: "hash256",
|
||||
out: Hash256Type,
|
||||
}, {
|
||||
in: "bytes",
|
||||
out: ByteArrayType,
|
||||
}, {
|
||||
in: "key",
|
||||
out: PublicKeyType,
|
||||
}, {
|
||||
in: "string",
|
||||
out: StringType,
|
||||
}, {
|
||||
in: "array",
|
||||
err: true,
|
||||
}, {
|
||||
in: "qwerty",
|
||||
err: true,
|
||||
}}
|
||||
for _, inout := range inouts {
|
||||
out, err := parseParamType(inout.in)
|
||||
if inout.err {
|
||||
assert.NotNil(t, err, "should error on '%s' input", inout.in)
|
||||
} else {
|
||||
assert.Nil(t, err, "shouldn't error on '%s' input", inout.in)
|
||||
assert.Equal(t, inout.out, out, "bad output for '%s' input", inout.in)
|
||||
var testCases = []struct {
|
||||
input string
|
||||
result Parameter
|
||||
}{
|
||||
{
|
||||
input: `{"type":"Integer","value":12345}`,
|
||||
result: Parameter{Type: IntegerType, Value: int64(12345)},
|
||||
},
|
||||
{
|
||||
input: `{"type":"Integer","value":"12345"}`,
|
||||
result: Parameter{Type: IntegerType, Value: int64(12345)},
|
||||
},
|
||||
{
|
||||
input: `{"type":"ByteArray","value":"010203"}`,
|
||||
result: Parameter{Type: ByteArrayType, Value: []byte{0x01, 0x02, 0x03}},
|
||||
},
|
||||
{
|
||||
input: `{"type":"String","value":"Some string"}`,
|
||||
result: Parameter{Type: StringType, Value: "Some string"},
|
||||
},
|
||||
{
|
||||
input: `{"type":"Array","value":[
|
||||
{"type": "String", "value": "str 1"},
|
||||
{"type": "Integer", "value": 2}]}`,
|
||||
result: Parameter{
|
||||
Type: ArrayType,
|
||||
Value: []Parameter{
|
||||
{Type: StringType, Value: "str 1"},
|
||||
{Type: IntegerType, Value: int64(2)},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: `{"type": "Hash160", "value": "0bcd2978634d961c24f5aea0802297ff128724d6"}`,
|
||||
result: Parameter{
|
||||
Type: Hash160Type,
|
||||
Value: util.Uint160{
|
||||
0x0b, 0xcd, 0x29, 0x78, 0x63, 0x4d, 0x96, 0x1c, 0x24, 0xf5,
|
||||
0xae, 0xa0, 0x80, 0x22, 0x97, 0xff, 0x12, 0x87, 0x24, 0xd6,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: `{"type": "Hash256", "value": "f037308fa0ab18155bccfc08485468c112409ea5064595699e98c545f245f32d"}`,
|
||||
result: Parameter{
|
||||
Type: Hash256Type,
|
||||
Value: util.Uint256{
|
||||
0x2d, 0xf3, 0x45, 0xf2, 0x45, 0xc5, 0x98, 0x9e,
|
||||
0x69, 0x95, 0x45, 0x06, 0xa5, 0x9e, 0x40, 0x12,
|
||||
0xc1, 0x68, 0x54, 0x48, 0x08, 0xfc, 0xcc, 0x5b,
|
||||
0x15, 0x18, 0xab, 0xa0, 0x8f, 0x30, 0x37, 0xf0,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var errorCases = []string{
|
||||
`{"type": "ByteArray","value":`, // incorrect JSON
|
||||
`{"type": "ByteArray","value":1}`, // incorrect Value
|
||||
`{"type": "ByteArray","value":"12zz"}`, // incorrect ByteArray value
|
||||
`{"type": "String","value":`, // incorrect JSON
|
||||
`{"type": "String","value":1}`, // incorrect Value
|
||||
`{"type": "Integer","value": "nn"}`, // incorrect Integer value
|
||||
`{"type": "Integer","value": []}`, // incorrect Integer value
|
||||
`{"type": "Array","value": 123}`, // incorrect Array value
|
||||
`{"type": "Hash160","value": "0bcd"}`, // incorrect Uint160 value
|
||||
`{"type": "Hash256","value": "0bcd"}`, // incorrect Uint256 value
|
||||
`{"type": "Stringg","value": ""}`, // incorrect type
|
||||
`{"type": {},"value": ""}`, // incorrect value
|
||||
|
||||
`{"type": "InteropInterface","value": ""}`, // ununmarshable type
|
||||
`{"type": "Map","value": ""}`, //unmarshable type
|
||||
}
|
||||
|
||||
func TestParam_UnmarshalJSON(t *testing.T) {
|
||||
var s Parameter
|
||||
for _, tc := range testCases {
|
||||
assert.NoError(t, json.Unmarshal([]byte(tc.input), &s))
|
||||
assert.Equal(t, s, tc.result)
|
||||
}
|
||||
|
||||
for _, input := range errorCases {
|
||||
assert.Error(t, json.Unmarshal([]byte(input), &s))
|
||||
}
|
||||
}
|
||||
|
||||
func TestInferParamType(t *testing.T) {
|
||||
var inouts = []struct {
|
||||
in string
|
||||
out ParamType
|
||||
}{{
|
||||
in: "42",
|
||||
out: IntegerType,
|
||||
}, {
|
||||
in: "-42",
|
||||
out: IntegerType,
|
||||
}, {
|
||||
in: "0",
|
||||
out: IntegerType,
|
||||
}, {
|
||||
in: "2e10",
|
||||
out: ByteArrayType,
|
||||
}, {
|
||||
in: "true",
|
||||
out: BoolType,
|
||||
}, {
|
||||
in: "false",
|
||||
out: BoolType,
|
||||
}, {
|
||||
in: "truee",
|
||||
out: StringType,
|
||||
}, {
|
||||
in: "AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y",
|
||||
out: Hash160Type,
|
||||
}, {
|
||||
in: "ZK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y",
|
||||
out: StringType,
|
||||
}, {
|
||||
in: "50befd26fdf6e4d957c11e078b24ebce6291456f",
|
||||
out: Hash160Type,
|
||||
}, {
|
||||
in: "03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c",
|
||||
out: PublicKeyType,
|
||||
}, {
|
||||
in: "30b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c",
|
||||
out: ByteArrayType,
|
||||
}, {
|
||||
in: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7",
|
||||
out: Hash256Type,
|
||||
}, {
|
||||
in: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7da",
|
||||
out: ByteArrayType,
|
||||
}, {
|
||||
in: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b",
|
||||
out: SignatureType,
|
||||
}, {
|
||||
in: "qwerty",
|
||||
out: StringType,
|
||||
}, {
|
||||
in: "ab",
|
||||
out: ByteArrayType,
|
||||
}, {
|
||||
in: "az",
|
||||
out: StringType,
|
||||
}, {
|
||||
in: "bad",
|
||||
out: StringType,
|
||||
}, {
|
||||
in: "фыва",
|
||||
out: StringType,
|
||||
}, {
|
||||
in: "dead",
|
||||
out: ByteArrayType,
|
||||
}}
|
||||
for _, inout := range inouts {
|
||||
out := inferParamType(inout.in)
|
||||
assert.Equal(t, inout.out, out, "bad output for '%s' input", inout.in)
|
||||
}
|
||||
var tryParseTestCases = []struct {
|
||||
input interface{}
|
||||
expected interface{}
|
||||
}{
|
||||
{
|
||||
input: []byte{
|
||||
0x0b, 0xcd, 0x29, 0x78, 0x63, 0x4d, 0x96, 0x1c, 0x24, 0xf5,
|
||||
0xae, 0xa0, 0x80, 0x22, 0x97, 0xff, 0x12, 0x87, 0x24, 0xd6,
|
||||
},
|
||||
expected: util.Uint160{
|
||||
0x0b, 0xcd, 0x29, 0x78, 0x63, 0x4d, 0x96, 0x1c, 0x24, 0xf5,
|
||||
0xae, 0xa0, 0x80, 0x22, 0x97, 0xff, 0x12, 0x87, 0x24, 0xd6,
|
||||
},
|
||||
},
|
||||
{
|
||||
input: []byte{
|
||||
0xf0, 0x37, 0x30, 0x8f, 0xa0, 0xab, 0x18, 0x15,
|
||||
0x5b, 0xcc, 0xfc, 0x08, 0x48, 0x54, 0x68, 0xc1,
|
||||
0x12, 0x40, 0x9e, 0xa5, 0x06, 0x45, 0x95, 0x69,
|
||||
0x9e, 0x98, 0xc5, 0x45, 0xf2, 0x45, 0xf3, 0x2d,
|
||||
},
|
||||
expected: util.Uint256{
|
||||
0x2d, 0xf3, 0x45, 0xf2, 0x45, 0xc5, 0x98, 0x9e,
|
||||
0x69, 0x95, 0x45, 0x06, 0xa5, 0x9e, 0x40, 0x12,
|
||||
0xc1, 0x68, 0x54, 0x48, 0x08, 0xfc, 0xcc, 0x5b,
|
||||
0x15, 0x18, 0xab, 0xa0, 0x8f, 0x30, 0x37, 0xf0,
|
||||
},
|
||||
},
|
||||
{
|
||||
input: []byte{0, 1, 2, 3, 4, 9, 8, 6},
|
||||
expected: []byte{0, 1, 2, 3, 4, 9, 8, 6},
|
||||
},
|
||||
{
|
||||
input: []byte{0x63, 0x78, 0x29, 0xcd, 0x0b},
|
||||
expected: int64(50686687331),
|
||||
},
|
||||
{
|
||||
input: []byte("this is a test string"),
|
||||
expected: "this is a test string",
|
||||
},
|
||||
}
|
||||
|
||||
func TestAdjustValToType(t *testing.T) {
|
||||
var inouts = []struct {
|
||||
typ ParamType
|
||||
val string
|
||||
out interface{}
|
||||
err bool
|
||||
}{{
|
||||
typ: SignatureType,
|
||||
val: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b",
|
||||
out: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b",
|
||||
}, {
|
||||
typ: SignatureType,
|
||||
val: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c",
|
||||
err: true,
|
||||
}, {
|
||||
typ: SignatureType,
|
||||
val: "qwerty",
|
||||
err: true,
|
||||
}, {
|
||||
typ: BoolType,
|
||||
val: "false",
|
||||
out: false,
|
||||
}, {
|
||||
typ: BoolType,
|
||||
val: "true",
|
||||
out: true,
|
||||
}, {
|
||||
typ: BoolType,
|
||||
val: "qwerty",
|
||||
err: true,
|
||||
}, {
|
||||
typ: BoolType,
|
||||
val: "42",
|
||||
err: true,
|
||||
}, {
|
||||
typ: BoolType,
|
||||
val: "0",
|
||||
err: true,
|
||||
}, {
|
||||
typ: IntegerType,
|
||||
val: "0",
|
||||
out: 0,
|
||||
}, {
|
||||
typ: IntegerType,
|
||||
val: "42",
|
||||
out: 42,
|
||||
}, {
|
||||
typ: IntegerType,
|
||||
val: "-42",
|
||||
out: -42,
|
||||
}, {
|
||||
typ: IntegerType,
|
||||
val: "q",
|
||||
err: true,
|
||||
}, {
|
||||
typ: Hash160Type,
|
||||
val: "AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y",
|
||||
out: "23ba2703c53263e8d6e522dc32203339dcd8eee9",
|
||||
}, {
|
||||
typ: Hash160Type,
|
||||
val: "50befd26fdf6e4d957c11e078b24ebce6291456f",
|
||||
out: "50befd26fdf6e4d957c11e078b24ebce6291456f",
|
||||
}, {
|
||||
typ: Hash160Type,
|
||||
val: "befd26fdf6e4d957c11e078b24ebce6291456f",
|
||||
err: true,
|
||||
}, {
|
||||
typ: Hash160Type,
|
||||
val: "q",
|
||||
err: true,
|
||||
}, {
|
||||
typ: Hash256Type,
|
||||
val: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7",
|
||||
out: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7",
|
||||
}, {
|
||||
typ: Hash256Type,
|
||||
val: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282d",
|
||||
err: true,
|
||||
}, {
|
||||
typ: Hash256Type,
|
||||
val: "q",
|
||||
err: true,
|
||||
}, {
|
||||
typ: ByteArrayType,
|
||||
val: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282d",
|
||||
out: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282d",
|
||||
}, {
|
||||
typ: ByteArrayType,
|
||||
val: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7",
|
||||
out: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7",
|
||||
}, {
|
||||
typ: ByteArrayType,
|
||||
val: "50befd26fdf6e4d957c11e078b24ebce6291456f",
|
||||
out: "50befd26fdf6e4d957c11e078b24ebce6291456f",
|
||||
}, {
|
||||
typ: ByteArrayType,
|
||||
val: "AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y",
|
||||
err: true,
|
||||
}, {
|
||||
typ: ByteArrayType,
|
||||
val: "q",
|
||||
err: true,
|
||||
}, {
|
||||
typ: ByteArrayType,
|
||||
val: "ab",
|
||||
out: "ab",
|
||||
}, {
|
||||
typ: PublicKeyType,
|
||||
val: "03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c",
|
||||
out: "03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c",
|
||||
}, {
|
||||
typ: PublicKeyType,
|
||||
val: "01b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c",
|
||||
err: true,
|
||||
}, {
|
||||
typ: PublicKeyType,
|
||||
val: "q",
|
||||
err: true,
|
||||
}, {
|
||||
typ: StringType,
|
||||
val: "q",
|
||||
out: "q",
|
||||
}, {
|
||||
typ: StringType,
|
||||
val: "dead",
|
||||
out: "dead",
|
||||
}, {
|
||||
typ: StringType,
|
||||
val: "йцукен",
|
||||
out: "йцукен",
|
||||
}, {
|
||||
typ: ArrayType,
|
||||
val: "",
|
||||
err: true,
|
||||
}}
|
||||
func TestParam_TryParse(t *testing.T) {
|
||||
for _, tc := range tryParseTestCases {
|
||||
t.Run(reflect.TypeOf(tc.expected).String(), func(t *testing.T) {
|
||||
input := Parameter{
|
||||
Type: ByteArrayType,
|
||||
Value: tc.input,
|
||||
}
|
||||
|
||||
for _, inout := range inouts {
|
||||
out, err := adjustValToType(inout.typ, inout.val)
|
||||
if inout.err {
|
||||
assert.NotNil(t, err, "should error on '%s/%s' input", inout.typ, inout.val)
|
||||
} else {
|
||||
assert.Nil(t, err, "shouldn't error on '%s/%s' input", inout.typ, inout.val)
|
||||
assert.Equal(t, inout.out, out, "bad output for '%s/%s' input", inout.typ, inout.val)
|
||||
val := reflect.New(reflect.TypeOf(tc.expected))
|
||||
assert.NoError(t, input.TryParse(val.Interface()))
|
||||
assert.Equal(t, tc.expected, val.Elem().Interface())
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("[]Uint160", func(t *testing.T) {
|
||||
exp1 := util.Uint160{1, 2, 3, 4, 5}
|
||||
exp2 := util.Uint160{9, 8, 7, 6, 5}
|
||||
|
||||
params := Params{
|
||||
{
|
||||
Type: ByteArrayType,
|
||||
Value: exp1.BytesBE(),
|
||||
},
|
||||
{
|
||||
Type: ByteArrayType,
|
||||
Value: exp2.BytesBE(),
|
||||
},
|
||||
}
|
||||
|
||||
var out1, out2 util.Uint160
|
||||
|
||||
assert.NoError(t, params.TryParseArray(&out1, &out2))
|
||||
assert.Equal(t, exp1, out1)
|
||||
assert.Equal(t, exp2, out2)
|
||||
})
|
||||
}
|
||||
|
||||
func TestParamType_String(t *testing.T) {
|
||||
types := []ParamType{
|
||||
SignatureType,
|
||||
BoolType,
|
||||
IntegerType,
|
||||
Hash160Type,
|
||||
Hash256Type,
|
||||
ByteArrayType,
|
||||
PublicKeyType,
|
||||
StringType,
|
||||
ArrayType,
|
||||
InteropInterfaceType,
|
||||
MapType,
|
||||
VoidType,
|
||||
}
|
||||
|
||||
for _, exp := range types {
|
||||
actual, err := ParseParamType(exp.String())
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, exp, actual)
|
||||
}
|
||||
|
||||
actual, err := ParseParamType(UnknownType.String())
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, UnknownType, actual)
|
||||
}
|
||||
|
||||
func TestNewParameterFromString(t *testing.T) {
|
||||
|
@ -330,6 +251,12 @@ func TestNewParameterFromString(t *testing.T) {
|
|||
}, {
|
||||
in: `bool:asdf`,
|
||||
err: true,
|
||||
}, {
|
||||
in: `InteropInterface:123`,
|
||||
err: true,
|
||||
}, {
|
||||
in: `Map:[]`,
|
||||
err: true,
|
||||
}}
|
||||
for _, inout := range inouts {
|
||||
out, err := NewParameterFromString(inout.in)
|
||||
|
|
269
pkg/smartcontract/param_type.go
Normal file
269
pkg/smartcontract/param_type.go
Normal file
|
@ -0,0 +1,269 @@
|
|||
package smartcontract
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
|
||||
"github.com/CityOfZion/neo-go/pkg/encoding/address"
|
||||
"github.com/CityOfZion/neo-go/pkg/io"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// ParamType represents the Type of the smart contract parameter.
|
||||
type ParamType int
|
||||
|
||||
// A list of supported smart contract parameter types.
|
||||
const (
|
||||
UnknownType ParamType = -1
|
||||
SignatureType ParamType = 0x00
|
||||
BoolType ParamType = 0x01
|
||||
IntegerType ParamType = 0x02
|
||||
Hash160Type ParamType = 0x03
|
||||
Hash256Type ParamType = 0x04
|
||||
ByteArrayType ParamType = 0x05
|
||||
PublicKeyType ParamType = 0x06
|
||||
StringType ParamType = 0x07
|
||||
ArrayType ParamType = 0x10
|
||||
MapType ParamType = 0x12
|
||||
InteropInterfaceType ParamType = 0xf0
|
||||
VoidType ParamType = 0xff
|
||||
)
|
||||
|
||||
// String implements the stringer interface.
|
||||
func (pt ParamType) String() string {
|
||||
switch pt {
|
||||
case SignatureType:
|
||||
return "Signature"
|
||||
case BoolType:
|
||||
return "Boolean"
|
||||
case IntegerType:
|
||||
return "Integer"
|
||||
case Hash160Type:
|
||||
return "Hash160"
|
||||
case Hash256Type:
|
||||
return "Hash256"
|
||||
case ByteArrayType:
|
||||
return "ByteArray"
|
||||
case PublicKeyType:
|
||||
return "PublicKey"
|
||||
case StringType:
|
||||
return "String"
|
||||
case ArrayType:
|
||||
return "Array"
|
||||
case MapType:
|
||||
return "Map"
|
||||
case InteropInterfaceType:
|
||||
return "InteropInterface"
|
||||
case VoidType:
|
||||
return "Void"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface.
|
||||
func (pt ParamType) MarshalJSON() ([]byte, error) {
|
||||
return []byte(`"` + pt.String() + `"`), nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaler interface.
|
||||
func (pt *ParamType) UnmarshalJSON(data []byte) error {
|
||||
var s string
|
||||
if err := json.Unmarshal(data, &s); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p, err := ParseParamType(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*pt = p
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalYAML implements the YAML Marshaler interface.
|
||||
func (pt *ParamType) MarshalYAML() (interface{}, error) {
|
||||
return pt.String(), nil
|
||||
}
|
||||
|
||||
// UnmarshalYAML implements the YAML Unmarshaler interface.
|
||||
func (pt *ParamType) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
var name string
|
||||
|
||||
err := unmarshal(&name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*pt, err = ParseParamType(name)
|
||||
return err
|
||||
}
|
||||
|
||||
// EncodeBinary implements io.Serializable interface.
|
||||
func (pt ParamType) EncodeBinary(w *io.BinWriter) {
|
||||
w.WriteB(byte(pt))
|
||||
}
|
||||
|
||||
// DecodeBinary implements io.Serializable interface.
|
||||
func (pt *ParamType) DecodeBinary(r *io.BinReader) {
|
||||
*pt = ParamType(r.ReadB())
|
||||
}
|
||||
|
||||
// ParseParamType is a user-friendly string to ParamType converter, it's
|
||||
// case-insensitive and makes the following conversions:
|
||||
// signature -> SignatureType
|
||||
// bool, boolean -> BoolType
|
||||
// int, integer -> IntegerType
|
||||
// hash160 -> Hash160Type
|
||||
// hash256 -> Hash256Type
|
||||
// bytes, bytearray -> ByteArrayType
|
||||
// key, publickey -> PublicKeyType
|
||||
// string -> StringType
|
||||
// array -> ArrayType
|
||||
// map -> MapType
|
||||
// interopinterface -> InteropInterfaceType
|
||||
// void -> VoidType
|
||||
// anything else generates an error.
|
||||
func ParseParamType(typ string) (ParamType, error) {
|
||||
switch strings.ToLower(typ) {
|
||||
case "signature":
|
||||
return SignatureType, nil
|
||||
case "bool", "boolean":
|
||||
return BoolType, nil
|
||||
case "int", "integer":
|
||||
return IntegerType, nil
|
||||
case "hash160":
|
||||
return Hash160Type, nil
|
||||
case "hash256":
|
||||
return Hash256Type, nil
|
||||
case "bytes", "bytearray":
|
||||
return ByteArrayType, nil
|
||||
case "key", "publickey":
|
||||
return PublicKeyType, nil
|
||||
case "string":
|
||||
return StringType, nil
|
||||
case "array":
|
||||
return ArrayType, nil
|
||||
case "map":
|
||||
return MapType, nil
|
||||
case "interopinterface":
|
||||
return InteropInterfaceType, nil
|
||||
case "void":
|
||||
return VoidType, nil
|
||||
default:
|
||||
return UnknownType, errors.Errorf("Unknown contract parameter type: %s", typ)
|
||||
}
|
||||
}
|
||||
|
||||
// adjustValToType is a value type-checker and converter.
|
||||
func adjustValToType(typ ParamType, val string) (interface{}, error) {
|
||||
switch typ {
|
||||
case SignatureType:
|
||||
b, err := hex.DecodeString(val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(b) != 64 {
|
||||
return nil, errors.New("not a signature")
|
||||
}
|
||||
return val, nil
|
||||
case BoolType:
|
||||
switch val {
|
||||
case "true":
|
||||
return true, nil
|
||||
case "false":
|
||||
return false, nil
|
||||
default:
|
||||
return nil, errors.New("invalid boolean value")
|
||||
}
|
||||
case IntegerType:
|
||||
return strconv.Atoi(val)
|
||||
case Hash160Type:
|
||||
u, err := address.StringToUint160(val)
|
||||
if err == nil {
|
||||
return hex.EncodeToString(u.BytesBE()), nil
|
||||
}
|
||||
b, err := hex.DecodeString(val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(b) != 20 {
|
||||
return nil, errors.New("not a hash160")
|
||||
}
|
||||
return val, nil
|
||||
case Hash256Type:
|
||||
b, err := hex.DecodeString(val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(b) != 32 {
|
||||
return nil, errors.New("not a hash256")
|
||||
}
|
||||
return val, nil
|
||||
case ByteArrayType:
|
||||
_, err := hex.DecodeString(val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return val, nil
|
||||
case PublicKeyType:
|
||||
_, err := keys.NewPublicKeyFromString(val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return val, nil
|
||||
case StringType:
|
||||
return val, nil
|
||||
default:
|
||||
return nil, errors.New("unsupported parameter type")
|
||||
}
|
||||
}
|
||||
|
||||
// inferParamType tries to infer the value type from its contents. It returns
|
||||
// IntegerType for anything that looks like decimal integer (can be converted
|
||||
// with strconv.Atoi), BoolType for true and false values, Hash160Type for
|
||||
// addresses and hex strings encoding 20 bytes long values, PublicKeyType for
|
||||
// valid hex-encoded public keys, Hash256Type for hex-encoded 32 bytes values,
|
||||
// SignatureType for hex-encoded 64 bytes values, ByteArrayType for any other
|
||||
// valid hex-encoded values and StringType for anything else.
|
||||
func inferParamType(val string) ParamType {
|
||||
var err error
|
||||
|
||||
_, err = strconv.Atoi(val)
|
||||
if err == nil {
|
||||
return IntegerType
|
||||
}
|
||||
|
||||
if val == "true" || val == "false" {
|
||||
return BoolType
|
||||
}
|
||||
|
||||
_, err = address.StringToUint160(val)
|
||||
if err == nil {
|
||||
return Hash160Type
|
||||
}
|
||||
|
||||
_, err = keys.NewPublicKeyFromString(val)
|
||||
if err == nil {
|
||||
return PublicKeyType
|
||||
}
|
||||
|
||||
unhexed, err := hex.DecodeString(val)
|
||||
if err == nil {
|
||||
switch len(unhexed) {
|
||||
case 20:
|
||||
return Hash160Type
|
||||
case 32:
|
||||
return Hash256Type
|
||||
case 64:
|
||||
return SignatureType
|
||||
default:
|
||||
return ByteArrayType
|
||||
}
|
||||
}
|
||||
// Anything can be a string.
|
||||
return StringType
|
||||
}
|
298
pkg/smartcontract/param_type_test.go
Normal file
298
pkg/smartcontract/param_type_test.go
Normal file
|
@ -0,0 +1,298 @@
|
|||
package smartcontract
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestParseParamType(t *testing.T) {
|
||||
var inouts = []struct {
|
||||
in string
|
||||
out ParamType
|
||||
err bool
|
||||
}{{
|
||||
in: "signature",
|
||||
out: SignatureType,
|
||||
}, {
|
||||
in: "Signature",
|
||||
out: SignatureType,
|
||||
}, {
|
||||
in: "SiGnAtUrE",
|
||||
out: SignatureType,
|
||||
}, {
|
||||
in: "bool",
|
||||
out: BoolType,
|
||||
}, {
|
||||
in: "int",
|
||||
out: IntegerType,
|
||||
}, {
|
||||
in: "hash160",
|
||||
out: Hash160Type,
|
||||
}, {
|
||||
in: "hash256",
|
||||
out: Hash256Type,
|
||||
}, {
|
||||
in: "bytes",
|
||||
out: ByteArrayType,
|
||||
}, {
|
||||
in: "key",
|
||||
out: PublicKeyType,
|
||||
}, {
|
||||
in: "string",
|
||||
out: StringType,
|
||||
}, {
|
||||
in: "array",
|
||||
out: ArrayType,
|
||||
}, {
|
||||
in: "map",
|
||||
out: MapType,
|
||||
}, {
|
||||
in: "interopinterface",
|
||||
out: InteropInterfaceType,
|
||||
}, {
|
||||
in: "void",
|
||||
out: VoidType,
|
||||
}, {
|
||||
in: "qwerty",
|
||||
err: true,
|
||||
}}
|
||||
for _, inout := range inouts {
|
||||
out, err := ParseParamType(inout.in)
|
||||
if inout.err {
|
||||
assert.NotNil(t, err, "should error on '%s' input", inout.in)
|
||||
} else {
|
||||
assert.Nil(t, err, "shouldn't error on '%s' input", inout.in)
|
||||
assert.Equal(t, inout.out, out, "bad output for '%s' input", inout.in)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInferParamType(t *testing.T) {
|
||||
var inouts = []struct {
|
||||
in string
|
||||
out ParamType
|
||||
}{{
|
||||
in: "42",
|
||||
out: IntegerType,
|
||||
}, {
|
||||
in: "-42",
|
||||
out: IntegerType,
|
||||
}, {
|
||||
in: "0",
|
||||
out: IntegerType,
|
||||
}, {
|
||||
in: "2e10",
|
||||
out: ByteArrayType,
|
||||
}, {
|
||||
in: "true",
|
||||
out: BoolType,
|
||||
}, {
|
||||
in: "false",
|
||||
out: BoolType,
|
||||
}, {
|
||||
in: "truee",
|
||||
out: StringType,
|
||||
}, {
|
||||
in: "AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y",
|
||||
out: Hash160Type,
|
||||
}, {
|
||||
in: "ZK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y",
|
||||
out: StringType,
|
||||
}, {
|
||||
in: "50befd26fdf6e4d957c11e078b24ebce6291456f",
|
||||
out: Hash160Type,
|
||||
}, {
|
||||
in: "03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c",
|
||||
out: PublicKeyType,
|
||||
}, {
|
||||
in: "30b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c",
|
||||
out: ByteArrayType,
|
||||
}, {
|
||||
in: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7",
|
||||
out: Hash256Type,
|
||||
}, {
|
||||
in: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7da",
|
||||
out: ByteArrayType,
|
||||
}, {
|
||||
in: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b",
|
||||
out: SignatureType,
|
||||
}, {
|
||||
in: "qwerty",
|
||||
out: StringType,
|
||||
}, {
|
||||
in: "ab",
|
||||
out: ByteArrayType,
|
||||
}, {
|
||||
in: "az",
|
||||
out: StringType,
|
||||
}, {
|
||||
in: "bad",
|
||||
out: StringType,
|
||||
}, {
|
||||
in: "фыва",
|
||||
out: StringType,
|
||||
}, {
|
||||
in: "dead",
|
||||
out: ByteArrayType,
|
||||
}}
|
||||
for _, inout := range inouts {
|
||||
out := inferParamType(inout.in)
|
||||
assert.Equal(t, inout.out, out, "bad output for '%s' input", inout.in)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdjustValToType(t *testing.T) {
|
||||
var inouts = []struct {
|
||||
typ ParamType
|
||||
val string
|
||||
out interface{}
|
||||
err bool
|
||||
}{{
|
||||
typ: SignatureType,
|
||||
val: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b",
|
||||
out: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b",
|
||||
}, {
|
||||
typ: SignatureType,
|
||||
val: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c",
|
||||
err: true,
|
||||
}, {
|
||||
typ: SignatureType,
|
||||
val: "qwerty",
|
||||
err: true,
|
||||
}, {
|
||||
typ: BoolType,
|
||||
val: "false",
|
||||
out: false,
|
||||
}, {
|
||||
typ: BoolType,
|
||||
val: "true",
|
||||
out: true,
|
||||
}, {
|
||||
typ: BoolType,
|
||||
val: "qwerty",
|
||||
err: true,
|
||||
}, {
|
||||
typ: BoolType,
|
||||
val: "42",
|
||||
err: true,
|
||||
}, {
|
||||
typ: BoolType,
|
||||
val: "0",
|
||||
err: true,
|
||||
}, {
|
||||
typ: IntegerType,
|
||||
val: "0",
|
||||
out: 0,
|
||||
}, {
|
||||
typ: IntegerType,
|
||||
val: "42",
|
||||
out: 42,
|
||||
}, {
|
||||
typ: IntegerType,
|
||||
val: "-42",
|
||||
out: -42,
|
||||
}, {
|
||||
typ: IntegerType,
|
||||
val: "q",
|
||||
err: true,
|
||||
}, {
|
||||
typ: Hash160Type,
|
||||
val: "AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y",
|
||||
out: "23ba2703c53263e8d6e522dc32203339dcd8eee9",
|
||||
}, {
|
||||
typ: Hash160Type,
|
||||
val: "50befd26fdf6e4d957c11e078b24ebce6291456f",
|
||||
out: "50befd26fdf6e4d957c11e078b24ebce6291456f",
|
||||
}, {
|
||||
typ: Hash160Type,
|
||||
val: "befd26fdf6e4d957c11e078b24ebce6291456f",
|
||||
err: true,
|
||||
}, {
|
||||
typ: Hash160Type,
|
||||
val: "q",
|
||||
err: true,
|
||||
}, {
|
||||
typ: Hash256Type,
|
||||
val: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7",
|
||||
out: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7",
|
||||
}, {
|
||||
typ: Hash256Type,
|
||||
val: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282d",
|
||||
err: true,
|
||||
}, {
|
||||
typ: Hash256Type,
|
||||
val: "q",
|
||||
err: true,
|
||||
}, {
|
||||
typ: ByteArrayType,
|
||||
val: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282d",
|
||||
out: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282d",
|
||||
}, {
|
||||
typ: ByteArrayType,
|
||||
val: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7",
|
||||
out: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7",
|
||||
}, {
|
||||
typ: ByteArrayType,
|
||||
val: "50befd26fdf6e4d957c11e078b24ebce6291456f",
|
||||
out: "50befd26fdf6e4d957c11e078b24ebce6291456f",
|
||||
}, {
|
||||
typ: ByteArrayType,
|
||||
val: "AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y",
|
||||
err: true,
|
||||
}, {
|
||||
typ: ByteArrayType,
|
||||
val: "q",
|
||||
err: true,
|
||||
}, {
|
||||
typ: ByteArrayType,
|
||||
val: "ab",
|
||||
out: "ab",
|
||||
}, {
|
||||
typ: PublicKeyType,
|
||||
val: "03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c",
|
||||
out: "03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c",
|
||||
}, {
|
||||
typ: PublicKeyType,
|
||||
val: "01b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c",
|
||||
err: true,
|
||||
}, {
|
||||
typ: PublicKeyType,
|
||||
val: "q",
|
||||
err: true,
|
||||
}, {
|
||||
typ: StringType,
|
||||
val: "q",
|
||||
out: "q",
|
||||
}, {
|
||||
typ: StringType,
|
||||
val: "dead",
|
||||
out: "dead",
|
||||
}, {
|
||||
typ: StringType,
|
||||
val: "йцукен",
|
||||
out: "йцукен",
|
||||
}, {
|
||||
typ: ArrayType,
|
||||
val: "",
|
||||
err: true,
|
||||
}, {
|
||||
typ: MapType,
|
||||
val: "[]",
|
||||
err: true,
|
||||
}, {
|
||||
typ: InteropInterfaceType,
|
||||
val: "",
|
||||
err: true,
|
||||
}}
|
||||
|
||||
for _, inout := range inouts {
|
||||
out, err := adjustValToType(inout.typ, inout.val)
|
||||
if inout.err {
|
||||
assert.NotNil(t, err, "should error on '%s/%s' input", inout.typ, inout.val)
|
||||
} else {
|
||||
assert.Nil(t, err, "shouldn't error on '%s/%s' input", inout.typ, inout.val)
|
||||
assert.Equal(t, inout.out, out, "bad output for '%s/%s' input", inout.typ, inout.val)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue