diff --git a/pkg/compiler/codegen.go b/pkg/compiler/codegen.go index 781d5a5a0..9dadbeff9 100644 --- a/pkg/compiler/codegen.go +++ b/pkg/compiler/codegen.go @@ -133,6 +133,7 @@ func (c *codegen) emitLoadConst(t types.TypeAndValue) { switch typ.Kind() { case types.Int, types.UntypedInt, types.Uint, + types.Int8, types.Uint8, types.Int16, types.Uint16, types.Int32, types.Uint32, types.Int64, types.Uint64: val, _ := constant.Int64Val(t.Value) @@ -143,10 +144,6 @@ func (c *codegen) emitLoadConst(t types.TypeAndValue) { case types.Bool, types.UntypedBool: val := constant.BoolVal(t.Value) emit.Bool(c.prog.BinWriter, val) - case types.Byte: - val, _ := constant.Int64Val(t.Value) - b := byte(val) - emit.Bytes(c.prog.BinWriter, []byte{b}) default: c.prog.Err = fmt.Errorf("compiler doesn't know how to convert this basic type: %v", t) return @@ -238,7 +235,8 @@ func (c *codegen) emitDefault(t types.Type) { if isCompoundSlice(t) { emit.Opcode(c.prog.BinWriter, opcode.NEWARRAY0) } else { - emit.Bytes(c.prog.BinWriter, []byte{}) + emit.Int(c.prog.BinWriter, 0) + emit.Opcode(c.prog.BinWriter, opcode.NEWBUFFER) } case *types.Struct: emit.Int(c.prog.BinWriter, int64(t.NumFields())) @@ -719,6 +717,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) return nil } @@ -1099,7 +1098,7 @@ func (c *codegen) convertBuiltin(expr *ast.CallExpr) { case "ToBool": typ = vm.BooleanT } - emit.Instruction(c.prog.BinWriter, opcode.CONVERT, []byte{byte(typ)}) + c.emitConvert(typ) case "SHA256": emit.Syscall(c.prog.BinWriter, "Neo.Crypto.SHA256") case "AppCall": @@ -1124,6 +1123,7 @@ func (c *codegen) convertBuiltin(expr *ast.CallExpr) { } bytes := uint160.BytesBE() emit.Bytes(c.prog.BinWriter, bytes) + c.emitConvert(vm.BufferT) } } @@ -1149,6 +1149,11 @@ func transformArgs(fun ast.Expr, args []ast.Expr) []ast.Expr { return args } +// emitConvert converts top stack item to the specified type. +func (c *codegen) emitConvert(typ vm.StackItemType) { + emit.Instruction(c.prog.BinWriter, opcode.CONVERT, []byte{byte(typ)}) +} + func (c *codegen) convertByteArray(lit *ast.CompositeLit) { buf := make([]byte, len(lit.Elts)) for i := 0; i < len(lit.Elts); i++ { @@ -1157,6 +1162,7 @@ func (c *codegen) convertByteArray(lit *ast.CompositeLit) { buf[i] = byte(val) } emit.Bytes(c.prog.BinWriter, buf) + c.emitConvert(vm.BufferT) } func (c *codegen) convertMap(lit *ast.CompositeLit) { diff --git a/pkg/compiler/constant_test.go b/pkg/compiler/constant_test.go index 9608e4e9c..725ff1dba 100644 --- a/pkg/compiler/constant_test.go +++ b/pkg/compiler/constant_test.go @@ -36,6 +36,17 @@ func TestShortHandMultiConst(t *testing.T) { eval(t, src, big.NewInt(6)) } +func TestByteConstant(t *testing.T) { + src := `package foo + import "github.com/nspcc-dev/neo-go/pkg/interop/convert" + const a byte = 0xFF + func Main() int64 { + x := convert.ToInteger(a) + return x+1 + }` + eval(t, src, big.NewInt(0x100)) +} + func TestGlobalsWithFunctionParams(t *testing.T) { src := ` package foobar diff --git a/pkg/compiler/convert_test.go b/pkg/compiler/convert_test.go index 0bcd8eace..379d7ce39 100644 --- a/pkg/compiler/convert_test.go +++ b/pkg/compiler/convert_test.go @@ -38,7 +38,8 @@ func TestConvert(t *testing.T) { {"bool", "12", true}, {"bool", "0", false}, {"bool", "[]byte{0, 1, 0}", true}, - {"bool", "[]byte{0}", false}, + {"bool", "[]byte{0}", true}, + {"bool", `""`, false}, {"int64", "true", big.NewInt(1)}, {"int64", "false", big.NewInt(0)}, {"int64", "12", big.NewInt(12)}, diff --git a/pkg/compiler/slice_test.go b/pkg/compiler/slice_test.go index 629b1b52d..af7d07f80 100644 --- a/pkg/compiler/slice_test.go +++ b/pkg/compiler/slice_test.go @@ -180,6 +180,38 @@ var sliceTestCases = []testCase{ vm.NewByteArrayItem([]byte("b")), }, }, + { + "byte-slice assignment", + `package foo + func Main() []byte { + a := []byte{0, 1, 2} + a[1] = 42 + return a + }`, + []byte{0, 42, 2}, + }, + { + "byte-slice assignment after string conversion", + `package foo + func Main() []byte { + a := "abc" + b := []byte(a) + b[1] = 42 + return []byte(a) + }`, + []byte{0x61, 0x62, 0x63}, + }, + { + "declare and append byte-slice", + `package foo + func Main() []byte { + var a []byte + a = append(a, 1) + a = append(a, 2) + return a + }`, + []byte{1, 2}, + }, } func TestSliceOperations(t *testing.T) { diff --git a/pkg/rpc/server/server_test.go b/pkg/rpc/server/server_test.go index 4bac91ab6..062c01770 100644 --- a/pkg/rpc/server/server_test.go +++ b/pkg/rpc/server/server_test.go @@ -48,18 +48,18 @@ type rpcTestCase struct { check func(t *testing.T, e *executor, result interface{}) } -const testContractHash = "33f3421677fab7f620bd70582f468b4a18df1e5d" +const testContractHash = "1b4357bff5a01bdf2a6581247cf9ed1e24629176" var rpcTestCases = map[string][]rpcTestCase{ "getapplicationlog": { { name: "positive", - params: `["3f1579e797fedb83b66a85fe21d427a119d0e25ef662582e56393fd0d70e4691"]`, + params: `["396d55aa14b6cd428d793e9e740d24f93f62d7ddcdc0f4fdadd4dfd89bdabd83"]`, result: func(e *executor) interface{} { return &result.ApplicationLog{} }, check: func(t *testing.T, e *executor, acc interface{}) { res, ok := acc.(*result.ApplicationLog) require.True(t, ok) - expectedTxHash, err := util.Uint256DecodeStringLE("3f1579e797fedb83b66a85fe21d427a119d0e25ef662582e56393fd0d70e4691") + expectedTxHash, err := util.Uint256DecodeStringLE("396d55aa14b6cd428d793e9e740d24f93f62d7ddcdc0f4fdadd4dfd89bdabd83") require.NoError(t, err) assert.Equal(t, expectedTxHash, res.TxHash) assert.Equal(t, 1, len(res.Executions)) diff --git a/pkg/rpc/server/testdata/test_contract.avm b/pkg/rpc/server/testdata/test_contract.avm index ed72f09df..36266c55e 100755 Binary files a/pkg/rpc/server/testdata/test_contract.avm and b/pkg/rpc/server/testdata/test_contract.avm differ diff --git a/pkg/rpc/server/testdata/testblocks.acc b/pkg/rpc/server/testdata/testblocks.acc index 16577dde5..0957d415d 100644 Binary files a/pkg/rpc/server/testdata/testblocks.acc and b/pkg/rpc/server/testdata/testblocks.acc differ diff --git a/pkg/vm/opcode/opcode.go b/pkg/vm/opcode/opcode.go index 026e1b01e..b729c1e16 100644 --- a/pkg/vm/opcode/opcode.go +++ b/pkg/vm/opcode/opcode.go @@ -146,10 +146,12 @@ const ( STARG Opcode = 0x87 // Splice - CAT Opcode = 0x8B - SUBSTR Opcode = 0x8C - LEFT Opcode = 0x8D - RIGHT Opcode = 0x8E + NEWBUFFER Opcode = 0x88 + MEMCPY Opcode = 0x89 + CAT Opcode = 0x8B + SUBSTR Opcode = 0x8C + LEFT Opcode = 0x8D + RIGHT Opcode = 0x8E // Bitwise logic INVERT Opcode = 0x90 diff --git a/pkg/vm/opcode/opcode_string.go b/pkg/vm/opcode/opcode_string.go index 74852c03e..8545c39f4 100644 --- a/pkg/vm/opcode/opcode_string.go +++ b/pkg/vm/opcode/opcode_string.go @@ -132,6 +132,8 @@ func _() { _ = x[STARG5-133] _ = x[STARG6-134] _ = x[STARG-135] + _ = x[NEWBUFFER-136] + _ = x[MEMCPY-137] _ = x[CAT-139] _ = x[SUBSTR-140] _ = x[LEFT-141] @@ -190,7 +192,7 @@ func _() { _ = x[CONVERT-219] } -const _Opcode_name = "PUSHINT8PUSHINT16PUSHINT32PUSHINT64PUSHINT128PUSHINT256PUSHAPUSHNULLPUSHDATA1PUSHDATA2PUSHDATA4PUSHM1PUSH0PUSH1PUSH2PUSH3PUSH4PUSH5PUSH6PUSH7PUSH8PUSH9PUSH10PUSH11PUSH12PUSH13PUSH14PUSH15PUSH16NOPJMPJMPLJMPIFJMPIFLJMPIFNOTJMPIFNOTLJMPEQJMPEQLJMPNEJMPNELJMPGTJMPGTLJMPGEJMPGELJMPLTJMPLTLJMPLEJMPLELCALLCALLLCALLAABORTASSERTTHROWRETSYSCALLDEPTHDROPNIPXDROPCLEARDUPOVERPICKTUCKSWAPOLDPUSH1ROLLREVERSE3REVERSE4REVERSENINITSSLOTINITSLOTLDSFLD0LDSFLD1LDSFLD2LDSFLD3LDSFLD4LDSFLD5LDSFLD6LDSFLDSTSFLD0STSFLD1STSFLD2STSFLD3STSFLD4STSFLD5STSFLD6STSFLDLDLOC0LDLOC1LDLOC2LDLOC3LDLOC4LDLOC5LDLOC6LDLOCSTLOC0STLOC1STLOC2STLOC3STLOC4STLOC5STLOC6STLOCLDARG0LDARG1LDARG2LDARG3LDARG4LDARG5LDARG6LDARGSTARG0STARG1STARG2STARG3STARG4STARG5STARG6STARGCATSUBSTRLEFTRIGHTINVERTANDORXOREQUALNOTEQUALSIGNABSNEGATEINCDECADDSUBMULDIVMODSHLSHRNOTBOOLANDBOOLORNZNUMEQUALNUMNOTEQUALLTLTEGTGTEMINMAXWITHINPACKUNPACKNEWARRAY0NEWARRAYNEWARRAYTNEWSTRUCT0NEWSTRUCTNEWMAPSIZEHASKEYKEYSVALUESPICKITEMAPPENDSETITEMREVERSEITEMSREMOVECLEARITEMSISNULLISTYPECONVERT" +const _Opcode_name = "PUSHINT8PUSHINT16PUSHINT32PUSHINT64PUSHINT128PUSHINT256PUSHAPUSHNULLPUSHDATA1PUSHDATA2PUSHDATA4PUSHM1PUSH0PUSH1PUSH2PUSH3PUSH4PUSH5PUSH6PUSH7PUSH8PUSH9PUSH10PUSH11PUSH12PUSH13PUSH14PUSH15PUSH16NOPJMPJMPLJMPIFJMPIFLJMPIFNOTJMPIFNOTLJMPEQJMPEQLJMPNEJMPNELJMPGTJMPGTLJMPGEJMPGELJMPLTJMPLTLJMPLEJMPLELCALLCALLLCALLAABORTASSERTTHROWRETSYSCALLDEPTHDROPNIPXDROPCLEARDUPOVERPICKTUCKSWAPOLDPUSH1ROLLREVERSE3REVERSE4REVERSENINITSSLOTINITSLOTLDSFLD0LDSFLD1LDSFLD2LDSFLD3LDSFLD4LDSFLD5LDSFLD6LDSFLDSTSFLD0STSFLD1STSFLD2STSFLD3STSFLD4STSFLD5STSFLD6STSFLDLDLOC0LDLOC1LDLOC2LDLOC3LDLOC4LDLOC5LDLOC6LDLOCSTLOC0STLOC1STLOC2STLOC3STLOC4STLOC5STLOC6STLOCLDARG0LDARG1LDARG2LDARG3LDARG4LDARG5LDARG6LDARGSTARG0STARG1STARG2STARG3STARG4STARG5STARG6STARGNEWBUFFERMEMCPYCATSUBSTRLEFTRIGHTINVERTANDORXOREQUALNOTEQUALSIGNABSNEGATEINCDECADDSUBMULDIVMODSHLSHRNOTBOOLANDBOOLORNZNUMEQUALNUMNOTEQUALLTLTEGTGTEMINMAXWITHINPACKUNPACKNEWARRAY0NEWARRAYNEWARRAYTNEWSTRUCT0NEWSTRUCTNEWMAPSIZEHASKEYKEYSVALUESPICKITEMAPPENDSETITEMREVERSEITEMSREMOVECLEARITEMSISNULLISTYPECONVERT" var _Opcode_map = map[Opcode]string{ 0: _Opcode_name[0:8], @@ -314,62 +316,64 @@ var _Opcode_map = map[Opcode]string{ 133: _Opcode_name[712:718], 134: _Opcode_name[718:724], 135: _Opcode_name[724:729], - 139: _Opcode_name[729:732], - 140: _Opcode_name[732:738], - 141: _Opcode_name[738:742], - 142: _Opcode_name[742:747], - 144: _Opcode_name[747:753], - 145: _Opcode_name[753:756], - 146: _Opcode_name[756:758], - 147: _Opcode_name[758:761], - 151: _Opcode_name[761:766], - 152: _Opcode_name[766:774], - 153: _Opcode_name[774:778], - 154: _Opcode_name[778:781], - 155: _Opcode_name[781:787], - 156: _Opcode_name[787:790], - 157: _Opcode_name[790:793], - 158: _Opcode_name[793:796], - 159: _Opcode_name[796:799], - 160: _Opcode_name[799:802], - 161: _Opcode_name[802:805], - 162: _Opcode_name[805:808], - 168: _Opcode_name[808:811], - 169: _Opcode_name[811:814], - 170: _Opcode_name[814:817], - 171: _Opcode_name[817:824], - 172: _Opcode_name[824:830], - 177: _Opcode_name[830:832], - 179: _Opcode_name[832:840], - 180: _Opcode_name[840:851], - 181: _Opcode_name[851:853], - 182: _Opcode_name[853:856], - 183: _Opcode_name[856:858], - 184: _Opcode_name[858:861], - 185: _Opcode_name[861:864], - 186: _Opcode_name[864:867], - 187: _Opcode_name[867:873], - 192: _Opcode_name[873:877], - 193: _Opcode_name[877:883], - 194: _Opcode_name[883:892], - 195: _Opcode_name[892:900], - 196: _Opcode_name[900:909], - 197: _Opcode_name[909:919], - 198: _Opcode_name[919:928], - 200: _Opcode_name[928:934], - 202: _Opcode_name[934:938], - 203: _Opcode_name[938:944], - 204: _Opcode_name[944:948], - 205: _Opcode_name[948:954], - 206: _Opcode_name[954:962], - 207: _Opcode_name[962:968], - 208: _Opcode_name[968:975], - 209: _Opcode_name[975:987], - 210: _Opcode_name[987:993], - 211: _Opcode_name[993:1003], - 216: _Opcode_name[1003:1009], - 217: _Opcode_name[1009:1015], - 219: _Opcode_name[1015:1022], + 136: _Opcode_name[729:738], + 137: _Opcode_name[738:744], + 139: _Opcode_name[744:747], + 140: _Opcode_name[747:753], + 141: _Opcode_name[753:757], + 142: _Opcode_name[757:762], + 144: _Opcode_name[762:768], + 145: _Opcode_name[768:771], + 146: _Opcode_name[771:773], + 147: _Opcode_name[773:776], + 151: _Opcode_name[776:781], + 152: _Opcode_name[781:789], + 153: _Opcode_name[789:793], + 154: _Opcode_name[793:796], + 155: _Opcode_name[796:802], + 156: _Opcode_name[802:805], + 157: _Opcode_name[805:808], + 158: _Opcode_name[808:811], + 159: _Opcode_name[811:814], + 160: _Opcode_name[814:817], + 161: _Opcode_name[817:820], + 162: _Opcode_name[820:823], + 168: _Opcode_name[823:826], + 169: _Opcode_name[826:829], + 170: _Opcode_name[829:832], + 171: _Opcode_name[832:839], + 172: _Opcode_name[839:845], + 177: _Opcode_name[845:847], + 179: _Opcode_name[847:855], + 180: _Opcode_name[855:866], + 181: _Opcode_name[866:868], + 182: _Opcode_name[868:871], + 183: _Opcode_name[871:873], + 184: _Opcode_name[873:876], + 185: _Opcode_name[876:879], + 186: _Opcode_name[879:882], + 187: _Opcode_name[882:888], + 192: _Opcode_name[888:892], + 193: _Opcode_name[892:898], + 194: _Opcode_name[898:907], + 195: _Opcode_name[907:915], + 196: _Opcode_name[915:924], + 197: _Opcode_name[924:934], + 198: _Opcode_name[934:943], + 200: _Opcode_name[943:949], + 202: _Opcode_name[949:953], + 203: _Opcode_name[953:959], + 204: _Opcode_name[959:963], + 205: _Opcode_name[963:969], + 206: _Opcode_name[969:977], + 207: _Opcode_name[977:983], + 208: _Opcode_name[983:990], + 209: _Opcode_name[990:1002], + 210: _Opcode_name[1002:1008], + 211: _Opcode_name[1008:1018], + 216: _Opcode_name[1018:1024], + 217: _Opcode_name[1024:1030], + 219: _Opcode_name[1030:1037], } func (i Opcode) String() string { diff --git a/pkg/vm/serialization.go b/pkg/vm/serialization.go index 23c68f494..52bd38445 100644 --- a/pkg/vm/serialization.go +++ b/pkg/vm/serialization.go @@ -89,6 +89,9 @@ func serializeItemTo(item StackItem, w *io.BinWriter, seen map[StackItem]bool) { 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) diff --git a/pkg/vm/stack_item.go b/pkg/vm/stack_item.go index 675105f6f..462984c3d 100644 --- a/pkg/vm/stack_item.go +++ b/pkg/vm/stack_item.go @@ -124,14 +124,15 @@ func convertPrimitive(item StackItem, typ StackItemType) (StackItem, error) { return nil, err } return NewBigIntegerItem(bi), nil - case ByteArrayT: + 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 BufferT: - panic("TODO") // #877 case BooleanT: return NewBoolItem(item.Bool()), nil default: @@ -517,16 +518,17 @@ func (i *ByteArrayItem) Bool() bool { // TryBytes implements StackItem interface. func (i *ByteArrayItem) TryBytes() ([]byte, error) { - return i.value, nil + val := make([]byte, len(i.value)) + copy(val, i.value) + return val, nil } // TryInteger implements StackItem interface. func (i *ByteArrayItem) TryInteger() (*big.Int, error) { - bi := emit.BytesToInt(i.value) - if bi.BitLen() > MaxBigIntegerSizeBits { + if len(i.value) > MaxBigIntegerSizeBits/8 { return nil, errors.New("integer is too big") } - return bi, nil + return emit.BytesToInt(i.value), nil } // Equals implements StackItem interface. @@ -940,3 +942,89 @@ func (p *PointerItem) Convert(typ StackItemType) (StackItem, error) { 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 index fb2f3633f..06db6ee61 100644 --- a/pkg/vm/stack_item_test.go +++ b/pkg/vm/stack_item_test.go @@ -374,6 +374,10 @@ var marshalJSONTestCases = []struct { 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"]`), @@ -432,6 +436,10 @@ var toContractParameterTestCases = []struct { 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{ diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index 07ca81046..515008ea1 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "io/ioutil" + "math" "math/big" "os" "text/tabwriter" @@ -634,6 +635,36 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro item := v.estack.Pop().Item() ctx.arguments.Set(int(parameter[0]), item) + case opcode.NEWBUFFER: + n := toInt(v.estack.Pop().BigInt()) + if n < 0 || n > MaxItemSize { + panic("invalid size") + } + v.estack.PushVal(NewBufferItem(make([]byte, n))) + + case opcode.MEMCPY: + n := toInt(v.estack.Pop().BigInt()) + if n < 0 { + panic("invalid size") + } + si := toInt(v.estack.Pop().BigInt()) + if si < 0 { + panic("invalid source index") + } + src := v.estack.Pop().Bytes() + if sum := si + n; sum < 0 || sum > len(src) { + panic("size is too big") + } + di := toInt(v.estack.Pop().BigInt()) + if di < 0 { + panic("invalid destination index") + } + dst := v.estack.Pop().value.(*BufferItem).value + if sum := si + n; sum < 0 || sum > len(dst) { + panic("size is too big") + } + copy(dst[di:], src[si:si+n]) + case opcode.CAT: b := v.estack.Pop().Bytes() a := v.estack.Pop().Bytes() @@ -641,7 +672,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(ab) + v.estack.PushVal(NewBufferItem(ab)) case opcode.SUBSTR: l := int(v.estack.Pop().BigInt().Int64()) @@ -657,7 +688,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro if last > len(s) { panic("invalid offset") } - v.estack.PushVal(s[o:last]) + v.estack.PushVal(NewBufferItem(s[o:last])) case opcode.LEFT: l := int(v.estack.Pop().BigInt().Int64()) @@ -668,7 +699,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro if t := len(s); l > t { l = t } - v.estack.PushVal(s[:l]) + v.estack.PushVal(NewBufferItem(s[:l])) case opcode.RIGHT: l := int(v.estack.Pop().BigInt().Int64()) @@ -676,7 +707,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(s[len(s)-l:]) + v.estack.PushVal(NewBufferItem(s[len(s)-l:])) case opcode.DEPTH: v.estack.PushVal(v.estack.Len()) @@ -1100,16 +1131,36 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro t.Add(key.value, item) v.refs.Add(item) + case *BufferItem: + index := toInt(key.BigInt()) + if index < 0 || index >= len(t.value) { + panic("invalid index") + } + bi, err := item.TryInteger() + b := toInt(bi) + if err != nil || b < math.MinInt8 || b > math.MaxUint8 { + panic("invalid value") + } + t.value[index] = byte(b) + default: panic(fmt.Sprintf("SETITEM: invalid item type %s", t)) } case opcode.REVERSEITEMS: - a := v.estack.Pop().Array() - if len(a) > 1 { - for i, j := 0, len(a)-1; i <= j; i, j = i+1, j-1 { + item := v.estack.Pop() + switch t := item.value.(type) { + case *ArrayItem, *StructItem: + a := t.Value().([]StackItem) + 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] + } + default: + panic(fmt.Sprintf("invalid item type %s", t)) } case opcode.REMOVE: key := v.estack.Pop() @@ -1322,6 +1373,12 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro v.estack.PushVal(index < int64(len(c.Array()))) case *MapItem: v.estack.PushVal(t.Has(key.Item())) + case *BufferItem: + index := key.BigInt().Int64() + if index < 0 { + panic("negative index") + } + v.estack.PushVal(index < int64(len(t.value))) default: panic("wrong collection type") } @@ -1576,3 +1633,15 @@ func (v *VM) GetEntryScriptHash() util.Uint160 { func (v *VM) GetCurrentScriptHash() util.Uint160 { return v.getContextScriptHash(0) } + +// toInt converts an item to a 32-bit int. +func toInt(i *big.Int) int { + if !i.IsInt64() { + panic("not an int32") + } + n := i.Int64() + if n < math.MinInt32 || n > math.MaxInt32 { + panic("not an int32") + } + return int(n) +} diff --git a/pkg/vm/vm_test.go b/pkg/vm/vm_test.go index a8d08c23d..2a906edab 100644 --- a/pkg/vm/vm_test.go +++ b/pkg/vm/vm_test.go @@ -937,6 +937,8 @@ func TestNOT(t *testing.T) { 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}))) } // getBigInt returns 2^a+b @@ -1056,6 +1058,7 @@ 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}))) } func TestEQUAL(t *testing.T) { @@ -1066,6 +1069,7 @@ func TestEQUAL(t *testing.T) { 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}))) } func runWithArgs(t *testing.T, prog []byte, result interface{}, args ...interface{}) { @@ -1150,6 +1154,26 @@ func TestDECBigResult(t *testing.T) { checkVMFailed(t, vm) } +func TestNEWBUFFER(t *testing.T) { + prog := makeProgram(opcode.NEWBUFFER) + t.Run("Good", getTestFuncForVM(prog, NewBufferItem([]byte{0, 0, 0}), 3)) + t.Run("Negative", getTestFuncForVM(prog, nil, -1)) + t.Run("TooBig", getTestFuncForVM(prog, nil, MaxItemSize+1)) +} + +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) + }) + 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)) +} + func TestNEWARRAY0(t *testing.T) { prog := makeProgram(opcode.NEWARRAY0) runWithArgs(t, prog, []StackItem{}) @@ -1270,6 +1294,7 @@ func TestPICKITEM(t *testing.T) { t.Run("bad index", getTestFuncForVM(prog, nil, []StackItem{}, 0)) t.Run("Array", getTestFuncForVM(prog, 2, []StackItem{makeStackItem(1), makeStackItem(2)}, 1)) t.Run("ByteArray", getTestFuncForVM(prog, 2, []byte{1, 2}, 1)) + t.Run("Buffer", getTestFuncForVM(prog, 2, NewBufferItem([]byte{1, 2}), 1)) } func TestPICKITEMDupArray(t *testing.T) { @@ -1305,6 +1330,13 @@ func TestPICKITEMMap(t *testing.T) { 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}))) +} + func TestSETITEMMap(t *testing.T) { prog := makeProgram(opcode.SETITEM, opcode.PICKITEM) m := NewMapItem() @@ -1341,6 +1373,7 @@ 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("Bool", getTestFuncForVM(prog, 1, false)) t.Run("Array", getTestFuncForVM(prog, 2, []StackItem{makeStackItem(1), makeStackItem([]byte{})})) t.Run("Map", func(t *testing.T) { @@ -1416,6 +1449,12 @@ func TestHASKEY(t *testing.T) { t.Run("True", getTestFuncForVM(prog, true, NewStructItem(arr), 4)) t.Run("False", getTestFuncForVM(prog, false, NewStructItem(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)) + }) } func TestHASKEYMap(t *testing.T) { @@ -1734,9 +1773,9 @@ func TestCAT(t *testing.T) { arg := make([]byte, MaxItemSize/2+1) runWithArgs(t, prog, nil, arg, arg) }) - t.Run("Good", getTestFuncForVM(prog, []byte("abcdef"), []byte("abc"), []byte("def"))) - t.Run("Int0ByteArray", getTestFuncForVM(prog, []byte{}, 0, []byte{})) - t.Run("ByteArrayInt1", getTestFuncForVM(prog, []byte{1}, []byte{}, 1)) + 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)) } func TestSUBSTR(t *testing.T) { @@ -1744,7 +1783,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, []byte("bc"), []byte("abcdef"), 1, 2)) + t.Run("Good", getTestFuncForVM(prog, NewBufferItem([]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)) @@ -1767,8 +1806,8 @@ 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, "ab", "abcdef", 2)) - t.Run("GoodBigLen", getTestFuncForVM(prog, "abcdef", "abcdef", 8)) + t.Run("Good", getTestFuncForVM(prog, NewBufferItem([]byte("ab")), "abcdef", 2)) + t.Run("GoodBigLen", getTestFuncForVM(prog, NewBufferItem([]byte("abcdef")), "abcdef", 8)) } func TestRIGHT(t *testing.T) { @@ -1776,7 +1815,7 @@ 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, "ef", "abcdef", 2)) + t.Run("Good", getTestFuncForVM(prog, NewBufferItem([]byte("ef")), "abcdef", 2)) t.Run("BadLen", getTestFuncForVM(prog, nil, "abcdef", 8)) } @@ -1838,11 +1877,10 @@ func TestUNPACKGood(t *testing.T) { assert.Equal(t, int64(1), vm.estack.Peek(len(elements)+1).BigInt().Int64()) } -func TestREVERSEITEMSBadNotArray(t *testing.T) { - prog := makeProgram(opcode.REVERSEITEMS) - vm := load(prog) - vm.estack.PushVal(1) - checkVMFailed(t, vm) +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}))) } func testREVERSEITEMSIssue437(t *testing.T, i1, i2 opcode.Opcode, reversed bool) {