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/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/config"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core"
|
||||
|
@ -54,8 +55,9 @@ const (
|
|||
|
||||
// Various flag names.
|
||||
const (
|
||||
verboseFlagFullName = "verbose"
|
||||
historicFlagFullName = "historic"
|
||||
verboseFlagFullName = "verbose"
|
||||
historicFlagFullName = "historic"
|
||||
backwardsFlagFullName = "backwards"
|
||||
)
|
||||
|
||||
var historicFlag = cli.IntFlag{
|
||||
|
@ -267,6 +269,32 @@ 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).",
|
||||
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
|
||||
|
@ -871,6 +899,50 @@ func handleEnv(c *cli.Context) error {
|
|||
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) {
|
||||
ic := getInteropContextFromContext(app)
|
||||
if len(ic.Notifications) == 0 {
|
||||
|
|
|
@ -221,6 +221,12 @@ func (e *executor) checkEvents(t *testing.T, isKeywordExpected bool, events ...s
|
|||
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{}) {
|
||||
d := json.NewDecoder(e.out)
|
||||
var actual interface{}
|
||||
|
@ -849,3 +855,28 @@ func TestEnv(t *testing.T) {
|
|||
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