cli: use hashes for verifying in invoke* calls

This commit is contained in:
Anna Shaleva 2020-08-06 21:59:03 +03:00
parent d20e54b725
commit 4b351f3123
4 changed files with 105 additions and 57 deletions

View file

@ -68,6 +68,9 @@ import "github.com/nspcc-dev/neo-go/pkg/interop/runtime"
func Main(op string, args []interface{}) {
runtime.Notify("Hello world!")
}`
// hashesForVerifyingSeparator delimits `invoke*` parameters from hashes for verifying
hashesForVerifyingSeparator = "--"
)
// NewCommands returns 'contract' command.
@ -169,8 +172,9 @@ func NewCommands() []cli.Command {
{
Name: "testinvoke",
Usage: "invoke deployed contract on the blockchain (test mode)",
UsageText: "neo-go contract testinvoke -e endpoint scripthash [arguments...]",
Description: `Executes given (as a script hash) deployed script with the given arguments.
UsageText: "neo-go contract testinvoke -e endpoint scripthash [arguments...] [-- hashesForVerifying...]",
Description: `Executes given (as a script hash) deployed script with the given arguments
and hashes to verify using runtime.CheckWitness syscall.
It's very similar to the tesinvokefunction command, but differs in the way
arguments are being passed. This invoker does not accept method parameter
and it passes all given parameters as plain values to the contract, not
@ -189,13 +193,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...] [-- hashesForVerifying...]",
Description: `Executes given (as a script hash) deployed script with the given method,
arguments and hashes to verify using System.Runtime.CheckWitness syscall.
If no method is given "" is passed to the script, if no arguments
are given, an empty array is passed, if no hashes are given, no 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
@ -251,6 +256,15 @@ func NewCommands() []cli.Command {
* 'string\:string' is a string with a value of 'string:string'
* '03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c' is a
key with a value of '03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c'
HashesForVerifying represent a set of Uint160 hashes which are used to verify
hashes in System.Runtime.CheckWitness syscall. To specify hash use its
hex-encoded 160 bit (20 byte) LE representation with optional '0x' prefix.
If no hashes were specified, no array is passed.
Examples:
* '0000000009070e030d0f0e020d0c06050e030c02'
* '0x0000000009070e030d0f0e020d0c06050e030c02'
`,
Action: testInvokeFunction,
Flags: []cli.Flag{
@ -258,9 +272,10 @@ func NewCommands() []cli.Command {
},
},
{
Name: "testinvokescript",
Usage: "Invoke compiled AVM code on the blockchain (test mode, not creating a transaction for it)",
Action: testInvokeScript,
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 testcontract.avm [-- hashesForVerifying...]",
Action: testInvokeScript,
Flags: []cli.Flag{
endpointFlag,
cli.StringFlag{
@ -407,13 +422,15 @@ func invokeFunction(ctx *cli.Context) error {
func invokeInternal(ctx *cli.Context, withMethod bool, 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
hashesForVerifying []util.Uint160
hashesForVerifyingStart = 0
resp *result.Invoke
acc *wallet.Account
)
endpoint := ctx.String("endpoint")
@ -435,6 +452,13 @@ func invokeInternal(ctx *cli.Context, withMethod bool, signAndPush bool) error {
}
if len(args) > paramsStart {
for k, s := range args[paramsStart:] {
if s == hashesForVerifyingSeparator {
if signAndPush {
return cli.NewExitError("adding hashes for verifying available for test invokes only", 1)
}
hashesForVerifyingStart = 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)
@ -443,6 +467,16 @@ func invokeInternal(ctx *cli.Context, withMethod bool, signAndPush bool) error {
}
}
if len(args) >= hashesForVerifyingStart && hashesForVerifyingStart > 0 {
for i, c := range args[hashesForVerifyingStart:] {
h, err := parseUint160(c)
if err != nil {
return cli.NewExitError(fmt.Errorf("failed to parse hash for verifying #%d: %v", i+1, err), 1)
}
hashesForVerifying = append(hashesForVerifying, h)
}
}
if signAndPush {
gas = flags.Fixed8FromContext(ctx, "gas")
acc, err = getAccFromContext(ctx)
@ -456,9 +490,9 @@ func invokeInternal(ctx *cli.Context, withMethod bool, signAndPush bool) error {
}
if withMethod {
resp, err = c.InvokeFunction(script, operation, params)
resp, err = c.InvokeFunction(script, operation, params, hashesForVerifying)
} else {
resp, err = c.Invoke(script, params)
resp, err = c.Invoke(script, params, hashesForVerifying)
}
if err != nil {
return cli.NewExitError(err, 1)
@ -503,13 +537,25 @@ func testInvokeScript(ctx *cli.Context) error {
return cli.NewExitError(err, 1)
}
args := ctx.Args()
var hashesForVerifying []util.Uint160
if args.Present() {
for i, c := range args[:] {
h, err := parseUint160(c)
if err != nil {
return cli.NewExitError(fmt.Errorf("failed to parse hash for verifying #%d: %v", i+1, err), 1)
}
hashesForVerifying = append(hashesForVerifying, h)
}
}
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, hashesForVerifying)
if err != nil {
return cli.NewExitError(err, 1)
}
@ -524,6 +570,13 @@ func testInvokeScript(ctx *cli.Context) error {
return nil
}
func parseUint160(s string) (util.Uint160, error) {
if len(s) == 2*util.Uint160Size+2 && s[0] == '0' && s[1] == 'x' {
s = s[2:]
}
return util.Uint160DecodeStringLE(s)
}
// ProjectConfig contains project metadata.
type ProjectConfig struct {
Version uint

View file

@ -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 {

View file

@ -394,39 +394,34 @@ 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) {
var (
params = request.NewRawParams(script)
resp = &result.Invoke{}
)
if err := c.performRequest("invokescript", params, resp); err != nil {
return nil, err
}
return resp, nil
func (c *Client) InvokeScript(script string, hashesForVerifying []util.Uint160) (*result.Invoke, error) {
params := request.NewRawParams(script)
return c.invokeSomething("invokescript", params, hashesForVerifying)
}
// 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) {
var (
p = request.NewRawParams(script, operation, params)
resp = &result.Invoke{}
)
if err := c.performRequest("invokefunction", p, resp); err != nil {
return nil, err
}
return resp, nil
func (c *Client) InvokeFunction(script, operation string, params []smartcontract.Parameter, hashesForVerifying []util.Uint160) (*result.Invoke, error) {
p := request.NewRawParams(script, operation, params)
return c.invokeSomething("invokefunction", p, hashesForVerifying)
}
// Invoke returns the results after calling the smart contract scripthash
// with the given parameters.
func (c *Client) Invoke(script string, params []smartcontract.Parameter) (*result.Invoke, error) {
var (
p = request.NewRawParams(script, params)
resp = &result.Invoke{}
)
if err := c.performRequest("invoke", p, resp); err != nil {
// NOTE: this is test invoke and will not affect the blockchain.
func (c *Client) Invoke(script string, params []smartcontract.Parameter, hashesForVerifying []util.Uint160) (*result.Invoke, error) {
p := request.NewRawParams(script, params)
return c.invokeSomething("invoke", p, hashesForVerifying)
}
// invokeSomething is an inner wrapper for Invoke* functions
func (c *Client) invokeSomething(method string, p request.RawParams, hashesForVerifying []util.Uint160) (*result.Invoke, error) {
var resp = new(result.Invoke)
if hashesForVerifying != nil {
p.Values = append(p.Values, hashesForVerifying)
}
if err := c.performRequest(method, p, resp); err != nil {
return nil, err
}
return resp, nil

View file

@ -813,7 +813,7 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
Type: smartcontract.Hash160Type,
Value: hash,
},
})
}, nil)
},
serverResponse: `{"jsonrpc":"2.0","id":1,"result":{"script":"1426ae7c6c9861ec418468c1f0fdc4a7f2963eb89151c10962616c616e63654f6667be39e7b562f60cbfe2aebca375a2e5ee28737caf","state":"HALT","gas_consumed":"0.311","stack":[{"type":"ByteArray","value":"262bec084432"}],"tx":"d101361426ae7c6c9861ec418468c1f0fdc4a7f2963eb89151c10962616c616e63654f6667be39e7b562f60cbfe2aebca375a2e5ee28737caf000000000000000000000000"}}`,
result: func(c *Client) interface{} {
@ -839,7 +839,7 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
{
name: "positive",
invoke: func(c *Client) (interface{}, error) {
return c.InvokeScript("00046e616d656724058e5e1b6008847cd662728549088a9ee82191")
return c.InvokeScript("00046e616d656724058e5e1b6008847cd662728549088a9ee82191", nil)
},
serverResponse: `{"jsonrpc":"2.0","id":1,"result":{"script":"00046e616d656724058e5e1b6008847cd662728549088a9ee82191","state":"HALT","gas_consumed":"0.161","stack":[{"type":"ByteArray","value":"4e45503520474153"}],"tx":"d1011b00046e616d656724058e5e1b6008847cd662728549088a9ee82191000000000000000000000000"}}`,
result: func(c *Client) interface{} {
@ -1186,13 +1186,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)
},
},
{
@ -1392,13 +1392,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)
},
},
{