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"
"fmt"
gio "io"
"os"
2021-11-17 11:14:22 +00:00
"path/filepath"
2020-12-01 15:27:38 +00:00
"strings"
"sync"
"testing"
"time"
2022-02-15 12:55:25 +00:00
"github.com/chzyer/readline"
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"
"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 )
customConfig := func ( c * config . ProtocolConfiguration ) {
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
}
func ( e * executor ) checkStack ( t * testing . T , items ... interface { } ) {
d := json . NewDecoder ( e . out )
var actual interface { }
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 )
var actual interface { }
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 ) ) )
}
}
2021-09-08 14:27:11 +00:00
func ( e * executor ) checkSlot ( t * testing . T , items ... interface { } ) {
d := json . NewDecoder ( e . out )
var actual interface { }
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 )
}
2020-12-01 15:27:38 +00:00
func TestLoad ( t * testing . T ) {
script := [ ] byte { byte ( opcode . PUSH3 ) , byte ( opcode . PUSH4 ) , byte ( opcode . ADD ) }
t . Run ( "loadhex" , func ( t * testing . T ) {
e := newTestVMCLI ( t )
e . runProg ( t ,
"loadhex" ,
"loadhex notahex" ,
"loadhex " + hex . EncodeToString ( script ) )
e . checkError ( t , ErrMissingParameter )
e . checkError ( t , ErrInvalidParameter )
e . checkNextLine ( t , "READY: loaded 3 instructions" )
} )
t . Run ( "loadbase64" , func ( t * testing . T ) {
e := newTestVMCLI ( t )
e . runProg ( t ,
"loadbase64" ,
"loadbase64 not_a_base64" ,
"loadbase64 " + base64 . StdEncoding . EncodeToString ( script ) )
e . checkError ( t , ErrMissingParameter )
e . checkError ( t , ErrInvalidParameter )
e . checkNextLine ( t , "READY: loaded 3 instructions" )
} )
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
}
} `
2021-08-25 19:17:37 +00:00
tmpDir := t . TempDir ( )
2020-12-01 15:27:38 +00:00
2022-02-18 13:27:00 +00:00
checkLoadgo := func ( t * testing . T , tName , cName , cErrName string ) {
t . Run ( "loadgo " + tName , func ( t * testing . T ) {
filename := filepath . Join ( tmpDir , cName )
2022-02-22 16:27:32 +00:00
require . NoError ( t , os . WriteFile ( filename , [ ] byte ( src ) , os . ModePerm ) )
2022-02-18 13:27:00 +00:00
filename = "'" + filename + "'"
filenameErr := filepath . Join ( tmpDir , cErrName )
2022-02-22 16:27:32 +00:00
require . NoError ( t , os . WriteFile ( filenameErr , [ ] byte ( src + "invalid_token" ) , os . ModePerm ) )
2022-02-18 13:27:00 +00:00
filenameErr = "'" + filenameErr + "'"
goMod := [ ] byte ( ` module test . example / vmcli
2022-09-08 18:18:51 +00:00
go 1.17 ` )
2022-02-22 16:27:32 +00:00
require . NoError ( t , os . WriteFile ( filepath . Join ( tmpDir , "go.mod" ) , goMod , os . ModePerm ) )
2022-02-18 13:27:00 +00:00
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 )
} )
}
2020-12-01 15:27:38 +00:00
2022-02-18 13:27:00 +00:00
checkLoadgo ( t , "simple" , "vmtestcontract.go" , "vmtestcontract_err.go" )
checkLoadgo ( t , "utf-8 with spaces" , "тестовый контракт.go" , "тестовый контракт с ошибкой.go" )
2020-12-01 15:27:38 +00:00
2021-05-28 09:07:03 +00:00
t . Run ( "loadgo, 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
}
`
2021-11-17 11:14:22 +00:00
filename := filepath . Join ( tmpDir , "vmtestcontract.go" )
2022-02-22 16:27:32 +00:00
require . NoError ( t , os . WriteFile ( filename , [ ] byte ( srcAllowNotify ) , os . ModePerm ) )
2021-11-30 17:13:52 +00:00
filename = "'" + filename + "'"
2021-12-02 14:44:53 +00:00
wd , err := os . Getwd ( )
require . NoError ( t , err )
goMod := [ ] byte ( ` module test . example / kek
require (
2022-01-14 10:06:20 +00:00
github . com / nspcc - dev / neo - go / pkg / interop v0 .0 .0
2021-12-02 14:44:53 +00:00
)
2022-10-05 09:30:54 +00:00
replace github . com / nspcc - dev / neo - go / pkg / interop = > ` + filepath.Join(wd, "../../pkg/interop") + `
2022-09-08 18:18:51 +00:00
go 1.17 ` )
2022-02-22 16:27:32 +00:00
require . NoError ( t , os . WriteFile ( filepath . Join ( tmpDir , "go.mod" ) , goMod , os . ModePerm ) )
2021-05-28 09:07:03 +00:00
e := newTestVMCLI ( t )
e . runProg ( t ,
"loadgo " + filename ,
"run main" )
e . checkNextLine ( t , "READY: loaded \\d* instructions" )
e . checkStack ( t , 1 )
} )
2020-12-01 15:27:38 +00:00
t . Run ( "loadnef" , func ( t * testing . T ) {
config . Version = "0.92.0-test"
2021-12-02 13:36:29 +00:00
nefFile , di , err := compiler . CompileWithOptions ( "test.go" , strings . NewReader ( src ) , nil )
2020-12-01 15:27:38 +00:00
require . NoError ( t , err )
2021-11-17 11:14:22 +00:00
filename := filepath . Join ( tmpDir , "vmtestcontract.nef" )
2020-12-01 15:27:38 +00:00
rawNef , err := nefFile . Bytes ( )
require . NoError ( t , err )
2022-02-22 16:27:32 +00:00
require . NoError ( t , os . WriteFile ( filename , rawNef , os . ModePerm ) )
2020-12-21 11:27:07 +00:00
m , err := di . ConvertToManifest ( & compiler . Options { } )
require . NoError ( t , err )
2021-11-17 11:14:22 +00:00
manifestFile := filepath . Join ( tmpDir , "vmtestcontract.manifest.json" )
2020-12-21 11:27:07 +00:00
rawManifest , err := json . Marshal ( m )
require . NoError ( t , err )
2022-02-22 16:27:32 +00:00
require . NoError ( t , os . WriteFile ( manifestFile , rawManifest , os . ModePerm ) )
2021-11-17 11:14:22 +00:00
filenameErr := filepath . Join ( tmpDir , "vmtestcontract_err.nef" )
2022-02-22 16:27:32 +00:00
require . NoError ( t , os . WriteFile ( filenameErr , append ( [ ] byte { 1 , 2 , 3 , 4 } , rawNef ... ) , os . ModePerm ) )
2021-11-17 11:14:22 +00:00
notExists := filepath . Join ( tmpDir , "notexists.json" )
2020-12-01 15:27:38 +00:00
2021-11-30 17:13:52 +00:00
manifestFile = "'" + manifestFile + "'"
filename = "'" + filename + "'"
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 ,
"loadnef " + filename + " " + notExists ,
"loadnef " + filename + " " + filename ,
"loadnef " + filename + " " + manifestFile ,
"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 )
} )
}
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
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" ,
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 )
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" )
e . checkNextLineExact ( t , "Error: at instruction 31 (SYSCALL): failed to invoke syscall 1381727586: called contract a00e3c2643a08a452d8b0bdd31849ae11a17c445 not found: key not found\n" )
}
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 )
emit . AppCall ( script . BinWriter , h , "notify" , callflag . All , [ ] interface { } { true , 5 } )
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 ) ,
} ) ,
} ) ,
}
e . checkNextLine ( t , "READY: loaded 44 instructions" )
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 ] )
}