forked from TrueCloudLab/neoneo-go
vm: add 'storage' VM CLI command to dump storage items
Another nice one, very useful for debugging.
This commit is contained in:
parent
ff03c33e6d
commit
8c78177806
2 changed files with 105 additions and 2 deletions
|
@ -17,6 +17,7 @@ import (
|
||||||
|
|
||||||
"github.com/chzyer/readline"
|
"github.com/chzyer/readline"
|
||||||
"github.com/kballard/go-shellquote"
|
"github.com/kballard/go-shellquote"
|
||||||
|
"github.com/nspcc-dev/neo-go/cli/flags"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/compiler"
|
"github.com/nspcc-dev/neo-go/pkg/compiler"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/config"
|
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core"
|
"github.com/nspcc-dev/neo-go/pkg/core"
|
||||||
|
@ -54,8 +55,9 @@ const (
|
||||||
|
|
||||||
// Various flag names.
|
// Various flag names.
|
||||||
const (
|
const (
|
||||||
verboseFlagFullName = "verbose"
|
verboseFlagFullName = "verbose"
|
||||||
historicFlagFullName = "historic"
|
historicFlagFullName = "historic"
|
||||||
|
backwardsFlagFullName = "backwards"
|
||||||
)
|
)
|
||||||
|
|
||||||
var historicFlag = cli.IntFlag{
|
var historicFlag = cli.IntFlag{
|
||||||
|
@ -267,6 +269,32 @@ Example:
|
||||||
> env -v`,
|
> env -v`,
|
||||||
Action: handleEnv,
|
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).",
|
||||||
|
UsageText: `storage <hash-or-address-or-id> [<prefix>] [--backwards]`,
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.BoolFlag{
|
||||||
|
Name: backwardsFlagFullName + ",b",
|
||||||
|
Usage: "Backwards traversal direction",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Description: `storage <hash-or-address-or-id> <prefix> --backwards
|
||||||
|
|
||||||
|
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).
|
||||||
|
|
||||||
|
Example:
|
||||||
|
> storage 0x0000000009070e030d0f0e020d0c06050e030c02 030e --backwards`,
|
||||||
|
Action: handleStorage,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var completer *readline.PrefixCompleter
|
var completer *readline.PrefixCompleter
|
||||||
|
@ -871,6 +899,50 @@ func handleEnv(c *cli.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handleStorage(c *cli.Context) error {
|
||||||
|
if !c.Args().Present() {
|
||||||
|
return errors.New("contract hash, address or ID is mandatory argument")
|
||||||
|
}
|
||||||
|
hashOrID := c.Args().Get(0)
|
||||||
|
var (
|
||||||
|
id int32
|
||||||
|
ic = getInteropContextFromContext(c.App)
|
||||||
|
prefix []byte
|
||||||
|
backwards bool
|
||||||
|
)
|
||||||
|
h, err := flags.ParseAddress(hashOrID)
|
||||||
|
if err != nil {
|
||||||
|
i, err := strconv.Atoi(hashOrID)
|
||||||
|
if err != nil {
|
||||||
|
return 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 fmt.Errorf("contract %s not found: %w", h.StringLE(), err)
|
||||||
|
}
|
||||||
|
id = cs.ID
|
||||||
|
}
|
||||||
|
if c.NArg() > 1 {
|
||||||
|
prefix, err = hex.DecodeString(c.Args().Get(1))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to decode prefix from hex: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if c.Bool(backwardsFlagFullName) {
|
||||||
|
backwards = true
|
||||||
|
}
|
||||||
|
ic.DAO.Seek(id, storage.SeekRange{
|
||||||
|
Prefix: prefix,
|
||||||
|
Backwards: backwards,
|
||||||
|
}, func(k, v []byte) bool {
|
||||||
|
fmt.Fprintf(c.App.Writer, "%s: %v\n", hex.EncodeToString(k), hex.EncodeToString(v))
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func dumpEvents(app *cli.App) (string, error) {
|
func dumpEvents(app *cli.App) (string, error) {
|
||||||
ic := getInteropContextFromContext(app)
|
ic := getInteropContextFromContext(app)
|
||||||
if len(ic.Notifications) == 0 {
|
if len(ic.Notifications) == 0 {
|
||||||
|
|
|
@ -221,6 +221,12 @@ func (e *executor) checkEvents(t *testing.T, isKeywordExpected bool, events ...s
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *executor) checkStorage(t *testing.T, kvs ...storage.KeyValue) {
|
||||||
|
for _, kv := range kvs {
|
||||||
|
e.checkNextLine(t, fmt.Sprintf("%s: %s", hex.EncodeToString(kv.Key), hex.EncodeToString(kv.Value)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (e *executor) checkSlot(t *testing.T, items ...interface{}) {
|
func (e *executor) checkSlot(t *testing.T, items ...interface{}) {
|
||||||
d := json.NewDecoder(e.out)
|
d := json.NewDecoder(e.out)
|
||||||
var actual interface{}
|
var actual interface{}
|
||||||
|
@ -849,3 +855,28 @@ func TestEnv(t *testing.T) {
|
||||||
e.checkNextLine(t, "Node config:") // Do not check exact node config.
|
e.checkNextLine(t, "Node config:") // Do not check exact node config.
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDumpStorage(t *testing.T) {
|
||||||
|
e := newTestVMClIWithState(t)
|
||||||
|
|
||||||
|
h, err := e.cli.chain.GetContractScriptHash(1) // examples/storage/storage.go
|
||||||
|
require.NoError(t, err)
|
||||||
|
expected := []storage.KeyValue{
|
||||||
|
{Key: []byte{1}, Value: []byte{2}},
|
||||||
|
{Key: []byte{2}, Value: []byte{2}},
|
||||||
|
}
|
||||||
|
e.runProg(t,
|
||||||
|
"storage "+h.StringLE(),
|
||||||
|
"storage 0x"+h.StringLE(),
|
||||||
|
"storage "+address.Uint160ToString(h),
|
||||||
|
"storage 1",
|
||||||
|
"storage 1 "+hex.EncodeToString(expected[0].Key),
|
||||||
|
"storage 1 --backwards",
|
||||||
|
)
|
||||||
|
e.checkStorage(t, expected...)
|
||||||
|
e.checkStorage(t, expected...)
|
||||||
|
e.checkStorage(t, expected...)
|
||||||
|
e.checkStorage(t, expected...)
|
||||||
|
e.checkStorage(t, storage.KeyValue{Key: nil, Value: []byte{2}}) // empty key because search prefix is trimmed
|
||||||
|
e.checkStorage(t, expected[1], expected[0])
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue