github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/compiler/vm_test.go (about) 1 package compiler_test 2 3 import ( 4 "errors" 5 "fmt" 6 "strings" 7 "testing" 8 9 "github.com/nspcc-dev/neo-go/pkg/compiler" 10 "github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames" 11 "github.com/nspcc-dev/neo-go/pkg/core/state" 12 "github.com/nspcc-dev/neo-go/pkg/io" 13 "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" 14 "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" 15 "github.com/nspcc-dev/neo-go/pkg/vm" 16 "github.com/nspcc-dev/neo-go/pkg/vm/emit" 17 "github.com/nspcc-dev/neo-go/pkg/vm/opcode" 18 "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" 19 "github.com/stretchr/testify/assert" 20 "github.com/stretchr/testify/require" 21 ) 22 23 type testCase struct { 24 name string 25 src string 26 result any 27 } 28 29 // testMainIdent is a method invoked in tests by default. 30 const testMainIdent = "Main" 31 32 func runTestCases(t *testing.T, tcases []testCase) { 33 for _, tcase := range tcases { 34 t.Run(tcase.name, func(t *testing.T) { eval(t, tcase.src, tcase.result) }) 35 } 36 } 37 38 func eval(t *testing.T, src string, result any, expectedOps ...any) []byte { 39 vm, _, script := vmAndCompileInterop(t, src) 40 if len(expectedOps) != 0 { 41 expected := io.NewBufBinWriter() 42 for _, op := range expectedOps { 43 switch typ := op.(type) { 44 case opcode.Opcode: 45 emit.Opcodes(expected.BinWriter, typ) 46 case []any: 47 emit.Instruction(expected.BinWriter, typ[0].(opcode.Opcode), typ[1].([]byte)) 48 default: 49 t.Fatalf("unexpected evaluation operation: %v", typ) 50 } 51 } 52 53 require.Equal(t, expected.Bytes(), script) 54 } 55 runAndCheck(t, vm, result) 56 return script 57 } 58 59 func evalWithError(t *testing.T, src string, e string) []byte { 60 vm, _, prog := vmAndCompileInterop(t, src) 61 err := vm.Run() 62 require.Error(t, err) 63 require.True(t, strings.Contains(err.Error(), e), err) 64 return prog 65 } 66 67 func runAndCheck(t *testing.T, v *vm.VM, result any) { 68 err := v.Run() 69 require.NoError(t, err) 70 assert.Equal(t, 1, v.Estack().Len(), "stack contains unexpected items") 71 assertResult(t, v, result) 72 } 73 74 func evalWithArgs(t *testing.T, src string, op []byte, args []stackitem.Item, result any) { 75 vm := vmAndCompile(t, src) 76 if len(args) > 0 { 77 vm.Estack().PushVal(args) 78 } 79 if op != nil { 80 vm.Estack().PushVal(op) 81 } 82 runAndCheck(t, vm, result) 83 } 84 85 func assertResult(t *testing.T, vm *vm.VM, result any) { 86 assert.Equal(t, result, vm.PopResult()) 87 assert.Nil(t, vm.Context()) 88 } 89 90 func vmAndCompile(t *testing.T, src string) *vm.VM { 91 v, _, _ := vmAndCompileInterop(t, src) 92 return v 93 } 94 95 func vmAndCompileInterop(t *testing.T, src string) (*vm.VM, *storagePlugin, []byte) { 96 vm := vm.New() 97 98 storePlugin := newStoragePlugin() 99 vm.GasLimit = -1 100 vm.SyscallHandler = storePlugin.syscallHandler 101 102 b, di, err := compiler.CompileWithOptions("foo.go", strings.NewReader(src), nil) 103 require.NoError(t, err) 104 105 storePlugin.info = di 106 invokeMethod(t, testMainIdent, b.Script, vm, di) 107 return vm, storePlugin, b.Script 108 } 109 110 func invokeMethod(t *testing.T, method string, script []byte, v *vm.VM, di *compiler.DebugInfo) { 111 mainOffset := -1 112 initOffset := -1 113 for i := range di.Methods { 114 switch di.Methods[i].ID { 115 case method: 116 mainOffset = int(di.Methods[i].Range.Start) 117 case manifest.MethodInit: 118 initOffset = int(di.Methods[i].Range.Start) 119 } 120 } 121 require.True(t, mainOffset >= 0) 122 v.LoadScriptWithFlags(script, callflag.All) 123 v.Context().Jump(mainOffset) 124 if initOffset >= 0 { 125 v.Call(initOffset) 126 } 127 } 128 129 type storagePlugin struct { 130 info *compiler.DebugInfo 131 mem map[string][]byte 132 interops map[uint32]func(v *vm.VM) error 133 events []state.NotificationEvent 134 } 135 136 func newStoragePlugin() *storagePlugin { 137 s := &storagePlugin{ 138 mem: make(map[string][]byte), 139 interops: make(map[uint32]func(v *vm.VM) error), 140 } 141 s.interops[interopnames.ToID([]byte(interopnames.SystemStorageGet))] = s.Get 142 s.interops[interopnames.ToID([]byte(interopnames.SystemStoragePut))] = s.Put 143 s.interops[interopnames.ToID([]byte(interopnames.SystemStorageGetContext))] = s.GetContext 144 s.interops[interopnames.ToID([]byte(interopnames.SystemRuntimeNotify))] = s.Notify 145 s.interops[interopnames.ToID([]byte(interopnames.SystemRuntimeGetTime))] = s.GetTime 146 return s 147 } 148 149 func (s *storagePlugin) syscallHandler(v *vm.VM, id uint32) error { 150 f := s.interops[id] 151 if f != nil { 152 if !v.AddGas(1) { 153 return errors.New("insufficient amount of gas") 154 } 155 return f(v) 156 } 157 return errors.New("syscall not found") 158 } 159 160 func (s *storagePlugin) Notify(v *vm.VM) error { 161 name := v.Estack().Pop().String() 162 item := stackitem.NewArray(v.Estack().Pop().Array()) 163 s.events = append(s.events, state.NotificationEvent{ 164 Name: name, 165 Item: item, 166 }) 167 return nil 168 } 169 170 func (s *storagePlugin) Delete(vm *vm.VM) error { 171 vm.Estack().Pop() 172 key := vm.Estack().Pop().Bytes() 173 delete(s.mem, string(key)) 174 return nil 175 } 176 177 func (s *storagePlugin) Put(vm *vm.VM) error { 178 vm.Estack().Pop() 179 key := vm.Estack().Pop().Bytes() 180 value := vm.Estack().Pop().Bytes() 181 s.mem[string(key)] = value 182 return nil 183 } 184 185 func (s *storagePlugin) Get(vm *vm.VM) error { 186 vm.Estack().Pop() 187 item := vm.Estack().Pop().Bytes() 188 if val, ok := s.mem[string(item)]; ok { 189 vm.Estack().PushVal(val) 190 return nil 191 } 192 return fmt.Errorf("could not find %+v", item) 193 } 194 195 func (s *storagePlugin) GetContext(vm *vm.VM) error { 196 // Pushing anything on the stack here will work. This is just to satisfy 197 // the compiler, thinking it has pushed the context ^^. 198 vm.Estack().PushVal(10) 199 return nil 200 } 201 202 func (s *storagePlugin) GetTime(vm *vm.VM) error { 203 // Pushing anything on the stack here will work. This is just to satisfy 204 // the compiler, thinking it has pushed the context ^^. 205 vm.Estack().PushVal(4) 206 return nil 207 }