cli: support 'loaddeployed' VM CLI command

This commit is contained in:
Anna Shaleva 2022-10-11 12:22:07 +03:00
parent 9977606e40
commit 3fba4e4f17
2 changed files with 103 additions and 21 deletions

View file

@ -183,6 +183,19 @@ The transaction script will be loaded into VM; the resulting execution context w
> loadtx /path/to/file`,
Action: handleLoadTx,
},
{
Name: "loaddeployed",
Usage: "Load deployed contract into the VM from chain. If '--historic' flag specified, then the historic contract state (historic script and manifest) will be loaded.",
UsageText: `loaddeployed [--historic <height>] <hash-or-address-or-id>`,
Flags: []cli.Flag{historicFlag},
Description: `loaddeployed [--historic <height>] <hash-or-address-or-id>
Load deployed contract into the VM from chain. If '--historic' flag specified, then the historic contract state (historic script and manifest) will be loaded.
<hash-or-address-or-id> is mandatory parameter, example:
> loaddeployed 0x0000000009070e030d0f0e020d0c06050e030c02`,
Action: handleLoadDeployed,
},
{
Name: "reset",
Usage: "Unload compiled script from the VM and reset context to proper (possibly, historic) state",
@ -733,6 +746,41 @@ func handleLoadTx(c *cli.Context) error {
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)
}
v := getVMFromContext(c.App)
v.LoadScriptWithHash(cs.NEF.Script, h, callflag.All)
fmt.Fprintf(c.App.Writer, "READY: loaded %d instructions\n", v.Context().LenInstr())
setManifestInContext(c.App, &cs.Manifest)
changePrompt(c.App)
return nil
}
func handleReset(c *cli.Context) error {
err := prepareVM(c, nil)
if err != nil {
@ -1071,29 +1119,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 {
@ -1103,6 +1133,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 {

View file

@ -1019,3 +1019,32 @@ func TestLoadtx(t *testing.T) {
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)
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",
"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})
e.checkError(t, errors.New("contract hash, address or ID is mandatory argument"))
}