smartcontract: add user-facing testinvokefunction command
With a very special syntax.
This commit is contained in:
parent
05f3329ec0
commit
e63b25d5ad
3 changed files with 670 additions and 0 deletions
|
@ -14,6 +14,7 @@ import (
|
||||||
"github.com/CityOfZion/neo-go/pkg/crypto/hash"
|
"github.com/CityOfZion/neo-go/pkg/crypto/hash"
|
||||||
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
|
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
|
||||||
"github.com/CityOfZion/neo-go/pkg/rpc"
|
"github.com/CityOfZion/neo-go/pkg/rpc"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/smartcontract"
|
||||||
"github.com/CityOfZion/neo-go/pkg/util"
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
"github.com/CityOfZion/neo-go/pkg/vm"
|
"github.com/CityOfZion/neo-go/pkg/vm"
|
||||||
"github.com/CityOfZion/neo-go/pkg/vm/compiler"
|
"github.com/CityOfZion/neo-go/pkg/vm/compiler"
|
||||||
|
@ -27,6 +28,7 @@ var (
|
||||||
errNoInput = errors.New("no input file was found, specify an input file with the '--in or -i' flag")
|
errNoInput = errors.New("no input file was found, specify an input file with the '--in or -i' flag")
|
||||||
errNoConfFile = errors.New("no config file was found, specify a config file with the '--config' or '-c' flag")
|
errNoConfFile = errors.New("no config file was found, specify a config file with the '--config' or '-c' flag")
|
||||||
errNoWIF = errors.New("no WIF parameter found, specify it with the '--wif or -w' flag")
|
errNoWIF = errors.New("no WIF parameter found, specify it with the '--wif or -w' flag")
|
||||||
|
errNoScriptHash = errors.New("no smart contract hash was provided, specify one as the first argument")
|
||||||
errNoSmartContractName = errors.New("no name was provided, specify the '--name or -n' flag")
|
errNoSmartContractName = errors.New("no name was provided, specify the '--name or -n' flag")
|
||||||
errFileExist = errors.New("A file with given smart-contract name already exists")
|
errFileExist = errors.New("A file with given smart-contract name already exists")
|
||||||
)
|
)
|
||||||
|
@ -95,6 +97,80 @@ func NewCommands() []cli.Command {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "testinvokefunction",
|
||||||
|
Usage: "invoke deployed contract on the blockchain (test mode)",
|
||||||
|
UsageText: "neo-go contract testinvokefunction -e endpoint scripthash [method] [arguments...]",
|
||||||
|
Description: `Executes given (as a script hash) deployed script with the given method and
|
||||||
|
arguments. If no method is given "" is passed to the script, if no arguments
|
||||||
|
are given, an empty array is passed. All of the given arguments are
|
||||||
|
encapsulated into array before invoking the script. The script thus should
|
||||||
|
follow the regular convention of smart contract arguments (method string and
|
||||||
|
an array of other arguments).
|
||||||
|
|
||||||
|
Arguments always do have regular Neo smart contract parameter types, either
|
||||||
|
specified explicitly or being inferred from the value. To specify the type
|
||||||
|
manually use "type:value" syntax where the type is one of the following:
|
||||||
|
'signature', 'bool', 'int', 'hash160', 'hash256', 'bytes', 'key' or 'string'.
|
||||||
|
Array types are not currently supported.
|
||||||
|
|
||||||
|
Given values are type-checked against given types with the following
|
||||||
|
restrictions applied:
|
||||||
|
* 'signature' type values should be hex-encoded and have a (decoded)
|
||||||
|
length of 64 bytes.
|
||||||
|
* 'bool' type values are 'true' and 'false'.
|
||||||
|
* 'int' values are decimal integers that can be successfully converted
|
||||||
|
from the string.
|
||||||
|
* 'hash160' values are Neo addresses and hex-encoded 20-bytes long (after
|
||||||
|
decoding) strings.
|
||||||
|
* 'hash256' type values should be hex-encoded and have a (decoded)
|
||||||
|
length of 32 bytes.
|
||||||
|
* 'bytes' type values are any hex-encoded things.
|
||||||
|
* 'key' type values are hex-encoded marshalled public keys.
|
||||||
|
* 'string' type values are any valid UTF-8 strings. In the value's part of
|
||||||
|
the string the colon looses it's special meaning as a separator between
|
||||||
|
type and value and is taken literally.
|
||||||
|
|
||||||
|
If no type is explicitly specified, it is inferred from the value using the
|
||||||
|
following logic:
|
||||||
|
- anything that can be interpreted as a decimal integer gets
|
||||||
|
an 'int' type
|
||||||
|
- 'true' and 'false' strings get 'bool' type
|
||||||
|
- valid Neo addresses and 20 bytes long hex-encoded strings get 'hash160'
|
||||||
|
type
|
||||||
|
- valid hex-encoded public keys get 'key' type
|
||||||
|
- 32 bytes long hex-encoded values get 'hash256' type
|
||||||
|
- 64 bytes long hex-encoded values get 'signature' type
|
||||||
|
- any other valid hex-encoded values get 'bytes' type
|
||||||
|
- anything else is a 'string'
|
||||||
|
|
||||||
|
Backslash character is used as an escape character and allows to use colon in
|
||||||
|
an implicitly typed string. For any other characters it has no special
|
||||||
|
meaning, to get a literal backslash in the string use the '\\' sequence.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
* 'int:42' is an integer with a value of 42
|
||||||
|
* '42' is an integer with a value of 42
|
||||||
|
* 'bad' is a string with a value of 'bad'
|
||||||
|
* 'dead' is a byte array with a value of 'dead'
|
||||||
|
* 'string:dead' is a string with a value of 'dead'
|
||||||
|
* 'AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y' is a hash160 with a value
|
||||||
|
of '23ba2703c53263e8d6e522dc32203339dcd8eee9'
|
||||||
|
* '\4\2' is an integer with a value of 42
|
||||||
|
* '\\4\2' is a string with a value of '\42'
|
||||||
|
* 'string:string' is a string with a value of 'string'
|
||||||
|
* 'string\:string' is a string with a value of 'string:string'
|
||||||
|
* '03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c' is a
|
||||||
|
key with a value of '03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c'
|
||||||
|
`,
|
||||||
|
Action: testInvokeFunction,
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "endpoint, e",
|
||||||
|
Usage: "RPC endpoint address (like 'http://seed4.ngd.network:20332')",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Name: "testinvokescript",
|
Name: "testinvokescript",
|
||||||
Usage: "Invoke compiled AVM code on the blockchain (test mode, not creating a transaction for it)",
|
Usage: "Invoke compiled AVM code on the blockchain (test mode, not creating a transaction for it)",
|
||||||
|
@ -211,6 +287,52 @@ func contractCompile(ctx *cli.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testInvokeFunction(ctx *cli.Context) error {
|
||||||
|
endpoint := ctx.String("endpoint")
|
||||||
|
if len(endpoint) == 0 {
|
||||||
|
return cli.NewExitError(errNoEndpoint, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
args := ctx.Args()
|
||||||
|
if !args.Present() {
|
||||||
|
return cli.NewExitError(errNoScriptHash, 1)
|
||||||
|
}
|
||||||
|
script := args[0]
|
||||||
|
operation := ""
|
||||||
|
if len(args) > 1 {
|
||||||
|
operation = args[1]
|
||||||
|
}
|
||||||
|
params := make([]smartcontract.Parameter, 0)
|
||||||
|
if len(args) > 2 {
|
||||||
|
for k, s := range args[2:] {
|
||||||
|
param, err := smartcontract.NewParameterFromString(s)
|
||||||
|
if err != nil {
|
||||||
|
return cli.NewExitError(fmt.Errorf("failed to parse argument #%d: %v", k+2+1, err), 1)
|
||||||
|
}
|
||||||
|
params = append(params, *param)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := rpc.NewClient(context.TODO(), endpoint, rpc.ClientOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return cli.NewExitError(err, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := client.InvokeFunction(script, operation, params)
|
||||||
|
if err != nil {
|
||||||
|
return cli.NewExitError(err, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := json.MarshalIndent(resp.Result, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return cli.NewExitError(err, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(string(b))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func testInvokeScript(ctx *cli.Context) error {
|
func testInvokeScript(ctx *cli.Context) error {
|
||||||
src := ctx.String("in")
|
src := ctx.String("in")
|
||||||
if len(src) == 0 {
|
if len(src) == 0 {
|
||||||
|
|
|
@ -1,6 +1,14 @@
|
||||||
package smartcontract
|
package smartcontract
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/crypto"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
|
||||||
"github.com/CityOfZion/neo-go/pkg/io"
|
"github.com/CityOfZion/neo-go/pkg/io"
|
||||||
"github.com/CityOfZion/neo-go/pkg/util"
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
)
|
)
|
||||||
|
@ -89,6 +97,203 @@ 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":
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 := crypto.Uint160DecodeAddress(val)
|
||||||
|
if err == nil {
|
||||||
|
return hex.EncodeToString(u.Bytes()), 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 = crypto.Uint160DecodeAddress(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
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewParameterFromString returns a new Parameter initialized from the given
|
||||||
|
// string in neo-go-specific format. It is intended to be used in user-facing
|
||||||
|
// interfaces and has some heuristics in it to simplify parameter passing. Exact
|
||||||
|
// syntax is documented in the cli documentation.
|
||||||
|
func NewParameterFromString(in string) (*Parameter, error) {
|
||||||
|
var (
|
||||||
|
char rune
|
||||||
|
val string
|
||||||
|
err error
|
||||||
|
r *strings.Reader
|
||||||
|
buf strings.Builder
|
||||||
|
escaped bool
|
||||||
|
hadType bool
|
||||||
|
res = &Parameter{}
|
||||||
|
)
|
||||||
|
r = strings.NewReader(in)
|
||||||
|
for char, _, err = r.ReadRune(); err == nil && char != utf8.RuneError; char, _, err = r.ReadRune() {
|
||||||
|
if char == '\\' && !escaped {
|
||||||
|
escaped = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if char == ':' && !escaped && !hadType {
|
||||||
|
typStr := buf.String()
|
||||||
|
res.Type, err = parseParamType(typStr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
buf.Reset()
|
||||||
|
hadType = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
escaped = false
|
||||||
|
// We don't care about length and it never fails.
|
||||||
|
_, _ = buf.WriteRune(char)
|
||||||
|
}
|
||||||
|
if char == utf8.RuneError {
|
||||||
|
return nil, errors.New("bad UTF-8 string")
|
||||||
|
}
|
||||||
|
// The only other error `ReadRune` returns is io.EOF, which is fine and
|
||||||
|
// expected, so we don't check err here.
|
||||||
|
|
||||||
|
val = buf.String()
|
||||||
|
if !hadType {
|
||||||
|
res.Type = inferParamType(val)
|
||||||
|
}
|
||||||
|
res.Value, err = adjustValToType(res.Type, val)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
// ContextItem represents a transaction context item.
|
// ContextItem represents a transaction context item.
|
||||||
type ContextItem struct {
|
type ContextItem struct {
|
||||||
Script util.Uint160
|
Script util.Uint160
|
||||||
|
|
343
pkg/smartcontract/param_context_test.go
Normal file
343
pkg/smartcontract/param_context_test.go
Normal file
|
@ -0,0 +1,343 @@
|
||||||
|
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",
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
}}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewParameterFromString(t *testing.T) {
|
||||||
|
var inouts = []struct {
|
||||||
|
in string
|
||||||
|
out Parameter
|
||||||
|
err bool
|
||||||
|
}{{
|
||||||
|
in: "qwerty",
|
||||||
|
out: Parameter{StringType, "qwerty"},
|
||||||
|
}, {
|
||||||
|
in: "42",
|
||||||
|
out: Parameter{IntegerType, 42},
|
||||||
|
}, {
|
||||||
|
in: "Hello, 世界",
|
||||||
|
out: Parameter{StringType, "Hello, 世界"},
|
||||||
|
}, {
|
||||||
|
in: `\4\2`,
|
||||||
|
out: Parameter{IntegerType, 42},
|
||||||
|
}, {
|
||||||
|
in: `\\4\2`,
|
||||||
|
out: Parameter{StringType, `\42`},
|
||||||
|
}, {
|
||||||
|
in: `\\\4\2`,
|
||||||
|
out: Parameter{StringType, `\42`},
|
||||||
|
}, {
|
||||||
|
in: "int:42",
|
||||||
|
out: Parameter{IntegerType, 42},
|
||||||
|
}, {
|
||||||
|
in: "true",
|
||||||
|
out: Parameter{BoolType, true},
|
||||||
|
}, {
|
||||||
|
in: "string:true",
|
||||||
|
out: Parameter{StringType, "true"},
|
||||||
|
}, {
|
||||||
|
in: "\xfe\xff",
|
||||||
|
err: true,
|
||||||
|
}, {
|
||||||
|
in: `string\:true`,
|
||||||
|
out: Parameter{StringType, "string:true"},
|
||||||
|
}, {
|
||||||
|
in: "string:true:true",
|
||||||
|
out: Parameter{StringType, "true:true"},
|
||||||
|
}, {
|
||||||
|
in: `string\\:true`,
|
||||||
|
err: true,
|
||||||
|
}, {
|
||||||
|
in: `qwerty:asdf`,
|
||||||
|
err: true,
|
||||||
|
}, {
|
||||||
|
in: `bool:asdf`,
|
||||||
|
err: true,
|
||||||
|
}}
|
||||||
|
for _, inout := range inouts {
|
||||||
|
out, err := NewParameterFromString(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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue