Skip to content

Commit

Permalink
Merge pull request #3020 from nspcc-dev/loadnef-enh-2
Browse files Browse the repository at this point in the history
cli: properly load specified method for `run` VM CLI command
  • Loading branch information
roman-khimov authored May 19, 2023
2 parents 7b86a54 + ddfbf7a commit aca12b5
Show file tree
Hide file tree
Showing 5 changed files with 347 additions and 147 deletions.
115 changes: 81 additions & 34 deletions cli/vm/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,14 @@ import (
"github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/core/interop/runtime"
"github.com/nspcc-dev/neo-go/pkg/core/native"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"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"
"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"
Expand All @@ -51,7 +53,7 @@ const (
chainKey = "chain"
chainCfgKey = "chainCfg"
icKey = "ic"
manifestKey = "manifest"
contractStateKey = "contractState"
exitFuncKey = "exitFunc"
readlineInstanceKey = "readlineKey"
printLogoKey = "printLogoKey"
Expand All @@ -64,6 +66,7 @@ const (
gasFlagFullName = "gas"
backwardsFlagFullName = "backwards"
diffFlagFullName = "diff"
hashFlagFullName = "hash"
)

var (
Expand All @@ -76,6 +79,10 @@ var (
Name: gasFlagFullName,
Usage: "GAS limit for this execution (integer number, satoshi).",
}
hashFlag = cli.StringFlag{
Name: hashFlagFullName,
Usage: "Smart-contract hash in LE form or address",
}
)

var commands = []cli.Command{
Expand Down Expand Up @@ -150,9 +157,9 @@ Example:
},
{
Name: "loadnef",
Usage: "Load a NEF-consistent script into the VM optionally attaching to it provided signers with scopes",
UsageText: `loadnef [--historic <height>] [--gas <int>] <file> [<manifest>] [-- <signer-with-scope>, ...]`,
Flags: []cli.Flag{historicFlag, gasFlag},
Usage: "Load a NEF (possibly with a contract hash) into the VM optionally using provided scoped signers in the context",
UsageText: `loadnef [--historic <height>] [--gas <int>] [--hash <hash-or-address>] <file> [<manifest>] [-- <signer-with-scope>, ...]`,
Flags: []cli.Flag{historicFlag, gasFlag, hashFlag},
Description: `<file> parameter is mandatory, <manifest> parameter (if omitted) will
be guessed from the <file> parameter by replacing '.nef' suffix with '.manifest.json'
suffix.
Expand Down Expand Up @@ -191,9 +198,9 @@ Example:
},
{
Name: "loadgo",
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>] [--gas <int>] <file> [-- <signer-with-scope>, ...]`,
Flags: []cli.Flag{historicFlag, gasFlag},
Usage: "Compile and load a Go file with the manifest into the VM optionally attaching to it provided signers with scopes and setting provided hash",
UsageText: `loadgo [--historic <height>] [--gas <int>] [--hash <hash-or-address>] <file> [-- <signer-with-scope>, ...]`,
Flags: []cli.Flag{historicFlag, gasFlag, hashFlag},
Description: `<file> is mandatory parameter.
` + cmdargs.SignersParsingDoc + `
Expand Down Expand Up @@ -489,7 +496,7 @@ func NewWithConfig(printLogotype bool, onExit func(int), c *readline.Config, cfg
chainKey: chain,
chainCfgKey: cfg,
icKey: ic,
manifestKey: new(manifest.Manifest),
contractStateKey: new(state.ContractBase),
exitFuncKey: exitF,
readlineInstanceKey: l,
printLogoKey: printLogotype,
Expand Down Expand Up @@ -522,8 +529,8 @@ func getInteropContextFromContext(app *cli.App) *interop.Context {
return app.Metadata[icKey].(*interop.Context)
}

func getManifestFromContext(app *cli.App) *manifest.Manifest {
return app.Metadata[manifestKey].(*manifest.Manifest)
func getContractStateFromContext(app *cli.App) *state.ContractBase {
return app.Metadata[contractStateKey].(*state.ContractBase)
}

func getPrintLogoFromContext(app *cli.App) bool {
Expand All @@ -534,8 +541,8 @@ func setInteropContextInContext(app *cli.App, ic *interop.Context) {
app.Metadata[icKey] = ic
}

func setManifestInContext(app *cli.App, m *manifest.Manifest) {
app.Metadata[manifestKey] = m
func setContractStateInContext(app *cli.App, cs *state.ContractBase) {
app.Metadata[contractStateKey] = cs
}

func checkVMIsReady(app *cli.App) bool {
Expand Down Expand Up @@ -671,6 +678,17 @@ func prepareVM(c *cli.Context, tx *transaction.Transaction) error {
return nil
}

func getHashFlag(c *cli.Context) (util.Uint160, error) {
if !c.IsSet(hashFlagFullName) {
return util.Uint160{}, nil
}
h, err := flags.ParseAddress(c.String(hashFlagFullName))
if err != nil {
return util.Uint160{}, fmt.Errorf("failed to parse contract hash: %w", err)
}
return h, nil
}

func handleLoadNEF(c *cli.Context) error {
args := c.Args()
if len(args) < 1 {
Expand Down Expand Up @@ -725,9 +743,19 @@ func handleLoadNEF(c *cli.Context) error {
if err != nil {
return err
}
h, err := getHashFlag(c)
if err != nil {
return err
}
cs := &state.ContractBase{
Hash: h,
NEF: nef,
Manifest: *m,
}
setContractStateInContext(c.App, cs)

v := getVMFromContext(c.App)
fmt.Fprintf(c.App.Writer, "READY: loaded %d instructions\n", v.Context().LenInstr())
setManifestInContext(c.App, m)
changePrompt(c.App)
return nil
}
Expand Down Expand Up @@ -811,7 +839,7 @@ func handleLoadGo(c *cli.Context) error {
}

name := strings.TrimSuffix(args[0], ".go")
b, di, err := compiler.CompileWithOptions(args[0], nil, &compiler.Options{Name: name})
ne, di, err := compiler.CompileWithOptions(args[0], nil, &compiler.Options{Name: name})
if err != nil {
return fmt.Errorf("failed to compile: %w", err)
}
Expand All @@ -835,12 +863,22 @@ func handleLoadGo(c *cli.Context) error {
}
}

err = prepareVM(c, createFakeTransaction(b.Script, signers))
err = prepareVM(c, createFakeTransaction(ne.Script, signers))
if err != nil {
return err
}
h, err := getHashFlag(c)
if err != nil {
return err
}
cs := &state.ContractBase{
Hash: h,
NEF: *ne,
Manifest: *m,
}
setContractStateInContext(c.App, cs)

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
Expand Down Expand Up @@ -937,7 +975,7 @@ func handleLoadDeployed(c *cli.Context) error {
ic.VM.GasLimit = gasLimit
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)
setContractStateInContext(c.App, &cs.ContractBase)
changePrompt(c.App)
return nil
}
Expand Down Expand Up @@ -992,9 +1030,9 @@ func resetInteropContext(app *cli.App, tx *transaction.Transaction, height ...ui
return nil
}

// resetManifest removes manifest from app context.
func resetManifest(app *cli.App) {
setManifestInContext(app, nil)
// resetContractState removes loaded contract state from app context.
func resetContractState(app *cli.App) {
setContractStateInContext(app, nil)
}

// resetState resets state of the app (clear interop context and manifest) so that it's ready
Expand All @@ -1004,7 +1042,7 @@ func resetState(app *cli.App, tx *transaction.Transaction, height ...uint32) err
if err != nil {
return err
}
resetManifest(app)
resetContractState(app)
return nil
}

Expand All @@ -1023,14 +1061,15 @@ func getManifestFromFile(name string) (*manifest.Manifest, error) {

func handleRun(c *cli.Context) error {
v := getVMFromContext(c.App)
m := getManifestFromContext(c.App)
cs := getContractStateFromContext(c.App)
args := c.Args()
if len(args) != 0 {
var (
params []stackitem.Item
offset int
err error
runCurrent = args[0] != "_"
hasRet bool
)

_, scParams, err := cmdargs.ParseParams(args[1:], true)
Expand All @@ -1045,27 +1084,35 @@ func handleRun(c *cli.Context) error {
}
}
if runCurrent {
if m == nil {
return fmt.Errorf("manifest is not loaded; either use 'run' command to run loaded script from the start or use 'loadgo' and 'loadnef' commands to provide manifest")
if cs == nil {
return fmt.Errorf("manifest is not loaded; either use 'run' command to run loaded script from the start or use 'loadgo', 'loadnef' or 'loaddeployed' commands to provide manifest")
}
md := m.ABI.GetMethod(args[0], len(params))
md := cs.Manifest.ABI.GetMethod(args[0], len(params))
if md == nil {
return fmt.Errorf("%w: method not found", ErrInvalidParameter)
}
hasRet = md.ReturnType != smartcontract.VoidType
offset = md.Offset
var initOff = -1
if initMD := cs.Manifest.ABI.GetMethod(manifest.MethodInit, 0); initMD != nil {
initOff = initMD.Offset
}

// Clear context loaded by 'loadgo', 'loadnef' or 'loaddeployed' to properly handle LoadNEFMethod.
// At the same time, preserve previously set gas limit and the set of breakpoints.
ic := getInteropContextFromContext(c.App)
gasLimit := v.GasLimit
breaks := v.Context().BreakPoints() // We ensure that there's a context loaded.
ic.ReuseVM(v)
v.GasLimit = gasLimit
v.LoadNEFMethod(&cs.NEF, util.Uint160{}, cs.Hash, callflag.All, hasRet, offset, initOff, nil)
for _, bp := range breaks {
v.AddBreakPoint(bp)
}
}
for i := len(params) - 1; i >= 0; i-- {
v.Estack().PushVal(params[i])
}
if runCurrent {
if !v.Ready() {
return errors.New("no program loaded")
}
v.Context().Jump(offset)
if initMD := m.ABI.GetMethod(manifest.MethodInit, 0); initMD != nil {
v.Call(initMD.Offset)
}
}
}
runVMWithHandling(c)
changePrompt(c.App)
Expand Down
Loading

0 comments on commit aca12b5

Please sign in to comment.