2022-10-05 09:30:54 +00:00
package vm
2020-12-01 15:27:38 +00:00
import (
"bytes"
"encoding/base64"
"encoding/hex"
"encoding/json"
2022-10-10 10:59:37 +00:00
"errors"
2020-12-01 15:27:38 +00:00
"fmt"
gio "io"
2022-10-10 11:00:26 +00:00
"math/big"
2020-12-01 15:27:38 +00:00
"os"
2021-11-17 11:14:22 +00:00
"path/filepath"
2022-10-12 10:16:19 +00:00
"strconv"
2020-12-01 15:27:38 +00:00
"strings"
"sync"
"testing"
"time"
2022-02-15 12:55:25 +00:00
"github.com/chzyer/readline"
2023-05-16 14:54:41 +00:00
"github.com/nspcc-dev/neo-go/cli/cmdargs"
2022-10-10 10:59:37 +00:00
"github.com/nspcc-dev/neo-go/cli/paramcontext"
2022-10-03 12:05:48 +00:00
"github.com/nspcc-dev/neo-go/internal/basicchain"
2021-10-29 11:55:08 +00:00
"github.com/nspcc-dev/neo-go/internal/random"
2020-12-01 15:27:38 +00:00
"github.com/nspcc-dev/neo-go/pkg/compiler"
"github.com/nspcc-dev/neo-go/pkg/config"
2022-10-10 10:59:37 +00:00
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
2020-12-01 15:27:38 +00:00
"github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames"
2022-10-04 10:05:51 +00:00
"github.com/nspcc-dev/neo-go/pkg/core/state"
2022-10-03 12:05:48 +00:00
"github.com/nspcc-dev/neo-go/pkg/core/storage"
"github.com/nspcc-dev/neo-go/pkg/core/storage/dbconfig"
2022-10-05 12:06:20 +00:00
"github.com/nspcc-dev/neo-go/pkg/core/storage/dboper"
2021-10-29 11:55:08 +00:00
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
2020-12-01 15:27:38 +00:00
"github.com/nspcc-dev/neo-go/pkg/io"
2022-10-03 12:05:48 +00:00
"github.com/nspcc-dev/neo-go/pkg/neotest"
"github.com/nspcc-dev/neo-go/pkg/neotest/chain"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
2020-12-01 15:27:38 +00:00
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
2021-09-08 14:27:11 +00:00
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
2020-12-01 15:27:38 +00:00
"github.com/stretchr/testify/require"
"go.uber.org/atomic"
)
type readCloser struct {
sync . Mutex
bytes . Buffer
}
func ( r * readCloser ) Close ( ) error {
return nil
}
func ( r * readCloser ) Read ( p [ ] byte ) ( int , error ) {
r . Lock ( )
defer r . Unlock ( )
return r . Buffer . Read ( p )
}
func ( r * readCloser ) WriteString ( s string ) {
r . Lock ( )
defer r . Unlock ( )
r . Buffer . WriteString ( s )
}
type executor struct {
in * readCloser
out * bytes . Buffer
2022-10-07 12:47:21 +00:00
cli * CLI
2020-12-01 15:27:38 +00:00
ch chan struct { }
exit atomic . Bool
}
func newTestVMCLI ( t * testing . T ) * executor {
return newTestVMCLIWithLogo ( t , false )
}
func newTestVMCLIWithLogo ( t * testing . T , printLogo bool ) * executor {
2022-10-03 12:05:48 +00:00
return newTestVMCLIWithLogoAndCustomConfig ( t , printLogo , nil )
}
func newTestVMCLIWithLogoAndCustomConfig ( t * testing . T , printLogo bool , cfg * config . Config ) * executor {
2020-12-01 15:27:38 +00:00
e := & executor {
in : & readCloser { Buffer : * bytes . NewBuffer ( nil ) } ,
out : bytes . NewBuffer ( nil ) ,
ch : make ( chan struct { } ) ,
}
2022-10-03 12:05:48 +00:00
var c config . Config
if cfg == nil {
configPath := "../../config/protocol.unit_testnet.single.yml"
var err error
c , err = config . LoadFile ( configPath )
require . NoError ( t , err , "could not load chain config" )
c . ApplicationConfiguration . DBConfiguration . Type = dbconfig . InMemoryDB
} else {
c = * cfg
}
var err error
e . cli , err = NewWithConfig ( printLogo ,
2020-12-01 15:27:38 +00:00
func ( int ) { e . exit . Store ( true ) } ,
& readline . Config {
Prompt : "" ,
Stdin : e . in ,
2022-02-15 12:55:25 +00:00
Stderr : e . out ,
2020-12-01 15:27:38 +00:00
Stdout : e . out ,
2021-11-30 17:13:52 +00:00
FuncIsTerminal : func ( ) bool {
return false
} ,
2022-10-03 12:05:48 +00:00
} , c )
require . NoError ( t , err )
2020-12-01 15:27:38 +00:00
return e
}
2022-10-10 04:37:08 +00:00
// newTestVMClIWithState creates executor backed by level DB filled by simple chain.
// LevelDB-backed CLI must be exited on cleanup.
2022-10-03 12:05:48 +00:00
func newTestVMClIWithState ( t * testing . T ) * executor {
// Firstly create a DB with chain, save and close it.
path := t . TempDir ( )
opts := dbconfig . LevelDBOptions {
DataDirectoryPath : path ,
}
store , err := storage . NewLevelDBStore ( opts )
require . NoError ( t , err )
2022-12-06 13:34:38 +00:00
customConfig := func ( c * config . Blockchain ) {
2022-10-03 12:05:48 +00:00
c . StateRootInHeader = true // Need for P2PStateExchangeExtensions check.
c . P2PSigExtensions = true // Need for basic chain initializer.
}
bc , validators , committee , err := chain . NewMultiWithCustomConfigAndStoreNoCheck ( t , customConfig , store )
require . NoError ( t , err )
go bc . Run ( )
e := neotest . NewExecutor ( t , bc , validators , committee )
basicchain . InitSimple ( t , "../../" , e )
bc . Close ( )
2022-10-07 12:47:21 +00:00
// After that create CLI backed by created chain.
2022-10-03 12:05:48 +00:00
configPath := "../../config/protocol.unit_testnet.yml"
cfg , err := config . LoadFile ( configPath )
require . NoError ( t , err )
cfg . ApplicationConfiguration . DBConfiguration . Type = dbconfig . LevelDB
cfg . ApplicationConfiguration . DBConfiguration . LevelDBOptions = opts
cfg . ProtocolConfiguration . StateRootInHeader = true
return newTestVMCLIWithLogoAndCustomConfig ( t , false , & cfg )
}
2020-12-01 15:27:38 +00:00
func ( e * executor ) runProg ( t * testing . T , commands ... string ) {
2022-02-09 13:36:27 +00:00
e . runProgWithTimeout ( t , 4 * time . Second , commands ... )
}
func ( e * executor ) runProgWithTimeout ( t * testing . T , timeout time . Duration , commands ... string ) {
2020-12-01 15:27:38 +00:00
cmd := strings . Join ( commands , "\n" ) + "\n"
e . in . WriteString ( cmd + "\n" )
2020-12-10 10:35:50 +00:00
go func ( ) {
require . NoError ( t , e . cli . Run ( ) )
close ( e . ch )
} ( )
2020-12-01 15:27:38 +00:00
select {
case <- e . ch :
2022-02-09 13:36:27 +00:00
case <- time . After ( timeout ) :
2020-12-01 15:27:38 +00:00
require . Fail ( t , "command took too long time" )
}
}
func ( e * executor ) checkNextLine ( t * testing . T , expected string ) {
line , err := e . out . ReadString ( '\n' )
require . NoError ( t , err )
require . Regexp ( t , expected , line )
}
2022-10-04 10:05:51 +00:00
func ( e * executor ) checkNextLineExact ( t * testing . T , expected string ) {
line , err := e . out . ReadString ( '\n' )
require . NoError ( t , err )
require . Equal ( t , expected , line )
}
2020-12-01 15:27:38 +00:00
func ( e * executor ) checkError ( t * testing . T , expectedErr error ) {
line , err := e . out . ReadString ( '\n' )
require . NoError ( t , err )
2021-11-29 08:09:39 +00:00
expected := "Error: " + expectedErr . Error ( )
require . True ( t , strings . HasPrefix ( line , expected ) , fmt . Errorf ( "expected `%s`, got `%s`" , expected , line ) )
2020-12-01 15:27:38 +00:00
}
2023-04-03 10:34:24 +00:00
func ( e * executor ) checkStack ( t * testing . T , items ... any ) {
2020-12-01 15:27:38 +00:00
d := json . NewDecoder ( e . out )
2023-04-03 10:34:24 +00:00
var actual any
2020-12-01 15:27:38 +00:00
require . NoError ( t , d . Decode ( & actual ) )
rawActual , err := json . Marshal ( actual )
require . NoError ( t , err )
expected := vm . NewStack ( "" )
for i := range items {
expected . PushVal ( items [ i ] )
}
rawExpected , err := json . Marshal ( expected )
require . NoError ( t , err )
require . JSONEq ( t , string ( rawExpected ) , string ( rawActual ) )
// Decoder has it's own buffer, we need to return unread part to the output.
outRemain := e . out . String ( )
e . out . Reset ( )
_ , err = gio . Copy ( e . out , d . Buffered ( ) )
require . NoError ( t , err )
e . out . WriteString ( outRemain )
_ , err = e . out . ReadString ( '\n' )
require . NoError ( t , err )
}
2022-10-04 10:05:51 +00:00
func ( e * executor ) checkEvents ( t * testing . T , isKeywordExpected bool , events ... state . NotificationEvent ) {
if isKeywordExpected {
e . checkNextLine ( t , "Events:" )
}
d := json . NewDecoder ( e . out )
2023-04-03 10:34:24 +00:00
var actual any
2022-10-04 10:05:51 +00:00
require . NoError ( t , d . Decode ( & actual ) )
rawActual , err := json . Marshal ( actual )
require . NoError ( t , err )
rawExpected , err := json . Marshal ( events )
require . NoError ( t , err )
require . JSONEq ( t , string ( rawExpected ) , string ( rawActual ) )
// Decoder has it's own buffer, we need to return unread part to the output.
outRemain := e . out . String ( )
e . out . Reset ( )
_ , err = gio . Copy ( e . out , d . Buffered ( ) )
require . NoError ( t , err )
e . out . WriteString ( outRemain )
_ , err = e . out . ReadString ( '\n' )
require . NoError ( t , err )
}
2022-10-04 12:38:42 +00:00
func ( e * executor ) checkStorage ( t * testing . T , kvs ... storage . KeyValue ) {
for _ , kv := range kvs {
e . checkNextLine ( t , fmt . Sprintf ( "%s: %s" , hex . EncodeToString ( kv . Key ) , hex . EncodeToString ( kv . Value ) ) )
}
}
2022-10-05 12:06:20 +00:00
type storageChange struct {
ContractID int32
dboper . Operation
}
func ( e * executor ) checkChange ( t * testing . T , c storageChange ) {
e . checkNextLine ( t , fmt . Sprintf ( "Contract ID: %d" , c . ContractID ) )
e . checkNextLine ( t , fmt . Sprintf ( "State: %s" , c . State ) )
e . checkNextLine ( t , fmt . Sprintf ( "Key: %s" , hex . EncodeToString ( c . Key ) ) )
if c . Value != nil {
e . checkNextLine ( t , fmt . Sprintf ( "Value: %s" , hex . EncodeToString ( c . Value ) ) )
}
}
2023-04-03 10:34:24 +00:00
func ( e * executor ) checkSlot ( t * testing . T , items ... any ) {
2021-09-08 14:27:11 +00:00
d := json . NewDecoder ( e . out )
2023-04-03 10:34:24 +00:00
var actual any
2021-09-08 14:27:11 +00:00
require . NoError ( t , d . Decode ( & actual ) )
rawActual , err := json . Marshal ( actual )
require . NoError ( t , err )
expected := make ( [ ] json . RawMessage , len ( items ) )
for i := range items {
if items [ i ] == nil {
expected [ i ] = [ ] byte ( "null" )
continue
}
data , err := stackitem . ToJSONWithTypes ( stackitem . Make ( items [ i ] ) )
require . NoError ( t , err )
expected [ i ] = data
}
rawExpected , err := json . MarshalIndent ( expected , "" , " " )
require . NoError ( t , err )
require . JSONEq ( t , string ( rawExpected ) , string ( rawActual ) )
// Decoder has it's own buffer, we need to return unread part to the output.
outRemain := e . out . String ( )
e . out . Reset ( )
_ , err = gio . Copy ( e . out , d . Buffered ( ) )
require . NoError ( t , err )
e . out . WriteString ( outRemain )
_ , err = e . out . ReadString ( '\n' )
require . NoError ( t , err )
}
2023-05-17 16:01:24 +00:00
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
}
2020-12-01 15:27:38 +00:00
func TestLoad ( t * testing . T ) {
script := [ ] byte { byte ( opcode . PUSH3 ) , byte ( opcode . PUSH4 ) , byte ( opcode . ADD ) }
2022-10-11 11:59:51 +00:00
ownerAddress := "NbrUYaZgyhSkNoRo9ugRyEMdUZxrhkNaWB"
ownerAcc , err := address . StringToUint160 ( ownerAddress )
require . NoError ( t , err )
sideAcc := util . Uint160 { 1 , 2 , 3 }
buff := io . NewBufBinWriter ( )
emit . Bytes ( buff . BinWriter , ownerAcc . BytesBE ( ) )
emit . Syscall ( buff . BinWriter , interopnames . SystemRuntimeCheckWitness )
checkWitnessScript := buff . Bytes ( )
2020-12-01 15:27:38 +00:00
t . Run ( "loadhex" , func ( t * testing . T ) {
e := newTestVMCLI ( t )
e . runProg ( t ,
"loadhex" ,
"loadhex notahex" ,
2022-10-11 11:59:51 +00:00
"loadhex " + hex . EncodeToString ( script ) ,
2023-05-16 14:54:41 +00:00
"loadhex " + hex . EncodeToString ( checkWitnessScript ) + " " + cmdargs . CosignersSeparator ,
"loadhex " + hex . EncodeToString ( checkWitnessScript ) + " " + "not-a-separator" ,
"loadhex " + hex . EncodeToString ( checkWitnessScript ) + " " + cmdargs . CosignersSeparator + " " + "not-a-signer" ,
"loadhex " + hex . EncodeToString ( checkWitnessScript ) + " " + cmdargs . CosignersSeparator + " " + ownerAddress , // owner:DefaultScope => true
2022-10-11 11:59:51 +00:00
"run" ,
2023-05-16 14:54:41 +00:00
"loadhex " + hex . EncodeToString ( checkWitnessScript ) + " " + cmdargs . CosignersSeparator + " " + ownerAddress + ":None" , // owner:None => false
2022-10-11 11:59:51 +00:00
"run" ,
2023-05-16 14:54:41 +00:00
"loadhex " + hex . EncodeToString ( checkWitnessScript ) + " " + cmdargs . CosignersSeparator + " " + ownerAcc . StringLE ( ) , // ownerLE:DefaultScope => true
2022-10-11 11:59:51 +00:00
"run" ,
2023-05-16 14:54:41 +00:00
"loadhex " + hex . EncodeToString ( checkWitnessScript ) + " " + cmdargs . CosignersSeparator + " " + "0x" + ownerAcc . StringLE ( ) , // owner0xLE:DefaultScope => true
2022-10-11 11:59:51 +00:00
"run" ,
2023-05-16 14:54:41 +00:00
"loadhex " + hex . EncodeToString ( checkWitnessScript ) + " " + cmdargs . CosignersSeparator + " " + sideAcc . StringLE ( ) , // sideLE:DefaultScope => false
2022-10-11 11:59:51 +00:00
"run" ,
)
2020-12-01 15:27:38 +00:00
e . checkError ( t , ErrMissingParameter )
e . checkError ( t , ErrInvalidParameter )
e . checkNextLine ( t , "READY: loaded 3 instructions" )
2023-05-16 14:54:41 +00:00
e . checkError ( t , ErrInvalidParameter )
e . checkError ( t , ErrInvalidParameter )
e . checkError ( t , ErrInvalidParameter )
2022-10-11 11:59:51 +00:00
e . checkNextLine ( t , "READY: loaded \\d+ instructions" )
e . checkStack ( t , true )
e . checkNextLine ( t , "READY: loaded \\d+ instructions" )
e . checkStack ( t , false )
e . checkNextLine ( t , "READY: loaded \\d+ instructions" )
e . checkStack ( t , true )
e . checkNextLine ( t , "READY: loaded \\d+ instructions" )
e . checkStack ( t , true )
e . checkNextLine ( t , "READY: loaded \\d+ instructions" )
e . checkStack ( t , false )
2020-12-01 15:27:38 +00:00
} )
t . Run ( "loadbase64" , func ( t * testing . T ) {
e := newTestVMCLI ( t )
e . runProg ( t ,
"loadbase64" ,
"loadbase64 not_a_base64" ,
2022-10-11 11:59:51 +00:00
"loadbase64 " + base64 . StdEncoding . EncodeToString ( script ) ,
2023-05-16 14:54:41 +00:00
"loadbase64 " + base64 . StdEncoding . EncodeToString ( checkWitnessScript ) + " " + cmdargs . CosignersSeparator ,
"loadbase64 " + base64 . StdEncoding . EncodeToString ( checkWitnessScript ) + " " + "not-a-separator" ,
"loadbase64 " + base64 . StdEncoding . EncodeToString ( checkWitnessScript ) + " " + cmdargs . CosignersSeparator + " not-a-signer" ,
"loadbase64 " + base64 . StdEncoding . EncodeToString ( checkWitnessScript ) + " " + cmdargs . CosignersSeparator + " " + ownerAddress , // owner:DefaultScope => true
2022-10-11 11:59:51 +00:00
"run" ,
2023-05-16 14:54:41 +00:00
"loadbase64 " + base64 . StdEncoding . EncodeToString ( checkWitnessScript ) + " " + cmdargs . CosignersSeparator + " " + ownerAddress + ":None" , // owner:None => false
2022-10-11 11:59:51 +00:00
"run" ,
2023-05-16 14:54:41 +00:00
"loadbase64 " + base64 . StdEncoding . EncodeToString ( checkWitnessScript ) + " " + cmdargs . CosignersSeparator + " " + ownerAcc . StringLE ( ) , // ownerLE:DefaultScope => true
2022-10-11 11:59:51 +00:00
"run" ,
2023-05-16 14:54:41 +00:00
"loadbase64 " + base64 . StdEncoding . EncodeToString ( checkWitnessScript ) + " " + cmdargs . CosignersSeparator + " " + "0x" + ownerAcc . StringLE ( ) , // owner0xLE:DefaultScope => true
2022-10-11 11:59:51 +00:00
"run" ,
2023-05-16 14:54:41 +00:00
"loadbase64 " + base64 . StdEncoding . EncodeToString ( checkWitnessScript ) + " " + cmdargs . CosignersSeparator + " " + sideAcc . StringLE ( ) , // sideLE:DefaultScope => false
2022-10-11 11:59:51 +00:00
"run" ,
)
2020-12-01 15:27:38 +00:00
e . checkError ( t , ErrMissingParameter )
e . checkError ( t , ErrInvalidParameter )
e . checkNextLine ( t , "READY: loaded 3 instructions" )
2023-05-16 14:54:41 +00:00
e . checkError ( t , ErrInvalidParameter )
e . checkError ( t , ErrInvalidParameter )
e . checkError ( t , ErrInvalidParameter )
2022-10-11 11:59:51 +00:00
e . checkNextLine ( t , "READY: loaded \\d+ instructions" )
e . checkStack ( t , true )
e . checkNextLine ( t , "READY: loaded \\d+ instructions" )
e . checkStack ( t , false )
e . checkNextLine ( t , "READY: loaded \\d+ instructions" )
e . checkStack ( t , true )
e . checkNextLine ( t , "READY: loaded \\d+ instructions" )
e . checkStack ( t , true )
e . checkNextLine ( t , "READY: loaded \\d+ instructions" )
e . checkStack ( t , false )
2020-12-01 15:27:38 +00:00
} )
src := ` package kek
2020-12-21 11:27:07 +00:00
func Main ( op string , a , b int ) int {
2020-12-01 15:27:38 +00:00
if op == "add" {
return a + b
} else {
return a * b
}
} `
2023-05-17 16:01:24 +00:00
t . Run ( "loadgo" , func ( t * testing . T ) {
tmpDir := t . TempDir ( )
checkLoadgo := func ( t * testing . T , cName , cErrName string ) {
2023-05-19 10:22:20 +00:00
filename := prepareLoadgoSrc ( t , tmpDir , src )
2022-02-18 13:27:00 +00:00
filenameErr := filepath . Join ( tmpDir , cErrName )
e := newTestVMCLI ( t )
e . runProgWithTimeout ( t , 10 * time . Second ,
"loadgo" ,
"loadgo " + filenameErr ,
"loadgo " + filename ,
"run main add 3 5" )
e . checkError ( t , ErrMissingParameter )
e . checkNextLine ( t , "Error:" )
e . checkNextLine ( t , "READY: loaded \\d* instructions" )
e . checkStack ( t , 8 )
2023-05-17 16:01:24 +00:00
}
2020-12-01 15:27:38 +00:00
2023-05-17 16:01:24 +00:00
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" )
} )
2020-12-01 15:27:38 +00:00
2023-05-17 16:01:24 +00:00
t . Run ( "check calling flags" , func ( t * testing . T ) {
srcAllowNotify := ` package kek
2022-10-11 11:59:51 +00:00
import "github.com/nspcc-dev/neo-go/pkg/interop/runtime"
func Main ( ) int {
runtime . Log ( "Hello, world!" )
return 1
}
`
2023-05-17 16:01:24 +00:00
filename := prepareLoadgoSrc ( t , tmpDir , srcAllowNotify )
2021-05-28 09:07:03 +00:00
2023-05-17 16:01:24 +00:00
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
2022-10-11 11:59:51 +00:00
import (
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
2023-03-18 07:44:12 +00:00
"github.com/nspcc-dev/neo-go/pkg/interop/lib/address"
2022-10-11 11:59:51 +00:00
)
func Main ( ) bool {
2023-03-18 07:44:12 +00:00
var owner = address . ToHash160 ( "` + ownerAddress + `" )
2022-10-11 11:59:51 +00:00
return runtime . CheckWitness ( owner )
}
`
2023-05-17 16:01:24 +00:00
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 )
} )
2022-11-17 13:23:33 +00:00
} )
2022-10-11 11:59:51 +00:00
} )
2023-05-17 16:01:24 +00:00
2020-12-01 15:27:38 +00:00
t . Run ( "loadnef" , func ( t * testing . T ) {
2023-05-17 16:01:24 +00:00
tmpDir := t . TempDir ( )
2020-12-01 15:27:38 +00:00
2023-05-17 16:01:24 +00:00
manifestFile , nefFile := prepareLoadnefSrc ( t , tmpDir , src )
2021-11-17 11:14:22 +00:00
filenameErr := filepath . Join ( tmpDir , "vmtestcontract_err.nef" )
2023-05-17 16:01:24 +00:00
require . NoError ( t , os . WriteFile ( filenameErr , [ ] byte { 1 , 2 , 3 , 4 } , os . ModePerm ) )
2021-11-17 11:14:22 +00:00
notExists := filepath . Join ( tmpDir , "notexists.json" )
2021-11-30 17:13:52 +00:00
filenameErr = "'" + filenameErr + "'"
2020-12-01 15:27:38 +00:00
e := newTestVMCLI ( t )
e . runProg ( t ,
"loadnef" ,
2020-12-21 11:27:07 +00:00
"loadnef " + filenameErr + " " + manifestFile ,
2023-05-17 16:01:24 +00:00
"loadnef " + nefFile + " " + notExists ,
"loadnef " + nefFile + " " + nefFile ,
"loadnef " + nefFile + " " + manifestFile ,
2023-05-16 14:57:13 +00:00
"run main add 3 5" ,
2023-05-17 16:01:24 +00:00
"loadnef " + nefFile ,
2023-05-16 14:57:13 +00:00
"run main add 3 5" ,
2023-05-17 16:01:24 +00:00
"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 ( ) ,
2023-05-16 14:57:13 +00:00
"run main add 3 5" ,
2023-05-17 16:01:24 +00:00
"loadnef " + nefFile + " " + manifestFile + " " + cmdargs . CosignersSeparator + " " + util . Uint160 { 1 , 2 , 3 } . StringLE ( ) ,
2023-05-16 14:57:13 +00:00
"run main add 3 5" ,
)
2020-12-01 15:27:38 +00:00
e . checkError ( t , ErrMissingParameter )
e . checkNextLine ( t , "Error:" )
2020-12-21 11:27:07 +00:00
e . checkNextLine ( t , "Error:" )
e . checkNextLine ( t , "Error:" )
2020-12-01 15:27:38 +00:00
e . checkNextLine ( t , "READY: loaded \\d* instructions" )
e . checkStack ( t , 8 )
2023-05-16 14:57:13 +00:00
e . checkNextLine ( t , "READY: loaded \\d* instructions" )
e . checkStack ( t , 8 )
e . checkNextLine ( t , "Error:" ) // manifest missing, missing signer after --
e . checkNextLine ( t , "Error:" ) // manifest present, missing signer after --
e . checkNextLine ( t , "Error:" ) // manifest present, invalid separator
e . checkNextLine ( t , "READY: loaded \\d* instructions" ) // manifest missing, signer present, OK
e . checkStack ( t , 8 )
e . checkNextLine ( t , "READY: loaded \\d* instructions" ) // manifest present, signer present, OK
e . checkStack ( t , 8 )
2020-12-01 15:27:38 +00:00
} )
}
2023-05-17 16:01:24 +00:00
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.
} )
}
2020-12-01 15:27:38 +00:00
func TestRunWithDifferentArguments ( t * testing . T ) {
src := ` package kek
2020-12-21 11:27:07 +00:00
var a = 1
func init ( ) {
a += 1
}
func InitHasRun ( ) bool {
return a == 2
}
func Negate ( arg bool ) bool {
return ! arg
}
func GetInt ( arg int ) int {
return arg
}
func GetString ( arg string ) string {
return arg
2022-10-10 11:00:26 +00:00
}
2023-04-03 10:34:24 +00:00
func GetArr ( arg [ ] any ) [ ] any {
2022-10-10 11:00:26 +00:00
return arg
2020-12-01 15:27:38 +00:00
} `
2021-08-25 19:17:37 +00:00
tmpDir := t . TempDir ( )
2021-11-17 11:14:22 +00:00
filename := filepath . Join ( tmpDir , "run_vmtestcontract.go" )
2022-02-22 16:27:32 +00:00
require . NoError ( t , os . WriteFile ( filename , [ ] byte ( src ) , os . ModePerm ) )
2020-12-01 15:27:38 +00:00
2021-11-30 17:13:52 +00:00
filename = "'" + filename + "'"
2020-12-01 15:27:38 +00:00
e := newTestVMCLI ( t )
2022-02-09 13:36:27 +00:00
e . runProgWithTimeout ( t , 30 * time . Second ,
2020-12-21 11:27:07 +00:00
"loadgo " + filename , "run notexists" ,
"loadgo " + filename , "run negate false" ,
"loadgo " + filename , "run negate true" ,
"loadgo " + filename , "run negate bool:invalid" ,
"loadgo " + filename , "run getInt 123" ,
"loadgo " + filename , "run getInt int:invalid" ,
"loadgo " + filename , "run getString validstring" ,
"loadgo " + filename , "run initHasRun" ,
"loadhex " + hex . EncodeToString ( [ ] byte { byte ( opcode . ADD ) } ) ,
"run _ 1 2" ,
"loadbase64 " + base64 . StdEncoding . EncodeToString ( [ ] byte { byte ( opcode . MUL ) } ) ,
"run _ 21 2" ,
2022-10-10 11:00:26 +00:00
"loadgo " + filename , "run getArr [ 1 2 3 ]" ,
2020-12-01 15:27:38 +00:00
)
2020-12-21 11:27:07 +00:00
e . checkNextLine ( t , "READY: loaded \\d.* instructions" )
e . checkNextLine ( t , "Error:" )
2020-12-01 15:27:38 +00:00
e . checkNextLine ( t , "READY: loaded \\d.* instructions" )
e . checkStack ( t , true )
e . checkNextLine ( t , "READY: loaded \\d.* instructions" )
e . checkStack ( t , false )
e . checkNextLine ( t , "READY: loaded \\d.* instructions" )
e . checkError ( t , ErrInvalidParameter )
e . checkNextLine ( t , "READY: loaded \\d.* instructions" )
e . checkStack ( t , 123 )
e . checkNextLine ( t , "READY: loaded \\d.* instructions" )
e . checkError ( t , ErrInvalidParameter )
e . checkNextLine ( t , "READY: loaded \\d.* instructions" )
e . checkStack ( t , "validstring" )
2020-12-21 11:27:07 +00:00
e . checkNextLine ( t , "READY: loaded \\d.* instructions" )
e . checkStack ( t , true )
e . checkNextLine ( t , "READY: loaded \\d.* instructions" )
e . checkStack ( t , 3 )
e . checkNextLine ( t , "READY: loaded \\d.* instructions" )
e . checkStack ( t , 42 )
2022-10-10 11:00:26 +00:00
e . checkNextLine ( t , "READY: loaded \\d.* instructions" )
e . checkStack ( t , [ ] stackitem . Item {
stackitem . NewBigInteger ( big . NewInt ( 1 ) ) ,
stackitem . NewBigInteger ( big . NewInt ( 2 ) ) ,
stackitem . NewBigInteger ( big . NewInt ( 3 ) ) ,
} )
2020-12-01 15:27:38 +00:00
}
func TestPrintOps ( t * testing . T ) {
w := io . NewBufBinWriter ( )
2021-03-04 12:46:56 +00:00
emit . String ( w . BinWriter , "log" )
emit . Syscall ( w . BinWriter , interopnames . SystemRuntimeLog )
2020-12-01 15:27:38 +00:00
emit . Instruction ( w . BinWriter , opcode . PUSHDATA1 , [ ] byte { 3 , 1 , 2 , 3 } )
script := w . Bytes ( )
e := newTestVMCLI ( t )
e . runProg ( t ,
"ops" ,
"loadhex " + hex . EncodeToString ( script ) ,
"ops" )
e . checkNextLine ( t , ".*no program loaded" )
e . checkNextLine ( t , fmt . Sprintf ( "READY: loaded %d instructions" , len ( script ) ) )
e . checkNextLine ( t , "INDEX.*OPCODE.*PARAMETER" )
2021-03-04 12:46:56 +00:00
e . checkNextLine ( t , "0.*PUSHDATA1.*6c6f67" )
e . checkNextLine ( t , "5.*SYSCALL.*System\\.Runtime\\.Log" )
e . checkNextLine ( t , "10.*PUSHDATA1.*010203" )
2020-12-01 15:27:38 +00:00
}
func TestLoadAbort ( t * testing . T ) {
e := newTestVMCLI ( t )
e . runProg ( t ,
"loadhex " + hex . EncodeToString ( [ ] byte { byte ( opcode . PUSH1 ) , byte ( opcode . ABORT ) } ) ,
"run" ,
)
2021-05-12 15:29:39 +00:00
e . checkNextLine ( t , "READY: loaded 2 instructions" )
2020-12-01 15:27:38 +00:00
e . checkNextLine ( t , "Error:.*at instruction 1.*ABORT" )
}
func TestBreakpoint ( t * testing . T ) {
w := io . NewBufBinWriter ( )
emit . Opcodes ( w . BinWriter , opcode . PUSH1 , opcode . PUSH2 , opcode . ADD , opcode . PUSH6 , opcode . ADD )
e := newTestVMCLI ( t )
e . runProg ( t ,
"break 3" ,
"cont" ,
"ip" ,
"loadhex " + hex . EncodeToString ( w . Bytes ( ) ) ,
"break" ,
"break second" ,
"break 2" ,
"break 4" ,
"cont" , "estack" ,
"run" , "estack" ,
"cont" ,
)
e . checkNextLine ( t , "no program loaded" )
e . checkNextLine ( t , "no program loaded" )
e . checkNextLine ( t , "no program loaded" )
2021-05-12 15:29:39 +00:00
e . checkNextLine ( t , "READY: loaded 5 instructions" )
2020-12-01 15:27:38 +00:00
e . checkError ( t , ErrMissingParameter )
e . checkError ( t , ErrInvalidParameter )
e . checkNextLine ( t , "breakpoint added at instruction 2" )
e . checkNextLine ( t , "breakpoint added at instruction 4" )
e . checkNextLine ( t , "at breakpoint 2.*ADD" )
e . checkStack ( t , 1 , 2 )
e . checkNextLine ( t , "at breakpoint 4.*ADD" )
e . checkStack ( t , 3 , 6 )
e . checkStack ( t , 9 )
}
2021-09-08 14:27:11 +00:00
func TestDumpSSlot ( t * testing . T ) {
w := io . NewBufBinWriter ( )
emit . Opcodes ( w . BinWriter , opcode . INITSSLOT , 2 , // init static slot with size=2
opcode . PUSH5 , opcode . STSFLD , 1 , // put `int(5)` to sslot[1]; sslot[0] is nil
opcode . LDSFLD1 ) // put sslot[1] to the top of estack
e := newTestVMCLI ( t )
e . runProg ( t ,
"loadhex " + hex . EncodeToString ( w . Bytes ( ) ) ,
"break 5" ,
"step" , "sslot" ,
"cont" , "estack" ,
)
e . checkNextLine ( t , "READY: loaded 6 instructions" )
e . checkNextLine ( t , "breakpoint added at instruction 5" )
e . checkNextLine ( t , "at breakpoint 5.*LDSFLD1" )
e . checkSlot ( t , nil , 5 )
e . checkStack ( t , 5 )
}
func TestDumpLSlot_DumpASlot ( t * testing . T ) {
w := io . NewBufBinWriter ( )
emit . Opcodes ( w . BinWriter , opcode . PUSH4 , opcode . PUSH5 , opcode . PUSH6 , // items for args slot
opcode . INITSLOT , 2 , 3 , // init local slot with size=2 and args slot with size 3
opcode . PUSH7 , opcode . STLOC1 , // put `int(7)` to lslot[1]; lslot[0] is nil
opcode . LDLOC , 1 ) // put lslot[1] to the top of estack
e := newTestVMCLI ( t )
e . runProg ( t ,
"loadhex " + hex . EncodeToString ( w . Bytes ( ) ) ,
"break 6" ,
"break 8" ,
"cont" , "aslot" ,
"cont" , "lslot" ,
"cont" , "estack" ,
)
e . checkNextLine ( t , "READY: loaded 10 instructions" )
e . checkNextLine ( t , "breakpoint added at instruction 6" )
e . checkNextLine ( t , "breakpoint added at instruction 8" )
e . checkNextLine ( t , "at breakpoint 6.*PUSH7" )
e . checkSlot ( t , 6 , 5 , 4 ) // args slot
e . checkNextLine ( t , "at breakpoint 8.*LDLOC" )
e . checkSlot ( t , nil , 7 ) // local slot
e . checkStack ( t , 7 )
}
2020-12-01 15:27:38 +00:00
func TestStep ( t * testing . T ) {
script := hex . EncodeToString ( [ ] byte {
byte ( opcode . PUSH0 ) , byte ( opcode . PUSH1 ) , byte ( opcode . PUSH2 ) , byte ( opcode . PUSH3 ) ,
} )
e := newTestVMCLI ( t )
e . runProg ( t ,
"step" ,
"loadhex " + script ,
"step invalid" ,
"step" ,
"step 2" ,
"ip" , "step" , "ip" )
e . checkNextLine ( t , "no program loaded" )
e . checkNextLine ( t , "READY: loaded \\d+ instructions" )
e . checkError ( t , ErrInvalidParameter )
e . checkNextLine ( t , "at breakpoint 1.*PUSH1" )
e . checkNextLine ( t , "at breakpoint 3.*PUSH3" )
e . checkNextLine ( t , "instruction pointer at 3.*PUSH3" )
e . checkNextLine ( t , "execution has finished" )
e . checkNextLine ( t , "execution has finished" )
}
func TestErrorOnStepInto ( t * testing . T ) {
script := hex . EncodeToString ( [ ] byte { byte ( opcode . ADD ) } )
e := newTestVMCLI ( t )
e . runProg ( t ,
"stepover" ,
"loadhex " + script ,
"stepover" )
e . checkNextLine ( t , "Error:.*no program loaded" )
e . checkNextLine ( t , "READY: loaded 1 instructions" )
e . checkNextLine ( t , "Error:" )
}
func TestStepIntoOverOut ( t * testing . T ) {
script := hex . EncodeToString ( [ ] byte {
byte ( opcode . PUSH2 ) , byte ( opcode . CALL ) , 4 , byte ( opcode . NOP ) , byte ( opcode . RET ) ,
byte ( opcode . PUSH3 ) , byte ( opcode . ADD ) , byte ( opcode . RET ) ,
} )
e := newTestVMCLI ( t )
e . runProg ( t ,
"loadhex " + script ,
"step" , "stepover" , "run" ,
"loadhex " + script ,
"step" , "stepinto" , "step" , "estack" , "run" ,
"loadhex " + script ,
"step" , "stepinto" , "stepout" , "run" )
2021-05-12 15:29:39 +00:00
e . checkNextLine ( t , "READY: loaded 8 instructions" )
2020-12-01 15:27:38 +00:00
e . checkNextLine ( t , "at breakpoint 1.*CALL" )
e . checkNextLine ( t , "instruction pointer at.*NOP" )
e . checkStack ( t , 5 )
2021-05-12 15:29:39 +00:00
e . checkNextLine ( t , "READY: loaded 8 instructions" )
2020-12-01 15:27:38 +00:00
e . checkNextLine ( t , "at breakpoint.*CALL" )
e . checkNextLine ( t , "instruction pointer at.*PUSH3" )
e . checkNextLine ( t , "at breakpoint.*ADD" )
e . checkStack ( t , 2 , 3 )
e . checkStack ( t , 5 )
2021-05-12 15:29:39 +00:00
e . checkNextLine ( t , "READY: loaded 8 instructions" )
2020-12-01 15:27:38 +00:00
e . checkNextLine ( t , "at breakpoint 1.*CALL" )
e . checkNextLine ( t , "instruction pointer at.*PUSH3" )
e . checkNextLine ( t , "instruction pointer at.*NOP" )
e . checkStack ( t , 5 )
}
// `Parse` output is written via `tabwriter` so if any problems
// are encountered in this test, try to replace ' ' with '\\s+'.
func TestParse ( t * testing . T ) {
t . Run ( "Integer" , func ( t * testing . T ) {
e := newTestVMCLI ( t )
e . runProg ( t ,
"parse" ,
"parse 6667" )
e . checkError ( t , ErrMissingParameter )
e . checkNextLine ( t , "Integer to Hex.*0b1a" )
e . checkNextLine ( t , "Integer to Base64.*Cxo=" )
e . checkNextLine ( t , "Hex to String.*\"fg\"" )
e . checkNextLine ( t , "Hex to Integer.*26470" )
e . checkNextLine ( t , "Swap Endianness.*6766" )
e . checkNextLine ( t , "Base64 to String.*\"뮻\"" )
e . checkNextLine ( t , "Base64 to BigInteger.*-4477205" )
e . checkNextLine ( t , "String to Hex.*36363637" )
e . checkNextLine ( t , "String to Base64.*NjY2Nw==" )
} )
t . Run ( "Address" , func ( t * testing . T ) {
e := newTestVMCLI ( t )
e . runProg ( t , "parse " + "NbTiM6h8r99kpRtb428XcsUk1TzKed2gTc" )
e . checkNextLine ( t , "Address to BE ScriptHash.*aa8acf859d4fe402b34e673f2156821796a488eb" )
e . checkNextLine ( t , "Address to LE ScriptHash.*eb88a496178256213f674eb302e44f9d85cf8aaa" )
e . checkNextLine ( t , "Address to Base64.*(BE).*qorPhZ1P5AKzTmc/IVaCF5akiOs=" )
e . checkNextLine ( t , "Address to Base64.*(LE).*64iklheCViE/Z06zAuRPnYXPiqo=" )
e . checkNextLine ( t , "String to Hex.*4e6254694d3668387239396b70527462343238586373556b31547a4b656432675463" )
e . checkNextLine ( t , "String to Base64.*TmJUaU02aDhyOTlrcFJ0YjQyOFhjc1VrMVR6S2VkMmdUYw==" )
} )
t . Run ( "Uint160" , func ( t * testing . T ) {
u := util . Uint160 { 66 , 67 , 68 }
e := newTestVMCLI ( t )
e . runProg ( t , "parse " + u . StringLE ( ) )
e . checkNextLine ( t , "Integer to Hex.*b6c706" )
e . checkNextLine ( t , "Integer to Base64.*tscG" )
e . checkNextLine ( t , "BE ScriptHash to Address.*NKuyBkoGdZZSLyPbJEetheRhQKhATAzN2A" )
e . checkNextLine ( t , "LE ScriptHash to Address.*NRxLN7apYwKJihzMt4eSSnU9BJ77dp2TNj" )
e . checkNextLine ( t , "Hex to String" )
e . checkNextLine ( t , "Hex to Integer.*378293464438118320046642359484100328446970822656" )
e . checkNextLine ( t , "Swap Endianness.*4243440000000000000000000000000000000000" )
e . checkNextLine ( t , "Base64 to String.*" )
e . checkNextLine ( t , "Base64 to BigInteger.*376115185060690908522683414825349447309891933036899526770189324554358227" )
e . checkNextLine ( t , "String to Hex.*30303030303030303030303030303030303030303030303030303030303030303030343434333432" )
e . checkNextLine ( t , "String to Base64.*MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDQ0NDM0Mg==" )
} )
2021-08-12 13:39:46 +00:00
t . Run ( "public key" , func ( t * testing . T ) {
pub := "02b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc2"
e := newTestVMCLI ( t )
e . runProg ( t , "parse " + pub )
e . checkNextLine ( t , "Public key to BE ScriptHash.*ee9ea22c27e34bd0148fc4108e08f74e8f5048b2" )
e . checkNextLine ( t , "Public key to LE ScriptHash.*b248508f4ef7088e10c48f14d04be3272ca29eee" )
e . checkNextLine ( t , "Public key to Address.*Nhfg3TbpwogLvDGVvAvqyThbsHgoSUKwtn" )
e . checkNextLine ( t , "Hex to String" )
e . checkNextLine ( t , "Hex to Integer.*-7115107707948693452214836319400158580475150561081357074343221218306172781415678" )
e . checkNextLine ( t , "Swap Endianness.*c28d7fbfc4bb74d7a76f0496b87d6b203f754c5fed8ac517e3df7b01f42b62b302" )
e . checkNextLine ( t , "String to Hex.*303262333632326266343031376264666533313763353861656435663463373533663230366237646238393630343666613764373734626263346266376638646332" )
e . checkNextLine ( t , "String to Base64.*MDJiMzYyMmJmNDAxN2JkZmUzMTdjNThhZWQ1ZjRjNzUzZjIwNmI3ZGI4OTYwNDZmYTdkNzc0YmJjNGJmN2Y4ZGMy" )
} )
2021-10-29 11:55:08 +00:00
t . Run ( "base64" , func ( t * testing . T ) {
e := newTestVMCLI ( t )
u := random . Uint160 ( )
e . runProg ( t , "parse " + base64 . StdEncoding . EncodeToString ( u . BytesBE ( ) ) )
e . checkNextLine ( t , "Base64 to String\\s+" )
e . checkNextLine ( t , "Base64 to BigInteger\\s+" )
e . checkNextLine ( t , "Base64 to BE ScriptHash\\s+" + u . StringBE ( ) )
e . checkNextLine ( t , "Base64 to LE ScriptHash\\s+" + u . StringLE ( ) )
e . checkNextLine ( t , "Base64 to Address \\(BE\\)\\s+" + address . Uint160ToString ( u ) )
e . checkNextLine ( t , "Base64 to Address \\(LE\\)\\s+" + address . Uint160ToString ( u . Reverse ( ) ) )
e . checkNextLine ( t , "String to Hex\\s+" )
e . checkNextLine ( t , "String to Base64\\s+" )
} )
2020-12-01 15:27:38 +00:00
}
func TestPrintLogo ( t * testing . T ) {
e := newTestVMCLIWithLogo ( t , true )
e . runProg ( t )
require . True ( t , strings . HasPrefix ( e . out . String ( ) , logo ) )
require . False ( t , e . exit . Load ( ) )
}
func TestExit ( t * testing . T ) {
e := newTestVMCLI ( t )
e . runProg ( t , "exit" )
require . True ( t , e . exit . Load ( ) )
}
2022-02-16 15:43:12 +00:00
func TestReset ( t * testing . T ) {
script := [ ] byte { byte ( opcode . PUSH1 ) }
e := newTestVMCLI ( t )
e . runProg ( t ,
"loadhex " + hex . EncodeToString ( script ) ,
"ops" ,
"reset" ,
"ops" )
e . checkNextLine ( t , "READY: loaded 1 instructions" )
e . checkNextLine ( t , "INDEX.*OPCODE.*PARAMETER" )
e . checkNextLine ( t , "0.*PUSH1.*" )
e . checkNextLine ( t , "" )
e . checkError ( t , fmt . Errorf ( "VM is not ready: no program loaded" ) )
}
2022-10-03 12:05:48 +00:00
func TestRunWithState ( t * testing . T ) {
e := newTestVMClIWithState ( t )
// Ensure that state is properly loaded and on-chain contract can be called.
script := io . NewBufBinWriter ( )
h , err := e . cli . chain . GetContractScriptHash ( 1 ) // examples/storage/storage.go
require . NoError ( t , err )
emit . AppCall ( script . BinWriter , h , "put" , callflag . All , 3 , 3 )
e . runProg ( t ,
"loadhex " + hex . EncodeToString ( script . Bytes ( ) ) ,
2022-10-10 04:37:08 +00:00
"run" ,
"exit" )
2022-10-03 12:05:48 +00:00
e . checkNextLine ( t , "READY: loaded 37 instructions" )
e . checkStack ( t , 3 )
}
2022-10-04 10:05:51 +00:00
2022-10-04 11:53:31 +00:00
func TestRunWithHistoricState ( t * testing . T ) {
e := newTestVMClIWithState ( t )
script := io . NewBufBinWriter ( )
h , err := e . cli . chain . GetContractScriptHash ( 1 ) // examples/storage/storage.go
require . NoError ( t , err )
emit . AppCall ( script . BinWriter , h , "get" , callflag . All , 1 )
b := script . Bytes ( )
e . runProg ( t ,
"loadhex " + hex . EncodeToString ( b ) , // normal invocation
"run" ,
"loadhex --historic 3 " + hex . EncodeToString ( b ) , // historic invocation, old value should be retrieved
"run" ,
"loadhex --historic 0 " + hex . EncodeToString ( b ) , // historic invocation, contract is not deployed yet
"run" ,
2022-10-10 04:37:08 +00:00
"exit" ,
2022-10-04 11:53:31 +00:00
)
e . checkNextLine ( t , "READY: loaded 36 instructions" )
e . checkStack ( t , [ ] byte { 2 } )
e . checkNextLine ( t , "READY: loaded 36 instructions" )
e . checkStack ( t , [ ] byte { 1 } )
e . checkNextLine ( t , "READY: loaded 36 instructions" )
2023-08-09 12:11:43 +00:00
e . checkNextLineExact ( t , "Error: at instruction 31 (SYSCALL): System.Contract.Call failed: called contract 0f825b050eb8ce9eaa82993e90615025ab798016 not found: key not found\n" )
2022-10-04 11:53:31 +00:00
}
2022-10-04 10:05:51 +00:00
func TestEvents ( t * testing . T ) {
e := newTestVMClIWithState ( t )
script := io . NewBufBinWriter ( )
h , err := e . cli . chain . GetContractScriptHash ( 2 ) // examples/runtime/runtime.go
require . NoError ( t , err )
2023-04-03 10:34:24 +00:00
emit . AppCall ( script . BinWriter , h , "notify" , callflag . All , [ ] any { true , 5 } )
2022-10-04 10:05:51 +00:00
e . runProg ( t ,
"loadhex " + hex . EncodeToString ( script . Bytes ( ) ) ,
"run" ,
2022-10-10 04:37:08 +00:00
"events" ,
"exit" )
2022-10-04 10:05:51 +00:00
expectedEvent := state . NotificationEvent {
ScriptHash : h ,
Name : "Event" ,
Item : stackitem . NewArray ( [ ] stackitem . Item {
stackitem . NewArray ( [ ] stackitem . Item {
stackitem . Make ( true ) ,
stackitem . Make ( 5 ) ,
} ) ,
} ) ,
}
2022-10-28 09:12:32 +00:00
e . checkNextLine ( t , "READY: loaded 42 instructions" )
2022-10-04 10:05:51 +00:00
e . checkStack ( t , stackitem . Null { } )
e . checkEvents ( t , true , expectedEvent ) // automatically printed after `run` command
e . checkEvents ( t , false , expectedEvent ) // printed after `events` command
}
2022-10-04 11:19:42 +00:00
func TestEnv ( t * testing . T ) {
t . Run ( "default setup" , func ( t * testing . T ) {
e := newTestVMCLI ( t )
e . runProg ( t , "env" )
e . checkNextLine ( t , "Chain height: 0" )
2022-10-04 11:53:31 +00:00
e . checkNextLineExact ( t , "VM height (may differ from chain height in case of historic call): 0\n" )
2022-10-04 11:19:42 +00:00
e . checkNextLine ( t , "Network magic: 42" )
e . checkNextLine ( t , "DB type: inmemory" )
} )
t . Run ( "setup with state" , func ( t * testing . T ) {
e := newTestVMClIWithState ( t )
2022-10-10 04:37:08 +00:00
e . runProg ( t , "env" , "exit" )
2022-10-04 11:19:42 +00:00
e . checkNextLine ( t , "Chain height: 5" )
2022-10-04 11:53:31 +00:00
e . checkNextLineExact ( t , "VM height (may differ from chain height in case of historic call): 5\n" )
e . checkNextLine ( t , "Network magic: 42" )
e . checkNextLine ( t , "DB type: leveldb" )
} )
t . Run ( "setup with historic state" , func ( t * testing . T ) {
e := newTestVMClIWithState ( t )
e . runProg ( t , "loadbase64 --historic 3 " + base64 . StdEncoding . EncodeToString ( [ ] byte { byte ( opcode . PUSH1 ) } ) ,
2022-10-10 04:37:08 +00:00
"env" , "exit" )
2022-10-04 11:53:31 +00:00
e . checkNextLine ( t , "READY: loaded 1 instructions" )
e . checkNextLine ( t , "Chain height: 5" )
e . checkNextLineExact ( t , "VM height (may differ from chain height in case of historic call): 3\n" )
2022-10-04 11:19:42 +00:00
e . checkNextLine ( t , "Network magic: 42" )
e . checkNextLine ( t , "DB type: leveldb" )
} )
t . Run ( "verbose" , func ( t * testing . T ) {
e := newTestVMClIWithState ( t )
2022-10-10 04:37:08 +00:00
e . runProg ( t , "env -v" , "exit" )
2022-10-04 11:19:42 +00:00
e . checkNextLine ( t , "Chain height: 5" )
2022-10-04 11:53:31 +00:00
e . checkNextLineExact ( t , "VM height (may differ from chain height in case of historic call): 5\n" )
2022-10-04 11:19:42 +00:00
e . checkNextLine ( t , "Network magic: 42" )
e . checkNextLine ( t , "DB type: leveldb" )
e . checkNextLine ( t , "Node config:" ) // Do not check exact node config.
} )
}
2022-10-04 12:38:42 +00:00
func TestDumpStorage ( t * testing . T ) {
e := newTestVMClIWithState ( t )
h , err := e . cli . chain . GetContractScriptHash ( 1 ) // examples/storage/storage.go
require . NoError ( t , err )
expected := [ ] storage . KeyValue {
{ Key : [ ] byte { 1 } , Value : [ ] byte { 2 } } ,
{ Key : [ ] byte { 2 } , Value : [ ] byte { 2 } } ,
}
e . runProg ( t ,
"storage " + h . StringLE ( ) ,
"storage 0x" + h . StringLE ( ) ,
"storage " + address . Uint160ToString ( h ) ,
"storage 1" ,
"storage 1 " + hex . EncodeToString ( expected [ 0 ] . Key ) ,
"storage 1 --backwards" ,
2022-10-10 04:37:08 +00:00
"exit" ,
2022-10-04 12:38:42 +00:00
)
e . checkStorage ( t , expected ... )
e . checkStorage ( t , expected ... )
e . checkStorage ( t , expected ... )
e . checkStorage ( t , expected ... )
e . checkStorage ( t , storage . KeyValue { Key : nil , Value : [ ] byte { 2 } } ) // empty key because search prefix is trimmed
e . checkStorage ( t , expected [ 1 ] , expected [ 0 ] )
}
2022-10-05 10:56:26 +00:00
func TestDumpStorageDiff ( t * testing . T ) {
e := newTestVMClIWithState ( t )
script := io . NewBufBinWriter ( )
h , err := e . cli . chain . GetContractScriptHash ( 1 ) // examples/storage/storage.go
require . NoError ( t , err )
emit . AppCall ( script . BinWriter , h , "put" , callflag . All , 3 , 3 )
expected := [ ] storage . KeyValue {
{ Key : [ ] byte { 1 } , Value : [ ] byte { 2 } } ,
{ Key : [ ] byte { 2 } , Value : [ ] byte { 2 } } ,
}
diff := storage . KeyValue { Key : [ ] byte { 3 } , Value : [ ] byte { 3 } }
e . runProg ( t ,
"storage 1" ,
"storage 1 --diff" ,
"loadhex " + hex . EncodeToString ( script . Bytes ( ) ) ,
"run" ,
"storage 1" ,
"storage 1 --diff" ,
2022-10-10 04:37:08 +00:00
"exit" ,
2022-10-05 10:56:26 +00:00
)
e . checkStorage ( t , expected ... )
// no script is executed => no diff
e . checkNextLine ( t , "READY: loaded 37 instructions" )
e . checkStack ( t , 3 )
e . checkStorage ( t , append ( expected , diff ) ... )
e . checkStorage ( t , diff )
}
2022-10-05 12:06:20 +00:00
func TestDumpChanges ( t * testing . T ) {
e := newTestVMClIWithState ( t )
script := io . NewBufBinWriter ( )
h , err := e . cli . chain . GetContractScriptHash ( 1 ) // examples/storage/storage.go
require . NoError ( t , err )
emit . AppCall ( script . BinWriter , h , "put" , callflag . All , 3 , 4 ) // add
emit . AppCall ( script . BinWriter , h , "delete" , callflag . All , 1 ) // remove
emit . AppCall ( script . BinWriter , h , "put" , callflag . All , 2 , 5 ) // update
expected := [ ] storageChange {
{
ContractID : 1 ,
Operation : dboper . Operation {
State : "Deleted" ,
Key : [ ] byte { 1 } ,
} ,
} ,
{
ContractID : 1 ,
Operation : dboper . Operation {
State : "Changed" ,
Key : [ ] byte { 2 } ,
Value : [ ] byte { 5 } ,
} ,
} ,
{
ContractID : 1 ,
Operation : dboper . Operation {
State : "Added" ,
Key : [ ] byte { 3 } ,
Value : [ ] byte { 4 } ,
} ,
} ,
}
e . runProg ( t ,
"changes" ,
"changes 1" ,
"loadhex " + hex . EncodeToString ( script . Bytes ( ) ) ,
"run" ,
"changes 1 " + hex . EncodeToString ( [ ] byte { 1 } ) ,
"changes 1 " + hex . EncodeToString ( [ ] byte { 2 } ) ,
"changes 1 " + hex . EncodeToString ( [ ] byte { 3 } ) ,
2022-10-10 04:37:08 +00:00
"exit" ,
2022-10-05 12:06:20 +00:00
)
// no script is executed => no diff
e . checkNextLine ( t , "READY: loaded 113 instructions" )
e . checkStack ( t , 3 , true , 2 )
e . checkChange ( t , expected [ 0 ] )
e . checkChange ( t , expected [ 1 ] )
e . checkChange ( t , expected [ 2 ] )
}
2022-10-10 10:59:37 +00:00
func TestLoadtx ( t * testing . T ) {
e := newTestVMClIWithState ( t )
b , err := e . cli . chain . GetBlock ( e . cli . chain . GetHeaderHash ( 2 ) ) // Block #2 contains transaction that puts (1,1) pair to storage contract.
require . NoError ( t , err )
require . Equal ( t , 1 , len ( b . Transactions ) )
tx := b . Transactions [ 0 ]
tmp := filepath . Join ( t . TempDir ( ) , "tx.json" )
require . NoError ( t , paramcontext . InitAndSave ( netmode . UnitTestNet , tx , nil , tmp ) )
e . runProg ( t ,
"loadtx " + tx . Hash ( ) . StringLE ( ) , // hash LE
"run" ,
2022-11-19 20:08:06 +00:00
"loadtx --gas 10000 " + tx . Hash ( ) . StringLE ( ) , // with GAS
"run" ,
2022-10-10 10:59:37 +00:00
"loadtx 0x" + tx . Hash ( ) . StringLE ( ) , // hash LE with 0x prefix
"run" ,
"loadtx '" + tmp + "'" , // Tx from parameter context file.
"run" ,
"loadtx" , // missing argument
"exit" ,
)
e . checkNextLine ( t , "READY: loaded \\d+ instructions" )
e . checkStack ( t , 1 )
e . checkNextLine ( t , "READY: loaded \\d+ instructions" )
2022-11-19 20:08:06 +00:00
e . checkError ( t , errors . New ( "at instruction 3 (PACK): gas limit is exceeded" ) )
e . checkNextLine ( t , "READY: loaded \\d+ instructions" )
2022-10-10 10:59:37 +00:00
e . checkStack ( t , 1 )
e . checkNextLine ( t , "READY: loaded \\d+ instructions" )
e . checkStack ( t , 1 )
e . checkError ( t , errors . New ( "missing argument: <file-or-hash>" ) )
}
2022-10-11 09:22:07 +00:00
func TestLoaddeployed ( t * testing . T ) {
e := newTestVMClIWithState ( t )
h , err := e . cli . chain . GetContractScriptHash ( 1 ) // examples/storage/storage.go
require . NoError ( t , err )
2022-10-11 11:59:51 +00:00
ownerAddress := "NbrUYaZgyhSkNoRo9ugRyEMdUZxrhkNaWB" // owner of examples/runtime/runtime.go (taken from deployed contract with ID=2)
ownerAcc , err := address . StringToUint160 ( ownerAddress )
require . NoError ( t , err )
sideAcc := util . Uint160 { 1 , 2 , 3 }
2022-10-11 09:22:07 +00:00
e . runProg ( t ,
"loaddeployed " + h . StringLE ( ) , // hash LE
"run get 1" ,
2022-11-19 20:08:06 +00:00
"loaddeployed --gas 420000 " + h . StringLE ( ) , // gas-limited
"run get 1" ,
2023-05-16 14:54:41 +00:00
"loaddeployed " + "0x" + h . StringLE ( ) , // hash LE with 0x prefix
2022-10-11 09:22:07 +00:00
"run get 1" ,
"loaddeployed 1" , // contract ID
"run get 1" ,
"loaddeployed --historic 2 1" , // historic state, check that hash is properly set
"run get 1" ,
2022-10-11 11:59:51 +00:00
// Check signers parsing:
2023-05-16 14:54:41 +00:00
"loaddeployed 2 " + cmdargs . CosignersSeparator ,
"loaddeployed 2 " + "not-a-separator" ,
"loaddeployed 2 " + cmdargs . CosignersSeparator + " not-a-signer" ,
"loaddeployed 2 " + cmdargs . CosignersSeparator + " " + ownerAddress , // check witness (owner:DefautScope => true)
2022-10-11 11:59:51 +00:00
"run checkWitness" ,
2023-05-16 14:54:41 +00:00
"loaddeployed 2 " + cmdargs . CosignersSeparator + " " + ownerAddress + ":None" , // check witness (owner:None => false)
2022-10-11 11:59:51 +00:00
"run checkWitness" ,
2023-05-16 14:54:41 +00:00
"loaddeployed 2 " + cmdargs . CosignersSeparator + " " + ownerAddress + ":CalledByEntry" , // check witness (owner:CalledByEntry => true)
2022-10-11 11:59:51 +00:00
"run checkWitness" ,
2023-05-16 14:54:41 +00:00
"loaddeployed 2 " + cmdargs . CosignersSeparator + " " + ownerAcc . StringLE ( ) + ":CalledByEntry" , // check witness (ownerLE:CalledByEntry => true)
2022-10-11 11:59:51 +00:00
"run checkWitness" ,
2023-05-16 14:54:41 +00:00
"loaddeployed 2 " + cmdargs . CosignersSeparator + " " + "0x" + ownerAcc . StringLE ( ) + ":CalledByEntry" , // check witness (owner0xLE:CalledByEntry => true)
2022-10-11 11:59:51 +00:00
"run checkWitness" ,
2023-05-16 14:54:41 +00:00
"loaddeployed 2 " + cmdargs . CosignersSeparator + " " + sideAcc . StringLE ( ) + ":Global" , // check witness (sideLE:Global => false)
2022-10-11 11:59:51 +00:00
"run checkWitness" ,
2022-10-11 09:22:07 +00:00
"loaddeployed" , // missing argument
"exit" ,
)
e . checkNextLine ( t , "READY: loaded \\d+ instructions" )
e . checkStack ( t , [ ] byte { 2 } )
e . checkNextLine ( t , "READY: loaded \\d+ instructions" )
2022-11-28 07:46:53 +00:00
e . checkError ( t , errors . New ( "at instruction 63 (SYSCALL): System.Storage.Get failed: insufficient amount of gas" ) )
2022-11-19 20:08:06 +00:00
e . checkNextLine ( t , "READY: loaded \\d+ instructions" )
2022-10-11 09:22:07 +00:00
e . checkStack ( t , [ ] byte { 2 } )
e . checkNextLine ( t , "READY: loaded \\d+ instructions" )
e . checkStack ( t , [ ] byte { 2 } )
e . checkNextLine ( t , "READY: loaded \\d+ instructions" )
e . checkStack ( t , [ ] byte { 1 } )
2022-10-11 11:59:51 +00:00
// Check signers parsing:
2023-05-16 14:54:41 +00:00
e . checkError ( t , ErrInvalidParameter )
e . checkError ( t , ErrInvalidParameter )
e . checkError ( t , ErrInvalidParameter )
2022-10-11 11:59:51 +00:00
e . checkNextLine ( t , "READY: loaded \\d+ instructions" ) // check witness of owner:DefaultScope
e . checkStack ( t , true )
e . checkNextLine ( t , "READY: loaded \\d+ instructions" ) // check witness of owner:None
e . checkStack ( t , false )
e . checkNextLine ( t , "READY: loaded \\d+ instructions" ) // check witness of owner:CalledByEntry
e . checkStack ( t , true )
e . checkNextLine ( t , "READY: loaded \\d+ instructions" ) // check witness of ownerLE:CalledByEntry
e . checkStack ( t , true )
e . checkNextLine ( t , "READY: loaded \\d+ instructions" ) // check witness of owner0xLE:CalledByEntry
e . checkStack ( t , true )
e . checkNextLine ( t , "READY: loaded \\d+ instructions" ) // check witness of owner0xLE:CalledByEntry
e . checkStack ( t , false )
2022-10-11 09:22:07 +00:00
e . checkError ( t , errors . New ( "contract hash, address or ID is mandatory argument" ) )
}
2022-10-12 10:16:19 +00:00
func TestJump ( t * testing . T ) {
buf := io . NewBufBinWriter ( )
emit . Opcodes ( buf . BinWriter , opcode . PUSH1 , opcode . PUSH2 , opcode . ABORT ) // some garbage
jmpTo := buf . Len ( )
emit . Opcodes ( buf . BinWriter , opcode . PUSH4 , opcode . PUSH5 , opcode . ADD ) // useful script
e := newTestVMCLI ( t )
e . runProg ( t ,
"loadhex " + hex . EncodeToString ( buf . Bytes ( ) ) ,
"jump " + strconv . Itoa ( jmpTo ) ,
"run" ,
)
e . checkNextLine ( t , "READY: loaded 6 instructions" )
e . checkNextLine ( t , fmt . Sprintf ( "jumped to instruction %d" , jmpTo ) )
e . checkStack ( t , 9 )
}