Merge pull request #3020 from nspcc-dev/loadnef-enh-2
cli: properly load specified method for `run` VM CLI command
This commit is contained in:
commit
aca12b58c0
5 changed files with 347 additions and 147 deletions
115
cli/vm/cli.go
115
cli/vm/cli.go
|
@ -28,12 +28,14 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
"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/interop/runtime"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/native"
|
"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"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/storage/dbconfig"
|
"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/core/transaction"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
"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/address"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/encoding/bigint"
|
"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/callflag"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
"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/nef"
|
||||||
|
@ -51,7 +53,7 @@ const (
|
||||||
chainKey = "chain"
|
chainKey = "chain"
|
||||||
chainCfgKey = "chainCfg"
|
chainCfgKey = "chainCfg"
|
||||||
icKey = "ic"
|
icKey = "ic"
|
||||||
manifestKey = "manifest"
|
contractStateKey = "contractState"
|
||||||
exitFuncKey = "exitFunc"
|
exitFuncKey = "exitFunc"
|
||||||
readlineInstanceKey = "readlineKey"
|
readlineInstanceKey = "readlineKey"
|
||||||
printLogoKey = "printLogoKey"
|
printLogoKey = "printLogoKey"
|
||||||
|
@ -64,6 +66,7 @@ const (
|
||||||
gasFlagFullName = "gas"
|
gasFlagFullName = "gas"
|
||||||
backwardsFlagFullName = "backwards"
|
backwardsFlagFullName = "backwards"
|
||||||
diffFlagFullName = "diff"
|
diffFlagFullName = "diff"
|
||||||
|
hashFlagFullName = "hash"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -76,6 +79,10 @@ var (
|
||||||
Name: gasFlagFullName,
|
Name: gasFlagFullName,
|
||||||
Usage: "GAS limit for this execution (integer number, satoshi).",
|
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{
|
var commands = []cli.Command{
|
||||||
|
@ -150,9 +157,9 @@ Example:
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "loadnef",
|
Name: "loadnef",
|
||||||
Usage: "Load a NEF-consistent script into the VM optionally attaching to it provided signers with scopes",
|
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>] <file> [<manifest>] [-- <signer-with-scope>, ...]`,
|
UsageText: `loadnef [--historic <height>] [--gas <int>] [--hash <hash-or-address>] <file> [<manifest>] [-- <signer-with-scope>, ...]`,
|
||||||
Flags: []cli.Flag{historicFlag, gasFlag},
|
Flags: []cli.Flag{historicFlag, gasFlag, hashFlag},
|
||||||
Description: `<file> parameter is mandatory, <manifest> parameter (if omitted) will
|
Description: `<file> parameter is mandatory, <manifest> parameter (if omitted) will
|
||||||
be guessed from the <file> parameter by replacing '.nef' suffix with '.manifest.json'
|
be guessed from the <file> parameter by replacing '.nef' suffix with '.manifest.json'
|
||||||
suffix.
|
suffix.
|
||||||
|
@ -191,9 +198,9 @@ Example:
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "loadgo",
|
Name: "loadgo",
|
||||||
Usage: "Compile and load a Go file with the manifest into the VM optionally attaching to it provided signers with scopes",
|
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>] <file> [-- <signer-with-scope>, ...]`,
|
UsageText: `loadgo [--historic <height>] [--gas <int>] [--hash <hash-or-address>] <file> [-- <signer-with-scope>, ...]`,
|
||||||
Flags: []cli.Flag{historicFlag, gasFlag},
|
Flags: []cli.Flag{historicFlag, gasFlag, hashFlag},
|
||||||
Description: `<file> is mandatory parameter.
|
Description: `<file> is mandatory parameter.
|
||||||
|
|
||||||
` + cmdargs.SignersParsingDoc + `
|
` + cmdargs.SignersParsingDoc + `
|
||||||
|
@ -489,7 +496,7 @@ func NewWithConfig(printLogotype bool, onExit func(int), c *readline.Config, cfg
|
||||||
chainKey: chain,
|
chainKey: chain,
|
||||||
chainCfgKey: cfg,
|
chainCfgKey: cfg,
|
||||||
icKey: ic,
|
icKey: ic,
|
||||||
manifestKey: new(manifest.Manifest),
|
contractStateKey: new(state.ContractBase),
|
||||||
exitFuncKey: exitF,
|
exitFuncKey: exitF,
|
||||||
readlineInstanceKey: l,
|
readlineInstanceKey: l,
|
||||||
printLogoKey: printLogotype,
|
printLogoKey: printLogotype,
|
||||||
|
@ -522,8 +529,8 @@ func getInteropContextFromContext(app *cli.App) *interop.Context {
|
||||||
return app.Metadata[icKey].(*interop.Context)
|
return app.Metadata[icKey].(*interop.Context)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getManifestFromContext(app *cli.App) *manifest.Manifest {
|
func getContractStateFromContext(app *cli.App) *state.ContractBase {
|
||||||
return app.Metadata[manifestKey].(*manifest.Manifest)
|
return app.Metadata[contractStateKey].(*state.ContractBase)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getPrintLogoFromContext(app *cli.App) bool {
|
func getPrintLogoFromContext(app *cli.App) bool {
|
||||||
|
@ -534,8 +541,8 @@ func setInteropContextInContext(app *cli.App, ic *interop.Context) {
|
||||||
app.Metadata[icKey] = ic
|
app.Metadata[icKey] = ic
|
||||||
}
|
}
|
||||||
|
|
||||||
func setManifestInContext(app *cli.App, m *manifest.Manifest) {
|
func setContractStateInContext(app *cli.App, cs *state.ContractBase) {
|
||||||
app.Metadata[manifestKey] = m
|
app.Metadata[contractStateKey] = cs
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkVMIsReady(app *cli.App) bool {
|
func checkVMIsReady(app *cli.App) bool {
|
||||||
|
@ -671,6 +678,17 @@ func prepareVM(c *cli.Context, tx *transaction.Transaction) error {
|
||||||
return nil
|
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 {
|
func handleLoadNEF(c *cli.Context) error {
|
||||||
args := c.Args()
|
args := c.Args()
|
||||||
if len(args) < 1 {
|
if len(args) < 1 {
|
||||||
|
@ -725,9 +743,19 @@ func handleLoadNEF(c *cli.Context) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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)
|
v := getVMFromContext(c.App)
|
||||||
fmt.Fprintf(c.App.Writer, "READY: loaded %d instructions\n", v.Context().LenInstr())
|
fmt.Fprintf(c.App.Writer, "READY: loaded %d instructions\n", v.Context().LenInstr())
|
||||||
setManifestInContext(c.App, m)
|
|
||||||
changePrompt(c.App)
|
changePrompt(c.App)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -811,7 +839,7 @@ func handleLoadGo(c *cli.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
name := strings.TrimSuffix(args[0], ".go")
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to compile: %w", err)
|
return fmt.Errorf("failed to compile: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
if err != nil {
|
||||||
return err
|
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)
|
v := getVMFromContext(c.App)
|
||||||
setManifestInContext(c.App, m)
|
|
||||||
fmt.Fprintf(c.App.Writer, "READY: loaded %d instructions\n", v.Context().LenInstr())
|
fmt.Fprintf(c.App.Writer, "READY: loaded %d instructions\n", v.Context().LenInstr())
|
||||||
changePrompt(c.App)
|
changePrompt(c.App)
|
||||||
return nil
|
return nil
|
||||||
|
@ -937,7 +975,7 @@ func handleLoadDeployed(c *cli.Context) error {
|
||||||
ic.VM.GasLimit = gasLimit
|
ic.VM.GasLimit = gasLimit
|
||||||
ic.VM.LoadScriptWithHash(cs.NEF.Script, cs.Hash, callflag.All)
|
ic.VM.LoadScriptWithHash(cs.NEF.Script, cs.Hash, callflag.All)
|
||||||
fmt.Fprintf(c.App.Writer, "READY: loaded %d instructions\n", ic.VM.Context().LenInstr())
|
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)
|
changePrompt(c.App)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -992,9 +1030,9 @@ func resetInteropContext(app *cli.App, tx *transaction.Transaction, height ...ui
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// resetManifest removes manifest from app context.
|
// resetContractState removes loaded contract state from app context.
|
||||||
func resetManifest(app *cli.App) {
|
func resetContractState(app *cli.App) {
|
||||||
setManifestInContext(app, nil)
|
setContractStateInContext(app, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// resetState resets state of the app (clear interop context and manifest) so that it's ready
|
// resetState resets state of the app (clear interop context and manifest) so that it's ready
|
||||||
|
@ -1004,7 +1042,7 @@ func resetState(app *cli.App, tx *transaction.Transaction, height ...uint32) err
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
resetManifest(app)
|
resetContractState(app)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1023,7 +1061,7 @@ func getManifestFromFile(name string) (*manifest.Manifest, error) {
|
||||||
|
|
||||||
func handleRun(c *cli.Context) error {
|
func handleRun(c *cli.Context) error {
|
||||||
v := getVMFromContext(c.App)
|
v := getVMFromContext(c.App)
|
||||||
m := getManifestFromContext(c.App)
|
cs := getContractStateFromContext(c.App)
|
||||||
args := c.Args()
|
args := c.Args()
|
||||||
if len(args) != 0 {
|
if len(args) != 0 {
|
||||||
var (
|
var (
|
||||||
|
@ -1031,6 +1069,7 @@ func handleRun(c *cli.Context) error {
|
||||||
offset int
|
offset int
|
||||||
err error
|
err error
|
||||||
runCurrent = args[0] != "_"
|
runCurrent = args[0] != "_"
|
||||||
|
hasRet bool
|
||||||
)
|
)
|
||||||
|
|
||||||
_, scParams, err := cmdargs.ParseParams(args[1:], true)
|
_, scParams, err := cmdargs.ParseParams(args[1:], true)
|
||||||
|
@ -1045,27 +1084,35 @@ func handleRun(c *cli.Context) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if runCurrent {
|
if runCurrent {
|
||||||
if m == nil {
|
if cs == 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")
|
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 {
|
if md == nil {
|
||||||
return fmt.Errorf("%w: method not found", ErrInvalidParameter)
|
return fmt.Errorf("%w: method not found", ErrInvalidParameter)
|
||||||
}
|
}
|
||||||
|
hasRet = md.ReturnType != smartcontract.VoidType
|
||||||
offset = md.Offset
|
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-- {
|
for i := len(params) - 1; i >= 0; i-- {
|
||||||
v.Estack().PushVal(params[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)
|
runVMWithHandling(c)
|
||||||
changePrompt(c.App)
|
changePrompt(c.App)
|
||||||
|
|
|
@ -281,6 +281,97 @@ func (e *executor) checkSlot(t *testing.T, items ...any) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRun_WithNewVMContextAndBreakpoints(t *testing.T) {
|
||||||
|
t.Run("contract without init", func(t *testing.T) {
|
||||||
|
src := `package kek
|
||||||
|
func Main(a, b int) int {
|
||||||
|
var c = a + b
|
||||||
|
return c + 5
|
||||||
|
}`
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
filename := prepareLoadgoSrc(t, tmpDir, src)
|
||||||
|
|
||||||
|
e := newTestVMCLI(t)
|
||||||
|
e.runProgWithTimeout(t, 10*time.Second,
|
||||||
|
"loadgo "+filename,
|
||||||
|
"break 8",
|
||||||
|
"run main 3 5",
|
||||||
|
"run",
|
||||||
|
)
|
||||||
|
|
||||||
|
e.checkNextLine(t, "READY: loaded \\d* instructions")
|
||||||
|
e.checkNextLine(t, "breakpoint added at instruction 8")
|
||||||
|
e.checkNextLine(t, "at breakpoint 8 (PUSH5)*")
|
||||||
|
e.checkStack(t, 13)
|
||||||
|
})
|
||||||
|
t.Run("contract with init", func(t *testing.T) {
|
||||||
|
src := `package kek
|
||||||
|
var I = 5
|
||||||
|
func Main(a, b int) int {
|
||||||
|
var c = a + b
|
||||||
|
return c + I
|
||||||
|
}`
|
||||||
|
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
filename := prepareLoadgoSrc(t, tmpDir, src)
|
||||||
|
|
||||||
|
e := newTestVMCLI(t)
|
||||||
|
e.runProgWithTimeout(t, 10*time.Second,
|
||||||
|
"loadgo "+filename,
|
||||||
|
"break 10",
|
||||||
|
"run main 3 5",
|
||||||
|
"run",
|
||||||
|
)
|
||||||
|
|
||||||
|
e.checkNextLine(t, "READY: loaded \\d* instructions")
|
||||||
|
e.checkNextLine(t, "breakpoint added at instruction 10")
|
||||||
|
e.checkNextLine(t, "at breakpoint 10 (ADD)*")
|
||||||
|
e.checkStack(t, 13)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepareLoadgoSrc prepares provided SC source file for loading into VM via `loadgo` command.
|
||||||
|
func prepareLoadgoSrc(t *testing.T, tmpDir, src string) string {
|
||||||
|
filename := filepath.Join(tmpDir, "vmtestcontract.go")
|
||||||
|
require.NoError(t, os.WriteFile(filename, []byte(src), os.ModePerm))
|
||||||
|
filename = "'" + filename + "'"
|
||||||
|
wd, err := os.Getwd()
|
||||||
|
require.NoError(t, err)
|
||||||
|
goMod := []byte(`module test.example/kek
|
||||||
|
require (
|
||||||
|
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0
|
||||||
|
)
|
||||||
|
replace github.com/nspcc-dev/neo-go/pkg/interop => ` + filepath.Join(wd, "../../pkg/interop") + `
|
||||||
|
go 1.18`)
|
||||||
|
require.NoError(t, os.WriteFile(filepath.Join(tmpDir, "go.mod"), goMod, os.ModePerm))
|
||||||
|
return filename
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepareLoadnefSrc compiles provided SC source and prepares NEF and manifest for loading into VM
|
||||||
|
// via `loadnef` command. It returns the name of manifest and NEF files ready to be used in CLI
|
||||||
|
// commands.
|
||||||
|
func prepareLoadnefSrc(t *testing.T, tmpDir, src string) (string, string) {
|
||||||
|
config.Version = "0.92.0-test"
|
||||||
|
|
||||||
|
nefFile, di, err := compiler.CompileWithOptions("test.go", strings.NewReader(src), nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
filename := filepath.Join(tmpDir, "vmtestcontract.nef")
|
||||||
|
rawNef, err := nefFile.Bytes()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, os.WriteFile(filename, rawNef, os.ModePerm))
|
||||||
|
m, err := di.ConvertToManifest(&compiler.Options{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
manifestFile := filepath.Join(tmpDir, "vmtestcontract.manifest.json")
|
||||||
|
rawManifest, err := json.Marshal(m)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, os.WriteFile(manifestFile, rawManifest, os.ModePerm))
|
||||||
|
|
||||||
|
manifestFile = "'" + manifestFile + "'"
|
||||||
|
filename = "'" + filename + "'"
|
||||||
|
|
||||||
|
return manifestFile, filename
|
||||||
|
}
|
||||||
|
|
||||||
func TestLoad(t *testing.T) {
|
func TestLoad(t *testing.T) {
|
||||||
script := []byte{byte(opcode.PUSH3), byte(opcode.PUSH4), byte(opcode.ADD)}
|
script := []byte{byte(opcode.PUSH3), byte(opcode.PUSH4), byte(opcode.ADD)}
|
||||||
|
|
||||||
|
@ -378,19 +469,13 @@ func TestLoad(t *testing.T) {
|
||||||
return a * b
|
return a * b
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
tmpDir := t.TempDir()
|
|
||||||
|
|
||||||
checkLoadgo := func(t *testing.T, tName, cName, cErrName string) {
|
t.Run("loadgo", func(t *testing.T) {
|
||||||
t.Run("loadgo "+tName, func(t *testing.T) {
|
tmpDir := t.TempDir()
|
||||||
filename := filepath.Join(tmpDir, cName)
|
|
||||||
require.NoError(t, os.WriteFile(filename, []byte(src), os.ModePerm))
|
checkLoadgo := func(t *testing.T, cName, cErrName string) {
|
||||||
filename = "'" + filename + "'"
|
filename := prepareLoadgoSrc(t, tmpDir, src)
|
||||||
filenameErr := filepath.Join(tmpDir, cErrName)
|
filenameErr := filepath.Join(tmpDir, cErrName)
|
||||||
require.NoError(t, os.WriteFile(filenameErr, []byte(src+"invalid_token"), os.ModePerm))
|
|
||||||
filenameErr = "'" + filenameErr + "'"
|
|
||||||
goMod := []byte(`module test.example/vmcli
|
|
||||||
go 1.18`)
|
|
||||||
require.NoError(t, os.WriteFile(filepath.Join(tmpDir, "go.mod"), goMod, os.ModePerm))
|
|
||||||
|
|
||||||
e := newTestVMCLI(t)
|
e := newTestVMCLI(t)
|
||||||
e.runProgWithTimeout(t, 10*time.Second,
|
e.runProgWithTimeout(t, 10*time.Second,
|
||||||
|
@ -403,46 +488,34 @@ go 1.18`)
|
||||||
e.checkNextLine(t, "Error:")
|
e.checkNextLine(t, "Error:")
|
||||||
e.checkNextLine(t, "READY: loaded \\d* instructions")
|
e.checkNextLine(t, "READY: loaded \\d* instructions")
|
||||||
e.checkStack(t, 8)
|
e.checkStack(t, 8)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("simple", func(t *testing.T) {
|
||||||
|
checkLoadgo(t, "vmtestcontract.go", "vmtestcontract_err.go")
|
||||||
|
})
|
||||||
|
t.Run("utf-8 with spaces", func(t *testing.T) {
|
||||||
|
checkLoadgo(t, "тестовый контракт.go", "тестовый контракт с ошибкой.go")
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
||||||
checkLoadgo(t, "simple", "vmtestcontract.go", "vmtestcontract_err.go")
|
t.Run("check calling flags", func(t *testing.T) {
|
||||||
checkLoadgo(t, "utf-8 with spaces", "тестовый контракт.go", "тестовый контракт с ошибкой.go")
|
srcAllowNotify := `package kek
|
||||||
|
|
||||||
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 + "'"
|
|
||||||
wd, err := os.Getwd()
|
|
||||||
require.NoError(t, err)
|
|
||||||
goMod := []byte(`module test.example/kek
|
|
||||||
require (
|
|
||||||
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0
|
|
||||||
)
|
|
||||||
replace github.com/nspcc-dev/neo-go/pkg/interop => ` + filepath.Join(wd, "../../pkg/interop") + `
|
|
||||||
go 1.18`)
|
|
||||||
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"
|
import "github.com/nspcc-dev/neo-go/pkg/interop/runtime"
|
||||||
func Main() int {
|
func Main() int {
|
||||||
runtime.Log("Hello, world!")
|
runtime.Log("Hello, world!")
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
filename := prepareLoadgoSrc(t, srcAllowNotify)
|
filename := prepareLoadgoSrc(t, tmpDir, srcAllowNotify)
|
||||||
|
|
||||||
e := newTestVMCLI(t)
|
e := newTestVMCLI(t)
|
||||||
e.runProg(t,
|
e.runProg(t,
|
||||||
"loadgo "+filename,
|
"loadgo "+filename,
|
||||||
"run main")
|
"run main")
|
||||||
e.checkNextLine(t, "READY: loaded \\d* instructions")
|
e.checkNextLine(t, "READY: loaded \\d* instructions")
|
||||||
e.checkStack(t, 1)
|
e.checkStack(t, 1)
|
||||||
})
|
})
|
||||||
t.Run("loadgo, check signers", func(t *testing.T) {
|
t.Run("check signers", func(t *testing.T) {
|
||||||
srcCheckWitness := `package kek
|
srcCheckWitness := `package kek
|
||||||
import (
|
import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
|
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/interop/lib/address"
|
"github.com/nspcc-dev/neo-go/pkg/interop/lib/address"
|
||||||
|
@ -452,90 +525,78 @@ go 1.18`)
|
||||||
return runtime.CheckWitness(owner)
|
return runtime.CheckWitness(owner)
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
filename := prepareLoadgoSrc(t, srcCheckWitness)
|
filename := prepareLoadgoSrc(t, tmpDir, srcCheckWitness)
|
||||||
t.Run("invalid", func(t *testing.T) {
|
t.Run("invalid", func(t *testing.T) {
|
||||||
e := newTestVMCLI(t)
|
e := newTestVMCLI(t)
|
||||||
e.runProg(t,
|
e.runProg(t,
|
||||||
"loadgo "+filename+" "+cmdargs.CosignersSeparator,
|
"loadgo "+filename+" "+cmdargs.CosignersSeparator,
|
||||||
"loadgo "+filename+" "+"not-a-separator",
|
"loadgo "+filename+" "+"not-a-separator",
|
||||||
"loadgo "+filename+" "+cmdargs.CosignersSeparator+" not-a-signer",
|
"loadgo "+filename+" "+cmdargs.CosignersSeparator+" not-a-signer",
|
||||||
)
|
)
|
||||||
e.checkError(t, ErrInvalidParameter)
|
e.checkError(t, ErrInvalidParameter)
|
||||||
e.checkError(t, ErrInvalidParameter)
|
e.checkError(t, ErrInvalidParameter)
|
||||||
e.checkError(t, ErrInvalidParameter)
|
e.checkError(t, ErrInvalidParameter)
|
||||||
})
|
})
|
||||||
t.Run("address", func(t *testing.T) {
|
t.Run("address", func(t *testing.T) {
|
||||||
e := newTestVMCLI(t)
|
e := newTestVMCLI(t)
|
||||||
e.runProg(t,
|
e.runProg(t,
|
||||||
"loadgo "+filename+" "+cmdargs.CosignersSeparator+" "+ownerAddress, // owner:DefaultScope => true
|
"loadgo "+filename+" "+cmdargs.CosignersSeparator+" "+ownerAddress, // owner:DefaultScope => true
|
||||||
"run main",
|
"run main",
|
||||||
"loadgo "+filename+" "+cmdargs.CosignersSeparator+" "+ownerAddress+":None", // owner:None => false
|
"loadgo "+filename+" "+cmdargs.CosignersSeparator+" "+ownerAddress+":None", // owner:None => false
|
||||||
"run main")
|
"run main")
|
||||||
e.checkNextLine(t, "READY: loaded \\d+ instructions")
|
e.checkNextLine(t, "READY: loaded \\d+ instructions")
|
||||||
e.checkStack(t, true)
|
e.checkStack(t, true)
|
||||||
e.checkNextLine(t, "READY: loaded \\d+ instructions")
|
e.checkNextLine(t, "READY: loaded \\d+ instructions")
|
||||||
e.checkStack(t, false)
|
e.checkStack(t, false)
|
||||||
})
|
})
|
||||||
t.Run("string LE", func(t *testing.T) {
|
t.Run("string LE", func(t *testing.T) {
|
||||||
e := newTestVMCLI(t)
|
e := newTestVMCLI(t)
|
||||||
e.runProg(t,
|
e.runProg(t,
|
||||||
"loadgo "+filename+" "+cmdargs.CosignersSeparator+" "+ownerAcc.StringLE(), // ownerLE:DefaultScope => true
|
"loadgo "+filename+" "+cmdargs.CosignersSeparator+" "+ownerAcc.StringLE(), // ownerLE:DefaultScope => true
|
||||||
"run main",
|
"run main",
|
||||||
"loadgo "+filename+" "+cmdargs.CosignersSeparator+" "+"0x"+ownerAcc.StringLE(), // owner0xLE:DefaultScope => true
|
"loadgo "+filename+" "+cmdargs.CosignersSeparator+" "+"0x"+ownerAcc.StringLE(), // owner0xLE:DefaultScope => true
|
||||||
"run main")
|
"run main")
|
||||||
e.checkNextLine(t, "READY: loaded \\d+ instructions")
|
e.checkNextLine(t, "READY: loaded \\d+ instructions")
|
||||||
e.checkStack(t, true)
|
e.checkStack(t, true)
|
||||||
e.checkNextLine(t, "READY: loaded \\d+ instructions")
|
e.checkNextLine(t, "READY: loaded \\d+ instructions")
|
||||||
e.checkStack(t, true)
|
e.checkStack(t, true)
|
||||||
})
|
})
|
||||||
t.Run("nonwitnessed signer", func(t *testing.T) {
|
t.Run("nonwitnessed signer", func(t *testing.T) {
|
||||||
e := newTestVMCLI(t)
|
e := newTestVMCLI(t)
|
||||||
e.runProg(t,
|
e.runProg(t,
|
||||||
"loadgo "+filename+" "+cmdargs.CosignersSeparator+" "+sideAcc.StringLE(), // sideLE:DefaultScope => false
|
"loadgo "+filename+" "+cmdargs.CosignersSeparator+" "+sideAcc.StringLE(), // sideLE:DefaultScope => false
|
||||||
"run main")
|
"run main")
|
||||||
e.checkNextLine(t, "READY: loaded \\d+ instructions")
|
e.checkNextLine(t, "READY: loaded \\d+ instructions")
|
||||||
e.checkStack(t, false)
|
e.checkStack(t, false)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("loadnef", func(t *testing.T) {
|
t.Run("loadnef", func(t *testing.T) {
|
||||||
config.Version = "0.92.0-test"
|
tmpDir := t.TempDir()
|
||||||
|
|
||||||
nefFile, di, err := compiler.CompileWithOptions("test.go", strings.NewReader(src), nil)
|
manifestFile, nefFile := prepareLoadnefSrc(t, tmpDir, src)
|
||||||
require.NoError(t, err)
|
|
||||||
filename := filepath.Join(tmpDir, "vmtestcontract.nef")
|
|
||||||
rawNef, err := nefFile.Bytes()
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NoError(t, os.WriteFile(filename, rawNef, os.ModePerm))
|
|
||||||
m, err := di.ConvertToManifest(&compiler.Options{})
|
|
||||||
require.NoError(t, err)
|
|
||||||
manifestFile := filepath.Join(tmpDir, "vmtestcontract.manifest.json")
|
|
||||||
rawManifest, err := json.Marshal(m)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NoError(t, os.WriteFile(manifestFile, rawManifest, os.ModePerm))
|
|
||||||
filenameErr := filepath.Join(tmpDir, "vmtestcontract_err.nef")
|
filenameErr := filepath.Join(tmpDir, "vmtestcontract_err.nef")
|
||||||
require.NoError(t, os.WriteFile(filenameErr, append([]byte{1, 2, 3, 4}, rawNef...), os.ModePerm))
|
require.NoError(t, os.WriteFile(filenameErr, []byte{1, 2, 3, 4}, os.ModePerm))
|
||||||
notExists := filepath.Join(tmpDir, "notexists.json")
|
notExists := filepath.Join(tmpDir, "notexists.json")
|
||||||
|
|
||||||
manifestFile = "'" + manifestFile + "'"
|
|
||||||
filename = "'" + filename + "'"
|
|
||||||
filenameErr = "'" + filenameErr + "'"
|
filenameErr = "'" + filenameErr + "'"
|
||||||
|
|
||||||
e := newTestVMCLI(t)
|
e := newTestVMCLI(t)
|
||||||
e.runProg(t,
|
e.runProg(t,
|
||||||
"loadnef",
|
"loadnef",
|
||||||
"loadnef "+filenameErr+" "+manifestFile,
|
"loadnef "+filenameErr+" "+manifestFile,
|
||||||
"loadnef "+filename+" "+notExists,
|
"loadnef "+nefFile+" "+notExists,
|
||||||
"loadnef "+filename+" "+filename,
|
"loadnef "+nefFile+" "+nefFile,
|
||||||
"loadnef "+filename+" "+manifestFile,
|
"loadnef "+nefFile+" "+manifestFile,
|
||||||
"run main add 3 5",
|
"run main add 3 5",
|
||||||
"loadnef "+filename,
|
"loadnef "+nefFile,
|
||||||
"run main add 3 5",
|
"run main add 3 5",
|
||||||
"loadnef "+filename+" "+cmdargs.CosignersSeparator,
|
"loadnef "+nefFile+" "+cmdargs.CosignersSeparator,
|
||||||
"loadnef "+filename+" "+manifestFile+" "+cmdargs.CosignersSeparator,
|
"loadnef "+nefFile+" "+manifestFile+" "+cmdargs.CosignersSeparator,
|
||||||
"loadnef "+filename+" "+manifestFile+" "+"not-a-separator",
|
"loadnef "+nefFile+" "+manifestFile+" "+"not-a-separator",
|
||||||
"loadnef "+filename+" "+cmdargs.CosignersSeparator+" "+util.Uint160{1, 2, 3}.StringLE(),
|
"loadnef "+nefFile+" "+cmdargs.CosignersSeparator+" "+util.Uint160{1, 2, 3}.StringLE(),
|
||||||
"run main add 3 5",
|
"run main add 3 5",
|
||||||
"loadnef "+filename+" "+manifestFile+" "+cmdargs.CosignersSeparator+" "+util.Uint160{1, 2, 3}.StringLE(),
|
"loadnef "+nefFile+" "+manifestFile+" "+cmdargs.CosignersSeparator+" "+util.Uint160{1, 2, 3}.StringLE(),
|
||||||
"run main add 3 5",
|
"run main add 3 5",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -557,6 +618,72 @@ go 1.18`)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLoad_RunWithCALLT(t *testing.T) {
|
||||||
|
// Our smart compiler will generate CALLT instruction for the following StdLib call:
|
||||||
|
src := `package kek
|
||||||
|
import "github.com/nspcc-dev/neo-go/pkg/interop/native/std"
|
||||||
|
func Main() int {
|
||||||
|
return std.Atoi("123", 10)
|
||||||
|
}`
|
||||||
|
|
||||||
|
t.Run("loadgo", func(t *testing.T) {
|
||||||
|
tmp := t.TempDir()
|
||||||
|
filename := prepareLoadgoSrc(t, tmp, src)
|
||||||
|
e := newTestVMCLI(t)
|
||||||
|
e.runProg(t,
|
||||||
|
"loadgo "+filename,
|
||||||
|
"run main",
|
||||||
|
)
|
||||||
|
e.checkNextLine(t, "READY: loaded \\d* instructions")
|
||||||
|
e.checkStack(t, 123)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("loadnef", func(t *testing.T) {
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
manifestFile, nefFile := prepareLoadnefSrc(t, tmpDir, src)
|
||||||
|
|
||||||
|
e := newTestVMCLI(t)
|
||||||
|
e.runProg(t,
|
||||||
|
"loadnef "+nefFile+" "+manifestFile,
|
||||||
|
"run main",
|
||||||
|
)
|
||||||
|
e.checkNextLine(t, "READY: loaded \\d* instructions")
|
||||||
|
e.checkStack(t, 123)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("loaddeployed", func(t *testing.T) {
|
||||||
|
// We'll use `Runtime example` example contract which has a call to native Management
|
||||||
|
// inside performed via CALLT instruction (`destroy` method).
|
||||||
|
e := newTestVMClIWithState(t)
|
||||||
|
var (
|
||||||
|
cH util.Uint160
|
||||||
|
cName = "Runtime example"
|
||||||
|
bc = e.cli.chain
|
||||||
|
)
|
||||||
|
for i := int32(1); ; i++ {
|
||||||
|
h, err := bc.GetContractScriptHash(i)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
cs := bc.GetContractState(h)
|
||||||
|
if cs == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if cs.Manifest.Name == cName {
|
||||||
|
cH = cs.Hash
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
require.NotEmpty(t, cH, fmt.Sprintf("failed to locate `%s` example contract with CALLT usage in the simple chain", cName))
|
||||||
|
e.runProg(t,
|
||||||
|
"loaddeployed "+cH.StringLE()+" -- NbrUYaZgyhSkNoRo9ugRyEMdUZxrhkNaWB:Global", // the contract's owner got from the contract's code.
|
||||||
|
"run destroy",
|
||||||
|
)
|
||||||
|
e.checkNextLine(t, "READY: loaded \\d* instructions")
|
||||||
|
e.checkStack(t) // Nothing on stack, successful execution.
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestRunWithDifferentArguments(t *testing.T) {
|
func TestRunWithDifferentArguments(t *testing.T) {
|
||||||
src := `package kek
|
src := `package kek
|
||||||
var a = 1
|
var a = 1
|
||||||
|
|
|
@ -65,7 +65,7 @@ func Verify() bool {
|
||||||
|
|
||||||
// Destroy destroys the contract, only the owner can do that.
|
// Destroy destroys the contract, only the owner can do that.
|
||||||
func Destroy() {
|
func Destroy() {
|
||||||
if !Verify() {
|
if !CheckWitness() {
|
||||||
panic("only owner can destroy")
|
panic("only owner can destroy")
|
||||||
}
|
}
|
||||||
management.Destroy()
|
management.Destroy()
|
||||||
|
@ -74,7 +74,7 @@ func Destroy() {
|
||||||
// Update updates the contract, only the owner can do that. _deploy will be called
|
// Update updates the contract, only the owner can do that. _deploy will be called
|
||||||
// after update.
|
// after update.
|
||||||
func Update(nef, manifest []byte) {
|
func Update(nef, manifest []byte) {
|
||||||
if !Verify() {
|
if !CheckWitness() {
|
||||||
panic("only owner can update")
|
panic("only owner can update")
|
||||||
}
|
}
|
||||||
management.Update(nef, manifest)
|
management.Update(nef, manifest)
|
||||||
|
|
|
@ -350,3 +350,10 @@ func DynamicOnUnload(v *VM, ctx *Context, commit bool) error {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BreakPoints returns the current set of Context's breakpoints.
|
||||||
|
func (c *Context) BreakPoints() []int {
|
||||||
|
res := make([]int, len(c.sc.breakPoints))
|
||||||
|
copy(res, c.sc.breakPoints)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
|
@ -4,6 +4,8 @@ import (
|
||||||
"math/big"
|
"math/big"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
@ -40,3 +42,20 @@ func TestVM_Debug(t *testing.T) {
|
||||||
require.Equal(t, big.NewInt(5), v.estack.Top().Value())
|
require.Equal(t, big.NewInt(5), v.estack.Top().Value())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestContext_BreakPoints(t *testing.T) {
|
||||||
|
prog := makeProgram(opcode.CALL, 3, opcode.RET,
|
||||||
|
opcode.PUSH2, opcode.PUSH3, opcode.ADD, opcode.RET)
|
||||||
|
v := load(prog)
|
||||||
|
v.AddBreakPoint(3)
|
||||||
|
v.AddBreakPoint(5)
|
||||||
|
require.Equal(t, []int{3, 5}, v.Context().BreakPoints())
|
||||||
|
|
||||||
|
// Preserve the set of breakpoints on Call.
|
||||||
|
v.Call(3)
|
||||||
|
require.Equal(t, []int{3, 5}, v.Context().BreakPoints())
|
||||||
|
|
||||||
|
// New context -> clean breakpoints.
|
||||||
|
v.loadScriptWithCallingHash(prog, nil, util.Uint160{}, util.Uint160{}, callflag.All, 1, 3, nil)
|
||||||
|
require.Equal(t, []int{}, v.Context().BreakPoints())
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue