forked from TrueCloudLab/neoneo-go
commit
1b5da8401a
15 changed files with 1332 additions and 36 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")
|
||||||
)
|
)
|
||||||
|
@ -96,9 +98,83 @@ func NewCommands() []cli.Command {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "testinvoke",
|
Name: "testinvokefunction",
|
||||||
Usage: "Test an invocation of a smart contract on the blockchain",
|
Usage: "invoke deployed contract on the blockchain (test mode)",
|
||||||
Action: testInvoke,
|
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",
|
||||||
|
Usage: "Invoke compiled AVM code on the blockchain (test mode, not creating a transaction for it)",
|
||||||
|
Action: testInvokeScript,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "endpoint, e",
|
Name: "endpoint, e",
|
||||||
|
@ -211,7 +287,53 @@ func contractCompile(ctx *cli.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func testInvoke(ctx *cli.Context) error {
|
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 {
|
||||||
src := ctx.String("in")
|
src := ctx.String("in")
|
||||||
if len(src) == 0 {
|
if len(src) == 0 {
|
||||||
return cli.NewExitError(errNoInput, 1)
|
return cli.NewExitError(errNoInput, 1)
|
||||||
|
|
12
docs/rpc.md
12
docs/rpc.md
|
@ -55,12 +55,22 @@ which would yield the response:
|
||||||
| `getunspents` | Yes |
|
| `getunspents` | Yes |
|
||||||
| `getversion` | Yes |
|
| `getversion` | Yes |
|
||||||
| `invoke` | No (#346) |
|
| `invoke` | No (#346) |
|
||||||
| `invokefunction` | No (#347) |
|
| `invokefunction` | Yes |
|
||||||
| `invokescript` | Yes |
|
| `invokescript` | Yes |
|
||||||
| `sendrawtransaction` | Yes |
|
| `sendrawtransaction` | Yes |
|
||||||
| `submitblock` | No (#344) |
|
| `submitblock` | No (#344) |
|
||||||
| `validateaddress` | Yes |
|
| `validateaddress` | Yes |
|
||||||
|
|
||||||
|
#### Implementation notices
|
||||||
|
|
||||||
|
##### `invokefunction`
|
||||||
|
|
||||||
|
neo-go's implementation of `invokefunction` does not return `tx` field in the
|
||||||
|
answer because that requires signing the transaction with some key in the
|
||||||
|
server which doesn't fit the model of our node-client interactions. Lacking
|
||||||
|
this signature the transaction is almost useless, so there is no point in
|
||||||
|
returning it.
|
||||||
|
|
||||||
## Reference
|
## Reference
|
||||||
|
|
||||||
* [JSON-RPC 2.0 Specification](http://www.jsonrpc.org/specification)
|
* [JSON-RPC 2.0 Specification](http://www.jsonrpc.org/specification)
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
package rpc
|
package rpc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/crypto"
|
||||||
"github.com/CityOfZion/neo-go/pkg/util"
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
@ -19,6 +21,12 @@ type (
|
||||||
}
|
}
|
||||||
|
|
||||||
paramType int
|
paramType int
|
||||||
|
// FuncParam represents a function argument parameter used in the
|
||||||
|
// invokefunction RPC method.
|
||||||
|
FuncParam struct {
|
||||||
|
Type StackParamType `json:"type"`
|
||||||
|
Value Param `json:"value"`
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -26,6 +34,7 @@ const (
|
||||||
stringT
|
stringT
|
||||||
numberT
|
numberT
|
||||||
arrayT
|
arrayT
|
||||||
|
funcParamT
|
||||||
)
|
)
|
||||||
|
|
||||||
func (p Param) String() string {
|
func (p Param) String() string {
|
||||||
|
@ -33,27 +42,82 @@ func (p Param) String() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetString returns string value of the parameter.
|
// GetString returns string value of the parameter.
|
||||||
func (p Param) GetString() string { return p.Value.(string) }
|
func (p Param) GetString() (string, error) {
|
||||||
|
str, ok := p.Value.(string)
|
||||||
|
if !ok {
|
||||||
|
return "", errors.New("not a string")
|
||||||
|
}
|
||||||
|
return str, nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetInt returns int value of te parameter.
|
// GetInt returns int value of te parameter.
|
||||||
func (p Param) GetInt() int { return p.Value.(int) }
|
func (p Param) GetInt() (int, error) {
|
||||||
|
i, ok := p.Value.(int)
|
||||||
|
if !ok {
|
||||||
|
return 0, errors.New("not an integer")
|
||||||
|
}
|
||||||
|
return i, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetArray returns a slice of Params stored in the parameter.
|
||||||
|
func (p Param) GetArray() ([]Param, error) {
|
||||||
|
a, ok := p.Value.([]Param)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("not an array")
|
||||||
|
}
|
||||||
|
return a, nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetUint256 returns Uint256 value of the parameter.
|
// GetUint256 returns Uint256 value of the parameter.
|
||||||
func (p Param) GetUint256() (util.Uint256, error) {
|
func (p Param) GetUint256() (util.Uint256, error) {
|
||||||
s, ok := p.Value.(string)
|
s, err := p.GetString()
|
||||||
if !ok {
|
if err != nil {
|
||||||
return util.Uint256{}, errors.New("must be a string")
|
return util.Uint256{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return util.Uint256DecodeReverseString(s)
|
return util.Uint256DecodeReverseString(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetUint160FromHex returns Uint160 value of the parameter encoded in hex.
|
||||||
|
func (p Param) GetUint160FromHex() (util.Uint160, error) {
|
||||||
|
s, err := p.GetString()
|
||||||
|
if err != nil {
|
||||||
|
return util.Uint160{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
scriptHashLE, err := util.Uint160DecodeString(s)
|
||||||
|
if err != nil {
|
||||||
|
return util.Uint160{}, err
|
||||||
|
}
|
||||||
|
return util.Uint160DecodeBytes(scriptHashLE.BytesReverse())
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUint160FromAddress returns Uint160 value of the parameter that was
|
||||||
|
// supplied as an address.
|
||||||
|
func (p Param) GetUint160FromAddress() (util.Uint160, error) {
|
||||||
|
s, err := p.GetString()
|
||||||
|
if err != nil {
|
||||||
|
return util.Uint160{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return crypto.Uint160DecodeAddress(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFuncParam returns current parameter as a function call parameter.
|
||||||
|
func (p Param) GetFuncParam() (FuncParam, error) {
|
||||||
|
fp, ok := p.Value.(FuncParam)
|
||||||
|
if !ok {
|
||||||
|
return FuncParam{}, errors.New("not a function parameter")
|
||||||
|
}
|
||||||
|
return fp, nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetBytesHex returns []byte value of the parameter if
|
// GetBytesHex returns []byte value of the parameter if
|
||||||
// it is a hex-encoded string.
|
// it is a hex-encoded string.
|
||||||
func (p Param) GetBytesHex() ([]byte, error) {
|
func (p Param) GetBytesHex() ([]byte, error) {
|
||||||
s, ok := p.Value.(string)
|
s, err := p.GetString()
|
||||||
if !ok {
|
if err != nil {
|
||||||
return nil, errors.New("must be a string")
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return hex.DecodeString(s)
|
return hex.DecodeString(s)
|
||||||
|
@ -77,6 +141,17 @@ func (p *Param) UnmarshalJSON(data []byte) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
r := bytes.NewReader(data)
|
||||||
|
jd := json.NewDecoder(r)
|
||||||
|
jd.DisallowUnknownFields()
|
||||||
|
var fp FuncParam
|
||||||
|
if err := jd.Decode(&fp); err == nil {
|
||||||
|
p.Type = funcParamT
|
||||||
|
p.Value = fp
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
var ps []Param
|
var ps []Param
|
||||||
if err := json.Unmarshal(data, &ps); err == nil {
|
if err := json.Unmarshal(data, &ps); err == nil {
|
||||||
p.Type = arrayT
|
p.Type = arrayT
|
||||||
|
|
|
@ -1,14 +1,18 @@
|
||||||
package rpc
|
package rpc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/crypto"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestParam_UnmarshalJSON(t *testing.T) {
|
func TestParam_UnmarshalJSON(t *testing.T) {
|
||||||
msg := `["str1", 123, ["str2", 3]]`
|
msg := `["str1", 123, ["str2", 3], [{"type": "String", "value": "jajaja"}]]`
|
||||||
expected := Params{
|
expected := Params{
|
||||||
{
|
{
|
||||||
Type: stringT,
|
Type: stringT,
|
||||||
|
@ -31,6 +35,21 @@ func TestParam_UnmarshalJSON(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Type: arrayT,
|
||||||
|
Value: []Param{
|
||||||
|
{
|
||||||
|
Type: funcParamT,
|
||||||
|
Value: FuncParam{
|
||||||
|
Type: String,
|
||||||
|
Value: Param{
|
||||||
|
Type: stringT,
|
||||||
|
Value: "jajaja",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var ps Params
|
var ps Params
|
||||||
|
@ -40,3 +59,126 @@ func TestParam_UnmarshalJSON(t *testing.T) {
|
||||||
msg = `[{"2": 3}]`
|
msg = `[{"2": 3}]`
|
||||||
require.Error(t, json.Unmarshal([]byte(msg), &ps))
|
require.Error(t, json.Unmarshal([]byte(msg), &ps))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParamGetString(t *testing.T) {
|
||||||
|
p := Param{stringT, "jajaja"}
|
||||||
|
str, err := p.GetString()
|
||||||
|
assert.Equal(t, "jajaja", str)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
p = Param{stringT, int(100500)}
|
||||||
|
_, err = p.GetString()
|
||||||
|
require.NotNil(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParamGetInt(t *testing.T) {
|
||||||
|
p := Param{numberT, int(100500)}
|
||||||
|
i, err := p.GetInt()
|
||||||
|
assert.Equal(t, 100500, i)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
p = Param{numberT, "jajaja"}
|
||||||
|
_, err = p.GetInt()
|
||||||
|
require.NotNil(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParamGetArray(t *testing.T) {
|
||||||
|
p := Param{arrayT, []Param{{numberT, 42}}}
|
||||||
|
a, err := p.GetArray()
|
||||||
|
assert.Equal(t, []Param{{numberT, 42}}, a)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
p = Param{arrayT, 42}
|
||||||
|
_, err = p.GetArray()
|
||||||
|
require.NotNil(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParamGetUint256(t *testing.T) {
|
||||||
|
gas := "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"
|
||||||
|
u256, _ := util.Uint256DecodeReverseString(gas)
|
||||||
|
p := Param{stringT, gas}
|
||||||
|
u, err := p.GetUint256()
|
||||||
|
assert.Equal(t, u256, u)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
p = Param{stringT, 42}
|
||||||
|
_, err = p.GetUint256()
|
||||||
|
require.NotNil(t, err)
|
||||||
|
|
||||||
|
p = Param{stringT, "qq2c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"}
|
||||||
|
_, err = p.GetUint256()
|
||||||
|
require.NotNil(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParamGetUint160FromHex(t *testing.T) {
|
||||||
|
in := "50befd26fdf6e4d957c11e078b24ebce6291456f"
|
||||||
|
u160, _ := util.Uint160DecodeString(in)
|
||||||
|
u160, _ = util.Uint160DecodeBytes(util.ArrayReverse(u160[:]))
|
||||||
|
p := Param{stringT, in}
|
||||||
|
u, err := p.GetUint160FromHex()
|
||||||
|
assert.Equal(t, u160, u)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
p = Param{stringT, 42}
|
||||||
|
_, err = p.GetUint160FromHex()
|
||||||
|
require.NotNil(t, err)
|
||||||
|
|
||||||
|
p = Param{stringT, "wwbefd26fdf6e4d957c11e078b24ebce6291456f"}
|
||||||
|
_, err = p.GetUint160FromHex()
|
||||||
|
require.NotNil(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParamGetUint160FromAddress(t *testing.T) {
|
||||||
|
in := "AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y"
|
||||||
|
u160, _ := crypto.Uint160DecodeAddress(in)
|
||||||
|
p := Param{stringT, in}
|
||||||
|
u, err := p.GetUint160FromAddress()
|
||||||
|
assert.Equal(t, u160, u)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
p = Param{stringT, 42}
|
||||||
|
_, err = p.GetUint160FromAddress()
|
||||||
|
require.NotNil(t, err)
|
||||||
|
|
||||||
|
p = Param{stringT, "QK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y"}
|
||||||
|
_, err = p.GetUint160FromAddress()
|
||||||
|
require.NotNil(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParamGetFuncParam(t *testing.T) {
|
||||||
|
fp := FuncParam{
|
||||||
|
Type: String,
|
||||||
|
Value: Param{
|
||||||
|
Type: stringT,
|
||||||
|
Value: "jajaja",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
p := Param{
|
||||||
|
Type: funcParamT,
|
||||||
|
Value: fp,
|
||||||
|
}
|
||||||
|
newfp, err := p.GetFuncParam()
|
||||||
|
assert.Equal(t, fp, newfp)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
p = Param{funcParamT, 42}
|
||||||
|
_, err = p.GetFuncParam()
|
||||||
|
require.NotNil(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParamGetBytesHex(t *testing.T) {
|
||||||
|
in := "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"
|
||||||
|
inb, _ := hex.DecodeString(in)
|
||||||
|
p := Param{stringT, in}
|
||||||
|
bh, err := p.GetBytesHex()
|
||||||
|
assert.Equal(t, inb, bh)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
p = Param{stringT, 42}
|
||||||
|
_, err = p.GetBytesHex()
|
||||||
|
require.NotNil(t, err)
|
||||||
|
|
||||||
|
p = Param{stringT, "qq2c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"}
|
||||||
|
_, err = p.GetBytesHex()
|
||||||
|
require.NotNil(t, err)
|
||||||
|
}
|
||||||
|
|
|
@ -10,7 +10,6 @@ import (
|
||||||
"github.com/CityOfZion/neo-go/config"
|
"github.com/CityOfZion/neo-go/config"
|
||||||
"github.com/CityOfZion/neo-go/pkg/core"
|
"github.com/CityOfZion/neo-go/pkg/core"
|
||||||
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
||||||
"github.com/CityOfZion/neo-go/pkg/crypto"
|
|
||||||
"github.com/CityOfZion/neo-go/pkg/io"
|
"github.com/CityOfZion/neo-go/pkg/io"
|
||||||
"github.com/CityOfZion/neo-go/pkg/network"
|
"github.com/CityOfZion/neo-go/pkg/network"
|
||||||
"github.com/CityOfZion/neo-go/pkg/rpc/result"
|
"github.com/CityOfZion/neo-go/pkg/rpc/result"
|
||||||
|
@ -136,13 +135,13 @@ Methods:
|
||||||
break Methods
|
break Methods
|
||||||
}
|
}
|
||||||
case numberT:
|
case numberT:
|
||||||
if !s.validBlockHeight(param) {
|
num, err := s.blockHeightFromParam(param)
|
||||||
|
if err != nil {
|
||||||
resultsErr = errInvalidParams
|
resultsErr = errInvalidParams
|
||||||
break Methods
|
break Methods
|
||||||
}
|
}
|
||||||
|
hash = s.chain.GetHeaderHash(num)
|
||||||
hash = s.chain.GetHeaderHash(param.GetInt())
|
default:
|
||||||
case defaultT:
|
|
||||||
resultsErr = errInvalidParams
|
resultsErr = errInvalidParams
|
||||||
break Methods
|
break Methods
|
||||||
}
|
}
|
||||||
|
@ -164,12 +163,14 @@ Methods:
|
||||||
if !ok {
|
if !ok {
|
||||||
resultsErr = errInvalidParams
|
resultsErr = errInvalidParams
|
||||||
break Methods
|
break Methods
|
||||||
} else if !s.validBlockHeight(param) {
|
}
|
||||||
resultsErr = invalidBlockHeightError(0, param.GetInt())
|
num, err := s.blockHeightFromParam(param)
|
||||||
|
if err != nil {
|
||||||
|
resultsErr = errInvalidParams
|
||||||
break Methods
|
break Methods
|
||||||
}
|
}
|
||||||
|
|
||||||
results = s.chain.GetHeaderHash(param.GetInt())
|
results = s.chain.GetHeaderHash(num)
|
||||||
|
|
||||||
case "getconnectioncount":
|
case "getconnectioncount":
|
||||||
getconnectioncountCalled.Inc()
|
getconnectioncountCalled.Inc()
|
||||||
|
@ -242,6 +243,9 @@ Methods:
|
||||||
getunspentsCalled.Inc()
|
getunspentsCalled.Inc()
|
||||||
results, resultsErr = s.getAccountState(reqParams, true)
|
results, resultsErr = s.getAccountState(reqParams, true)
|
||||||
|
|
||||||
|
case "invokefunction":
|
||||||
|
results, resultsErr = s.invokeFunction(reqParams)
|
||||||
|
|
||||||
case "invokescript":
|
case "invokescript":
|
||||||
results, resultsErr = s.invokescript(reqParams)
|
results, resultsErr = s.invokescript(reqParams)
|
||||||
|
|
||||||
|
@ -306,11 +310,15 @@ func (s *Server) getAccountState(reqParams Params, unspents bool) (interface{},
|
||||||
param, ok := reqParams.ValueWithType(0, stringT)
|
param, ok := reqParams.ValueWithType(0, stringT)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, errInvalidParams
|
return nil, errInvalidParams
|
||||||
} else if scriptHash, err := crypto.Uint160DecodeAddress(param.GetString()); err != nil {
|
} else if scriptHash, err := param.GetUint160FromAddress(); err != nil {
|
||||||
return nil, errInvalidParams
|
return nil, errInvalidParams
|
||||||
} else if as := s.chain.GetAccountState(scriptHash); as != nil {
|
} else if as := s.chain.GetAccountState(scriptHash); as != nil {
|
||||||
if unspents {
|
if unspents {
|
||||||
results = wrappers.NewUnspents(as, s.chain, param.GetString())
|
str, err := param.GetString()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errInvalidParams
|
||||||
|
}
|
||||||
|
results = wrappers.NewUnspents(as, s.chain, str)
|
||||||
} else {
|
} else {
|
||||||
results = wrappers.NewAccountState(as)
|
results = wrappers.NewAccountState(as)
|
||||||
}
|
}
|
||||||
|
@ -320,6 +328,32 @@ func (s *Server) getAccountState(reqParams Params, unspents bool) (interface{},
|
||||||
return results, resultsErr
|
return results, resultsErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// invokescript implements the `invokescript` RPC call.
|
||||||
|
func (s *Server) invokeFunction(reqParams Params) (interface{}, error) {
|
||||||
|
scriptHashHex, ok := reqParams.ValueWithType(0, stringT)
|
||||||
|
if !ok {
|
||||||
|
return nil, errInvalidParams
|
||||||
|
}
|
||||||
|
scriptHash, err := scriptHashHex.GetUint160FromHex()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
script, err := CreateFunctionInvocationScript(scriptHash, reqParams[1:])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
vm, _ := s.chain.GetTestVM()
|
||||||
|
vm.LoadScript(script)
|
||||||
|
_ = vm.Run()
|
||||||
|
result := &wrappers.InvokeResult{
|
||||||
|
State: vm.State(),
|
||||||
|
GasConsumed: "0.1",
|
||||||
|
Script: hex.EncodeToString(script),
|
||||||
|
Stack: vm.Estack(),
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
// invokescript implements the `invokescript` RPC call.
|
// invokescript implements the `invokescript` RPC call.
|
||||||
func (s *Server) invokescript(reqParams Params) (interface{}, error) {
|
func (s *Server) invokescript(reqParams Params) (interface{}, error) {
|
||||||
if len(reqParams) < 1 {
|
if len(reqParams) < 1 {
|
||||||
|
@ -334,10 +368,12 @@ func (s *Server) invokescript(reqParams Params) (interface{}, error) {
|
||||||
vm, _ := s.chain.GetTestVM()
|
vm, _ := s.chain.GetTestVM()
|
||||||
vm.LoadScript(script)
|
vm.LoadScript(script)
|
||||||
_ = vm.Run()
|
_ = vm.Run()
|
||||||
|
// It's already being GetBytesHex'ed, so it's a correct string.
|
||||||
|
echo, _ := reqParams[0].GetString()
|
||||||
result := &wrappers.InvokeResult{
|
result := &wrappers.InvokeResult{
|
||||||
State: vm.State(),
|
State: vm.State(),
|
||||||
GasConsumed: "0.1",
|
GasConsumed: "0.1",
|
||||||
Script: reqParams[0].GetString(),
|
Script: echo,
|
||||||
Stack: vm.Estack(),
|
Stack: vm.Estack(),
|
||||||
}
|
}
|
||||||
return result, nil
|
return result, nil
|
||||||
|
@ -384,6 +420,14 @@ func (s *Server) sendrawtransaction(reqParams Params) (interface{}, error) {
|
||||||
return results, resultsErr
|
return results, resultsErr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s Server) validBlockHeight(param *Param) bool {
|
func (s Server) blockHeightFromParam(param *Param) (int, error) {
|
||||||
return param.GetInt() >= 0 && param.GetInt() <= int(s.chain.BlockHeight())
|
num, err := param.GetInt()
|
||||||
|
if err != nil {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if num < 0 || num > int(s.chain.BlockHeight()) {
|
||||||
|
return 0, invalidBlockHeightError(0, num)
|
||||||
|
}
|
||||||
|
return num, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,19 @@ type SendTXResponse struct {
|
||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InvokeFunctionResponse struct for testing.
|
||||||
|
type InvokeFunctionResponse struct {
|
||||||
|
Jsonrpc string `json:"jsonrpc"`
|
||||||
|
Result struct {
|
||||||
|
Script string `json:"script"`
|
||||||
|
State string `json:"state"`
|
||||||
|
GasConsumed string `json:"gas_consumed"`
|
||||||
|
Stack []FuncParam `json:"stack"`
|
||||||
|
TX string `json:"tx,omitempty"`
|
||||||
|
} `json:"result"`
|
||||||
|
ID int `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
// ValidateAddrResponse struct for testing.
|
// ValidateAddrResponse struct for testing.
|
||||||
type ValidateAddrResponse struct {
|
type ValidateAddrResponse struct {
|
||||||
Jsonrpc string `json:"jsonrpc"`
|
Jsonrpc string `json:"jsonrpc"`
|
||||||
|
|
|
@ -126,6 +126,11 @@ var rpcTestCases = map[string][]rpcTestCase{
|
||||||
params: `[]`,
|
params: `[]`,
|
||||||
fail: true,
|
fail: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "bad params",
|
||||||
|
params: `[[]]`,
|
||||||
|
fail: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "invalid height",
|
name: "invalid height",
|
||||||
params: `[-1]`,
|
params: `[-1]`,
|
||||||
|
@ -246,6 +251,69 @@ var rpcTestCases = map[string][]rpcTestCase{
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"invokefunction": {
|
||||||
|
{
|
||||||
|
name: "positive",
|
||||||
|
params: `["50befd26fdf6e4d957c11e078b24ebce6291456f", "test", []]`,
|
||||||
|
result: func(e *executor) interface{} { return &InvokeFunctionResponse{} },
|
||||||
|
check: func(t *testing.T, e *executor, result interface{}) {
|
||||||
|
res, ok := result.(*InvokeFunctionResponse)
|
||||||
|
require.True(t, ok)
|
||||||
|
assert.NotEqual(t, "", res.Result.Script)
|
||||||
|
assert.NotEqual(t, "", res.Result.State)
|
||||||
|
assert.NotEqual(t, 0, res.Result.GasConsumed)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no params",
|
||||||
|
params: `[]`,
|
||||||
|
fail: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "not a string",
|
||||||
|
params: `[42, "test", []]`,
|
||||||
|
fail: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "not a scripthash",
|
||||||
|
params: `["qwerty", "test", []]`,
|
||||||
|
fail: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bad params",
|
||||||
|
params: `["50befd26fdf6e4d957c11e078b24ebce6291456f", "test", [{"type": "Integer", "value": "qwerty"}]]`,
|
||||||
|
fail: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"invokescript": {
|
||||||
|
{
|
||||||
|
name: "positive",
|
||||||
|
params: `["51c56b0d48656c6c6f2c20776f726c6421680f4e656f2e52756e74696d652e4c6f67616c7566"]`,
|
||||||
|
result: func(e *executor) interface{} { return &InvokeFunctionResponse{} },
|
||||||
|
check: func(t *testing.T, e *executor, result interface{}) {
|
||||||
|
res, ok := result.(*InvokeFunctionResponse)
|
||||||
|
require.True(t, ok)
|
||||||
|
assert.NotEqual(t, "", res.Result.Script)
|
||||||
|
assert.NotEqual(t, "", res.Result.State)
|
||||||
|
assert.NotEqual(t, 0, res.Result.GasConsumed)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no params",
|
||||||
|
params: `[]`,
|
||||||
|
fail: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "not a string",
|
||||||
|
params: `[42]`,
|
||||||
|
fail: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bas string",
|
||||||
|
params: `["qwerty"]`,
|
||||||
|
fail: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
"sendrawtransaction": {
|
"sendrawtransaction": {
|
||||||
{
|
{
|
||||||
name: "positive",
|
name: "positive",
|
||||||
|
|
|
@ -89,6 +89,11 @@ func StackParamTypeFromString(s string) (StackParamType, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MarshalJSON implements the json.Marshaler interface.
|
||||||
|
func (t *StackParamType) MarshalJSON() ([]byte, error) {
|
||||||
|
return []byte(`"` + t.String() + `"`), nil
|
||||||
|
}
|
||||||
|
|
||||||
// UnmarshalJSON sets StackParamType from JSON-encoded data.
|
// UnmarshalJSON sets StackParamType from JSON-encoded data.
|
||||||
func (t *StackParamType) UnmarshalJSON(data []byte) (err error) {
|
func (t *StackParamType) UnmarshalJSON(data []byte) (err error) {
|
||||||
var (
|
var (
|
||||||
|
|
|
@ -2,6 +2,9 @@ package rpc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
||||||
"github.com/CityOfZion/neo-go/pkg/crypto"
|
"github.com/CityOfZion/neo-go/pkg/crypto"
|
||||||
|
@ -162,3 +165,121 @@ func CreateDeploymentScript(avm []byte, contract *ContractDetails) ([]byte, erro
|
||||||
}
|
}
|
||||||
return script.Bytes(), nil
|
return script.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateFunctionInvocationScript creates a script to invoke given contract with
|
||||||
|
// given parameters.
|
||||||
|
func CreateFunctionInvocationScript(contract util.Uint160, params Params) ([]byte, error) {
|
||||||
|
script := new(bytes.Buffer)
|
||||||
|
for i := len(params) - 1; i >= 0; i-- {
|
||||||
|
switch params[i].Type {
|
||||||
|
case stringT:
|
||||||
|
if err := vm.EmitString(script, params[i].String()); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
case numberT:
|
||||||
|
num, err := params[i].GetInt()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := vm.EmitString(script, strconv.Itoa(num)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
case arrayT:
|
||||||
|
slice, err := params[i].GetArray()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for j := len(slice) - 1; j >= 0; j-- {
|
||||||
|
fp, err := slice[j].GetFuncParam()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch fp.Type {
|
||||||
|
case ByteArray, Signature:
|
||||||
|
str, err := fp.Value.GetBytesHex()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := vm.EmitBytes(script, str); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
case String:
|
||||||
|
str, err := fp.Value.GetString()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := vm.EmitString(script, str); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
case Hash160:
|
||||||
|
hash, err := fp.Value.GetUint160FromHex()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := vm.EmitBytes(script, hash.Bytes()); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
case Hash256:
|
||||||
|
hash, err := fp.Value.GetUint256()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := vm.EmitBytes(script, hash.Bytes()); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
case PublicKey:
|
||||||
|
str, err := fp.Value.GetString()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
key, err := keys.NewPublicKeyFromString(string(str))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := vm.EmitBytes(script, key.Bytes()); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
case Integer:
|
||||||
|
val, err := fp.Value.GetInt()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := vm.EmitInt(script, int64(val)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
case Boolean:
|
||||||
|
str, err := fp.Value.GetString()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch str {
|
||||||
|
case "true":
|
||||||
|
err = vm.EmitInt(script, 1)
|
||||||
|
case "false":
|
||||||
|
err = vm.EmitInt(script, 0)
|
||||||
|
default:
|
||||||
|
err = errors.New("wrong boolean value")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("parameter type %v is not supported", fp.Type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = vm.EmitInt(script, int64(len(slice)))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = vm.EmitOpcode(script, vm.PACK)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := vm.EmitAppCall(script, contract, false); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return script.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
89
pkg/rpc/tx_builder_test.go
Normal file
89
pkg/rpc/tx_builder_test.go
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
package rpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestInvocationScriptCreationGood(t *testing.T) {
|
||||||
|
p := Param{stringT, "50befd26fdf6e4d957c11e078b24ebce6291456f"}
|
||||||
|
contract, err := p.GetUint160FromHex()
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
var paramScripts = []struct {
|
||||||
|
ps Params
|
||||||
|
script string
|
||||||
|
}{{
|
||||||
|
script: "676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
||||||
|
}, {
|
||||||
|
ps: Params{{stringT, "transfer"}},
|
||||||
|
script: "087472616e73666572676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
||||||
|
}, {
|
||||||
|
ps: Params{{numberT, 42}},
|
||||||
|
script: "023432676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
||||||
|
}, {
|
||||||
|
ps: Params{{stringT, "a"}, {arrayT, []Param{}}},
|
||||||
|
script: "00c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
||||||
|
}, {
|
||||||
|
ps: Params{{stringT, "a"}, {arrayT, []Param{{funcParamT, FuncParam{ByteArray, Param{stringT, "50befd26fdf6e4d957c11e078b24ebce6291456f"}}}}}},
|
||||||
|
script: "1450befd26fdf6e4d957c11e078b24ebce6291456f51c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
||||||
|
}, {
|
||||||
|
ps: Params{{stringT, "a"}, {arrayT, []Param{{funcParamT, FuncParam{Signature, Param{stringT, "4edf5005771de04619235d5a4c7a9a11bb78e008541f1da7725f654c33380a3c87e2959a025da706d7255cb3a3fa07ebe9c6559d0d9e6213c68049168eb1056f"}}}}}},
|
||||||
|
script: "404edf5005771de04619235d5a4c7a9a11bb78e008541f1da7725f654c33380a3c87e2959a025da706d7255cb3a3fa07ebe9c6559d0d9e6213c68049168eb1056f51c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
||||||
|
}, {
|
||||||
|
ps: Params{{stringT, "a"}, {arrayT, []Param{{funcParamT, FuncParam{String, Param{stringT, "50befd26fdf6e4d957c11e078b24ebce6291456f"}}}}}},
|
||||||
|
script: "283530626566643236666466366534643935376331316530373862323465626365363239313435366651c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
||||||
|
}, {
|
||||||
|
ps: Params{{stringT, "a"}, {arrayT, []Param{{funcParamT, FuncParam{Hash160, Param{stringT, "50befd26fdf6e4d957c11e078b24ebce6291456f"}}}}}},
|
||||||
|
script: "146f459162ceeb248b071ec157d9e4f6fd26fdbe5051c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
||||||
|
}, {
|
||||||
|
ps: Params{{stringT, "a"}, {arrayT, []Param{{funcParamT, FuncParam{Hash256, Param{stringT, "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"}}}}}},
|
||||||
|
script: "20e72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c6051c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
||||||
|
}, {
|
||||||
|
ps: Params{{stringT, "a"}, {arrayT, []Param{{funcParamT, FuncParam{PublicKey, Param{stringT, "03c089d7122b840a4935234e82e26ae5efd0c2acb627239dc9f207311337b6f2c1"}}}}}},
|
||||||
|
script: "2103c089d7122b840a4935234e82e26ae5efd0c2acb627239dc9f207311337b6f2c151c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
||||||
|
}, {
|
||||||
|
ps: Params{{stringT, "a"}, {arrayT, []Param{{funcParamT, FuncParam{Integer, Param{numberT, 42}}}}}},
|
||||||
|
script: "012a51c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
||||||
|
}, {
|
||||||
|
ps: Params{{stringT, "a"}, {arrayT, []Param{{funcParamT, FuncParam{Boolean, Param{stringT, "true"}}}}}},
|
||||||
|
script: "5151c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
||||||
|
}, {
|
||||||
|
ps: Params{{stringT, "a"}, {arrayT, []Param{{funcParamT, FuncParam{Boolean, Param{stringT, "false"}}}}}},
|
||||||
|
script: "0051c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
||||||
|
}}
|
||||||
|
for _, ps := range paramScripts {
|
||||||
|
script, err := CreateFunctionInvocationScript(contract, ps.ps)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, ps.script, hex.EncodeToString(script))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInvocationScriptCreationBad(t *testing.T) {
|
||||||
|
contract := util.Uint160{}
|
||||||
|
|
||||||
|
var testParams = []Params{
|
||||||
|
Params{{numberT, "qwerty"}},
|
||||||
|
Params{{arrayT, 42}},
|
||||||
|
Params{{arrayT, []Param{{numberT, 42}}}},
|
||||||
|
Params{{arrayT, []Param{{funcParamT, FuncParam{ByteArray, Param{stringT, "qwerty"}}}}}},
|
||||||
|
Params{{arrayT, []Param{{funcParamT, FuncParam{Signature, Param{stringT, "qwerty"}}}}}},
|
||||||
|
Params{{arrayT, []Param{{funcParamT, FuncParam{String, Param{numberT, 42}}}}}},
|
||||||
|
Params{{arrayT, []Param{{funcParamT, FuncParam{Hash160, Param{stringT, "qwerty"}}}}}},
|
||||||
|
Params{{arrayT, []Param{{funcParamT, FuncParam{Hash256, Param{stringT, "qwerty"}}}}}},
|
||||||
|
Params{{arrayT, []Param{{funcParamT, FuncParam{PublicKey, Param{numberT, 42}}}}}},
|
||||||
|
Params{{arrayT, []Param{{funcParamT, FuncParam{PublicKey, Param{stringT, "qwerty"}}}}}},
|
||||||
|
Params{{arrayT, []Param{{funcParamT, FuncParam{Integer, Param{stringT, "qwerty"}}}}}},
|
||||||
|
Params{{arrayT, []Param{{funcParamT, FuncParam{Boolean, Param{numberT, 42}}}}}},
|
||||||
|
Params{{arrayT, []Param{{funcParamT, FuncParam{Boolean, Param{stringT, "qwerty"}}}}}},
|
||||||
|
Params{{arrayT, []Param{{funcParamT, FuncParam{Unknown, Param{}}}}}},
|
||||||
|
}
|
||||||
|
for _, ps := range testParams {
|
||||||
|
_, err := CreateFunctionInvocationScript(contract, ps)
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,13 +9,34 @@ type stackItem struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func appendToItems(items *[]stackItem, val StackItem, seen map[StackItem]bool) {
|
||||||
|
if arr, ok := val.Value().([]StackItem); ok {
|
||||||
|
if seen[val] {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
seen[val] = true
|
||||||
|
intItems := make([]stackItem, 0, len(arr))
|
||||||
|
for _, v := range arr {
|
||||||
|
appendToItems(&intItems, v, seen)
|
||||||
|
}
|
||||||
|
*items = append(*items, stackItem{
|
||||||
|
Value: intItems,
|
||||||
|
Type: val.String(),
|
||||||
|
})
|
||||||
|
|
||||||
|
} else {
|
||||||
|
*items = append(*items, stackItem{
|
||||||
|
Value: val,
|
||||||
|
Type: val.String(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func stackToArray(s *Stack) []stackItem {
|
func stackToArray(s *Stack) []stackItem {
|
||||||
items := make([]stackItem, 0, s.Len())
|
items := make([]stackItem, 0, s.Len())
|
||||||
s.Iter(func(e *Element) {
|
seen := make(map[StackItem]bool)
|
||||||
items = append(items, stackItem{
|
s.IterBack(func(e *Element) {
|
||||||
Value: e.value,
|
appendToItems(&items, e.value, seen)
|
||||||
Type: e.value.String(),
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
|
|
|
@ -360,6 +360,17 @@ func (s *Stack) Iter(f func(*Element)) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IterBack iterates over all the elements of the stack, starting from the bottom
|
||||||
|
// of the stack.
|
||||||
|
// s.IterBack(func(elem *Element) {
|
||||||
|
// // do something with the element.
|
||||||
|
// })
|
||||||
|
func (s *Stack) IterBack(f func(*Element)) {
|
||||||
|
for e := s.Back(); e != nil; e = e.Prev() {
|
||||||
|
f(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// popSigElements pops keys or signatures from the stack as needed for
|
// popSigElements pops keys or signatures from the stack as needed for
|
||||||
// CHECKMULTISIG.
|
// CHECKMULTISIG.
|
||||||
func (s *Stack) popSigElements() ([][]byte, error) {
|
func (s *Stack) popSigElements() ([][]byte, error) {
|
||||||
|
|
|
@ -154,19 +154,46 @@ func TestIterAfterRemove(t *testing.T) {
|
||||||
|
|
||||||
func TestIteration(t *testing.T) {
|
func TestIteration(t *testing.T) {
|
||||||
var (
|
var (
|
||||||
|
n = 10
|
||||||
s = NewStack("test")
|
s = NewStack("test")
|
||||||
elems = makeElements(10)
|
elems = makeElements(n)
|
||||||
)
|
)
|
||||||
for _, elem := range elems {
|
for _, elem := range elems {
|
||||||
s.Push(elem)
|
s.Push(elem)
|
||||||
}
|
}
|
||||||
assert.Equal(t, len(elems), s.Len())
|
assert.Equal(t, len(elems), s.Len())
|
||||||
|
|
||||||
i := 0
|
iteratedElems := make([]*Element, 0)
|
||||||
|
|
||||||
s.Iter(func(elem *Element) {
|
s.Iter(func(elem *Element) {
|
||||||
i++
|
iteratedElems = append(iteratedElems, elem)
|
||||||
})
|
})
|
||||||
assert.Equal(t, len(elems), i)
|
// Top to bottom order of iteration.
|
||||||
|
poppedElems := make([]*Element, 0)
|
||||||
|
for elem := s.Pop(); elem != nil; elem = s.Pop() {
|
||||||
|
poppedElems = append(poppedElems, elem)
|
||||||
|
}
|
||||||
|
assert.Equal(t, poppedElems, iteratedElems)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBackIteration(t *testing.T) {
|
||||||
|
var (
|
||||||
|
n = 10
|
||||||
|
s = NewStack("test")
|
||||||
|
elems = makeElements(n)
|
||||||
|
)
|
||||||
|
for _, elem := range elems {
|
||||||
|
s.Push(elem)
|
||||||
|
}
|
||||||
|
assert.Equal(t, len(elems), s.Len())
|
||||||
|
|
||||||
|
iteratedElems := make([]*Element, 0)
|
||||||
|
|
||||||
|
s.IterBack(func(elem *Element) {
|
||||||
|
iteratedElems = append(iteratedElems, elem)
|
||||||
|
})
|
||||||
|
// Bottom to the top order of iteration.
|
||||||
|
assert.Equal(t, elems, iteratedElems)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPushVal(t *testing.T) {
|
func TestPushVal(t *testing.T) {
|
||||||
|
|
Loading…
Reference in a new issue