diff --git a/pkg/compiler/codegen.go b/pkg/compiler/codegen.go index cdb7a0ab5..29127d02e 100644 --- a/pkg/compiler/codegen.go +++ b/pkg/compiler/codegen.go @@ -17,6 +17,7 @@ import ( "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" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "golang.org/x/tools/go/loader" ) @@ -720,7 +721,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { // For now we will assume that there are only byte slice conversions. // E.g. []byte("foobar") or []byte(scriptHash). ast.Walk(c, n.Args[0]) - c.emitConvert(vm.BufferT) + c.emitConvert(stackitem.BufferT) return nil } @@ -1091,12 +1092,12 @@ func (c *codegen) convertBuiltin(expr *ast.CallExpr) { c.prog.Err = errors.New("panic should have string or nil argument") } case "ToInteger", "ToByteArray", "ToBool": - typ := vm.IntegerT + typ := stackitem.IntegerT switch name { case "ToByteArray": - typ = vm.ByteArrayT + typ = stackitem.ByteArrayT case "ToBool": - typ = vm.BooleanT + typ = stackitem.BooleanT } c.emitConvert(typ) case "SHA256": @@ -1123,7 +1124,7 @@ func (c *codegen) convertBuiltin(expr *ast.CallExpr) { } bytes := uint160.BytesBE() emit.Bytes(c.prog.BinWriter, bytes) - c.emitConvert(vm.BufferT) + c.emitConvert(stackitem.BufferT) } } @@ -1150,7 +1151,7 @@ func transformArgs(fun ast.Expr, args []ast.Expr) []ast.Expr { } // emitConvert converts top stack item to the specified type. -func (c *codegen) emitConvert(typ vm.StackItemType) { +func (c *codegen) emitConvert(typ stackitem.Type) { emit.Instruction(c.prog.BinWriter, opcode.CONVERT, []byte{byte(typ)}) } @@ -1162,7 +1163,7 @@ func (c *codegen) convertByteArray(lit *ast.CompositeLit) { buf[i] = byte(val) } emit.Bytes(c.prog.BinWriter, buf) - c.emitConvert(vm.BufferT) + c.emitConvert(stackitem.BufferT) } func (c *codegen) convertMap(lit *ast.CompositeLit) { diff --git a/pkg/compiler/for_test.go b/pkg/compiler/for_test.go index f3ea262f3..df2fa06df 100644 --- a/pkg/compiler/for_test.go +++ b/pkg/compiler/for_test.go @@ -5,7 +5,7 @@ import ( "math/big" "testing" - "github.com/nspcc-dev/neo-go/pkg/vm" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" ) func TestEntryPointWithMethod(t *testing.T) { @@ -30,7 +30,7 @@ func TestEntryPointWithArgs(t *testing.T) { return 2 + args[1].(int) } ` - args := []vm.StackItem{vm.NewBigIntegerItem(big.NewInt(0)), vm.NewBigIntegerItem(big.NewInt(1))} + args := []stackitem.Item{stackitem.NewBigInteger(big.NewInt(0)), stackitem.NewBigInteger(big.NewInt(1))} evalWithArgs(t, src, nil, args, big.NewInt(3)) } @@ -45,7 +45,7 @@ func TestEntryPointWithMethodAndArgs(t *testing.T) { return 0 } ` - args := []vm.StackItem{vm.NewBigIntegerItem(big.NewInt(0)), vm.NewBigIntegerItem(big.NewInt(1))} + args := []stackitem.Item{stackitem.NewBigInteger(big.NewInt(0)), stackitem.NewBigInteger(big.NewInt(1))} evalWithArgs(t, src, []byte("foobar"), args, big.NewInt(3)) } @@ -134,10 +134,10 @@ func TestStringArray(t *testing.T) { return x } ` - eval(t, src, []vm.StackItem{ - vm.NewByteArrayItem([]byte("foo")), - vm.NewByteArrayItem([]byte("bar")), - vm.NewByteArrayItem([]byte("foobar")), + eval(t, src, []stackitem.Item{ + stackitem.NewByteArray([]byte("foo")), + stackitem.NewByteArray([]byte("bar")), + stackitem.NewByteArray([]byte("foobar")), }) } @@ -149,10 +149,10 @@ func TestIntArray(t *testing.T) { return arr } ` - eval(t, src, []vm.StackItem{ - vm.NewBigIntegerItem(big.NewInt(1)), - vm.NewBigIntegerItem(big.NewInt(2)), - vm.NewBigIntegerItem(big.NewInt(3)), + eval(t, src, []stackitem.Item{ + stackitem.NewBigInteger(big.NewInt(1)), + stackitem.NewBigInteger(big.NewInt(2)), + stackitem.NewBigInteger(big.NewInt(3)), }) } @@ -198,7 +198,7 @@ func TestSimpleString(t *testing.T) { return x } ` - eval(t, src, vm.NewByteArrayItem([]byte("NEO")).Value()) + eval(t, src, stackitem.NewByteArray([]byte("NEO")).Value()) } func TestBoolAssign(t *testing.T) { @@ -315,7 +315,7 @@ func TestAppendString(t *testing.T) { return arr[3] } ` - eval(t, src, vm.NewByteArrayItem([]byte("d")).Value()) + eval(t, src, stackitem.NewByteArray([]byte("d")).Value()) } func TestAppendInt(t *testing.T) { diff --git a/pkg/compiler/slice_test.go b/pkg/compiler/slice_test.go index 326fe0e72..b90283a2a 100644 --- a/pkg/compiler/slice_test.go +++ b/pkg/compiler/slice_test.go @@ -4,7 +4,7 @@ import ( "math/big" "testing" - "github.com/nspcc-dev/neo-go/pkg/vm" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" ) var sliceTestCases = []testCase{ @@ -160,9 +160,9 @@ var sliceTestCases = []testCase{ a = append(a, "b") return a }`, - []vm.StackItem{ - vm.NewByteArrayItem([]byte("a")), - vm.NewByteArrayItem([]byte("b")), + []stackitem.Item{ + stackitem.NewByteArray([]byte("a")), + stackitem.NewByteArray([]byte("b")), }, }, { @@ -175,9 +175,9 @@ var sliceTestCases = []testCase{ a = append(a, "b") return a }`, - []vm.StackItem{ - vm.NewByteArrayItem([]byte("a")), - vm.NewByteArrayItem([]byte("b")), + []stackitem.Item{ + stackitem.NewByteArray([]byte("a")), + stackitem.NewByteArray([]byte("b")), }, }, { diff --git a/pkg/compiler/struct_test.go b/pkg/compiler/struct_test.go index 208f6a11b..b11b55ac0 100644 --- a/pkg/compiler/struct_test.go +++ b/pkg/compiler/struct_test.go @@ -4,7 +4,7 @@ import ( "math/big" "testing" - "github.com/nspcc-dev/neo-go/pkg/vm" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" ) var structTestCases = []testCase{ @@ -281,11 +281,11 @@ var structTestCases = []testCase{ return newToken() } `, - []vm.StackItem{ - vm.NewBigIntegerItem(big.NewInt(1)), - vm.NewBigIntegerItem(big.NewInt(2)), - vm.NewByteArrayItem([]byte("hello")), - vm.NewBoolItem(false), + []stackitem.Item{ + stackitem.NewBigInteger(big.NewInt(1)), + stackitem.NewBigInteger(big.NewInt(2)), + stackitem.NewByteArray([]byte("hello")), + stackitem.NewBool(false), }, }, { diff --git a/pkg/compiler/syscall_test.go b/pkg/compiler/syscall_test.go index d3fc3f14f..353b6efb7 100644 --- a/pkg/compiler/syscall_test.go +++ b/pkg/compiler/syscall_test.go @@ -4,7 +4,7 @@ import ( "math/big" "testing" - "github.com/nspcc-dev/neo-go/pkg/vm" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -41,8 +41,8 @@ func TestNotify(t *testing.T) { require.NoError(t, v.Run()) require.Equal(t, 3, len(s.events)) - exp0 := []vm.StackItem{vm.NewBigIntegerItem(big.NewInt(11)), vm.NewByteArrayItem([]byte("sum")), vm.NewBigIntegerItem(big.NewInt(12))} + exp0 := []stackitem.Item{stackitem.NewBigInteger(big.NewInt(11)), stackitem.NewByteArray([]byte("sum")), stackitem.NewBigInteger(big.NewInt(12))} assert.Equal(t, exp0, s.events[0].Value()) - assert.Equal(t, []vm.StackItem{}, s.events[1].Value()) - assert.Equal(t, []vm.StackItem{vm.NewByteArrayItem([]byte("single"))}, s.events[2].Value()) + assert.Equal(t, []stackitem.Item{}, s.events[1].Value()) + assert.Equal(t, []stackitem.Item{stackitem.NewByteArray([]byte("single"))}, s.events[2].Value()) } diff --git a/pkg/compiler/vm_test.go b/pkg/compiler/vm_test.go index c180bf55a..f841318d2 100644 --- a/pkg/compiler/vm_test.go +++ b/pkg/compiler/vm_test.go @@ -8,6 +8,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/compiler" "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/stackitem" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -38,7 +39,7 @@ func eval(t *testing.T, src string, result interface{}) { assertResult(t, vm, result) } -func evalWithArgs(t *testing.T, src string, op []byte, args []vm.StackItem, result interface{}) { +func evalWithArgs(t *testing.T, src string, op []byte, args []stackitem.Item, result interface{}) { vm := vmAndCompile(t, src) vm.LoadArgs(op, args) err := vm.Run() @@ -73,7 +74,7 @@ func vmAndCompileInterop(t *testing.T, src string) (*vm.VM, *storagePlugin) { type storagePlugin struct { mem map[string][]byte interops map[uint32]vm.InteropFunc - events []vm.StackItem + events []stackitem.Item } func newStoragePlugin() *storagePlugin { diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 1979b39d6..982886bcc 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -18,11 +18,13 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core/storage" "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" + "github.com/nspcc-dev/neo-go/pkg/encoding/bigint" "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" "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/stackitem" "github.com/pkg/errors" "go.uber.org/zap" ) @@ -573,7 +575,7 @@ func (bc *Blockchain) storeBlock(block *block.Block) error { return errors.Wrap(err, "failed to persist invocation results") } for _, note := range systemInterop.Notifications { - arr, ok := note.Item.Value().([]vm.StackItem) + arr, ok := note.Item.Value().([]stackitem.Item) if !ok || len(arr) != 4 { continue } @@ -605,7 +607,7 @@ func (bc *Blockchain) storeBlock(block *block.Block) error { if !ok { continue } - amount = emit.BytesToInt(bs) + amount = bigint.FromBytes(bs) } bc.processNEP5Transfer(cache, tx, block, note.ScriptHash, from, to, amount.Int64()) } diff --git a/pkg/core/interop/context.go b/pkg/core/interop/context.go index 61e56ad59..b1e400624 100644 --- a/pkg/core/interop/context.go +++ b/pkg/core/interop/context.go @@ -15,6 +15,7 @@ import ( "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/stackitem" "go.uber.org/zap" ) @@ -60,7 +61,7 @@ type Function struct { } // Method is a signature for a native method. -type Method = func(ic *Context, args []vm.StackItem) vm.StackItem +type Method = func(ic *Context, args []stackitem.Item) stackitem.Item // MethodAndPrice is a native-contract method descriptor. type MethodAndPrice struct { diff --git a/pkg/core/interop/crypto/ecdsa.go b/pkg/core/interop/crypto/ecdsa.go index 9e634f5ea..d80d67578 100644 --- a/pkg/core/interop/crypto/ecdsa.go +++ b/pkg/core/interop/crypto/ecdsa.go @@ -9,6 +9,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/vm" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" ) // ECDSAVerify checks ECDSA signature. @@ -48,12 +49,12 @@ func ECDSACheckMultisig(ic *interop.Context, v *vm.VM) error { return nil } -func getMessage(ic *interop.Context, item vm.StackItem) []byte { +func getMessage(ic *interop.Context, item stackitem.Item) []byte { var msg []byte switch val := item.(type) { - case *vm.InteropItem: + case *stackitem.Interop: msg = val.Value().(crypto.Verifiable).GetSignedPart() - case vm.NullItem: + case stackitem.Null: msg = ic.Container.GetSignedPart() default: var err error diff --git a/pkg/core/interop/crypto/ecdsa_test.go b/pkg/core/interop/crypto/ecdsa_test.go index da652ca97..8dfa856ef 100644 --- a/pkg/core/interop/crypto/ecdsa_test.go +++ b/pkg/core/interop/crypto/ecdsa_test.go @@ -9,16 +9,17 @@ import ( "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" "github.com/nspcc-dev/neo-go/pkg/vm" "github.com/nspcc-dev/neo-go/pkg/vm/opcode" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func initCHECKMULTISIG(msg []byte, n int) ([]vm.StackItem, []vm.StackItem, map[string]*keys.PublicKey, error) { +func initCHECKMULTISIG(msg []byte, n int) ([]stackitem.Item, []stackitem.Item, map[string]*keys.PublicKey, error) { var err error keyMap := make(map[string]*keys.PublicKey) pkeys := make([]*keys.PrivateKey, n) - pubs := make([]vm.StackItem, n) + pubs := make([]stackitem.Item, n) for i := range pubs { pkeys[i], err = keys.NewPrivateKey() if err != nil { @@ -27,25 +28,25 @@ func initCHECKMULTISIG(msg []byte, n int) ([]vm.StackItem, []vm.StackItem, map[s pk := pkeys[i].PublicKey() data := pk.Bytes() - pubs[i] = vm.NewByteArrayItem(data) + pubs[i] = stackitem.NewByteArray(data) keyMap[string(data)] = pk } - sigs := make([]vm.StackItem, n) + sigs := make([]stackitem.Item, n) for i := range sigs { sig := pkeys[i].Sign(msg) - sigs[i] = vm.NewByteArrayItem(sig) + sigs[i] = stackitem.NewByteArray(sig) } return pubs, sigs, keyMap, nil } -func subSlice(arr []vm.StackItem, indices []int) []vm.StackItem { +func subSlice(arr []stackitem.Item, indices []int) []stackitem.Item { if indices == nil { return arr } - result := make([]vm.StackItem, len(indices)) + result := make([]stackitem.Item, len(indices)) for i, j := range indices { result[i] = arr[j] } diff --git a/pkg/core/interop_neo.go b/pkg/core/interop_neo.go index 5997a8764..5295d922b 100644 --- a/pkg/core/interop_neo.go +++ b/pkg/core/interop_neo.go @@ -13,6 +13,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" ) const ( @@ -66,9 +67,9 @@ func txGetAttributes(ic *interop.Context, v *vm.VM) error { if len(tx.Attributes) > vm.MaxArraySize { return errors.New("too many attributes") } - attrs := make([]vm.StackItem, 0, len(tx.Attributes)) + attrs := make([]stackitem.Item, 0, len(tx.Attributes)) for i := range tx.Attributes { - attrs = append(attrs, vm.NewInteropItem(&tx.Attributes[i])) + attrs = append(attrs, stackitem.NewInterop(&tx.Attributes[i])) } v.Estack().PushVal(attrs) return nil @@ -84,9 +85,9 @@ func txGetWitnesses(ic *interop.Context, v *vm.VM) error { if len(tx.Scripts) > vm.MaxArraySize { return errors.New("too many outputs") } - scripts := make([]vm.StackItem, 0, len(tx.Scripts)) + scripts := make([]stackitem.Item, 0, len(tx.Scripts)) for i := range tx.Scripts { - scripts = append(scripts, vm.NewInteropItem(&tx.Scripts[i])) + scripts = append(scripts, stackitem.NewInterop(&tx.Scripts[i])) } v.Estack().PushVal(scripts) return nil @@ -139,7 +140,7 @@ func bcGetAccount(ic *interop.Context, v *vm.VM) error { if err != nil { return err } - v.Estack().PushVal(vm.NewInteropItem(acc)) + v.Estack().PushVal(stackitem.NewInterop(acc)) return nil } @@ -204,13 +205,13 @@ func storageFind(ic *interop.Context, v *vm.VM) error { return err } - filteredMap := vm.NewMapItem() + filteredMap := stackitem.NewMap() for k, v := range siMap { - filteredMap.Add(vm.NewByteArrayItem(append(prefix, []byte(k)...)), vm.NewByteArrayItem(v.Value)) + filteredMap.Add(stackitem.NewByteArray(append(prefix, []byte(k)...)), stackitem.NewByteArray(v.Value)) } - sort.Slice(filteredMap.Value().([]vm.MapElement), func(i, j int) bool { - return bytes.Compare(filteredMap.Value().([]vm.MapElement)[i].Key.Value().([]byte), - filteredMap.Value().([]vm.MapElement)[j].Key.Value().([]byte)) == -1 + sort.Slice(filteredMap.Value().([]stackitem.MapElement), func(i, j int) bool { + return bytes.Compare(filteredMap.Value().([]stackitem.MapElement)[i].Key.Value().([]byte), + filteredMap.Value().([]stackitem.MapElement)[j].Key.Value().([]byte)) == -1 }) item := vm.NewMapIterator(filteredMap) @@ -288,7 +289,7 @@ func contractCreate(ic *interop.Context, v *vm.VM) error { return err } } - v.Estack().PushVal(vm.NewInteropItem(contract)) + v.Estack().PushVal(stackitem.NewInterop(contract)) return nil } @@ -342,7 +343,7 @@ func contractMigrate(ic *interop.Context, v *vm.VM) error { } } } - v.Estack().PushVal(vm.NewInteropItem(contract)) + v.Estack().PushVal(stackitem.NewInterop(contract)) return contractDestroy(ic, v) } diff --git a/pkg/core/interop_neo_test.go b/pkg/core/interop_neo_test.go index 90e1ce2ae..50cb0d9f4 100644 --- a/pkg/core/interop_neo_test.go +++ b/pkg/core/interop_neo_test.go @@ -21,6 +21,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm" "github.com/nspcc-dev/neo-go/pkg/vm/opcode" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/stretchr/testify/require" ) @@ -70,12 +71,12 @@ func TestStorageFind(t *testing.T) { t.Run("normal invocation", func(t *testing.T) { v.Estack().PushVal([]byte{0x01}) - v.Estack().PushVal(vm.NewInteropItem(&StorageContext{ScriptHash: scriptHash})) + v.Estack().PushVal(stackitem.NewInterop(&StorageContext{ScriptHash: scriptHash})) err := storageFind(context, v) require.NoError(t, err) - var iter *vm.InteropItem + var iter *stackitem.Interop require.NotPanics(t, func() { iter = v.Estack().Top().Interop() }) require.NoError(t, enumerator.Next(context, v)) @@ -108,7 +109,7 @@ func TestStorageFind(t *testing.T) { t.Run("normal invocation, empty result", func(t *testing.T) { v.Estack().PushVal([]byte{0x03}) - v.Estack().PushVal(vm.NewInteropItem(&StorageContext{ScriptHash: scriptHash})) + v.Estack().PushVal(stackitem.NewInterop(&StorageContext{ScriptHash: scriptHash})) err := storageFind(context, v) require.NoError(t, err) @@ -119,7 +120,7 @@ func TestStorageFind(t *testing.T) { t.Run("invalid type for StorageContext", func(t *testing.T) { v.Estack().PushVal([]byte{0x01}) - v.Estack().PushVal(vm.NewInteropItem(nil)) + v.Estack().PushVal(stackitem.NewInterop(nil)) require.Error(t, storageFind(context, v)) }) @@ -129,7 +130,7 @@ func TestStorageFind(t *testing.T) { invalidHash[0] = ^invalidHash[0] v.Estack().PushVal([]byte{0x01}) - v.Estack().PushVal(vm.NewInteropItem(&StorageContext{ScriptHash: invalidHash})) + v.Estack().PushVal(stackitem.NewInterop(&StorageContext{ScriptHash: invalidHash})) require.Error(t, storageFind(context, v)) }) @@ -151,7 +152,7 @@ func TestHeaderGetVersion_Negative(t *testing.T) { chain := newTestChain(t) defer chain.Close() context := chain.newInteropContext(trigger.Application, dao.NewSimple(storage.NewMemoryStore()), block, nil) - v.Estack().PushVal(vm.NewBoolItem(false)) + v.Estack().PushVal(stackitem.NewBool(false)) err := headerGetVersion(context, v) require.Errorf(t, err, "value is not a header or block") @@ -183,7 +184,7 @@ func TestTxGetAttributes(t *testing.T) { err := txGetAttributes(context, v) require.NoError(t, err) - value := v.Estack().Pop().Value().([]vm.StackItem) + value := v.Estack().Pop().Value().([]stackitem.Item) require.Equal(t, tx.Attributes[0].Usage, value[0].Value().(*transaction.Attribute).Usage) } @@ -196,7 +197,7 @@ func TestWitnessGetVerificationScript(t *testing.T) { defer chain.Close() context := chain.newInteropContext(trigger.Application, dao.NewSimple(storage.NewMemoryStore()), nil, nil) - v.Estack().PushVal(vm.NewInteropItem(&witness)) + v.Estack().PushVal(stackitem.NewInterop(&witness)) err := witnessGetVerificationScript(context, v) require.NoError(t, err) value := v.Estack().Pop().Value().([]byte) @@ -247,7 +248,7 @@ func TestECDSAVerify(t *testing.T) { tx := transaction.New([]byte{0, 1, 2}, 1) msg := tx.GetSignedPart() sign := priv.Sign(msg) - runCase(t, false, true, sign, priv.PublicKey().Bytes(), vm.NewInteropItem(tx)) + runCase(t, false, true, sign, priv.PublicKey().Bytes(), stackitem.NewInterop(tx)) }) t.Run("signed script container", func(t *testing.T) { @@ -255,7 +256,7 @@ func TestECDSAVerify(t *testing.T) { msg := tx.GetSignedPart() sign := priv.Sign(msg) ic.Container = tx - runCase(t, false, true, sign, priv.PublicKey().Bytes(), vm.NullItem{}) + runCase(t, false, true, sign, priv.PublicKey().Bytes(), stackitem.Null{}) }) t.Run("missing arguments", func(t *testing.T) { @@ -282,7 +283,7 @@ func TestECDSAVerify(t *testing.T) { func TestAttrGetData(t *testing.T) { v, tx, context, chain := createVMAndTX(t) defer chain.Close() - v.Estack().PushVal(vm.NewInteropItem(&tx.Attributes[0])) + v.Estack().PushVal(stackitem.NewInterop(&tx.Attributes[0])) err := attrGetData(context, v) require.NoError(t, err) @@ -293,7 +294,7 @@ func TestAttrGetData(t *testing.T) { func TestAttrGetUsage(t *testing.T) { v, tx, context, chain := createVMAndTX(t) defer chain.Close() - v.Estack().PushVal(vm.NewInteropItem(&tx.Attributes[0])) + v.Estack().PushVal(stackitem.NewInterop(&tx.Attributes[0])) err := attrGetUsage(context, v) require.NoError(t, err) @@ -304,7 +305,7 @@ func TestAttrGetUsage(t *testing.T) { func TestAccountGetScriptHash(t *testing.T) { v, accState, context, chain := createVMAndAccState(t) defer chain.Close() - v.Estack().PushVal(vm.NewInteropItem(accState)) + v.Estack().PushVal(stackitem.NewInterop(accState)) err := accountGetScriptHash(context, v) require.NoError(t, err) @@ -315,7 +316,7 @@ func TestAccountGetScriptHash(t *testing.T) { func TestContractGetScript(t *testing.T) { v, contractState, context, chain := createVMAndContractState(t) defer chain.Close() - v.Estack().PushVal(vm.NewInteropItem(contractState)) + v.Estack().PushVal(stackitem.NewInterop(contractState)) err := contractGetScript(context, v) require.NoError(t, err) @@ -326,7 +327,7 @@ func TestContractGetScript(t *testing.T) { func TestContractIsPayable(t *testing.T) { v, contractState, context, chain := createVMAndContractState(t) defer chain.Close() - v.Estack().PushVal(vm.NewInteropItem(contractState)) + v.Estack().PushVal(stackitem.NewInterop(contractState)) err := contractIsPayable(context, v) require.NoError(t, err) @@ -341,13 +342,13 @@ func createVMAndPushBlock(t *testing.T) (*vm.VM, *block.Block, *interop.Context, block := newDumbBlock() chain := newTestChain(t) context := chain.newInteropContext(trigger.Application, dao.NewSimple(storage.NewMemoryStore()), block, nil) - v.Estack().PushVal(vm.NewInteropItem(block)) + v.Estack().PushVal(stackitem.NewInterop(block)) return v, block, context, chain } func createVMAndPushTX(t *testing.T) (*vm.VM, *transaction.Transaction, *interop.Context, *Blockchain) { v, tx, context, chain := createVMAndTX(t) - v.Estack().PushVal(vm.NewInteropItem(tx)) + v.Estack().PushVal(stackitem.NewInterop(tx)) return v, tx, context, chain } diff --git a/pkg/core/interop_system.go b/pkg/core/interop_system.go index b6dc95aa6..55b57fa09 100644 --- a/pkg/core/interop_system.go +++ b/pkg/core/interop_system.go @@ -15,6 +15,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "go.uber.org/zap" ) @@ -58,7 +59,7 @@ func bcGetBlock(ic *interop.Context, v *vm.VM) error { if err != nil { v.Estack().PushVal([]byte{}) } else { - v.Estack().PushVal(vm.NewInteropItem(block)) + v.Estack().PushVal(stackitem.NewInterop(block)) } return nil } @@ -74,7 +75,7 @@ func bcGetContract(ic *interop.Context, v *vm.VM) error { if err != nil { v.Estack().PushVal([]byte{}) } else { - v.Estack().PushVal(vm.NewInteropItem(cs)) + v.Estack().PushVal(stackitem.NewInterop(cs)) } return nil } @@ -89,7 +90,7 @@ func bcGetHeader(ic *interop.Context, v *vm.VM) error { if err != nil { v.Estack().PushVal([]byte{}) } else { - v.Estack().PushVal(vm.NewInteropItem(header)) + v.Estack().PushVal(stackitem.NewInterop(header)) } return nil } @@ -117,7 +118,7 @@ func bcGetTransaction(ic *interop.Context, v *vm.VM) error { if err != nil { return err } - v.Estack().PushVal(vm.NewInteropItem(tx)) + v.Estack().PushVal(stackitem.NewInterop(tx)) return nil } @@ -208,9 +209,9 @@ func blockGetTransactions(ic *interop.Context, v *vm.VM) error { if len(block.Transactions) > vm.MaxArraySize { return errors.New("too many transactions") } - txes := make([]vm.StackItem, 0, len(block.Transactions)) + txes := make([]stackitem.Item, 0, len(block.Transactions)) for _, tx := range block.Transactions { - txes = append(txes, vm.NewInteropItem(tx)) + txes = append(txes, stackitem.NewInterop(tx)) } v.Estack().PushVal(txes) return nil @@ -229,7 +230,7 @@ func blockGetTransaction(ic *interop.Context, v *vm.VM) error { return errors.New("wrong transaction index") } tx := block.Transactions[index] - v.Estack().PushVal(vm.NewInteropItem(tx)) + v.Estack().PushVal(stackitem.NewInterop(tx)) return nil } @@ -247,7 +248,7 @@ func txGetHash(ic *interop.Context, v *vm.VM) error { // engineGetScriptContainer returns transaction that contains the script being // run. func engineGetScriptContainer(ic *interop.Context, v *vm.VM) error { - v.Estack().PushVal(vm.NewInteropItem(ic.Container)) + v.Estack().PushVal(stackitem.NewInterop(ic.Container)) return nil } @@ -289,9 +290,9 @@ func runtimeNotify(ic *interop.Context, v *vm.VM) error { // outside of the interop subsystem anyway. I'd probably fail transactions // that emit such broken notifications, but that might break compatibility // with testnet/mainnet, so we're replacing these with error messages. - _, err := vm.SerializeItem(item) + _, err := stackitem.SerializeItem(item) if err != nil { - item = vm.NewByteArrayItem([]byte(fmt.Sprintf("bad notification: %v", err))) + item = stackitem.NewByteArray([]byte(fmt.Sprintf("bad notification: %v", err))) } ne := state.NotificationEvent{ScriptHash: v.GetCurrentScriptHash(), Item: item} ic.Notifications = append(ic.Notifications, ne) @@ -387,7 +388,7 @@ func storageGetContext(ic *interop.Context, v *vm.VM) error { ScriptHash: v.GetCurrentScriptHash(), ReadOnly: false, } - v.Estack().PushVal(vm.NewInteropItem(sc)) + v.Estack().PushVal(stackitem.NewInterop(sc)) return nil } @@ -397,7 +398,7 @@ func storageGetReadOnlyContext(ic *interop.Context, v *vm.VM) error { ScriptHash: v.GetCurrentScriptHash(), ReadOnly: true, } - v.Estack().PushVal(vm.NewInteropItem(sc)) + v.Estack().PushVal(stackitem.NewInterop(sc)) return nil } @@ -467,7 +468,7 @@ func storageContextAsReadOnly(ic *interop.Context, v *vm.VM) error { } stc = stx } - v.Estack().PushVal(vm.NewInteropItem(stc)) + v.Estack().PushVal(stackitem.NewInterop(stc)) return nil } @@ -488,7 +489,7 @@ func contractCallEx(ic *interop.Context, v *vm.VM) error { return contractCallExInternal(ic, v, h, method, args, flags) } -func contractCallExInternal(ic *interop.Context, v *vm.VM, h []byte, method vm.StackItem, args vm.StackItem, _ smartcontract.CallFlag) error { +func contractCallExInternal(ic *interop.Context, v *vm.VM, h []byte, method stackitem.Item, args stackitem.Item, _ smartcontract.CallFlag) error { u, err := util.Uint160DecodeBytesBE(h) if err != nil { return errors.New("invalid contract hash") @@ -548,6 +549,6 @@ func contractGetStorageContext(ic *interop.Context, v *vm.VM) error { stc := &StorageContext{ ScriptHash: cs.ScriptHash(), } - v.Estack().PushVal(vm.NewInteropItem(stc)) + v.Estack().PushVal(stackitem.NewInterop(stc)) return nil } diff --git a/pkg/core/native/native_neo.go b/pkg/core/native/native_neo.go index 0c9506e60..84b85cf74 100644 --- a/pkg/core/native/native_neo.go +++ b/pkg/core/native/native_neo.go @@ -14,7 +14,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "github.com/nspcc-dev/neo-go/pkg/util" - "github.com/nspcc-dev/neo-go/pkg/vm" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/pkg/errors" ) @@ -194,7 +194,7 @@ func (n *NEO) distributeGas(ic *interop.Context, h util.Uint160, acc *state.NEOB return nil } -func (n *NEO) unclaimedGas(ic *interop.Context, args []vm.StackItem) vm.StackItem { +func (n *NEO) unclaimedGas(ic *interop.Context, args []stackitem.Item) stackitem.Item { u := toUint160(args[0]) end := uint32(toBigInt(args[1]).Int64()) bs, err := ic.DAO.GetNEP5Balances(u) @@ -204,12 +204,12 @@ func (n *NEO) unclaimedGas(ic *interop.Context, args []vm.StackItem) vm.StackIte tr := bs.Trackers[n.Hash] gen := ic.Chain.CalculateClaimable(tr.Balance, tr.LastUpdatedBlock, end) - return vm.NewBigIntegerItem(big.NewInt(int64(gen))) + return stackitem.NewBigInteger(big.NewInt(int64(gen))) } -func (n *NEO) registerValidator(ic *interop.Context, args []vm.StackItem) vm.StackItem { +func (n *NEO) registerValidator(ic *interop.Context, args []stackitem.Item) stackitem.Item { err := n.registerValidatorInternal(ic, toPublicKey(args[0])) - return vm.NewBoolItem(err == nil) + return stackitem.NewBool(err == nil) } func (n *NEO) registerValidatorInternal(ic *interop.Context, pub *keys.PublicKey) error { @@ -226,9 +226,9 @@ func (n *NEO) registerValidatorInternal(ic *interop.Context, pub *keys.PublicKey return ic.DAO.PutStorageItem(n.Hash, key, si) } -func (n *NEO) vote(ic *interop.Context, args []vm.StackItem) vm.StackItem { +func (n *NEO) vote(ic *interop.Context, args []stackitem.Item) stackitem.Item { acc := toUint160(args[0]) - arr := args[1].Value().([]vm.StackItem) + arr := args[1].Value().([]stackitem.Item) var pubs keys.PublicKeys for i := range arr { pub := new(keys.PublicKey) @@ -241,7 +241,7 @@ func (n *NEO) vote(ic *interop.Context, args []vm.StackItem) vm.StackItem { pubs = append(pubs, pub) } err := n.VoteInternal(ic, acc, pubs) - return vm.NewBoolItem(err == nil) + return stackitem.NewBool(err == nil) } // VoteInternal votes from account h for validarors specified in pubs. @@ -361,19 +361,19 @@ func (n *NEO) GetRegisteredValidators(d dao.DAO) ([]state.Validator, error) { return arr, nil } -func (n *NEO) getRegisteredValidatorsCall(ic *interop.Context, _ []vm.StackItem) vm.StackItem { +func (n *NEO) getRegisteredValidatorsCall(ic *interop.Context, _ []stackitem.Item) stackitem.Item { validators, err := n.getRegisteredValidators(ic.DAO) if err != nil { panic(err) } - arr := make([]vm.StackItem, len(validators)) + arr := make([]stackitem.Item, len(validators)) for i := range validators { - arr[i] = vm.NewStructItem([]vm.StackItem{ - vm.NewByteArrayItem([]byte(validators[i].Key)), - vm.NewBigIntegerItem(validators[i].Votes), + arr[i] = stackitem.NewStruct([]stackitem.Item{ + stackitem.NewByteArray([]byte(validators[i].Key)), + stackitem.NewBigInteger(validators[i].Votes), }) } - return vm.NewArrayItem(arr) + return stackitem.NewArray(arr) } // GetValidatorsInternal returns a list of current validators. @@ -430,7 +430,7 @@ func (n *NEO) GetValidatorsInternal(bc blockchainer.Blockchainer, d dao.DAO) (ke return result, nil } -func (n *NEO) getValidators(ic *interop.Context, _ []vm.StackItem) vm.StackItem { +func (n *NEO) getValidators(ic *interop.Context, _ []stackitem.Item) stackitem.Item { result, err := n.GetValidatorsInternal(ic.Chain, ic.DAO) if err != nil { panic(err) @@ -438,7 +438,7 @@ func (n *NEO) getValidators(ic *interop.Context, _ []vm.StackItem) vm.StackItem return pubsToArray(result) } -func (n *NEO) getNextBlockValidators(ic *interop.Context, _ []vm.StackItem) vm.StackItem { +func (n *NEO) getNextBlockValidators(ic *interop.Context, _ []stackitem.Item) stackitem.Item { result, err := n.GetNextBlockValidatorsInternal(ic.Chain, ic.DAO) if err != nil { panic(err) @@ -460,15 +460,15 @@ func (n *NEO) GetNextBlockValidatorsInternal(bc blockchainer.Blockchainer, d dao return pubs, nil } -func pubsToArray(pubs keys.PublicKeys) vm.StackItem { - arr := make([]vm.StackItem, len(pubs)) +func pubsToArray(pubs keys.PublicKeys) stackitem.Item { + arr := make([]stackitem.Item, len(pubs)) for i := range pubs { - arr[i] = vm.NewByteArrayItem(pubs[i].Bytes()) + arr[i] = stackitem.NewByteArray(pubs[i].Bytes()) } - return vm.NewArrayItem(arr) + return stackitem.NewArray(arr) } -func toPublicKey(s vm.StackItem) *keys.PublicKey { +func toPublicKey(s stackitem.Item) *keys.PublicKey { buf, err := s.TryBytes() if err != nil { panic(err) diff --git a/pkg/core/native/native_nep5.go b/pkg/core/native/native_nep5.go index eb8c0ef23..66d9fbd3d 100644 --- a/pkg/core/native/native_nep5.go +++ b/pkg/core/native/native_nep5.go @@ -7,11 +7,11 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/core/interop/runtime" "github.com/nspcc-dev/neo-go/pkg/core/state" + "github.com/nspcc-dev/neo-go/pkg/encoding/bigint" "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "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/stackitem" ) // prefixAccount is the standard prefix used to store account data. @@ -86,20 +86,20 @@ func (c *nep5TokenNative) Initialize(_ *interop.Context) error { return nil } -func (c *nep5TokenNative) Name(_ *interop.Context, _ []vm.StackItem) vm.StackItem { - return vm.NewByteArrayItem([]byte(c.name)) +func (c *nep5TokenNative) Name(_ *interop.Context, _ []stackitem.Item) stackitem.Item { + return stackitem.NewByteArray([]byte(c.name)) } -func (c *nep5TokenNative) Symbol(_ *interop.Context, _ []vm.StackItem) vm.StackItem { - return vm.NewByteArrayItem([]byte(c.symbol)) +func (c *nep5TokenNative) Symbol(_ *interop.Context, _ []stackitem.Item) stackitem.Item { + return stackitem.NewByteArray([]byte(c.symbol)) } -func (c *nep5TokenNative) Decimals(_ *interop.Context, _ []vm.StackItem) vm.StackItem { - return vm.NewBigIntegerItem(big.NewInt(c.decimals)) +func (c *nep5TokenNative) Decimals(_ *interop.Context, _ []stackitem.Item) stackitem.Item { + return stackitem.NewBigInteger(big.NewInt(c.decimals)) } -func (c *nep5TokenNative) TotalSupply(ic *interop.Context, _ []vm.StackItem) vm.StackItem { - return vm.NewBigIntegerItem(c.getTotalSupply(ic)) +func (c *nep5TokenNative) TotalSupply(ic *interop.Context, _ []stackitem.Item) stackitem.Item { + return stackitem.NewBigInteger(c.getTotalSupply(ic)) } func (c *nep5TokenNative) getTotalSupply(ic *interop.Context) *big.Int { @@ -107,37 +107,37 @@ func (c *nep5TokenNative) getTotalSupply(ic *interop.Context) *big.Int { if si == nil { return big.NewInt(0) } - return emit.BytesToInt(si.Value) + return bigint.FromBytes(si.Value) } func (c *nep5TokenNative) saveTotalSupply(ic *interop.Context, supply *big.Int) error { - si := &state.StorageItem{Value: emit.IntToBytes(supply)} + si := &state.StorageItem{Value: bigint.ToBytes(supply)} return ic.DAO.PutStorageItem(c.Hash, totalSupplyKey, si) } -func (c *nep5TokenNative) Transfer(ic *interop.Context, args []vm.StackItem) vm.StackItem { +func (c *nep5TokenNative) Transfer(ic *interop.Context, args []stackitem.Item) stackitem.Item { from := toUint160(args[0]) to := toUint160(args[1]) amount := toBigInt(args[2]) err := c.transfer(ic, from, to, amount) - return vm.NewBoolItem(err == nil) + return stackitem.NewBool(err == nil) } -func addrToStackItem(u *util.Uint160) vm.StackItem { +func addrToStackItem(u *util.Uint160) stackitem.Item { if u == nil { - return vm.NullItem{} + return stackitem.Null{} } - return vm.NewByteArrayItem(u.BytesBE()) + return stackitem.NewByteArray(u.BytesBE()) } func (c *nep5TokenNative) emitTransfer(ic *interop.Context, from, to *util.Uint160, amount *big.Int) { ne := state.NotificationEvent{ ScriptHash: c.Hash, - Item: vm.NewArrayItem([]vm.StackItem{ - vm.NewByteArrayItem([]byte("Transfer")), + Item: stackitem.NewArray([]stackitem.Item{ + stackitem.NewByteArray([]byte("Transfer")), addrToStackItem(from), addrToStackItem(to), - vm.NewBigIntegerItem(amount), + stackitem.NewBigInteger(amount), }), } ic.Notifications = append(ic.Notifications, ne) @@ -197,14 +197,14 @@ func (c *nep5TokenNative) transfer(ic *interop.Context, from, to util.Uint160, a return nil } -func (c *nep5TokenNative) balanceOf(ic *interop.Context, args []vm.StackItem) vm.StackItem { +func (c *nep5TokenNative) balanceOf(ic *interop.Context, args []stackitem.Item) stackitem.Item { h := toUint160(args[0]) bs, err := ic.DAO.GetNEP5Balances(h) if err != nil { panic(err) } balance := bs.Trackers[c.Hash].Balance - return vm.NewBigIntegerItem(big.NewInt(balance)) + return stackitem.NewBigInteger(big.NewInt(balance)) } func (c *nep5TokenNative) mint(ic *interop.Context, h util.Uint160, amount *big.Int) { @@ -269,7 +269,7 @@ func newMethodAndPrice(f interop.Method, price int64, flags smartcontract.CallFl } } -func toBigInt(s vm.StackItem) *big.Int { +func toBigInt(s stackitem.Item) *big.Int { bi, err := s.TryInteger() if err != nil { panic(err) @@ -277,7 +277,7 @@ func toBigInt(s vm.StackItem) *big.Int { return bi } -func toUint160(s vm.StackItem) util.Uint160 { +func toUint160(s stackitem.Item) util.Uint160 { buf, err := s.TryBytes() if err != nil { panic(err) diff --git a/pkg/core/native/validators_count.go b/pkg/core/native/validators_count.go index 25cfe3c7c..d3b0feccf 100644 --- a/pkg/core/native/validators_count.go +++ b/pkg/core/native/validators_count.go @@ -1,10 +1,11 @@ package native import ( + "errors" "math/big" + "github.com/nspcc-dev/neo-go/pkg/encoding/bigint" "github.com/nspcc-dev/neo-go/pkg/io" - "github.com/nspcc-dev/neo-go/pkg/vm/emit" ) // MaxValidatorsVoted limits the number of validators that one can vote for. @@ -41,19 +42,25 @@ func (vc *ValidatorsCount) Bytes() []byte { // EncodeBinary implements io.Serializable interface. func (vc *ValidatorsCount) EncodeBinary(w *io.BinWriter) { + w.WriteVarUint(uint64(MaxValidatorsVoted)) for i := range vc { - w.WriteVarBytes(emit.IntToBytes(&vc[i])) + w.WriteVarBytes(bigint.ToBytes(&vc[i])) } } // DecodeBinary implements io.Serializable interface. func (vc *ValidatorsCount) DecodeBinary(r *io.BinReader) { - for i := range vc { + count := r.ReadVarUint() + if count < 0 || count > MaxValidatorsVoted { + r.Err = errors.New("invalid validators count") + return + } + for i := 0; i < int(count); i++ { buf := r.ReadVarBytes() if r.Err != nil { return } - vc[i] = *emit.BytesToInt(buf) + vc[i] = *bigint.FromBytes(buf) } } diff --git a/pkg/core/native_contract_test.go b/pkg/core/native_contract_test.go index a95e8cd0b..855803125 100644 --- a/pkg/core/native_contract_test.go +++ b/pkg/core/native_contract_test.go @@ -10,8 +10,8 @@ import ( "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" - "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/stackitem" "github.com/stretchr/testify/require" ) @@ -67,7 +67,7 @@ func newTestNative() *testNative { return tn } -func (tn *testNative) sum(_ *interop.Context, args []vm.StackItem) vm.StackItem { +func (tn *testNative) sum(_ *interop.Context, args []stackitem.Item) stackitem.Item { s1, err := args[0].TryInteger() if err != nil { panic(err) @@ -76,7 +76,7 @@ func (tn *testNative) sum(_ *interop.Context, args []vm.StackItem) vm.StackItem if err != nil { panic(err) } - return vm.NewBigIntegerItem(s1.Add(s1, s2)) + return stackitem.NewBigInteger(s1.Add(s1, s2)) } func TestNativeContract_Invoke(t *testing.T) { diff --git a/pkg/core/state/native_state.go b/pkg/core/state/native_state.go index f1377f460..d551c6711 100644 --- a/pkg/core/state/native_state.go +++ b/pkg/core/state/native_state.go @@ -5,7 +5,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/io" - "github.com/nspcc-dev/neo-go/pkg/vm/emit" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" ) // NEP5BalanceState represents balance state of a NEP5-token. @@ -44,18 +44,27 @@ func (s *NEP5BalanceState) Bytes() []byte { return w.Bytes() } +func (s *NEP5BalanceState) toStackItem() stackitem.Item { + return stackitem.NewStruct([]stackitem.Item{stackitem.NewBigInteger(&s.Balance)}) +} + +func (s *NEP5BalanceState) fromStackItem(item stackitem.Item) { + s.Balance = *item.(*stackitem.Struct).Value().([]stackitem.Item)[0].Value().(*big.Int) +} + // EncodeBinary implements io.Serializable interface. func (s *NEP5BalanceState) EncodeBinary(w *io.BinWriter) { - w.WriteVarBytes(emit.IntToBytes(&s.Balance)) + si := s.toStackItem() + stackitem.EncodeBinaryStackItem(si, w) } // DecodeBinary implements io.Serializable interface. func (s *NEP5BalanceState) DecodeBinary(r *io.BinReader) { - buf := r.ReadVarBytes() + si := stackitem.DecodeBinaryStackItem(r) if r.Err != nil { return } - s.Balance = *emit.BytesToInt(buf) + s.fromStackItem(si) } // NEOBalanceStateFromBytes converts serialized NEOBalanceState to structure. @@ -85,14 +94,37 @@ func (s *NEOBalanceState) Bytes() []byte { // EncodeBinary implements io.Serializable interface. func (s *NEOBalanceState) EncodeBinary(w *io.BinWriter) { - s.NEP5BalanceState.EncodeBinary(w) - w.WriteU32LE(s.BalanceHeight) - w.WriteArray(s.Votes) + si := s.toStackItem() + stackitem.EncodeBinaryStackItem(si, w) } // DecodeBinary implements io.Serializable interface. func (s *NEOBalanceState) DecodeBinary(r *io.BinReader) { - s.NEP5BalanceState.DecodeBinary(r) - s.BalanceHeight = r.ReadU32LE() - r.ReadArray(&s.Votes) + si := stackitem.DecodeBinaryStackItem(r) + if r.Err != nil { + return + } + s.fromStackItem(si) +} + +func (s *NEOBalanceState) toStackItem() stackitem.Item { + result := s.NEP5BalanceState.toStackItem().(*stackitem.Struct) + result.Append(stackitem.NewBigInteger(big.NewInt(int64(s.BalanceHeight)))) + votes := make([]stackitem.Item, len(s.Votes)) + for i, v := range s.Votes { + votes[i] = stackitem.NewByteArray(v.Bytes()) + } + result.Append(stackitem.NewArray(votes)) + return result +} + +func (s *NEOBalanceState) fromStackItem(item stackitem.Item) { + structItem := item.Value().([]stackitem.Item) + s.Balance = *structItem[0].Value().(*big.Int) + s.BalanceHeight = uint32(structItem[1].Value().(*big.Int).Int64()) + votes := structItem[2].Value().([]stackitem.Item) + s.Votes = make([]*keys.PublicKey, len(votes)) + for i, v := range votes { + s.Votes[i].DecodeBytes(v.Value().([]byte)) + } } diff --git a/pkg/core/state/notification_event.go b/pkg/core/state/notification_event.go index 06e634464..b7efe327f 100644 --- a/pkg/core/state/notification_event.go +++ b/pkg/core/state/notification_event.go @@ -5,14 +5,14 @@ import ( "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" "github.com/nspcc-dev/neo-go/pkg/util" - "github.com/nspcc-dev/neo-go/pkg/vm" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" ) -// NotificationEvent is a tuple of scripthash that emitted the StackItem as a +// NotificationEvent is a tuple of scripthash that emitted the Item as a // notification and that item itself. type NotificationEvent struct { ScriptHash util.Uint160 - Item vm.StackItem + Item stackitem.Item } // AppExecResult represent the result of the script execution, gathering together @@ -29,13 +29,13 @@ type AppExecResult struct { // EncodeBinary implements the Serializable interface. func (ne *NotificationEvent) EncodeBinary(w *io.BinWriter) { ne.ScriptHash.EncodeBinary(w) - vm.EncodeBinaryStackItem(ne.Item, w) + stackitem.EncodeBinaryStackItem(ne.Item, w) } // DecodeBinary implements the Serializable interface. func (ne *NotificationEvent) DecodeBinary(r *io.BinReader) { ne.ScriptHash.DecodeBinary(r) - ne.Item = vm.DecodeBinaryStackItem(r) + ne.Item = stackitem.DecodeBinaryStackItem(r) } // EncodeBinary implements the Serializable interface. diff --git a/pkg/core/state/notification_event_test.go b/pkg/core/state/notification_event_test.go index 1e17e40e9..c88e2d5ff 100644 --- a/pkg/core/state/notification_event_test.go +++ b/pkg/core/state/notification_event_test.go @@ -6,13 +6,13 @@ import ( "github.com/nspcc-dev/neo-go/pkg/internal/random" "github.com/nspcc-dev/neo-go/pkg/internal/testserdes" "github.com/nspcc-dev/neo-go/pkg/smartcontract" - "github.com/nspcc-dev/neo-go/pkg/vm" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" ) func TestEncodeDecodeNotificationEvent(t *testing.T) { event := &NotificationEvent{ ScriptHash: random.Uint160(), - Item: vm.NewBoolItem(true), + Item: stackitem.NewBool(true), } testserdes.EncodeDecodeBinary(t, event, new(NotificationEvent)) diff --git a/pkg/vm/emit/bigint.go b/pkg/encoding/bigint/bigint.go similarity index 84% rename from pkg/vm/emit/bigint.go rename to pkg/encoding/bigint/bigint.go index c3fc6fb18..33cdc00c2 100644 --- a/pkg/vm/emit/bigint.go +++ b/pkg/encoding/bigint/bigint.go @@ -1,4 +1,4 @@ -package emit +package bigint import ( "encoding/binary" @@ -9,9 +9,9 @@ import ( // wordSizeBytes is a size of a big.Word (uint) in bytes.` const wordSizeBytes = bits.UintSize / 8 -// BytesToInt converts data in little-endian format to +// FromBytes converts data in little-endian format to // an integer. -func BytesToInt(data []byte) *big.Int { +func FromBytes(data []byte) *big.Int { n := new(big.Int) size := len(data) if size == 0 { @@ -79,15 +79,17 @@ func getEffectiveSize(buf []byte, isNeg bool) int { return size } -// IntToBytes converts integer to a slice in little-endian format. +// ToBytes converts integer to a slice in little-endian format. // Note: NEO3 serialization differs from default C# BigInteger.ToByteArray() // when n == 0. For zero is equal to empty slice in NEO3. // https://github.com/neo-project/neo-vm/blob/master/src/neo-vm/Types/Integer.cs#L16 -func IntToBytes(n *big.Int) []byte { - return intToBytes(n, []byte{}) +func ToBytes(n *big.Int) []byte { + return ToPreallocatedBytes(n, []byte{}) } -func intToBytes(n *big.Int, data []byte) []byte { +// ToPreallocatedBytes converts integer to a slice in little-endian format using given +// byte array for conversion result. +func ToPreallocatedBytes(n *big.Int, data []byte) []byte { sign := n.Sign() if sign == 0 { return data diff --git a/pkg/vm/emit/bigint_test.go b/pkg/encoding/bigint/bigint_test.go similarity index 97% rename from pkg/vm/emit/bigint_test.go rename to pkg/encoding/bigint/bigint_test.go index 71ec2326a..7b0455c34 100644 --- a/pkg/vm/emit/bigint_test.go +++ b/pkg/encoding/bigint/bigint_test.go @@ -1,4 +1,4 @@ -package emit +package bigint import ( "math" @@ -106,19 +106,19 @@ var testCases = []struct { func TestIntToBytes(t *testing.T) { for _, tc := range testCases { - buf := IntToBytes(big.NewInt(tc.number)) + buf := ToBytes(big.NewInt(tc.number)) assert.Equal(t, tc.buf, buf, "error while converting %d", tc.number) } } func TestBytesToInt(t *testing.T) { for _, tc := range testCases { - num := BytesToInt(tc.buf) + num := FromBytes(tc.buf) assert.Equal(t, tc.number, num.Int64(), "error while converting %d", tc.number) } t.Run("empty array", func(t *testing.T) { - require.EqualValues(t, 0, BytesToInt([]byte{}).Int64()) + require.EqualValues(t, 0, FromBytes([]byte{}).Int64()) }) } @@ -131,7 +131,7 @@ func TestEquivalentRepresentations(t *testing.T) { buf = append(buf, 0xFF, 0xFF, 0xFF) } - num := BytesToInt(buf) + num := FromBytes(buf) assert.Equal(t, tc.number, num.Int64(), "error while converting %d", tc.number) } } @@ -170,16 +170,16 @@ func TestVeryBigInts(t *testing.T) { num, ok := new(big.Int).SetString(tc.numStr, 10) assert.True(t, ok) - result := BytesToInt(tc.buf) + result := FromBytes(tc.buf) assert.Equal(t, num, result, "error while converting %s from bytes", tc.numStr) - assert.Equal(t, tc.buf, IntToBytes(result), "error while converting %s to bytes", tc.numStr) + assert.Equal(t, tc.buf, ToBytes(result), "error while converting %s to bytes", tc.numStr) } for _, tc := range stdlibCases { num, ok := new(big.Int).SetString(tc.numStr, 10) assert.True(t, ok) - result := BytesToInt(util.ArrayReverse(tc.buf)) + result := FromBytes(util.ArrayReverse(tc.buf)) assert.Equal(t, num, result, "error while converting %s from bytes", tc.numStr) } } diff --git a/pkg/rpc/client/nep5.go b/pkg/rpc/client/nep5.go index 95fcf83ec..ee06343e3 100644 --- a/pkg/rpc/client/nep5.go +++ b/pkg/rpc/client/nep5.go @@ -7,6 +7,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/encoding/address" + "github.com/nspcc-dev/neo-go/pkg/encoding/bigint" "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/util" @@ -180,7 +181,7 @@ func topIntFromStack(st []smartcontract.Parameter) (int64, error) { if !ok { return 0, errors.New("invalid ByteArray item") } - decimals = emit.BytesToInt(data).Int64() + decimals = bigint.FromBytes(data).Int64() default: return 0, fmt.Errorf("invalid stack item type: %s", typ) } diff --git a/pkg/rpc/response/result/application_log.go b/pkg/rpc/response/result/application_log.go index ee59fc43d..8852cb773 100644 --- a/pkg/rpc/response/result/application_log.go +++ b/pkg/rpc/response/result/application_log.go @@ -4,7 +4,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/util" - "github.com/nspcc-dev/neo-go/pkg/vm" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" ) // ApplicationLog wrapper used for the representation of the @@ -33,8 +33,8 @@ type NotificationEvent struct { // StateEventToResultNotification converts state.NotificationEvent to // result.NotificationEvent. func StateEventToResultNotification(event state.NotificationEvent) NotificationEvent { - seen := make(map[vm.StackItem]bool) - item := event.Item.ToContractParameter(seen) + seen := make(map[stackitem.Item]bool) + item := smartcontract.ParameterFromStackItem(event.Item, seen) return NotificationEvent{ Contract: event.ScriptHash, Item: item, diff --git a/pkg/rpc/server/server.go b/pkg/rpc/server/server.go index dbed9ab8b..8006b7293 100644 --- a/pkg/rpc/server/server.go +++ b/pkg/rpc/server/server.go @@ -21,6 +21,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/encoding/address" + "github.com/nspcc-dev/neo-go/pkg/encoding/bigint" "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/network" "github.com/nspcc-dev/neo-go/pkg/rpc" @@ -29,7 +30,6 @@ import ( "github.com/nspcc-dev/neo-go/pkg/rpc/response/result" "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/util" - "github.com/nspcc-dev/neo-go/pkg/vm/emit" "github.com/pkg/errors" "go.uber.org/zap" ) @@ -625,7 +625,7 @@ func (s *Server) getDecimals(h util.Uint160, cache map[util.Uint160]int64) (int6 case smartcontract.IntegerType: d = item.Value.(int64) case smartcontract.ByteArrayType: - d = emit.BytesToInt(item.Value.([]byte)).Int64() + d = bigint.FromBytes(item.Value.([]byte)).Int64() default: return 0, response.NewInternalServerError("invalid result", errors.New("not an integer")) } diff --git a/pkg/smartcontract/convertor.go b/pkg/smartcontract/convertor.go new file mode 100644 index 000000000..680238f29 --- /dev/null +++ b/pkg/smartcontract/convertor.go @@ -0,0 +1,72 @@ +package smartcontract + +import ( + "fmt" + "math/big" + + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" +) + +// ParameterFromStackItem converts stackitem.Item to Parameter +func ParameterFromStackItem(i stackitem.Item, seen map[stackitem.Item]bool) Parameter { + switch t := i.(type) { + case stackitem.Null, *stackitem.Pointer: + return NewParameter(AnyType) + case *stackitem.BigInteger: + return Parameter{ + Type: IntegerType, + Value: i.Value().(*big.Int).Int64(), + } + case *stackitem.Bool: + return Parameter{ + Type: BoolType, + Value: i.Value().(bool), + } + case *stackitem.ByteArray: + return Parameter{ + Type: ByteArrayType, + Value: i.Value().([]byte), + } + case *stackitem.Interop: + return Parameter{ + Type: InteropInterfaceType, + Value: nil, + } + case *stackitem.Buffer: + return Parameter{ + Type: ByteArrayType, + Value: i.Value().([]byte), + } + case *stackitem.Struct, *stackitem.Array: + var value []Parameter + + if !seen[i] { + seen[i] = true + for _, stackItem := range i.Value().([]stackitem.Item) { + parameter := ParameterFromStackItem(stackItem, seen) + value = append(value, parameter) + } + } + return Parameter{ + Type: ArrayType, + Value: value, + } + case *stackitem.Map: + value := make([]ParameterPair, 0) + if !seen[i] { + seen[i] = true + for _, element := range i.Value().([]stackitem.MapElement) { + value = append(value, ParameterPair{ + Key: ParameterFromStackItem(element.Key, seen), + Value: ParameterFromStackItem(element.Value, seen), + }) + } + } + return Parameter{ + Type: MapType, + Value: value, + } + default: + panic(fmt.Sprintf("unknown stack item type: %v", t)) + } +} diff --git a/pkg/smartcontract/convertor_test.go b/pkg/smartcontract/convertor_test.go new file mode 100644 index 000000000..bd1585609 --- /dev/null +++ b/pkg/smartcontract/convertor_test.go @@ -0,0 +1,79 @@ +package smartcontract + +import ( + "math/big" + "testing" + + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" + "github.com/stretchr/testify/assert" +) + +var toContractParameterTestCases = []struct { + input stackitem.Item + result Parameter +}{ + { + input: stackitem.NewStruct([]stackitem.Item{ + stackitem.NewBigInteger(big.NewInt(1)), + stackitem.NewBool(true), + }), + result: Parameter{Type: ArrayType, Value: []Parameter{ + {Type: IntegerType, Value: int64(1)}, + {Type: BoolType, Value: true}, + }}, + }, + { + input: stackitem.NewBool(false), + result: Parameter{Type: BoolType, Value: false}, + }, + { + input: stackitem.NewByteArray([]byte{0x01, 0x02, 0x03}), + result: Parameter{Type: ByteArrayType, Value: []byte{0x01, 0x02, 0x03}}, + }, + { + input: stackitem.NewBuffer([]byte{0x01, 0x02, 0x03}), + result: Parameter{Type: ByteArrayType, Value: []byte{0x01, 0x02, 0x03}}, + }, + { + input: stackitem.NewArray([]stackitem.Item{stackitem.NewBigInteger(big.NewInt(2)), stackitem.NewBool(true)}), + result: Parameter{Type: ArrayType, Value: []Parameter{ + {Type: IntegerType, Value: int64(2)}, + {Type: BoolType, Value: true}, + }}, + }, + { + input: stackitem.NewInterop(nil), + result: Parameter{Type: InteropInterfaceType, Value: nil}, + }, + { + input: stackitem.NewMapWithValue([]stackitem.MapElement{ + {Key: stackitem.NewBigInteger(big.NewInt(1)), Value: stackitem.NewBool(true)}, + {Key: stackitem.NewByteArray([]byte("qwerty")), Value: stackitem.NewBigInteger(big.NewInt(3))}, + {Key: stackitem.NewBool(true), Value: stackitem.NewBool(false)}, + }), + result: Parameter{ + Type: MapType, + Value: []ParameterPair{ + { + Key: Parameter{Type: IntegerType, Value: int64(1)}, + Value: Parameter{Type: BoolType, Value: true}, + }, { + Key: Parameter{Type: ByteArrayType, Value: []byte("qwerty")}, + Value: Parameter{Type: IntegerType, Value: int64(3)}, + }, { + + Key: Parameter{Type: BoolType, Value: true}, + Value: Parameter{Type: BoolType, Value: false}, + }, + }, + }, + }, +} + +func TestToContractParameter(t *testing.T) { + for _, tc := range toContractParameterTestCases { + seen := make(map[stackitem.Item]bool) + res := ParameterFromStackItem(tc.input, seen) + assert.Equal(t, res, tc.result) + } +} diff --git a/pkg/vm/cli/cli.go b/pkg/vm/cli/cli.go index 0e3bee234..433917335 100644 --- a/pkg/vm/cli/cli.go +++ b/pkg/vm/cli/cli.go @@ -13,6 +13,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/compiler" "github.com/nspcc-dev/neo-go/pkg/vm" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "gopkg.in/abiosoft/ishell.v2" ) @@ -276,7 +277,7 @@ func handleRun(c *ishell.Context) { if len(c.Args) != 0 { var ( method []byte - params []vm.StackItem + params []stackitem.Item err error ) method = []byte(c.Args[0]) @@ -406,8 +407,8 @@ func isMethodArg(s string) bool { return len(strings.Split(s, ":")) == 1 } -func parseArgs(args []string) ([]vm.StackItem, error) { - items := make([]vm.StackItem, len(args)) +func parseArgs(args []string) ([]stackitem.Item, error) { + items := make([]stackitem.Item, len(args)) for i, arg := range args { var typ, value string typeAndVal := strings.Split(arg, ":") @@ -428,9 +429,9 @@ func parseArgs(args []string) ([]vm.StackItem, error) { switch typ { case boolType: if value == boolFalse { - items[i] = vm.NewBoolItem(false) + items[i] = stackitem.NewBool(false) } else if value == boolTrue { - items[i] = vm.NewBoolItem(true) + items[i] = stackitem.NewBool(true) } else { return nil, errors.New("failed to parse bool parameter") } @@ -439,9 +440,9 @@ func parseArgs(args []string) ([]vm.StackItem, error) { if err != nil { return nil, err } - items[i] = vm.NewBigIntegerItem(big.NewInt(val)) + items[i] = stackitem.NewBigInteger(big.NewInt(val)) case stringType: - items[i] = vm.NewByteArrayItem([]byte(value)) + items[i] = stackitem.NewByteArray([]byte(value)) } } diff --git a/pkg/vm/context.go b/pkg/vm/context.go index 1cecbc4f9..a32942893 100644 --- a/pkg/vm/context.go +++ b/pkg/vm/context.go @@ -6,9 +6,9 @@ import ( "math/big" "github.com/nspcc-dev/neo-go/pkg/crypto/hash" - "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm/opcode" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" ) // Context represents the current execution context of the VM. @@ -170,50 +170,42 @@ func (c *Context) ScriptHash() util.Uint160 { return c.scriptHash } -// Value implements StackItem interface. +// Value implements stackitem.Item interface. func (c *Context) Value() interface{} { return c } -// Dup implements StackItem interface. -func (c *Context) Dup() StackItem { +// Dup implements stackitem.Item interface. +func (c *Context) Dup() stackitem.Item { return c } -// Bool implements StackItem interface. +// Bool implements stackitem.Item interface. func (c *Context) Bool() bool { panic("can't convert Context to Bool") } -// TryBytes implements StackItem interface. +// TryBytes implements stackitem.Item interface. func (c *Context) TryBytes() ([]byte, error) { return nil, errors.New("can't convert Context to ByteArray") } -// TryInteger implements StackItem interface. +// TryInteger implements stackitem.Item interface. func (c *Context) TryInteger() (*big.Int, error) { return nil, errors.New("can't convert Context to Integer") } -// Type implements StackItem interface. -func (c *Context) Type() StackItemType { panic("Context cannot appear on evaluation stack") } +// Type implements stackitem.Item interface. +func (c *Context) Type() stackitem.Type { panic("Context cannot appear on evaluation stack") } -// Convert implements StackItem interface. -func (c *Context) Convert(_ StackItemType) (StackItem, error) { +// Convert implements stackitem.Item interface. +func (c *Context) Convert(_ stackitem.Type) (stackitem.Item, error) { panic("Context cannot be converted to anything") } -// Equals implements StackItem interface. -func (c *Context) Equals(s StackItem) bool { +// Equals implements stackitem.Item interface. +func (c *Context) Equals(s stackitem.Item) bool { return c == s } -// ToContractParameter implements StackItem interface. -func (c *Context) ToContractParameter(map[StackItem]bool) smartcontract.Parameter { - return smartcontract.Parameter{ - Type: smartcontract.StringType, - Value: c.String(), - } -} - func (c *Context) atBreakPoint() bool { for _, n := range c.breakPoints { if n == c.ip { diff --git a/pkg/vm/contract_checks.go b/pkg/vm/contract_checks.go index e4acc3766..9622d2781 100644 --- a/pkg/vm/contract_checks.go +++ b/pkg/vm/contract_checks.go @@ -3,6 +3,7 @@ package vm import ( "encoding/binary" + "github.com/nspcc-dev/neo-go/pkg/encoding/bigint" "github.com/nspcc-dev/neo-go/pkg/vm/emit" "github.com/nspcc-dev/neo-go/pkg/vm/opcode" ) @@ -19,7 +20,7 @@ func getNumOfThingsFromInstr(instr opcode.Opcode, param []byte) (int, bool) { case opcode.PUSH1 <= instr && instr <= opcode.PUSH16: nthings = int(instr-opcode.PUSH1) + 1 case instr <= opcode.PUSHINT256: - n := emit.BytesToInt(param) + n := bigint.FromBytes(param) if !n.IsInt64() || n.Int64() > MaxArraySize { return 0, false } diff --git a/pkg/vm/emit/emit.go b/pkg/vm/emit/emit.go index b962efb99..0d3fbd38a 100644 --- a/pkg/vm/emit/emit.go +++ b/pkg/vm/emit/emit.go @@ -8,9 +8,11 @@ import ( "math/big" "math/bits" + "github.com/nspcc-dev/neo-go/pkg/encoding/bigint" "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm/opcode" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" ) // Instruction emits a VM Instruction with data to the given buffer. @@ -31,7 +33,7 @@ func Bool(w *io.BinWriter, ok bool) { return } Opcode(w, opcode.PUSHF) - Instruction(w, opcode.CONVERT, []byte{0x20}) // 0x20 for Boolean type + Instruction(w, opcode.CONVERT, []byte{byte(stackitem.BooleanT)}) } func padRight(s int, buf []byte) []byte { @@ -54,7 +56,7 @@ func Int(w *io.BinWriter, i int64) { val := opcode.Opcode(int(opcode.PUSH1) - 1 + int(i)) Opcode(w, val) default: - buf := intToBytes(big.NewInt(i), make([]byte, 0, 32)) + buf := bigint.ToPreallocatedBytes(big.NewInt(i), make([]byte, 0, 32)) // l != 0 becase of switch padSize := byte(8 - bits.LeadingZeros8(byte(len(buf)-1))) Opcode(w, opcode.PUSHINT8+opcode.Opcode(padSize)) diff --git a/pkg/vm/emit/emit_test.go b/pkg/vm/emit/emit_test.go index 8d76f4170..bb0024fb9 100644 --- a/pkg/vm/emit/emit_test.go +++ b/pkg/vm/emit/emit_test.go @@ -5,6 +5,7 @@ import ( "errors" "testing" + "github.com/nspcc-dev/neo-go/pkg/encoding/bigint" "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/vm/opcode" "github.com/stretchr/testify/assert" @@ -49,7 +50,7 @@ func TestEmitInt(t *testing.T) { result := buf.Bytes() assert.Equal(t, 3, len(result)) assert.EqualValues(t, opcode.PUSHINT16, result[0]) - assert.EqualValues(t, 300, BytesToInt(result[1:]).Int64()) + assert.EqualValues(t, 300, bigint.FromBytes(result[1:]).Int64()) }) t.Run("3-byte int", func(t *testing.T) { @@ -58,7 +59,7 @@ func TestEmitInt(t *testing.T) { result := buf.Bytes() assert.Equal(t, 5, len(result)) assert.EqualValues(t, opcode.PUSHINT32, result[0]) - assert.EqualValues(t, 1<<20, BytesToInt(result[1:]).Int64()) + assert.EqualValues(t, 1<<20, bigint.FromBytes(result[1:]).Int64()) }) t.Run("4-byte int", func(t *testing.T) { @@ -67,7 +68,7 @@ func TestEmitInt(t *testing.T) { result := buf.Bytes() assert.Equal(t, 5, len(result)) assert.EqualValues(t, opcode.PUSHINT32, result[0]) - assert.EqualValues(t, 1<<28, BytesToInt(result[1:]).Int64()) + assert.EqualValues(t, 1<<28, bigint.FromBytes(result[1:]).Int64()) }) t.Run("negative 3-byte int with padding", func(t *testing.T) { @@ -77,7 +78,7 @@ func TestEmitInt(t *testing.T) { result := buf.Bytes() assert.Equal(t, 5, len(result)) assert.EqualValues(t, opcode.PUSHINT32, result[0]) - assert.EqualValues(t, num, BytesToInt(result[1:]).Int64()) + assert.EqualValues(t, num, bigint.FromBytes(result[1:]).Int64()) }) } diff --git a/pkg/vm/interop.go b/pkg/vm/interop.go index 930892f27..8003d6a76 100644 --- a/pkg/vm/interop.go +++ b/pkg/vm/interop.go @@ -6,6 +6,7 @@ import ( "sort" "github.com/nspcc-dev/neo-go/pkg/vm/emit" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" ) // InteropFunc allows to hook into the VM. @@ -87,7 +88,7 @@ func runtimeNotify(vm *VM) error { // RuntimeSerialize handles syscalls System.Runtime.Serialize and Neo.Runtime.Serialize. func RuntimeSerialize(vm *VM) error { item := vm.Estack().Pop() - data, err := SerializeItem(item.value) + data, err := stackitem.SerializeItem(item.value) if err != nil { return err } else if len(data) > MaxItemSize { @@ -103,7 +104,7 @@ func RuntimeSerialize(vm *VM) error { func RuntimeDeserialize(vm *VM) error { data := vm.Estack().Pop().Bytes() - item, err := DeserializeItem(data) + item, err := stackitem.DeserializeItem(data) if err != nil { return err } @@ -124,7 +125,7 @@ func init() { func EnumeratorCreate(v *VM) error { data := v.Estack().Pop().Array() v.Estack().Push(&Element{ - value: NewInteropItem(&arrayWrapper{ + value: stackitem.NewInterop(&arrayWrapper{ index: -1, value: data, }), @@ -136,7 +137,7 @@ func EnumeratorCreate(v *VM) error { // EnumeratorNext handles syscall Neo.Enumerator.Next. func EnumeratorNext(v *VM) error { iop := v.Estack().Pop().Interop() - arr := iop.value.(enumerator) + arr := iop.Value().(enumerator) v.Estack().PushVal(arr.Next()) return nil @@ -145,7 +146,7 @@ func EnumeratorNext(v *VM) error { // EnumeratorValue handles syscall Neo.Enumerator.Value. func EnumeratorValue(v *VM) error { iop := v.Estack().Pop().Interop() - arr := iop.value.(enumerator) + arr := iop.Value().(enumerator) v.Estack().Push(&Element{value: arr.Value()}) return nil @@ -154,12 +155,12 @@ func EnumeratorValue(v *VM) error { // EnumeratorConcat handles syscall Neo.Enumerator.Concat. func EnumeratorConcat(v *VM) error { iop1 := v.Estack().Pop().Interop() - arr1 := iop1.value.(enumerator) + arr1 := iop1.Value().(enumerator) iop2 := v.Estack().Pop().Interop() - arr2 := iop2.value.(enumerator) + arr2 := iop2.Value().(enumerator) v.Estack().Push(&Element{ - value: NewInteropItem(&concatEnum{ + value: stackitem.NewInterop(&concatEnum{ current: arr1, second: arr2, }), @@ -171,14 +172,14 @@ func EnumeratorConcat(v *VM) error { // IteratorCreate handles syscall Neo.Iterator.Create. func IteratorCreate(v *VM) error { data := v.Estack().Pop() - var item StackItem + var item stackitem.Item switch t := data.value.(type) { - case *ArrayItem, *StructItem: - item = NewInteropItem(&arrayWrapper{ + case *stackitem.Array, *stackitem.Struct: + item = stackitem.NewInterop(&arrayWrapper{ index: -1, - value: t.Value().([]StackItem), + value: t.Value().([]stackitem.Item), }) - case *MapItem: + case *stackitem.Map: item = NewMapIterator(t) default: return errors.New("non-iterable type") @@ -189,21 +190,21 @@ func IteratorCreate(v *VM) error { } // NewMapIterator returns new interop item containing iterator over m. -func NewMapIterator(m *MapItem) *InteropItem { - return NewInteropItem(&mapWrapper{ +func NewMapIterator(m *stackitem.Map) *stackitem.Interop { + return stackitem.NewInterop(&mapWrapper{ index: -1, - m: m.value, + m: m.Value().([]stackitem.MapElement), }) } // IteratorConcat handles syscall Neo.Iterator.Concat. func IteratorConcat(v *VM) error { iop1 := v.Estack().Pop().Interop() - iter1 := iop1.value.(iterator) + iter1 := iop1.Value().(iterator) iop2 := v.Estack().Pop().Interop() - iter2 := iop2.value.(iterator) + iter2 := iop2.Value().(iterator) - v.Estack().Push(&Element{value: NewInteropItem( + v.Estack().Push(&Element{value: stackitem.NewInterop( &concatIter{ current: iter1, second: iter2, @@ -216,7 +217,7 @@ func IteratorConcat(v *VM) error { // IteratorKey handles syscall Neo.Iterator.Key. func IteratorKey(v *VM) error { iop := v.estack.Pop().Interop() - iter := iop.value.(iterator) + iter := iop.Value().(iterator) v.Estack().Push(&Element{value: iter.Key()}) return nil @@ -225,8 +226,8 @@ func IteratorKey(v *VM) error { // IteratorKeys handles syscall Neo.Iterator.Keys. func IteratorKeys(v *VM) error { iop := v.estack.Pop().Interop() - iter := iop.value.(iterator) - v.Estack().Push(&Element{value: NewInteropItem( + iter := iop.Value().(iterator) + v.Estack().Push(&Element{value: stackitem.NewInterop( &keysWrapper{iter}, )}) @@ -236,8 +237,8 @@ func IteratorKeys(v *VM) error { // IteratorValues handles syscall Neo.Iterator.Values. func IteratorValues(v *VM) error { iop := v.estack.Pop().Interop() - iter := iop.value.(iterator) - v.Estack().Push(&Element{value: NewInteropItem( + iter := iop.Value().(iterator) + v.Estack().Push(&Element{value: stackitem.NewInterop( &valuesWrapper{iter}, )}) diff --git a/pkg/vm/interop_iterators.go b/pkg/vm/interop_iterators.go index 8ecf69b83..473acf65c 100644 --- a/pkg/vm/interop_iterators.go +++ b/pkg/vm/interop_iterators.go @@ -1,14 +1,18 @@ package vm +import ( + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" +) + type ( enumerator interface { Next() bool - Value() StackItem + Value() stackitem.Item } arrayWrapper struct { index int - value []StackItem + value []stackitem.Item } concatEnum struct { @@ -20,12 +24,12 @@ type ( type ( iterator interface { enumerator - Key() StackItem + Key() stackitem.Item } mapWrapper struct { index int - m []MapElement + m []stackitem.MapElement } concatIter struct { @@ -51,12 +55,12 @@ func (a *arrayWrapper) Next() bool { return false } -func (a *arrayWrapper) Value() StackItem { +func (a *arrayWrapper) Value() stackitem.Item { return a.value[a.index] } -func (a *arrayWrapper) Key() StackItem { - return makeStackItem(a.index) +func (a *arrayWrapper) Key() stackitem.Item { + return stackitem.Make(a.index) } func (c *concatEnum) Next() bool { @@ -68,7 +72,7 @@ func (c *concatEnum) Next() bool { return c.current.Next() } -func (c *concatEnum) Value() StackItem { +func (c *concatEnum) Value() stackitem.Item { return c.current.Value() } @@ -81,11 +85,11 @@ func (i *concatIter) Next() bool { return i.second.Next() } -func (i *concatIter) Value() StackItem { +func (i *concatIter) Value() stackitem.Item { return i.current.Value() } -func (i *concatIter) Key() StackItem { +func (i *concatIter) Key() stackitem.Item { return i.current.Key() } @@ -98,11 +102,11 @@ func (m *mapWrapper) Next() bool { return false } -func (m *mapWrapper) Value() StackItem { +func (m *mapWrapper) Value() stackitem.Item { return m.m[m.index].Value } -func (m *mapWrapper) Key() StackItem { +func (m *mapWrapper) Key() stackitem.Item { return m.m[m.index].Key } @@ -110,7 +114,7 @@ func (e *keysWrapper) Next() bool { return e.iter.Next() } -func (e *keysWrapper) Value() StackItem { +func (e *keysWrapper) Value() stackitem.Item { return e.iter.Key() } @@ -118,6 +122,6 @@ func (e *valuesWrapper) Next() bool { return e.iter.Next() } -func (e *valuesWrapper) Value() StackItem { +func (e *valuesWrapper) Value() stackitem.Item { return e.iter.Value() } diff --git a/pkg/vm/json_test.go b/pkg/vm/json_test.go index 00eb106a2..ee2823d49 100644 --- a/pkg/vm/json_test.go +++ b/pkg/vm/json_test.go @@ -16,8 +16,9 @@ import ( "strings" "testing" - "github.com/nspcc-dev/neo-go/pkg/vm/emit" + "github.com/nspcc-dev/neo-go/pkg/encoding/bigint" "github.com/nspcc-dev/neo-go/pkg/vm/opcode" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/stretchr/testify/require" ) @@ -112,7 +113,7 @@ func TestUT(t *testing.T) { func getTestingInterop(id uint32) *InteropFuncPrice { if id == binary.LittleEndian.Uint32([]byte{0x77, 0x77, 0x77, 0x77}) { return &InteropFuncPrice{InteropFunc(func(v *VM) error { - v.estack.PushVal(&InteropItem{new(int)}) + v.estack.PushVal(stackitem.NewInterop(new(int))) return nil }), 0} } @@ -174,17 +175,17 @@ func testFile(t *testing.T, filename string) { }) } -func compareItems(t *testing.T, a, b StackItem) { +func compareItems(t *testing.T, a, b stackitem.Item) { switch si := a.(type) { - case *BigIntegerItem: - val := si.value.Int64() + case *stackitem.BigInteger: + val := si.Value().(*big.Int).Int64() switch ac := b.(type) { - case *BigIntegerItem: - require.Equal(t, val, ac.value.Int64()) - case *ByteArrayItem: - require.Equal(t, val, emit.BytesToInt(ac.value).Int64()) - case *BoolItem: - if ac.value { + case *stackitem.BigInteger: + require.Equal(t, val, ac.Value().(*big.Int).Int64()) + case *stackitem.ByteArray: + require.Equal(t, val, bigint.FromBytes(ac.Value().([]byte)).Int64()) + case *stackitem.Bool: + if ac.Value().(bool) { require.Equal(t, val, int64(1)) } else { require.Equal(t, val, int64(0)) @@ -192,17 +193,17 @@ func compareItems(t *testing.T, a, b StackItem) { default: require.Fail(t, "wrong type") } - case *PointerItem: - p, ok := b.(*PointerItem) + case *stackitem.Pointer: + p, ok := b.(*stackitem.Pointer) require.True(t, ok) - require.Equal(t, si.pos, p.pos) // there no script in test files + require.Equal(t, si.Position(), p.Position()) // there no script in test files default: require.Equal(t, a, b) } } func compareStacks(t *testing.T, expected []vmUTStackItem, actual *Stack) { - compareItemArrays(t, expected, actual.Len(), func(i int) StackItem { return actual.Peek(i).Item() }) + compareItemArrays(t, expected, actual.Len(), func(i int) stackitem.Item { return actual.Peek(i).Item() }) } func compareSlots(t *testing.T, expected []vmUTStackItem, actual *Slot) { @@ -213,7 +214,7 @@ func compareSlots(t *testing.T, expected []vmUTStackItem, actual *Slot) { compareItemArrays(t, expected, actual.Size(), actual.Get) } -func compareItemArrays(t *testing.T, expected []vmUTStackItem, n int, getItem func(i int) StackItem) { +func compareItemArrays(t *testing.T, expected []vmUTStackItem, n int, getItem func(i int) stackitem.Item) { if expected == nil { return } @@ -224,57 +225,47 @@ func compareItemArrays(t *testing.T, expected []vmUTStackItem, n int, getItem fu require.NotNil(t, it) if item.Type == typeInterop { - require.IsType(t, (*InteropItem)(nil), it) + require.IsType(t, (*stackitem.Interop)(nil), it) continue } compareItems(t, item.toStackItem(), it) } } -func (v *vmUTStackItem) toStackItem() StackItem { +func (v *vmUTStackItem) toStackItem() stackitem.Item { switch v.Type.toLower() { case typeArray: items := v.Value.([]vmUTStackItem) - result := make([]StackItem, len(items)) + result := make([]stackitem.Item, len(items)) for i := range items { result[i] = items[i].toStackItem() } - return &ArrayItem{ - value: result, - } + return stackitem.NewArray(result) case typeString: panic("not implemented") case typeMap: - return v.Value.(*MapItem) + return v.Value.(*stackitem.Map) case typeInterop: panic("not implemented") case typeByteString: - return &ByteArrayItem{ - v.Value.([]byte), - } + return stackitem.NewByteArray(v.Value.([]byte)) case typeBuffer: - return &BufferItem{v.Value.([]byte)} + return stackitem.NewBuffer(v.Value.([]byte)) case typePointer: - return NewPointerItem(v.Value.(int), nil) + return stackitem.NewPointer(v.Value.(int), nil) case typeNull: - return NullItem{} + return stackitem.Null{} case typeBoolean: - return &BoolItem{ - v.Value.(bool), - } + return stackitem.NewBool(v.Value.(bool)) case typeInteger: - return &BigIntegerItem{ - value: v.Value.(*big.Int), - } + return stackitem.NewBigInteger(v.Value.(*big.Int)) case typeStruct: items := v.Value.([]vmUTStackItem) - result := make([]StackItem, len(items)) + result := make([]stackitem.Item, len(items)) for i := range items { result[i] = items[i].toStackItem() } - return &StructItem{ - value: result, - } + return stackitem.NewStruct(result) default: panic(fmt.Sprintf("invalid type: %s", v.Type)) } @@ -303,10 +294,10 @@ func execStep(t *testing.T, v *VM, step vmUTStep) { } } -func jsonStringToInteger(s string) StackItem { +func jsonStringToInteger(s string) stackitem.Item { b, err := decodeHex(s) if err == nil { - return NewBigIntegerItem(new(big.Int).SetBytes(b)) + return stackitem.NewBigInteger(new(big.Int).SetBytes(b)) } return nil } @@ -418,7 +409,7 @@ func (v *vmUTStackItem) UnmarshalJSON(data []byte) error { return fmt.Errorf("invalid map start") } - result := NewMapItem() + result := stackitem.NewMap() for { tok, err := d.Token() if err != nil { @@ -438,7 +429,7 @@ func (v *vmUTStackItem) UnmarshalJSON(data []byte) error { item := jsonStringToInteger(key) if item == nil { - return fmt.Errorf("can't unmarshal StackItem %s", key) + return fmt.Errorf("can't unmarshal Item %s", key) } result.Add(item, it.toStackItem()) } diff --git a/pkg/vm/ref_counter.go b/pkg/vm/ref_counter.go index c89f07983..0d176cadf 100644 --- a/pkg/vm/ref_counter.go +++ b/pkg/vm/ref_counter.go @@ -1,46 +1,50 @@ package vm +import ( + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" +) + // refCounter represents reference counter for the VM. type refCounter struct { - items map[StackItem]int + items map[stackitem.Item]int size int } func newRefCounter() *refCounter { return &refCounter{ - items: make(map[StackItem]int), + items: make(map[stackitem.Item]int), } } // Add adds an item to the reference counter. -func (r *refCounter) Add(item StackItem) { +func (r *refCounter) Add(item stackitem.Item) { r.size++ switch item.(type) { - case *ArrayItem, *StructItem, *MapItem: + case *stackitem.Array, *stackitem.Struct, *stackitem.Map: if r.items[item]++; r.items[item] > 1 { return } switch t := item.(type) { - case *ArrayItem, *StructItem: - for _, it := range item.Value().([]StackItem) { + case *stackitem.Array, *stackitem.Struct: + for _, it := range item.Value().([]stackitem.Item) { r.Add(it) } - case *MapItem: - for i := range t.value { - r.Add(t.value[i].Value) + case *stackitem.Map: + for i := range t.Value().([]stackitem.MapElement) { + r.Add(t.Value().([]stackitem.MapElement)[i].Value) } } } } // Remove removes item from the reference counter. -func (r *refCounter) Remove(item StackItem) { +func (r *refCounter) Remove(item stackitem.Item) { r.size-- switch item.(type) { - case *ArrayItem, *StructItem, *MapItem: + case *stackitem.Array, *stackitem.Struct, *stackitem.Map: if r.items[item] > 1 { r.items[item]-- return @@ -49,13 +53,13 @@ func (r *refCounter) Remove(item StackItem) { delete(r.items, item) switch t := item.(type) { - case *ArrayItem, *StructItem: - for _, it := range item.Value().([]StackItem) { + case *stackitem.Array, *stackitem.Struct: + for _, it := range item.Value().([]stackitem.Item) { r.Remove(it) } - case *MapItem: - for i := range t.value { - r.Remove(t.value[i].Value) + case *stackitem.Map: + for i := range t.Value().([]stackitem.MapElement) { + r.Remove(t.Value().([]stackitem.MapElement)[i].Value) } } } diff --git a/pkg/vm/ref_counter_test.go b/pkg/vm/ref_counter_test.go index 731a5c46a..b50390609 100644 --- a/pkg/vm/ref_counter_test.go +++ b/pkg/vm/ref_counter_test.go @@ -3,6 +3,7 @@ package vm import ( "testing" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/stretchr/testify/require" ) @@ -11,13 +12,13 @@ func TestRefCounter_Add(t *testing.T) { require.Equal(t, 0, r.size) - r.Add(NullItem{}) + r.Add(stackitem.Null{}) require.Equal(t, 1, r.size) - r.Add(NullItem{}) + r.Add(stackitem.Null{}) require.Equal(t, 2, r.size) // count scalar items twice - arr := NewArrayItem([]StackItem{NewByteArrayItem([]byte{1}), NewBoolItem(false)}) + arr := stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray([]byte{1}), stackitem.NewBool(false)}) r.Add(arr) require.Equal(t, 5, r.size) // array + 2 elements diff --git a/pkg/vm/serialization.go b/pkg/vm/serialization.go deleted file mode 100644 index 52bd38445..000000000 --- a/pkg/vm/serialization.go +++ /dev/null @@ -1,190 +0,0 @@ -package vm - -import ( - "errors" - - "github.com/nspcc-dev/neo-go/pkg/io" - "github.com/nspcc-dev/neo-go/pkg/vm/emit" -) - -// StackItemType represents type of the stack item. -type StackItemType byte - -// This block defines all known stack item types. -const ( - AnyT StackItemType = 0x00 - PointerT StackItemType = 0x10 - BooleanT StackItemType = 0x20 - IntegerT StackItemType = 0x21 - ByteArrayT StackItemType = 0x28 - BufferT StackItemType = 0x30 - ArrayT StackItemType = 0x40 - StructT StackItemType = 0x41 - MapT StackItemType = 0x48 - InteropT StackItemType = 0x60 -) - -// String implements fmt.Stringer interface. -func (t StackItemType) String() string { - switch t { - case AnyT: - return "Any" - case PointerT: - return "Pointer" - case BooleanT: - return "Boolean" - case IntegerT: - return "Integer" - case ByteArrayT: - return "ByteArray" - case BufferT: - return "Buffer" - case ArrayT: - return "Array" - case StructT: - return "Struct" - case MapT: - return "Map" - case InteropT: - return "Interop" - default: - return "INVALID" - } -} - -// IsValid checks if s is a well defined stack item type. -func (t StackItemType) IsValid() bool { - switch t { - case AnyT, PointerT, BooleanT, IntegerT, ByteArrayT, BufferT, ArrayT, StructT, MapT, InteropT: - return true - default: - return false - } -} - -// SerializeItem encodes given StackItem into the byte slice. -func SerializeItem(item StackItem) ([]byte, error) { - w := io.NewBufBinWriter() - EncodeBinaryStackItem(item, w.BinWriter) - if w.Err != nil { - return nil, w.Err - } - return w.Bytes(), nil -} - -// EncodeBinaryStackItem encodes given StackItem into the given BinWriter. It's -// similar to io.Serializable's EncodeBinary, but works with StackItem -// interface. -func EncodeBinaryStackItem(item StackItem, w *io.BinWriter) { - serializeItemTo(item, w, make(map[StackItem]bool)) -} - -func serializeItemTo(item StackItem, w *io.BinWriter, seen map[StackItem]bool) { - if seen[item] { - w.Err = errors.New("recursive structures can't be serialized") - return - } - - switch t := item.(type) { - case *ByteArrayItem: - w.WriteBytes([]byte{byte(ByteArrayT)}) - w.WriteVarBytes(t.value) - case *BufferItem: - w.WriteBytes([]byte{byte(BufferT)}) - w.WriteVarBytes(t.value) - case *BoolItem: - w.WriteBytes([]byte{byte(BooleanT)}) - w.WriteBool(t.value) - case *BigIntegerItem: - w.WriteBytes([]byte{byte(IntegerT)}) - w.WriteVarBytes(emit.IntToBytes(t.value)) - case *InteropItem: - w.Err = errors.New("interop item can't be serialized") - case *ArrayItem, *StructItem: - seen[item] = true - - _, isArray := t.(*ArrayItem) - if isArray { - w.WriteBytes([]byte{byte(ArrayT)}) - } else { - w.WriteBytes([]byte{byte(StructT)}) - } - - arr := t.Value().([]StackItem) - w.WriteVarUint(uint64(len(arr))) - for i := range arr { - serializeItemTo(arr[i], w, seen) - } - case *MapItem: - seen[item] = true - - w.WriteBytes([]byte{byte(MapT)}) - w.WriteVarUint(uint64(len(t.value))) - for i := range t.value { - serializeItemTo(t.value[i].Key, w, seen) - serializeItemTo(t.value[i].Value, w, seen) - } - } -} - -// DeserializeItem decodes StackItem from the given byte slice. -func DeserializeItem(data []byte) (StackItem, error) { - r := io.NewBinReaderFromBuf(data) - item := DecodeBinaryStackItem(r) - if r.Err != nil { - return nil, r.Err - } - return item, nil -} - -// DecodeBinaryStackItem decodes previously serialized StackItem from the given -// reader. It's similar to the io.Serializable's DecodeBinary(), but implemented -// as a function because StackItem itself is an interface. Caveat: always check -// reader's error value before using the returned StackItem. -func DecodeBinaryStackItem(r *io.BinReader) StackItem { - var t = r.ReadB() - if r.Err != nil { - return nil - } - - switch StackItemType(t) { - case ByteArrayT: - data := r.ReadVarBytes() - return NewByteArrayItem(data) - case BooleanT: - var b = r.ReadBool() - return NewBoolItem(b) - case IntegerT: - data := r.ReadVarBytes() - num := emit.BytesToInt(data) - return &BigIntegerItem{ - value: num, - } - case ArrayT, StructT: - size := int(r.ReadVarUint()) - arr := make([]StackItem, size) - for i := 0; i < size; i++ { - arr[i] = DecodeBinaryStackItem(r) - } - - if StackItemType(t) == ArrayT { - return &ArrayItem{value: arr} - } - return &StructItem{value: arr} - case MapT: - size := int(r.ReadVarUint()) - m := NewMapItem() - for i := 0; i < size; i++ { - key := DecodeBinaryStackItem(r) - value := DecodeBinaryStackItem(r) - if r.Err != nil { - break - } - m.Add(key, value) - } - return m - default: - r.Err = errors.New("unknown type") - return nil - } -} diff --git a/pkg/vm/slot.go b/pkg/vm/slot.go index 279cb3f39..1f4107ee2 100644 --- a/pkg/vm/slot.go +++ b/pkg/vm/slot.go @@ -1,15 +1,19 @@ package vm +import ( + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" +) + // Slot is a fixed-size slice of stack items. type Slot struct { - storage []StackItem + storage []stackitem.Item refs *refCounter } // newSlot returns new slot of n items. func newSlot(n int, refs *refCounter) *Slot { return &Slot{ - storage: make([]StackItem, n), + storage: make([]stackitem.Item, n), refs: refs, } } @@ -19,7 +23,7 @@ func (v *VM) newSlot(n int) *Slot { } // Set sets i-th storage slot. -func (s *Slot) Set(i int, item StackItem) { +func (s *Slot) Set(i int, item stackitem.Item) { if s.storage[i] == item { return } @@ -32,11 +36,11 @@ func (s *Slot) Set(i int, item StackItem) { } // Get returns item contained in i-th slot. -func (s *Slot) Get(i int) StackItem { +func (s *Slot) Get(i int) stackitem.Item { if item := s.storage[i]; item != nil { return item } - return NullItem{} + return stackitem.Null{} } // Size returns slot size. diff --git a/pkg/vm/slot_test.go b/pkg/vm/slot_test.go index 65781b7af..19186cdee 100644 --- a/pkg/vm/slot_test.go +++ b/pkg/vm/slot_test.go @@ -4,6 +4,7 @@ import ( "math/big" "testing" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/stretchr/testify/require" ) @@ -12,10 +13,10 @@ func TestSlot_Get(t *testing.T) { require.NotNil(t, s) require.Equal(t, 3, s.Size()) - // NullItem is the default + // Null is the default item := s.Get(2) - require.Equal(t, NullItem{}, item) + require.Equal(t, stackitem.Null{}, item) - s.Set(1, NewBigIntegerItem(big.NewInt(42))) - require.Equal(t, NewBigIntegerItem(big.NewInt(42)), s.Get(1)) + s.Set(1, stackitem.NewBigInteger(big.NewInt(42))) + require.Equal(t, stackitem.NewBigInteger(big.NewInt(42)), s.Get(1)) } diff --git a/pkg/vm/stack.go b/pkg/vm/stack.go index 19e9e9e8f..44e9d6d94 100644 --- a/pkg/vm/stack.go +++ b/pkg/vm/stack.go @@ -7,6 +7,7 @@ import ( "math/big" "github.com/nspcc-dev/neo-go/pkg/smartcontract" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" ) // Stack implementation for the neo-go virtual machine. The stack implements @@ -29,9 +30,9 @@ import ( // [ 0 ] // Element represents an element in the double linked list (the stack), -// which will hold the underlying StackItem. +// which will hold the underlying stackitem.Item. type Element struct { - value StackItem + value stackitem.Item next, prev *Element stack *Stack } @@ -40,7 +41,7 @@ type Element struct { // to the corresponding type. func NewElement(v interface{}) *Element { return &Element{ - value: makeStackItem(v), + value: stackitem.Make(v), } } @@ -60,12 +61,12 @@ func (e *Element) Prev() *Element { return nil } -// Item returns StackItem contained in the element. -func (e *Element) Item() StackItem { +// Item returns Item contained in the element. +func (e *Element) Item() stackitem.Item { return e.value } -// Value returns value of the StackItem contained in the element. +// Value returns value of the Item contained in the element. func (e *Element) Value() interface{} { return e.value.Value() } @@ -98,12 +99,12 @@ func (e *Element) Bytes() []byte { // Array attempts to get the underlying value of the element as an array of // other items. Will panic if the item type is different which will be caught // by the VM. -func (e *Element) Array() []StackItem { +func (e *Element) Array() []stackitem.Item { switch t := e.value.(type) { - case *ArrayItem: - return t.value - case *StructItem: - return t.value + case *stackitem.Array: + return t.Value().([]stackitem.Item) + case *stackitem.Struct: + return t.Value().([]stackitem.Item) default: panic("element is not an array") } @@ -111,9 +112,9 @@ func (e *Element) Array() []StackItem { // Interop attempts to get the underlying value of the element // as an interop item. -func (e *Element) Interop() *InteropItem { +func (e *Element) Interop() *stackitem.Interop { switch t := e.value.(type) { - case *InteropItem: + case *stackitem.Interop: return t default: panic("element is not an interop") @@ -190,7 +191,7 @@ func (s *Stack) Push(e *Element) { } // PushVal pushes the given value on the stack. It will infer the -// underlying StackItem to its corresponding type. +// underlying Item to its corresponding type. func (s *Stack) PushVal(v interface{}) { s.Push(NewElement(v)) } @@ -369,7 +370,7 @@ func (s *Stack) PopSigElements() ([][]byte, error) { return nil, fmt.Errorf("nothing on the stack") } switch item.value.(type) { - case *ArrayItem: + case *stackitem.Array: num = len(item.Array()) if num < 1 { return nil, fmt.Errorf("less than one element in the array") @@ -400,8 +401,8 @@ func (s *Stack) ToContractParameters() []smartcontract.Parameter { items := make([]smartcontract.Parameter, 0, s.Len()) s.IterBack(func(e *Element) { // Each item is independent. - seen := make(map[StackItem]bool) - items = append(items, e.value.ToContractParameter(seen)) + seen := make(map[stackitem.Item]bool) + items = append(items, smartcontract.ParameterFromStackItem(e.value, seen)) }) return items } diff --git a/pkg/vm/stack_item.go b/pkg/vm/stack_item.go deleted file mode 100644 index b2a857fa5..000000000 --- a/pkg/vm/stack_item.go +++ /dev/null @@ -1,1034 +0,0 @@ -package vm - -import ( - "bytes" - "encoding/binary" - "encoding/hex" - "encoding/json" - "errors" - "fmt" - "math/big" - "reflect" - - "github.com/nspcc-dev/neo-go/pkg/crypto/hash" - "github.com/nspcc-dev/neo-go/pkg/smartcontract" - "github.com/nspcc-dev/neo-go/pkg/util" - "github.com/nspcc-dev/neo-go/pkg/vm/emit" -) - -// A StackItem represents the "real" value that is pushed on the stack. -type StackItem interface { - fmt.Stringer - Value() interface{} - // Dup duplicates current StackItem. - Dup() StackItem - // Bool converts StackItem to a boolean value. - Bool() bool - // TryBytes converts StackItem to a byte slice. - TryBytes() ([]byte, error) - // TryInteger converts StackItem to an integer. - TryInteger() (*big.Int, error) - // Equals checks if 2 StackItems are equal. - Equals(s StackItem) bool - // ToContractParameter converts StackItem to smartcontract.Parameter - ToContractParameter(map[StackItem]bool) smartcontract.Parameter - // Type returns stack item type. - Type() StackItemType - // Convert converts StackItem to another type. - Convert(StackItemType) (StackItem, error) -} - -var errInvalidConversion = errors.New("invalid conversion type") - -func makeStackItem(v interface{}) StackItem { - switch val := v.(type) { - case int: - return &BigIntegerItem{ - value: big.NewInt(int64(val)), - } - case int64: - return &BigIntegerItem{ - value: big.NewInt(val), - } - case uint8: - return &BigIntegerItem{ - value: big.NewInt(int64(val)), - } - case uint16: - return &BigIntegerItem{ - value: big.NewInt(int64(val)), - } - case uint32: - return &BigIntegerItem{ - value: big.NewInt(int64(val)), - } - case uint64: - b := make([]byte, 8) - binary.BigEndian.PutUint64(b, val) - bigInt := big.NewInt(0) - bigInt.SetBytes(b) - return &BigIntegerItem{ - value: bigInt, - } - case []byte: - return &ByteArrayItem{ - value: val, - } - case string: - return &ByteArrayItem{ - value: []byte(val), - } - case bool: - return &BoolItem{ - value: val, - } - case []StackItem: - return &ArrayItem{ - value: val, - } - case *big.Int: - return NewBigIntegerItem(val) - case StackItem: - return val - case []int: - var a []StackItem - for _, i := range val { - a = append(a, makeStackItem(i)) - } - return makeStackItem(a) - default: - i64T := reflect.TypeOf(int64(0)) - if reflect.TypeOf(val).ConvertibleTo(i64T) { - i64Val := reflect.ValueOf(val).Convert(i64T).Interface() - return makeStackItem(i64Val) - } - panic( - fmt.Sprintf( - "invalid stack item type: %v (%v)", - val, - reflect.TypeOf(val), - ), - ) - } -} - -// convertPrimitive converts primitive item to a specified type. -func convertPrimitive(item StackItem, typ StackItemType) (StackItem, error) { - if item.Type() == typ { - return item, nil - } - switch typ { - case IntegerT: - bi, err := item.TryInteger() - if err != nil { - return nil, err - } - return NewBigIntegerItem(bi), nil - case ByteArrayT, BufferT: - b, err := item.TryBytes() - if err != nil { - return nil, err - } - if typ == BufferT { - return NewBufferItem(b), nil - } - return NewByteArrayItem(b), nil - case BooleanT: - return NewBoolItem(item.Bool()), nil - default: - return nil, errInvalidConversion - } -} - -// StructItem represents a struct on the stack. -type StructItem struct { - value []StackItem -} - -// NewStructItem returns an new StructItem object. -func NewStructItem(items []StackItem) *StructItem { - return &StructItem{ - value: items, - } -} - -// Value implements StackItem interface. -func (i *StructItem) Value() interface{} { - return i.value -} - -func (i *StructItem) String() string { - return "Struct" -} - -// Dup implements StackItem interface. -func (i *StructItem) Dup() StackItem { - // it's a reference type, so no copying here. - return i -} - -// Bool implements StackItem interface. -func (i *StructItem) Bool() bool { return true } - -// TryBytes implements StackItem interface. -func (i *StructItem) TryBytes() ([]byte, error) { - return nil, errors.New("can't convert Struct to ByteArray") -} - -// TryInteger implements StackItem interface. -func (i *StructItem) TryInteger() (*big.Int, error) { - return nil, errors.New("can't convert Struct to Integer") -} - -// Equals implements StackItem interface. -func (i *StructItem) Equals(s StackItem) bool { - if i == s { - return true - } else if s == nil { - return false - } - val, ok := s.(*StructItem) - if !ok || len(i.value) != len(val.value) { - return false - } - for j := range i.value { - if !i.value[j].Equals(val.value[j]) { - return false - } - } - return true -} - -// ToContractParameter implements StackItem interface. -func (i *StructItem) ToContractParameter(seen map[StackItem]bool) smartcontract.Parameter { - var value []smartcontract.Parameter - - if !seen[i] { - seen[i] = true - for _, stackItem := range i.value { - parameter := stackItem.ToContractParameter(seen) - value = append(value, parameter) - } - } - return smartcontract.Parameter{ - Type: smartcontract.ArrayType, - Value: value, - } -} - -// Type implements StackItem interface. -func (i *StructItem) Type() StackItemType { return StructT } - -// Convert implements StackItem interface. -func (i *StructItem) Convert(typ StackItemType) (StackItem, error) { - switch typ { - case StructT: - return i, nil - case ArrayT: - arr := make([]StackItem, len(i.value)) - copy(arr, i.value) - return NewArrayItem(arr), nil - case BooleanT: - return NewBoolItem(i.Bool()), nil - default: - return nil, errInvalidConversion - } -} - -// Clone returns a Struct with all Struct fields copied by value. -// Array fields are still copied by reference. -func (i *StructItem) Clone() *StructItem { - ret := &StructItem{make([]StackItem, len(i.value))} - for j := range i.value { - switch t := i.value[j].(type) { - case *StructItem: - ret.value[j] = t.Clone() - default: - ret.value[j] = t - } - } - return ret -} - -// NullItem represents null on the stack. -type NullItem struct{} - -// String implements StackItem interface. -func (i NullItem) String() string { - return "Null" -} - -// Value implements StackItem interface. -func (i NullItem) Value() interface{} { - return nil -} - -// Dup implements StackItem interface. -// There is no need to perform a real copy here, -// as NullItem has no internal state. -func (i NullItem) Dup() StackItem { - return i -} - -// Bool implements StackItem interface. -func (i NullItem) Bool() bool { return false } - -// TryBytes implements StackItem interface. -func (i NullItem) TryBytes() ([]byte, error) { - return nil, errors.New("can't convert Null to ByteArray") -} - -// TryInteger implements StackItem interface. -func (i NullItem) TryInteger() (*big.Int, error) { - return nil, errors.New("can't convert Null to Integer") -} - -// Equals implements StackItem interface. -func (i NullItem) Equals(s StackItem) bool { - _, ok := s.(NullItem) - return ok -} - -// ToContractParameter implements StackItem interface. -func (i NullItem) ToContractParameter(map[StackItem]bool) smartcontract.Parameter { - return smartcontract.Parameter{ - Type: smartcontract.AnyType, - } -} - -// Type implements StackItem interface. -func (i NullItem) Type() StackItemType { return AnyT } - -// Convert implements StackItem interface. -func (i NullItem) Convert(typ StackItemType) (StackItem, error) { - if typ == AnyT || !typ.IsValid() { - return nil, errInvalidConversion - } - return i, nil -} - -// BigIntegerItem represents a big integer on the stack. -type BigIntegerItem struct { - value *big.Int -} - -// NewBigIntegerItem returns an new BigIntegerItem object. -func NewBigIntegerItem(value *big.Int) *BigIntegerItem { - if value.BitLen() > MaxBigIntegerSizeBits { - panic("integer is too big") - } - return &BigIntegerItem{ - value: value, - } -} - -// Bytes converts i to a slice of bytes. -func (i *BigIntegerItem) Bytes() []byte { - return emit.IntToBytes(i.value) -} - -// Bool implements StackItem interface. -func (i *BigIntegerItem) Bool() bool { - return i.value.Sign() != 0 -} - -// TryBytes implements StackItem interface. -func (i *BigIntegerItem) TryBytes() ([]byte, error) { - return i.Bytes(), nil -} - -// TryInteger implements StackItem interface. -func (i *BigIntegerItem) TryInteger() (*big.Int, error) { - return i.value, nil -} - -// Equals implements StackItem interface. -func (i *BigIntegerItem) Equals(s StackItem) bool { - if i == s { - return true - } else if s == nil { - return false - } - val, ok := s.(*BigIntegerItem) - if ok { - return i.value.Cmp(val.value) == 0 - } - bs, err := s.TryBytes() - return err == nil && bytes.Equal(i.Bytes(), bs) -} - -// Value implements StackItem interface. -func (i *BigIntegerItem) Value() interface{} { - return i.value -} - -func (i *BigIntegerItem) String() string { - return "BigInteger" -} - -// Dup implements StackItem interface. -func (i *BigIntegerItem) Dup() StackItem { - n := new(big.Int) - return &BigIntegerItem{n.Set(i.value)} -} - -// ToContractParameter implements StackItem interface. -func (i *BigIntegerItem) ToContractParameter(map[StackItem]bool) smartcontract.Parameter { - return smartcontract.Parameter{ - Type: smartcontract.IntegerType, - Value: i.value.Int64(), - } -} - -// Type implements StackItem interface. -func (i *BigIntegerItem) Type() StackItemType { return IntegerT } - -// Convert implements StackItem interface. -func (i *BigIntegerItem) Convert(typ StackItemType) (StackItem, error) { - return convertPrimitive(i, typ) -} - -// MarshalJSON implements the json.Marshaler interface. -func (i *BigIntegerItem) MarshalJSON() ([]byte, error) { - return json.Marshal(i.value) -} - -// BoolItem represents a boolean StackItem. -type BoolItem struct { - value bool -} - -// NewBoolItem returns an new BoolItem object. -func NewBoolItem(val bool) *BoolItem { - return &BoolItem{ - value: val, - } -} - -// Value implements StackItem interface. -func (i *BoolItem) Value() interface{} { - return i.value -} - -// MarshalJSON implements the json.Marshaler interface. -func (i *BoolItem) MarshalJSON() ([]byte, error) { - return json.Marshal(i.value) -} - -func (i *BoolItem) String() string { - return "Boolean" -} - -// Dup implements StackItem interface. -func (i *BoolItem) Dup() StackItem { - return &BoolItem{i.value} -} - -// Bool implements StackItem interface. -func (i *BoolItem) Bool() bool { return i.value } - -// Bytes converts BoolItem to bytes. -func (i *BoolItem) Bytes() []byte { - if i.value { - return []byte{1} - } - return []byte{0} -} - -// TryBytes implements StackItem interface. -func (i *BoolItem) TryBytes() ([]byte, error) { - return i.Bytes(), nil -} - -// TryInteger implements StackItem interface. -func (i *BoolItem) TryInteger() (*big.Int, error) { - if i.value { - return big.NewInt(1), nil - } - return big.NewInt(0), nil -} - -// Equals implements StackItem interface. -func (i *BoolItem) Equals(s StackItem) bool { - if i == s { - return true - } else if s == nil { - return false - } - val, ok := s.(*BoolItem) - if ok { - return i.value == val.value - } - bs, err := s.TryBytes() - return err == nil && bytes.Equal(i.Bytes(), bs) -} - -// ToContractParameter implements StackItem interface. -func (i *BoolItem) ToContractParameter(map[StackItem]bool) smartcontract.Parameter { - return smartcontract.Parameter{ - Type: smartcontract.BoolType, - Value: i.value, - } -} - -// Type implements StackItem interface. -func (i *BoolItem) Type() StackItemType { return BooleanT } - -// Convert implements StackItem interface. -func (i *BoolItem) Convert(typ StackItemType) (StackItem, error) { - return convertPrimitive(i, typ) -} - -// ByteArrayItem represents a byte array on the stack. -type ByteArrayItem struct { - value []byte -} - -// NewByteArrayItem returns an new ByteArrayItem object. -func NewByteArrayItem(b []byte) *ByteArrayItem { - return &ByteArrayItem{ - value: b, - } -} - -// Value implements StackItem interface. -func (i *ByteArrayItem) Value() interface{} { - return i.value -} - -// MarshalJSON implements the json.Marshaler interface. -func (i *ByteArrayItem) MarshalJSON() ([]byte, error) { - return json.Marshal(hex.EncodeToString(i.value)) -} - -func (i *ByteArrayItem) String() string { - return "ByteArray" -} - -// Bool implements StackItem interface. -func (i *ByteArrayItem) Bool() bool { - if len(i.value) > MaxBigIntegerSizeBits/8 { - return true - } - for _, b := range i.value { - if b != 0 { - return true - } - } - return false -} - -// TryBytes implements StackItem interface. -func (i *ByteArrayItem) TryBytes() ([]byte, error) { - val := make([]byte, len(i.value)) - copy(val, i.value) - return val, nil -} - -// TryInteger implements StackItem interface. -func (i *ByteArrayItem) TryInteger() (*big.Int, error) { - if len(i.value) > MaxBigIntegerSizeBits/8 { - return nil, errors.New("integer is too big") - } - return emit.BytesToInt(i.value), nil -} - -// Equals implements StackItem interface. -func (i *ByteArrayItem) Equals(s StackItem) bool { - if i == s { - return true - } else if s == nil { - return false - } - bs, err := s.TryBytes() - return err == nil && bytes.Equal(i.value, bs) -} - -// Dup implements StackItem interface. -func (i *ByteArrayItem) Dup() StackItem { - a := make([]byte, len(i.value)) - copy(a, i.value) - return &ByteArrayItem{a} -} - -// ToContractParameter implements StackItem interface. -func (i *ByteArrayItem) ToContractParameter(map[StackItem]bool) smartcontract.Parameter { - return smartcontract.Parameter{ - Type: smartcontract.ByteArrayType, - Value: i.value, - } -} - -// Type implements StackItem interface. -func (i *ByteArrayItem) Type() StackItemType { return ByteArrayT } - -// Convert implements StackItem interface. -func (i *ByteArrayItem) Convert(typ StackItemType) (StackItem, error) { - return convertPrimitive(i, typ) -} - -// ArrayItem represents a new ArrayItem object. -type ArrayItem struct { - value []StackItem -} - -// NewArrayItem returns a new ArrayItem object. -func NewArrayItem(items []StackItem) *ArrayItem { - return &ArrayItem{ - value: items, - } -} - -// Value implements StackItem interface. -func (i *ArrayItem) Value() interface{} { - return i.value -} - -// MarshalJSON implements the json.Marshaler interface. -func (i *ArrayItem) MarshalJSON() ([]byte, error) { - return json.Marshal(i.value) -} - -func (i *ArrayItem) String() string { - return "Array" -} - -// Bool implements StackItem interface. -func (i *ArrayItem) Bool() bool { return true } - -// TryBytes implements StackItem interface. -func (i *ArrayItem) TryBytes() ([]byte, error) { - return nil, errors.New("can't convert Array to ByteArray") -} - -// TryInteger implements StackItem interface. -func (i *ArrayItem) TryInteger() (*big.Int, error) { - return nil, errors.New("can't convert Array to Integer") -} - -// Equals implements StackItem interface. -func (i *ArrayItem) Equals(s StackItem) bool { - return i == s -} - -// Dup implements StackItem interface. -func (i *ArrayItem) Dup() StackItem { - // reference type - return i -} - -// ToContractParameter implements StackItem interface. -func (i *ArrayItem) ToContractParameter(seen map[StackItem]bool) smartcontract.Parameter { - var value []smartcontract.Parameter - - if !seen[i] { - seen[i] = true - for _, stackItem := range i.value { - parameter := stackItem.ToContractParameter(seen) - value = append(value, parameter) - } - } - return smartcontract.Parameter{ - Type: smartcontract.ArrayType, - Value: value, - } -} - -// Type implements StackItem interface. -func (i *ArrayItem) Type() StackItemType { return ArrayT } - -// Convert implements StackItem interface. -func (i *ArrayItem) Convert(typ StackItemType) (StackItem, error) { - switch typ { - case ArrayT: - return i, nil - case StructT: - arr := make([]StackItem, len(i.value)) - copy(arr, i.value) - return NewStructItem(arr), nil - case BooleanT: - return NewBoolItem(i.Bool()), nil - default: - return nil, errInvalidConversion - } -} - -// MapElement is a key-value pair of StackItems. -type MapElement struct { - Key StackItem - Value StackItem -} - -// MapItem represents Map object. It's ordered, so we use slice representation -// which should be fine for maps with less than 32 or so elements. Given that -// our VM has quite low limit of overall stack items, it should be good enough, -// but it can be extended with a real map for fast random access in the future -// if need be. -type MapItem struct { - value []MapElement -} - -// NewMapItem returns new MapItem object. -func NewMapItem() *MapItem { - return &MapItem{ - value: make([]MapElement, 0), - } -} - -// Value implements StackItem interface. -func (i *MapItem) Value() interface{} { - return i.value -} - -// Bool implements StackItem interface. -func (i *MapItem) Bool() bool { return true } - -// TryBytes implements StackItem interface. -func (i *MapItem) TryBytes() ([]byte, error) { - return nil, errors.New("can't convert Map to ByteArray") -} - -// TryInteger implements StackItem interface. -func (i *MapItem) TryInteger() (*big.Int, error) { - return nil, errors.New("can't convert Map to Integer") -} - -// Equals implements StackItem interface. -func (i *MapItem) Equals(s StackItem) bool { - return i == s -} - -func (i *MapItem) String() string { - return "Map" -} - -// Index returns an index of the key in map. -func (i *MapItem) Index(key StackItem) int { - for k := range i.value { - if i.value[k].Key.Equals(key) { - return k - } - } - return -1 -} - -// Has checks if map has specified key. -func (i *MapItem) Has(key StackItem) bool { - return i.Index(key) >= 0 -} - -// Dup implements StackItem interface. -func (i *MapItem) Dup() StackItem { - // reference type - return i -} - -// ToContractParameter implements StackItem interface. -func (i *MapItem) ToContractParameter(seen map[StackItem]bool) smartcontract.Parameter { - value := make([]smartcontract.ParameterPair, 0) - if !seen[i] { - seen[i] = true - for k := range i.value { - value = append(value, smartcontract.ParameterPair{ - Key: i.value[k].Key.ToContractParameter(seen), - Value: i.value[k].Value.ToContractParameter(seen), - }) - } - } - return smartcontract.Parameter{ - Type: smartcontract.MapType, - Value: value, - } -} - -// Type implements StackItem interface. -func (i *MapItem) Type() StackItemType { return MapT } - -// Convert implements StackItem interface. -func (i *MapItem) Convert(typ StackItemType) (StackItem, error) { - switch typ { - case MapT: - return i, nil - case BooleanT: - return NewBoolItem(i.Bool()), nil - default: - return nil, errInvalidConversion - } -} - -// Add adds key-value pair to the map. -func (i *MapItem) Add(key, value StackItem) { - if !isValidMapKey(key) { - panic("wrong key type") - } - index := i.Index(key) - if index >= 0 { - i.value[index].Value = value - } else { - i.value = append(i.value, MapElement{key, value}) - } -} - -// Drop removes given index from the map (no bounds check done here). -func (i *MapItem) Drop(index int) { - copy(i.value[index:], i.value[index+1:]) - i.value = i.value[:len(i.value)-1] -} - -// isValidMapKey checks whether it's possible to use given StackItem as a Map -// key. -func isValidMapKey(key StackItem) bool { - switch key.(type) { - case *BoolItem, *BigIntegerItem, *ByteArrayItem: - return true - default: - return false - } -} - -// InteropItem represents interop data on the stack. -type InteropItem struct { - value interface{} -} - -// NewInteropItem returns new InteropItem object. -func NewInteropItem(value interface{}) *InteropItem { - return &InteropItem{ - value: value, - } -} - -// Value implements StackItem interface. -func (i *InteropItem) Value() interface{} { - return i.value -} - -// String implements stringer interface. -func (i *InteropItem) String() string { - return "InteropItem" -} - -// Dup implements StackItem interface. -func (i *InteropItem) Dup() StackItem { - // reference type - return i -} - -// Bool implements StackItem interface. -func (i *InteropItem) Bool() bool { return true } - -// TryBytes implements StackItem interface. -func (i *InteropItem) TryBytes() ([]byte, error) { - return nil, errors.New("can't convert Interop to ByteArray") -} - -// TryInteger implements StackItem interface. -func (i *InteropItem) TryInteger() (*big.Int, error) { - return nil, errors.New("can't convert Interop to Integer") -} - -// Equals implements StackItem interface. -func (i *InteropItem) Equals(s StackItem) bool { - if i == s { - return true - } else if s == nil { - return false - } - val, ok := s.(*InteropItem) - return ok && i.value == val.value -} - -// ToContractParameter implements StackItem interface. -func (i *InteropItem) ToContractParameter(map[StackItem]bool) smartcontract.Parameter { - return smartcontract.Parameter{ - Type: smartcontract.InteropInterfaceType, - Value: nil, - } -} - -// Type implements StackItem interface. -func (i *InteropItem) Type() StackItemType { return InteropT } - -// Convert implements StackItem interface. -func (i *InteropItem) Convert(typ StackItemType) (StackItem, error) { - switch typ { - case InteropT: - return i, nil - case BooleanT: - return NewBoolItem(i.Bool()), nil - default: - return nil, errInvalidConversion - } -} - -// MarshalJSON implements the json.Marshaler interface. -func (i *InteropItem) MarshalJSON() ([]byte, error) { - return json.Marshal(i.value) -} - -// PointerItem represents VM-level instruction pointer. -type PointerItem struct { - pos int - script []byte - hash util.Uint160 -} - -// NewPointerItem returns new pointer on the specified position. -func NewPointerItem(pos int, script []byte) *PointerItem { - return &PointerItem{ - pos: pos, - script: script, - hash: hash.Hash160(script), - } -} - -// String implements StackItem interface. -func (p *PointerItem) String() string { - return "Pointer" -} - -// Value implements StackItem interface. -func (p *PointerItem) Value() interface{} { - return p.pos -} - -// Dup implements StackItem interface. -func (p *PointerItem) Dup() StackItem { - return &PointerItem{ - pos: p.pos, - script: p.script, - hash: p.hash, - } -} - -// Bool implements StackItem interface. -func (p *PointerItem) Bool() bool { - return true -} - -// TryBytes implements StackItem interface. -func (p *PointerItem) TryBytes() ([]byte, error) { - return nil, errors.New("can't convert Pointer to ByteArray") -} - -// TryInteger implements StackItem interface. -func (p *PointerItem) TryInteger() (*big.Int, error) { - return nil, errors.New("can't convert Pointer to Integer") -} - -// Equals implements StackItem interface. -func (p *PointerItem) Equals(s StackItem) bool { - if p == s { - return true - } - ptr, ok := s.(*PointerItem) - return ok && p.pos == ptr.pos && p.hash == ptr.hash -} - -// ToContractParameter implements StackItem interface. -func (p *PointerItem) ToContractParameter(map[StackItem]bool) smartcontract.Parameter { - return smartcontract.NewParameter(smartcontract.AnyType) -} - -// Type implements StackItem interface. -func (p *PointerItem) Type() StackItemType { - return PointerT -} - -// Convert implements StackItem interface. -func (p *PointerItem) Convert(typ StackItemType) (StackItem, error) { - switch typ { - case PointerT: - return p, nil - case BooleanT: - return NewBoolItem(p.Bool()), nil - default: - return nil, errInvalidConversion - } -} - -// BufferItem represents represents Buffer stack item. -type BufferItem struct { - value []byte -} - -// NewBufferItem returns a new BufferItem object. -func NewBufferItem(b []byte) *BufferItem { - return &BufferItem{ - value: b, - } -} - -// Value implements StackItem interface. -func (i *BufferItem) Value() interface{} { - return i.value -} - -// String implements fmt.Stringer interface. -func (i *BufferItem) String() string { - return "Buffer" -} - -// Bool implements StackItem interface. -func (i *BufferItem) Bool() bool { - return true -} - -// TryBytes implements StackItem interface. -func (i *BufferItem) TryBytes() ([]byte, error) { - val := make([]byte, len(i.value)) - copy(val, i.value) - return val, nil -} - -// TryInteger implements StackItem interface. -func (i *BufferItem) TryInteger() (*big.Int, error) { - return nil, errors.New("can't convert Buffer to Integer") -} - -// Equals implements StackItem interface. -func (i *BufferItem) Equals(s StackItem) bool { - return i == s -} - -// Dup implements StackItem interface. -func (i *BufferItem) Dup() StackItem { - return i -} - -// MarshalJSON implements the json.Marshaler interface. -func (i *BufferItem) MarshalJSON() ([]byte, error) { - return json.Marshal(hex.EncodeToString(i.value)) -} - -// ToContractParameter implements StackItem interface. -func (i *BufferItem) ToContractParameter(map[StackItem]bool) smartcontract.Parameter { - return smartcontract.Parameter{ - Type: smartcontract.ByteArrayType, - Value: i.value, - } -} - -// Type implements StackItem interface. -func (i *BufferItem) Type() StackItemType { return BufferT } - -// Convert implements StackItem interface. -func (i *BufferItem) Convert(typ StackItemType) (StackItem, error) { - switch typ { - case BooleanT: - return NewBoolItem(i.Bool()), nil - case BufferT: - return i, nil - case ByteArrayT: - val := make([]byte, len(i.value)) - copy(val, i.value) - return NewByteArrayItem(val), nil - case IntegerT: - if len(i.value) > MaxBigIntegerSizeBits/8 { - return nil, errInvalidConversion - } - return NewBigIntegerItem(emit.BytesToInt(i.value)), nil - default: - return nil, errInvalidConversion - } -} diff --git a/pkg/vm/stack_item_test.go b/pkg/vm/stack_item_test.go deleted file mode 100644 index 06db6ee61..000000000 --- a/pkg/vm/stack_item_test.go +++ /dev/null @@ -1,485 +0,0 @@ -package vm - -import ( - "math/big" - "testing" - - "github.com/nspcc-dev/neo-go/pkg/smartcontract" - "github.com/stretchr/testify/assert" -) - -var makeStackItemTestCases = []struct { - input interface{} - result StackItem -}{ - { - input: int64(3), - result: &BigIntegerItem{value: big.NewInt(3)}, - }, - { - input: int16(3), - result: &BigIntegerItem{value: big.NewInt(3)}, - }, - { - input: 3, - result: &BigIntegerItem{value: big.NewInt(3)}, - }, - { - input: uint8(3), - result: &BigIntegerItem{value: big.NewInt(3)}, - }, - { - input: uint16(3), - result: &BigIntegerItem{value: big.NewInt(3)}, - }, - { - input: uint32(3), - result: &BigIntegerItem{value: big.NewInt(3)}, - }, - { - input: uint64(3), - result: &BigIntegerItem{value: big.NewInt(3)}, - }, - { - input: big.NewInt(3), - result: &BigIntegerItem{value: big.NewInt(3)}, - }, - { - input: []byte{1, 2, 3, 4}, - result: &ByteArrayItem{value: []byte{1, 2, 3, 4}}, - }, - { - input: []byte{}, - result: &ByteArrayItem{value: []byte{}}, - }, - { - input: "bla", - result: &ByteArrayItem{value: []byte("bla")}, - }, - { - input: "", - result: &ByteArrayItem{value: []byte{}}, - }, - { - input: true, - result: &BoolItem{value: true}, - }, - { - input: false, - result: &BoolItem{value: false}, - }, - { - input: []StackItem{&BigIntegerItem{value: big.NewInt(3)}, &ByteArrayItem{value: []byte{1, 2, 3}}}, - result: &ArrayItem{value: []StackItem{&BigIntegerItem{value: big.NewInt(3)}, &ByteArrayItem{value: []byte{1, 2, 3}}}}, - }, - { - input: []int{1, 2, 3}, - result: &ArrayItem{value: []StackItem{&BigIntegerItem{value: big.NewInt(1)}, &BigIntegerItem{value: big.NewInt(2)}, &BigIntegerItem{value: big.NewInt(3)}}}, - }, -} - -var makeStackItemErrorCases = []struct { - input interface{} -}{ - { - input: nil, - }, -} - -func TestMakeStackItem(t *testing.T) { - for _, testCase := range makeStackItemTestCases { - assert.Equal(t, testCase.result, makeStackItem(testCase.input)) - } - for _, errorCase := range makeStackItemErrorCases { - assert.Panics(t, func() { makeStackItem(errorCase.input) }) - } -} - -var stringerTestCases = []struct { - input StackItem - result string -}{ - { - input: NewStructItem([]StackItem{}), - result: "Struct", - }, - { - input: NewBigIntegerItem(big.NewInt(3)), - result: "BigInteger", - }, - { - input: NewBoolItem(true), - result: "Boolean", - }, - { - input: NewByteArrayItem([]byte{}), - result: "ByteArray", - }, - { - input: NewArrayItem([]StackItem{}), - result: "Array", - }, - { - input: NewMapItem(), - result: "Map", - }, - { - input: NewInteropItem(nil), - result: "InteropItem", - }, - { - input: NewPointerItem(0, nil), - result: "Pointer", - }, -} - -func TestStringer(t *testing.T) { - for _, testCase := range stringerTestCases { - assert.Equal(t, testCase.result, testCase.input.String()) - } -} - -var equalsTestCases = map[string][]struct { - item1 StackItem - item2 StackItem - result bool -}{ - "struct": { - { - item1: NewStructItem(nil), - item2: nil, - result: false, - }, - { - item1: NewStructItem(nil), - item2: NewBigIntegerItem(big.NewInt(1)), - result: false, - }, - { - item1: NewStructItem(nil), - item2: NewStructItem([]StackItem{NewBigIntegerItem(big.NewInt(1))}), - result: false, - }, - { - item1: NewStructItem([]StackItem{NewBigIntegerItem(big.NewInt(1))}), - item2: NewStructItem([]StackItem{NewBigIntegerItem(big.NewInt(2))}), - result: false, - }, - { - item1: NewStructItem([]StackItem{NewBigIntegerItem(big.NewInt(1))}), - item2: NewStructItem([]StackItem{NewBigIntegerItem(big.NewInt(1))}), - result: true, - }, - }, - "bigint": { - { - item1: NewBigIntegerItem(big.NewInt(2)), - item2: nil, - result: false, - }, - { - item1: NewBigIntegerItem(big.NewInt(2)), - item2: NewBigIntegerItem(big.NewInt(2)), - result: true, - }, - { - item1: NewBigIntegerItem(big.NewInt(2)), - item2: NewBoolItem(false), - result: false, - }, - { - item1: NewBigIntegerItem(big.NewInt(0)), - item2: NewBoolItem(false), - result: false, - }, - { - item1: NewBigIntegerItem(big.NewInt(2)), - item2: makeStackItem(int32(2)), - result: true, - }, - }, - "bool": { - { - item1: NewBoolItem(true), - item2: nil, - result: false, - }, - { - item1: NewBoolItem(true), - item2: NewBoolItem(true), - result: true, - }, - { - item1: NewBoolItem(true), - item2: NewBigIntegerItem(big.NewInt(1)), - result: true, - }, - { - item1: NewBoolItem(true), - item2: NewBoolItem(false), - result: false, - }, - { - item1: NewBoolItem(true), - item2: makeStackItem(true), - result: true, - }, - }, - "bytearray": { - { - item1: NewByteArrayItem(nil), - item2: nil, - result: false, - }, - { - item1: NewByteArrayItem([]byte{1, 2, 3}), - item2: NewByteArrayItem([]byte{1, 2, 3}), - result: true, - }, - { - item1: NewByteArrayItem([]byte{1}), - item2: NewBigIntegerItem(big.NewInt(1)), - result: true, - }, - { - item1: NewByteArrayItem([]byte{1, 2, 3}), - item2: NewByteArrayItem([]byte{1, 2, 4}), - result: false, - }, - { - item1: NewByteArrayItem([]byte{1, 2, 3}), - item2: makeStackItem([]byte{1, 2, 3}), - result: true, - }, - }, - "array": { - { - item1: NewArrayItem(nil), - item2: nil, - result: false, - }, - { - item1: NewArrayItem([]StackItem{&BigIntegerItem{big.NewInt(1)}, &BigIntegerItem{big.NewInt(2)}, &BigIntegerItem{big.NewInt(3)}}), - item2: NewArrayItem([]StackItem{&BigIntegerItem{big.NewInt(1)}, &BigIntegerItem{big.NewInt(2)}, &BigIntegerItem{big.NewInt(3)}}), - result: false, - }, - { - item1: NewArrayItem([]StackItem{&BigIntegerItem{big.NewInt(1)}}), - item2: NewBigIntegerItem(big.NewInt(1)), - result: false, - }, - { - item1: NewArrayItem([]StackItem{&BigIntegerItem{big.NewInt(1)}, &BigIntegerItem{big.NewInt(2)}, &BigIntegerItem{big.NewInt(3)}}), - item2: NewArrayItem([]StackItem{&BigIntegerItem{big.NewInt(1)}, &BigIntegerItem{big.NewInt(2)}, &BigIntegerItem{big.NewInt(4)}}), - result: false, - }, - }, - "map": { - { - item1: NewMapItem(), - item2: nil, - result: false, - }, - { - item1: NewMapItem(), - item2: NewMapItem(), - result: false, - }, - { - item1: &MapItem{value: []MapElement{{NewByteArrayItem([]byte("first")), NewBigIntegerItem(big.NewInt(1))}, {NewBoolItem(true), NewByteArrayItem([]byte{2})}}}, - item2: &MapItem{value: []MapElement{{NewByteArrayItem([]byte("first")), NewBigIntegerItem(big.NewInt(1))}, {NewBoolItem(true), NewByteArrayItem([]byte{2})}}}, - result: false, - }, - { - item1: &MapItem{value: []MapElement{{NewByteArrayItem([]byte("first")), NewBigIntegerItem(big.NewInt(1))}, {NewBoolItem(true), NewByteArrayItem([]byte{2})}}}, - item2: &MapItem{value: []MapElement{{NewByteArrayItem([]byte("first")), NewBigIntegerItem(big.NewInt(1))}, {NewBoolItem(true), NewByteArrayItem([]byte{3})}}}, - result: false, - }, - }, - "interop": { - { - item1: NewInteropItem(nil), - item2: nil, - result: false, - }, - { - item1: NewInteropItem(nil), - item2: NewInteropItem(nil), - result: true, - }, - { - item1: NewInteropItem(2), - item2: NewInteropItem(3), - result: false, - }, - { - item1: NewInteropItem(3), - item2: NewInteropItem(3), - result: true, - }, - }, - "pointer": { - { - item1: NewPointerItem(0, []byte{}), - result: false, - }, - { - item1: NewPointerItem(1, []byte{1}), - item2: NewPointerItem(1, []byte{1}), - result: true, - }, - { - item1: NewPointerItem(1, []byte{1}), - item2: NewPointerItem(2, []byte{1}), - result: false, - }, - { - item1: NewPointerItem(1, []byte{1}), - item2: NewPointerItem(1, []byte{2}), - result: false, - }, - { - item1: NewPointerItem(0, []byte{}), - item2: NewBigIntegerItem(big.NewInt(0)), - result: false, - }, - }, -} - -func TestEquals(t *testing.T) { - for name, testBatch := range equalsTestCases { - for _, testCase := range testBatch { - t.Run(name, func(t *testing.T) { - assert.Equal(t, testCase.result, testCase.item1.Equals(testCase.item2)) - // Reference equals - assert.Equal(t, true, testCase.item1.Equals(testCase.item1)) - }) - } - } -} - -var marshalJSONTestCases = []struct { - input StackItem - result []byte -}{ - { - input: NewBigIntegerItem(big.NewInt(2)), - result: []byte(`2`), - }, - { - input: NewBoolItem(true), - result: []byte(`true`), - }, - { - input: NewByteArrayItem([]byte{1, 2, 3}), - result: []byte(`"010203"`), - }, - { - input: NewBufferItem([]byte{1, 2, 3}), - result: []byte(`"010203"`), - }, - { - input: &ArrayItem{value: []StackItem{&BigIntegerItem{value: big.NewInt(3)}, &ByteArrayItem{value: []byte{1, 2, 3}}}}, - result: []byte(`[3,"010203"]`), - }, - { - input: &InteropItem{value: 3}, - result: []byte(`3`), - }, -} - -func TestMarshalJSON(t *testing.T) { - var ( - actual []byte - err error - ) - for _, testCase := range marshalJSONTestCases { - switch testCase.input.(type) { - case *BigIntegerItem: - actual, err = testCase.input.(*BigIntegerItem).MarshalJSON() - case *BoolItem: - actual, err = testCase.input.(*BoolItem).MarshalJSON() - case *ByteArrayItem: - actual, err = testCase.input.(*ByteArrayItem).MarshalJSON() - case *ArrayItem: - actual, err = testCase.input.(*ArrayItem).MarshalJSON() - case *InteropItem: - actual, err = testCase.input.(*InteropItem).MarshalJSON() - default: - continue - } - - assert.NoError(t, err) - assert.Equal(t, testCase.result, actual) - } -} - -var toContractParameterTestCases = []struct { - input StackItem - result smartcontract.Parameter -}{ - { - input: NewStructItem([]StackItem{ - NewBigIntegerItem(big.NewInt(1)), - NewBoolItem(true), - }), - result: smartcontract.Parameter{Type: smartcontract.ArrayType, Value: []smartcontract.Parameter{ - {Type: smartcontract.IntegerType, Value: int64(1)}, - {Type: smartcontract.BoolType, Value: true}, - }}, - }, - { - input: NewBoolItem(false), - result: smartcontract.Parameter{Type: smartcontract.BoolType, Value: false}, - }, - { - input: NewByteArrayItem([]byte{0x01, 0x02, 0x03}), - result: smartcontract.Parameter{Type: smartcontract.ByteArrayType, Value: []byte{0x01, 0x02, 0x03}}, - }, - { - input: NewBufferItem([]byte{0x01, 0x02, 0x03}), - result: smartcontract.Parameter{Type: smartcontract.ByteArrayType, Value: []byte{0x01, 0x02, 0x03}}, - }, - { - input: NewArrayItem([]StackItem{NewBigIntegerItem(big.NewInt(2)), NewBoolItem(true)}), - result: smartcontract.Parameter{Type: smartcontract.ArrayType, Value: []smartcontract.Parameter{ - {Type: smartcontract.IntegerType, Value: int64(2)}, - {Type: smartcontract.BoolType, Value: true}, - }}, - }, - { - input: NewInteropItem(nil), - result: smartcontract.Parameter{Type: smartcontract.InteropInterfaceType, Value: nil}, - }, - { - input: &MapItem{value: []MapElement{ - {NewBigIntegerItem(big.NewInt(1)), NewBoolItem(true)}, - {NewByteArrayItem([]byte("qwerty")), NewBigIntegerItem(big.NewInt(3))}, - {NewBoolItem(true), NewBoolItem(false)}, - }}, - result: smartcontract.Parameter{ - Type: smartcontract.MapType, - Value: []smartcontract.ParameterPair{ - { - Key: smartcontract.Parameter{Type: smartcontract.IntegerType, Value: int64(1)}, - Value: smartcontract.Parameter{Type: smartcontract.BoolType, Value: true}, - }, { - Key: smartcontract.Parameter{Type: smartcontract.ByteArrayType, Value: []byte("qwerty")}, - Value: smartcontract.Parameter{Type: smartcontract.IntegerType, Value: int64(3)}, - }, { - - Key: smartcontract.Parameter{Type: smartcontract.BoolType, Value: true}, - Value: smartcontract.Parameter{Type: smartcontract.BoolType, Value: false}, - }, - }, - }, - }, -} - -func TestToContractParameter(t *testing.T) { - for _, tc := range toContractParameterTestCases { - seen := make(map[StackItem]bool) - res := tc.input.ToContractParameter(seen) - assert.Equal(t, res, tc.result) - } -} diff --git a/pkg/vm/stack_test.go b/pkg/vm/stack_test.go index 24a0671f6..82c15e962 100644 --- a/pkg/vm/stack_test.go +++ b/pkg/vm/stack_test.go @@ -4,6 +4,7 @@ import ( "math/big" "testing" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -231,9 +232,9 @@ func TestPushVal(t *testing.T) { assert.Equal(t, true, elem.Bool()) // array - s.PushVal([]StackItem{&BoolItem{true}, &BoolItem{false}, &BoolItem{true}}) + s.PushVal([]stackitem.Item{stackitem.NewBool(true), stackitem.NewBool(false), stackitem.NewBool(true)}) elem = s.Pop() - assert.IsType(t, elem.value, &ArrayItem{}) + assert.IsType(t, elem.value, &stackitem.Array{}) } func TestSwapElemValues(t *testing.T) { @@ -320,17 +321,17 @@ func TestPopSigElements(t *testing.T) { _, err := s.PopSigElements() assert.NotNil(t, err) - s.PushVal([]StackItem{}) + s.PushVal([]stackitem.Item{}) _, err = s.PopSigElements() assert.NotNil(t, err) - s.PushVal([]StackItem{NewBoolItem(false)}) + s.PushVal([]stackitem.Item{stackitem.NewBool(false)}) _, err = s.PopSigElements() assert.NotNil(t, err) b1 := []byte("smth") b2 := []byte("strange") - s.PushVal([]StackItem{NewByteArrayItem(b1), NewByteArrayItem(b2)}) + s.PushVal([]stackitem.Item{stackitem.NewByteArray(b1), stackitem.NewByteArray(b2)}) z, err := s.PopSigElements() assert.Nil(t, err) assert.Equal(t, z, [][]byte{b1, b2}) diff --git a/pkg/vm/stackitem/item.go b/pkg/vm/stackitem/item.go new file mode 100644 index 000000000..07304b037 --- /dev/null +++ b/pkg/vm/stackitem/item.go @@ -0,0 +1,1010 @@ +package stackitem + +import ( + "bytes" + "encoding/binary" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "math/big" + "reflect" + + "github.com/nspcc-dev/neo-go/pkg/crypto/hash" + "github.com/nspcc-dev/neo-go/pkg/encoding/bigint" + "github.com/nspcc-dev/neo-go/pkg/util" +) + +// MaxBigIntegerSizeBits is the maximum size of BigInt item in bits. +const MaxBigIntegerSizeBits = 32 * 8 + +// Item represents the "real" value that is pushed on the stack. +type Item interface { + fmt.Stringer + Value() interface{} + // Dup duplicates current Item. + Dup() Item + // Bool converts Item to a boolean value. + Bool() bool + // TryBytes converts Item to a byte slice. + TryBytes() ([]byte, error) + // TryInteger converts Item to an integer. + TryInteger() (*big.Int, error) + // Equals checks if 2 StackItems are equal. + Equals(s Item) bool + // Type returns stack item type. + Type() Type + // Convert converts Item to another type. + Convert(Type) (Item, error) +} + +var errInvalidConversion = errors.New("invalid conversion type") + +// Make tries to make appropriate stack item from provided value. +// It will panic if it's not possible. +func Make(v interface{}) Item { + switch val := v.(type) { + case int: + return &BigInteger{ + value: big.NewInt(int64(val)), + } + case int64: + return &BigInteger{ + value: big.NewInt(val), + } + case uint8: + return &BigInteger{ + value: big.NewInt(int64(val)), + } + case uint16: + return &BigInteger{ + value: big.NewInt(int64(val)), + } + case uint32: + return &BigInteger{ + value: big.NewInt(int64(val)), + } + case uint64: + b := make([]byte, 8) + binary.BigEndian.PutUint64(b, val) + bigInt := big.NewInt(0) + bigInt.SetBytes(b) + return &BigInteger{ + value: bigInt, + } + case []byte: + return &ByteArray{ + value: val, + } + case string: + return &ByteArray{ + value: []byte(val), + } + case bool: + return &Bool{ + value: val, + } + case []Item: + return &Array{ + value: val, + } + case *big.Int: + return NewBigInteger(val) + case Item: + return val + case []int: + var a []Item + for _, i := range val { + a = append(a, Make(i)) + } + return Make(a) + default: + i64T := reflect.TypeOf(int64(0)) + if reflect.TypeOf(val).ConvertibleTo(i64T) { + i64Val := reflect.ValueOf(val).Convert(i64T).Interface() + return Make(i64Val) + } + panic( + fmt.Sprintf( + "invalid stack item type: %v (%v)", + val, + reflect.TypeOf(val), + ), + ) + } +} + +// convertPrimitive converts primitive item to a specified type. +func convertPrimitive(item Item, typ Type) (Item, error) { + if item.Type() == typ { + return item, nil + } + switch typ { + case IntegerT: + bi, err := item.TryInteger() + if err != nil { + return nil, err + } + return NewBigInteger(bi), nil + case ByteArrayT, BufferT: + b, err := item.TryBytes() + if err != nil { + return nil, err + } + if typ == BufferT { + return NewBuffer(b), nil + } + return NewByteArray(b), nil + case BooleanT: + return NewBool(item.Bool()), nil + default: + return nil, errInvalidConversion + } +} + +// Struct represents a struct on the stack. +type Struct struct { + value []Item +} + +// NewStruct returns an new Struct object. +func NewStruct(items []Item) *Struct { + return &Struct{ + value: items, + } +} + +// Value implements Item interface. +func (i *Struct) Value() interface{} { + return i.value +} + +// Remove removes element at `pos` index from Struct value. +// It will panics on bad index. +func (i *Struct) Remove(pos int) { + i.value = append(i.value[:pos], i.value[pos+1:]...) +} + +// Append adds Item at the end of Struct value. +func (i *Struct) Append(item Item) { + i.value = append(i.value, item) +} + +// Clear removes all elements from Struct item value. +func (i *Struct) Clear() { + i.value = i.value[:0] +} + +// Len returns length of Struct value. +func (i *Struct) Len() int { + return len(i.value) +} + +// String implements Item interface. +func (i *Struct) String() string { + return "Struct" +} + +// Dup implements Item interface. +func (i *Struct) Dup() Item { + // it's a reference type, so no copying here. + return i +} + +// Bool implements Item interface. +func (i *Struct) Bool() bool { return true } + +// TryBytes implements Item interface. +func (i *Struct) TryBytes() ([]byte, error) { + return nil, errors.New("can't convert Struct to ByteArray") +} + +// TryInteger implements Item interface. +func (i *Struct) TryInteger() (*big.Int, error) { + return nil, errors.New("can't convert Struct to Integer") +} + +// Equals implements Item interface. +func (i *Struct) Equals(s Item) bool { + if i == s { + return true + } else if s == nil { + return false + } + val, ok := s.(*Struct) + if !ok || len(i.value) != len(val.value) { + return false + } + for j := range i.value { + if !i.value[j].Equals(val.value[j]) { + return false + } + } + return true +} + +// Type implements Item interface. +func (i *Struct) Type() Type { return StructT } + +// Convert implements Item interface. +func (i *Struct) Convert(typ Type) (Item, error) { + switch typ { + case StructT: + return i, nil + case ArrayT: + arr := make([]Item, len(i.value)) + copy(arr, i.value) + return NewArray(arr), nil + case BooleanT: + return NewBool(i.Bool()), nil + default: + return nil, errInvalidConversion + } +} + +// Clone returns a Struct with all Struct fields copied by value. +// Array fields are still copied by reference. +func (i *Struct) Clone() *Struct { + ret := &Struct{make([]Item, len(i.value))} + for j := range i.value { + switch t := i.value[j].(type) { + case *Struct: + ret.value[j] = t.Clone() + default: + ret.value[j] = t + } + } + return ret +} + +// Null represents null on the stack. +type Null struct{} + +// String implements Item interface. +func (i Null) String() string { + return "Null" +} + +// Value implements Item interface. +func (i Null) Value() interface{} { + return nil +} + +// Dup implements Item interface. +// There is no need to perform a real copy here, +// as Null has no internal state. +func (i Null) Dup() Item { + return i +} + +// Bool implements Item interface. +func (i Null) Bool() bool { return false } + +// TryBytes implements Item interface. +func (i Null) TryBytes() ([]byte, error) { + return nil, errors.New("can't convert Null to ByteArray") +} + +// TryInteger implements Item interface. +func (i Null) TryInteger() (*big.Int, error) { + return nil, errors.New("can't convert Null to Integer") +} + +// Equals implements Item interface. +func (i Null) Equals(s Item) bool { + _, ok := s.(Null) + return ok +} + +// Type implements Item interface. +func (i Null) Type() Type { return AnyT } + +// Convert implements Item interface. +func (i Null) Convert(typ Type) (Item, error) { + if typ == AnyT || !typ.IsValid() { + return nil, errInvalidConversion + } + return i, nil +} + +// BigInteger represents a big integer on the stack. +type BigInteger struct { + value *big.Int +} + +// NewBigInteger returns an new BigInteger object. +func NewBigInteger(value *big.Int) *BigInteger { + if value.BitLen() > MaxBigIntegerSizeBits { + panic("integer is too big") + } + return &BigInteger{ + value: value, + } +} + +// Bytes converts i to a slice of bytes. +func (i *BigInteger) Bytes() []byte { + return bigint.ToBytes(i.value) +} + +// Bool implements Item interface. +func (i *BigInteger) Bool() bool { + return i.value.Sign() != 0 +} + +// TryBytes implements Item interface. +func (i *BigInteger) TryBytes() ([]byte, error) { + return i.Bytes(), nil +} + +// TryInteger implements Item interface. +func (i *BigInteger) TryInteger() (*big.Int, error) { + return i.value, nil +} + +// Equals implements Item interface. +func (i *BigInteger) Equals(s Item) bool { + if i == s { + return true + } else if s == nil { + return false + } + val, ok := s.(*BigInteger) + if ok { + return i.value.Cmp(val.value) == 0 + } + bs, err := s.TryBytes() + return err == nil && bytes.Equal(i.Bytes(), bs) +} + +// Value implements Item interface. +func (i *BigInteger) Value() interface{} { + return i.value +} + +func (i *BigInteger) String() string { + return "BigInteger" +} + +// Dup implements Item interface. +func (i *BigInteger) Dup() Item { + n := new(big.Int) + return &BigInteger{n.Set(i.value)} +} + +// Type implements Item interface. +func (i *BigInteger) Type() Type { return IntegerT } + +// Convert implements Item interface. +func (i *BigInteger) Convert(typ Type) (Item, error) { + return convertPrimitive(i, typ) +} + +// MarshalJSON implements the json.Marshaler interface. +func (i *BigInteger) MarshalJSON() ([]byte, error) { + return json.Marshal(i.value) +} + +// Bool represents a boolean Item. +type Bool struct { + value bool +} + +// NewBool returns an new Bool object. +func NewBool(val bool) *Bool { + return &Bool{ + value: val, + } +} + +// Value implements Item interface. +func (i *Bool) Value() interface{} { + return i.value +} + +// MarshalJSON implements the json.Marshaler interface. +func (i *Bool) MarshalJSON() ([]byte, error) { + return json.Marshal(i.value) +} + +func (i *Bool) String() string { + return "Boolean" +} + +// Dup implements Item interface. +func (i *Bool) Dup() Item { + return &Bool{i.value} +} + +// Bool implements Item interface. +func (i *Bool) Bool() bool { return i.value } + +// Bytes converts Bool to bytes. +func (i *Bool) Bytes() []byte { + if i.value { + return []byte{1} + } + return []byte{0} +} + +// TryBytes implements Item interface. +func (i *Bool) TryBytes() ([]byte, error) { + return i.Bytes(), nil +} + +// TryInteger implements Item interface. +func (i *Bool) TryInteger() (*big.Int, error) { + if i.value { + return big.NewInt(1), nil + } + return big.NewInt(0), nil +} + +// Equals implements Item interface. +func (i *Bool) Equals(s Item) bool { + if i == s { + return true + } else if s == nil { + return false + } + val, ok := s.(*Bool) + if ok { + return i.value == val.value + } + bs, err := s.TryBytes() + return err == nil && bytes.Equal(i.Bytes(), bs) +} + +// Type implements Item interface. +func (i *Bool) Type() Type { return BooleanT } + +// Convert implements Item interface. +func (i *Bool) Convert(typ Type) (Item, error) { + return convertPrimitive(i, typ) +} + +// ByteArray represents a byte array on the stack. +type ByteArray struct { + value []byte +} + +// NewByteArray returns an new ByteArray object. +func NewByteArray(b []byte) *ByteArray { + return &ByteArray{ + value: b, + } +} + +// Value implements Item interface. +func (i *ByteArray) Value() interface{} { + return i.value +} + +// MarshalJSON implements the json.Marshaler interface. +func (i *ByteArray) MarshalJSON() ([]byte, error) { + return json.Marshal(hex.EncodeToString(i.value)) +} + +func (i *ByteArray) String() string { + return "ByteArray" +} + +// Bool implements Item interface. +func (i *ByteArray) Bool() bool { + if len(i.value) > MaxBigIntegerSizeBits/8 { + return true + } + for _, b := range i.value { + if b != 0 { + return true + } + } + return false +} + +// TryBytes implements Item interface. +func (i *ByteArray) TryBytes() ([]byte, error) { + val := make([]byte, len(i.value)) + copy(val, i.value) + return val, nil +} + +// TryInteger implements Item interface. +func (i *ByteArray) TryInteger() (*big.Int, error) { + if len(i.value) > MaxBigIntegerSizeBits/8 { + return nil, errors.New("integer is too big") + } + return bigint.FromBytes(i.value), nil +} + +// Equals implements Item interface. +func (i *ByteArray) Equals(s Item) bool { + if i == s { + return true + } else if s == nil { + return false + } + bs, err := s.TryBytes() + return err == nil && bytes.Equal(i.value, bs) +} + +// Dup implements Item interface. +func (i *ByteArray) Dup() Item { + a := make([]byte, len(i.value)) + copy(a, i.value) + return &ByteArray{a} +} + +// Type implements Item interface. +func (i *ByteArray) Type() Type { return ByteArrayT } + +// Convert implements Item interface. +func (i *ByteArray) Convert(typ Type) (Item, error) { + return convertPrimitive(i, typ) +} + +// Array represents a new Array object. +type Array struct { + value []Item +} + +// NewArray returns a new Array object. +func NewArray(items []Item) *Array { + return &Array{ + value: items, + } +} + +// Value implements Item interface. +func (i *Array) Value() interface{} { + return i.value +} + +// Remove removes element at `pos` index from Array value. +// It will panics on bad index. +func (i *Array) Remove(pos int) { + i.value = append(i.value[:pos], i.value[pos+1:]...) +} + +// Append adds Item at the end of Array value. +func (i *Array) Append(item Item) { + i.value = append(i.value, item) +} + +// Clear removes all elements from Array item value. +func (i *Array) Clear() { + i.value = i.value[:0] +} + +// Len returns length of Array value. +func (i *Array) Len() int { + return len(i.value) +} + +// MarshalJSON implements the json.Marshaler interface. +func (i *Array) MarshalJSON() ([]byte, error) { + return json.Marshal(i.value) +} + +func (i *Array) String() string { + return "Array" +} + +// Bool implements Item interface. +func (i *Array) Bool() bool { return true } + +// TryBytes implements Item interface. +func (i *Array) TryBytes() ([]byte, error) { + return nil, errors.New("can't convert Array to ByteArray") +} + +// TryInteger implements Item interface. +func (i *Array) TryInteger() (*big.Int, error) { + return nil, errors.New("can't convert Array to Integer") +} + +// Equals implements Item interface. +func (i *Array) Equals(s Item) bool { + return i == s +} + +// Dup implements Item interface. +func (i *Array) Dup() Item { + // reference type + return i +} + +// Type implements Item interface. +func (i *Array) Type() Type { return ArrayT } + +// Convert implements Item interface. +func (i *Array) Convert(typ Type) (Item, error) { + switch typ { + case ArrayT: + return i, nil + case StructT: + arr := make([]Item, len(i.value)) + copy(arr, i.value) + return NewStruct(arr), nil + case BooleanT: + return NewBool(i.Bool()), nil + default: + return nil, errInvalidConversion + } +} + +// MapElement is a key-value pair of StackItems. +type MapElement struct { + Key Item + Value Item +} + +// Map represents Map object. It's ordered, so we use slice representation +// which should be fine for maps with less than 32 or so elements. Given that +// our VM has quite low limit of overall stack items, it should be good enough, +// but it can be extended with a real map for fast random access in the future +// if need be. +type Map struct { + value []MapElement +} + +// NewMap returns new Map object. +func NewMap() *Map { + return &Map{ + value: make([]MapElement, 0), + } +} + +// NewMapWithValue returns new Map object filled with specified value. +func NewMapWithValue(value []MapElement) *Map { + if value != nil { + return &Map{ + value: value, + } + } + return NewMap() +} + +// Value implements Item interface. +func (i *Map) Value() interface{} { + return i.value +} + +// Clear removes all elements from Map item value. +func (i *Map) Clear() { + i.value = i.value[:0] +} + +// Len returns length of Map value. +func (i *Map) Len() int { + return len(i.value) +} + +// Bool implements Item interface. +func (i *Map) Bool() bool { return true } + +// TryBytes implements Item interface. +func (i *Map) TryBytes() ([]byte, error) { + return nil, errors.New("can't convert Map to ByteArray") +} + +// TryInteger implements Item interface. +func (i *Map) TryInteger() (*big.Int, error) { + return nil, errors.New("can't convert Map to Integer") +} + +// Equals implements Item interface. +func (i *Map) Equals(s Item) bool { + return i == s +} + +func (i *Map) String() string { + return "Map" +} + +// Index returns an index of the key in map. +func (i *Map) Index(key Item) int { + for k := range i.value { + if i.value[k].Key.Equals(key) { + return k + } + } + return -1 +} + +// Has checks if map has specified key. +func (i *Map) Has(key Item) bool { + return i.Index(key) >= 0 +} + +// Dup implements Item interface. +func (i *Map) Dup() Item { + // reference type + return i +} + +// Type implements Item interface. +func (i *Map) Type() Type { return MapT } + +// Convert implements Item interface. +func (i *Map) Convert(typ Type) (Item, error) { + switch typ { + case MapT: + return i, nil + case BooleanT: + return NewBool(i.Bool()), nil + default: + return nil, errInvalidConversion + } +} + +// Add adds key-value pair to the map. +func (i *Map) Add(key, value Item) { + if !IsValidMapKey(key) { + panic("wrong key type") + } + index := i.Index(key) + if index >= 0 { + i.value[index].Value = value + } else { + i.value = append(i.value, MapElement{key, value}) + } +} + +// Drop removes given index from the map (no bounds check done here). +func (i *Map) Drop(index int) { + copy(i.value[index:], i.value[index+1:]) + i.value = i.value[:len(i.value)-1] +} + +// IsValidMapKey checks whether it's possible to use given Item as a Map +// key. +func IsValidMapKey(key Item) bool { + switch key.(type) { + case *Bool, *BigInteger, *ByteArray: + return true + default: + return false + } +} + +// Interop represents interop data on the stack. +type Interop struct { + value interface{} +} + +// NewInterop returns new Interop object. +func NewInterop(value interface{}) *Interop { + return &Interop{ + value: value, + } +} + +// Value implements Item interface. +func (i *Interop) Value() interface{} { + return i.value +} + +// String implements stringer interface. +func (i *Interop) String() string { + return "Interop" +} + +// Dup implements Item interface. +func (i *Interop) Dup() Item { + // reference type + return i +} + +// Bool implements Item interface. +func (i *Interop) Bool() bool { return true } + +// TryBytes implements Item interface. +func (i *Interop) TryBytes() ([]byte, error) { + return nil, errors.New("can't convert Interop to ByteArray") +} + +// TryInteger implements Item interface. +func (i *Interop) TryInteger() (*big.Int, error) { + return nil, errors.New("can't convert Interop to Integer") +} + +// Equals implements Item interface. +func (i *Interop) Equals(s Item) bool { + if i == s { + return true + } else if s == nil { + return false + } + val, ok := s.(*Interop) + return ok && i.value == val.value +} + +// Type implements Item interface. +func (i *Interop) Type() Type { return InteropT } + +// Convert implements Item interface. +func (i *Interop) Convert(typ Type) (Item, error) { + switch typ { + case InteropT: + return i, nil + case BooleanT: + return NewBool(i.Bool()), nil + default: + return nil, errInvalidConversion + } +} + +// MarshalJSON implements the json.Marshaler interface. +func (i *Interop) MarshalJSON() ([]byte, error) { + return json.Marshal(i.value) +} + +// Pointer represents VM-level instruction pointer. +type Pointer struct { + pos int + script []byte + hash util.Uint160 +} + +// NewPointer returns new pointer on the specified position. +func NewPointer(pos int, script []byte) *Pointer { + return &Pointer{ + pos: pos, + script: script, + hash: hash.Hash160(script), + } +} + +// String implements Item interface. +func (p *Pointer) String() string { + return "Pointer" +} + +// Value implements Item interface. +func (p *Pointer) Value() interface{} { + return p.pos +} + +// Dup implements Item interface. +func (p *Pointer) Dup() Item { + return &Pointer{ + pos: p.pos, + script: p.script, + hash: p.hash, + } +} + +// Bool implements Item interface. +func (p *Pointer) Bool() bool { + return true +} + +// TryBytes implements Item interface. +func (p *Pointer) TryBytes() ([]byte, error) { + return nil, errors.New("can't convert Pointer to ByteArray") +} + +// TryInteger implements Item interface. +func (p *Pointer) TryInteger() (*big.Int, error) { + return nil, errors.New("can't convert Pointer to Integer") +} + +// Equals implements Item interface. +func (p *Pointer) Equals(s Item) bool { + if p == s { + return true + } + ptr, ok := s.(*Pointer) + return ok && p.pos == ptr.pos && p.hash == ptr.hash +} + +// Type implements Item interface. +func (p *Pointer) Type() Type { + return PointerT +} + +// Convert implements Item interface. +func (p *Pointer) Convert(typ Type) (Item, error) { + switch typ { + case PointerT: + return p, nil + case BooleanT: + return NewBool(p.Bool()), nil + default: + return nil, errInvalidConversion + } +} + +// ScriptHash returns pointer item hash +func (p *Pointer) ScriptHash() util.Uint160 { + return p.hash +} + +// Position returns pointer item position +func (p *Pointer) Position() int { + return p.pos +} + +// Buffer represents represents Buffer stack item. +type Buffer struct { + value []byte +} + +// NewBuffer returns a new Buffer object. +func NewBuffer(b []byte) *Buffer { + return &Buffer{ + value: b, + } +} + +// Value implements Item interface. +func (i *Buffer) Value() interface{} { + return i.value +} + +// String implements fmt.Stringer interface. +func (i *Buffer) String() string { + return "Buffer" +} + +// Bool implements Item interface. +func (i *Buffer) Bool() bool { + return true +} + +// TryBytes implements Item interface. +func (i *Buffer) TryBytes() ([]byte, error) { + val := make([]byte, len(i.value)) + copy(val, i.value) + return val, nil +} + +// TryInteger implements Item interface. +func (i *Buffer) TryInteger() (*big.Int, error) { + return nil, errors.New("can't convert Buffer to Integer") +} + +// Equals implements Item interface. +func (i *Buffer) Equals(s Item) bool { + return i == s +} + +// Dup implements Item interface. +func (i *Buffer) Dup() Item { + return i +} + +// MarshalJSON implements the json.Marshaler interface. +func (i *Buffer) MarshalJSON() ([]byte, error) { + return json.Marshal(hex.EncodeToString(i.value)) +} + +// Type implements Item interface. +func (i *Buffer) Type() Type { return BufferT } + +// Convert implements Item interface. +func (i *Buffer) Convert(typ Type) (Item, error) { + switch typ { + case BooleanT: + return NewBool(i.Bool()), nil + case BufferT: + return i, nil + case ByteArrayT: + val := make([]byte, len(i.value)) + copy(val, i.value) + return NewByteArray(val), nil + case IntegerT: + if len(i.value) > MaxBigIntegerSizeBits/8 { + return nil, errInvalidConversion + } + return NewBigInteger(bigint.FromBytes(i.value)), nil + default: + return nil, errInvalidConversion + } +} + +// Len returns length of Buffer value. +func (i *Buffer) Len() int { + return len(i.value) +} diff --git a/pkg/vm/stackitem/item_test.go b/pkg/vm/stackitem/item_test.go new file mode 100644 index 000000000..b7a59827b --- /dev/null +++ b/pkg/vm/stackitem/item_test.go @@ -0,0 +1,414 @@ +package stackitem + +import ( + "math/big" + "testing" + + "github.com/stretchr/testify/assert" +) + +var makeStackItemTestCases = []struct { + input interface{} + result Item +}{ + { + input: int64(3), + result: &BigInteger{value: big.NewInt(3)}, + }, + { + input: int16(3), + result: &BigInteger{value: big.NewInt(3)}, + }, + { + input: 3, + result: &BigInteger{value: big.NewInt(3)}, + }, + { + input: uint8(3), + result: &BigInteger{value: big.NewInt(3)}, + }, + { + input: uint16(3), + result: &BigInteger{value: big.NewInt(3)}, + }, + { + input: uint32(3), + result: &BigInteger{value: big.NewInt(3)}, + }, + { + input: uint64(3), + result: &BigInteger{value: big.NewInt(3)}, + }, + { + input: big.NewInt(3), + result: &BigInteger{value: big.NewInt(3)}, + }, + { + input: []byte{1, 2, 3, 4}, + result: &ByteArray{value: []byte{1, 2, 3, 4}}, + }, + { + input: []byte{}, + result: &ByteArray{value: []byte{}}, + }, + { + input: "bla", + result: &ByteArray{value: []byte("bla")}, + }, + { + input: "", + result: &ByteArray{value: []byte{}}, + }, + { + input: true, + result: &Bool{value: true}, + }, + { + input: false, + result: &Bool{value: false}, + }, + { + input: []Item{&BigInteger{value: big.NewInt(3)}, &ByteArray{value: []byte{1, 2, 3}}}, + result: &Array{value: []Item{&BigInteger{value: big.NewInt(3)}, &ByteArray{value: []byte{1, 2, 3}}}}, + }, + { + input: []int{1, 2, 3}, + result: &Array{value: []Item{&BigInteger{value: big.NewInt(1)}, &BigInteger{value: big.NewInt(2)}, &BigInteger{value: big.NewInt(3)}}}, + }, +} + +var makeStackItemErrorCases = []struct { + input interface{} +}{ + { + input: nil, + }, +} + +func TestMakeStackItem(t *testing.T) { + for _, testCase := range makeStackItemTestCases { + assert.Equal(t, testCase.result, Make(testCase.input)) + } + for _, errorCase := range makeStackItemErrorCases { + assert.Panics(t, func() { Make(errorCase.input) }) + } +} + +var stringerTestCases = []struct { + input Item + result string +}{ + { + input: NewStruct([]Item{}), + result: "Struct", + }, + { + input: NewBigInteger(big.NewInt(3)), + result: "BigInteger", + }, + { + input: NewBool(true), + result: "Boolean", + }, + { + input: NewByteArray([]byte{}), + result: "ByteArray", + }, + { + input: NewArray([]Item{}), + result: "Array", + }, + { + input: NewMap(), + result: "Map", + }, + { + input: NewInterop(nil), + result: "Interop", + }, + { + input: NewPointer(0, nil), + result: "Pointer", + }, +} + +func TestStringer(t *testing.T) { + for _, testCase := range stringerTestCases { + assert.Equal(t, testCase.result, testCase.input.String()) + } +} + +var equalsTestCases = map[string][]struct { + item1 Item + item2 Item + result bool +}{ + "struct": { + { + item1: NewStruct(nil), + item2: nil, + result: false, + }, + { + item1: NewStruct(nil), + item2: NewBigInteger(big.NewInt(1)), + result: false, + }, + { + item1: NewStruct(nil), + item2: NewStruct([]Item{NewBigInteger(big.NewInt(1))}), + result: false, + }, + { + item1: NewStruct([]Item{NewBigInteger(big.NewInt(1))}), + item2: NewStruct([]Item{NewBigInteger(big.NewInt(2))}), + result: false, + }, + { + item1: NewStruct([]Item{NewBigInteger(big.NewInt(1))}), + item2: NewStruct([]Item{NewBigInteger(big.NewInt(1))}), + result: true, + }, + }, + "bigint": { + { + item1: NewBigInteger(big.NewInt(2)), + item2: nil, + result: false, + }, + { + item1: NewBigInteger(big.NewInt(2)), + item2: NewBigInteger(big.NewInt(2)), + result: true, + }, + { + item1: NewBigInteger(big.NewInt(2)), + item2: NewBool(false), + result: false, + }, + { + item1: NewBigInteger(big.NewInt(0)), + item2: NewBool(false), + result: false, + }, + { + item1: NewBigInteger(big.NewInt(2)), + item2: Make(int32(2)), + result: true, + }, + }, + "bool": { + { + item1: NewBool(true), + item2: nil, + result: false, + }, + { + item1: NewBool(true), + item2: NewBool(true), + result: true, + }, + { + item1: NewBool(true), + item2: NewBigInteger(big.NewInt(1)), + result: true, + }, + { + item1: NewBool(true), + item2: NewBool(false), + result: false, + }, + { + item1: NewBool(true), + item2: Make(true), + result: true, + }, + }, + "bytearray": { + { + item1: NewByteArray(nil), + item2: nil, + result: false, + }, + { + item1: NewByteArray([]byte{1, 2, 3}), + item2: NewByteArray([]byte{1, 2, 3}), + result: true, + }, + { + item1: NewByteArray([]byte{1}), + item2: NewBigInteger(big.NewInt(1)), + result: true, + }, + { + item1: NewByteArray([]byte{1, 2, 3}), + item2: NewByteArray([]byte{1, 2, 4}), + result: false, + }, + { + item1: NewByteArray([]byte{1, 2, 3}), + item2: Make([]byte{1, 2, 3}), + result: true, + }, + }, + "array": { + { + item1: NewArray(nil), + item2: nil, + result: false, + }, + { + item1: NewArray([]Item{&BigInteger{big.NewInt(1)}, &BigInteger{big.NewInt(2)}, &BigInteger{big.NewInt(3)}}), + item2: NewArray([]Item{&BigInteger{big.NewInt(1)}, &BigInteger{big.NewInt(2)}, &BigInteger{big.NewInt(3)}}), + result: false, + }, + { + item1: NewArray([]Item{&BigInteger{big.NewInt(1)}}), + item2: NewBigInteger(big.NewInt(1)), + result: false, + }, + { + item1: NewArray([]Item{&BigInteger{big.NewInt(1)}, &BigInteger{big.NewInt(2)}, &BigInteger{big.NewInt(3)}}), + item2: NewArray([]Item{&BigInteger{big.NewInt(1)}, &BigInteger{big.NewInt(2)}, &BigInteger{big.NewInt(4)}}), + result: false, + }, + }, + "map": { + { + item1: NewMap(), + item2: nil, + result: false, + }, + { + item1: NewMap(), + item2: NewMap(), + result: false, + }, + { + item1: &Map{value: []MapElement{{NewByteArray([]byte("first")), NewBigInteger(big.NewInt(1))}, {NewBool(true), NewByteArray([]byte{2})}}}, + item2: &Map{value: []MapElement{{NewByteArray([]byte("first")), NewBigInteger(big.NewInt(1))}, {NewBool(true), NewByteArray([]byte{2})}}}, + result: false, + }, + { + item1: &Map{value: []MapElement{{NewByteArray([]byte("first")), NewBigInteger(big.NewInt(1))}, {NewBool(true), NewByteArray([]byte{2})}}}, + item2: &Map{value: []MapElement{{NewByteArray([]byte("first")), NewBigInteger(big.NewInt(1))}, {NewBool(true), NewByteArray([]byte{3})}}}, + result: false, + }, + }, + "interop": { + { + item1: NewInterop(nil), + item2: nil, + result: false, + }, + { + item1: NewInterop(nil), + item2: NewInterop(nil), + result: true, + }, + { + item1: NewInterop(2), + item2: NewInterop(3), + result: false, + }, + { + item1: NewInterop(3), + item2: NewInterop(3), + result: true, + }, + }, + "pointer": { + { + item1: NewPointer(0, []byte{}), + result: false, + }, + { + item1: NewPointer(1, []byte{1}), + item2: NewPointer(1, []byte{1}), + result: true, + }, + { + item1: NewPointer(1, []byte{1}), + item2: NewPointer(2, []byte{1}), + result: false, + }, + { + item1: NewPointer(1, []byte{1}), + item2: NewPointer(1, []byte{2}), + result: false, + }, + { + item1: NewPointer(0, []byte{}), + item2: NewBigInteger(big.NewInt(0)), + result: false, + }, + }, +} + +func TestEquals(t *testing.T) { + for name, testBatch := range equalsTestCases { + for _, testCase := range testBatch { + t.Run(name, func(t *testing.T) { + assert.Equal(t, testCase.result, testCase.item1.Equals(testCase.item2)) + // Reference equals + assert.Equal(t, true, testCase.item1.Equals(testCase.item1)) + }) + } + } +} + +var marshalJSONTestCases = []struct { + input Item + result []byte +}{ + { + input: NewBigInteger(big.NewInt(2)), + result: []byte(`2`), + }, + { + input: NewBool(true), + result: []byte(`true`), + }, + { + input: NewByteArray([]byte{1, 2, 3}), + result: []byte(`"010203"`), + }, + { + input: NewBuffer([]byte{1, 2, 3}), + result: []byte(`"010203"`), + }, + { + input: &Array{value: []Item{&BigInteger{value: big.NewInt(3)}, &ByteArray{value: []byte{1, 2, 3}}}}, + result: []byte(`[3,"010203"]`), + }, + { + input: &Interop{value: 3}, + result: []byte(`3`), + }, +} + +func TestMarshalJSON(t *testing.T) { + var ( + actual []byte + err error + ) + for _, testCase := range marshalJSONTestCases { + switch testCase.input.(type) { + case *BigInteger: + actual, err = testCase.input.(*BigInteger).MarshalJSON() + case *Bool: + actual, err = testCase.input.(*Bool).MarshalJSON() + case *ByteArray: + actual, err = testCase.input.(*ByteArray).MarshalJSON() + case *Array: + actual, err = testCase.input.(*Array).MarshalJSON() + case *Interop: + actual, err = testCase.input.(*Interop).MarshalJSON() + default: + continue + } + + assert.NoError(t, err) + assert.Equal(t, testCase.result, actual) + } +} diff --git a/pkg/vm/stackitem/serialization.go b/pkg/vm/stackitem/serialization.go new file mode 100644 index 000000000..e6cd51d90 --- /dev/null +++ b/pkg/vm/stackitem/serialization.go @@ -0,0 +1,134 @@ +package stackitem + +import ( + "errors" + "math/big" + + "github.com/nspcc-dev/neo-go/pkg/encoding/bigint" + "github.com/nspcc-dev/neo-go/pkg/io" +) + +// SerializeItem encodes given Item into the byte slice. +func SerializeItem(item Item) ([]byte, error) { + w := io.NewBufBinWriter() + EncodeBinaryStackItem(item, w.BinWriter) + if w.Err != nil { + return nil, w.Err + } + return w.Bytes(), nil +} + +// EncodeBinaryStackItem encodes given Item into the given BinWriter. It's +// similar to io.Serializable's EncodeBinary, but works with Item +// interface. +func EncodeBinaryStackItem(item Item, w *io.BinWriter) { + serializeItemTo(item, w, make(map[Item]bool)) +} + +func serializeItemTo(item Item, w *io.BinWriter, seen map[Item]bool) { + if seen[item] { + w.Err = errors.New("recursive structures can't be serialized") + return + } + + switch t := item.(type) { + case *ByteArray: + w.WriteBytes([]byte{byte(ByteArrayT)}) + w.WriteVarBytes(t.Value().([]byte)) + case *Buffer: + w.WriteBytes([]byte{byte(BufferT)}) + w.WriteVarBytes(t.Value().([]byte)) + case *Bool: + w.WriteBytes([]byte{byte(BooleanT)}) + w.WriteBool(t.Value().(bool)) + case *BigInteger: + w.WriteBytes([]byte{byte(IntegerT)}) + w.WriteVarBytes(bigint.ToBytes(t.Value().(*big.Int))) + case *Interop: + w.Err = errors.New("interop item can't be serialized") + case *Array, *Struct: + seen[item] = true + + _, isArray := t.(*Array) + if isArray { + w.WriteBytes([]byte{byte(ArrayT)}) + } else { + w.WriteBytes([]byte{byte(StructT)}) + } + + arr := t.Value().([]Item) + w.WriteVarUint(uint64(len(arr))) + for i := range arr { + serializeItemTo(arr[i], w, seen) + } + case *Map: + seen[item] = true + + w.WriteBytes([]byte{byte(MapT)}) + w.WriteVarUint(uint64(len(t.Value().([]MapElement)))) + for i := range t.Value().([]MapElement) { + serializeItemTo(t.Value().([]MapElement)[i].Key, w, seen) + serializeItemTo(t.Value().([]MapElement)[i].Value, w, seen) + } + } +} + +// DeserializeItem decodes Item from the given byte slice. +func DeserializeItem(data []byte) (Item, error) { + r := io.NewBinReaderFromBuf(data) + item := DecodeBinaryStackItem(r) + if r.Err != nil { + return nil, r.Err + } + return item, nil +} + +// DecodeBinaryStackItem decodes previously serialized Item from the given +// reader. It's similar to the io.Serializable's DecodeBinary(), but implemented +// as a function because Item itself is an interface. Caveat: always check +// reader's error value before using the returned Item. +func DecodeBinaryStackItem(r *io.BinReader) Item { + var t = Type(r.ReadB()) + if r.Err != nil { + return nil + } + + switch t { + case ByteArrayT: + data := r.ReadVarBytes() + return NewByteArray(data) + case BooleanT: + var b = r.ReadBool() + return NewBool(b) + case IntegerT: + data := r.ReadVarBytes() + num := bigint.FromBytes(data) + return NewBigInteger(num) + case ArrayT, StructT: + size := int(r.ReadVarUint()) + arr := make([]Item, size) + for i := 0; i < size; i++ { + arr[i] = DecodeBinaryStackItem(r) + } + + if t == ArrayT { + return NewArray(arr) + } + return NewStruct(arr) + case MapT: + size := int(r.ReadVarUint()) + m := NewMap() + for i := 0; i < size; i++ { + key := DecodeBinaryStackItem(r) + value := DecodeBinaryStackItem(r) + if r.Err != nil { + break + } + m.Add(key, value) + } + return m + default: + r.Err = errors.New("unknown type") + return nil + } +} diff --git a/pkg/vm/stackitem/type.go b/pkg/vm/stackitem/type.go new file mode 100644 index 000000000..303e28508 --- /dev/null +++ b/pkg/vm/stackitem/type.go @@ -0,0 +1,56 @@ +package stackitem + +// Type represents type of the stack item. +type Type byte + +// This block defines all known stack item types. +const ( + AnyT Type = 0x00 + PointerT Type = 0x10 + BooleanT Type = 0x20 + IntegerT Type = 0x21 + ByteArrayT Type = 0x28 + BufferT Type = 0x30 + ArrayT Type = 0x40 + StructT Type = 0x41 + MapT Type = 0x48 + InteropT Type = 0x60 +) + +// String implements fmt.Stringer interface. +func (t Type) String() string { + switch t { + case AnyT: + return "Any" + case PointerT: + return "Pointer" + case BooleanT: + return "Boolean" + case IntegerT: + return "Integer" + case ByteArrayT: + return "ByteArray" + case BufferT: + return "Buffer" + case ArrayT: + return "Array" + case StructT: + return "Struct" + case MapT: + return "Map" + case InteropT: + return "Interop" + default: + return "INVALID" + } +} + +// IsValid checks if s is a well defined stack item type. +func (t Type) IsValid() bool { + switch t { + case AnyT, PointerT, BooleanT, IntegerT, ByteArrayT, BufferT, ArrayT, StructT, MapT, InteropT: + return true + default: + return false + } +} diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index 1b3d93004..6d0c36f29 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -12,9 +12,10 @@ import ( "unicode/utf8" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" + "github.com/nspcc-dev/neo-go/pkg/encoding/bigint" "github.com/nspcc-dev/neo-go/pkg/util" - "github.com/nspcc-dev/neo-go/pkg/vm/emit" "github.com/nspcc-dev/neo-go/pkg/vm/opcode" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/pkg/errors" ) @@ -52,14 +53,11 @@ const ( // MaxInvocationStackSize is the maximum size of an invocation stack. MaxInvocationStackSize = 1024 - // MaxBigIntegerSizeBits is the maximum size of BigInt item in bits. - MaxBigIntegerSizeBits = 32 * 8 - // MaxStackSize is the maximum number of items allowed to be // on all stacks at once. MaxStackSize = 2 * 1024 - maxSHLArg = MaxBigIntegerSizeBits + maxSHLArg = stackitem.MaxBigIntegerSizeBits ) // VM represents the virtual machine. @@ -165,7 +163,7 @@ func (v *VM) GetPublicKeys() map[string]*keys.PublicKey { } // LoadArgs loads in the arguments used in the Mian entry point. -func (v *VM) LoadArgs(method []byte, args []StackItem) { +func (v *VM) LoadArgs(method []byte, args []stackitem.Item) { if len(args) > 0 { v.estack.PushVal(args) } @@ -513,7 +511,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro } if op <= opcode.PUSHINT256 { - v.estack.PushVal(emit.BytesToInt(parameter)) + v.estack.PushVal(bigint.FromBytes(parameter)) return } @@ -534,22 +532,22 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro if n < 0 || int(n) > len(ctx.prog) { panic(fmt.Sprintf("invalid pointer offset (%d)", n)) } - ptr := NewPointerItem(int(n), ctx.prog) + ptr := stackitem.NewPointer(int(n), ctx.prog) v.estack.PushVal(ptr) case opcode.PUSHNULL: - v.estack.PushVal(NullItem{}) + v.estack.PushVal(stackitem.Null{}) case opcode.ISNULL: - res := v.estack.Pop().value.Equals(NullItem{}) + res := v.estack.Pop().value.Equals(stackitem.Null{}) v.estack.PushVal(res) case opcode.ISTYPE: res := v.estack.Pop().Item() - v.estack.PushVal(res.Type() == StackItemType(parameter[0])) + v.estack.PushVal(res.Type() == stackitem.Type(parameter[0])) case opcode.CONVERT: - typ := StackItemType(parameter[0]) + typ := stackitem.Type(parameter[0]) item := v.estack.Pop().Item() result, err := item.Convert(typ) if err != nil { @@ -637,7 +635,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro if n < 0 || n > MaxItemSize { panic("invalid size") } - v.estack.PushVal(NewBufferItem(make([]byte, n))) + v.estack.PushVal(stackitem.NewBuffer(make([]byte, n))) case opcode.MEMCPY: n := toInt(v.estack.Pop().BigInt()) @@ -656,7 +654,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro if di < 0 { panic("invalid destination index") } - dst := v.estack.Pop().value.(*BufferItem).value + dst := v.estack.Pop().value.(*stackitem.Buffer).Value().([]byte) if sum := si + n; sum < 0 || sum > len(dst) { panic("size is too big") } @@ -669,7 +667,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro panic(fmt.Sprintf("too big item: %d", l)) } ab := append(a, b...) - v.estack.PushVal(NewBufferItem(ab)) + v.estack.PushVal(stackitem.NewBuffer(ab)) case opcode.SUBSTR: l := int(v.estack.Pop().BigInt().Int64()) @@ -685,7 +683,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro if last > len(s) { panic("invalid offset") } - v.estack.PushVal(NewBufferItem(s[o:last])) + v.estack.PushVal(stackitem.NewBuffer(s[o:last])) case opcode.LEFT: l := int(v.estack.Pop().BigInt().Int64()) @@ -696,7 +694,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro if t := len(s); l > t { panic("size is too big") } - v.estack.PushVal(NewBufferItem(s[:l])) + v.estack.PushVal(stackitem.NewBuffer(s[:l])) case opcode.RIGHT: l := int(v.estack.Pop().BigInt().Int64()) @@ -704,7 +702,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro panic("negative length") } s := v.estack.Pop().Bytes() - v.estack.PushVal(NewBufferItem(s[len(s)-l:])) + v.estack.PushVal(stackitem.NewBuffer(s[len(s)-l:])) case opcode.DEPTH: v.estack.PushVal(v.estack.Len()) @@ -801,7 +799,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro // inplace e := v.estack.Peek(0) i := e.BigInt() - e.value = makeStackItem(i.Not(i)) + e.value = stackitem.Make(i.Not(i)) case opcode.AND: b := v.estack.Pop().BigInt() @@ -977,7 +975,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro // Object operations case opcode.NEWARRAY0: - v.estack.PushVal(&ArrayItem{[]StackItem{}}) + v.estack.PushVal(stackitem.NewArray([]stackitem.Item{})) case opcode.NEWARRAY, opcode.NEWARRAYT: item := v.estack.Pop() @@ -985,15 +983,15 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro if n > MaxArraySize { panic("too long array") } - typ := AnyT + typ := stackitem.AnyT if op == opcode.NEWARRAYT { - typ = StackItemType(parameter[0]) + typ = stackitem.Type(parameter[0]) } items := makeArrayOfType(int(n), typ) - v.estack.PushVal(&ArrayItem{items}) + v.estack.PushVal(stackitem.NewArray(items)) case opcode.NEWSTRUCT0: - v.estack.PushVal(&StructItem{[]StackItem{}}) + v.estack.PushVal(stackitem.NewStruct([]stackitem.Item{})) case opcode.NEWSTRUCT: item := v.estack.Pop() @@ -1001,8 +999,8 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro if n > MaxArraySize { panic("too long struct") } - items := makeArrayOfType(int(n), AnyT) - v.estack.PushVal(&StructItem{items}) + items := makeArrayOfType(int(n), stackitem.AnyT) + v.estack.PushVal(stackitem.NewStruct(items)) case opcode.APPEND: itemElem := v.estack.Pop() @@ -1011,20 +1009,16 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro val := cloneIfStruct(itemElem.value) switch t := arrElem.value.(type) { - case *ArrayItem: - arr := t.Value().([]StackItem) - if len(arr) >= MaxArraySize { + case *stackitem.Array: + if t.Len() >= MaxArraySize { panic("too long array") } - arr = append(arr, val) - t.value = arr - case *StructItem: - arr := t.Value().([]StackItem) - if len(arr) >= MaxArraySize { + t.Append(val) + case *stackitem.Struct: + if t.Len() >= MaxArraySize { panic("too long struct") } - arr = append(arr, val) - t.value = arr + t.Append(val) default: panic("APPEND: not of underlying type Array") } @@ -1037,7 +1031,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro panic("OPACK: invalid length") } - items := make([]StackItem, n) + items := make([]stackitem.Item, n) for i := 0; i < n; i++ { items[i] = v.estack.Pop().value } @@ -1060,20 +1054,20 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro index := int(key.BigInt().Int64()) switch t := obj.value.(type) { - // Struct and Array items have their underlying value as []StackItem. - case *ArrayItem, *StructItem: - arr := t.Value().([]StackItem) + // Struct and Array items have their underlying value as []Item. + case *stackitem.Array, *stackitem.Struct: + arr := t.Value().([]stackitem.Item) if index < 0 || index >= len(arr) { panic("PICKITEM: invalid index") } item := arr[index].Dup() v.estack.PushVal(item) - case *MapItem: + case *stackitem.Map: index := t.Index(key.Item()) if index < 0 { panic("invalid key") } - v.estack.Push(&Element{value: t.value[index].Value.Dup()}) + v.estack.Push(&Element{value: t.Value().([]stackitem.MapElement)[index].Value.Dup()}) default: arr := obj.Bytes() if index < 0 || index >= len(arr) { @@ -1091,9 +1085,9 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro obj := v.estack.Pop() switch t := obj.value.(type) { - // Struct and Array items have their underlying value as []StackItem. - case *ArrayItem, *StructItem: - arr := t.Value().([]StackItem) + // Struct and Array items have their underlying value as []Item. + case *stackitem.Array, *stackitem.Struct: + arr := t.Value().([]stackitem.Item) index := int(key.BigInt().Int64()) if index < 0 || index >= len(arr) { panic("SETITEM: invalid index") @@ -1101,18 +1095,18 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro v.refs.Remove(arr[index]) arr[index] = item v.refs.Add(arr[index]) - case *MapItem: + case *stackitem.Map: if i := t.Index(key.value); i >= 0 { - v.refs.Remove(t.value[i].Value) - } else if len(t.value) >= MaxArraySize { + v.refs.Remove(t.Value().([]stackitem.MapElement)[i].Value) + } else if t.Len() >= MaxArraySize { panic("too big map") } t.Add(key.value, item) v.refs.Add(item) - case *BufferItem: + case *stackitem.Buffer: index := toInt(key.BigInt()) - if index < 0 || index >= len(t.value) { + if index < 0 || index >= t.Len() { panic("invalid index") } bi, err := item.TryInteger() @@ -1120,7 +1114,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro if err != nil || b < math.MinInt8 || b > math.MaxUint8 { panic("invalid value") } - t.value[index] = byte(b) + t.Value().([]byte)[index] = byte(b) default: panic(fmt.Sprintf("SETITEM: invalid item type %s", t)) @@ -1129,14 +1123,14 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro case opcode.REVERSEITEMS: item := v.estack.Pop() switch t := item.value.(type) { - case *ArrayItem, *StructItem: - a := t.Value().([]StackItem) + case *stackitem.Array, *stackitem.Struct: + a := t.Value().([]stackitem.Item) for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 { a[i], a[j] = a[j], a[i] } - case *BufferItem: - for i, j := 0, len(t.value)-1; i < j; i, j = i+1, j-1 { - t.value[i], t.value[j] = t.value[j], t.value[i] + case *stackitem.Buffer: + for i, j := 0, t.Len()-1; i < j; i, j = i+1, j-1 { + t.Value().([]byte)[i], t.Value().([]byte)[j] = t.Value().([]byte)[j], t.Value().([]byte)[i] } default: panic(fmt.Sprintf("invalid item type %s", t)) @@ -1147,29 +1141,27 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro elem := v.estack.Pop() switch t := elem.value.(type) { - case *ArrayItem: - a := t.value + case *stackitem.Array: + a := t.Value().([]stackitem.Item) k := int(key.BigInt().Int64()) if k < 0 || k >= len(a) { panic("REMOVE: invalid index") } v.refs.Remove(a[k]) - a = append(a[:k], a[k+1:]...) - t.value = a - case *StructItem: - a := t.value + t.Remove(k) + case *stackitem.Struct: + a := t.Value().([]stackitem.Item) k := int(key.BigInt().Int64()) if k < 0 || k >= len(a) { panic("REMOVE: invalid index") } v.refs.Remove(a[k]) - a = append(a[:k], a[k+1:]...) - t.value = a - case *MapItem: + t.Remove(k) + case *stackitem.Map: index := t.Index(key.Item()) // NEO 2.0 doesn't error on missing key. if index >= 0 { - v.refs.Remove(t.value[index].Value) + v.refs.Remove(t.Value().([]stackitem.MapElement)[index].Value) t.Drop(index) } default: @@ -1179,21 +1171,21 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro case opcode.CLEARITEMS: elem := v.estack.Pop() switch t := elem.value.(type) { - case *ArrayItem: - for _, item := range t.value { + case *stackitem.Array: + for _, item := range t.Value().([]stackitem.Item) { v.refs.Remove(item) } - t.value = t.value[:0] - case *StructItem: - for _, item := range t.value { + t.Clear() + case *stackitem.Struct: + for _, item := range t.Value().([]stackitem.Item) { v.refs.Remove(item) } - t.value = t.value[:0] - case *MapItem: - for i := range t.value { - v.refs.Remove(t.value[i].Value) + t.Clear() + case *stackitem.Map: + for i := range t.Value().([]stackitem.MapElement) { + v.refs.Remove(t.Value().([]stackitem.MapElement)[i].Value) } - t.value = t.value[:0] + t.Clear() default: panic("CLEARITEMS: invalid type") } @@ -1203,9 +1195,9 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro // Cause there is no native (byte) item type here, hence we need to check // the type of the item for array size operations. switch t := elem.Value().(type) { - case []StackItem: + case []stackitem.Item: v.estack.PushVal(len(t)) - case []MapElement: + case []stackitem.MapElement: v.estack.PushVal(len(t)) default: v.estack.PushVal(len(elem.Bytes())) @@ -1241,8 +1233,8 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro v.jumpIf(newCtx, offset, true) case opcode.CALLA: - ptr := v.estack.Pop().Item().(*PointerItem) - if ptr.hash != ctx.ScriptHash() { + ptr := v.estack.Pop().Item().(*stackitem.Pointer) + if ptr.ScriptHash() != ctx.ScriptHash() { panic("invalid script in pointer") } @@ -1251,7 +1243,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro newCtx.arguments = nil newCtx.rvcount = -1 v.istack.PushVal(newCtx) - v.jumpIf(newCtx, ptr.pos, true) + v.jumpIf(newCtx, ptr.Position(), true) case opcode.SYSCALL: interopID := GetInteropID(parameter) @@ -1291,7 +1283,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro } case opcode.NEWMAP: - v.estack.Push(&Element{value: NewMapItem()}) + v.estack.Push(&Element{value: stackitem.NewMap()}) case opcode.KEYS: item := v.estack.Pop() @@ -1299,14 +1291,14 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro panic("no argument") } - m, ok := item.value.(*MapItem) + m, ok := item.value.(*stackitem.Map) if !ok { panic("not a Map") } - arr := make([]StackItem, 0, len(m.value)) - for k := range m.value { - arr = append(arr, m.value[k].Key.Dup()) + arr := make([]stackitem.Item, 0, m.Len()) + for k := range m.Value().([]stackitem.MapElement) { + arr = append(arr, m.Value().([]stackitem.MapElement)[k].Key.Dup()) } v.estack.PushVal(arr) @@ -1316,18 +1308,18 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro panic("no argument") } - var arr []StackItem + var arr []stackitem.Item switch t := item.value.(type) { - case *ArrayItem, *StructItem: - src := t.Value().([]StackItem) - arr = make([]StackItem, len(src)) + case *stackitem.Array, *stackitem.Struct: + src := t.Value().([]stackitem.Item) + arr = make([]stackitem.Item, len(src)) for i := range src { arr[i] = cloneIfStruct(src[i]) } - case *MapItem: - arr = make([]StackItem, 0, len(t.value)) - for k := range t.value { - arr = append(arr, cloneIfStruct(t.value[k].Value)) + case *stackitem.Map: + arr = make([]stackitem.Item, 0, t.Len()) + for k := range t.Value().([]stackitem.MapElement) { + arr = append(arr, cloneIfStruct(t.Value().([]stackitem.MapElement)[k].Value)) } default: panic("not a Map, Array or Struct") @@ -1344,20 +1336,20 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro panic("no value found") } switch t := c.value.(type) { - case *ArrayItem, *StructItem: + case *stackitem.Array, *stackitem.Struct: index := key.BigInt().Int64() if index < 0 { panic("negative index") } v.estack.PushVal(index < int64(len(c.Array()))) - case *MapItem: + case *stackitem.Map: v.estack.PushVal(t.Has(key.Item())) - case *BufferItem: + case *stackitem.Buffer: index := key.BigInt().Int64() if index < 0 { panic("negative index") } - v.estack.PushVal(index < int64(len(t.value))) + v.estack.PushVal(index < int64(t.Len())) default: panic("wrong collection type") } @@ -1536,30 +1528,30 @@ func checkMultisig1(v *VM, h []byte, pkeys [][]byte, sig []byte) bool { return false } -func cloneIfStruct(item StackItem) StackItem { +func cloneIfStruct(item stackitem.Item) stackitem.Item { switch it := item.(type) { - case *StructItem: + case *stackitem.Struct: return it.Clone() default: return it } } -func makeArrayOfType(n int, typ StackItemType) []StackItem { +func makeArrayOfType(n int, typ stackitem.Type) []stackitem.Item { if !typ.IsValid() { panic(fmt.Sprintf("invalid stack item type: %d", typ)) } - items := make([]StackItem, n) + items := make([]stackitem.Item, n) for i := range items { switch typ { - case BooleanT: - items[i] = NewBoolItem(false) - case IntegerT: - items[i] = NewBigIntegerItem(big.NewInt(0)) - case ByteArrayT: - items[i] = NewByteArrayItem([]byte{}) + case stackitem.BooleanT: + items[i] = stackitem.NewBool(false) + case stackitem.IntegerT: + items[i] = stackitem.NewBigInteger(big.NewInt(0)) + case stackitem.ByteArrayT: + items[i] = stackitem.NewByteArray([]byte{}) default: - items[i] = NullItem{} + items[i] = stackitem.Null{} } } return items @@ -1569,7 +1561,7 @@ func validateMapKey(key *Element) { if key == nil { panic("no key found") } - if !isValidMapKey(key.Item()) { + if !stackitem.IsValidMapKey(key.Item()) { panic("key can't be a collection") } } diff --git a/pkg/vm/vm_test.go b/pkg/vm/vm_test.go index d7a1666ef..bf7d75702 100644 --- a/pkg/vm/vm_test.go +++ b/pkg/vm/vm_test.go @@ -9,11 +9,13 @@ import ( "math/rand" "testing" + "github.com/nspcc-dev/neo-go/pkg/encoding/bigint" "github.com/nspcc-dev/neo-go/pkg/internal/random" "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm/emit" "github.com/nspcc-dev/neo-go/pkg/vm/opcode" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -127,7 +129,7 @@ func TestPushBytes1to75(t *testing.T) { assert.Equal(t, 1, vm.estack.Len()) elem := vm.estack.Pop() - assert.IsType(t, &ByteArrayItem{}, elem.value) + assert.IsType(t, &stackitem.ByteArray{}, elem.value) assert.IsType(t, elem.Bytes(), b) assert.Equal(t, 0, vm.estack.Len()) @@ -180,7 +182,7 @@ func TestPUSHINT(t *testing.T) { t.Run(op.String(), func(t *testing.T) { buf := random.Bytes((8 << i) / 8) prog := append([]byte{byte(op)}, buf...) - runWithArgs(t, prog, emit.BytesToInt(buf)) + runWithArgs(t, prog, bigint.FromBytes(buf)) }) } } @@ -197,46 +199,46 @@ func TestPUSHNULL(t *testing.T) { func TestISNULL(t *testing.T) { prog := makeProgram(opcode.ISNULL) t.Run("Integer", getTestFuncForVM(prog, false, 1)) - t.Run("Null", getTestFuncForVM(prog, true, NullItem{})) + t.Run("Null", getTestFuncForVM(prog, true, stackitem.Null{})) } -func testISTYPE(t *testing.T, result bool, typ StackItemType, item StackItem) { +func testISTYPE(t *testing.T, result bool, typ stackitem.Type, item stackitem.Item) { prog := []byte{byte(opcode.ISTYPE), byte(typ)} runWithArgs(t, prog, result, item) } func TestISTYPE(t *testing.T) { t.Run("Integer", func(t *testing.T) { - testISTYPE(t, true, IntegerT, NewBigIntegerItem(big.NewInt(42))) - testISTYPE(t, false, IntegerT, NewByteArrayItem([]byte{})) + testISTYPE(t, true, stackitem.IntegerT, stackitem.NewBigInteger(big.NewInt(42))) + testISTYPE(t, false, stackitem.IntegerT, stackitem.NewByteArray([]byte{})) }) t.Run("Boolean", func(t *testing.T) { - testISTYPE(t, true, BooleanT, NewBoolItem(true)) - testISTYPE(t, false, BooleanT, NewByteArrayItem([]byte{})) + testISTYPE(t, true, stackitem.BooleanT, stackitem.NewBool(true)) + testISTYPE(t, false, stackitem.BooleanT, stackitem.NewByteArray([]byte{})) }) t.Run("ByteArray", func(t *testing.T) { - testISTYPE(t, true, ByteArrayT, NewByteArrayItem([]byte{})) - testISTYPE(t, false, ByteArrayT, NewBigIntegerItem(big.NewInt(42))) + testISTYPE(t, true, stackitem.ByteArrayT, stackitem.NewByteArray([]byte{})) + testISTYPE(t, false, stackitem.ByteArrayT, stackitem.NewBigInteger(big.NewInt(42))) }) t.Run("Array", func(t *testing.T) { - testISTYPE(t, true, ArrayT, NewArrayItem([]StackItem{})) - testISTYPE(t, false, ArrayT, NewByteArrayItem([]byte{})) + testISTYPE(t, true, stackitem.ArrayT, stackitem.NewArray([]stackitem.Item{})) + testISTYPE(t, false, stackitem.ArrayT, stackitem.NewByteArray([]byte{})) }) t.Run("Struct", func(t *testing.T) { - testISTYPE(t, true, StructT, NewStructItem([]StackItem{})) - testISTYPE(t, false, StructT, NewByteArrayItem([]byte{})) + testISTYPE(t, true, stackitem.StructT, stackitem.NewStruct([]stackitem.Item{})) + testISTYPE(t, false, stackitem.StructT, stackitem.NewByteArray([]byte{})) }) t.Run("Map", func(t *testing.T) { - testISTYPE(t, true, MapT, NewMapItem()) - testISTYPE(t, false, MapT, NewByteArrayItem([]byte{})) + testISTYPE(t, true, stackitem.MapT, stackitem.NewMap()) + testISTYPE(t, false, stackitem.MapT, stackitem.NewByteArray([]byte{})) }) t.Run("Interop", func(t *testing.T) { - testISTYPE(t, true, InteropT, NewInteropItem(42)) - testISTYPE(t, false, InteropT, NewByteArrayItem([]byte{})) + testISTYPE(t, true, stackitem.InteropT, stackitem.NewInterop(42)) + testISTYPE(t, false, stackitem.InteropT, stackitem.NewByteArray([]byte{})) }) } -func testCONVERT(to StackItemType, item, res StackItem) func(t *testing.T) { +func testCONVERT(to stackitem.Type, item, res stackitem.Item) func(t *testing.T) { return func(t *testing.T) { prog := []byte{byte(opcode.CONVERT), byte(to)} runWithArgs(t, prog, res, item) @@ -245,44 +247,46 @@ func testCONVERT(to StackItemType, item, res StackItem) func(t *testing.T) { func TestCONVERT(t *testing.T) { type convertTC struct { - item, res StackItem + item, res stackitem.Item } - arr := []StackItem{ - NewBigIntegerItem(big.NewInt(7)), - NewByteArrayItem([]byte{4, 8, 15}), + arr := []stackitem.Item{ + stackitem.NewBigInteger(big.NewInt(7)), + stackitem.NewByteArray([]byte{4, 8, 15}), } - m := NewMapItem() - m.Add(NewByteArrayItem([]byte{1}), NewByteArrayItem([]byte{2})) + m := stackitem.NewMap() + m.Add(stackitem.NewByteArray([]byte{1}), stackitem.NewByteArray([]byte{2})) - getName := func(item StackItem, typ StackItemType) string { return fmt.Sprintf("%s->%s", item, typ) } + getName := func(item stackitem.Item, typ stackitem.Type) string { + return fmt.Sprintf("%s->%s", item, typ) + } t.Run("->Bool", func(t *testing.T) { - testBool := func(a, b StackItem) func(t *testing.T) { - return testCONVERT(BooleanT, a, b) + testBool := func(a, b stackitem.Item) func(t *testing.T) { + return testCONVERT(stackitem.BooleanT, a, b) } - trueCases := []StackItem{ - NewBoolItem(true), NewBigIntegerItem(big.NewInt(11)), NewByteArrayItem([]byte{1, 2, 3}), - NewArrayItem(arr), NewArrayItem(nil), - NewStructItem(arr), NewStructItem(nil), - NewMapItem(), m, NewInteropItem(struct{}{}), - NewPointerItem(0, []byte{}), + trueCases := []stackitem.Item{ + stackitem.NewBool(true), stackitem.NewBigInteger(big.NewInt(11)), stackitem.NewByteArray([]byte{1, 2, 3}), + stackitem.NewArray(arr), stackitem.NewArray(nil), + stackitem.NewStruct(arr), stackitem.NewStruct(nil), + stackitem.NewMap(), m, stackitem.NewInterop(struct{}{}), + stackitem.NewPointer(0, []byte{}), } for i := range trueCases { - t.Run(getName(trueCases[i], BooleanT), testBool(trueCases[i], NewBoolItem(true))) + t.Run(getName(trueCases[i], stackitem.BooleanT), testBool(trueCases[i], stackitem.NewBool(true))) } - falseCases := []StackItem{ - NewBigIntegerItem(big.NewInt(0)), NewByteArrayItem([]byte{0, 0}), NewBoolItem(false), + falseCases := []stackitem.Item{ + stackitem.NewBigInteger(big.NewInt(0)), stackitem.NewByteArray([]byte{0, 0}), stackitem.NewBool(false), } for i := range falseCases { - testBool(falseCases[i], NewBoolItem(false)) + testBool(falseCases[i], stackitem.NewBool(false)) } }) t.Run("compound/interop -> basic", func(t *testing.T) { - types := []StackItemType{IntegerT, ByteArrayT} - items := []StackItem{NewArrayItem(nil), NewStructItem(nil), NewMapItem(), NewInteropItem(struct{}{})} + types := []stackitem.Type{stackitem.IntegerT, stackitem.ByteArrayT} + items := []stackitem.Item{stackitem.NewArray(nil), stackitem.NewStruct(nil), stackitem.NewMap(), stackitem.NewInterop(struct{}{})} for _, typ := range types { for j := range items { t.Run(getName(items[j], typ), testCONVERT(typ, items[j], nil)) @@ -292,23 +296,23 @@ func TestCONVERT(t *testing.T) { t.Run("primitive -> Integer/ByteArray", func(t *testing.T) { n := big.NewInt(42) - b := emit.IntToBytes(n) + b := bigint.ToBytes(n) - itemInt := NewBigIntegerItem(n) - itemBytes := NewByteArrayItem(b) + itemInt := stackitem.NewBigInteger(n) + itemBytes := stackitem.NewByteArray(b) - trueCases := map[StackItemType][]convertTC{ - IntegerT: { + trueCases := map[stackitem.Type][]convertTC{ + stackitem.IntegerT: { {itemInt, itemInt}, {itemBytes, itemInt}, - {NewBoolItem(true), NewBigIntegerItem(big.NewInt(1))}, - {NewBoolItem(false), NewBigIntegerItem(big.NewInt(0))}, + {stackitem.NewBool(true), stackitem.NewBigInteger(big.NewInt(1))}, + {stackitem.NewBool(false), stackitem.NewBigInteger(big.NewInt(0))}, }, - ByteArrayT: { + stackitem.ByteArrayT: { {itemInt, itemBytes}, {itemBytes, itemBytes}, - {NewBoolItem(true), NewByteArrayItem([]byte{1})}, - {NewBoolItem(false), NewByteArrayItem([]byte{0})}, + {stackitem.NewBool(true), stackitem.NewByteArray([]byte{1})}, + {stackitem.NewBool(false), stackitem.NewByteArray([]byte{0})}, }, } @@ -320,36 +324,36 @@ func TestCONVERT(t *testing.T) { }) t.Run("Struct<->Array", func(t *testing.T) { - arrayItem := NewArrayItem(arr) - structItem := NewStructItem(arr) - t.Run("Array->Array", testCONVERT(ArrayT, arrayItem, arrayItem)) - t.Run("Array->Struct", testCONVERT(StructT, arrayItem, structItem)) - t.Run("Struct->Array", testCONVERT(ArrayT, structItem, arrayItem)) - t.Run("Struct->Struct", testCONVERT(StructT, structItem, structItem)) + arrayItem := stackitem.NewArray(arr) + structItem := stackitem.NewStruct(arr) + t.Run("Array->Array", testCONVERT(stackitem.ArrayT, arrayItem, arrayItem)) + t.Run("Array->Struct", testCONVERT(stackitem.StructT, arrayItem, structItem)) + t.Run("Struct->Array", testCONVERT(stackitem.ArrayT, structItem, arrayItem)) + t.Run("Struct->Struct", testCONVERT(stackitem.StructT, structItem, structItem)) }) - t.Run("Map->Map", testCONVERT(MapT, m, m)) + t.Run("Map->Map", testCONVERT(stackitem.MapT, m, m)) - ptr := NewPointerItem(1, []byte{1}) - t.Run("Pointer->Pointer", testCONVERT(PointerT, ptr, ptr)) + ptr := stackitem.NewPointer(1, []byte{1}) + t.Run("Pointer->Pointer", testCONVERT(stackitem.PointerT, ptr, ptr)) t.Run("Null->", func(t *testing.T) { - types := []StackItemType{ - BooleanT, ByteArrayT, IntegerT, ArrayT, StructT, MapT, InteropT, PointerT, + types := []stackitem.Type{ + stackitem.BooleanT, stackitem.ByteArrayT, stackitem.IntegerT, stackitem.ArrayT, stackitem.StructT, stackitem.MapT, stackitem.InteropT, stackitem.PointerT, } for i := range types { - t.Run(types[i].String(), testCONVERT(types[i], NullItem{}, NullItem{})) + t.Run(types[i].String(), testCONVERT(types[i], stackitem.Null{}, stackitem.Null{})) } }) t.Run("->Any", func(t *testing.T) { - items := []StackItem{ - NewBigIntegerItem(big.NewInt(1)), NewByteArrayItem([]byte{1}), NewBoolItem(true), - NewArrayItem(arr), NewStructItem(arr), m, NewInteropItem(struct{}{}), + items := []stackitem.Item{ + stackitem.NewBigInteger(big.NewInt(1)), stackitem.NewByteArray([]byte{1}), stackitem.NewBool(true), + stackitem.NewArray(arr), stackitem.NewStruct(arr), m, stackitem.NewInterop(struct{}{}), } for i := range items { - t.Run(items[i].String(), testCONVERT(AnyT, items[i], nil)) + t.Run(items[i].String(), testCONVERT(stackitem.AnyT, items[i], nil)) } }) } @@ -372,7 +376,7 @@ func appendBigStruct(size uint16) []opcode.Opcode { return append(prog, opcode.INITSSLOT, 1, opcode.PUSHINT16, opcode.Opcode(size), opcode.Opcode(size>>8), // LE - opcode.PACK, opcode.CONVERT, opcode.Opcode(StructT), + opcode.PACK, opcode.CONVERT, opcode.Opcode(stackitem.StructT), opcode.STSFLD0, opcode.LDSFLD0, opcode.DUP, opcode.PUSH0, opcode.NEWARRAY, @@ -498,9 +502,9 @@ func getEnumeratorProg(n int, isIter bool) (prog []byte) { return } -func checkEnumeratorStack(t *testing.T, vm *VM, arr []StackItem) { +func checkEnumeratorStack(t *testing.T, vm *VM, arr []stackitem.Item) { require.Equal(t, len(arr)+1, vm.estack.Len()) - require.Equal(t, NewBoolItem(false), vm.estack.Peek(0).value) + require.Equal(t, stackitem.NewBool(false), vm.estack.Peek(0).value) for i := 0; i < len(arr); i++ { require.Equal(t, arr[i], vm.estack.Peek(i+1).value, "pos: %d", i+1) } @@ -512,22 +516,22 @@ func testIterableCreate(t *testing.T, typ string) { prog = append(prog, getEnumeratorProg(2, isIter)...) vm := load(prog) - arr := []StackItem{ - NewBigIntegerItem(big.NewInt(42)), - NewByteArrayItem([]byte{3, 2, 1}), + arr := []stackitem.Item{ + stackitem.NewBigInteger(big.NewInt(42)), + stackitem.NewByteArray([]byte{3, 2, 1}), } - vm.estack.Push(&Element{value: NewArrayItem(arr)}) + vm.estack.Push(&Element{value: stackitem.NewArray(arr)}) runVM(t, vm) if isIter { - checkEnumeratorStack(t, vm, []StackItem{ - makeStackItem(1), arr[1], NewBoolItem(true), - makeStackItem(0), arr[0], NewBoolItem(true), + checkEnumeratorStack(t, vm, []stackitem.Item{ + stackitem.Make(1), arr[1], stackitem.NewBool(true), + stackitem.Make(0), arr[0], stackitem.NewBool(true), }) } else { - checkEnumeratorStack(t, vm, []StackItem{ - arr[1], NewBoolItem(true), - arr[0], NewBoolItem(true), + checkEnumeratorStack(t, vm, []stackitem.Item{ + arr[1], stackitem.NewBool(true), + arr[0], stackitem.NewBool(true), }) } } @@ -549,29 +553,29 @@ func testIterableConcat(t *testing.T, typ string) { prog = append(prog, getEnumeratorProg(3, isIter)...) vm := load(prog) - arr := []StackItem{ - NewBoolItem(false), - NewBigIntegerItem(big.NewInt(123)), - NewMapItem(), + arr := []stackitem.Item{ + stackitem.NewBool(false), + stackitem.NewBigInteger(big.NewInt(123)), + stackitem.NewMap(), } - vm.estack.Push(&Element{value: NewArrayItem(arr[:1])}) - vm.estack.Push(&Element{value: NewArrayItem(arr[1:])}) + vm.estack.Push(&Element{value: stackitem.NewArray(arr[:1])}) + vm.estack.Push(&Element{value: stackitem.NewArray(arr[1:])}) runVM(t, vm) if isIter { // Yes, this is how iterators are concatenated in reference VM // https://github.com/neo-project/neo/blob/master-2.x/neo.UnitTests/UT_ConcatenatedIterator.cs#L54 - checkEnumeratorStack(t, vm, []StackItem{ - makeStackItem(1), arr[2], NewBoolItem(true), - makeStackItem(0), arr[1], NewBoolItem(true), - makeStackItem(0), arr[0], NewBoolItem(true), + checkEnumeratorStack(t, vm, []stackitem.Item{ + stackitem.Make(1), arr[2], stackitem.NewBool(true), + stackitem.Make(0), arr[1], stackitem.NewBool(true), + stackitem.Make(0), arr[0], stackitem.NewBool(true), }) } else { - checkEnumeratorStack(t, vm, []StackItem{ - arr[2], NewBoolItem(true), - arr[1], NewBoolItem(true), - arr[0], NewBoolItem(true), + checkEnumeratorStack(t, vm, []stackitem.Item{ + arr[2], stackitem.NewBool(true), + arr[1], stackitem.NewBool(true), + arr[0], stackitem.NewBool(true), }) } } @@ -590,17 +594,17 @@ func TestIteratorKeys(t *testing.T) { prog = append(prog, getEnumeratorProg(2, false)...) v := load(prog) - arr := NewArrayItem([]StackItem{ - NewBoolItem(false), - NewBigIntegerItem(big.NewInt(42)), + arr := stackitem.NewArray([]stackitem.Item{ + stackitem.NewBool(false), + stackitem.NewBigInteger(big.NewInt(42)), }) v.estack.PushVal(arr) runVM(t, v) - checkEnumeratorStack(t, v, []StackItem{ - NewBigIntegerItem(big.NewInt(1)), NewBoolItem(true), - NewBigIntegerItem(big.NewInt(0)), NewBoolItem(true), + checkEnumeratorStack(t, v, []stackitem.Item{ + stackitem.NewBigInteger(big.NewInt(1)), stackitem.NewBool(true), + stackitem.NewBigInteger(big.NewInt(0)), stackitem.NewBool(true), }) } @@ -610,26 +614,26 @@ func TestIteratorValues(t *testing.T) { prog = append(prog, getEnumeratorProg(2, false)...) v := load(prog) - m := NewMapItem() - m.Add(NewBigIntegerItem(big.NewInt(1)), NewBoolItem(false)) - m.Add(NewByteArrayItem([]byte{32}), NewByteArrayItem([]byte{7})) + m := stackitem.NewMap() + m.Add(stackitem.NewBigInteger(big.NewInt(1)), stackitem.NewBool(false)) + m.Add(stackitem.NewByteArray([]byte{32}), stackitem.NewByteArray([]byte{7})) v.estack.PushVal(m) runVM(t, v) require.Equal(t, 5, v.estack.Len()) - require.Equal(t, NewBoolItem(false), v.estack.Peek(0).value) + require.Equal(t, stackitem.NewBool(false), v.estack.Peek(0).value) // Map values can be enumerated in any order. i1, i2 := 1, 3 - if _, ok := v.estack.Peek(i1).value.(*BoolItem); !ok { + if _, ok := v.estack.Peek(i1).value.(*stackitem.Bool); !ok { i1, i2 = i2, i1 } - require.Equal(t, NewBoolItem(false), v.estack.Peek(i1).value) - require.Equal(t, NewByteArrayItem([]byte{7}), v.estack.Peek(i2).value) + require.Equal(t, stackitem.NewBool(false), v.estack.Peek(i1).value) + require.Equal(t, stackitem.NewByteArray([]byte{7}), v.estack.Peek(i2).value) - require.Equal(t, NewBoolItem(true), v.estack.Peek(2).value) - require.Equal(t, NewBoolItem(true), v.estack.Peek(4).value) + require.Equal(t, stackitem.NewBool(true), v.estack.Peek(2).value) + require.Equal(t, stackitem.NewBool(true), v.estack.Peek(4).value) } func getSyscallProg(name string) (prog []byte) { @@ -650,7 +654,7 @@ func testSerialize(t *testing.T, vm *VM) { err := vm.Step() require.NoError(t, err) require.Equal(t, 1, vm.estack.Len()) - require.IsType(t, (*ByteArrayItem)(nil), vm.estack.Top().value) + require.IsType(t, (*stackitem.ByteArray)(nil), vm.estack.Top().value) err = vm.Step() require.NoError(t, err) @@ -663,7 +667,7 @@ func TestSerializeBool(t *testing.T) { testSerialize(t, vm) - require.IsType(t, (*BoolItem)(nil), vm.estack.Top().value) + require.IsType(t, (*stackitem.Bool)(nil), vm.estack.Top().value) require.Equal(t, true, vm.estack.Top().Bool()) } @@ -674,7 +678,7 @@ func TestSerializeByteArray(t *testing.T) { testSerialize(t, vm) - require.IsType(t, (*ByteArrayItem)(nil), vm.estack.Top().value) + require.IsType(t, (*stackitem.ByteArray)(nil), vm.estack.Top().value) require.Equal(t, value, vm.estack.Top().Bytes()) } @@ -685,30 +689,30 @@ func TestSerializeInteger(t *testing.T) { testSerialize(t, vm) - require.IsType(t, (*BigIntegerItem)(nil), vm.estack.Top().value) + require.IsType(t, (*stackitem.BigInteger)(nil), vm.estack.Top().value) require.Equal(t, value, vm.estack.Top().BigInt().Int64()) } func TestSerializeArray(t *testing.T) { vm := load(getSerializeProg()) - item := NewArrayItem([]StackItem{ - makeStackItem(true), - makeStackItem(123), - NewMapItem(), + item := stackitem.NewArray([]stackitem.Item{ + stackitem.Make(true), + stackitem.Make(123), + stackitem.NewMap(), }) vm.estack.Push(&Element{value: item}) testSerialize(t, vm) - require.IsType(t, (*ArrayItem)(nil), vm.estack.Top().value) - require.Equal(t, item.value, vm.estack.Top().Array()) + require.IsType(t, (*stackitem.Array)(nil), vm.estack.Top().value) + require.Equal(t, item.Value().([]stackitem.Item), vm.estack.Top().Array()) } func TestSerializeArrayBad(t *testing.T) { vm := load(getSerializeProg()) - item := NewArrayItem(makeArrayOfType(2, BooleanT)) - item.value[1] = item + item := stackitem.NewArray(makeArrayOfType(2, stackitem.BooleanT)) + item.Value().([]stackitem.Item)[1] = item vm.estack.Push(&Element{value: item}) @@ -730,24 +734,24 @@ func TestSerializeDupInteger(t *testing.T) { func TestSerializeStruct(t *testing.T) { vm := load(getSerializeProg()) - item := NewStructItem([]StackItem{ - makeStackItem(true), - makeStackItem(123), - NewMapItem(), + item := stackitem.NewStruct([]stackitem.Item{ + stackitem.Make(true), + stackitem.Make(123), + stackitem.NewMap(), }) vm.estack.Push(&Element{value: item}) testSerialize(t, vm) - require.IsType(t, (*StructItem)(nil), vm.estack.Top().value) - require.Equal(t, item.value, vm.estack.Top().Array()) + require.IsType(t, (*stackitem.Struct)(nil), vm.estack.Top().value) + require.Equal(t, item.Value().([]stackitem.Item), vm.estack.Top().Array()) } func TestDeserializeUnknown(t *testing.T) { prog := append(getSyscallProg("Neo.Runtime.Deserialize"), byte(opcode.RET)) - data, err := SerializeItem(NewBigIntegerItem(big.NewInt(123))) + data, err := stackitem.SerializeItem(stackitem.NewBigInteger(big.NewInt(123))) require.NoError(t, err) data[0] = 0xFF @@ -757,16 +761,16 @@ func TestDeserializeUnknown(t *testing.T) { func TestSerializeMap(t *testing.T) { vm := load(getSerializeProg()) - item := NewMapItem() - item.Add(makeStackItem(true), makeStackItem([]byte{1, 2, 3})) - item.Add(makeStackItem([]byte{0}), makeStackItem(false)) + item := stackitem.NewMap() + item.Add(stackitem.Make(true), stackitem.Make([]byte{1, 2, 3})) + item.Add(stackitem.Make([]byte{0}), stackitem.Make(false)) vm.estack.Push(&Element{value: item}) testSerialize(t, vm) - require.IsType(t, (*MapItem)(nil), vm.estack.Top().value) - require.Equal(t, item.value, vm.estack.Top().value.(*MapItem).value) + require.IsType(t, (*stackitem.Map)(nil), vm.estack.Top().value) + require.Equal(t, item.Value(), vm.estack.Top().value.(*stackitem.Map).Value()) } func TestSerializeMapCompat(t *testing.T) { @@ -791,7 +795,7 @@ func TestSerializeMapCompat(t *testing.T) { func TestSerializeInterop(t *testing.T) { vm := load(getSerializeProg()) - item := NewInteropItem("kek") + item := stackitem.NewInterop("kek") vm.estack.Push(&Element{value: item}) @@ -917,27 +921,27 @@ func TestPUSHA(t *testing.T) { t.Run("TooBig", getTestFuncForVM(makeProgram(opcode.PUSHA, 10, 0, 0, 0), nil)) t.Run("Good", func(t *testing.T) { prog := makeProgram(opcode.PUSHA, 2, 0, 0, 0) - runWithArgs(t, prog, NewPointerItem(2, prog)) + runWithArgs(t, prog, stackitem.NewPointer(2, prog)) }) } func TestCALLA(t *testing.T) { prog := makeProgram(opcode.CALLA, opcode.PUSH2, opcode.ADD, opcode.RET, opcode.PUSH3, opcode.RET) - t.Run("InvalidScript", getTestFuncForVM(prog, nil, NewPointerItem(4, []byte{1}))) - t.Run("Good", getTestFuncForVM(prog, 5, NewPointerItem(4, prog))) + t.Run("InvalidScript", getTestFuncForVM(prog, nil, stackitem.NewPointer(4, []byte{1}))) + t.Run("Good", getTestFuncForVM(prog, 5, stackitem.NewPointer(4, prog))) } func TestNOT(t *testing.T) { prog := makeProgram(opcode.NOT) t.Run("Bool", getTestFuncForVM(prog, true, false)) t.Run("NonZeroInt", getTestFuncForVM(prog, false, 3)) - t.Run("Array", getTestFuncForVM(prog, false, []StackItem{})) - t.Run("Struct", getTestFuncForVM(prog, false, NewStructItem([]StackItem{}))) + t.Run("Array", getTestFuncForVM(prog, false, []stackitem.Item{})) + t.Run("Struct", getTestFuncForVM(prog, false, stackitem.NewStruct([]stackitem.Item{}))) t.Run("ByteArray0", getTestFuncForVM(prog, true, []byte{0, 0})) t.Run("ByteArray1", getTestFuncForVM(prog, false, []byte{0, 1})) t.Run("NoArgument", getTestFuncForVM(prog, nil)) - t.Run("Buffer0", getTestFuncForVM(prog, false, NewBufferItem([]byte{}))) - t.Run("Buffer1", getTestFuncForVM(prog, false, NewBufferItem([]byte{1}))) + t.Run("Buffer0", getTestFuncForVM(prog, false, stackitem.NewBuffer([]byte{}))) + t.Run("Buffer1", getTestFuncForVM(prog, false, stackitem.NewBuffer([]byte{1}))) } // getBigInt returns 2^a+b @@ -964,12 +968,12 @@ func TestArith(t *testing.T) { func TestADDBigResult(t *testing.T) { prog := makeProgram(opcode.ADD) - runWithArgs(t, prog, nil, getBigInt(MaxBigIntegerSizeBits, -1), 1) + runWithArgs(t, prog, nil, getBigInt(stackitem.MaxBigIntegerSizeBits, -1), 1) } func TestMULBigResult(t *testing.T) { prog := makeProgram(opcode.MUL) - bi := getBigInt(MaxBigIntegerSizeBits/2+1, 0) + bi := getBigInt(stackitem.MaxBigIntegerSizeBits/2+1, 0) runWithArgs(t, prog, nil, bi, bi) } @@ -1005,7 +1009,7 @@ func TestArithNegativeArguments(t *testing.T) { func TestSUBBigResult(t *testing.T) { prog := makeProgram(opcode.SUB) - runWithArgs(t, prog, nil, getBigInt(MaxBigIntegerSizeBits, -1), -1) + runWithArgs(t, prog, nil, getBigInt(stackitem.MaxBigIntegerSizeBits, -1), -1) } func TestSHR(t *testing.T) { @@ -1020,7 +1024,7 @@ func TestSHL(t *testing.T) { t.Run("Good", getTestFuncForVM(prog, 16, 4, 2)) t.Run("Zero", getTestFuncForVM(prog, []byte{0, 1}, []byte{0, 1}, 0)) t.Run("BigShift", getTestFuncForVM(prog, nil, 5, maxSHLArg+1)) - t.Run("BigResult", getTestFuncForVM(prog, nil, getBigInt(MaxBigIntegerSizeBits/2, 0), MaxBigIntegerSizeBits/2)) + t.Run("BigResult", getTestFuncForVM(prog, nil, getBigInt(stackitem.MaxBigIntegerSizeBits/2, 0), stackitem.MaxBigIntegerSizeBits/2)) } func TestLT(t *testing.T) { @@ -1055,9 +1059,9 @@ func TestDepth(t *testing.T) { func TestEQUALTrue(t *testing.T) { prog := makeProgram(opcode.DUP, opcode.EQUAL) - t.Run("Array", getTestFuncForVM(prog, true, []StackItem{})) - t.Run("Map", getTestFuncForVM(prog, true, NewMapItem())) - t.Run("Buffer", getTestFuncForVM(prog, true, NewBufferItem([]byte{1, 2}))) + t.Run("Array", getTestFuncForVM(prog, true, []stackitem.Item{})) + t.Run("Map", getTestFuncForVM(prog, true, stackitem.NewMap())) + t.Run("Buffer", getTestFuncForVM(prog, true, stackitem.NewBuffer([]byte{1, 2}))) } func TestEQUAL(t *testing.T) { @@ -1066,9 +1070,9 @@ func TestEQUAL(t *testing.T) { t.Run("OneArgument", getTestFuncForVM(prog, nil, 1)) t.Run("Integer", getTestFuncForVM(prog, true, 5, 5)) t.Run("IntegerByteArray", getTestFuncForVM(prog, true, []byte{16}, 16)) - t.Run("Map", getTestFuncForVM(prog, false, NewMapItem(), NewMapItem())) - t.Run("Array", getTestFuncForVM(prog, false, []StackItem{}, []StackItem{})) - t.Run("Buffer", getTestFuncForVM(prog, false, NewBufferItem([]byte{42}), NewBufferItem([]byte{42}))) + t.Run("Map", getTestFuncForVM(prog, false, stackitem.NewMap(), stackitem.NewMap())) + t.Run("Array", getTestFuncForVM(prog, false, []stackitem.Item{}, []stackitem.Item{})) + t.Run("Buffer", getTestFuncForVM(prog, false, stackitem.NewBuffer([]byte{42}), stackitem.NewBuffer([]byte{42}))) } func runWithArgs(t *testing.T, prog []byte, result interface{}, args ...interface{}) { @@ -1095,7 +1099,7 @@ func getTestFuncForVM(prog []byte, result interface{}, args ...interface{}) func if result != nil { f = func(t *testing.T, v *VM) { require.Equal(t, 1, v.estack.Len()) - require.Equal(t, makeStackItem(result), v.estack.Pop().value) + require.Equal(t, stackitem.Make(result), v.estack.Pop().value) } } return getCustomTestFuncForVM(prog, f, args...) @@ -1127,7 +1131,7 @@ func TestINC(t *testing.T) { func TestINCBigResult(t *testing.T) { prog := makeProgram(opcode.INC, opcode.INC) vm := load(prog) - x := getBigInt(MaxBigIntegerSizeBits, -2) + x := getBigInt(stackitem.MaxBigIntegerSizeBits, -2) vm.estack.PushVal(x) require.NoError(t, vm.Step()) @@ -1141,7 +1145,7 @@ func TestINCBigResult(t *testing.T) { func TestDECBigResult(t *testing.T) { prog := makeProgram(opcode.DEC, opcode.DEC) vm := load(prog) - x := getBigInt(MaxBigIntegerSizeBits, -2) + x := getBigInt(stackitem.MaxBigIntegerSizeBits, -2) x.Neg(x) vm.estack.PushVal(x) @@ -1155,7 +1159,7 @@ func TestDECBigResult(t *testing.T) { func TestNEWBUFFER(t *testing.T) { prog := makeProgram(opcode.NEWBUFFER) - t.Run("Good", getTestFuncForVM(prog, NewBufferItem([]byte{0, 0, 0}), 3)) + t.Run("Good", getTestFuncForVM(prog, stackitem.NewBuffer([]byte{0, 0, 0}), 3)) t.Run("Negative", getTestFuncForVM(prog, nil, -1)) t.Run("TooBig", getTestFuncForVM(prog, nil, MaxItemSize+1)) } @@ -1163,34 +1167,34 @@ func TestNEWBUFFER(t *testing.T) { func TestMEMCPY(t *testing.T) { prog := makeProgram(opcode.MEMCPY) t.Run("Good", func(t *testing.T) { - buf := NewBufferItem([]byte{0, 1, 2, 3}) - runWithArgs(t, prog, NewBufferItem([]byte{0, 6, 7, 3}), buf, buf, 1, []byte{4, 5, 6, 7}, 2, 2) + buf := stackitem.NewBuffer([]byte{0, 1, 2, 3}) + runWithArgs(t, prog, stackitem.NewBuffer([]byte{0, 6, 7, 3}), buf, buf, 1, []byte{4, 5, 6, 7}, 2, 2) }) - t.Run("NegativeSize", getTestFuncForVM(prog, nil, NewBufferItem([]byte{0, 1}), 0, []byte{2}, 0, -1)) - t.Run("NegativeSrcIndex", getTestFuncForVM(prog, nil, NewBufferItem([]byte{0, 1}), 0, []byte{2}, -1, 1)) - t.Run("NegativeDstIndex", getTestFuncForVM(prog, nil, NewBufferItem([]byte{0, 1}), -1, []byte{2}, 0, 1)) - t.Run("BigSizeSrc", getTestFuncForVM(prog, nil, NewBufferItem([]byte{0, 1}), 0, []byte{2}, 0, 2)) - t.Run("BigSizeDst", getTestFuncForVM(prog, nil, NewBufferItem([]byte{0, 1}), 0, []byte{2, 3, 4}, 0, 3)) + t.Run("NegativeSize", getTestFuncForVM(prog, nil, stackitem.NewBuffer([]byte{0, 1}), 0, []byte{2}, 0, -1)) + t.Run("NegativeSrcIndex", getTestFuncForVM(prog, nil, stackitem.NewBuffer([]byte{0, 1}), 0, []byte{2}, -1, 1)) + t.Run("NegativeDstIndex", getTestFuncForVM(prog, nil, stackitem.NewBuffer([]byte{0, 1}), -1, []byte{2}, 0, 1)) + t.Run("BigSizeSrc", getTestFuncForVM(prog, nil, stackitem.NewBuffer([]byte{0, 1}), 0, []byte{2}, 0, 2)) + t.Run("BigSizeDst", getTestFuncForVM(prog, nil, stackitem.NewBuffer([]byte{0, 1}), 0, []byte{2, 3, 4}, 0, 3)) } func TestNEWARRAY0(t *testing.T) { prog := makeProgram(opcode.NEWARRAY0) - runWithArgs(t, prog, []StackItem{}) + runWithArgs(t, prog, []stackitem.Item{}) } func TestNEWSTRUCT0(t *testing.T) { prog := makeProgram(opcode.NEWSTRUCT0) - runWithArgs(t, prog, NewStructItem([]StackItem{})) + runWithArgs(t, prog, stackitem.NewStruct([]stackitem.Item{})) } func TestNEWARRAYArray(t *testing.T) { prog := makeProgram(opcode.NEWARRAY) - t.Run("ByteArray", getTestFuncForVM(prog, NewArrayItem([]StackItem{}), []byte{})) + t.Run("ByteArray", getTestFuncForVM(prog, stackitem.NewArray([]stackitem.Item{}), []byte{})) t.Run("BadSize", getTestFuncForVM(prog, nil, MaxArraySize+1)) - t.Run("Integer", getTestFuncForVM(prog, []StackItem{NullItem{}}, 1)) + t.Run("Integer", getTestFuncForVM(prog, []stackitem.Item{stackitem.Null{}}, 1)) } -func testNEWARRAYIssue437(t *testing.T, i1 opcode.Opcode, t2 StackItemType, appended bool) { +func testNEWARRAYIssue437(t *testing.T, i1 opcode.Opcode, t2 stackitem.Type, appended bool) { prog := makeProgram( opcode.PUSH2, i1, opcode.DUP, opcode.PUSH3, opcode.APPEND, @@ -1199,34 +1203,34 @@ func testNEWARRAYIssue437(t *testing.T, i1 opcode.Opcode, t2 StackItemType, appe opcode.DUP, opcode.PUSH4, opcode.APPEND, opcode.LDSFLD0, opcode.PUSH5, opcode.APPEND) - arr := makeArrayOfType(4, AnyT) - arr[2] = makeStackItem(3) - arr[3] = makeStackItem(4) + arr := makeArrayOfType(4, stackitem.AnyT) + arr[2] = stackitem.Make(3) + arr[3] = stackitem.Make(4) if appended { - arr = append(arr, makeStackItem(5)) + arr = append(arr, stackitem.Make(5)) } - if t2 == ArrayT { - runWithArgs(t, prog, &ArrayItem{arr}) + if t2 == stackitem.ArrayT { + runWithArgs(t, prog, stackitem.NewArray(arr)) } else { - runWithArgs(t, prog, &StructItem{arr}) + runWithArgs(t, prog, stackitem.NewStruct(arr)) } } func TestNEWARRAYIssue437(t *testing.T) { - t.Run("Array+Array", func(t *testing.T) { testNEWARRAYIssue437(t, opcode.NEWARRAY, ArrayT, true) }) - t.Run("Struct+Struct", func(t *testing.T) { testNEWARRAYIssue437(t, opcode.NEWSTRUCT, StructT, true) }) - t.Run("Array+Struct", func(t *testing.T) { testNEWARRAYIssue437(t, opcode.NEWARRAY, StructT, false) }) - t.Run("Struct+Array", func(t *testing.T) { testNEWARRAYIssue437(t, opcode.NEWSTRUCT, ArrayT, false) }) + t.Run("Array+Array", func(t *testing.T) { testNEWARRAYIssue437(t, opcode.NEWARRAY, stackitem.ArrayT, true) }) + t.Run("Struct+Struct", func(t *testing.T) { testNEWARRAYIssue437(t, opcode.NEWSTRUCT, stackitem.StructT, true) }) + t.Run("Array+Struct", func(t *testing.T) { testNEWARRAYIssue437(t, opcode.NEWARRAY, stackitem.StructT, false) }) + t.Run("Struct+Array", func(t *testing.T) { testNEWARRAYIssue437(t, opcode.NEWSTRUCT, stackitem.ArrayT, false) }) } func TestNEWARRAYT(t *testing.T) { - testCases := map[StackItemType]StackItem{ - BooleanT: NewBoolItem(false), - IntegerT: NewBigIntegerItem(big.NewInt(0)), - ByteArrayT: NewByteArrayItem([]byte{}), - ArrayT: NullItem{}, - 0xFF: nil, + testCases := map[stackitem.Type]stackitem.Item{ + stackitem.BooleanT: stackitem.NewBool(false), + stackitem.IntegerT: stackitem.NewBigInteger(big.NewInt(0)), + stackitem.ByteArrayT: stackitem.NewByteArray([]byte{}), + stackitem.ArrayT: stackitem.Null{}, + 0xFF: nil, } for typ, item := range testCases { prog := makeProgram(opcode.NEWARRAYT, opcode.Opcode(typ), opcode.PUSH0, opcode.PICKITEM) @@ -1236,23 +1240,23 @@ func TestNEWARRAYT(t *testing.T) { func TestNEWSTRUCT(t *testing.T) { prog := makeProgram(opcode.NEWSTRUCT) - t.Run("ByteArray", getTestFuncForVM(prog, NewStructItem([]StackItem{}), []byte{})) + t.Run("ByteArray", getTestFuncForVM(prog, stackitem.NewStruct([]stackitem.Item{}), []byte{})) t.Run("BadSize", getTestFuncForVM(prog, nil, MaxArraySize+1)) - t.Run("Integer", getTestFuncForVM(prog, NewStructItem([]StackItem{NullItem{}}), 1)) + t.Run("Integer", getTestFuncForVM(prog, stackitem.NewStruct([]stackitem.Item{stackitem.Null{}}), 1)) } func TestAPPEND(t *testing.T) { prog := makeProgram(opcode.DUP, opcode.PUSH5, opcode.APPEND) - arr := []StackItem{makeStackItem(5)} - t.Run("Array", getTestFuncForVM(prog, NewArrayItem(arr), NewArrayItem(nil))) - t.Run("Struct", getTestFuncForVM(prog, NewStructItem(arr), NewStructItem(nil))) + arr := []stackitem.Item{stackitem.Make(5)} + t.Run("Array", getTestFuncForVM(prog, stackitem.NewArray(arr), stackitem.NewArray(nil))) + t.Run("Struct", getTestFuncForVM(prog, stackitem.NewStruct(arr), stackitem.NewStruct(nil))) } func TestAPPENDCloneStruct(t *testing.T) { prog := makeProgram(opcode.DUP, opcode.PUSH0, opcode.NEWSTRUCT, opcode.INITSSLOT, 1, opcode.STSFLD0, opcode.LDSFLD0, opcode.APPEND, opcode.LDSFLD0, opcode.PUSH1, opcode.APPEND) - arr := []StackItem{&StructItem{[]StackItem{}}} - runWithArgs(t, prog, NewArrayItem(arr), NewArrayItem(nil)) + arr := []stackitem.Item{stackitem.NewStruct([]stackitem.Item{})} + runWithArgs(t, prog, stackitem.NewArray(arr), stackitem.NewArray(nil)) } func TestAPPENDBad(t *testing.T) { @@ -1278,33 +1282,33 @@ func TestAPPENDBadSizeLimit(t *testing.T) { func TestPICKITEM(t *testing.T) { prog := makeProgram(opcode.PICKITEM) - t.Run("bad index", getTestFuncForVM(prog, nil, []StackItem{}, 0)) - t.Run("Array", getTestFuncForVM(prog, 2, []StackItem{makeStackItem(1), makeStackItem(2)}, 1)) + t.Run("bad index", getTestFuncForVM(prog, nil, []stackitem.Item{}, 0)) + t.Run("Array", getTestFuncForVM(prog, 2, []stackitem.Item{stackitem.Make(1), stackitem.Make(2)}, 1)) t.Run("ByteArray", getTestFuncForVM(prog, 2, []byte{1, 2}, 1)) - t.Run("Buffer", getTestFuncForVM(prog, 2, NewBufferItem([]byte{1, 2}), 1)) + t.Run("Buffer", getTestFuncForVM(prog, 2, stackitem.NewBuffer([]byte{1, 2}), 1)) } func TestPICKITEMDupArray(t *testing.T) { prog := makeProgram(opcode.DUP, opcode.PUSH0, opcode.PICKITEM, opcode.ABS) vm := load(prog) - vm.estack.PushVal([]StackItem{makeStackItem(-1)}) + vm.estack.PushVal([]stackitem.Item{stackitem.Make(-1)}) runVM(t, vm) assert.Equal(t, 2, vm.estack.Len()) assert.Equal(t, int64(1), vm.estack.Pop().BigInt().Int64()) - items := vm.estack.Pop().Value().([]StackItem) + items := vm.estack.Pop().Value().([]stackitem.Item) assert.Equal(t, big.NewInt(-1), items[0].Value()) } func TestPICKITEMDupMap(t *testing.T) { prog := makeProgram(opcode.DUP, opcode.PUSHINT8, 42, opcode.PICKITEM, opcode.ABS) vm := load(prog) - m := NewMapItem() - m.Add(makeStackItem([]byte{42}), makeStackItem(-1)) + m := stackitem.NewMap() + m.Add(stackitem.Make([]byte{42}), stackitem.Make(-1)) vm.estack.Push(&Element{value: m}) runVM(t, vm) assert.Equal(t, 2, vm.estack.Len()) assert.Equal(t, int64(1), vm.estack.Pop().BigInt().Int64()) - items := vm.estack.Pop().Value().([]MapElement) + items := vm.estack.Pop().Value().([]stackitem.MapElement) assert.Equal(t, 1, len(items)) assert.Equal(t, []byte{42}, items[0].Key.Value()) assert.Equal(t, big.NewInt(-1), items[0].Value.Value()) @@ -1312,30 +1316,30 @@ func TestPICKITEMDupMap(t *testing.T) { func TestPICKITEMMap(t *testing.T) { prog := makeProgram(opcode.PICKITEM) - m := NewMapItem() - m.Add(makeStackItem(5), makeStackItem(3)) + m := stackitem.NewMap() + m.Add(stackitem.Make(5), stackitem.Make(3)) runWithArgs(t, prog, 3, m, 5) } func TestSETITEMBuffer(t *testing.T) { prog := makeProgram(opcode.DUP, opcode.REVERSE4, opcode.SETITEM) - t.Run("Good", getTestFuncForVM(prog, NewBufferItem([]byte{0, 42, 2}), 42, 1, NewBufferItem([]byte{0, 1, 2}))) - t.Run("BadIndex", getTestFuncForVM(prog, nil, 42, -1, NewBufferItem([]byte{0, 1, 2}))) - t.Run("BadValue", getTestFuncForVM(prog, nil, 256, 1, NewBufferItem([]byte{0, 1, 2}))) + t.Run("Good", getTestFuncForVM(prog, stackitem.NewBuffer([]byte{0, 42, 2}), 42, 1, stackitem.NewBuffer([]byte{0, 1, 2}))) + t.Run("BadIndex", getTestFuncForVM(prog, nil, 42, -1, stackitem.NewBuffer([]byte{0, 1, 2}))) + t.Run("BadValue", getTestFuncForVM(prog, nil, 256, 1, stackitem.NewBuffer([]byte{0, 1, 2}))) } func TestSETITEMMap(t *testing.T) { prog := makeProgram(opcode.SETITEM, opcode.PICKITEM) - m := NewMapItem() - m.Add(makeStackItem(5), makeStackItem(3)) + m := stackitem.NewMap() + m.Add(stackitem.Make(5), stackitem.Make(3)) runWithArgs(t, prog, []byte{0, 1}, m, 5, m, 5, []byte{0, 1}) } func TestSETITEMBigMapBad(t *testing.T) { prog := makeProgram(opcode.SETITEM) - m := NewMapItem() + m := stackitem.NewMap() for i := 0; i < MaxArraySize; i++ { - m.Add(makeStackItem(i), makeStackItem(i)) + m.Add(stackitem.Make(i), stackitem.Make(i)) } runWithArgs(t, prog, nil, m, MaxArraySize, 0) @@ -1347,9 +1351,9 @@ func TestSETITEMBigMapBad(t *testing.T) { // 3. Replace each of them with a scalar value. func TestSETITEMMapStackLimit(t *testing.T) { size := MaxArraySize - 3 - m := NewMapItem() - m.Add(NewBigIntegerItem(big.NewInt(1)), NewArrayItem(makeArrayOfType(size, BooleanT))) - m.Add(NewBigIntegerItem(big.NewInt(2)), NewArrayItem(makeArrayOfType(size, BooleanT))) + m := stackitem.NewMap() + m.Add(stackitem.NewBigInteger(big.NewInt(1)), stackitem.NewArray(makeArrayOfType(size, stackitem.BooleanT))) + m.Add(stackitem.NewBigInteger(big.NewInt(2)), stackitem.NewArray(makeArrayOfType(size, stackitem.BooleanT))) prog := makeProgram( opcode.DUP, opcode.PUSH1, opcode.PUSH1, opcode.SETITEM, @@ -1365,9 +1369,9 @@ func TestSETITEMBigMapGood(t *testing.T) { prog := makeProgram(opcode.SETITEM) vm := load(prog) - m := NewMapItem() + m := stackitem.NewMap() for i := 0; i < MaxArraySize; i++ { - m.Add(makeStackItem(i), makeStackItem(i)) + m.Add(stackitem.Make(i), stackitem.Make(i)) } vm.estack.Push(&Element{value: m}) vm.estack.PushVal(0) @@ -1380,13 +1384,13 @@ func TestSIZE(t *testing.T) { prog := makeProgram(opcode.SIZE) t.Run("NoArgument", getTestFuncForVM(prog, nil)) t.Run("ByteArray", getTestFuncForVM(prog, 2, []byte{0, 1})) - t.Run("Buffer", getTestFuncForVM(prog, 2, NewBufferItem([]byte{0, 1}))) + t.Run("Buffer", getTestFuncForVM(prog, 2, stackitem.NewBuffer([]byte{0, 1}))) t.Run("Bool", getTestFuncForVM(prog, 1, false)) - t.Run("Array", getTestFuncForVM(prog, 2, []StackItem{makeStackItem(1), makeStackItem([]byte{})})) + t.Run("Array", getTestFuncForVM(prog, 2, []stackitem.Item{stackitem.Make(1), stackitem.Make([]byte{})})) t.Run("Map", func(t *testing.T) { - m := NewMapItem() - m.Add(makeStackItem(5), makeStackItem(6)) - m.Add(makeStackItem([]byte{0, 1}), makeStackItem(6)) + m := stackitem.NewMap() + m.Add(stackitem.Make(5), stackitem.Make(6)) + m.Add(stackitem.Make([]byte{0, 1}), stackitem.Make(6)) runWithArgs(t, prog, 2, m) }) } @@ -1395,42 +1399,42 @@ func TestKEYSMap(t *testing.T) { prog := makeProgram(opcode.KEYS) vm := load(prog) - m := NewMapItem() - m.Add(makeStackItem(5), makeStackItem(6)) - m.Add(makeStackItem([]byte{0, 1}), makeStackItem(6)) + m := stackitem.NewMap() + m.Add(stackitem.Make(5), stackitem.Make(6)) + m.Add(stackitem.Make([]byte{0, 1}), stackitem.Make(6)) vm.estack.Push(&Element{value: m}) runVM(t, vm) assert.Equal(t, 1, vm.estack.Len()) - top := vm.estack.Pop().value.(*ArrayItem) - assert.Equal(t, 2, len(top.value)) - assert.Contains(t, top.value, makeStackItem(5)) - assert.Contains(t, top.value, makeStackItem([]byte{0, 1})) + top := vm.estack.Pop().value.(*stackitem.Array) + assert.Equal(t, 2, len(top.Value().([]stackitem.Item))) + assert.Contains(t, top.Value().([]stackitem.Item), stackitem.Make(5)) + assert.Contains(t, top.Value().([]stackitem.Item), stackitem.Make([]byte{0, 1})) } func TestKEYS(t *testing.T) { prog := makeProgram(opcode.KEYS) t.Run("NoArgument", getTestFuncForVM(prog, nil)) - t.Run("WrongType", getTestFuncForVM(prog, nil, []StackItem{})) + t.Run("WrongType", getTestFuncForVM(prog, nil, []stackitem.Item{})) } func TestVALUESMap(t *testing.T) { prog := makeProgram(opcode.VALUES) vm := load(prog) - m := NewMapItem() - m.Add(makeStackItem(5), makeStackItem([]byte{2, 3})) - m.Add(makeStackItem([]byte{0, 1}), makeStackItem([]StackItem{})) + m := stackitem.NewMap() + m.Add(stackitem.Make(5), stackitem.Make([]byte{2, 3})) + m.Add(stackitem.Make([]byte{0, 1}), stackitem.Make([]stackitem.Item{})) vm.estack.Push(&Element{value: m}) runVM(t, vm) assert.Equal(t, 1, vm.estack.Len()) - top := vm.estack.Pop().value.(*ArrayItem) - assert.Equal(t, 2, len(top.value)) - assert.Contains(t, top.value, makeStackItem([]byte{2, 3})) - assert.Contains(t, top.value, makeStackItem([]StackItem{})) + top := vm.estack.Pop().value.(*stackitem.Array) + assert.Equal(t, 2, len(top.Value().([]stackitem.Item))) + assert.Contains(t, top.Value().([]stackitem.Item), stackitem.Make([]byte{2, 3})) + assert.Contains(t, top.Value().([]stackitem.Item), stackitem.Make([]stackitem.Item{})) } func TestVALUES(t *testing.T) { @@ -1444,30 +1448,30 @@ func TestHASKEY(t *testing.T) { prog := makeProgram(opcode.HASKEY) t.Run("NoArgument", getTestFuncForVM(prog, nil)) t.Run("OneArgument", getTestFuncForVM(prog, nil, 1)) - t.Run("WrongKeyType", getTestFuncForVM(prog, nil, []StackItem{}, []StackItem{})) + t.Run("WrongKeyType", getTestFuncForVM(prog, nil, []stackitem.Item{}, []stackitem.Item{})) t.Run("WrongCollectionType", getTestFuncForVM(prog, nil, 1, 2)) - arr := makeArrayOfType(5, BooleanT) + arr := makeArrayOfType(5, stackitem.BooleanT) t.Run("Array", func(t *testing.T) { - t.Run("True", getTestFuncForVM(prog, true, NewArrayItem(arr), 4)) - t.Run("False", getTestFuncForVM(prog, false, NewArrayItem(arr), 5)) + t.Run("True", getTestFuncForVM(prog, true, stackitem.NewArray(arr), 4)) + t.Run("False", getTestFuncForVM(prog, false, stackitem.NewArray(arr), 5)) }) t.Run("Struct", func(t *testing.T) { - t.Run("True", getTestFuncForVM(prog, true, NewStructItem(arr), 4)) - t.Run("False", getTestFuncForVM(prog, false, NewStructItem(arr), 5)) + t.Run("True", getTestFuncForVM(prog, true, stackitem.NewStruct(arr), 4)) + t.Run("False", getTestFuncForVM(prog, false, stackitem.NewStruct(arr), 5)) }) t.Run("Buffer", func(t *testing.T) { - t.Run("True", getTestFuncForVM(prog, true, NewBufferItem([]byte{5, 5, 5}), 2)) - t.Run("False", getTestFuncForVM(prog, false, NewBufferItem([]byte{5, 5, 5}), 3)) - t.Run("Negative", getTestFuncForVM(prog, nil, NewBufferItem([]byte{5, 5, 5}), -1)) + t.Run("True", getTestFuncForVM(prog, true, stackitem.NewBuffer([]byte{5, 5, 5}), 2)) + t.Run("False", getTestFuncForVM(prog, false, stackitem.NewBuffer([]byte{5, 5, 5}), 3)) + t.Run("Negative", getTestFuncForVM(prog, nil, stackitem.NewBuffer([]byte{5, 5, 5}), -1)) }) } func TestHASKEYMap(t *testing.T) { prog := makeProgram(opcode.HASKEY) - m := NewMapItem() - m.Add(makeStackItem(5), makeStackItem(6)) + m := stackitem.NewMap() + m.Add(stackitem.Make(5), stackitem.Make(6)) t.Run("True", getTestFuncForVM(prog, true, m, 5)) t.Run("False", getTestFuncForVM(prog, false, m, 6)) } @@ -1475,7 +1479,7 @@ func TestHASKEYMap(t *testing.T) { func TestSIGN(t *testing.T) { prog := makeProgram(opcode.SIGN) t.Run("NoArgument", getTestFuncForVM(prog, nil)) - t.Run("WrongType", getTestFuncForVM(prog, nil, []StackItem{})) + t.Run("WrongType", getTestFuncForVM(prog, nil, []stackitem.Item{})) t.Run("Bool", getTestFuncForVM(prog, 0, false)) t.Run("PositiveInt", getTestFuncForVM(prog, 1, 2)) t.Run("NegativeInt", getTestFuncForVM(prog, -1, -1)) @@ -1554,9 +1558,9 @@ func TestROTGood(t *testing.T) { vm.estack.PushVal(3) runVM(t, vm) assert.Equal(t, 3, vm.estack.Len()) - assert.Equal(t, makeStackItem(1), vm.estack.Pop().value) - assert.Equal(t, makeStackItem(3), vm.estack.Pop().value) - assert.Equal(t, makeStackItem(2), vm.estack.Pop().value) + assert.Equal(t, stackitem.Make(1), vm.estack.Pop().value) + assert.Equal(t, stackitem.Make(3), vm.estack.Pop().value) + assert.Equal(t, stackitem.Make(2), vm.estack.Pop().value) } func TestROLLBad1(t *testing.T) { @@ -1579,17 +1583,17 @@ func TestROLLGood(t *testing.T) { vm.estack.PushVal(1) runVM(t, vm) assert.Equal(t, 4, vm.estack.Len()) - assert.Equal(t, makeStackItem(3), vm.estack.Pop().value) - assert.Equal(t, makeStackItem(4), vm.estack.Pop().value) - assert.Equal(t, makeStackItem(2), vm.estack.Pop().value) - assert.Equal(t, makeStackItem(1), vm.estack.Pop().value) + assert.Equal(t, stackitem.Make(3), vm.estack.Pop().value) + assert.Equal(t, stackitem.Make(4), vm.estack.Pop().value) + assert.Equal(t, stackitem.Make(2), vm.estack.Pop().value) + assert.Equal(t, stackitem.Make(1), vm.estack.Pop().value) } func getCheckEStackFunc(items ...interface{}) func(t *testing.T, v *VM) { return func(t *testing.T, v *VM) { require.Equal(t, len(items), v.estack.Len()) for i := 0; i < len(items); i++ { - assert.Equal(t, makeStackItem(items[i]), v.estack.Peek(i).Item()) + assert.Equal(t, stackitem.Make(items[i]), v.estack.Peek(i).Item()) } } } @@ -1780,9 +1784,9 @@ func TestCAT(t *testing.T) { arg := make([]byte, MaxItemSize/2+1) runWithArgs(t, prog, nil, arg, arg) }) - t.Run("Good", getTestFuncForVM(prog, NewBufferItem([]byte("abcdef")), []byte("abc"), []byte("def"))) - t.Run("Int0ByteArray", getTestFuncForVM(prog, NewBufferItem([]byte{}), 0, []byte{})) - t.Run("ByteArrayInt1", getTestFuncForVM(prog, NewBufferItem([]byte{1}), []byte{}, 1)) + t.Run("Good", getTestFuncForVM(prog, stackitem.NewBuffer([]byte("abcdef")), []byte("abc"), []byte("def"))) + t.Run("Int0ByteArray", getTestFuncForVM(prog, stackitem.NewBuffer([]byte{}), 0, []byte{})) + t.Run("ByteArrayInt1", getTestFuncForVM(prog, stackitem.NewBuffer([]byte{1}), []byte{}, 1)) } func TestSUBSTR(t *testing.T) { @@ -1790,7 +1794,7 @@ func TestSUBSTR(t *testing.T) { t.Run("NoArgument", getTestFuncForVM(prog, nil)) t.Run("OneArgument", getTestFuncForVM(prog, nil, 1)) t.Run("TwoArguments", getTestFuncForVM(prog, nil, 0, 2)) - t.Run("Good", getTestFuncForVM(prog, NewBufferItem([]byte("bc")), []byte("abcdef"), 1, 2)) + t.Run("Good", getTestFuncForVM(prog, stackitem.NewBuffer([]byte("bc")), []byte("abcdef"), 1, 2)) t.Run("BadOffset", getTestFuncForVM(prog, nil, []byte("abcdef"), 7, 1)) t.Run("BigLen", getTestFuncForVM(prog, nil, []byte("abcdef"), 1, 6)) t.Run("NegativeOffset", getTestFuncForVM(prog, nil, []byte("abcdef"), -1, 3)) @@ -1813,7 +1817,7 @@ func TestLEFT(t *testing.T) { t.Run("NoArgument", getTestFuncForVM(prog, nil)) t.Run("NoString", getTestFuncForVM(prog, nil, 2)) t.Run("NegativeLen", getTestFuncForVM(prog, nil, "abcdef", -1)) - t.Run("Good", getTestFuncForVM(prog, NewBufferItem([]byte("ab")), "abcdef", 2)) + t.Run("Good", getTestFuncForVM(prog, stackitem.NewBuffer([]byte("ab")), "abcdef", 2)) t.Run("BadBigLen", getTestFuncForVM(prog, nil, "abcdef", 8)) } @@ -1822,14 +1826,14 @@ func TestRIGHT(t *testing.T) { t.Run("NoArgument", getTestFuncForVM(prog, nil)) t.Run("NoString", getTestFuncForVM(prog, nil, 2)) t.Run("NegativeLen", getTestFuncForVM(prog, nil, "abcdef", -1)) - t.Run("Good", getTestFuncForVM(prog, NewBufferItem([]byte("ef")), "abcdef", 2)) + t.Run("Good", getTestFuncForVM(prog, stackitem.NewBuffer([]byte("ef")), "abcdef", 2)) t.Run("BadLen", getTestFuncForVM(prog, nil, "abcdef", 8)) } func TestPACK(t *testing.T) { prog := makeProgram(opcode.PACK) t.Run("BadLen", getTestFuncForVM(prog, nil, 1)) - t.Run("Good0Len", getTestFuncForVM(prog, []StackItem{}, 0)) + t.Run("Good0Len", getTestFuncForVM(prog, []stackitem.Item{}, 0)) } func TestPACKBigLen(t *testing.T) { @@ -1887,37 +1891,37 @@ func TestUNPACKGood(t *testing.T) { func TestREVERSEITEMS(t *testing.T) { prog := makeProgram(opcode.DUP, opcode.REVERSEITEMS) t.Run("InvalidItem", getTestFuncForVM(prog, nil, 1)) - t.Run("Buffer", getTestFuncForVM(prog, NewBufferItem([]byte{3, 2, 1}), NewBufferItem([]byte{1, 2, 3}))) + t.Run("Buffer", getTestFuncForVM(prog, stackitem.NewBuffer([]byte{3, 2, 1}), stackitem.NewBuffer([]byte{1, 2, 3}))) } -func testREVERSEITEMSIssue437(t *testing.T, i1 opcode.Opcode, t2 StackItemType, reversed bool) { +func testREVERSEITEMSIssue437(t *testing.T, i1 opcode.Opcode, t2 stackitem.Type, reversed bool) { prog := makeProgram( opcode.PUSH0, i1, opcode.DUP, opcode.PUSH1, opcode.APPEND, opcode.DUP, opcode.PUSH2, opcode.APPEND, opcode.DUP, opcode.CONVERT, opcode.Opcode(t2), opcode.REVERSEITEMS) - arr := make([]StackItem, 2) + arr := make([]stackitem.Item, 2) if reversed { - arr[0] = makeStackItem(2) - arr[1] = makeStackItem(1) + arr[0] = stackitem.Make(2) + arr[1] = stackitem.Make(1) } else { - arr[0] = makeStackItem(1) - arr[1] = makeStackItem(2) + arr[0] = stackitem.Make(1) + arr[1] = stackitem.Make(2) } if i1 == opcode.NEWARRAY { - runWithArgs(t, prog, &ArrayItem{arr}) + runWithArgs(t, prog, stackitem.NewArray(arr)) } else { - runWithArgs(t, prog, &StructItem{arr}) + runWithArgs(t, prog, stackitem.NewStruct(arr)) } } func TestREVERSEITEMSIssue437(t *testing.T) { - t.Run("Array+Array", func(t *testing.T) { testREVERSEITEMSIssue437(t, opcode.NEWARRAY, ArrayT, true) }) - t.Run("Struct+Struct", func(t *testing.T) { testREVERSEITEMSIssue437(t, opcode.NEWSTRUCT, StructT, true) }) - t.Run("Array+Struct", func(t *testing.T) { testREVERSEITEMSIssue437(t, opcode.NEWARRAY, StructT, false) }) - t.Run("Struct+Array", func(t *testing.T) { testREVERSEITEMSIssue437(t, opcode.NEWSTRUCT, ArrayT, false) }) + t.Run("Array+Array", func(t *testing.T) { testREVERSEITEMSIssue437(t, opcode.NEWARRAY, stackitem.ArrayT, true) }) + t.Run("Struct+Struct", func(t *testing.T) { testREVERSEITEMSIssue437(t, opcode.NEWSTRUCT, stackitem.StructT, true) }) + t.Run("Array+Struct", func(t *testing.T) { testREVERSEITEMSIssue437(t, opcode.NEWARRAY, stackitem.StructT, false) }) + t.Run("Struct+Array", func(t *testing.T) { testREVERSEITEMSIssue437(t, opcode.NEWSTRUCT, stackitem.ArrayT, false) }) } func TestREVERSEITEMSGoodOneElem(t *testing.T) { @@ -1944,11 +1948,11 @@ func TestREVERSEITEMSGoodStruct(t *testing.T) { vm := load(prog) vm.estack.PushVal(1) - arr := make([]StackItem, len(elements)) + arr := make([]stackitem.Item, len(elements)) for i := range elements { - arr[i] = makeStackItem(elements[i]) + arr[i] = stackitem.Make(elements[i]) } - vm.estack.Push(&Element{value: &StructItem{arr}}) + vm.estack.Push(&Element{value: stackitem.NewStruct(arr)}) runVM(t, vm) assert.Equal(t, 2, vm.estack.Len()) @@ -2001,27 +2005,27 @@ func TestREMOVEGood(t *testing.T) { vm.estack.PushVal(elements) runVM(t, vm) assert.Equal(t, 2, vm.estack.Len()) - assert.Equal(t, makeStackItem(reselements), vm.estack.Pop().value) - assert.Equal(t, makeStackItem(1), vm.estack.Pop().value) + assert.Equal(t, stackitem.Make(reselements), vm.estack.Pop().value) + assert.Equal(t, stackitem.Make(1), vm.estack.Pop().value) } func TestREMOVEMap(t *testing.T) { prog := makeProgram(opcode.REMOVE, opcode.PUSH5, opcode.HASKEY) vm := load(prog) - m := NewMapItem() - m.Add(makeStackItem(5), makeStackItem(3)) - m.Add(makeStackItem([]byte{0, 1}), makeStackItem([]byte{2, 3})) + m := stackitem.NewMap() + m.Add(stackitem.Make(5), stackitem.Make(3)) + m.Add(stackitem.Make([]byte{0, 1}), stackitem.Make([]byte{2, 3})) vm.estack.Push(&Element{value: m}) vm.estack.Push(&Element{value: m}) - vm.estack.PushVal(makeStackItem(5)) + vm.estack.PushVal(stackitem.Make(5)) runVM(t, vm) assert.Equal(t, 1, vm.estack.Len()) - assert.Equal(t, makeStackItem(false), vm.estack.Pop().value) + assert.Equal(t, stackitem.Make(false), vm.estack.Pop().value) } -func testCLEARITEMS(t *testing.T, item StackItem) { +func testCLEARITEMS(t *testing.T, item stackitem.Item) { prog := makeProgram(opcode.DUP, opcode.DUP, opcode.CLEARITEMS, opcode.SIZE) v := load(prog) v.estack.PushVal(item) @@ -2032,17 +2036,17 @@ func testCLEARITEMS(t *testing.T, item StackItem) { } func TestCLEARITEMS(t *testing.T) { - arr := []StackItem{NewBigIntegerItem(big.NewInt(1)), NewByteArrayItem([]byte{1})} - m := NewMapItem() - m.Add(NewBigIntegerItem(big.NewInt(1)), NewByteArrayItem([]byte{})) - m.Add(NewByteArrayItem([]byte{42}), NewBigIntegerItem(big.NewInt(2))) + arr := []stackitem.Item{stackitem.NewBigInteger(big.NewInt(1)), stackitem.NewByteArray([]byte{1})} + m := stackitem.NewMap() + m.Add(stackitem.NewBigInteger(big.NewInt(1)), stackitem.NewByteArray([]byte{})) + m.Add(stackitem.NewByteArray([]byte{42}), stackitem.NewBigInteger(big.NewInt(2))) - testCases := map[string]StackItem{ - "empty Array": NewArrayItem([]StackItem{}), - "filled Array": NewArrayItem(arr), - "empty Struct": NewStructItem([]StackItem{}), - "filled Struct": NewStructItem(arr), - "empty Map": NewMapItem(), + testCases := map[string]stackitem.Item{ + "empty Array": stackitem.NewArray([]stackitem.Item{}), + "filled Array": stackitem.NewArray(arr), + "empty Struct": stackitem.NewStruct([]stackitem.Item{}), + "filled Struct": stackitem.NewStruct(arr), + "empty Map": stackitem.NewMap(), "filled Map": m, } @@ -2425,8 +2429,8 @@ func TestSLOTOpcodes(t *testing.T) { }) t.Run("Default", func(t *testing.T) { - t.Run("DefaultStatic", getTestFuncForVM(makeProgram(opcode.INITSSLOT, 2, opcode.LDSFLD1), NullItem{})) - t.Run("DefaultLocal", getTestFuncForVM(makeProgram(opcode.INITSLOT, 2, 0, opcode.LDLOC1), NullItem{})) + t.Run("DefaultStatic", getTestFuncForVM(makeProgram(opcode.INITSSLOT, 2, opcode.LDSFLD1), stackitem.Null{})) + t.Run("DefaultLocal", getTestFuncForVM(makeProgram(opcode.INITSLOT, 2, 0, opcode.LDLOC1), stackitem.Null{})) t.Run("DefaultArgument", getTestFuncForVM(makeProgram(opcode.INITSLOT, 0, 2, opcode.LDARG1), 2, 2, 1)) })