Merge pull request #2740 from nspcc-dev/cli-improvement
cli: improve VM CLI a bit more
This commit is contained in:
commit
c3001bc5bd
12 changed files with 807 additions and 338 deletions
|
@ -24,23 +24,148 @@ const (
|
|||
ArrayEndSeparator = "]"
|
||||
)
|
||||
|
||||
const (
|
||||
// ParamsParsingDoc is a documentation for parameters parsing.
|
||||
ParamsParsingDoc = ` Arguments always do have regular Neo smart contract parameter types, either
|
||||
specified explicitly or being inferred from the value. To specify the type
|
||||
manually use "type:value" syntax where the type is one of the following:
|
||||
'signature', 'bool', 'int', 'hash160', 'hash256', 'bytes', 'key' or 'string'.
|
||||
Array types are also supported: use special space-separated '[' and ']'
|
||||
symbols around array values to denote array bounds. Nested arrays are also
|
||||
supported. Null parameter is supported via 'nil' keyword without additional
|
||||
type specification.
|
||||
|
||||
There is ability to provide an argument of 'bytearray' type via file. Use a
|
||||
special 'filebytes' argument type for this with a filepath specified after
|
||||
the colon, e.g. 'filebytes:my_file.txt'.
|
||||
|
||||
Given values are type-checked against given types with the following
|
||||
restrictions applied:
|
||||
* 'signature' type values should be hex-encoded and have a (decoded)
|
||||
length of 64 bytes.
|
||||
* 'bool' type values are 'true' and 'false'.
|
||||
* 'int' values are decimal integers that can be successfully converted
|
||||
from the string.
|
||||
* 'hash160' values are Neo addresses and hex-encoded 20-bytes long (after
|
||||
decoding) strings.
|
||||
* 'hash256' type values should be hex-encoded and have a (decoded)
|
||||
length of 32 bytes.
|
||||
* 'bytes' type values are any hex-encoded things.
|
||||
* 'filebytes' type values are filenames with the argument value inside.
|
||||
* 'key' type values are hex-encoded marshalled public keys.
|
||||
* 'string' type values are any valid UTF-8 strings. In the value's part of
|
||||
the string the colon looses it's special meaning as a separator between
|
||||
type and value and is taken literally.
|
||||
|
||||
If no type is explicitly specified, it is inferred from the value using the
|
||||
following logic:
|
||||
- anything that can be interpreted as a decimal integer gets
|
||||
an 'int' type
|
||||
- 'nil' string gets 'Any' NEP-14 parameter type and nil value which corresponds
|
||||
to Null stackitem
|
||||
- 'true' and 'false' strings get 'bool' type
|
||||
- valid Neo addresses and 20 bytes long hex-encoded strings get 'hash160'
|
||||
type
|
||||
- valid hex-encoded public keys get 'key' type
|
||||
- 32 bytes long hex-encoded values get 'hash256' type
|
||||
- 64 bytes long hex-encoded values get 'signature' type
|
||||
- any other valid hex-encoded values get 'bytes' type
|
||||
- anything else is a 'string'
|
||||
|
||||
Backslash character is used as an escape character and allows to use colon in
|
||||
an implicitly typed string. For any other characters it has no special
|
||||
meaning, to get a literal backslash in the string use the '\\' sequence.
|
||||
|
||||
Examples:
|
||||
* 'int:42' is an integer with a value of 42
|
||||
* '42' is an integer with a value of 42
|
||||
* 'nil' is a parameter with Any NEP-14 type and nil value (corresponds to Null stackitem)
|
||||
* 'bad' is a string with a value of 'bad'
|
||||
* 'dead' is a byte array with a value of 'dead'
|
||||
* 'string:dead' is a string with a value of 'dead'
|
||||
* 'filebytes:my_data.txt' is bytes decoded from a content of my_data.txt
|
||||
* 'AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y' is a hash160 with a value
|
||||
of '23ba2703c53263e8d6e522dc32203339dcd8eee9'
|
||||
* '\4\2' is an integer with a value of 42
|
||||
* '\\4\2' is a string with a value of '\42'
|
||||
* 'string:string' is a string with a value of 'string'
|
||||
* 'string\:string' is a string with a value of 'string:string'
|
||||
* '03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c' is a
|
||||
key with a value of '03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c'
|
||||
* '[ a b c ]' is an array with strings values 'a', 'b' and 'c'
|
||||
* '[ a b [ c d ] e ]' is an array with 4 values: string 'a', string 'b',
|
||||
array of two strings 'c' and 'd', string 'e'
|
||||
* '[ ]' is an empty array`
|
||||
|
||||
// SignersParsingDoc is a documentation for signers parsing.
|
||||
SignersParsingDoc = ` Signers represent a set of Uint160 hashes with witness scopes and are used
|
||||
to verify hashes in System.Runtime.CheckWitness syscall. First signer is treated
|
||||
as a sender. To specify signers use signer[:scope] syntax where
|
||||
* 'signer' is a signer's address (as Neo address or hex-encoded 160 bit (20 byte)
|
||||
LE value with or without '0x' prefix).
|
||||
* 'scope' is a comma-separated set of cosigner's scopes, which could be:
|
||||
- 'None' - default witness scope which may be used for the sender
|
||||
to only pay fee for the transaction.
|
||||
- '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.
|
||||
Hashes are be provided as hex-encoded LE value string.
|
||||
At lest one hash must be provided. Multiple hashes
|
||||
are separated by ':'.
|
||||
- 'CustomGroups' - define custom public keys for group members. Public keys are
|
||||
provided as short-form (1-byte prefix + 32 bytes) hex-encoded
|
||||
values. At least one key must be provided. Multiple keys
|
||||
are separated by ':'.
|
||||
|
||||
If no scopes were specified, 'CalledByEntry' used as default. If no signers were
|
||||
specified, no array is passed. Note that scopes are properly handled by
|
||||
neo-go RPC server only. C# implementation does not support scopes capability.
|
||||
|
||||
Examples:
|
||||
* 'NNQk4QXsxvsrr3GSozoWBUxEmfag7B6hz5'
|
||||
* 'NVquyZHoPirw6zAEPvY1ZezxM493zMWQqs:Global'
|
||||
* '0x0000000009070e030d0f0e020d0c06050e030c02'
|
||||
* '0000000009070e030d0f0e020d0c06050e030c02:CalledByEntry,` +
|
||||
`CustomGroups:0206d7495ceb34c197093b5fc1cccf1996ada05e69ef67e765462a7f5d88ee14d0'
|
||||
* '0000000009070e030d0f0e020d0c06050e030c02:CalledByEntry,` +
|
||||
`CustomContracts:1011120009070e030d0f0e020d0c06050e030c02:0x1211100009070e030d0f0e020d0c06050e030c02'`
|
||||
)
|
||||
|
||||
// GetSignersFromContext returns signers parsed from context args starting
|
||||
// from the specified offset.
|
||||
func GetSignersFromContext(ctx *cli.Context, offset int) ([]transaction.Signer, *cli.ExitError) {
|
||||
args := ctx.Args()
|
||||
var signers []transaction.Signer
|
||||
var (
|
||||
signers []transaction.Signer
|
||||
err error
|
||||
)
|
||||
if args.Present() && len(args) > offset {
|
||||
for i, c := range args[offset:] {
|
||||
cosigner, err := parseCosigner(c)
|
||||
if err != nil {
|
||||
return nil, cli.NewExitError(fmt.Errorf("failed to parse signer #%d: %w", i, err), 1)
|
||||
}
|
||||
signers = append(signers, cosigner)
|
||||
signers, err = ParseSigners(args[offset:])
|
||||
if err != nil {
|
||||
return nil, cli.NewExitError(err, 1)
|
||||
}
|
||||
}
|
||||
return signers, nil
|
||||
}
|
||||
|
||||
// ParseSigners returns array of signers parsed from their string representation.
|
||||
func ParseSigners(args []string) ([]transaction.Signer, error) {
|
||||
var signers []transaction.Signer
|
||||
for i, c := range args {
|
||||
cosigner, err := parseCosigner(c)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse signer #%d: %w", i, err)
|
||||
}
|
||||
signers = append(signers, cosigner)
|
||||
}
|
||||
return signers, nil
|
||||
}
|
||||
|
||||
func parseCosigner(c string) (transaction.Signer, error) {
|
||||
var (
|
||||
err error
|
||||
|
|
|
@ -209,109 +209,9 @@ func NewCommands() []cli.Command {
|
|||
follow the regular convention of smart contract arguments (method string and
|
||||
an array of other arguments).
|
||||
|
||||
Arguments always do have regular Neo smart contract parameter types, either
|
||||
specified explicitly or being inferred from the value. To specify the type
|
||||
manually use "type:value" syntax where the type is one of the following:
|
||||
'signature', 'bool', 'int', 'hash160', 'hash256', 'bytes', 'key' or 'string'.
|
||||
Array types are also supported: use special space-separated '[' and ']'
|
||||
symbols around array values to denote array bounds. Nested arrays are also
|
||||
supported.
|
||||
` + cmdargs.ParamsParsingDoc + `
|
||||
|
||||
There is ability to provide an argument of 'bytearray' type via file. Use a
|
||||
special 'filebytes' argument type for this with a filepath specified after
|
||||
the colon, e.g. 'filebytes:my_file.txt'.
|
||||
|
||||
Given values are type-checked against given types with the following
|
||||
restrictions applied:
|
||||
* 'signature' type values should be hex-encoded and have a (decoded)
|
||||
length of 64 bytes.
|
||||
* 'bool' type values are 'true' and 'false'.
|
||||
* 'int' values are decimal integers that can be successfully converted
|
||||
from the string.
|
||||
* 'hash160' values are Neo addresses and hex-encoded 20-bytes long (after
|
||||
decoding) strings.
|
||||
* 'hash256' type values should be hex-encoded and have a (decoded)
|
||||
length of 32 bytes.
|
||||
* 'bytes' type values are any hex-encoded things.
|
||||
* 'filebytes' type values are filenames with the argument value inside.
|
||||
* 'key' type values are hex-encoded marshalled public keys.
|
||||
* 'string' type values are any valid UTF-8 strings. In the value's part of
|
||||
the string the colon looses it's special meaning as a separator between
|
||||
type and value and is taken literally.
|
||||
|
||||
If no type is explicitly specified, it is inferred from the value using the
|
||||
following logic:
|
||||
- anything that can be interpreted as a decimal integer gets
|
||||
an 'int' type
|
||||
- 'true' and 'false' strings get 'bool' type
|
||||
- valid Neo addresses and 20 bytes long hex-encoded strings get 'hash160'
|
||||
type
|
||||
- valid hex-encoded public keys get 'key' type
|
||||
- 32 bytes long hex-encoded values get 'hash256' type
|
||||
- 64 bytes long hex-encoded values get 'signature' type
|
||||
- any other valid hex-encoded values get 'bytes' type
|
||||
- anything else is a 'string'
|
||||
|
||||
Backslash character is used as an escape character and allows to use colon in
|
||||
an implicitly typed string. For any other characters it has no special
|
||||
meaning, to get a literal backslash in the string use the '\\' sequence.
|
||||
|
||||
Examples:
|
||||
* 'int:42' is an integer with a value of 42
|
||||
* '42' is an integer with a value of 42
|
||||
* 'bad' is a string with a value of 'bad'
|
||||
* 'dead' is a byte array with a value of 'dead'
|
||||
* 'string:dead' is a string with a value of 'dead'
|
||||
* 'filebytes:my_data.txt' is bytes decoded from a content of my_data.txt
|
||||
* 'AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y' is a hash160 with a value
|
||||
of '23ba2703c53263e8d6e522dc32203339dcd8eee9'
|
||||
* '\4\2' is an integer with a value of 42
|
||||
* '\\4\2' is a string with a value of '\42'
|
||||
* 'string:string' is a string with a value of 'string'
|
||||
* 'string\:string' is a string with a value of 'string:string'
|
||||
* '03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c' is a
|
||||
key with a value of '03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c'
|
||||
* '[ a b c ]' is an array with strings values 'a', 'b' and 'c'
|
||||
* '[ a b [ c d ] e ]' is an array with 4 values: string 'a', string 'b',
|
||||
array of two strings 'c' and 'd', string 'e'
|
||||
* '[ ]' is an empty array
|
||||
|
||||
Signers represent a set of Uint160 hashes with witness scopes and are used
|
||||
to verify hashes in System.Runtime.CheckWitness syscall. First signer is treated
|
||||
as a sender. To specify signers use signer[:scope] syntax where
|
||||
* 'signer' is a signer's address (as Neo address or hex-encoded 160 bit (20 byte)
|
||||
LE value with or without '0x' prefix).
|
||||
* 'scope' is a comma-separated set of cosigner's scopes, which could be:
|
||||
- 'None' - default witness scope which may be used for the sender
|
||||
to only pay fee for the transaction.
|
||||
- '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.
|
||||
Hashes are be provided as hex-encoded LE value string.
|
||||
At lest one hash must be provided. Multiple hashes
|
||||
are separated by ':'.
|
||||
- 'CustomGroups' - define custom public keys for group members. Public keys are
|
||||
provided as short-form (1-byte prefix + 32 bytes) hex-encoded
|
||||
values. At least one key must be provided. Multiple keys
|
||||
are separated by ':'.
|
||||
|
||||
If no scopes were specified, 'CalledByEntry' used as default. If no signers were
|
||||
specified, no array is passed. Note that scopes are properly handled by
|
||||
neo-go RPC server only. C# implementation does not support scopes capability.
|
||||
|
||||
Examples:
|
||||
* 'NNQk4QXsxvsrr3GSozoWBUxEmfag7B6hz5'
|
||||
* 'NVquyZHoPirw6zAEPvY1ZezxM493zMWQqs:Global'
|
||||
* '0x0000000009070e030d0f0e020d0c06050e030c02'
|
||||
* '0000000009070e030d0f0e020d0c06050e030c02:CalledByEntry,` +
|
||||
`CustomGroups:0206d7495ceb34c197093b5fc1cccf1996ada05e69ef67e765462a7f5d88ee14d0'
|
||||
* '0000000009070e030d0f0e020d0c06050e030c02:CalledByEntry,` +
|
||||
`CustomContracts:1011120009070e030d0f0e020d0c06050e030c02:0x1211100009070e030d0f0e020d0c06050e030c02'
|
||||
` + cmdargs.SignersParsingDoc + `
|
||||
`,
|
||||
Action: testInvokeFunction,
|
||||
Flags: testInvokeFunctionFlags,
|
||||
|
|
560
cli/vm/cli.go
560
cli/vm/cli.go
|
@ -18,8 +18,10 @@ import (
|
|||
|
||||
"github.com/chzyer/readline"
|
||||
"github.com/kballard/go-shellquote"
|
||||
"github.com/nspcc-dev/neo-go/cli/cmdargs"
|
||||
"github.com/nspcc-dev/neo-go/cli/flags"
|
||||
"github.com/nspcc-dev/neo-go/cli/options"
|
||||
"github.com/nspcc-dev/neo-go/cli/paramcontext"
|
||||
"github.com/nspcc-dev/neo-go/pkg/compiler"
|
||||
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core"
|
||||
|
@ -27,11 +29,13 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/core/interop/runtime"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/storage"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/storage/dbconfig"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/bigint"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util/slice"
|
||||
|
@ -50,11 +54,6 @@ const (
|
|||
exitFuncKey = "exitFunc"
|
||||
readlineInstanceKey = "readlineKey"
|
||||
printLogoKey = "printLogoKey"
|
||||
boolType = "bool"
|
||||
boolFalse = "false"
|
||||
boolTrue = "true"
|
||||
intType = "int"
|
||||
stringType = "string"
|
||||
)
|
||||
|
||||
// Various flag names.
|
||||
|
@ -75,184 +74,243 @@ var commands = []cli.Command{
|
|||
{
|
||||
Name: "exit",
|
||||
Usage: "Exit the VM prompt",
|
||||
Description: "Exit the VM prompt",
|
||||
UsageText: "exit",
|
||||
Description: "Exit the VM prompt.",
|
||||
Action: handleExit,
|
||||
},
|
||||
{
|
||||
Name: "ip",
|
||||
Usage: "Show current instruction",
|
||||
Description: "Show current instruction",
|
||||
UsageText: "ip",
|
||||
Description: "Show current instruction.",
|
||||
Action: handleIP,
|
||||
},
|
||||
{
|
||||
Name: "break",
|
||||
Usage: "Place a breakpoint",
|
||||
UsageText: `break <ip>`,
|
||||
Description: `break <ip>
|
||||
<ip> is mandatory parameter, example:
|
||||
Description: `<ip> is mandatory parameter.
|
||||
|
||||
Example:
|
||||
> break 12`,
|
||||
Action: handleBreak,
|
||||
},
|
||||
{
|
||||
Name: "jump",
|
||||
Usage: "Jump to the specified instruction (absolute IP value)",
|
||||
UsageText: `jump <ip>`,
|
||||
Description: `<ip> is mandatory parameter (absolute IP value).
|
||||
|
||||
Example:
|
||||
> jump 12`,
|
||||
Action: handleJump,
|
||||
},
|
||||
{
|
||||
Name: "estack",
|
||||
Usage: "Show evaluation stack contents",
|
||||
Description: "Show evaluation stack contents",
|
||||
UsageText: "estack",
|
||||
Description: "Show evaluation stack contents.",
|
||||
Action: handleXStack,
|
||||
},
|
||||
{
|
||||
Name: "istack",
|
||||
Usage: "Show invocation stack contents",
|
||||
Description: "Show invocation stack contents",
|
||||
UsageText: "istack",
|
||||
Description: "Show invocation stack contents.",
|
||||
Action: handleXStack,
|
||||
},
|
||||
{
|
||||
Name: "sslot",
|
||||
Usage: "Show static slot contents",
|
||||
Description: "Show static slot contents",
|
||||
UsageText: "sslot",
|
||||
Description: "Show static slot contents.",
|
||||
Action: handleSlots,
|
||||
},
|
||||
{
|
||||
Name: "lslot",
|
||||
Usage: "Show local slot contents",
|
||||
UsageText: "lslot",
|
||||
Description: "Show local slot contents",
|
||||
Action: handleSlots,
|
||||
},
|
||||
{
|
||||
Name: "aslot",
|
||||
Usage: "Show arguments slot contents",
|
||||
Description: "Show arguments slot contents",
|
||||
UsageText: "aslot",
|
||||
Description: "Show arguments slot contents.",
|
||||
Action: handleSlots,
|
||||
},
|
||||
{
|
||||
Name: "loadnef",
|
||||
Usage: "Load a NEF-consistent script into the VM",
|
||||
UsageText: `loadnef <file> <manifest>`,
|
||||
Usage: "Load a NEF-consistent script into the VM optionally attaching to it provided signers with scopes",
|
||||
UsageText: `loadnef [--historic <height>] <file> <manifest> [<signer-with-scope>, ...]`,
|
||||
Flags: []cli.Flag{historicFlag},
|
||||
Description: `loadnef [--historic <height>] <file> <manifest>
|
||||
both parameters are mandatory, example:
|
||||
Description: `<file> and <manifest> parameters are mandatory.
|
||||
|
||||
` + cmdargs.SignersParsingDoc + `
|
||||
|
||||
Example:
|
||||
> loadnef /path/to/script.nef /path/to/manifest.json`,
|
||||
Action: handleLoadNEF,
|
||||
},
|
||||
{
|
||||
Name: "loadbase64",
|
||||
Usage: "Load a base64-encoded script string into the VM",
|
||||
UsageText: `loadbase64 [--historic <height>] <string>`,
|
||||
Usage: "Load a base64-encoded script string into the VM optionally attaching to it provided signers with scopes",
|
||||
UsageText: `loadbase64 [--historic <height>] <string> [<signer-with-scope>, ...]`,
|
||||
Flags: []cli.Flag{historicFlag},
|
||||
Description: `loadbase64 [--historic <height>] <string>
|
||||
Description: `<string> is mandatory parameter.
|
||||
|
||||
<string> is mandatory parameter, example:
|
||||
` + cmdargs.SignersParsingDoc + `
|
||||
|
||||
Example:
|
||||
> loadbase64 AwAQpdToAAAADBQV9ehtQR1OrVZVhtHtoUHRfoE+agwUzmFvf3Rhfg/EuAVYOvJgKiON9j8TwAwIdHJhbnNmZXIMFDt9NxHG8Mz5sdypA9G/odiW8SOMQWJ9W1I4`,
|
||||
Action: handleLoadBase64,
|
||||
},
|
||||
{
|
||||
Name: "loadhex",
|
||||
Usage: "Load a hex-encoded script string into the VM",
|
||||
UsageText: `loadhex [--historic <height>] <string>`,
|
||||
Usage: "Load a hex-encoded script string into the VM optionally attaching to it provided signers with scopes",
|
||||
UsageText: `loadhex [--historic <height>] <string> [<signer-with-scope>, ...]`,
|
||||
Flags: []cli.Flag{historicFlag},
|
||||
Description: `loadhex [--historic <height>] <string>
|
||||
Description: `<string> is mandatory parameter.
|
||||
|
||||
<string> is mandatory parameter, example:
|
||||
` + cmdargs.SignersParsingDoc + `
|
||||
|
||||
Example:
|
||||
> loadhex 0c0c48656c6c6f20776f726c6421`,
|
||||
Action: handleLoadHex,
|
||||
},
|
||||
{
|
||||
Name: "loadgo",
|
||||
Usage: "Compile and load a Go file with the manifest into the VM",
|
||||
UsageText: `loadgo [--historic <height>] <file>`,
|
||||
Usage: "Compile and load a Go file with the manifest into the VM optionally attaching to it provided signers with scopes",
|
||||
UsageText: `loadgo [--historic <height>] <file> [<signer-with-scope>, ...]`,
|
||||
Flags: []cli.Flag{historicFlag},
|
||||
Description: `loadgo [--historic <height>] <file>
|
||||
Description: `<file> is mandatory parameter.
|
||||
|
||||
<file> is mandatory parameter, example:
|
||||
` + cmdargs.SignersParsingDoc + `
|
||||
|
||||
Example:
|
||||
> loadgo /path/to/file.go`,
|
||||
Action: handleLoadGo,
|
||||
},
|
||||
{
|
||||
Name: "reset",
|
||||
Usage: "Unload compiled script from the VM and reset context to proper (possibly, historic) state",
|
||||
Flags: []cli.Flag{historicFlag},
|
||||
Action: handleReset,
|
||||
Name: "loadtx",
|
||||
Usage: "Load transaction into the VM from chain or from parameter context file",
|
||||
UsageText: `loadtx [--historic <height>] <file-or-hash>`,
|
||||
Flags: []cli.Flag{historicFlag},
|
||||
Description: `Load transaction into the VM from chain or from parameter context file.
|
||||
The transaction script will be loaded into VM; the resulting execution context will use the provided transaction as script container including its signers, hash and nonce.
|
||||
|
||||
<file-or-hash> is mandatory parameter.
|
||||
|
||||
Example:
|
||||
> loadtx /path/to/file`,
|
||||
Action: handleLoadTx,
|
||||
},
|
||||
{
|
||||
Name: "loaddeployed",
|
||||
Usage: "Load deployed contract into the VM from chain optionally attaching to it provided signers with scopes",
|
||||
UsageText: `loaddeployed [--historic <height>] <hash-or-address-or-id> [<signer-with-scope>, ...]`,
|
||||
Flags: []cli.Flag{historicFlag},
|
||||
Description: `Load deployed contract into the VM from chain optionally attaching to it provided signers with scopes.
|
||||
If '--historic' flag specified, then the historic contract state (historic script and manifest) will be loaded.
|
||||
|
||||
<hash-or-address-or-id> is mandatory parameter.
|
||||
|
||||
` + cmdargs.SignersParsingDoc + `
|
||||
|
||||
Example:
|
||||
> loaddeployed 0x0000000009070e030d0f0e020d0c06050e030c02`,
|
||||
Action: handleLoadDeployed,
|
||||
},
|
||||
{
|
||||
Name: "reset",
|
||||
Usage: "Unload compiled script from the VM and reset context to proper (possibly, historic) state",
|
||||
UsageText: "reset",
|
||||
Flags: []cli.Flag{historicFlag},
|
||||
Description: "Unload compiled script from the VM and reset context to proper (possibly, historic) state.",
|
||||
Action: handleReset,
|
||||
},
|
||||
{
|
||||
Name: "parse",
|
||||
Usage: "Parse provided argument and convert it into other possible formats",
|
||||
UsageText: `parse <arg>`,
|
||||
Description: `parse <arg>
|
||||
|
||||
<arg> is an argument which is tried to be interpreted as an item of different types
|
||||
Description: `<arg> is an argument which is tried to be interpreted as an item of different types
|
||||
and converted to other formats. Strings are escaped and output in quotes.`,
|
||||
Action: handleParse,
|
||||
},
|
||||
{
|
||||
Name: "run",
|
||||
Usage: "Execute the current loaded script",
|
||||
Usage: "Usage Execute the current loaded script",
|
||||
UsageText: `run [<method> [<parameter>...]]`,
|
||||
Description: `run [<method> [<parameter>...]]
|
||||
|
||||
<method> is a contract method, specified in manifest. It can be '_' which will push
|
||||
Description: `<method> is a contract method, specified in manifest. It can be '_' which will push
|
||||
parameters onto the stack and execute from the current offset.
|
||||
<parameter> is a parameter (can be repeated multiple times) that can be specified
|
||||
as <type>:<value>, where type can be:
|
||||
'` + boolType + `': supports '` + boolFalse + `' and '` + boolTrue + `' values
|
||||
'` + intType + `': supports integers as values
|
||||
'` + stringType + `': supports strings as values (that are pushed as a byte array
|
||||
values to the stack)
|
||||
or can be just <value>, for which the type will be detected automatically
|
||||
following these rules: '` + boolTrue + `' and '` + boolFalse + `' are treated as respective
|
||||
boolean values, everything that can be converted to integer is treated as
|
||||
integer and everything else is treated like a string.
|
||||
using the same rules as for 'contract testinvokefunction' command:
|
||||
|
||||
` + cmdargs.ParamsParsingDoc + `
|
||||
|
||||
Example:
|
||||
> run put ` + stringType + `:"Something to put"`,
|
||||
> run put int:5 string:some_string_value`,
|
||||
Action: handleRun,
|
||||
},
|
||||
{
|
||||
Name: "cont",
|
||||
Usage: "Continue execution of the current loaded script",
|
||||
Description: "Continue execution of the current loaded script",
|
||||
UsageText: "cont",
|
||||
Description: "Continue execution of the current loaded script.",
|
||||
Action: handleCont,
|
||||
},
|
||||
{
|
||||
Name: "step",
|
||||
Usage: "Step (n) instruction in the program",
|
||||
UsageText: `step [<n>]`,
|
||||
Description: `step [<n>]
|
||||
<n> is optional parameter to specify number of instructions to run, example:
|
||||
Description: `<n> is optional parameter to specify number of instructions to run.
|
||||
|
||||
Example:
|
||||
> step 10`,
|
||||
Action: handleStep,
|
||||
},
|
||||
{
|
||||
Name: "stepinto",
|
||||
Usage: "Stepinto instruction to take in the debugger",
|
||||
Description: `Usage: stepInto
|
||||
example:
|
||||
Name: "stepinto",
|
||||
Usage: "Stepinto instruction to take in the debugger",
|
||||
UsageText: "stepinto",
|
||||
Description: `Stepinto instruction to take in the debugger.
|
||||
|
||||
Example:
|
||||
> stepinto`,
|
||||
Action: handleStepInto,
|
||||
},
|
||||
{
|
||||
Name: "stepout",
|
||||
Usage: "Stepout instruction to take in the debugger",
|
||||
Description: `stepOut
|
||||
example:
|
||||
Name: "stepout",
|
||||
Usage: "Stepout instruction to take in the debugger",
|
||||
UsageText: "stepout",
|
||||
Description: `Stepout instruction to take in the debugger.
|
||||
|
||||
Example:
|
||||
> stepout`,
|
||||
Action: handleStepOut,
|
||||
},
|
||||
{
|
||||
Name: "stepover",
|
||||
Usage: "Stepover instruction to take in the debugger",
|
||||
Description: `stepOver
|
||||
example:
|
||||
Name: "stepover",
|
||||
Usage: "Stepover instruction to take in the debugger",
|
||||
UsageText: "stepover",
|
||||
Description: `Stepover instruction to take in the debugger.
|
||||
|
||||
Example:
|
||||
> stepover`,
|
||||
Action: handleStepOver,
|
||||
},
|
||||
{
|
||||
Name: "ops",
|
||||
Usage: "Dump opcodes of the current loaded program",
|
||||
UsageText: "ops",
|
||||
Description: "Dump opcodes of the current loaded program",
|
||||
Action: handleOps,
|
||||
},
|
||||
{
|
||||
Name: "events",
|
||||
Usage: "Dump events emitted by the current loaded program",
|
||||
UsageText: "events",
|
||||
Description: "Dump events emitted by the current loaded program",
|
||||
Action: handleEvents,
|
||||
},
|
||||
|
@ -266,23 +324,15 @@ example:
|
|||
Usage: "Print the whole blockchain node configuration.",
|
||||
},
|
||||
},
|
||||
Description: `env [-v]
|
||||
|
||||
Dump state of the chain that is used for VM CLI invocations (use -v for verbose node configuration).
|
||||
Description: `Dump state of the chain that is used for VM CLI invocations (use -v for verbose node configuration).
|
||||
|
||||
Example:
|
||||
> env -v`,
|
||||
Action: handleEnv,
|
||||
},
|
||||
{
|
||||
Name: "storage",
|
||||
Usage: "Dump storage of the contract with the specified hash, address or ID as is at the current stage of script invocation. " +
|
||||
"Can be used if no script is loaded. " +
|
||||
"Hex-encoded storage items prefix may be specified (empty by default to return the whole set of storage items). " +
|
||||
"If seek prefix is not empty, then it's trimmed from the resulting keys." +
|
||||
"Items are sorted. Backwards seek direction may be specified (false by default, which means forwards storage seek direction). " +
|
||||
"It is possible to dump only those storage items that were added or changed during current script invocation (use --diff flag for it). " +
|
||||
"To dump the whole set of storage changes including removed items use 'changes' command.",
|
||||
Name: "storage",
|
||||
Usage: "Dump storage of the contract with the specified hash, address or ID as is at the current stage of script invocation",
|
||||
UsageText: `storage <hash-or-address-or-id> [<prefix>] [--backwards] [--diff]`,
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
|
@ -294,9 +344,7 @@ Example:
|
|||
Usage: "Dump only those storage items that were added or changed during the current script invocation. Note that this call won't show removed storage items, use 'changes' command for that.",
|
||||
},
|
||||
},
|
||||
Description: `storage <hash-or-address-or-id> <prefix> [--backwards] [--diff]
|
||||
|
||||
Dump storage of the contract with the specified hash, address or ID as is at the current stage of script invocation.
|
||||
Description: `Dump storage of the contract with the specified hash, address or ID as is at the current stage of script invocation.
|
||||
Can be used if no script is loaded.
|
||||
Hex-encoded storage items prefix may be specified (empty by default to return the whole set of storage items).
|
||||
If seek prefix is not empty, then it's trimmed from the resulting keys.
|
||||
|
@ -309,16 +357,10 @@ Example:
|
|||
Action: handleStorage,
|
||||
},
|
||||
{
|
||||
Name: "changes",
|
||||
Usage: "Dump storage changes as is at the current stage of loaded script invocation. " +
|
||||
"If no script is loaded or executed, then no changes are present. " +
|
||||
"The contract hash, address or ID may be specified as the first parameter to dump the specified contract storage changes. " +
|
||||
"Hex-encoded search prefix (without contract ID) may be specified to dump matching storage changes. " +
|
||||
"Resulting values are not sorted.",
|
||||
Name: "changes",
|
||||
Usage: "Dump storage changes as is at the current stage of loaded script invocation",
|
||||
UsageText: `changes [<hash-or-address-or-id> [<prefix>]]`,
|
||||
Description: `changes [<hash-or-address-or-id> [<prefix>]]
|
||||
|
||||
Dump storage changes as is at the current stage of loaded script invocation.
|
||||
Description: `Dump storage changes as is at the current stage of loaded script invocation.
|
||||
If no script is loaded or executed, then no changes are present.
|
||||
The contract hash, address or ID may be specified as the first parameter to dump the specified contract storage changes.
|
||||
Hex-encoded search prefix (without contract ID) may be specified to dump matching storage changes.
|
||||
|
@ -521,21 +563,44 @@ func handleBreak(c *cli.Context) error {
|
|||
if !checkVMIsReady(c.App) {
|
||||
return nil
|
||||
}
|
||||
v := getVMFromContext(c.App)
|
||||
args := c.Args()
|
||||
if len(args) != 1 {
|
||||
return fmt.Errorf("%w: <ip>", ErrMissingParameter)
|
||||
}
|
||||
n, err := strconv.Atoi(args[0])
|
||||
n, err := getInstructionParameter(c)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %s", ErrInvalidParameter, err)
|
||||
return err
|
||||
}
|
||||
|
||||
v := getVMFromContext(c.App)
|
||||
v.AddBreakPoint(n)
|
||||
fmt.Fprintf(c.App.Writer, "breakpoint added at instruction %d\n", n)
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleJump(c *cli.Context) error {
|
||||
if !checkVMIsReady(c.App) {
|
||||
return nil
|
||||
}
|
||||
n, err := getInstructionParameter(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
v := getVMFromContext(c.App)
|
||||
v.Context().Jump(n)
|
||||
fmt.Fprintf(c.App.Writer, "jumped to instruction %d\n", n)
|
||||
return nil
|
||||
}
|
||||
|
||||
func getInstructionParameter(c *cli.Context) (int, error) {
|
||||
args := c.Args()
|
||||
if len(args) != 1 {
|
||||
return 0, fmt.Errorf("%w: <ip>", ErrMissingParameter)
|
||||
}
|
||||
n, err := strconv.Atoi(args[0])
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("%w: %s", ErrInvalidParameter, err)
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func handleXStack(c *cli.Context) error {
|
||||
v := getVMFromContext(c.App)
|
||||
var stackDump string
|
||||
|
@ -574,32 +639,44 @@ func handleSlots(c *cli.Context) error {
|
|||
|
||||
// prepareVM retrieves --historic flag from context (if set) and resets app state
|
||||
// (to the specified historic height if given).
|
||||
func prepareVM(c *cli.Context) error {
|
||||
func prepareVM(c *cli.Context, tx *transaction.Transaction) error {
|
||||
if c.IsSet(historicFlagFullName) {
|
||||
height := c.Int(historicFlagFullName)
|
||||
return resetState(c.App, uint32(height))
|
||||
return resetState(c.App, tx, uint32(height))
|
||||
}
|
||||
|
||||
return resetState(c.App)
|
||||
return resetState(c.App, tx)
|
||||
}
|
||||
|
||||
func handleLoadNEF(c *cli.Context) error {
|
||||
err := prepareVM(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v := getVMFromContext(c.App)
|
||||
args := c.Args()
|
||||
if len(args) < 2 {
|
||||
return fmt.Errorf("%w: <file> <manifest>", ErrMissingParameter)
|
||||
}
|
||||
if err := v.LoadFileWithFlags(args[0], callflag.All); err != nil {
|
||||
return fmt.Errorf("failed to read nef: %w", err)
|
||||
b, err := os.ReadFile(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nef, err := nef.FileFromBytes(b)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decode NEF file: %w", err)
|
||||
}
|
||||
m, err := getManifestFromFile(args[1])
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read manifest: %w", err)
|
||||
}
|
||||
var signers []transaction.Signer
|
||||
if len(args) > 2 {
|
||||
signers, err = cmdargs.ParseSigners(c.Args()[2:])
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %v", ErrInvalidParameter, err)
|
||||
}
|
||||
}
|
||||
err = prepareVM(c, createFakeTransaction(nef.Script, signers))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v := getVMFromContext(c.App)
|
||||
fmt.Fprintf(c.App.Writer, "READY: loaded %d instructions\n", v.Context().LenInstr())
|
||||
setManifestInContext(c.App, m)
|
||||
changePrompt(c.App)
|
||||
|
@ -607,11 +684,6 @@ func handleLoadNEF(c *cli.Context) error {
|
|||
}
|
||||
|
||||
func handleLoadBase64(c *cli.Context) error {
|
||||
err := prepareVM(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v := getVMFromContext(c.App)
|
||||
args := c.Args()
|
||||
if len(args) < 1 {
|
||||
return fmt.Errorf("%w: <string>", ErrMissingParameter)
|
||||
|
@ -620,18 +692,32 @@ func handleLoadBase64(c *cli.Context) error {
|
|||
if err != nil {
|
||||
return fmt.Errorf("%w: %s", ErrInvalidParameter, err)
|
||||
}
|
||||
v.LoadWithFlags(b, callflag.All)
|
||||
var signers []transaction.Signer
|
||||
if len(args) > 1 {
|
||||
signers, err = cmdargs.ParseSigners(args[1:])
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %v", ErrInvalidParameter, err)
|
||||
}
|
||||
}
|
||||
err = prepareVM(c, createFakeTransaction(b, signers))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v := getVMFromContext(c.App)
|
||||
fmt.Fprintf(c.App.Writer, "READY: loaded %d instructions\n", v.Context().LenInstr())
|
||||
changePrompt(c.App)
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleLoadHex(c *cli.Context) error {
|
||||
err := prepareVM(c)
|
||||
if err != nil {
|
||||
return err
|
||||
// createFakeTransaction creates fake transaction with prefilled script, VUB and signers.
|
||||
func createFakeTransaction(script []byte, signers []transaction.Signer) *transaction.Transaction {
|
||||
return &transaction.Transaction{
|
||||
Script: script,
|
||||
Signers: signers,
|
||||
}
|
||||
v := getVMFromContext(c.App)
|
||||
}
|
||||
|
||||
func handleLoadHex(c *cli.Context) error {
|
||||
args := c.Args()
|
||||
if len(args) < 1 {
|
||||
return fmt.Errorf("%w: <string>", ErrMissingParameter)
|
||||
|
@ -640,18 +726,24 @@ func handleLoadHex(c *cli.Context) error {
|
|||
if err != nil {
|
||||
return fmt.Errorf("%w: %s", ErrInvalidParameter, err)
|
||||
}
|
||||
v.LoadWithFlags(b, callflag.All)
|
||||
var signers []transaction.Signer
|
||||
if len(args) > 1 {
|
||||
signers, err = cmdargs.ParseSigners(args[1:])
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %v", ErrInvalidParameter, err)
|
||||
}
|
||||
}
|
||||
err = prepareVM(c, createFakeTransaction(b, signers))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v := getVMFromContext(c.App)
|
||||
fmt.Fprintf(c.App.Writer, "READY: loaded %d instructions\n", v.Context().LenInstr())
|
||||
changePrompt(c.App)
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleLoadGo(c *cli.Context) error {
|
||||
err := prepareVM(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v := getVMFromContext(c.App)
|
||||
args := c.Args()
|
||||
if len(args) < 1 {
|
||||
return fmt.Errorf("%w: <file>", ErrMissingParameter)
|
||||
|
@ -668,16 +760,112 @@ func handleLoadGo(c *cli.Context) error {
|
|||
if err != nil {
|
||||
return fmt.Errorf("can't create manifest: %w", err)
|
||||
}
|
||||
setManifestInContext(c.App, m)
|
||||
var signers []transaction.Signer
|
||||
if len(args) > 1 {
|
||||
signers, err = cmdargs.ParseSigners(args[1:])
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %v", ErrInvalidParameter, err)
|
||||
}
|
||||
}
|
||||
|
||||
v.LoadWithFlags(b.Script, callflag.All)
|
||||
err = prepareVM(c, createFakeTransaction(b.Script, signers))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v := getVMFromContext(c.App)
|
||||
setManifestInContext(c.App, m)
|
||||
fmt.Fprintf(c.App.Writer, "READY: loaded %d instructions\n", v.Context().LenInstr())
|
||||
changePrompt(c.App)
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleLoadTx(c *cli.Context) error {
|
||||
args := c.Args()
|
||||
if len(args) < 1 {
|
||||
return fmt.Errorf("%w: <file-or-hash>", ErrMissingParameter)
|
||||
}
|
||||
|
||||
var (
|
||||
tx *transaction.Transaction
|
||||
err error
|
||||
)
|
||||
h, err := util.Uint256DecodeStringLE(strings.TrimPrefix(args[0], "0x"))
|
||||
if err != nil {
|
||||
pc, err := paramcontext.Read(args[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid tx hash or path to parameter context: %w", err)
|
||||
}
|
||||
var ok bool
|
||||
tx, ok = pc.Verifiable.(*transaction.Transaction)
|
||||
if !ok {
|
||||
return errors.New("failed to retrieve transaction from parameter context: verifiable item is not a transaction")
|
||||
}
|
||||
} else {
|
||||
bc := getChainFromContext(c.App)
|
||||
tx, _, err = bc.GetTransaction(h)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get transaction from chain: %w", err)
|
||||
}
|
||||
}
|
||||
err = prepareVM(c, tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v := getVMFromContext(c.App)
|
||||
fmt.Fprintf(c.App.Writer, "READY: loaded %d instructions\n", v.Context().LenInstr())
|
||||
changePrompt(c.App)
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleLoadDeployed(c *cli.Context) error {
|
||||
err := prepareVM(c, nil) // prepare historic IC if needed (for further historic contract state retrieving).
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !c.Args().Present() {
|
||||
return errors.New("contract hash, address or ID is mandatory argument")
|
||||
}
|
||||
hashOrID := c.Args().Get(0)
|
||||
ic := getInteropContextFromContext(c.App)
|
||||
h, err := flags.ParseAddress(hashOrID)
|
||||
if err != nil {
|
||||
i, err := strconv.ParseInt(hashOrID, 10, 32)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse contract hash, address or ID: %w", err)
|
||||
}
|
||||
bc := getChainFromContext(c.App)
|
||||
h, err = bc.GetContractScriptHash(int32(i)) // @fixme: can be improved after #2702 to retrieve historic state of destroyed contract by ID.
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to retrieve contract hash by ID: %w", err)
|
||||
}
|
||||
}
|
||||
cs, err := ic.GetContract(h) // will return historic contract state.
|
||||
if err != nil {
|
||||
return fmt.Errorf("contract %s not found: %w", h.StringLE(), err)
|
||||
}
|
||||
|
||||
var signers []transaction.Signer
|
||||
if len(c.Args()) > 1 {
|
||||
signers, err = cmdargs.ParseSigners(c.Args()[1:])
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %v", ErrInvalidParameter, err)
|
||||
}
|
||||
}
|
||||
err = prepareVM(c, createFakeTransaction(cs.NEF.Script, signers)) // prepare VM one more time for proper IC initialization.
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ic = getInteropContextFromContext(c.App) // fetch newly-created IC.
|
||||
ic.ReuseVM(ic.VM) // clear previously loaded program and context.
|
||||
ic.VM.LoadScriptWithHash(cs.NEF.Script, cs.Hash, callflag.All)
|
||||
fmt.Fprintf(c.App.Writer, "READY: loaded %d instructions\n", ic.VM.Context().LenInstr())
|
||||
setManifestInContext(c.App, &cs.Manifest)
|
||||
changePrompt(c.App)
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleReset(c *cli.Context) error {
|
||||
err := prepareVM(c)
|
||||
err := prepareVM(c, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -692,8 +880,9 @@ func finalizeInteropContext(app *cli.App) {
|
|||
}
|
||||
|
||||
// resetInteropContext calls finalizer for current interop context and replaces
|
||||
// it with the newly created one.
|
||||
func resetInteropContext(app *cli.App, height ...uint32) error {
|
||||
// it with the newly created one. If transaction is provided, then its script is
|
||||
// loaded into bound VM.
|
||||
func resetInteropContext(app *cli.App, tx *transaction.Transaction, height ...uint32) error {
|
||||
finalizeInteropContext(app)
|
||||
bc := getChainFromContext(app)
|
||||
var (
|
||||
|
@ -701,16 +890,25 @@ func resetInteropContext(app *cli.App, height ...uint32) error {
|
|||
err error
|
||||
)
|
||||
if len(height) != 0 {
|
||||
newIc, err = bc.GetTestHistoricVM(trigger.Application, nil, height[0]+1)
|
||||
if tx != nil {
|
||||
tx.ValidUntilBlock = height[0] + 1
|
||||
}
|
||||
newIc, err = bc.GetTestHistoricVM(trigger.Application, tx, height[0]+1)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create historic VM for height %d: %w", height[0], err)
|
||||
}
|
||||
} else {
|
||||
newIc, err = bc.GetTestVM(trigger.Application, nil, nil)
|
||||
if tx != nil {
|
||||
tx.ValidUntilBlock = bc.BlockHeight() + 1
|
||||
}
|
||||
newIc, err = bc.GetTestVM(trigger.Application, tx, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create VM: %w", err)
|
||||
}
|
||||
}
|
||||
if tx != nil {
|
||||
newIc.VM.LoadWithFlags(tx.Script, callflag.All)
|
||||
}
|
||||
|
||||
setInteropContextInContext(app, newIc)
|
||||
return nil
|
||||
|
@ -723,8 +921,8 @@ func resetManifest(app *cli.App) {
|
|||
|
||||
// resetState resets state of the app (clear interop context and manifest) so that it's ready
|
||||
// to load new program.
|
||||
func resetState(app *cli.App, height ...uint32) error {
|
||||
err := resetInteropContext(app, height...)
|
||||
func resetState(app *cli.App, tx *transaction.Transaction, height ...uint32) error {
|
||||
err := resetInteropContext(app, tx, height...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -757,9 +955,16 @@ func handleRun(c *cli.Context) error {
|
|||
runCurrent = args[0] != "_"
|
||||
)
|
||||
|
||||
params, err = parseArgs(args[1:])
|
||||
_, scParams, err := cmdargs.ParseParams(args[1:], true)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("%w: %v", ErrInvalidParameter, err)
|
||||
}
|
||||
params = make([]stackitem.Item, len(scParams))
|
||||
for i := range scParams {
|
||||
params[i], err = scParams[i].ToStackItem()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to convert parameter #%d to stackitem: %w", i, err)
|
||||
}
|
||||
}
|
||||
if runCurrent {
|
||||
if m == nil {
|
||||
|
@ -1014,29 +1219,11 @@ func handleChanges(c *cli.Context) error {
|
|||
|
||||
// getDumpArgs is a helper function that retrieves contract ID and search prefix (if given).
|
||||
func getDumpArgs(c *cli.Context) (int32, []byte, error) {
|
||||
if !c.Args().Present() {
|
||||
return 0, nil, errors.New("contract hash, address or ID is mandatory argument")
|
||||
}
|
||||
hashOrID := c.Args().Get(0)
|
||||
var (
|
||||
ic = getInteropContextFromContext(c.App)
|
||||
id int32
|
||||
prefix []byte
|
||||
)
|
||||
h, err := flags.ParseAddress(hashOrID)
|
||||
id, err := getContractID(c)
|
||||
if err != nil {
|
||||
i, err := strconv.ParseInt(hashOrID, 10, 32)
|
||||
if err != nil {
|
||||
return 0, nil, fmt.Errorf("failed to parse contract hash, address or ID: %w", err)
|
||||
}
|
||||
id = int32(i)
|
||||
} else {
|
||||
cs, err := ic.GetContract(h)
|
||||
if err != nil {
|
||||
return 0, nil, fmt.Errorf("contract %s not found: %w", h.StringLE(), err)
|
||||
}
|
||||
id = cs.ID
|
||||
return 0, nil, err
|
||||
}
|
||||
var prefix []byte
|
||||
if c.NArg() > 1 {
|
||||
prefix, err = hex.DecodeString(c.Args().Get(1))
|
||||
if err != nil {
|
||||
|
@ -1046,6 +1233,29 @@ func getDumpArgs(c *cli.Context) (int32, []byte, error) {
|
|||
return id, prefix, nil
|
||||
}
|
||||
|
||||
// getContractID returns contract ID parsed from the first argument which can be ID,
|
||||
// hash or address.
|
||||
func getContractID(c *cli.Context) (int32, error) {
|
||||
if !c.Args().Present() {
|
||||
return 0, errors.New("contract hash, address or ID is mandatory argument")
|
||||
}
|
||||
hashOrID := c.Args().Get(0)
|
||||
var ic = getInteropContextFromContext(c.App)
|
||||
h, err := flags.ParseAddress(hashOrID)
|
||||
if err != nil {
|
||||
i, err := strconv.ParseInt(hashOrID, 10, 32)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to parse contract hash, address or ID: %w", err)
|
||||
}
|
||||
return int32(i), nil
|
||||
}
|
||||
cs, err := ic.GetContract(h)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("contract %s not found: %w", h.StringLE(), err)
|
||||
}
|
||||
return cs.ID, nil
|
||||
}
|
||||
|
||||
func dumpEvents(app *cli.App) (string, error) {
|
||||
ic := getInteropContextFromContext(app)
|
||||
if len(ic.Notifications) == 0 {
|
||||
|
@ -1155,48 +1365,6 @@ func Parse(args []string) (string, error) {
|
|||
return buf.String(), nil
|
||||
}
|
||||
|
||||
func parseArgs(args []string) ([]stackitem.Item, error) {
|
||||
items := make([]stackitem.Item, len(args))
|
||||
for i, arg := range args {
|
||||
var typ, value string
|
||||
typeAndVal := strings.Split(arg, ":")
|
||||
if len(typeAndVal) < 2 {
|
||||
if typeAndVal[0] == boolFalse || typeAndVal[0] == boolTrue {
|
||||
typ = boolType
|
||||
} else if _, err := strconv.Atoi(typeAndVal[0]); err == nil {
|
||||
typ = intType
|
||||
} else {
|
||||
typ = stringType
|
||||
}
|
||||
value = typeAndVal[0]
|
||||
} else {
|
||||
typ = typeAndVal[0]
|
||||
value = typeAndVal[1]
|
||||
}
|
||||
|
||||
switch typ {
|
||||
case boolType:
|
||||
if value == boolFalse {
|
||||
items[i] = stackitem.NewBool(false)
|
||||
} else if value == boolTrue {
|
||||
items[i] = stackitem.NewBool(true)
|
||||
} else {
|
||||
return nil, fmt.Errorf("%w: invalid bool value", ErrInvalidParameter)
|
||||
}
|
||||
case intType:
|
||||
val, err := strconv.ParseInt(value, 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: invalid integer value", ErrInvalidParameter)
|
||||
}
|
||||
items[i] = stackitem.NewBigInteger(big.NewInt(val))
|
||||
case stringType:
|
||||
items[i] = stackitem.NewByteArray([]byte(value))
|
||||
}
|
||||
}
|
||||
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const logo = `
|
||||
_ ____________ __________ _ ____ ___
|
||||
/ | / / ____/ __ \ / ____/ __ \ | | / / |/ /
|
||||
|
|
|
@ -5,20 +5,25 @@ import (
|
|||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
gio "io"
|
||||
"math/big"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/chzyer/readline"
|
||||
"github.com/nspcc-dev/neo-go/cli/paramcontext"
|
||||
"github.com/nspcc-dev/neo-go/internal/basicchain"
|
||||
"github.com/nspcc-dev/neo-go/internal/random"
|
||||
"github.com/nspcc-dev/neo-go/pkg/compiler"
|
||||
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/storage"
|
||||
|
@ -277,27 +282,79 @@ func (e *executor) checkSlot(t *testing.T, items ...interface{}) {
|
|||
|
||||
func TestLoad(t *testing.T) {
|
||||
script := []byte{byte(opcode.PUSH3), byte(opcode.PUSH4), byte(opcode.ADD)}
|
||||
|
||||
ownerAddress := "NbrUYaZgyhSkNoRo9ugRyEMdUZxrhkNaWB"
|
||||
ownerAcc, err := address.StringToUint160(ownerAddress)
|
||||
require.NoError(t, err)
|
||||
sideAcc := util.Uint160{1, 2, 3}
|
||||
buff := io.NewBufBinWriter()
|
||||
emit.Bytes(buff.BinWriter, ownerAcc.BytesBE())
|
||||
emit.Syscall(buff.BinWriter, interopnames.SystemRuntimeCheckWitness)
|
||||
checkWitnessScript := buff.Bytes()
|
||||
|
||||
t.Run("loadhex", func(t *testing.T) {
|
||||
e := newTestVMCLI(t)
|
||||
e.runProg(t,
|
||||
"loadhex",
|
||||
"loadhex notahex",
|
||||
"loadhex "+hex.EncodeToString(script))
|
||||
"loadhex "+hex.EncodeToString(script),
|
||||
"loadhex "+hex.EncodeToString(checkWitnessScript)+" "+ownerAddress, // owner:DefaultScope => true
|
||||
"run",
|
||||
"loadhex "+hex.EncodeToString(checkWitnessScript)+" "+ownerAddress+":None", // owner:None => false
|
||||
"run",
|
||||
"loadhex "+hex.EncodeToString(checkWitnessScript)+" "+ownerAcc.StringLE(), // ownerLE:DefaultScope => true
|
||||
"run",
|
||||
"loadhex "+hex.EncodeToString(checkWitnessScript)+" 0x"+ownerAcc.StringLE(), // owner0xLE:DefaultScope => true
|
||||
"run",
|
||||
"loadhex "+hex.EncodeToString(checkWitnessScript)+" "+sideAcc.StringLE(), // sideLE:DefaultScope => false
|
||||
"run",
|
||||
)
|
||||
|
||||
e.checkError(t, ErrMissingParameter)
|
||||
e.checkError(t, ErrInvalidParameter)
|
||||
e.checkNextLine(t, "READY: loaded 3 instructions")
|
||||
e.checkNextLine(t, "READY: loaded \\d+ instructions")
|
||||
e.checkStack(t, true)
|
||||
e.checkNextLine(t, "READY: loaded \\d+ instructions")
|
||||
e.checkStack(t, false)
|
||||
e.checkNextLine(t, "READY: loaded \\d+ instructions")
|
||||
e.checkStack(t, true)
|
||||
e.checkNextLine(t, "READY: loaded \\d+ instructions")
|
||||
e.checkStack(t, true)
|
||||
e.checkNextLine(t, "READY: loaded \\d+ instructions")
|
||||
e.checkStack(t, false)
|
||||
})
|
||||
t.Run("loadbase64", func(t *testing.T) {
|
||||
e := newTestVMCLI(t)
|
||||
e.runProg(t,
|
||||
"loadbase64",
|
||||
"loadbase64 not_a_base64",
|
||||
"loadbase64 "+base64.StdEncoding.EncodeToString(script))
|
||||
"loadbase64 "+base64.StdEncoding.EncodeToString(script),
|
||||
"loadbase64 "+base64.StdEncoding.EncodeToString(checkWitnessScript)+" "+ownerAddress, // owner:DefaultScope => true
|
||||
"run",
|
||||
"loadbase64 "+base64.StdEncoding.EncodeToString(checkWitnessScript)+" "+ownerAddress+":None", // owner:None => false
|
||||
"run",
|
||||
"loadbase64 "+base64.StdEncoding.EncodeToString(checkWitnessScript)+" "+ownerAcc.StringLE(), // ownerLE:DefaultScope => true
|
||||
"run",
|
||||
"loadbase64 "+base64.StdEncoding.EncodeToString(checkWitnessScript)+" 0x"+ownerAcc.StringLE(), // owner0xLE:DefaultScope => true
|
||||
"run",
|
||||
"loadbase64 "+base64.StdEncoding.EncodeToString(checkWitnessScript)+" "+sideAcc.StringLE(), // sideLE:DefaultScope => false
|
||||
"run",
|
||||
)
|
||||
|
||||
e.checkError(t, ErrMissingParameter)
|
||||
e.checkError(t, ErrInvalidParameter)
|
||||
e.checkNextLine(t, "READY: loaded 3 instructions")
|
||||
e.checkNextLine(t, "READY: loaded \\d+ instructions")
|
||||
e.checkStack(t, true)
|
||||
e.checkNextLine(t, "READY: loaded \\d+ instructions")
|
||||
e.checkStack(t, false)
|
||||
e.checkNextLine(t, "READY: loaded \\d+ instructions")
|
||||
e.checkStack(t, true)
|
||||
e.checkNextLine(t, "READY: loaded \\d+ instructions")
|
||||
e.checkStack(t, true)
|
||||
e.checkNextLine(t, "READY: loaded \\d+ instructions")
|
||||
e.checkStack(t, false)
|
||||
})
|
||||
|
||||
src := `package kek
|
||||
|
@ -339,14 +396,7 @@ go 1.17`)
|
|||
checkLoadgo(t, "simple", "vmtestcontract.go", "vmtestcontract_err.go")
|
||||
checkLoadgo(t, "utf-8 with spaces", "тестовый контракт.go", "тестовый контракт с ошибкой.go")
|
||||
|
||||
t.Run("loadgo, check calling flags", func(t *testing.T) {
|
||||
srcAllowNotify := `package kek
|
||||
import "github.com/nspcc-dev/neo-go/pkg/interop/runtime"
|
||||
func Main() int {
|
||||
runtime.Log("Hello, world!")
|
||||
return 1
|
||||
}
|
||||
`
|
||||
prepareLoadgoSrc := func(t *testing.T, srcAllowNotify string) string {
|
||||
filename := filepath.Join(tmpDir, "vmtestcontract.go")
|
||||
require.NoError(t, os.WriteFile(filename, []byte(srcAllowNotify), os.ModePerm))
|
||||
filename = "'" + filename + "'"
|
||||
|
@ -359,6 +409,17 @@ require (
|
|||
replace github.com/nspcc-dev/neo-go/pkg/interop => ` + filepath.Join(wd, "../../pkg/interop") + `
|
||||
go 1.17`)
|
||||
require.NoError(t, os.WriteFile(filepath.Join(tmpDir, "go.mod"), goMod, os.ModePerm))
|
||||
return filename
|
||||
}
|
||||
t.Run("loadgo, check calling flags", func(t *testing.T) {
|
||||
srcAllowNotify := `package kek
|
||||
import "github.com/nspcc-dev/neo-go/pkg/interop/runtime"
|
||||
func Main() int {
|
||||
runtime.Log("Hello, world!")
|
||||
return 1
|
||||
}
|
||||
`
|
||||
filename := prepareLoadgoSrc(t, srcAllowNotify)
|
||||
|
||||
e := newTestVMCLI(t)
|
||||
e.runProg(t,
|
||||
|
@ -367,6 +428,41 @@ go 1.17`)
|
|||
e.checkNextLine(t, "READY: loaded \\d* instructions")
|
||||
e.checkStack(t, 1)
|
||||
})
|
||||
t.Run("loadgo, check signers", func(t *testing.T) {
|
||||
srcCheckWitness := `package kek
|
||||
import (
|
||||
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
|
||||
"github.com/nspcc-dev/neo-go/pkg/interop/util"
|
||||
)
|
||||
func Main() bool {
|
||||
var owner = util.FromAddress("` + ownerAddress + `")
|
||||
return runtime.CheckWitness(owner)
|
||||
}
|
||||
`
|
||||
filename := prepareLoadgoSrc(t, srcCheckWitness)
|
||||
e := newTestVMCLI(t)
|
||||
e.runProg(t,
|
||||
"loadgo "+filename+" "+ownerAddress, // owner:DefaultScope => true
|
||||
"run main",
|
||||
"loadgo "+filename+" "+ownerAddress+":None", // owner:None => false
|
||||
"run main",
|
||||
"loadgo "+filename+" "+ownerAcc.StringLE(), // ownerLE:DefaultScope => true
|
||||
"run main",
|
||||
"loadgo "+filename+" 0x"+ownerAcc.StringLE(), // owner0xLE:DefaultScope => true
|
||||
"run main",
|
||||
"loadgo "+filename+" "+sideAcc.StringLE(), // sideLE:DefaultScope => false
|
||||
"run main")
|
||||
e.checkNextLine(t, "READY: loaded \\d+ instructions")
|
||||
e.checkStack(t, true)
|
||||
e.checkNextLine(t, "READY: loaded \\d+ instructions")
|
||||
e.checkStack(t, false)
|
||||
e.checkNextLine(t, "READY: loaded \\d+ instructions")
|
||||
e.checkStack(t, true)
|
||||
e.checkNextLine(t, "READY: loaded \\d+ instructions")
|
||||
e.checkStack(t, true)
|
||||
e.checkNextLine(t, "READY: loaded \\d+ instructions")
|
||||
e.checkStack(t, false)
|
||||
})
|
||||
t.Run("loadnef", func(t *testing.T) {
|
||||
config.Version = "0.92.0-test"
|
||||
|
||||
|
@ -425,6 +521,9 @@ func TestRunWithDifferentArguments(t *testing.T) {
|
|||
}
|
||||
func GetString(arg string) string {
|
||||
return arg
|
||||
}
|
||||
func GetArr(arg []interface{}) []interface{}{
|
||||
return arg
|
||||
}`
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
|
@ -446,6 +545,7 @@ func TestRunWithDifferentArguments(t *testing.T) {
|
|||
"run _ 1 2",
|
||||
"loadbase64 "+base64.StdEncoding.EncodeToString([]byte{byte(opcode.MUL)}),
|
||||
"run _ 21 2",
|
||||
"loadgo "+filename, "run getArr [ 1 2 3 ]",
|
||||
)
|
||||
|
||||
e.checkNextLine(t, "READY: loaded \\d.* instructions")
|
||||
|
@ -477,6 +577,13 @@ func TestRunWithDifferentArguments(t *testing.T) {
|
|||
|
||||
e.checkNextLine(t, "READY: loaded \\d.* instructions")
|
||||
e.checkStack(t, 42)
|
||||
|
||||
e.checkNextLine(t, "READY: loaded \\d.* instructions")
|
||||
e.checkStack(t, []stackitem.Item{
|
||||
stackitem.NewBigInteger(big.NewInt(1)),
|
||||
stackitem.NewBigInteger(big.NewInt(2)),
|
||||
stackitem.NewBigInteger(big.NewInt(3)),
|
||||
})
|
||||
}
|
||||
|
||||
func TestPrintOps(t *testing.T) {
|
||||
|
@ -986,3 +1093,109 @@ func TestDumpChanges(t *testing.T) {
|
|||
e.checkChange(t, expected[1])
|
||||
e.checkChange(t, expected[2])
|
||||
}
|
||||
|
||||
func TestLoadtx(t *testing.T) {
|
||||
e := newTestVMClIWithState(t)
|
||||
|
||||
b, err := e.cli.chain.GetBlock(e.cli.chain.GetHeaderHash(2)) // Block #2 contains transaction that puts (1,1) pair to storage contract.
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(b.Transactions))
|
||||
tx := b.Transactions[0]
|
||||
|
||||
tmp := filepath.Join(t.TempDir(), "tx.json")
|
||||
require.NoError(t, paramcontext.InitAndSave(netmode.UnitTestNet, tx, nil, tmp))
|
||||
|
||||
e.runProg(t,
|
||||
"loadtx "+tx.Hash().StringLE(), // hash LE
|
||||
"run",
|
||||
"loadtx 0x"+tx.Hash().StringLE(), // hash LE with 0x prefix
|
||||
"run",
|
||||
"loadtx '"+tmp+"'", // Tx from parameter context file.
|
||||
"run",
|
||||
"loadtx", // missing argument
|
||||
"exit",
|
||||
)
|
||||
e.checkNextLine(t, "READY: loaded \\d+ instructions")
|
||||
e.checkStack(t, 1)
|
||||
e.checkNextLine(t, "READY: loaded \\d+ instructions")
|
||||
e.checkStack(t, 1)
|
||||
e.checkNextLine(t, "READY: loaded \\d+ instructions")
|
||||
e.checkStack(t, 1)
|
||||
e.checkError(t, errors.New("missing argument: <file-or-hash>"))
|
||||
}
|
||||
|
||||
func TestLoaddeployed(t *testing.T) {
|
||||
e := newTestVMClIWithState(t)
|
||||
|
||||
h, err := e.cli.chain.GetContractScriptHash(1) // examples/storage/storage.go
|
||||
require.NoError(t, err)
|
||||
ownerAddress := "NbrUYaZgyhSkNoRo9ugRyEMdUZxrhkNaWB" // owner of examples/runtime/runtime.go (taken from deployed contract with ID=2)
|
||||
ownerAcc, err := address.StringToUint160(ownerAddress)
|
||||
require.NoError(t, err)
|
||||
sideAcc := util.Uint160{1, 2, 3}
|
||||
|
||||
e.runProg(t,
|
||||
"loaddeployed "+h.StringLE(), // hash LE
|
||||
"run get 1",
|
||||
"loaddeployed 0x"+h.StringLE(), // hash LE with 0x prefix
|
||||
"run get 1",
|
||||
"loaddeployed 1", // contract ID
|
||||
"run get 1",
|
||||
"loaddeployed --historic 2 1", // historic state, check that hash is properly set
|
||||
"run get 1",
|
||||
// Check signers parsing:
|
||||
"loaddeployed 2 "+ownerAddress, // check witness (owner:DefautScope => true)
|
||||
"run checkWitness",
|
||||
"loaddeployed 2 "+ownerAddress+":None", // check witness (owner:None => false)
|
||||
"run checkWitness",
|
||||
"loaddeployed 2 "+ownerAddress+":CalledByEntry", // check witness (owner:CalledByEntry => true)
|
||||
"run checkWitness",
|
||||
"loaddeployed 2 "+ownerAcc.StringLE()+":CalledByEntry", // check witness (ownerLE:CalledByEntry => true)
|
||||
"run checkWitness",
|
||||
"loaddeployed 2 0x"+ownerAcc.StringLE()+":CalledByEntry", // check witness (owner0xLE:CalledByEntry => true)
|
||||
"run checkWitness",
|
||||
"loaddeployed 2 "+sideAcc.StringLE()+":Global", // check witness (sideLE:Global => false)
|
||||
"run checkWitness",
|
||||
"loaddeployed", // missing argument
|
||||
"exit",
|
||||
)
|
||||
e.checkNextLine(t, "READY: loaded \\d+ instructions")
|
||||
e.checkStack(t, []byte{2})
|
||||
e.checkNextLine(t, "READY: loaded \\d+ instructions")
|
||||
e.checkStack(t, []byte{2})
|
||||
e.checkNextLine(t, "READY: loaded \\d+ instructions")
|
||||
e.checkStack(t, []byte{2})
|
||||
e.checkNextLine(t, "READY: loaded \\d+ instructions")
|
||||
e.checkStack(t, []byte{1})
|
||||
// Check signers parsing:
|
||||
e.checkNextLine(t, "READY: loaded \\d+ instructions") // check witness of owner:DefaultScope
|
||||
e.checkStack(t, true)
|
||||
e.checkNextLine(t, "READY: loaded \\d+ instructions") // check witness of owner:None
|
||||
e.checkStack(t, false)
|
||||
e.checkNextLine(t, "READY: loaded \\d+ instructions") // check witness of owner:CalledByEntry
|
||||
e.checkStack(t, true)
|
||||
e.checkNextLine(t, "READY: loaded \\d+ instructions") // check witness of ownerLE:CalledByEntry
|
||||
e.checkStack(t, true)
|
||||
e.checkNextLine(t, "READY: loaded \\d+ instructions") // check witness of owner0xLE:CalledByEntry
|
||||
e.checkStack(t, true)
|
||||
e.checkNextLine(t, "READY: loaded \\d+ instructions") // check witness of owner0xLE:CalledByEntry
|
||||
e.checkStack(t, false)
|
||||
e.checkError(t, errors.New("contract hash, address or ID is mandatory argument"))
|
||||
}
|
||||
|
||||
func TestJump(t *testing.T) {
|
||||
buf := io.NewBufBinWriter()
|
||||
emit.Opcodes(buf.BinWriter, opcode.PUSH1, opcode.PUSH2, opcode.ABORT) // some garbage
|
||||
jmpTo := buf.Len()
|
||||
emit.Opcodes(buf.BinWriter, opcode.PUSH4, opcode.PUSH5, opcode.ADD) // useful script
|
||||
e := newTestVMCLI(t)
|
||||
e.runProg(t,
|
||||
"loadhex "+hex.EncodeToString(buf.Bytes()),
|
||||
"jump "+strconv.Itoa(jmpTo),
|
||||
"run",
|
||||
)
|
||||
|
||||
e.checkNextLine(t, "READY: loaded 6 instructions")
|
||||
e.checkNextLine(t, fmt.Sprintf("jumped to instruction %d", jmpTo))
|
||||
e.checkStack(t, 9)
|
||||
}
|
||||
|
|
|
@ -62,6 +62,8 @@ func NewParameterContext(typ string, network netmode.Magic, verif crypto.Verifia
|
|||
}
|
||||
}
|
||||
|
||||
// GetCompleteTransaction clears transaction witnesses (if any) and refills them with
|
||||
// signatures from the parameter context.
|
||||
func (c *ParameterContext) GetCompleteTransaction() (*transaction.Transaction, error) {
|
||||
tx, ok := c.Verifiable.(*transaction.Transaction)
|
||||
if !ok {
|
||||
|
|
|
@ -327,6 +327,8 @@ func adjustValToType(typ ParamType, val string) (interface{}, error) {
|
|||
return pub.Bytes(), nil
|
||||
case StringType:
|
||||
return val, nil
|
||||
case AnyType:
|
||||
return nil, nil
|
||||
default:
|
||||
return nil, errors.New("unsupported parameter type")
|
||||
}
|
||||
|
@ -347,6 +349,10 @@ func inferParamType(val string) ParamType {
|
|||
return IntegerType
|
||||
}
|
||||
|
||||
if val == "nil" {
|
||||
return AnyType
|
||||
}
|
||||
|
||||
if val == "true" || val == "false" {
|
||||
return BoolType
|
||||
}
|
||||
|
|
|
@ -155,6 +155,9 @@ func TestInferParamType(t *testing.T) {
|
|||
}, {
|
||||
in: "dead",
|
||||
out: ByteArrayType,
|
||||
}, {
|
||||
in: "nil",
|
||||
out: AnyType,
|
||||
}}
|
||||
for _, inout := range inouts {
|
||||
out := inferParamType(inout.in)
|
||||
|
@ -323,6 +326,10 @@ func TestAdjustValToType(t *testing.T) {
|
|||
typ: InteropInterfaceType,
|
||||
val: "",
|
||||
err: true,
|
||||
}, {
|
||||
typ: AnyType,
|
||||
val: "nil",
|
||||
out: nil,
|
||||
}}
|
||||
|
||||
for _, inout := range inouts {
|
||||
|
|
|
@ -403,3 +403,12 @@ func ExpandParameterToEmitable(param Parameter) (interface{}, error) {
|
|||
return param.Value, nil
|
||||
}
|
||||
}
|
||||
|
||||
// ToStackItem converts smartcontract parameter to stackitem.Item.
|
||||
func (p *Parameter) ToStackItem() (stackitem.Item, error) {
|
||||
e, err := ExpandParameterToEmitable(*p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return stackitem.Make(e), nil
|
||||
}
|
||||
|
|
|
@ -9,6 +9,8 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
|
@ -446,47 +448,57 @@ func hexToBase64(s string) string {
|
|||
return base64.StdEncoding.EncodeToString(b)
|
||||
}
|
||||
|
||||
func TestExpandParameterToEmitable(t *testing.T) {
|
||||
func TestExpandParameterToEmitableToStackitem(t *testing.T) {
|
||||
pk, _ := keys.NewPrivateKey()
|
||||
testCases := []struct {
|
||||
In Parameter
|
||||
Expected interface{}
|
||||
In Parameter
|
||||
Expected interface{}
|
||||
ExpectedStackitem stackitem.Item
|
||||
}{
|
||||
{
|
||||
In: Parameter{Type: BoolType, Value: true},
|
||||
Expected: true,
|
||||
In: Parameter{Type: BoolType, Value: true},
|
||||
Expected: true,
|
||||
ExpectedStackitem: stackitem.NewBool(true),
|
||||
},
|
||||
{
|
||||
In: Parameter{Type: IntegerType, Value: big.NewInt(123)},
|
||||
Expected: big.NewInt(123),
|
||||
In: Parameter{Type: IntegerType, Value: big.NewInt(123)},
|
||||
Expected: big.NewInt(123),
|
||||
ExpectedStackitem: stackitem.NewBigInteger(big.NewInt(123)),
|
||||
},
|
||||
{
|
||||
In: Parameter{Type: ByteArrayType, Value: []byte{1, 2, 3}},
|
||||
Expected: []byte{1, 2, 3},
|
||||
In: Parameter{Type: ByteArrayType, Value: []byte{1, 2, 3}},
|
||||
Expected: []byte{1, 2, 3},
|
||||
ExpectedStackitem: stackitem.NewByteArray([]byte{1, 2, 3}),
|
||||
},
|
||||
{
|
||||
In: Parameter{Type: StringType, Value: "writing's on the wall"},
|
||||
Expected: "writing's on the wall",
|
||||
In: Parameter{Type: StringType, Value: "writing's on the wall"},
|
||||
Expected: "writing's on the wall",
|
||||
ExpectedStackitem: stackitem.NewByteArray([]byte("writing's on the wall")),
|
||||
},
|
||||
{
|
||||
In: Parameter{Type: Hash160Type, Value: util.Uint160{1, 2, 3}},
|
||||
Expected: util.Uint160{1, 2, 3},
|
||||
In: Parameter{Type: Hash160Type, Value: util.Uint160{1, 2, 3}},
|
||||
Expected: util.Uint160{1, 2, 3},
|
||||
ExpectedStackitem: stackitem.NewByteArray(util.Uint160{1, 2, 3}.BytesBE()),
|
||||
},
|
||||
{
|
||||
In: Parameter{Type: Hash256Type, Value: util.Uint256{1, 2, 3}},
|
||||
Expected: util.Uint256{1, 2, 3},
|
||||
In: Parameter{Type: Hash256Type, Value: util.Uint256{1, 2, 3}},
|
||||
Expected: util.Uint256{1, 2, 3},
|
||||
ExpectedStackitem: stackitem.NewByteArray(util.Uint256{1, 2, 3}.BytesBE()),
|
||||
},
|
||||
{
|
||||
In: Parameter{Type: PublicKeyType, Value: pk.PublicKey().Bytes()},
|
||||
Expected: pk.PublicKey().Bytes(),
|
||||
In: Parameter{Type: PublicKeyType, Value: pk.PublicKey().Bytes()},
|
||||
Expected: pk.PublicKey().Bytes(),
|
||||
ExpectedStackitem: stackitem.NewByteArray(pk.PublicKey().Bytes()),
|
||||
},
|
||||
{
|
||||
In: Parameter{Type: SignatureType, Value: []byte{1, 2, 3}},
|
||||
Expected: []byte{1, 2, 3},
|
||||
In: Parameter{Type: SignatureType, Value: []byte{1, 2, 3}},
|
||||
Expected: []byte{1, 2, 3},
|
||||
ExpectedStackitem: stackitem.NewByteArray([]byte{1, 2, 3}),
|
||||
},
|
||||
{
|
||||
In: Parameter{Type: AnyType},
|
||||
Expected: nil,
|
||||
In: Parameter{Type: AnyType},
|
||||
Expected: nil,
|
||||
ExpectedStackitem: stackitem.Null{},
|
||||
},
|
||||
{
|
||||
In: Parameter{Type: ArrayType, Value: []Parameter{
|
||||
|
@ -509,6 +521,13 @@ func TestExpandParameterToEmitable(t *testing.T) {
|
|||
},
|
||||
}},
|
||||
Expected: []interface{}{big.NewInt(123), []byte{1, 2, 3}, []interface{}{true}},
|
||||
ExpectedStackitem: stackitem.NewArray([]stackitem.Item{
|
||||
stackitem.NewBigInteger(big.NewInt(123)),
|
||||
stackitem.NewByteArray([]byte{1, 2, 3}),
|
||||
stackitem.NewArray([]stackitem.Item{
|
||||
stackitem.NewBool(true),
|
||||
}),
|
||||
}),
|
||||
},
|
||||
}
|
||||
bw := io.NewBufBinWriter()
|
||||
|
@ -519,6 +538,10 @@ func TestExpandParameterToEmitable(t *testing.T) {
|
|||
|
||||
emit.Array(bw.BinWriter, actual)
|
||||
require.NoError(t, bw.Err)
|
||||
|
||||
actualSI, err := testCase.In.ToStackItem()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, testCase.ExpectedStackitem, actualSI)
|
||||
}
|
||||
errCases := []Parameter{
|
||||
{Type: UnknownType},
|
||||
|
|
|
@ -122,6 +122,18 @@ func Make(v interface{}) Item {
|
|||
a = append(a, Make(i))
|
||||
}
|
||||
return Make(a)
|
||||
case []interface{}:
|
||||
res := make([]Item, len(val))
|
||||
for i := range val {
|
||||
res[i] = Make(val[i])
|
||||
}
|
||||
return Make(res)
|
||||
case util.Uint160:
|
||||
return Make(val.BytesBE())
|
||||
case util.Uint256:
|
||||
return Make(val.BytesBE())
|
||||
case nil:
|
||||
return Null{}
|
||||
default:
|
||||
i64T := reflect.TypeOf(int64(0))
|
||||
if reflect.TypeOf(val).ConvertibleTo(i64T) {
|
||||
|
|
|
@ -77,13 +77,17 @@ var makeStackItemTestCases = []struct {
|
|||
input: []int{1, 2, 3},
|
||||
result: &Array{value: []Item{(*BigInteger)(big.NewInt(1)), (*BigInteger)(big.NewInt(2)), (*BigInteger)(big.NewInt(3))}},
|
||||
},
|
||||
{
|
||||
input: nil,
|
||||
result: Null{},
|
||||
},
|
||||
}
|
||||
|
||||
var makeStackItemErrorCases = []struct {
|
||||
input interface{}
|
||||
}{
|
||||
{
|
||||
input: nil,
|
||||
input: map[int]int{1: 2},
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -262,7 +262,7 @@ func (v *VM) LoadFileWithFlags(path string, f callflag.CallFlag) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.Load(nef.Script)
|
||||
v.LoadWithFlags(nef.Script, f)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue