rpc: implement invokefunction, fix #347

Param getters were redone to return errors because otherwise bad FuncParam
values could lead to panic. FuncParam itself might be not the most elegant
solution, but it works good enough for now.
This commit is contained in:
Roman Khimov 2019-11-26 13:13:17 +03:00
parent ea9bc22510
commit cf39171485
8 changed files with 550 additions and 22 deletions

View file

@ -55,12 +55,22 @@ which would yield the response:
| `getunspents` | Yes |
| `getversion` | Yes |
| `invoke` | No (#346) |
| `invokefunction` | No (#347) |
| `invokefunction` | Yes |
| `invokescript` | Yes |
| `sendrawtransaction` | Yes |
| `submitblock` | No (#344) |
| `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
* [JSON-RPC 2.0 Specification](http://www.jsonrpc.org/specification)

View file

@ -1,10 +1,12 @@
package rpc
import (
"bytes"
"encoding/hex"
"encoding/json"
"fmt"
"github.com/CityOfZion/neo-go/pkg/crypto"
"github.com/CityOfZion/neo-go/pkg/util"
"github.com/pkg/errors"
)
@ -19,6 +21,12 @@ type (
}
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 (
@ -26,6 +34,7 @@ const (
stringT
numberT
arrayT
funcParamT
)
func (p Param) String() string {
@ -33,27 +42,82 @@ func (p Param) String() string {
}
// 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.
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.
func (p Param) GetUint256() (util.Uint256, error) {
s, ok := p.Value.(string)
if !ok {
return util.Uint256{}, errors.New("must be a string")
s, err := p.GetString()
if err != nil {
return util.Uint256{}, err
}
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
// it is a hex-encoded string.
func (p Param) GetBytesHex() ([]byte, error) {
s, ok := p.Value.(string)
if !ok {
return nil, errors.New("must be a string")
s, err := p.GetString()
if err != nil {
return nil, err
}
return hex.DecodeString(s)
@ -77,6 +141,17 @@ func (p *Param) UnmarshalJSON(data []byte) error {
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
if err := json.Unmarshal(data, &ps); err == nil {
p.Type = arrayT

View file

@ -1,14 +1,18 @@
package rpc
import (
"encoding/hex"
"encoding/json"
"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"
)
func TestParam_UnmarshalJSON(t *testing.T) {
msg := `["str1", 123, ["str2", 3]]`
msg := `["str1", 123, ["str2", 3], [{"type": "String", "value": "jajaja"}]]`
expected := Params{
{
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
@ -40,3 +59,126 @@ func TestParam_UnmarshalJSON(t *testing.T) {
msg = `[{"2": 3}]`
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)
}

View file

@ -10,7 +10,6 @@ import (
"github.com/CityOfZion/neo-go/config"
"github.com/CityOfZion/neo-go/pkg/core"
"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/network"
"github.com/CityOfZion/neo-go/pkg/rpc/result"
@ -136,12 +135,12 @@ Methods:
break Methods
}
case numberT:
if !s.validBlockHeight(param) {
num, err := s.blockHeightFromParam(param)
if err != nil {
resultsErr = errInvalidParams
break Methods
}
hash = s.chain.GetHeaderHash(param.GetInt())
hash = s.chain.GetHeaderHash(num)
case defaultT:
resultsErr = errInvalidParams
break Methods
@ -164,12 +163,14 @@ Methods:
if !ok {
resultsErr = errInvalidParams
break Methods
} else if !s.validBlockHeight(param) {
resultsErr = invalidBlockHeightError(0, param.GetInt())
}
num, err := s.blockHeightFromParam(param)
if err != nil {
resultsErr = errInvalidParams
break Methods
}
results = s.chain.GetHeaderHash(param.GetInt())
results = s.chain.GetHeaderHash(num)
case "getconnectioncount":
getconnectioncountCalled.Inc()
@ -242,6 +243,9 @@ Methods:
getunspentsCalled.Inc()
results, resultsErr = s.getAccountState(reqParams, true)
case "invokefunction":
results, resultsErr = s.invokeFunction(reqParams)
case "invokescript":
results, resultsErr = s.invokescript(reqParams)
@ -306,11 +310,15 @@ func (s *Server) getAccountState(reqParams Params, unspents bool) (interface{},
param, ok := reqParams.ValueWithType(0, stringT)
if !ok {
return nil, errInvalidParams
} else if scriptHash, err := crypto.Uint160DecodeAddress(param.GetString()); err != nil {
} else if scriptHash, err := param.GetUint160FromAddress(); err != nil {
return nil, errInvalidParams
} else if as := s.chain.GetAccountState(scriptHash); as != nil {
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 {
results = wrappers.NewAccountState(as)
}
@ -320,6 +328,32 @@ func (s *Server) getAccountState(reqParams Params, unspents bool) (interface{},
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.
func (s *Server) invokescript(reqParams Params) (interface{}, error) {
if len(reqParams) < 1 {
@ -334,10 +368,12 @@ func (s *Server) invokescript(reqParams Params) (interface{}, error) {
vm, _ := s.chain.GetTestVM()
vm.LoadScript(script)
_ = vm.Run()
// It's already being GetBytesHex'ed, so it's a correct string.
echo, _ := reqParams[0].GetString()
result := &wrappers.InvokeResult{
State: vm.State(),
GasConsumed: "0.1",
Script: reqParams[0].GetString(),
Script: echo,
Stack: vm.Estack(),
}
return result, nil
@ -384,6 +420,14 @@ func (s *Server) sendrawtransaction(reqParams Params) (interface{}, error) {
return results, resultsErr
}
func (s Server) validBlockHeight(param *Param) bool {
return param.GetInt() >= 0 && param.GetInt() <= int(s.chain.BlockHeight())
func (s Server) blockHeightFromParam(param *Param) (int, error) {
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
}

View file

@ -32,6 +32,19 @@ type SendTXResponse struct {
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.
type ValidateAddrResponse struct {
Jsonrpc string `json:"jsonrpc"`

View file

@ -246,6 +246,40 @@ 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,
},
},
"sendrawtransaction": {
{
name: "positive",

View file

@ -2,6 +2,9 @@ package rpc
import (
"bytes"
"errors"
"fmt"
"strconv"
"github.com/CityOfZion/neo-go/pkg/core/transaction"
"github.com/CityOfZion/neo-go/pkg/crypto"
@ -162,3 +165,121 @@ func CreateDeploymentScript(avm []byte, contract *ContractDetails) ([]byte, erro
}
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
}

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