Merge pull request #2740 from nspcc-dev/cli-improvement

cli: improve VM CLI a bit more
This commit is contained in:
Roman Khimov 2022-10-13 20:21:47 +07:00 committed by GitHub
commit c3001bc5bd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 807 additions and 338 deletions

View file

@ -24,20 +24,145 @@ 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:] {
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, cli.NewExitError(fmt.Errorf("failed to parse signer #%d: %w", i, err), 1)
return nil, fmt.Errorf("failed to parse signer #%d: %w", i, err)
}
signers = append(signers, cosigner)
}
}
return signers, nil
}

View file

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

View file

@ -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: "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:
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:
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:
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,9 +324,7 @@ 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`,
@ -276,13 +332,7 @@ Example:
},
{
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.",
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.
@ -310,15 +358,9 @@ Example:
},
{
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.",
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 = `
_ ____________ __________ _ ____ ___
/ | / / ____/ __ \ / ____/ __ \ | | / / |/ /

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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{}
ExpectedStackitem stackitem.Item
}{
{
In: Parameter{Type: BoolType, Value: true},
Expected: true,
ExpectedStackitem: stackitem.NewBool(true),
},
{
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},
ExpectedStackitem: stackitem.NewByteArray([]byte{1, 2, 3}),
},
{
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},
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},
ExpectedStackitem: stackitem.NewByteArray(util.Uint256{1, 2, 3}.BytesBE()),
},
{
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},
ExpectedStackitem: stackitem.NewByteArray([]byte{1, 2, 3}),
},
{
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},

View file

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

View file

@ -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},
},
}

View file

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