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++ {