diff --git a/pkg/vm/stack.go b/pkg/vm/stack.go index 740aa835e..e840bccb7 100644 --- a/pkg/vm/stack.go +++ b/pkg/vm/stack.go @@ -121,6 +121,8 @@ func (e *Element) Array() []StackItem { switch t := e.value.(type) { case *ArrayItem: return t.value + case *StructItem: + return t.value default: panic("element is not an array") } diff --git a/pkg/vm/tests/binary_expr_test.go b/pkg/vm/tests/binary_expr_test.go index 16e7509fb..fd24c2e37 100644 --- a/pkg/vm/tests/binary_expr_test.go +++ b/pkg/vm/tests/binary_expr_test.go @@ -26,7 +26,7 @@ var binaryExprTestCases = []testCase{ return x } `, - big.NewInt(0), + []byte{}, }, { "simple div", @@ -86,7 +86,7 @@ var binaryExprTestCases = []testCase{ return 0 } `, - big.NewInt(0), + []byte{}, }, { "compare equal strings with eql", @@ -128,7 +128,7 @@ var binaryExprTestCases = []testCase{ return 0 } `, - big.NewInt(0), + []byte{}, }, { "compare equal ints with eql", @@ -156,7 +156,7 @@ var binaryExprTestCases = []testCase{ return 0 } `, - big.NewInt(0), + []byte{}, }, { "compare not equal ints with eql", @@ -170,7 +170,7 @@ var binaryExprTestCases = []testCase{ return 0 } `, - big.NewInt(0), + []byte{}, }, { "compare not equal ints with neq", diff --git a/pkg/vm/tests/for_test.go b/pkg/vm/tests/for_test.go index 70b063ffe..2e3c15288 100644 --- a/pkg/vm/tests/for_test.go +++ b/pkg/vm/tests/for_test.go @@ -275,7 +275,7 @@ func TestIfUnaryInvert(t *testing.T) { return 0 } ` - eval(t, src, big.NewInt(0)) + eval(t, src, []byte{}) } func TestAppendByte(t *testing.T) { diff --git a/pkg/vm/tests/function_call_test.go b/pkg/vm/tests/function_call_test.go index 442d703b4..1ac71b011 100644 --- a/pkg/vm/tests/function_call_test.go +++ b/pkg/vm/tests/function_call_test.go @@ -35,7 +35,7 @@ func TestNotAssignedFunctionCall(t *testing.T) { return 0 } ` - eval(t, src, big.NewInt(0)) + eval(t, src, []byte{}) } func TestMultipleFunctionCalls(t *testing.T) { diff --git a/pkg/vm/tests/if_test.go b/pkg/vm/tests/if_test.go index 6e39d0ab6..c4483309e 100644 --- a/pkg/vm/tests/if_test.go +++ b/pkg/vm/tests/if_test.go @@ -30,7 +30,7 @@ func TestGT(t *testing.T) { return 0 } ` - eval(t, src, big.NewInt(0)) + eval(t, src, []byte{}) } func TestGTE(t *testing.T) { @@ -44,7 +44,7 @@ func TestGTE(t *testing.T) { return 0 } ` - eval(t, src, big.NewInt(0)) + eval(t, src, []byte{}) } func TestLAND(t *testing.T) { @@ -89,5 +89,5 @@ func TestNestedIF(t *testing.T) { return 0 } ` - eval(t, src, big.NewInt(0)) + eval(t, src, []byte{}) } diff --git a/pkg/vm/tests/import_test.go b/pkg/vm/tests/import_test.go index c969ba71f..fb831fcef 100644 --- a/pkg/vm/tests/import_test.go +++ b/pkg/vm/tests/import_test.go @@ -32,7 +32,7 @@ func TestImportStruct(t *testing.T) { return b.Y } ` - eval(t, src, big.NewInt(0)) + eval(t, src, []byte{}) } func TestMultipleDirFileImport(t *testing.T) { diff --git a/pkg/vm/tests/struct_test.go b/pkg/vm/tests/struct_test.go index 759396889..e3de28f91 100644 --- a/pkg/vm/tests/struct_test.go +++ b/pkg/vm/tests/struct_test.go @@ -179,7 +179,7 @@ var structTestCases = []testCase{ return t.y } `, - big.NewInt(0), + []byte{}, }, { "test return struct from func", @@ -209,7 +209,7 @@ var structTestCases = []testCase{ vm.NewBigIntegerItem(1), vm.NewBigIntegerItem(2), vm.NewByteArrayItem([]byte("hello")), - vm.NewBigIntegerItem(0), + vm.NewByteArrayItem([]byte{}), }, }, { diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index c9bc97d2b..f0647d8d6 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -257,7 +257,7 @@ func (v *VM) execute(ctx *Context, op Instruction) { v.estack.PushVal(val) case PUSH0: - v.estack.PushVal(0) + v.estack.PushVal([]byte{}) case PUSHDATA1: n := ctx.readByte() @@ -375,7 +375,7 @@ func (v *VM) execute(ctx *Context, op Instruction) { case XTUCK: n := int(v.estack.Pop().BigInt().Int64()) - if n < 0 { + if n <= 0 { panic("XTUCK: invalid length") } a := v.estack.Dup(0) @@ -388,21 +388,20 @@ func (v *VM) execute(ctx *Context, op Instruction) { v.estack.InsertAt(a, n) case ROT: - c := v.estack.Pop() - b := v.estack.Pop() - a := v.estack.Pop() - - v.estack.Push(b) - v.estack.Push(c) - v.estack.Push(a) + e := v.estack.RemoveAt(2) + if e == nil { + panic("no top-level element found") + } + v.estack.Push(e) case DEPTH: v.estack.PushVal(v.estack.Len()) case NIP: - elem := v.estack.Pop() - _ = v.estack.Pop() - v.estack.Push(elem) + elem := v.estack.RemoveAt(1) + if elem == nil { + panic("no second element found") + } case OVER: b := v.estack.Pop() @@ -441,11 +440,20 @@ func (v *VM) execute(ctx *Context, op Instruction) { } case DROP: + if v.estack.Len() < 1 { + panic("stack is too small") + } v.estack.Pop() case EQUAL: b := v.estack.Pop() + if b == nil { + panic("no top-level element found") + } a := v.estack.Pop() + if a == nil { + panic("no second-to-the-top element found") + } v.estack.PushVal(reflect.DeepEqual(a, b)) // Bit operations. @@ -497,11 +505,17 @@ func (v *VM) execute(ctx *Context, op Instruction) { case SHL: b := v.estack.Pop().BigInt() + if b.Int64() == 0 { + return + } a := v.estack.Pop().BigInt() v.estack.PushVal(new(big.Int).Lsh(a, uint(b.Int64()))) case SHR: b := v.estack.Pop().BigInt() + if b.Int64() == 0 { + return + } a := v.estack.Pop().BigInt() v.estack.PushVal(new(big.Int).Rsh(a, uint(b.Int64()))) @@ -601,31 +615,27 @@ func (v *VM) execute(ctx *Context, op Instruction) { case NEWARRAY: item := v.estack.Pop() switch t := item.value.(type) { - case *BigIntegerItem: - n := t.value.Int64() - items := make([]StackItem, n) - v.estack.PushVal(&ArrayItem{items}) case *StructItem: v.estack.PushVal(&ArrayItem{t.value}) case *ArrayItem: v.estack.PushVal(t) default: - panic("NEWARRAY: invalid operand") + n := item.BigInt() + items := makeArrayOfFalses(int(n.Int64())) + v.estack.PushVal(&ArrayItem{items}) } case NEWSTRUCT: item := v.estack.Pop() switch t := item.value.(type) { - case *BigIntegerItem: - n := t.value.Int64() - items := make([]StackItem, n) - v.estack.PushVal(&StructItem{items}) case *ArrayItem: v.estack.PushVal(&StructItem{t.value}) case *StructItem: v.estack.PushVal(t) default: - panic("NEWSTRUCT: invalid operand") + n := item.BigInt() + items := makeArrayOfFalses(int(n.Int64())) + v.estack.PushVal(&StructItem{items}) } case APPEND: @@ -683,7 +693,12 @@ func (v *VM) execute(ctx *Context, op Instruction) { item := arr[index] v.estack.PushVal(item) default: - panic("PICKITEM: unknown type") + arr := obj.Bytes() + if index < 0 || index >= len(arr) { + panic("PICKITEM: invalid index") + } + item := arr[index] + v.estack.PushVal(int(item)) } case SETITEM: @@ -707,7 +722,7 @@ func (v *VM) execute(ctx *Context, op Instruction) { } case REVERSE: - a := v.estack.Peek(0).Array() + a := v.estack.Pop().Array() if len(a) > 1 { for i, j := 0, len(a)-1; i <= j; i, j = i+1, j-1 { a[i], a[j] = a[j], a[i] @@ -715,10 +730,25 @@ func (v *VM) execute(ctx *Context, op Instruction) { } case REMOVE: key := int(v.estack.Pop().BigInt().Int64()) - elem := v.estack.Peek(0) - a := elem.Array() - a = append(a[:key], a[key+1:]...) - elem.value = makeStackItem(a) + elem := v.estack.Pop() + switch t := elem.value.(type) { + case *ArrayItem: + a := t.value + if key < 0 || key >= len(a) { + panic("REMOVE: invalid index") + } + a = append(a[:key], a[key+1:]...) + t.value = a + case *StructItem: + a := t.value + if key < 0 || key >= len(a) { + panic("REMOVE: invalid index") + } + a = append(a[:key], a[key+1:]...) + t.value = a + default: + panic("REMOVE: invalid type") + } case ARRAYSIZE: elem := v.estack.Pop() @@ -735,10 +765,7 @@ func (v *VM) execute(ctx *Context, op Instruction) { case SIZE: elem := v.estack.Pop() - arr, ok := elem.value.Value().([]uint8) - if !ok { - panic("SIZE: item not of type []uint8") - } + arr := elem.Bytes() v.estack.PushVal(len(arr)) case JMP, JMPIF, JMPIFNOT: @@ -840,6 +867,14 @@ func (v *VM) execute(ctx *Context, op Instruction) { } } +func makeArrayOfFalses(n int) []StackItem { + items := make([]StackItem, n) + for i := range items { + items[i] = &BoolItem{false} + } + return items +} + func init() { log.SetPrefix("NEO-GO-VM > ") log.SetFlags(0) diff --git a/pkg/vm/vm_test.go b/pkg/vm/vm_test.go index 5a7f5771a..2193afcb9 100644 --- a/pkg/vm/vm_test.go +++ b/pkg/vm/vm_test.go @@ -283,6 +283,50 @@ func TestSub(t *testing.T) { assert.Equal(t, int64(2), vm.estack.Pop().BigInt().Int64()) } +func TestSHRGood(t *testing.T) { + prog := makeProgram(SHR) + vm := load(prog) + vm.estack.PushVal(4) + vm.estack.PushVal(2) + vm.Run() + assert.Equal(t, false, vm.state.HasFlag(faultState)) + assert.Equal(t, 1, vm.estack.Len()) + assert.Equal(t, makeStackItem(1), vm.estack.Pop().value) +} + +func TestSHRZero(t *testing.T) { + prog := makeProgram(SHR) + vm := load(prog) + vm.estack.PushVal([]byte{0, 1}) + vm.estack.PushVal(0) + vm.Run() + assert.Equal(t, false, vm.state.HasFlag(faultState)) + assert.Equal(t, 1, vm.estack.Len()) + assert.Equal(t, makeStackItem([]byte{0, 1}), vm.estack.Pop().value) +} + +func TestSHLGood(t *testing.T) { + prog := makeProgram(SHL) + vm := load(prog) + vm.estack.PushVal(4) + vm.estack.PushVal(2) + vm.Run() + assert.Equal(t, false, vm.state.HasFlag(faultState)) + assert.Equal(t, 1, vm.estack.Len()) + assert.Equal(t, makeStackItem(16), vm.estack.Pop().value) +} + +func TestSHLZero(t *testing.T) { + prog := makeProgram(SHL) + vm := load(prog) + vm.estack.PushVal([]byte{0, 1}) + vm.estack.PushVal(0) + vm.Run() + assert.Equal(t, false, vm.state.HasFlag(faultState)) + assert.Equal(t, 1, vm.estack.Len()) + assert.Equal(t, makeStackItem([]byte{0, 1}), vm.estack.Pop().value) +} + func TestLT(t *testing.T) { prog := makeProgram(LT) vm := load(prog) @@ -335,6 +379,32 @@ func TestDepth(t *testing.T) { assert.Equal(t, int64(3), vm.estack.Pop().BigInt().Int64()) } +func TestEQUALNoArguments(t *testing.T) { + prog := makeProgram(EQUAL) + vm := load(prog) + vm.Run() + assert.Equal(t, true, vm.state.HasFlag(faultState)) +} + +func TestEQUALBad1Argument(t *testing.T) { + prog := makeProgram(EQUAL) + vm := load(prog) + vm.estack.PushVal(1) + vm.Run() + assert.Equal(t, true, vm.state.HasFlag(faultState)) +} + +func TestEQUALGoodInteger(t *testing.T) { + prog := makeProgram(EQUAL) + vm := load(prog) + vm.estack.PushVal(5) + vm.estack.PushVal(5) + vm.Run() + assert.Equal(t, false, vm.state.HasFlag(faultState)) + assert.Equal(t, 1, vm.estack.Len()) + assert.Equal(t, &BoolItem{true}, vm.estack.Pop().value) +} + func TestNumEqual(t *testing.T) { prog := makeProgram(NUMEQUAL) vm := load(prog) @@ -371,7 +441,7 @@ func TestNEWARRAYInteger(t *testing.T) { vm.Run() assert.Equal(t, false, vm.state.HasFlag(faultState)) assert.Equal(t, 1, vm.estack.Len()) - assert.Equal(t, &ArrayItem{make([]StackItem, 1)}, vm.estack.Pop().value) + assert.Equal(t, &ArrayItem{[]StackItem{makeStackItem(false)}}, vm.estack.Pop().value) } func TestNEWARRAYStruct(t *testing.T) { @@ -396,12 +466,14 @@ func TestNEWARRAYArray(t *testing.T) { assert.Equal(t, &ArrayItem{arr}, vm.estack.Pop().value) } -func TestNEWARRAYWrongType(t *testing.T) { +func TestNEWARRAYByteArray(t *testing.T) { prog := makeProgram(NEWARRAY) vm := load(prog) - vm.estack.Push(NewElement([]byte{})) + vm.estack.PushVal([]byte{}) vm.Run() - assert.Equal(t, true, vm.state.HasFlag(faultState)) + assert.Equal(t, false, vm.state.HasFlag(faultState)) + assert.Equal(t, 1, vm.estack.Len()) + assert.Equal(t, &ArrayItem{[]StackItem{}}, vm.estack.Pop().value) } func TestNEWSTRUCTInteger(t *testing.T) { @@ -411,7 +483,7 @@ func TestNEWSTRUCTInteger(t *testing.T) { vm.Run() assert.Equal(t, false, vm.state.HasFlag(faultState)) assert.Equal(t, 1, vm.estack.Len()) - assert.Equal(t, &StructItem{make([]StackItem, 1)}, vm.estack.Pop().value) + assert.Equal(t, &StructItem{[]StackItem{makeStackItem(false)}}, vm.estack.Pop().value) } func TestNEWSTRUCTArray(t *testing.T) { @@ -436,12 +508,14 @@ func TestNEWSTRUCTStruct(t *testing.T) { assert.Equal(t, &StructItem{arr}, vm.estack.Pop().value) } -func TestNEWSTRUCTWrongType(t *testing.T) { +func TestNEWSTRUCTByteArray(t *testing.T) { prog := makeProgram(NEWSTRUCT) vm := load(prog) - vm.estack.Push(NewElement([]byte{})) + vm.estack.PushVal([]byte{}) vm.Run() - assert.Equal(t, true, vm.state.HasFlag(faultState)) + assert.Equal(t, false, vm.state.HasFlag(faultState)) + assert.Equal(t, 1, vm.estack.Len()) + assert.Equal(t, &StructItem{[]StackItem{}}, vm.estack.Pop().value) } func TestAPPENDArray(t *testing.T) { @@ -488,6 +562,64 @@ func TestAPPENDWrongType(t *testing.T) { assert.Equal(t, true, vm.state.HasFlag(faultState)) } +func TestPICKITEMBadIndex(t *testing.T) { + prog := makeProgram(PICKITEM) + vm := load(prog) + vm.estack.PushVal([]StackItem{}) + vm.estack.PushVal(0) + vm.Run() + assert.Equal(t, true, vm.state.HasFlag(faultState)) +} + +func TestPICKITEMArray(t *testing.T) { + prog := makeProgram(PICKITEM) + vm := load(prog) + vm.estack.PushVal([]StackItem{makeStackItem(1), makeStackItem(2)}) + vm.estack.PushVal(1) + vm.Run() + assert.Equal(t, false, vm.state.HasFlag(faultState)) + assert.Equal(t, 1, vm.estack.Len()) + assert.Equal(t, makeStackItem(2), vm.estack.Pop().value) +} + +func TestPICKITEMByteArray(t *testing.T) { + prog := makeProgram(PICKITEM) + vm := load(prog) + vm.estack.PushVal([]byte{1, 2}) + vm.estack.PushVal(1) + vm.Run() + assert.Equal(t, false, vm.state.HasFlag(faultState)) + assert.Equal(t, 1, vm.estack.Len()) + assert.Equal(t, makeStackItem(2), vm.estack.Pop().value) +} + +func TestSIZENoArgument(t *testing.T) { + prog := makeProgram(SIZE) + vm := load(prog) + vm.Run() + assert.Equal(t, true, vm.state.HasFlag(faultState)) +} + +func TestSIZEByteArray(t *testing.T) { + prog := makeProgram(SIZE) + vm := load(prog) + vm.estack.PushVal([]byte{0, 1}) + vm.Run() + assert.Equal(t, false, vm.state.HasFlag(faultState)) + assert.Equal(t, 1, vm.estack.Len()) + assert.Equal(t, makeStackItem(2), vm.estack.Pop().value) +} + +func TestSIZEBool(t *testing.T) { + prog := makeProgram(SIZE) + vm := load(prog) + vm.estack.PushVal(false) + vm.Run() + assert.Equal(t, false, vm.state.HasFlag(faultState)) + assert.Equal(t, 1, vm.estack.Len()) + assert.Equal(t, makeStackItem(1), vm.estack.Pop().value) +} + func TestSIGNNoArgument(t *testing.T) { prog := makeProgram(SIGN) vm := load(prog) @@ -633,6 +765,29 @@ func TestPICKgood(t *testing.T) { assert.Equal(t, int64(result), vm.estack.Pop().BigInt().Int64()) } +func TestROTBad(t *testing.T) { + prog := makeProgram(ROT) + vm := load(prog) + vm.estack.PushVal(1) + vm.estack.PushVal(2) + vm.Run() + assert.Equal(t, true, vm.state.HasFlag(faultState)) +} + +func TestROTGood(t *testing.T) { + prog := makeProgram(ROT) + vm := load(prog) + vm.estack.PushVal(1) + vm.estack.PushVal(2) + vm.estack.PushVal(3) + vm.Run() + assert.Equal(t, false, vm.state.HasFlag(faultState)) + 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) +} + func TestXTUCKbadNoitem(t *testing.T) { prog := makeProgram(XTUCK) vm := load(prog) @@ -658,6 +813,15 @@ func TestXTUCKbadNegative(t *testing.T) { assert.Equal(t, true, vm.state.HasFlag(faultState)) } +func TestXTUCKbadZero(t *testing.T) { + prog := makeProgram(XTUCK) + vm := load(prog) + vm.estack.PushVal(1) + vm.estack.PushVal(0) + vm.Run() + assert.Equal(t, true, vm.state.HasFlag(faultState)) +} + func TestXTUCKgood(t *testing.T) { prog := makeProgram(XTUCK) topelement := 5 @@ -745,6 +909,41 @@ func TestOVERgood(t *testing.T) { assert.Equal(t, 3, vm.estack.Len()) } +func TestNIPBadNoItem(t *testing.T) { + prog := makeProgram(NIP) + vm := load(prog) + vm.estack.PushVal(1) + vm.Run() + assert.Equal(t, true, vm.state.HasFlag(faultState)) +} + +func TestNIPGood(t *testing.T) { + prog := makeProgram(NIP) + vm := load(prog) + vm.estack.PushVal(1) + vm.estack.PushVal(2) + vm.Run() + assert.Equal(t, false, vm.state.HasFlag(faultState)) + assert.Equal(t, 1, vm.estack.Len()) + assert.Equal(t, makeStackItem(2), vm.estack.Pop().value) +} + +func TestDROPBadNoItem(t *testing.T) { + prog := makeProgram(DROP) + vm := load(prog) + vm.Run() + assert.Equal(t, true, vm.state.HasFlag(faultState)) +} + +func TestDROPGood(t *testing.T) { + prog := makeProgram(DROP) + vm := load(prog) + vm.estack.PushVal(1) + vm.Run() + assert.Equal(t, false, vm.state.HasFlag(faultState)) + assert.Equal(t, 0, vm.estack.Len()) +} + func TestXDROPbadNoitem(t *testing.T) { prog := makeProgram(XDROP) vm := load(prog) @@ -1118,7 +1317,7 @@ func TestREVERSEBadNotArray(t *testing.T) { } func TestREVERSEGoodOneElem(t *testing.T) { - prog := makeProgram(REVERSE) + prog := makeProgram(DUP, REVERSE) elements := []int{22} vm := load(prog) vm.estack.PushVal(1) @@ -1132,13 +1331,42 @@ func TestREVERSEGoodOneElem(t *testing.T) { assert.Equal(t, int64(elements[0]), e.Int64()) } +func TestREVERSEGoodStruct(t *testing.T) { + eodd := []int{22, 34, 42, 55, 81} + even := []int{22, 34, 42, 55, 81, 99} + eall := [][]int{eodd, even} + + for _, elements := range eall { + prog := makeProgram(DUP, REVERSE) + vm := load(prog) + vm.estack.PushVal(1) + + arr := make([]StackItem, len(elements)) + for i := range elements { + arr[i] = makeStackItem(elements[i]) + } + vm.estack.Push(&Element{value: &StructItem{arr}}) + + vm.Run() + assert.Equal(t, false, vm.state.HasFlag(faultState)) + assert.Equal(t, 2, vm.estack.Len()) + a := vm.estack.Peek(0).Array() + assert.Equal(t, len(elements), len(a)) + for k, v := range elements { + e := a[len(a)-1-k].Value().(*big.Int) + assert.Equal(t, int64(v), e.Int64()) + } + assert.Equal(t, int64(1), vm.estack.Peek(1).BigInt().Int64()) + } +} + func TestREVERSEGood(t *testing.T) { eodd := []int{22, 34, 42, 55, 81} even := []int{22, 34, 42, 55, 81, 99} eall := [][]int{eodd, even} for _, elements := range eall { - prog := makeProgram(REVERSE) + prog := makeProgram(DUP, REVERSE) vm := load(prog) vm.estack.PushVal(1) vm.estack.PushVal(elements) @@ -1190,23 +1418,17 @@ func TestREMOVEBadIndex(t *testing.T) { } func TestREMOVEGood(t *testing.T) { - prog := makeProgram(REMOVE) + prog := makeProgram(DUP, PUSH2, REMOVE) elements := []int{22, 34, 42, 55, 81} reselements := []int{22, 34, 55, 81} vm := load(prog) vm.estack.PushVal(1) vm.estack.PushVal(elements) - vm.estack.PushVal(2) vm.Run() assert.Equal(t, false, vm.state.HasFlag(faultState)) assert.Equal(t, 2, vm.estack.Len()) - a := vm.estack.Peek(0).Array() - assert.Equal(t, len(reselements), len(a)) - for k, v := range reselements { - e := a[k].Value().(*big.Int) - assert.Equal(t, int64(v), e.Int64()) - } - assert.Equal(t, int64(1), vm.estack.Peek(1).BigInt().Int64()) + assert.Equal(t, makeStackItem(reselements), vm.estack.Pop().value) + assert.Equal(t, makeStackItem(1), vm.estack.Pop().value) } func makeProgram(opcodes ...Instruction) []byte {