parent
d5355acfa9
commit
9e7fca013e
6 changed files with 225 additions and 39 deletions
|
@ -16,6 +16,7 @@ import (
|
|||
"github.com/go-yaml/yaml"
|
||||
"github.com/nspcc-dev/neo-go/cli/flags"
|
||||
"github.com/nspcc-dev/neo-go/pkg/compiler"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpc/client"
|
||||
|
@ -68,6 +69,9 @@ import "github.com/nspcc-dev/neo-go/pkg/interop/runtime"
|
|||
func Main(op string, args []interface{}) {
|
||||
runtime.Notify("Hello world!")
|
||||
}`
|
||||
// cosignersSeparator is a special value which is used to distinguish
|
||||
// parameters and cosigners for invoke* commands
|
||||
cosignersSeparator = "--"
|
||||
)
|
||||
|
||||
// NewCommands returns 'contract' command.
|
||||
|
@ -135,11 +139,11 @@ func NewCommands() []cli.Command {
|
|||
{
|
||||
Name: "invokefunction",
|
||||
Usage: "invoke deployed contract on the blockchain",
|
||||
UsageText: "neo-go contract invokefunction -e endpoint -w wallet [-a address] [-g gas] scripthash [method] [arguments...]",
|
||||
Description: `Executes given (as a script hash) deployed script with the given method and
|
||||
and arguments. See testinvokefunction documentation for the details about
|
||||
parameters. It differs from testinvokefunction in that this command sends an
|
||||
invocation transaction to the network.
|
||||
UsageText: "neo-go contract invokefunction -e endpoint -w wallet [-a address] [-g gas] scripthash [method] [arguments...] [--] [cosigners...]",
|
||||
Description: `Executes given (as a script hash) deployed script with the given method,
|
||||
arguments and cosigners. See testinvokefunction documentation for the details
|
||||
about parameters. It differs from testinvokefunction in that this command
|
||||
sends an invocation transaction to the network.
|
||||
`,
|
||||
Action: invokeFunction,
|
||||
Flags: []cli.Flag{
|
||||
|
@ -152,13 +156,14 @@ 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).
|
||||
UsageText: "neo-go contract testinvokefunction -e endpoint scripthash [method] [arguments...] [--] [cosigners...]",
|
||||
Description: `Executes given (as a script hash) deployed script with the given method,
|
||||
arguments and cosigners. If no method is given "" is passed to the script, if
|
||||
no arguments are given, an empty array is passed, if no cosigners are given,
|
||||
no array will be 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
|
||||
|
@ -214,6 +219,32 @@ func NewCommands() []cli.Command {
|
|||
* 'string\:string' is a string with a value of 'string:string'
|
||||
* '03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c' is a
|
||||
key with a value of '03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c'
|
||||
|
||||
Cosigners represent a set of Uint160 hashes with witness scopes and are used
|
||||
to verify hashes in System.Runtime.CheckWitness syscall. To specify cosigners
|
||||
use cosigner[:scope] syntax where
|
||||
* 'cosigner' is hex-encoded 160 bit (20 byte) LE value of cosigner's address,
|
||||
which could have '0x' prefix.
|
||||
* 'scope' is a comma-separated set of cosigner's scopes, which could be:
|
||||
- 'Global' - allows this witness in all contexts. This cannot be combined
|
||||
with other flags.
|
||||
- 'CalledByEntry' - means that this condition must hold: EntryScriptHash
|
||||
== CallingScriptHash. The witness/permission/signature
|
||||
given on first invocation will automatically expire if
|
||||
entering deeper internal invokes. This can be default
|
||||
safe choice for native NEO/GAS.
|
||||
- 'CustomContracts' - define valid custom contract hashes for witness check.
|
||||
- 'CustomGroups' - define custom pubkey for group members.
|
||||
|
||||
If no scopes were specified, 'Global' used as default. If no cosigners were
|
||||
specified, no array will be passed. Note that scopes are properly handled by
|
||||
neo-go RPC server only. C# implementation does not support scopes capability.
|
||||
|
||||
Examples:
|
||||
* '0000000009070e030d0f0e020d0c06050e030c02'
|
||||
* '0x0000000009070e030d0f0e020d0c06050e030c02'
|
||||
* '0x0000000009070e030d0f0e020d0c06050e030c02:Global'
|
||||
* '0000000009070e030d0f0e020d0c06050e030c02:CalledByEntry,CustomGroups'
|
||||
`,
|
||||
Action: testInvokeFunction,
|
||||
Flags: []cli.Flag{
|
||||
|
@ -221,8 +252,12 @@ func NewCommands() []cli.Command {
|
|||
},
|
||||
},
|
||||
{
|
||||
Name: "testinvokescript",
|
||||
Usage: "Invoke compiled AVM code on the blockchain (test mode, not creating a transaction for it)",
|
||||
Name: "testinvokescript",
|
||||
Usage: "Invoke compiled AVM code on the blockchain (test mode, not creating a transaction for it)",
|
||||
UsageText: "neo-go contract testinvokescript -e endpoint -i input.avm [cosigners...]",
|
||||
Description: `Executes given compiled AVM instructions with the given set of
|
||||
cosigners. See testinvokefunction documentation for the details about parameters.
|
||||
`,
|
||||
Action: testInvokeScript,
|
||||
Flags: []cli.Flag{
|
||||
endpointFlag,
|
||||
|
@ -362,13 +397,15 @@ func invokeFunction(ctx *cli.Context) error {
|
|||
|
||||
func invokeInternal(ctx *cli.Context, signAndPush bool) error {
|
||||
var (
|
||||
err error
|
||||
gas util.Fixed8
|
||||
operation string
|
||||
params = make([]smartcontract.Parameter, 0)
|
||||
paramsStart = 1
|
||||
resp *result.Invoke
|
||||
acc *wallet.Account
|
||||
err error
|
||||
gas util.Fixed8
|
||||
operation string
|
||||
params = make([]smartcontract.Parameter, 0)
|
||||
paramsStart = 1
|
||||
cosigners []transaction.Cosigner
|
||||
cosignersStart = 0
|
||||
resp *result.Invoke
|
||||
acc *wallet.Account
|
||||
)
|
||||
|
||||
endpoint := ctx.String("endpoint")
|
||||
|
@ -390,6 +427,10 @@ func invokeInternal(ctx *cli.Context, signAndPush bool) error {
|
|||
|
||||
if len(args) > paramsStart {
|
||||
for k, s := range args[paramsStart:] {
|
||||
if s == cosignersSeparator {
|
||||
cosignersStart = paramsStart + k + 1
|
||||
break
|
||||
}
|
||||
param, err := smartcontract.NewParameterFromString(s)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("failed to parse argument #%d: %v", k+paramsStart+1, err), 1)
|
||||
|
@ -398,6 +439,16 @@ func invokeInternal(ctx *cli.Context, signAndPush bool) error {
|
|||
}
|
||||
}
|
||||
|
||||
if len(args) >= cosignersStart && cosignersStart > 0 {
|
||||
for i, c := range args[cosignersStart:] {
|
||||
cosigner, err := parseCosigner(c)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("failed to parse cosigner #%d: %v", i+cosignersStart+1, err), 1)
|
||||
}
|
||||
cosigners = append(cosigners, cosigner)
|
||||
}
|
||||
}
|
||||
|
||||
if signAndPush {
|
||||
gas = flags.Fixed8FromContext(ctx, "gas")
|
||||
acc, err = getAccFromContext(ctx)
|
||||
|
@ -410,7 +461,7 @@ func invokeInternal(ctx *cli.Context, signAndPush bool) error {
|
|||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
|
||||
resp, err = c.InvokeFunction(script, operation, params)
|
||||
resp, err = c.InvokeFunction(script, operation, params, cosigners)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
|
@ -454,13 +505,25 @@ func testInvokeScript(ctx *cli.Context) error {
|
|||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
|
||||
args := ctx.Args()
|
||||
var cosigners []transaction.Cosigner
|
||||
if args.Present() {
|
||||
for i, c := range args[:] {
|
||||
cosigner, err := parseCosigner(c)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("failed to parse cosigner #%d: %v", i+1, err), 1)
|
||||
}
|
||||
cosigners = append(cosigners, cosigner)
|
||||
}
|
||||
}
|
||||
|
||||
c, err := client.New(context.TODO(), endpoint, client.Options{})
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
|
||||
scriptHex := hex.EncodeToString(b)
|
||||
resp, err := c.InvokeScript(scriptHex)
|
||||
resp, err := c.InvokeScript(scriptHex, cosigners)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
|
@ -625,3 +688,26 @@ func parseContractConfig(confFile string) (ProjectConfig, error) {
|
|||
}
|
||||
return conf, nil
|
||||
}
|
||||
|
||||
func parseCosigner(c string) (transaction.Cosigner, error) {
|
||||
var (
|
||||
err error
|
||||
res = transaction.Cosigner{}
|
||||
)
|
||||
data := strings.SplitN(strings.ToLower(c), ":", 2)
|
||||
s := data[0]
|
||||
if len(s) == 2*util.Uint160Size+2 && s[0:2] == "0x" {
|
||||
s = s[2:]
|
||||
}
|
||||
res.Account, err = util.Uint160DecodeStringLE(s)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
if len(data) > 1 {
|
||||
res.Scopes, err = transaction.ScopesFromString(data[1])
|
||||
if err != nil {
|
||||
return transaction.Cosigner{}, err
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
package transaction
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// WitnessScope represents set of witness flags for Transaction cosigner.
|
||||
type WitnessScope byte
|
||||
|
||||
|
@ -17,3 +22,34 @@ const (
|
|||
// CustomGroups define custom pubkey for group members.
|
||||
CustomGroups WitnessScope = 0x20
|
||||
)
|
||||
|
||||
// ScopesFromString converts string of comma-separated scopes to a set of scopes
|
||||
// (case doesn't matter). String can combine several scopes, e.g. be any of:
|
||||
// 'Global', 'CalledByEntry,CustomGroups' etc. In case of an empty string an
|
||||
// error will be returned.
|
||||
func ScopesFromString(s string) (WitnessScope, error) {
|
||||
var result WitnessScope
|
||||
s = strings.ToLower(s)
|
||||
scopes := strings.Split(s, ",")
|
||||
dict := map[string]WitnessScope{
|
||||
"global": Global,
|
||||
"calledbyentry": CalledByEntry,
|
||||
"customcontracts": CustomContracts,
|
||||
"customgroups": CustomGroups,
|
||||
}
|
||||
var isGlobal bool
|
||||
for _, scopeStr := range scopes {
|
||||
scope, ok := dict[scopeStr]
|
||||
if !ok {
|
||||
return result, fmt.Errorf("invalid witness scope: %v", scopeStr)
|
||||
}
|
||||
if isGlobal && !(scope == Global) {
|
||||
return result, fmt.Errorf("Global scope can not be combined with other scopes")
|
||||
}
|
||||
result |= scope
|
||||
if scope == Global {
|
||||
isGlobal = true
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
|
45
pkg/core/transaction/witness_scope_test.go
Normal file
45
pkg/core/transaction/witness_scope_test.go
Normal file
|
@ -0,0 +1,45 @@
|
|||
package transaction
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestScopesFromString(t *testing.T) {
|
||||
s, err := ScopesFromString("")
|
||||
require.Error(t, err)
|
||||
|
||||
_, err = ScopesFromString("123")
|
||||
require.Error(t, err)
|
||||
|
||||
s, err = ScopesFromString("Global")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, Global, s)
|
||||
|
||||
s, err = ScopesFromString("CalledByEntry")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, CalledByEntry, s)
|
||||
|
||||
s, err = ScopesFromString("CustomContracts")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, CustomContracts, s)
|
||||
|
||||
s, err = ScopesFromString("CustomGroups")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, CustomGroups, s)
|
||||
|
||||
s, err = ScopesFromString("Calledbyentry,customgroups")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, CalledByEntry|CustomGroups, s)
|
||||
|
||||
_, err = ScopesFromString("global,customgroups")
|
||||
require.Error(t, err)
|
||||
|
||||
_, err = ScopesFromString("calledbyentry,global,customgroups")
|
||||
require.Error(t, err)
|
||||
|
||||
s, err = ScopesFromString("Calledbyentry,customgroups,Customgroups")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, CalledByEntry|CustomGroups, s)
|
||||
}
|
|
@ -18,7 +18,7 @@ import (
|
|||
|
||||
// NEP5Decimals invokes `decimals` NEP5 method on a specified contract.
|
||||
func (c *Client) NEP5Decimals(tokenHash util.Uint160) (int64, error) {
|
||||
result, err := c.InvokeFunction(tokenHash.StringLE(), "decimals", []smartcontract.Parameter{})
|
||||
result, err := c.InvokeFunction(tokenHash.StringLE(), "decimals", []smartcontract.Parameter{}, nil)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
} else if result.State != "HALT" || len(result.Stack) == 0 {
|
||||
|
@ -30,7 +30,7 @@ func (c *Client) NEP5Decimals(tokenHash util.Uint160) (int64, error) {
|
|||
|
||||
// NEP5Name invokes `name` NEP5 method on a specified contract.
|
||||
func (c *Client) NEP5Name(tokenHash util.Uint160) (string, error) {
|
||||
result, err := c.InvokeFunction(tokenHash.StringLE(), "name", []smartcontract.Parameter{})
|
||||
result, err := c.InvokeFunction(tokenHash.StringLE(), "name", []smartcontract.Parameter{}, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
} else if result.State != "HALT" || len(result.Stack) == 0 {
|
||||
|
@ -42,7 +42,7 @@ func (c *Client) NEP5Name(tokenHash util.Uint160) (string, error) {
|
|||
|
||||
// NEP5Symbol invokes `symbol` NEP5 method on a specified contract.
|
||||
func (c *Client) NEP5Symbol(tokenHash util.Uint160) (string, error) {
|
||||
result, err := c.InvokeFunction(tokenHash.StringLE(), "symbol", []smartcontract.Parameter{})
|
||||
result, err := c.InvokeFunction(tokenHash.StringLE(), "symbol", []smartcontract.Parameter{}, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
} else if result.State != "HALT" || len(result.Stack) == 0 {
|
||||
|
@ -54,7 +54,7 @@ func (c *Client) NEP5Symbol(tokenHash util.Uint160) (string, error) {
|
|||
|
||||
// NEP5TotalSupply invokes `totalSupply` NEP5 method on a specified contract.
|
||||
func (c *Client) NEP5TotalSupply(tokenHash util.Uint160) (int64, error) {
|
||||
result, err := c.InvokeFunction(tokenHash.StringLE(), "totalSupply", []smartcontract.Parameter{})
|
||||
result, err := c.InvokeFunction(tokenHash.StringLE(), "totalSupply", []smartcontract.Parameter{}, nil)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
} else if result.State != "HALT" || len(result.Stack) == 0 {
|
||||
|
@ -66,7 +66,7 @@ func (c *Client) NEP5TotalSupply(tokenHash util.Uint160) (int64, error) {
|
|||
|
||||
// NEP5BalanceOf invokes `balanceOf` NEP5 method on a specified contract.
|
||||
func (c *Client) NEP5BalanceOf(tokenHash util.Uint160) (int64, error) {
|
||||
result, err := c.InvokeFunction(tokenHash.StringLE(), "balanceOf", []smartcontract.Parameter{})
|
||||
result, err := c.InvokeFunction(tokenHash.StringLE(), "balanceOf", []smartcontract.Parameter{}, nil)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
} else if result.State != "HALT" || len(result.Stack) == 0 {
|
||||
|
@ -120,7 +120,12 @@ func (c *Client) CreateNEP5TransferTx(acc *wallet.Account, to util.Uint160, toke
|
|||
},
|
||||
}
|
||||
|
||||
result, err := c.InvokeScript(hex.EncodeToString(script))
|
||||
result, err := c.InvokeScript(hex.EncodeToString(script), []transaction.Cosigner{
|
||||
{
|
||||
Account: from,
|
||||
Scopes: transaction.Global,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't add system fee to transaction: %v", err)
|
||||
}
|
||||
|
|
|
@ -338,11 +338,16 @@ func (c *Client) GetVersion() (*result.Version, error) {
|
|||
|
||||
// InvokeScript returns the result of the given script after running it true the VM.
|
||||
// NOTE: This is a test invoke and will not affect the blockchain.
|
||||
func (c *Client) InvokeScript(script string) (*result.Invoke, error) {
|
||||
func (c *Client) InvokeScript(script string, cosigners []transaction.Cosigner) (*result.Invoke, error) {
|
||||
var (
|
||||
params = request.NewRawParams(script)
|
||||
params request.RawParams
|
||||
resp = &result.Invoke{}
|
||||
)
|
||||
if cosigners != nil {
|
||||
params = request.NewRawParams(script, cosigners)
|
||||
} else {
|
||||
params = request.NewRawParams(script)
|
||||
}
|
||||
if err := c.performRequest("invokescript", params, resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -352,11 +357,16 @@ func (c *Client) InvokeScript(script string) (*result.Invoke, error) {
|
|||
// InvokeFunction returns the results after calling the smart contract scripthash
|
||||
// with the given operation and parameters.
|
||||
// NOTE: this is test invoke and will not affect the blockchain.
|
||||
func (c *Client) InvokeFunction(script, operation string, params []smartcontract.Parameter) (*result.Invoke, error) {
|
||||
func (c *Client) InvokeFunction(script, operation string, params []smartcontract.Parameter, cosigners []transaction.Cosigner) (*result.Invoke, error) {
|
||||
var (
|
||||
p = request.NewRawParams(script, operation, params)
|
||||
p request.RawParams
|
||||
resp = &result.Invoke{}
|
||||
)
|
||||
if cosigners != nil {
|
||||
p = request.NewRawParams(script, operation, params, cosigners)
|
||||
} else {
|
||||
p = request.NewRawParams(script, operation, params)
|
||||
}
|
||||
if err := c.performRequest("invokefunction", p, resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -565,7 +565,9 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
|
|||
Type: smartcontract.Hash160Type,
|
||||
Value: hash,
|
||||
},
|
||||
})
|
||||
}, []transaction.Cosigner{{
|
||||
Account: util.Uint160{1, 2, 3},
|
||||
}})
|
||||
},
|
||||
serverResponse: `{"jsonrpc":"2.0","id":1,"result":{"script":"1426ae7c6c9861ec418468c1f0fdc4a7f2963eb89151c10962616c616e63654f6667be39e7b562f60cbfe2aebca375a2e5ee28737caf","state":"HALT","gas_consumed":"0.311","stack":[{"type":"ByteArray","value":"JivsCEQy"}],"tx":"d101361426ae7c6c9861ec418468c1f0fdc4a7f2963eb89151c10962616c616e63654f6667be39e7b562f60cbfe2aebca375a2e5ee28737caf000000000000000000000000"}}`,
|
||||
result: func(c *Client) interface{} {
|
||||
|
@ -591,7 +593,9 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
|
|||
{
|
||||
name: "positive",
|
||||
invoke: func(c *Client) (interface{}, error) {
|
||||
return c.InvokeScript("00046e616d656724058e5e1b6008847cd662728549088a9ee82191")
|
||||
return c.InvokeScript("00046e616d656724058e5e1b6008847cd662728549088a9ee82191", []transaction.Cosigner{{
|
||||
Account: util.Uint160{1, 2, 3},
|
||||
}})
|
||||
},
|
||||
serverResponse: `{"jsonrpc":"2.0","id":1,"result":{"script":"00046e616d656724058e5e1b6008847cd662728549088a9ee82191","state":"HALT","gas_consumed":"0.161","stack":[{"type":"ByteArray","value":"TkVQNSBHQVM="}],"tx":"d1011b00046e616d656724058e5e1b6008847cd662728549088a9ee82191000000000000000000000000"}}`,
|
||||
result: func(c *Client) interface{} {
|
||||
|
@ -876,13 +880,13 @@ var rpcClientErrorCases = map[string][]rpcClientErrorCase{
|
|||
{
|
||||
name: "invokefunction_invalid_params_error",
|
||||
invoke: func(c *Client) (interface{}, error) {
|
||||
return c.InvokeFunction("", "", []smartcontract.Parameter{})
|
||||
return c.InvokeFunction("", "", []smartcontract.Parameter{}, nil)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invokescript_invalid_params_error",
|
||||
invoke: func(c *Client) (interface{}, error) {
|
||||
return c.InvokeScript("")
|
||||
return c.InvokeScript("", nil)
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -1052,13 +1056,13 @@ var rpcClientErrorCases = map[string][]rpcClientErrorCase{
|
|||
{
|
||||
name: "invokefunction_unmarshalling_error",
|
||||
invoke: func(c *Client) (interface{}, error) {
|
||||
return c.InvokeFunction("", "", []smartcontract.Parameter{})
|
||||
return c.InvokeFunction("", "", []smartcontract.Parameter{}, nil)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invokescript_unmarshalling_error",
|
||||
invoke: func(c *Client) (interface{}, error) {
|
||||
return c.InvokeScript("")
|
||||
return c.InvokeScript("", nil)
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
Loading…
Reference in a new issue