forked from TrueCloudLab/neoneo-go
Merge pull request #1047 from nspcc-dev/neo3/rpc/invoke
rpc: update invoke* RPC-calls
This commit is contained in:
commit
fe31c7ed2d
15 changed files with 464 additions and 187 deletions
|
@ -15,6 +15,7 @@ import (
|
||||||
"github.com/go-yaml/yaml"
|
"github.com/go-yaml/yaml"
|
||||||
"github.com/nspcc-dev/neo-go/cli/flags"
|
"github.com/nspcc-dev/neo-go/cli/flags"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/compiler"
|
"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/crypto/hash"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpc/client"
|
"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{}) {
|
func Main(op string, args []interface{}) {
|
||||||
runtime.Notify("Hello world!")
|
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.
|
// NewCommands returns 'contract' command.
|
||||||
|
@ -132,31 +136,14 @@ func NewCommands() []cli.Command {
|
||||||
gasFlag,
|
gasFlag,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
Name: "invoke",
|
|
||||||
Usage: "invoke deployed contract on the blockchain",
|
|
||||||
UsageText: "neo-go contract invoke -e endpoint -w wallet [-a address] [-g gas] scripthash [arguments...]",
|
|
||||||
Description: `Executes given (as a script hash) deployed script with the given arguments.
|
|
||||||
See testinvoke documentation for the details about parameters. It differs
|
|
||||||
from testinvoke in that this command sends an invocation transaction to
|
|
||||||
the network.
|
|
||||||
`,
|
|
||||||
Action: invoke,
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
endpointFlag,
|
|
||||||
walletFlag,
|
|
||||||
addressFlag,
|
|
||||||
gasFlag,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
Name: "invokefunction",
|
Name: "invokefunction",
|
||||||
Usage: "invoke deployed contract on the blockchain",
|
Usage: "invoke deployed contract on the blockchain",
|
||||||
UsageText: "neo-go contract invokefunction -e endpoint -w wallet [-a address] [-g gas] scripthash [method] [arguments...]",
|
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 and
|
Description: `Executes given (as a script hash) deployed script with the given method,
|
||||||
and arguments. See testinvokefunction documentation for the details about
|
arguments and cosigners. See testinvokefunction documentation for the details
|
||||||
parameters. It differs from testinvokefunction in that this command sends an
|
about parameters. It differs from testinvokefunction in that this command
|
||||||
invocation transaction to the network.
|
sends an invocation transaction to the network.
|
||||||
`,
|
`,
|
||||||
Action: invokeFunction,
|
Action: invokeFunction,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
|
@ -166,36 +153,17 @@ func NewCommands() []cli.Command {
|
||||||
gasFlag,
|
gasFlag,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
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.
|
|
||||||
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
|
|
||||||
wrapping them them into array like testinvokefunction does. For arguments
|
|
||||||
syntax please refer to the testinvokefunction command help.
|
|
||||||
|
|
||||||
Most of the time (if your contract follows the standard convention of
|
|
||||||
method with array of values parameters) you want to use testinvokefunction
|
|
||||||
command instead of testinvoke.
|
|
||||||
`,
|
|
||||||
Action: testInvoke,
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
endpointFlag,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
Name: "testinvokefunction",
|
Name: "testinvokefunction",
|
||||||
Usage: "invoke deployed contract on the blockchain (test mode)",
|
Usage: "invoke deployed contract on the blockchain (test mode)",
|
||||||
UsageText: "neo-go contract testinvokefunction -e endpoint scripthash [method] [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 and
|
Description: `Executes given (as a script hash) deployed script with the given method,
|
||||||
arguments. If no method is given "" is passed to the script, if no arguments
|
arguments and cosigners. If no method is given "" is passed to the script, if
|
||||||
are given, an empty array is passed. All of the given arguments are
|
no arguments are given, an empty array is passed, if no cosigners are given,
|
||||||
encapsulated into array before invoking the script. The script thus should
|
no array will be passed. All of the given arguments are encapsulated into
|
||||||
follow the regular convention of smart contract arguments (method string and
|
array before invoking the script. The script thus should follow the regular
|
||||||
an array of other arguments).
|
convention of smart contract arguments (method string and an array of other
|
||||||
|
arguments).
|
||||||
|
|
||||||
Arguments always do have regular Neo smart contract parameter types, either
|
Arguments always do have regular Neo smart contract parameter types, either
|
||||||
specified explicitly or being inferred from the value. To specify the type
|
specified explicitly or being inferred from the value. To specify the type
|
||||||
|
@ -251,6 +219,32 @@ func NewCommands() []cli.Command {
|
||||||
* 'string\:string' is a string with a value of 'string:string'
|
* 'string\:string' is a string with a value of 'string:string'
|
||||||
* '03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c' is a
|
* '03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c' is a
|
||||||
key with a value of '03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c'
|
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,
|
Action: testInvokeFunction,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
|
@ -258,8 +252,12 @@ func NewCommands() []cli.Command {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "testinvokescript",
|
Name: "testinvokescript",
|
||||||
Usage: "Invoke compiled AVM code on the blockchain (test mode, not creating a transaction for it)",
|
Usage: "Invoke compiled AVM code on the blockchain (test mode, not creating a transaction for it)",
|
||||||
|
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,
|
Action: testInvokeScript,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
endpointFlag,
|
endpointFlag,
|
||||||
|
@ -388,31 +386,25 @@ func contractCompile(ctx *cli.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func testInvoke(ctx *cli.Context) error {
|
|
||||||
return invokeInternal(ctx, false, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func testInvokeFunction(ctx *cli.Context) error {
|
func testInvokeFunction(ctx *cli.Context) error {
|
||||||
return invokeInternal(ctx, true, false)
|
return invokeInternal(ctx, false)
|
||||||
}
|
|
||||||
|
|
||||||
func invoke(ctx *cli.Context) error {
|
|
||||||
return invokeInternal(ctx, false, true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func invokeFunction(ctx *cli.Context) error {
|
func invokeFunction(ctx *cli.Context) error {
|
||||||
return invokeInternal(ctx, true, true)
|
return invokeInternal(ctx, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func invokeInternal(ctx *cli.Context, withMethod bool, signAndPush bool) error {
|
func invokeInternal(ctx *cli.Context, signAndPush bool) error {
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
gas util.Fixed8
|
gas util.Fixed8
|
||||||
operation string
|
operation string
|
||||||
params = make([]smartcontract.Parameter, 0)
|
params = make([]smartcontract.Parameter, 0)
|
||||||
paramsStart = 1
|
paramsStart = 1
|
||||||
resp *result.Invoke
|
cosigners []transaction.Cosigner
|
||||||
acc *wallet.Account
|
cosignersStart = 0
|
||||||
|
resp *result.Invoke
|
||||||
|
acc *wallet.Account
|
||||||
)
|
)
|
||||||
|
|
||||||
endpoint := ctx.String("endpoint")
|
endpoint := ctx.String("endpoint")
|
||||||
|
@ -425,15 +417,19 @@ func invokeInternal(ctx *cli.Context, withMethod bool, signAndPush bool) error {
|
||||||
return cli.NewExitError(errNoScriptHash, 1)
|
return cli.NewExitError(errNoScriptHash, 1)
|
||||||
}
|
}
|
||||||
script := args[0]
|
script := args[0]
|
||||||
if withMethod {
|
|
||||||
if len(args) <= 1 {
|
if len(args) <= 1 {
|
||||||
return cli.NewExitError(errNoMethod, 1)
|
return cli.NewExitError(errNoMethod, 1)
|
||||||
}
|
|
||||||
operation = args[1]
|
|
||||||
paramsStart++
|
|
||||||
}
|
}
|
||||||
|
operation = args[1]
|
||||||
|
paramsStart++
|
||||||
|
|
||||||
if len(args) > paramsStart {
|
if len(args) > paramsStart {
|
||||||
for k, s := range args[paramsStart:] {
|
for k, s := range args[paramsStart:] {
|
||||||
|
if s == cosignersSeparator {
|
||||||
|
cosignersStart = paramsStart + k + 1
|
||||||
|
break
|
||||||
|
}
|
||||||
param, err := smartcontract.NewParameterFromString(s)
|
param, err := smartcontract.NewParameterFromString(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cli.NewExitError(fmt.Errorf("failed to parse argument #%d: %v", k+paramsStart+1, err), 1)
|
return cli.NewExitError(fmt.Errorf("failed to parse argument #%d: %v", k+paramsStart+1, err), 1)
|
||||||
|
@ -442,6 +438,16 @@ func invokeInternal(ctx *cli.Context, withMethod bool, 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 {
|
if signAndPush {
|
||||||
gas = flags.Fixed8FromContext(ctx, "gas")
|
gas = flags.Fixed8FromContext(ctx, "gas")
|
||||||
acc, err = getAccFromContext(ctx)
|
acc, err = getAccFromContext(ctx)
|
||||||
|
@ -454,11 +460,7 @@ func invokeInternal(ctx *cli.Context, withMethod bool, signAndPush bool) error {
|
||||||
return cli.NewExitError(err, 1)
|
return cli.NewExitError(err, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if withMethod {
|
resp, err = c.InvokeFunction(script, operation, params, cosigners)
|
||||||
resp, err = c.InvokeFunction(script, operation, params)
|
|
||||||
} else {
|
|
||||||
resp, err = c.Invoke(script, params)
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cli.NewExitError(err, 1)
|
return cli.NewExitError(err, 1)
|
||||||
}
|
}
|
||||||
|
@ -502,13 +504,25 @@ func testInvokeScript(ctx *cli.Context) error {
|
||||||
return cli.NewExitError(err, 1)
|
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{})
|
c, err := client.New(context.TODO(), endpoint, client.Options{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cli.NewExitError(err, 1)
|
return cli.NewExitError(err, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
scriptHex := hex.EncodeToString(b)
|
scriptHex := hex.EncodeToString(b)
|
||||||
resp, err := c.InvokeScript(scriptHex)
|
resp, err := c.InvokeScript(scriptHex, cosigners)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cli.NewExitError(err, 1)
|
return cli.NewExitError(err, 1)
|
||||||
}
|
}
|
||||||
|
@ -677,3 +691,26 @@ func parseContractConfig(confFile string) (ProjectConfig, error) {
|
||||||
}
|
}
|
||||||
return conf, nil
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -1246,8 +1246,8 @@ func (bc *Blockchain) GetScriptHashesForVerifying(t *transaction.Transaction) ([
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTestVM returns a VM and a Store setup for a test run of some sort of code.
|
// GetTestVM returns a VM and a Store setup for a test run of some sort of code.
|
||||||
func (bc *Blockchain) GetTestVM() *vm.VM {
|
func (bc *Blockchain) GetTestVM(tx *transaction.Transaction) *vm.VM {
|
||||||
systemInterop := bc.newInteropContext(trigger.Application, bc.dao, nil, nil)
|
systemInterop := bc.newInteropContext(trigger.Application, bc.dao, nil, tx)
|
||||||
vm := SpawnVM(systemInterop)
|
vm := SpawnVM(systemInterop)
|
||||||
vm.SetPriceGetter(getPrice)
|
vm.SetPriceGetter(getPrice)
|
||||||
return vm
|
return vm
|
||||||
|
|
|
@ -41,7 +41,7 @@ type Blockchainer interface {
|
||||||
GetScriptHashesForVerifying(*transaction.Transaction) ([]util.Uint160, error)
|
GetScriptHashesForVerifying(*transaction.Transaction) ([]util.Uint160, error)
|
||||||
GetStorageItem(scripthash util.Uint160, key []byte) *state.StorageItem
|
GetStorageItem(scripthash util.Uint160, key []byte) *state.StorageItem
|
||||||
GetStorageItems(hash util.Uint160) (map[string]*state.StorageItem, error)
|
GetStorageItems(hash util.Uint160) (map[string]*state.StorageItem, error)
|
||||||
GetTestVM() *vm.VM
|
GetTestVM(tx *transaction.Transaction) *vm.VM
|
||||||
GetTransaction(util.Uint256) (*transaction.Transaction, uint32, error)
|
GetTransaction(util.Uint256) (*transaction.Transaction, uint32, error)
|
||||||
mempool.Feer // fee interface
|
mempool.Feer // fee interface
|
||||||
PoolTx(*transaction.Transaction) error
|
PoolTx(*transaction.Transaction) error
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
package transaction
|
package transaction
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
// WitnessScope represents set of witness flags for Transaction cosigner.
|
// WitnessScope represents set of witness flags for Transaction cosigner.
|
||||||
type WitnessScope byte
|
type WitnessScope byte
|
||||||
|
|
||||||
|
@ -17,3 +22,34 @@ const (
|
||||||
// CustomGroups define custom pubkey for group members.
|
// CustomGroups define custom pubkey for group members.
|
||||||
CustomGroups WitnessScope = 0x20
|
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)
|
||||||
|
}
|
|
@ -97,7 +97,7 @@ func (chain testChain) GetScriptHashesForVerifying(*transaction.Transaction) ([]
|
||||||
func (chain testChain) GetStorageItem(scripthash util.Uint160, key []byte) *state.StorageItem {
|
func (chain testChain) GetStorageItem(scripthash util.Uint160, key []byte) *state.StorageItem {
|
||||||
panic("TODO")
|
panic("TODO")
|
||||||
}
|
}
|
||||||
func (chain testChain) GetTestVM() *vm.VM {
|
func (chain testChain) GetTestVM(tx *transaction.Transaction) *vm.VM {
|
||||||
panic("TODO")
|
panic("TODO")
|
||||||
}
|
}
|
||||||
func (chain testChain) GetStorageItems(hash util.Uint160) (map[string]*state.StorageItem, error) {
|
func (chain testChain) GetStorageItems(hash util.Uint160) (map[string]*state.StorageItem, error) {
|
||||||
|
|
|
@ -18,7 +18,7 @@ import (
|
||||||
|
|
||||||
// NEP5Decimals invokes `decimals` NEP5 method on a specified contract.
|
// NEP5Decimals invokes `decimals` NEP5 method on a specified contract.
|
||||||
func (c *Client) NEP5Decimals(tokenHash util.Uint160) (int64, error) {
|
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 {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
} else if result.State != "HALT" || len(result.Stack) == 0 {
|
} 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.
|
// NEP5Name invokes `name` NEP5 method on a specified contract.
|
||||||
func (c *Client) NEP5Name(tokenHash util.Uint160) (string, error) {
|
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 {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
} else if result.State != "HALT" || len(result.Stack) == 0 {
|
} 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.
|
// NEP5Symbol invokes `symbol` NEP5 method on a specified contract.
|
||||||
func (c *Client) NEP5Symbol(tokenHash util.Uint160) (string, error) {
|
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 {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
} else if result.State != "HALT" || len(result.Stack) == 0 {
|
} 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.
|
// NEP5TotalSupply invokes `totalSupply` NEP5 method on a specified contract.
|
||||||
func (c *Client) NEP5TotalSupply(tokenHash util.Uint160) (int64, error) {
|
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 {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
} else if result.State != "HALT" || len(result.Stack) == 0 {
|
} 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.
|
// NEP5BalanceOf invokes `balanceOf` NEP5 method on a specified contract.
|
||||||
func (c *Client) NEP5BalanceOf(tokenHash util.Uint160) (int64, error) {
|
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 {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
} else if result.State != "HALT" || len(result.Stack) == 0 {
|
} 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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("can't add system fee to transaction: %v", err)
|
return nil, fmt.Errorf("can't add system fee to transaction: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -339,11 +339,16 @@ func (c *Client) GetVersion() (*result.Version, error) {
|
||||||
|
|
||||||
// InvokeScript returns the result of the given script after running it true the VM.
|
// 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.
|
// 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 (
|
var (
|
||||||
params = request.NewRawParams(script)
|
params request.RawParams
|
||||||
resp = &result.Invoke{}
|
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 {
|
if err := c.performRequest("invokescript", params, resp); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -353,25 +358,17 @@ func (c *Client) InvokeScript(script string) (*result.Invoke, error) {
|
||||||
// InvokeFunction returns the results after calling the smart contract scripthash
|
// InvokeFunction returns the results after calling the smart contract scripthash
|
||||||
// with the given operation and parameters.
|
// with the given operation and parameters.
|
||||||
// NOTE: this is test invoke and will not affect the blockchain.
|
// 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 (
|
var (
|
||||||
p = request.NewRawParams(script, operation, params)
|
p request.RawParams
|
||||||
resp = &result.Invoke{}
|
resp = &result.Invoke{}
|
||||||
)
|
)
|
||||||
if err := c.performRequest("invokefunction", p, resp); err != nil {
|
if cosigners != nil {
|
||||||
return nil, err
|
p = request.NewRawParams(script, operation, params, cosigners)
|
||||||
|
} else {
|
||||||
|
p = request.NewRawParams(script, operation, params)
|
||||||
}
|
}
|
||||||
return resp, nil
|
if err := c.performRequest("invokefunction", p, resp); err != nil {
|
||||||
}
|
|
||||||
|
|
||||||
// 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 {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return resp, nil
|
return resp, nil
|
||||||
|
|
|
@ -563,7 +563,9 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
|
||||||
Type: smartcontract.Hash160Type,
|
Type: smartcontract.Hash160Type,
|
||||||
Value: hash,
|
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"}}`,
|
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{} {
|
result: func(c *Client) interface{} {
|
||||||
|
@ -589,7 +591,9 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
|
||||||
{
|
{
|
||||||
name: "positive",
|
name: "positive",
|
||||||
invoke: func(c *Client) (interface{}, error) {
|
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"}}`,
|
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{} {
|
result: func(c *Client) interface{} {
|
||||||
|
@ -874,13 +878,13 @@ var rpcClientErrorCases = map[string][]rpcClientErrorCase{
|
||||||
{
|
{
|
||||||
name: "invokefunction_invalid_params_error",
|
name: "invokefunction_invalid_params_error",
|
||||||
invoke: func(c *Client) (interface{}, error) {
|
invoke: func(c *Client) (interface{}, error) {
|
||||||
return c.InvokeFunction("", "", []smartcontract.Parameter{})
|
return c.InvokeFunction("", "", []smartcontract.Parameter{}, nil)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "invokescript_invalid_params_error",
|
name: "invokescript_invalid_params_error",
|
||||||
invoke: func(c *Client) (interface{}, error) {
|
invoke: func(c *Client) (interface{}, error) {
|
||||||
return c.InvokeScript("")
|
return c.InvokeScript("", nil)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -1050,13 +1054,13 @@ var rpcClientErrorCases = map[string][]rpcClientErrorCase{
|
||||||
{
|
{
|
||||||
name: "invokefunction_unmarshalling_error",
|
name: "invokefunction_unmarshalling_error",
|
||||||
invoke: func(c *Client) (interface{}, error) {
|
invoke: func(c *Client) (interface{}, error) {
|
||||||
return c.InvokeFunction("", "", []smartcontract.Parameter{})
|
return c.InvokeFunction("", "", []smartcontract.Parameter{}, nil)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "invokescript_unmarshalling_error",
|
name: "invokescript_unmarshalling_error",
|
||||||
invoke: func(c *Client) (interface{}, error) {
|
invoke: func(c *Client) (interface{}, error) {
|
||||||
return c.InvokeScript("")
|
return c.InvokeScript("", nil)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
@ -65,6 +66,7 @@ const (
|
||||||
TxFilterT
|
TxFilterT
|
||||||
NotificationFilterT
|
NotificationFilterT
|
||||||
ExecutionFilterT
|
ExecutionFilterT
|
||||||
|
Cosigner
|
||||||
)
|
)
|
||||||
|
|
||||||
func (p Param) String() string {
|
func (p Param) String() string {
|
||||||
|
@ -154,6 +156,47 @@ func (p Param) GetBytesHex() ([]byte, error) {
|
||||||
return hex.DecodeString(s)
|
return hex.DecodeString(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetCosigner returns transaction.Cosigner value of the parameter.
|
||||||
|
func (p Param) GetCosigner() (transaction.Cosigner, error) {
|
||||||
|
c, ok := p.Value.(transaction.Cosigner)
|
||||||
|
if !ok {
|
||||||
|
return transaction.Cosigner{}, errors.New("not a cosigner")
|
||||||
|
}
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCosigners returns a slice of transaction.Cosigner with global scope from
|
||||||
|
// array of Uint160 or array of serialized transaction.Cosigner stored in the
|
||||||
|
// parameter.
|
||||||
|
func (p Param) GetCosigners() ([]transaction.Cosigner, error) {
|
||||||
|
hashes, err := p.GetArray()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cosigners := make([]transaction.Cosigner, len(hashes))
|
||||||
|
// try to extract hashes first
|
||||||
|
for i, h := range hashes {
|
||||||
|
var u util.Uint160
|
||||||
|
u, err = h.GetUint160FromHex()
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
cosigners[i] = transaction.Cosigner{
|
||||||
|
Account: u,
|
||||||
|
Scopes: transaction.Global,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
for i, h := range hashes {
|
||||||
|
cosigners[i], err = h.GetCosigner()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cosigners, nil
|
||||||
|
}
|
||||||
|
|
||||||
// UnmarshalJSON implements json.Unmarshaler interface.
|
// UnmarshalJSON implements json.Unmarshaler interface.
|
||||||
func (p *Param) UnmarshalJSON(data []byte) error {
|
func (p *Param) UnmarshalJSON(data []byte) error {
|
||||||
var s string
|
var s string
|
||||||
|
@ -167,6 +210,7 @@ func (p *Param) UnmarshalJSON(data []byte) error {
|
||||||
{TxFilterT, &TxFilter{}},
|
{TxFilterT, &TxFilter{}},
|
||||||
{NotificationFilterT, &NotificationFilter{}},
|
{NotificationFilterT, &NotificationFilter{}},
|
||||||
{ExecutionFilterT, &ExecutionFilter{}},
|
{ExecutionFilterT, &ExecutionFilter{}},
|
||||||
|
{Cosigner, &transaction.Cosigner{}},
|
||||||
{ArrayT, &[]Param{}},
|
{ArrayT, &[]Param{}},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -196,6 +240,8 @@ func (p *Param) UnmarshalJSON(data []byte) error {
|
||||||
} else {
|
} else {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
case *transaction.Cosigner:
|
||||||
|
p.Value = *val
|
||||||
case *[]Param:
|
case *[]Param:
|
||||||
p.Value = *val
|
p.Value = *val
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
@ -19,9 +20,13 @@ func TestParam_UnmarshalJSON(t *testing.T) {
|
||||||
{"cosigner": "f84d6a337fbc3d3a201d41da99e86b479e7a2554"},
|
{"cosigner": "f84d6a337fbc3d3a201d41da99e86b479e7a2554"},
|
||||||
{"sender": "f84d6a337fbc3d3a201d41da99e86b479e7a2554", "cosigner": "f84d6a337fbc3d3a201d41da99e86b479e7a2554"},
|
{"sender": "f84d6a337fbc3d3a201d41da99e86b479e7a2554", "cosigner": "f84d6a337fbc3d3a201d41da99e86b479e7a2554"},
|
||||||
{"contract": "f84d6a337fbc3d3a201d41da99e86b479e7a2554"},
|
{"contract": "f84d6a337fbc3d3a201d41da99e86b479e7a2554"},
|
||||||
{"state": "HALT"}]`
|
{"state": "HALT"},
|
||||||
|
{"account": "0xcadb3dc2faa3ef14a13b619c9a43124755aa2569", "scopes": 0},
|
||||||
|
[{"account": "0xcadb3dc2faa3ef14a13b619c9a43124755aa2569", "scopes": 0}]]`
|
||||||
contr, err := util.Uint160DecodeStringLE("f84d6a337fbc3d3a201d41da99e86b479e7a2554")
|
contr, err := util.Uint160DecodeStringLE("f84d6a337fbc3d3a201d41da99e86b479e7a2554")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
accountHash, err := util.Uint160DecodeStringLE("cadb3dc2faa3ef14a13b619c9a43124755aa2569")
|
||||||
|
require.NoError(t, err)
|
||||||
expected := Params{
|
expected := Params{
|
||||||
{
|
{
|
||||||
Type: StringT,
|
Type: StringT,
|
||||||
|
@ -83,6 +88,25 @@ func TestParam_UnmarshalJSON(t *testing.T) {
|
||||||
Type: ExecutionFilterT,
|
Type: ExecutionFilterT,
|
||||||
Value: ExecutionFilter{State: "HALT"},
|
Value: ExecutionFilter{State: "HALT"},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Type: Cosigner,
|
||||||
|
Value: transaction.Cosigner{
|
||||||
|
Account: accountHash,
|
||||||
|
Scopes: transaction.Global,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: ArrayT,
|
||||||
|
Value: []Param{
|
||||||
|
{
|
||||||
|
Type: Cosigner,
|
||||||
|
Value: transaction.Cosigner{
|
||||||
|
Account: accountHash,
|
||||||
|
Scopes: transaction.Global,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var ps Params
|
var ps Params
|
||||||
|
@ -214,3 +238,67 @@ func TestParamGetBytesHex(t *testing.T) {
|
||||||
_, err = p.GetBytesHex()
|
_, err = p.GetBytesHex()
|
||||||
require.NotNil(t, err)
|
require.NotNil(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParamGetCosigner(t *testing.T) {
|
||||||
|
c := transaction.Cosigner{
|
||||||
|
Account: util.Uint160{1, 2, 3, 4},
|
||||||
|
Scopes: transaction.Global,
|
||||||
|
}
|
||||||
|
p := Param{Type: Cosigner, Value: c}
|
||||||
|
actual, err := p.GetCosigner()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, c, actual)
|
||||||
|
|
||||||
|
p = Param{Type: Cosigner, Value: `{"account": "0xcadb3dc2faa3ef14a13b619c9a43124755aa2569", "scopes": 0}`}
|
||||||
|
_, err = p.GetCosigner()
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParamGetCosigners(t *testing.T) {
|
||||||
|
u1 := util.Uint160{1, 2, 3, 4}
|
||||||
|
u2 := util.Uint160{5, 6, 7, 8}
|
||||||
|
t.Run("from hashes", func(t *testing.T) {
|
||||||
|
p := Param{ArrayT, []Param{
|
||||||
|
{Type: StringT, Value: u1.StringLE()},
|
||||||
|
{Type: StringT, Value: u2.StringLE()},
|
||||||
|
}}
|
||||||
|
actual, err := p.GetCosigners()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 2, len(actual))
|
||||||
|
require.True(t, u1.Equals(actual[0].Account))
|
||||||
|
require.True(t, u2.Equals(actual[1].Account))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("from cosigners", func(t *testing.T) {
|
||||||
|
c1 := transaction.Cosigner{
|
||||||
|
Account: u1,
|
||||||
|
Scopes: transaction.Global,
|
||||||
|
}
|
||||||
|
c2 := transaction.Cosigner{
|
||||||
|
Account: u2,
|
||||||
|
Scopes: transaction.CustomContracts,
|
||||||
|
AllowedContracts: []util.Uint160{
|
||||||
|
{1, 2, 3},
|
||||||
|
{4, 5, 6},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
p := Param{ArrayT, []Param{
|
||||||
|
{Type: Cosigner, Value: c1},
|
||||||
|
{Type: Cosigner, Value: c2},
|
||||||
|
}}
|
||||||
|
actual, err := p.GetCosigners()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 2, len(actual))
|
||||||
|
require.Equal(t, c1, actual[0])
|
||||||
|
require.Equal(t, c2, actual[1])
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("bad format", func(t *testing.T) {
|
||||||
|
p := Param{ArrayT, []Param{
|
||||||
|
{Type: StringT, Value: u1.StringLE()},
|
||||||
|
{Type: StringT, Value: "bla"},
|
||||||
|
}}
|
||||||
|
_, err := p.GetCosigners()
|
||||||
|
require.Error(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -98,7 +98,6 @@ var rpcHandlers = map[string]func(*Server, request.Params) (interface{}, *respon
|
||||||
"getunclaimedgas": (*Server).getUnclaimedGas,
|
"getunclaimedgas": (*Server).getUnclaimedGas,
|
||||||
"getvalidators": (*Server).getValidators,
|
"getvalidators": (*Server).getValidators,
|
||||||
"getversion": (*Server).getVersion,
|
"getversion": (*Server).getVersion,
|
||||||
"invoke": (*Server).invoke,
|
|
||||||
"invokefunction": (*Server).invokeFunction,
|
"invokefunction": (*Server).invokeFunction,
|
||||||
"invokescript": (*Server).invokescript,
|
"invokescript": (*Server).invokescript,
|
||||||
"sendrawtransaction": (*Server).sendrawtransaction,
|
"sendrawtransaction": (*Server).sendrawtransaction,
|
||||||
|
@ -619,7 +618,7 @@ func (s *Server) getDecimals(h util.Uint160, cache map[util.Uint160]int64) (int6
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, response.NewInternalServerError("Can't create script", err)
|
return 0, response.NewInternalServerError("Can't create script", err)
|
||||||
}
|
}
|
||||||
res := s.runScriptInVM(script)
|
res := s.runScriptInVM(script, nil)
|
||||||
if res == nil || res.State != "HALT" || len(res.Stack) == 0 {
|
if res == nil || res.State != "HALT" || len(res.Stack) == 0 {
|
||||||
return 0, response.NewInternalServerError("execution error", errors.New("no result"))
|
return 0, response.NewInternalServerError("execution error", errors.New("no result"))
|
||||||
}
|
}
|
||||||
|
@ -854,32 +853,7 @@ func (s *Server) getValidators(_ request.Params) (interface{}, *response.Error)
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// invoke implements the `invoke` RPC call.
|
// invokeFunction implements the `invokeFunction` RPC call.
|
||||||
func (s *Server) invoke(reqParams request.Params) (interface{}, *response.Error) {
|
|
||||||
scriptHashHex, ok := reqParams.ValueWithType(0, request.StringT)
|
|
||||||
if !ok {
|
|
||||||
return nil, response.ErrInvalidParams
|
|
||||||
}
|
|
||||||
scriptHash, err := scriptHashHex.GetUint160FromHex()
|
|
||||||
if err != nil {
|
|
||||||
return nil, response.ErrInvalidParams
|
|
||||||
}
|
|
||||||
sliceP, ok := reqParams.ValueWithType(1, request.ArrayT)
|
|
||||||
if !ok {
|
|
||||||
return nil, response.ErrInvalidParams
|
|
||||||
}
|
|
||||||
slice, err := sliceP.GetArray()
|
|
||||||
if err != nil {
|
|
||||||
return nil, response.ErrInvalidParams
|
|
||||||
}
|
|
||||||
script, err := request.CreateInvocationScript(scriptHash, slice)
|
|
||||||
if err != nil {
|
|
||||||
return nil, response.NewInternalServerError("can't create invocation script", err)
|
|
||||||
}
|
|
||||||
return s.runScriptInVM(script), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// invokescript implements the `invokescript` RPC call.
|
|
||||||
func (s *Server) invokeFunction(reqParams request.Params) (interface{}, *response.Error) {
|
func (s *Server) invokeFunction(reqParams request.Params) (interface{}, *response.Error) {
|
||||||
scriptHashHex, ok := reqParams.ValueWithType(0, request.StringT)
|
scriptHashHex, ok := reqParams.ValueWithType(0, request.StringT)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -889,11 +863,21 @@ func (s *Server) invokeFunction(reqParams request.Params) (interface{}, *respons
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, response.ErrInvalidParams
|
return nil, response.ErrInvalidParams
|
||||||
}
|
}
|
||||||
script, err := request.CreateFunctionInvocationScript(scriptHash, reqParams[1:])
|
tx := &transaction.Transaction{}
|
||||||
|
checkWitnessHashesIndex := len(reqParams)
|
||||||
|
if checkWitnessHashesIndex > 3 {
|
||||||
|
cosigners, err := reqParams[3].GetCosigners()
|
||||||
|
if err != nil {
|
||||||
|
return nil, response.ErrInvalidParams
|
||||||
|
}
|
||||||
|
tx.Cosigners = cosigners
|
||||||
|
checkWitnessHashesIndex--
|
||||||
|
}
|
||||||
|
script, err := request.CreateFunctionInvocationScript(scriptHash, reqParams[1:checkWitnessHashesIndex])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, response.NewInternalServerError("can't create invocation script", err)
|
return nil, response.NewInternalServerError("can't create invocation script", err)
|
||||||
}
|
}
|
||||||
return s.runScriptInVM(script), nil
|
return s.runScriptInVM(script, tx), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// invokescript implements the `invokescript` RPC call.
|
// invokescript implements the `invokescript` RPC call.
|
||||||
|
@ -907,13 +891,21 @@ func (s *Server) invokescript(reqParams request.Params) (interface{}, *response.
|
||||||
return nil, response.ErrInvalidParams
|
return nil, response.ErrInvalidParams
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.runScriptInVM(script), nil
|
tx := &transaction.Transaction{}
|
||||||
|
if len(reqParams) > 1 {
|
||||||
|
cosigners, err := reqParams[1].GetCosigners()
|
||||||
|
if err != nil {
|
||||||
|
return nil, response.ErrInvalidParams
|
||||||
|
}
|
||||||
|
tx.Cosigners = cosigners
|
||||||
|
}
|
||||||
|
return s.runScriptInVM(script, tx), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// runScriptInVM runs given script in a new test VM and returns the invocation
|
// runScriptInVM runs given script in a new test VM and returns the invocation
|
||||||
// result.
|
// result.
|
||||||
func (s *Server) runScriptInVM(script []byte) *result.Invoke {
|
func (s *Server) runScriptInVM(script []byte, tx *transaction.Transaction) *result.Invoke {
|
||||||
vm := s.chain.GetTestVM()
|
vm := s.chain.GetTestVM(tx)
|
||||||
vm.SetGasLimit(s.config.MaxGasInvoke)
|
vm.SetGasLimit(s.config.MaxGasInvoke)
|
||||||
vm.LoadScriptWithFlags(script, smartcontract.All)
|
vm.LoadScriptWithFlags(script, smartcontract.All)
|
||||||
_ = vm.Run()
|
_ = vm.Run()
|
||||||
|
|
|
@ -572,45 +572,6 @@ var rpcTestCases = map[string][]rpcTestCase{
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"invoke": {
|
|
||||||
{
|
|
||||||
name: "positive",
|
|
||||||
params: `["50befd26fdf6e4d957c11e078b24ebce6291456f", [{"type": "String", "value": "qwerty"}]]`,
|
|
||||||
result: func(e *executor) interface{} { return &result.Invoke{} },
|
|
||||||
check: func(t *testing.T, e *executor, inv interface{}) {
|
|
||||||
res, ok := inv.(*result.Invoke)
|
|
||||||
require.True(t, ok)
|
|
||||||
assert.Equal(t, "0c067177657274790c146f459162ceeb248b071ec157d9e4f6fd26fdbe5041627d5b52", res.Script)
|
|
||||||
assert.NotEqual(t, "", res.State)
|
|
||||||
assert.NotEqual(t, 0, res.GasConsumed)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "no params",
|
|
||||||
params: `[]`,
|
|
||||||
fail: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "not a string",
|
|
||||||
params: `[42, []]`,
|
|
||||||
fail: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "not a scripthash",
|
|
||||||
params: `["qwerty", []]`,
|
|
||||||
fail: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "not an array",
|
|
||||||
params: `["50befd26fdf6e4d957c11e078b24ebce6291456f", 42]`,
|
|
||||||
fail: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "bad params",
|
|
||||||
params: `["50befd26fdf6e4d957c11e078b24ebce6291456f", [{"type": "Integer", "value": "qwerty"}]]`,
|
|
||||||
fail: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"invokefunction": {
|
"invokefunction": {
|
||||||
{
|
{
|
||||||
name: "positive",
|
name: "positive",
|
||||||
|
@ -658,6 +619,55 @@ var rpcTestCases = map[string][]rpcTestCase{
|
||||||
assert.NotEqual(t, 0, res.GasConsumed)
|
assert.NotEqual(t, 0, res.GasConsumed)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "positive, good witness",
|
||||||
|
// script is hex-encoded `test_verify.avm` representation, hashes are hex-encoded LE bytes of hashes used in the contract with `0x` prefix
|
||||||
|
params: `["5707000c14010c030e05060c0d020e0f0d030e070900000000db307068115541f827ec8c21aa270700000011400c140d0f03020900020103070304050201000e060c09db307169115541f827ec8c21aa270700000012401340",["0x0000000009070e030d0f0e020d0c06050e030c01","0x090c060e00010205040307030102000902030f0d"]]`,
|
||||||
|
result: func(e *executor) interface{} { return &result.Invoke{} },
|
||||||
|
check: func(t *testing.T, e *executor, inv interface{}) {
|
||||||
|
res, ok := inv.(*result.Invoke)
|
||||||
|
require.True(t, ok)
|
||||||
|
assert.Equal(t, "HALT", res.State)
|
||||||
|
require.Equal(t, 1, len(res.Stack))
|
||||||
|
require.Equal(t, int64(3), res.Stack[0].Value)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "positive, bad witness of second hash",
|
||||||
|
params: `["5707000c14010c030e05060c0d020e0f0d030e070900000000db307068115541f827ec8c21aa270700000011400c140d0f03020900020103070304050201000e060c09db307169115541f827ec8c21aa270700000012401340",["0x0000000009070e030d0f0e020d0c06050e030c01"]]`,
|
||||||
|
result: func(e *executor) interface{} { return &result.Invoke{} },
|
||||||
|
check: func(t *testing.T, e *executor, inv interface{}) {
|
||||||
|
res, ok := inv.(*result.Invoke)
|
||||||
|
require.True(t, ok)
|
||||||
|
assert.Equal(t, "HALT", res.State)
|
||||||
|
require.Equal(t, 1, len(res.Stack))
|
||||||
|
require.Equal(t, int64(2), res.Stack[0].Value)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "positive, no good hashes",
|
||||||
|
params: `["5707000c14010c030e05060c0d020e0f0d030e070900000000db307068115541f827ec8c21aa270700000011400c140d0f03020900020103070304050201000e060c09db307169115541f827ec8c21aa270700000012401340"]`,
|
||||||
|
result: func(e *executor) interface{} { return &result.Invoke{} },
|
||||||
|
check: func(t *testing.T, e *executor, inv interface{}) {
|
||||||
|
res, ok := inv.(*result.Invoke)
|
||||||
|
require.True(t, ok)
|
||||||
|
assert.Equal(t, "HALT", res.State)
|
||||||
|
require.Equal(t, 1, len(res.Stack))
|
||||||
|
require.Equal(t, int64(1), res.Stack[0].Value)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "positive, bad hashes witness",
|
||||||
|
params: `["5707000c14010c030e05060c0d020e0f0d030e070900000000db307068115541f827ec8c21aa270700000011400c140d0f03020900020103070304050201000e060c09db307169115541f827ec8c21aa270700000012401340",["0x0000000009070e030d0f0e020d0c06050e030c02"]]`,
|
||||||
|
result: func(e *executor) interface{} { return &result.Invoke{} },
|
||||||
|
check: func(t *testing.T, e *executor, inv interface{}) {
|
||||||
|
res, ok := inv.(*result.Invoke)
|
||||||
|
require.True(t, ok)
|
||||||
|
assert.Equal(t, "HALT", res.State)
|
||||||
|
assert.Equal(t, 1, len(res.Stack))
|
||||||
|
assert.Equal(t, int64(1), res.Stack[0].Value)
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "no params",
|
name: "no params",
|
||||||
params: `[]`,
|
params: `[]`,
|
||||||
|
|
BIN
pkg/rpc/server/testdata/test_verify.avm
vendored
Executable file
BIN
pkg/rpc/server/testdata/test_verify.avm
vendored
Executable file
Binary file not shown.
17
pkg/rpc/server/testdata/test_verify.go
vendored
Normal file
17
pkg/rpc/server/testdata/test_verify.go
vendored
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
package testdata
|
||||||
|
|
||||||
|
import "github.com/nspcc-dev/neo-go/pkg/interop/runtime"
|
||||||
|
|
||||||
|
// This contract is used to test `invokescript` and `invokefunction` RPC-calls
|
||||||
|
func Main() int {
|
||||||
|
// h1 and h2 are just random uint160 hashes
|
||||||
|
h1 := []byte{1, 12, 3, 14, 5, 6, 12, 13, 2, 14, 15, 13, 3, 14, 7, 9, 0, 0, 0, 0}
|
||||||
|
if !runtime.CheckWitness(h1) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
h2 := []byte{13, 15, 3, 2, 9, 0, 2, 1, 3, 7, 3, 4, 5, 2, 1, 0, 14, 6, 12, 9}
|
||||||
|
if !runtime.CheckWitness(h2) {
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
return 3
|
||||||
|
}
|
Loading…
Reference in a new issue