From c596a6b273c837571914c1c088052846d0dd5eab Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Tue, 17 Dec 2019 14:01:15 +0300 Subject: [PATCH 1/5] vm: fix INVERT behaviour for converted values Tests added were failing before this change. --- pkg/vm/vm.go | 5 +++-- pkg/vm/vm_test.go | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index 4c57ea3bc..1d36341b2 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -677,8 +677,9 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro // Bit operations. case opcode.INVERT: // inplace - a := v.estack.Peek(0).BigInt() - a.Not(a) + e := v.estack.Peek(0) + i := e.BigInt() + e.value = makeStackItem(i.Not(i)) case opcode.AND: b := v.estack.Pop().BigInt() diff --git a/pkg/vm/vm_test.go b/pkg/vm/vm_test.go index 838b1faf4..1bf8d1887 100644 --- a/pkg/vm/vm_test.go +++ b/pkg/vm/vm_test.go @@ -1759,6 +1759,20 @@ func TestINVERTgood3(t *testing.T) { assert.Equal(t, int64(-0x6A), vm.estack.Peek(0).BigInt().Int64()) } +func TestINVERTWithConversion1(t *testing.T) { + prog := makeProgram(opcode.PUSHDATA2, 0, 0, opcode.INVERT) + vm := load(prog) + runVM(t, vm) + assert.Equal(t, int64(-1), vm.estack.Peek(0).BigInt().Int64()) +} + +func TestINVERTWithConversion2(t *testing.T) { + prog := makeProgram(opcode.PUSH0, opcode.PUSH1, opcode.NUMEQUAL, opcode.INVERT) + vm := load(prog) + runVM(t, vm) + assert.Equal(t, int64(-1), vm.estack.Peek(0).BigInt().Int64()) +} + func TestCATBadNoArgs(t *testing.T) { prog := makeProgram(opcode.CAT) vm := load(prog) From 60dfa05b19cac9ed8ac9d5945d86e1679b79577e Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Tue, 17 Dec 2019 16:38:42 +0300 Subject: [PATCH 2/5] vm: duplicate an item in Dup TestDupByteArray and TestDupInt were failing before this patch. --- pkg/vm/context.go | 5 +++++ pkg/vm/stack.go | 2 +- pkg/vm/stack_item.go | 44 ++++++++++++++++++++++++++++++++++++++++++++ pkg/vm/vm_test.go | 36 ++++++++++++++++++++++++++++++++++++ 4 files changed, 86 insertions(+), 1 deletion(-) diff --git a/pkg/vm/context.go b/pkg/vm/context.go index f1d5899df..3b689cedf 100644 --- a/pkg/vm/context.go +++ b/pkg/vm/context.go @@ -159,6 +159,11 @@ func (c *Context) Value() interface{} { return c } +// Dup implements StackItem interface. +func (c *Context) Dup() StackItem { + return c +} + func (c *Context) atBreakPoint() bool { for _, n := range c.breakPoints { if n == c.ip { diff --git a/pkg/vm/stack.go b/pkg/vm/stack.go index af8f2cecc..caafbdc06 100644 --- a/pkg/vm/stack.go +++ b/pkg/vm/stack.go @@ -346,7 +346,7 @@ func (s *Stack) Dup(n int) *Element { } return &Element{ - value: e.value, + value: e.value.Dup(), } } diff --git a/pkg/vm/stack_item.go b/pkg/vm/stack_item.go index 4b13c12ee..de277159a 100644 --- a/pkg/vm/stack_item.go +++ b/pkg/vm/stack_item.go @@ -15,6 +15,8 @@ import ( type StackItem interface { fmt.Stringer Value() interface{} + // Dup duplicates current StackItem. + Dup() StackItem } func makeStackItem(v interface{}) StackItem { @@ -107,6 +109,12 @@ 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 +} + // Clone returns a Struct with all Struct fields copied by value. // Array fields are still copied by reference. func (i *StructItem) Clone() *StructItem { @@ -148,6 +156,12 @@ 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)} +} + // MarshalJSON implements the json.Marshaler interface. func (i *BigIntegerItem) MarshalJSON() ([]byte, error) { return json.Marshal(i.value) @@ -179,6 +193,11 @@ func (i *BoolItem) String() string { return "Bool" } +// Dup implements StackItem interface. +func (i *BoolItem) Dup() StackItem { + return &BoolItem{i.value} +} + // ByteArrayItem represents a byte array on the stack. type ByteArrayItem struct { value []byte @@ -205,6 +224,13 @@ func (i *ByteArrayItem) String() string { return "ByteArray" } +// Dup implements StackItem interface. +func (i *ByteArrayItem) Dup() StackItem { + a := make([]byte, len(i.value)) + copy(a, i.value) + return &ByteArrayItem{a} +} + // ArrayItem represents a new ArrayItem object. type ArrayItem struct { value []StackItem @@ -231,6 +257,12 @@ func (i *ArrayItem) String() string { return "Array" } +// Dup implements StackItem interface. +func (i *ArrayItem) Dup() StackItem { + // reference type + return i +} + // MapItem represents Map object. type MapItem struct { value map[interface{}]StackItem @@ -259,6 +291,12 @@ func (i *MapItem) Has(key StackItem) (ok bool) { return } +// Dup implements StackItem interface. +func (i *MapItem) Dup() StackItem { + // reference type + return i +} + // Add adds key-value pair to the map. func (i *MapItem) Add(key, value StackItem) { i.value[toMapKey(key)] = value @@ -300,6 +338,12 @@ func (i *InteropItem) String() string { return "InteropItem" } +// Dup implements StackItem interface. +func (i *InteropItem) Dup() StackItem { + // reference type + return i +} + // MarshalJSON implements the json.Marshaler interface. func (i *InteropItem) MarshalJSON() ([]byte, error) { return json.Marshal(i.value) diff --git a/pkg/vm/vm_test.go b/pkg/vm/vm_test.go index 1bf8d1887..dc68d19e1 100644 --- a/pkg/vm/vm_test.go +++ b/pkg/vm/vm_test.go @@ -2519,6 +2519,42 @@ func TestXSWAPBad2(t *testing.T) { checkVMFailed(t, vm) } +func TestDupInt(t *testing.T) { + prog := makeProgram(opcode.DUP, opcode.ABS) + vm := load(prog) + vm.estack.PushVal(-1) + runVM(t, vm) + assert.Equal(t, 2, vm.estack.Len()) + assert.Equal(t, int64(1), vm.estack.Pop().BigInt().Int64()) + assert.Equal(t, int64(-1), vm.estack.Pop().BigInt().Int64()) +} + +func TestDupByteArray(t *testing.T) { + prog := makeProgram(opcode.PUSHBYTES2, 1, 0, + opcode.DUP, + opcode.PUSH1, + opcode.LEFT, + opcode.PUSHBYTES1, 2, + opcode.CAT) + vm := load(prog) + runVM(t, vm) + assert.Equal(t, 2, vm.estack.Len()) + assert.Equal(t, []byte{0x01, 0x02}, vm.estack.Pop().Bytes()) + assert.Equal(t, []byte{0x01, 0x00}, vm.estack.Pop().Bytes()) +} + +func TestDupBool(t *testing.T) { + prog := makeProgram(opcode.PUSH0, opcode.NOT, + opcode.DUP, + opcode.PUSH1, opcode.NOT, + opcode.BOOLAND) + vm := load(prog) + runVM(t, vm) + assert.Equal(t, 2, vm.estack.Len()) + assert.Equal(t, false, vm.estack.Pop().Bool()) + assert.Equal(t, true, vm.estack.Pop().Bool()) +} + func makeProgram(opcodes ...opcode.Opcode) []byte { prog := make([]byte, len(opcodes)+1) // RET for i := 0; i < len(opcodes); i++ { From a6d60e387a6c53d8b629a645e7c86847a53fe977 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Tue, 17 Dec 2019 16:56:58 +0300 Subject: [PATCH 3/5] vm: fix OVER and PICK to duplicate stack items TestPICKDup and TestOVERDup failed without this. --- pkg/vm/vm.go | 4 ++-- pkg/vm/vm_test.go | 31 +++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index 1d36341b2..825780737 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -622,7 +622,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro } case opcode.OVER: - a := v.estack.Peek(1) + a := v.estack.Dup(1) if a == nil { panic("no second element found") } @@ -633,7 +633,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro if n < 0 { panic("negative stack item returned") } - a := v.estack.Peek(n) + a := v.estack.Dup(n) if a == nil { panic("no nth element found") } diff --git a/pkg/vm/vm_test.go b/pkg/vm/vm_test.go index dc68d19e1..ef701a792 100644 --- a/pkg/vm/vm_test.go +++ b/pkg/vm/vm_test.go @@ -1497,6 +1497,21 @@ func TestPICKgood(t *testing.T) { assert.Equal(t, int64(result), vm.estack.Pop().BigInt().Int64()) } +func TestPICKDup(t *testing.T) { + prog := makeProgram(opcode.PUSHM1, opcode.PUSH0, + opcode.PUSH1, + opcode.PUSH2, + opcode.PICK, + opcode.ABS) + vm := load(prog) + runVM(t, vm) + assert.Equal(t, 4, vm.estack.Len()) + assert.Equal(t, int64(1), vm.estack.Pop().BigInt().Int64()) + assert.Equal(t, int64(1), vm.estack.Pop().BigInt().Int64()) + assert.Equal(t, int64(0), vm.estack.Pop().BigInt().Int64()) + assert.Equal(t, int64(-1), vm.estack.Pop().BigInt().Int64()) +} + func TestROTBad(t *testing.T) { prog := makeProgram(opcode.ROT) vm := load(prog) @@ -1663,6 +1678,22 @@ func TestOVERgood(t *testing.T) { assert.Equal(t, 3, vm.estack.Len()) } +func TestOVERDup(t *testing.T) { + prog := makeProgram(opcode.PUSHBYTES2, 1, 0, + opcode.PUSH1, + opcode.OVER, + opcode.PUSH1, + opcode.LEFT, + opcode.PUSHBYTES1, 2, + opcode.CAT) + vm := load(prog) + runVM(t, vm) + assert.Equal(t, 3, vm.estack.Len()) + assert.Equal(t, []byte{0x01, 0x02}, vm.estack.Pop().Bytes()) + assert.Equal(t, int64(1), vm.estack.Pop().BigInt().Int64()) + assert.Equal(t, []byte{0x01, 0x00}, vm.estack.Pop().Bytes()) +} + func TestNIPBadNoItem(t *testing.T) { prog := makeProgram(opcode.NIP) vm := load(prog) From 21efcb012bee088f7226e3d10dfbc70edf30aff0 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Tue, 17 Dec 2019 17:20:44 +0300 Subject: [PATCH 4/5] vm: fix non-dupped items in PICKITEM TestPICKITEMDupMap and TestPICKITEMDupArray were failing. --- pkg/vm/vm.go | 4 ++-- pkg/vm/vm_test.go | 24 ++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index 825780737..93f190c10 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -958,14 +958,14 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro if index < 0 || index >= len(arr) { panic("PICKITEM: invalid index") } - item := arr[index] + item := arr[index].Dup() v.estack.PushVal(item) case *MapItem: if !t.Has(key.value) { panic("invalid key") } k := toMapKey(key.value) - v.estack.Push(&Element{value: t.value[k]}) + v.estack.Push(&Element{value: t.value[k].Dup()}) default: arr := obj.Bytes() if index < 0 || index >= len(arr) { diff --git a/pkg/vm/vm_test.go b/pkg/vm/vm_test.go index ef701a792..b3fb9f81a 100644 --- a/pkg/vm/vm_test.go +++ b/pkg/vm/vm_test.go @@ -1092,6 +1092,30 @@ func TestPICKITEMByteArray(t *testing.T) { assert.Equal(t, makeStackItem(2), vm.estack.Pop().value) } +func TestPICKITEMDupArray(t *testing.T) { + prog := makeProgram(opcode.DUP, opcode.PUSH0, opcode.PICKITEM, opcode.ABS) + vm := load(prog) + vm.estack.PushVal([]StackItem{makeStackItem(-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) + assert.Equal(t, big.NewInt(-1), items[0].Value()) +} + +func TestPICKITEMDupMap(t *testing.T) { + prog := makeProgram(opcode.DUP, opcode.PUSHBYTES1, 42, opcode.PICKITEM, opcode.ABS) + vm := load(prog) + m := NewMapItem() + m.Add(makeStackItem([]byte{42}), makeStackItem(-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().(map[interface{}]StackItem) + assert.Equal(t, big.NewInt(-1), items[string([]byte{42})].Value()) +} + func TestPICKITEMMap(t *testing.T) { prog := makeProgram(opcode.PICKITEM) vm := load(prog) From 10766fe813f5d90ce3bf19d89d57658d0770ce67 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Tue, 17 Dec 2019 20:31:16 +0300 Subject: [PATCH 5/5] vm: add tests for hashing instructions These were cross-checked with the C# implementation. --- pkg/vm/vm_test.go | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/pkg/vm/vm_test.go b/pkg/vm/vm_test.go index b3fb9f81a..9d6d86452 100644 --- a/pkg/vm/vm_test.go +++ b/pkg/vm/vm_test.go @@ -2610,6 +2610,50 @@ func TestDupBool(t *testing.T) { assert.Equal(t, true, vm.estack.Pop().Bool()) } +func TestSHA1(t *testing.T) { + // 0x0100 hashes to 0e356ba505631fbf715758bed27d503f8b260e3a + res := "0e356ba505631fbf715758bed27d503f8b260e3a" + prog := makeProgram(opcode.PUSHBYTES2, 1, 0, + opcode.SHA1) + vm := load(prog) + runVM(t, vm) + assert.Equal(t, 1, vm.estack.Len()) + assert.Equal(t, res, hex.EncodeToString(vm.estack.Pop().Bytes())) +} + +func TestSHA256(t *testing.T) { + // 0x0100 hashes to 47dc540c94ceb704a23875c11273e16bb0b8a87aed84de911f2133568115f254 + res := "47dc540c94ceb704a23875c11273e16bb0b8a87aed84de911f2133568115f254" + prog := makeProgram(opcode.PUSHBYTES2, 1, 0, + opcode.SHA256) + vm := load(prog) + runVM(t, vm) + assert.Equal(t, 1, vm.estack.Len()) + assert.Equal(t, res, hex.EncodeToString(vm.estack.Pop().Bytes())) +} + +func TestHASH160(t *testing.T) { + // 0x0100 hashes to fbc22d517f38e7612798ece8e5957cf6c41d8caf + res := "fbc22d517f38e7612798ece8e5957cf6c41d8caf" + prog := makeProgram(opcode.PUSHBYTES2, 1, 0, + opcode.HASH160) + vm := load(prog) + runVM(t, vm) + assert.Equal(t, 1, vm.estack.Len()) + assert.Equal(t, res, hex.EncodeToString(vm.estack.Pop().Bytes())) +} + +func TestHASH256(t *testing.T) { + // 0x0100 hashes to 677b2d718464ee0121475600b929c0b4155667486577d1320b18c2dc7d4b4f99 + res := "677b2d718464ee0121475600b929c0b4155667486577d1320b18c2dc7d4b4f99" + prog := makeProgram(opcode.PUSHBYTES2, 1, 0, + opcode.HASH256) + vm := load(prog) + runVM(t, vm) + assert.Equal(t, 1, vm.estack.Len()) + assert.Equal(t, res, hex.EncodeToString(vm.estack.Pop().Bytes())) +} + func makeProgram(opcodes ...opcode.Opcode) []byte { prog := make([]byte, len(opcodes)+1) // RET for i := 0; i < len(opcodes); i++ {