cli: properly handle run
VM CLI command
Properly load the provided method using NEF and hash specified. It allows to have NEF properly set in the VM context and handle CALLT instruction correctly. Signed-off-by: Anna Shaleva <shaleva.ann@nspcc.ru>
This commit is contained in:
parent
35c3518b37
commit
b6b80f3abf
4 changed files with 343 additions and 136 deletions
113
cli/vm/cli.go
113
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' and 'loadnef' 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,10 +469,11 @@ func TestLoad(t *testing.T) {
|
||||||
return a * b
|
return a * b
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
|
|
||||||
|
t.Run("loadgo", func(t *testing.T) {
|
||||||
tmpDir := t.TempDir()
|
tmpDir := t.TempDir()
|
||||||
|
|
||||||
checkLoadgo := func(t *testing.T, tName, cName, cErrName string) {
|
checkLoadgo := func(t *testing.T, cName, cErrName string) {
|
||||||
t.Run("loadgo "+tName, func(t *testing.T) {
|
|
||||||
filename := filepath.Join(tmpDir, cName)
|
filename := filepath.Join(tmpDir, cName)
|
||||||
require.NoError(t, os.WriteFile(filename, []byte(src), os.ModePerm))
|
require.NoError(t, os.WriteFile(filename, []byte(src), os.ModePerm))
|
||||||
filename = "'" + filename + "'"
|
filename = "'" + filename + "'"
|
||||||
|
@ -403,28 +495,16 @@ 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")
|
|
||||||
|
|
||||||
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
|
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 {
|
||||||
|
@ -432,7 +512,7 @@ go 1.18`)
|
||||||
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,
|
||||||
|
@ -441,7 +521,7 @@ go 1.18`)
|
||||||
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"
|
||||||
|
@ -452,7 +532,7 @@ 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,
|
||||||
|
@ -497,45 +577,33 @@ go 1.18`)
|
||||||
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 +625,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
|
||||||
|
|
|
@ -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…
Add table
Add a link
Reference in a new issue