forked from TrueCloudLab/neoneo-go
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/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"
|
||||
|
@ -51,7 +53,7 @@ const (
|
|||
chainKey = "chain"
|
||||
chainCfgKey = "chainCfg"
|
||||
icKey = "ic"
|
||||
manifestKey = "manifest"
|
||||
contractStateKey = "contractState"
|
||||
exitFuncKey = "exitFunc"
|
||||
readlineInstanceKey = "readlineKey"
|
||||
printLogoKey = "printLogoKey"
|
||||
|
@ -64,6 +66,7 @@ const (
|
|||
gasFlagFullName = "gas"
|
||||
backwardsFlagFullName = "backwards"
|
||||
diffFlagFullName = "diff"
|
||||
hashFlagFullName = "hash"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -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{
|
||||
|
@ -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.
|
||||
|
@ -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 + `
|
||||
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -1023,7 +1061,7 @@ 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 (
|
||||
|
@ -1031,6 +1069,7 @@ func handleRun(c *cli.Context) error {
|
|||
offset int
|
||||
err error
|
||||
runCurrent = args[0] != "_"
|
||||
hasRet bool
|
||||
)
|
||||
|
||||
_, scParams, err := cmdargs.ParseParams(args[1:], true)
|
||||
|
@ -1045,27 +1084,35 @@ func handleRun(c *cli.Context) error {
|
|||
}
|
||||
}
|
||||
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")
|
||||
}
|
||||
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)
|
||||
|
|
|
@ -281,6 +281,97 @@ func (e *executor) checkSlot(t *testing.T, items ...any) {
|
|||
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) {
|
||||
script := []byte{byte(opcode.PUSH3), byte(opcode.PUSH4), byte(opcode.ADD)}
|
||||
|
||||
|
@ -378,10 +469,11 @@ func TestLoad(t *testing.T) {
|
|||
return a * b
|
||||
}
|
||||
}`
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
checkLoadgo := func(t *testing.T, tName, cName, cErrName string) {
|
||||
t.Run("loadgo "+tName, func(t *testing.T) {
|
||||
t.Run("loadgo", func(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
checkLoadgo := func(t *testing.T, cName, cErrName string) {
|
||||
filename := filepath.Join(tmpDir, cName)
|
||||
require.NoError(t, os.WriteFile(filename, []byte(src), os.ModePerm))
|
||||
filename = "'" + filename + "'"
|
||||
|
@ -403,46 +495,34 @@ go 1.18`)
|
|||
e.checkNextLine(t, "Error:")
|
||||
e.checkNextLine(t, "READY: loaded \\d* instructions")
|
||||
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")
|
||||
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
|
||||
t.Run("check calling flags", func(t *testing.T) {
|
||||
srcAllowNotify := `package kek
|
||||
import "github.com/nspcc-dev/neo-go/pkg/interop/runtime"
|
||||
func Main() int {
|
||||
runtime.Log("Hello, world!")
|
||||
return 1
|
||||
}
|
||||
`
|
||||
filename := prepareLoadgoSrc(t, srcAllowNotify)
|
||||
filename := prepareLoadgoSrc(t, tmpDir, srcAllowNotify)
|
||||
|
||||
e := newTestVMCLI(t)
|
||||
e.runProg(t,
|
||||
"loadgo "+filename,
|
||||
"run main")
|
||||
e.checkNextLine(t, "READY: loaded \\d* instructions")
|
||||
e.checkStack(t, 1)
|
||||
})
|
||||
t.Run("loadgo, check signers", func(t *testing.T) {
|
||||
srcCheckWitness := `package kek
|
||||
e := newTestVMCLI(t)
|
||||
e.runProg(t,
|
||||
"loadgo "+filename,
|
||||
"run main")
|
||||
e.checkNextLine(t, "READY: loaded \\d* instructions")
|
||||
e.checkStack(t, 1)
|
||||
})
|
||||
t.Run("check signers", func(t *testing.T) {
|
||||
srcCheckWitness := `package kek
|
||||
import (
|
||||
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
|
||||
"github.com/nspcc-dev/neo-go/pkg/interop/lib/address"
|
||||
|
@ -452,90 +532,78 @@ go 1.18`)
|
|||
return runtime.CheckWitness(owner)
|
||||
}
|
||||
`
|
||||
filename := prepareLoadgoSrc(t, srcCheckWitness)
|
||||
t.Run("invalid", func(t *testing.T) {
|
||||
e := newTestVMCLI(t)
|
||||
e.runProg(t,
|
||||
"loadgo "+filename+" "+cmdargs.CosignersSeparator,
|
||||
"loadgo "+filename+" "+"not-a-separator",
|
||||
"loadgo "+filename+" "+cmdargs.CosignersSeparator+" not-a-signer",
|
||||
)
|
||||
e.checkError(t, ErrInvalidParameter)
|
||||
e.checkError(t, ErrInvalidParameter)
|
||||
e.checkError(t, ErrInvalidParameter)
|
||||
})
|
||||
t.Run("address", func(t *testing.T) {
|
||||
e := newTestVMCLI(t)
|
||||
e.runProg(t,
|
||||
"loadgo "+filename+" "+cmdargs.CosignersSeparator+" "+ownerAddress, // owner:DefaultScope => true
|
||||
"run main",
|
||||
"loadgo "+filename+" "+cmdargs.CosignersSeparator+" "+ownerAddress+":None", // owner:None => false
|
||||
"run main")
|
||||
e.checkNextLine(t, "READY: loaded \\d+ instructions")
|
||||
e.checkStack(t, true)
|
||||
e.checkNextLine(t, "READY: loaded \\d+ instructions")
|
||||
e.checkStack(t, false)
|
||||
})
|
||||
t.Run("string LE", func(t *testing.T) {
|
||||
e := newTestVMCLI(t)
|
||||
e.runProg(t,
|
||||
"loadgo "+filename+" "+cmdargs.CosignersSeparator+" "+ownerAcc.StringLE(), // ownerLE:DefaultScope => true
|
||||
"run main",
|
||||
"loadgo "+filename+" "+cmdargs.CosignersSeparator+" "+"0x"+ownerAcc.StringLE(), // owner0xLE:DefaultScope => true
|
||||
"run main")
|
||||
e.checkNextLine(t, "READY: loaded \\d+ instructions")
|
||||
e.checkStack(t, true)
|
||||
e.checkNextLine(t, "READY: loaded \\d+ instructions")
|
||||
e.checkStack(t, true)
|
||||
})
|
||||
t.Run("nonwitnessed signer", func(t *testing.T) {
|
||||
e := newTestVMCLI(t)
|
||||
e.runProg(t,
|
||||
"loadgo "+filename+" "+cmdargs.CosignersSeparator+" "+sideAcc.StringLE(), // sideLE:DefaultScope => false
|
||||
"run main")
|
||||
e.checkNextLine(t, "READY: loaded \\d+ instructions")
|
||||
e.checkStack(t, false)
|
||||
filename := prepareLoadgoSrc(t, tmpDir, srcCheckWitness)
|
||||
t.Run("invalid", func(t *testing.T) {
|
||||
e := newTestVMCLI(t)
|
||||
e.runProg(t,
|
||||
"loadgo "+filename+" "+cmdargs.CosignersSeparator,
|
||||
"loadgo "+filename+" "+"not-a-separator",
|
||||
"loadgo "+filename+" "+cmdargs.CosignersSeparator+" not-a-signer",
|
||||
)
|
||||
e.checkError(t, ErrInvalidParameter)
|
||||
e.checkError(t, ErrInvalidParameter)
|
||||
e.checkError(t, ErrInvalidParameter)
|
||||
})
|
||||
t.Run("address", func(t *testing.T) {
|
||||
e := newTestVMCLI(t)
|
||||
e.runProg(t,
|
||||
"loadgo "+filename+" "+cmdargs.CosignersSeparator+" "+ownerAddress, // owner:DefaultScope => true
|
||||
"run main",
|
||||
"loadgo "+filename+" "+cmdargs.CosignersSeparator+" "+ownerAddress+":None", // owner:None => false
|
||||
"run main")
|
||||
e.checkNextLine(t, "READY: loaded \\d+ instructions")
|
||||
e.checkStack(t, true)
|
||||
e.checkNextLine(t, "READY: loaded \\d+ instructions")
|
||||
e.checkStack(t, false)
|
||||
})
|
||||
t.Run("string LE", func(t *testing.T) {
|
||||
e := newTestVMCLI(t)
|
||||
e.runProg(t,
|
||||
"loadgo "+filename+" "+cmdargs.CosignersSeparator+" "+ownerAcc.StringLE(), // ownerLE:DefaultScope => true
|
||||
"run main",
|
||||
"loadgo "+filename+" "+cmdargs.CosignersSeparator+" "+"0x"+ownerAcc.StringLE(), // owner0xLE:DefaultScope => true
|
||||
"run main")
|
||||
e.checkNextLine(t, "READY: loaded \\d+ instructions")
|
||||
e.checkStack(t, true)
|
||||
e.checkNextLine(t, "READY: loaded \\d+ instructions")
|
||||
e.checkStack(t, true)
|
||||
})
|
||||
t.Run("nonwitnessed signer", func(t *testing.T) {
|
||||
e := newTestVMCLI(t)
|
||||
e.runProg(t,
|
||||
"loadgo "+filename+" "+cmdargs.CosignersSeparator+" "+sideAcc.StringLE(), // sideLE:DefaultScope => false
|
||||
"run main")
|
||||
e.checkNextLine(t, "READY: loaded \\d+ instructions")
|
||||
e.checkStack(t, false)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
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)
|
||||
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, nefFile := prepareLoadnefSrc(t, tmpDir, src)
|
||||
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")
|
||||
|
||||
manifestFile = "'" + manifestFile + "'"
|
||||
filename = "'" + filename + "'"
|
||||
filenameErr = "'" + filenameErr + "'"
|
||||
|
||||
e := newTestVMCLI(t)
|
||||
e.runProg(t,
|
||||
"loadnef",
|
||||
"loadnef "+filenameErr+" "+manifestFile,
|
||||
"loadnef "+filename+" "+notExists,
|
||||
"loadnef "+filename+" "+filename,
|
||||
"loadnef "+filename+" "+manifestFile,
|
||||
"loadnef "+nefFile+" "+notExists,
|
||||
"loadnef "+nefFile+" "+nefFile,
|
||||
"loadnef "+nefFile+" "+manifestFile,
|
||||
"run main add 3 5",
|
||||
"loadnef "+filename,
|
||||
"loadnef "+nefFile,
|
||||
"run main add 3 5",
|
||||
"loadnef "+filename+" "+cmdargs.CosignersSeparator,
|
||||
"loadnef "+filename+" "+manifestFile+" "+cmdargs.CosignersSeparator,
|
||||
"loadnef "+filename+" "+manifestFile+" "+"not-a-separator",
|
||||
"loadnef "+filename+" "+cmdargs.CosignersSeparator+" "+util.Uint160{1, 2, 3}.StringLE(),
|
||||
"loadnef "+nefFile+" "+cmdargs.CosignersSeparator,
|
||||
"loadnef "+nefFile+" "+manifestFile+" "+cmdargs.CosignersSeparator,
|
||||
"loadnef "+nefFile+" "+manifestFile+" "+"not-a-separator",
|
||||
"loadnef "+nefFile+" "+cmdargs.CosignersSeparator+" "+util.Uint160{1, 2, 3}.StringLE(),
|
||||
"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",
|
||||
)
|
||||
|
||||
|
@ -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) {
|
||||
src := `package kek
|
||||
var a = 1
|
||||
|
|
|
@ -350,3 +350,10 @@ func DynamicOnUnload(v *VM, ctx *Context, commit bool) error {
|
|||
}
|
||||
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"
|
||||
"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/stretchr/testify/require"
|
||||
)
|
||||
|
@ -40,3 +42,20 @@ func TestVM_Debug(t *testing.T) {
|
|||
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