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  }