From e29b85d0d760e09b3a653bba55285d049cfc4673 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Wed, 27 Feb 2019 20:50:00 +0000 Subject: [PATCH 01/81] VM:Add abstract stack item --- pkg/vm/stack/stackitem.go | 42 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 pkg/vm/stack/stackitem.go diff --git a/pkg/vm/stack/stackitem.go b/pkg/vm/stack/stackitem.go new file mode 100644 index 000000000..9f29f9d3b --- /dev/null +++ b/pkg/vm/stack/stackitem.go @@ -0,0 +1,42 @@ +package stack + +import ( + "errors" +) + +//Item is an interface which represents object that can be placed on the stack +type Item interface { + Integer() (*Int, error) + Boolean() (*Boolean, error) + ByteArray() (*ByteArray, error) + Array() (*Array, error) +} + +// Represents an `abstract` stack item +// which will hold default values for stack items +// this is intended to be embedded into types that you will use on the stack +type abstractItem struct{} + +// Integer is the default implementation for a stackItem +// Implements Item interface +func (a *abstractItem) Integer() (*Int, error) { + return nil, errors.New("This stack item is not an Integer") +} + +// Boolean is the default implementation for a stackItem +// Implements Item interface +func (a *abstractItem) Boolean() (*Boolean, error) { + return nil, errors.New("This stack item is not a Boolean") +} + +// ByteArray is the default implementation for a stackItem +// Implements Item interface +func (a *abstractItem) ByteArray() (*ByteArray, error) { + return nil, errors.New("This stack item is not a byte array") +} + +// Array is the default implementation for a stackItem +// Implements Item interface +func (a *abstractItem) Array() (*Array, error) { + return nil, errors.New("This stack item is not an array") +} From d8d27761aed3adf71d30635eb18c4ef07525ae27 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Wed, 27 Feb 2019 20:55:48 +0000 Subject: [PATCH 02/81] VM: Add stackItems; Array, Boolean, Int and ByteArray --- pkg/vm/stack/Int.go | 39 +++++++++++++++++++++++++++++++++++++++ pkg/vm/stack/array.go | 13 +++++++++++++ pkg/vm/stack/boolean.go | 13 +++++++++++++ pkg/vm/stack/bytearray.go | 12 ++++++++++++ 4 files changed, 77 insertions(+) create mode 100644 pkg/vm/stack/Int.go create mode 100644 pkg/vm/stack/array.go create mode 100644 pkg/vm/stack/boolean.go create mode 100644 pkg/vm/stack/bytearray.go diff --git a/pkg/vm/stack/Int.go b/pkg/vm/stack/Int.go new file mode 100644 index 000000000..6ba864b90 --- /dev/null +++ b/pkg/vm/stack/Int.go @@ -0,0 +1,39 @@ +package stack + +import "math/big" + +// Int represents an integer on the stack +type Int struct { + *abstractItem + val *big.Int +} + +// NewInt will convert a big integer into +// a StackInteger +func NewInt(val *big.Int) (*Int, error) { + return &Int{ + abstractItem: &abstractItem{}, + val: val, + }, nil +} + +// Equal will check if two integers hold equal value +func (i *Int) Equal(s *Int) bool { + if i.val.Cmp(s.val) != 0 { + return false + } + return true +} + +// Add will add two stackIntegers together +func (i *Int) Add(s *Int) (*Int, error) { + return &Int{ + val: new(big.Int).Sub(i.val, s.val), + }, nil +} + +// Integer will overwrite the default implementation +// to allow go to cast this item as an integer. +func (i *Int) Integer() (*Int, error) { + return i, nil +} diff --git a/pkg/vm/stack/array.go b/pkg/vm/stack/array.go new file mode 100644 index 000000000..96fe876a4 --- /dev/null +++ b/pkg/vm/stack/array.go @@ -0,0 +1,13 @@ +package stack + +// Array represents an Array of stackItems on the stack +type Array struct { + *abstractItem + val []Item +} + +// Array overrides the default implementation +// by the abstractItem, returning an Array struct +func (a *Array) Array() (*Array, error) { + return a, nil +} diff --git a/pkg/vm/stack/boolean.go b/pkg/vm/stack/boolean.go new file mode 100644 index 000000000..d6a2c12ac --- /dev/null +++ b/pkg/vm/stack/boolean.go @@ -0,0 +1,13 @@ +package stack + +// Boolean represents a boolean value on the stack +type Boolean struct { + *abstractItem + val bool +} + +// Boolean overrides the default implementation +// by the abstractItem, returning a Boolean struct +func (b *Boolean) Boolean() (*Boolean, error) { + return b, nil +} diff --git a/pkg/vm/stack/bytearray.go b/pkg/vm/stack/bytearray.go new file mode 100644 index 000000000..23b7fc805 --- /dev/null +++ b/pkg/vm/stack/bytearray.go @@ -0,0 +1,12 @@ +package stack + +// ByteArray represents a slice of bytes on the stack +type ByteArray struct { + *abstractItem + val []byte +} + +//ByteArray overrides the default abstractItem Bytes array method +func (ba *ByteArray) ByteArray() (*ByteArray, error) { + return ba, nil +} From b79602cc5d113552131b3adbed142b48bfab0bd7 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Wed, 27 Feb 2019 20:56:19 +0000 Subject: [PATCH 03/81] VM: Add tests for stack item --- pkg/vm/stack/stackitem_test.go | 68 ++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 pkg/vm/stack/stackitem_test.go diff --git a/pkg/vm/stack/stackitem_test.go b/pkg/vm/stack/stackitem_test.go new file mode 100644 index 000000000..741d6f530 --- /dev/null +++ b/pkg/vm/stack/stackitem_test.go @@ -0,0 +1,68 @@ +package stack + +import ( + "math/big" + "testing" + + "github.com/stretchr/testify/assert" +) + +// A simple test to ensure that by embedding the abstract interface +// we immediately become a stack item, with the default values set to nil +func TestInterfaceEmbedding(t *testing.T) { + + // Create an anonymous struct that embeds the abstractItem + a := struct { + *abstractItem + }{ + &abstractItem{}, + } + + // Since interface checking can be done at compile time. + // If he abstractItem did not implement all methods of our interface `Item` + // Then any struct which embeds it, will also not implement the Item interface. + // This test would then give errors, at compile time. + var Items []Item + Items = append(Items, a) + + // Default methods should give errors + // Here we just need to test against one of the methods in the interface + for _, element := range Items { + x, err := element.Integer() + assert.Nil(t, x) + assert.NotNil(t, err, nil) + } + +} + +// TestIntCasting is a simple test to test that the Integer method is overwritten +// from the abstractItem +func TestIntMethodOverride(t *testing.T) { + + testValues := []int64{0, 10, 200, 30, 90} + var Items []Item + + // Convert a range of int64s into Stack Integers + // Adding them into an array of StackItems + for _, num := range testValues { + stackInteger, err := NewInt(big.NewInt(num)) + if err != nil { + t.Fail() + } + Items = append(Items, stackInteger) + } + + // For each item, call the Integer method on the interface + // Which should return an integer and no error + // as the stack integer struct overrides that method + for i, element := range Items { + k, err := element.Integer() + if err != nil { + t.Fail() + } + if k.val.Cmp(big.NewInt(testValues[i])) != 0 { + t.Fail() + } + } + +} From c163ae201937f12a3415212cc812d9cf62f669ce Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Wed, 27 Feb 2019 20:58:17 +0000 Subject: [PATCH 04/81] VM: first pass at Random Access Stack object --- pkg/vm/stack/stack.go | 123 ++++++++++++++++++++++++ pkg/vm/stack/stack_test.go | 188 +++++++++++++++++++++++++++++++++++++ 2 files changed, 311 insertions(+) create mode 100644 pkg/vm/stack/stack.go create mode 100644 pkg/vm/stack/stack_test.go diff --git a/pkg/vm/stack/stack.go b/pkg/vm/stack/stack.go new file mode 100644 index 000000000..0136f3f03 --- /dev/null +++ b/pkg/vm/stack/stack.go @@ -0,0 +1,123 @@ +package stack + +import ( + "errors" + "fmt" +) + +const ( + // StackAverageSize is used to set the capacity of the stack + // setting this number too low, will cause extra allocations + StackAverageSize = 20 +) + +// RandomAccess represents a Random Access Stack +type RandomAccess struct { + vals []Item +} + +// New will return a new random access stack +func New() *RandomAccess { + return &RandomAccess{ + vals: make([]Item, 0, StackAverageSize), + } +} + +// Items will return all items in the stack +func (ras *RandomAccess) items() []Item { + return ras.vals +} + +//Len will return the length of the stack +func (ras *RandomAccess) Len() int { + if ras.vals == nil { + return -1 + } + return len(ras.vals) +} + +// Clear will remove all items in the stack +func (ras *RandomAccess) Clear() { + ras.vals = make([]Item, 0, StackAverageSize) +} + +// Pop will remove the last stack item that was added +func (ras *RandomAccess) Pop() (Item, error) { + if len(ras.vals) == 0 { + return nil, errors.New("There are no items on the stack to pop") + } + if ras.vals == nil { + return nil, errors.New("Cannot pop from a nil stack") + } + + l := len(ras.vals) + item := ras.vals[l-1] + ras.vals = ras.vals[:l-1] + + return item, nil +} + +// Push will put a stack item onto the top of the stack +func (ras *RandomAccess) Push(item Item) *RandomAccess { + if ras.vals == nil { + ras.vals = make([]Item, 0, StackAverageSize) + } + + ras.vals = append(ras.vals, item) + + return ras +} + +// Insert will push a stackItem onto the stack at position `n` +// Note; index 0 is the top of the stack, which is the end of slice +// REDO: +func (ras *RandomAccess) Insert(n uint16, item Item) (*RandomAccess, error) { + + if n == 0 { + return ras.Push(item), nil + } + + if ras.vals == nil { + ras.vals = make([]Item, 0, StackAverageSize) + } + + // Check that we are not inserting out of the bounds + stackSize := uint16(len(ras.vals)) + if n > stackSize-1 { + return nil, fmt.Errorf("Tried to insert at index %d when length of stack is %d", n, len(ras.vals)) + } + + index := stackSize - n + + ras.vals = append(ras.vals, item) + copy(ras.vals[index:], ras.vals[index-1:]) + ras.vals[index] = item + + return ras, nil +} + +// Peek will check an element at a given index +// Note: 0 is the top of the stack, which is the end of the slice +func (ras *RandomAccess) Peek(n uint16) (Item, error) { + + stackSize := uint16(len(ras.vals)) + + if n == 0 { + index := stackSize - 1 + return ras.vals[index], nil + } + + if ras.vals == nil { + return nil, errors.New("Cannot peak at a nil stack") + } + + // Check that we are not peeking out of the bounds + + if n > stackSize-1 { + return nil, fmt.Errorf("Tried to peek at index %d when length of stack is %d", n, len(ras.vals)) + } + + index := stackSize - n - 1 + + return ras.vals[index], nil +} diff --git a/pkg/vm/stack/stack_test.go b/pkg/vm/stack/stack_test.go new file mode 100644 index 000000000..05a192bcc --- /dev/null +++ b/pkg/vm/stack/stack_test.go @@ -0,0 +1,188 @@ +package stack + +import ( + "fmt" + "math/big" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestStackPushPop(t *testing.T) { + // Create two stack Integers + a, err := NewInt(big.NewInt(10)) + if err != nil { + t.Fail() + } + b, err := NewInt(big.NewInt(20)) + if err != nil { + t.Fail() + } + + // Create a new stack + testStack := New() + + // Push to stack + testStack.Push(a).Push(b) + + // There should only be two values on the stack + assert.Equal(t, 2, testStack.Len()) + + // Pop first element and it should be equal to b + stackElement, err := testStack.Pop() + if err != nil { + t.Fail() + } + item, err := stackElement.Integer() + if err != nil { + t.Fail() + } + assert.Equal(t, true, item.Equal(b)) + + // Pop second element and it should be equal to a + stackElement, err = testStack.Pop() + if err != nil { + t.Fail() + } + item, err = stackElement.Integer() + if err != nil { + t.Fail() + } + assert.Equal(t, true, item.Equal(a)) + + // We should get an error as there are nomore items left to pop + stackElement, err = testStack.Pop() + assert.NotNil(t, err) + +} + +// For this test to pass, we should get an error when popping from a nil stack +// and we should initialise and push an element if pushing to an empty stack +func TestPushPopNil(t *testing.T) { + + // stack is nil when initialised without New constructor + testStack := RandomAccess{} + + // Popping from nil stack + // - should give an error + // - element returned should be nil + stackElement, err := testStack.Pop() + assert.NotNil(t, err) + assert.Nil(t, stackElement) + + // stack should still be nil after failing to pop + assert.Nil(t, testStack.vals) + + // create a random test stack item + a, err := NewInt(big.NewInt(2)) + assert.Nil(t, err) + + // push random item to stack + testStack.Push(a) + + // push should initialise the stack and put one element on the stack + assert.Equal(t, 1, testStack.Len()) +} + +// Test passes if we can peek and modify an item +//without modifying the value on the stack +func TestStackPeekMutability(t *testing.T) { + + testStack := New() + + a, err := NewInt(big.NewInt(2)) + assert.Nil(t, err) + b, err := NewInt(big.NewInt(3)) + assert.Nil(t, err) + + testStack.Push(a).Push(b) + + peekedItem := testPeakInteger(t, testStack, 0) + assert.Equal(t, true, peekedItem.Equal(b)) + + // Check that by modifying the peeked value, + // we did not modify the item on the stack + peekedItem = a + peekedItem.val = big.NewInt(0) + + // Pop item from stack and check it is still the same + poppedItem := testPopInteger(t, testStack) + assert.Equal(t, true, poppedItem.Equal(b)) +} +func TestStackPeek(t *testing.T) { + + testStack := New() + + values := []int64{23, 45, 67, 89, 12, 344} + for _, val := range values { + a := testMakeStackInt(t, val) + testStack.Push(a) + } + + // i starts at 0, j starts at len(values)-1 + for i, j := 0, len(values)-1; j >= 0; i, j = i+1, j-1 { + + peekedItem := testPeakInteger(t, testStack, uint16(i)) + a := testMakeStackInt(t, values[j]) + + fmt.Printf("%#v\n", peekedItem.val.Int64()) + + assert.Equal(t, true, a.Equal(peekedItem)) + + } + +} + +func TestStackInsert(t *testing.T) { + + testStack := New() + + a := testMakeStackInt(t, 2) + b := testMakeStackInt(t, 4) + c := testMakeStackInt(t, 6) + + // insert on an empty stack should put element on top + _, err := testStack.Insert(0, a) + assert.Equal(t, err, nil) + _, err = testStack.Insert(0, b) + assert.Equal(t, err, nil) + _, err = testStack.Insert(1, c) + assert.Equal(t, err, nil) + + // Order should be [a,c,b] + pop1 := testPopInteger(t, testStack) + pop2 := testPopInteger(t, testStack) + pop3 := testPopInteger(t, testStack) + + assert.Equal(t, true, pop1.Equal(b)) + assert.Equal(t, true, pop2.Equal(c)) + assert.Equal(t, true, pop3.Equal(a)) + +} + +// helper functions +func testPeakInteger(t *testing.T, tStack *RandomAccess, n uint16) *Int { + stackElement, err := tStack.Peek(n) + assert.Nil(t, err) + item, err := stackElement.Integer() + if err != nil { + t.Fail() + } + return item +} + +func testPopInteger(t *testing.T, tStack *RandomAccess) *Int { + stackElement, err := tStack.Pop() + assert.Nil(t, err) + item, err := stackElement.Integer() + if err != nil { + t.Fail() + } + return item +} + +func testMakeStackInt(t *testing.T, num int64) *Int { + a, err := NewInt(big.NewInt(num)) + assert.Nil(t, err) + return a +} From 64491a4d83ab99f81ee10c708723e47fb3c54838 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Wed, 27 Feb 2019 21:38:11 +0000 Subject: [PATCH 05/81] VM: Add Sub, Mul, Mod LSH, RSH --- pkg/vm/stack/Int.go | 35 ++++++++++++++++++++++++ pkg/vm/stack/int_test.go | 57 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 pkg/vm/stack/int_test.go diff --git a/pkg/vm/stack/Int.go b/pkg/vm/stack/Int.go index 6ba864b90..ae81a2120 100644 --- a/pkg/vm/stack/Int.go +++ b/pkg/vm/stack/Int.go @@ -27,11 +27,46 @@ func (i *Int) Equal(s *Int) bool { // Add will add two stackIntegers together func (i *Int) Add(s *Int) (*Int, error) { + return &Int{ + val: new(big.Int).Add(i.val, s.val), + }, nil +} + +// Sub will subtract two stackIntegers together +func (i *Int) Sub(s *Int) (*Int, error) { return &Int{ val: new(big.Int).Sub(i.val, s.val), }, nil } +// Mul will multiply two stackIntegers together +func (i *Int) Mul(s *Int) (*Int, error) { + return &Int{ + val: new(big.Int).Mul(i.val, s.val), + }, nil +} + +// Mod will take the mod of two stackIntegers together +func (i *Int) Mod(s *Int) (*Int, error) { + return &Int{ + val: new(big.Int).Mod(i.val, s.val), + }, nil +} + +// Rsh will shift the integer b to the right by `n` bits +func (i *Int) Rsh(n *Int) (*Int, error) { + return &Int{ + val: new(big.Int).Rsh(i.val, uint(n.val.Int64())), + }, nil +} + +// Lsh will shift the integer b to the left by `n` bits +func (i *Int) Lsh(n *Int) (*Int, error) { + return &Int{ + val: new(big.Int).Lsh(i.val, uint(n.val.Int64())), + }, nil +} + // Integer will overwrite the default implementation // to allow go to cast this item as an integer. func (i *Int) Integer() (*Int, error) { diff --git a/pkg/vm/stack/int_test.go b/pkg/vm/stack/int_test.go new file mode 100644 index 000000000..40ef3c8b2 --- /dev/null +++ b/pkg/vm/stack/int_test.go @@ -0,0 +1,57 @@ +package stack + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestAdd(t *testing.T) { + a := testMakeStackInt(t, 10) + b := testMakeStackInt(t, 20) + expected := testMakeStackInt(t, 30) + c, err := a.Add(b) + assert.Nil(t, err) + assert.Equal(t, true, expected.Equal(c)) +} +func TestSub(t *testing.T) { + a := testMakeStackInt(t, 30) + b := testMakeStackInt(t, 200) + expected := testMakeStackInt(t, 170) + c, err := b.Sub(a) + assert.Nil(t, err) + assert.Equal(t, true, expected.Equal(c)) +} +func TestMul(t *testing.T) { + a := testMakeStackInt(t, 10) + b := testMakeStackInt(t, 20) + expected := testMakeStackInt(t, 200) + c, err := a.Mul(b) + assert.Nil(t, err) + assert.Equal(t, true, expected.Equal(c)) +} +func TestMod(t *testing.T) { + a := testMakeStackInt(t, 10) + b := testMakeStackInt(t, 20) + expected := testMakeStackInt(t, 10) + c, err := a.Mod(b) + assert.Nil(t, err) + assert.Equal(t, true, expected.Equal(c)) +} +func TestLsh(t *testing.T) { + a := testMakeStackInt(t, 23) + b := testMakeStackInt(t, 8) + expected := testMakeStackInt(t, 5888) + c, err := a.Lsh(b) + assert.Nil(t, err) + assert.Equal(t, true, expected.Equal(c)) +} + +func TestRsh(t *testing.T) { + a := testMakeStackInt(t, 128) + b := testMakeStackInt(t, 3) + expected := testMakeStackInt(t, 16) + c, err := a.Rsh(b) + assert.Nil(t, err) + assert.Equal(t, true, expected.Equal(c)) +} From 263bc4c1b1ddbff84ac2382ca1366dc3b7f2ab88 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Wed, 27 Feb 2019 21:38:39 +0000 Subject: [PATCH 06/81] VM: moved test helper functions into separate file --- pkg/vm/stack/testhelper.go | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 pkg/vm/stack/testhelper.go diff --git a/pkg/vm/stack/testhelper.go b/pkg/vm/stack/testhelper.go new file mode 100644 index 000000000..443a522ce --- /dev/null +++ b/pkg/vm/stack/testhelper.go @@ -0,0 +1,35 @@ +package stack + +import ( + "math/big" + "testing" + + "github.com/stretchr/testify/assert" +) + +// helper functions +func testPeakInteger(t *testing.T, tStack *RandomAccess, n uint16) *Int { + stackElement, err := tStack.Peek(n) + assert.Nil(t, err) + item, err := stackElement.Integer() + if err != nil { + t.Fail() + } + return item +} + +func testPopInteger(t *testing.T, tStack *RandomAccess) *Int { + stackElement, err := tStack.Pop() + assert.Nil(t, err) + item, err := stackElement.Integer() + if err != nil { + t.Fail() + } + return item +} + +func testMakeStackInt(t *testing.T, num int64) *Int { + a, err := NewInt(big.NewInt(num)) + assert.Nil(t, err) + return a +} From 8d55ea12f00c6d3d1cea2b02e353754d61e1b2db Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Wed, 27 Feb 2019 21:40:31 +0000 Subject: [PATCH 07/81] VM: removed helper functions from stack_test.go --- pkg/vm/stack/stack_test.go | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/pkg/vm/stack/stack_test.go b/pkg/vm/stack/stack_test.go index 05a192bcc..246e983f4 100644 --- a/pkg/vm/stack/stack_test.go +++ b/pkg/vm/stack/stack_test.go @@ -159,30 +159,3 @@ func TestStackInsert(t *testing.T) { assert.Equal(t, true, pop3.Equal(a)) } - -// helper functions -func testPeakInteger(t *testing.T, tStack *RandomAccess, n uint16) *Int { - stackElement, err := tStack.Peek(n) - assert.Nil(t, err) - item, err := stackElement.Integer() - if err != nil { - t.Fail() - } - return item -} - -func testPopInteger(t *testing.T, tStack *RandomAccess) *Int { - stackElement, err := tStack.Pop() - assert.Nil(t, err) - item, err := stackElement.Integer() - if err != nil { - t.Fail() - } - return item -} - -func testMakeStackInt(t *testing.T, num int64) *Int { - a, err := NewInt(big.NewInt(num)) - assert.Nil(t, err) - return a -} From da0a56f9222f06198a867c14247c6bbff00baa33 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Wed, 27 Feb 2019 22:41:46 +0000 Subject: [PATCH 08/81] Add conversions for bytearray and Int stack items --- pkg/vm/stack/Int.go | 21 +++++++++++++++++ pkg/vm/stack/bytearray.go | 47 +++++++++++++++++++++++++++++++++++++++ pkg/vm/stack/stack.go | 2 -- 3 files changed, 68 insertions(+), 2 deletions(-) diff --git a/pkg/vm/stack/Int.go b/pkg/vm/stack/Int.go index ae81a2120..4f1d070a5 100644 --- a/pkg/vm/stack/Int.go +++ b/pkg/vm/stack/Int.go @@ -72,3 +72,24 @@ func (i *Int) Lsh(n *Int) (*Int, error) { func (i *Int) Integer() (*Int, error) { return i, nil } + +// ByteArray override the default ByteArray method +// to convert a Integer into a byte Array +func (i *Int) ByteArray() (*ByteArray, error) { + return &ByteArray{ + i.abstractItem, + i.val.Bytes(), + }, nil +} + +//Boolean override the default Boolean method +// to convert an Integer into a Boolean StackItem +func (i *Int) Boolean() (*Boolean, error) { + + boolean := (i.val.Int64() != 0) + return &Boolean{ + i.abstractItem, + boolean, + }, nil + +} diff --git a/pkg/vm/stack/bytearray.go b/pkg/vm/stack/bytearray.go index 23b7fc805..4ff318e2b 100644 --- a/pkg/vm/stack/bytearray.go +++ b/pkg/vm/stack/bytearray.go @@ -1,12 +1,59 @@ package stack +import ( + "errors" + "math/big" + "strconv" +) + // ByteArray represents a slice of bytes on the stack type ByteArray struct { *abstractItem val []byte } +//NewByteArray returns a ByteArray stack item +// given a byte slice +func NewByteArray(val []byte) *ByteArray { + return &ByteArray{ + &abstractItem{}, + val, + } +} + //ByteArray overrides the default abstractItem Bytes array method func (ba *ByteArray) ByteArray() (*ByteArray, error) { return ba, nil } + +//Integer overrides the default Integer method to convert an +// ByteArray Into an integer +func (ba *ByteArray) Integer() (*Int, error) { + + dest := make([]byte, 0) + + for i, j := 0, len(ba.val)-1; i < j+1; i, j = i+1, j-1 { + dest[i], dest[j] = ba.val[j], ba.val[i] + } + + integerVal := new(big.Int).SetBytes(dest) + + return &Int{ + ba.abstractItem, + integerVal, + }, nil + + // return ba, nil +} + +// Boolean will convert +func (ba *ByteArray) Boolean() (*Boolean, error) { + boolean, err := strconv.ParseBool(string(ba.val)) + if err != nil { + return nil, errors.New("cannot convert byte array to a boolean") + } + return &Boolean{ + ba.abstractItem, + boolean, + }, nil +} diff --git a/pkg/vm/stack/stack.go b/pkg/vm/stack/stack.go index 0136f3f03..3c01ad4e7 100644 --- a/pkg/vm/stack/stack.go +++ b/pkg/vm/stack/stack.go @@ -70,7 +70,6 @@ func (ras *RandomAccess) Push(item Item) *RandomAccess { // Insert will push a stackItem onto the stack at position `n` // Note; index 0 is the top of the stack, which is the end of slice -// REDO: func (ras *RandomAccess) Insert(n uint16, item Item) (*RandomAccess, error) { if n == 0 { @@ -112,7 +111,6 @@ func (ras *RandomAccess) Peek(n uint16) (Item, error) { } // Check that we are not peeking out of the bounds - if n > stackSize-1 { return nil, fmt.Errorf("Tried to peek at index %d when length of stack is %d", n, len(ras.vals)) } From 5789aba4b29783afc7236249b2135f6d707d5d23 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Wed, 27 Feb 2019 22:52:58 +0000 Subject: [PATCH 09/81] Add instructions file for vm --- pkg/vm/instructions.go | 133 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 pkg/vm/instructions.go diff --git a/pkg/vm/instructions.go b/pkg/vm/instructions.go new file mode 100644 index 000000000..6a78f6a6a --- /dev/null +++ b/pkg/vm/instructions.go @@ -0,0 +1,133 @@ +package vm + +// Instruction represents a operation code in the neovm +type Instruction byte + +// Viable list of supported instruction constants. +const ( + // Constants + PUSH0 Instruction = 0x00 + PUSHF Instruction = PUSH0 + PUSHBYTES1 Instruction = 0x01 + PUSHBYTES75 Instruction = 0x4B + PUSHDATA1 Instruction = 0x4C + PUSHDATA2 Instruction = 0x4D + PUSHDATA4 Instruction = 0x4E + PUSHM1 Instruction = 0x4F + PUSH1 Instruction = 0x51 + PUSHT Instruction = PUSH1 + PUSH2 Instruction = 0x52 + PUSH3 Instruction = 0x53 + PUSH4 Instruction = 0x54 + PUSH5 Instruction = 0x55 + PUSH6 Instruction = 0x56 + PUSH7 Instruction = 0x57 + PUSH8 Instruction = 0x58 + PUSH9 Instruction = 0x59 + PUSH10 Instruction = 0x5A + PUSH11 Instruction = 0x5B + PUSH12 Instruction = 0x5C + PUSH13 Instruction = 0x5D + PUSH14 Instruction = 0x5E + PUSH15 Instruction = 0x5F + PUSH16 Instruction = 0x60 + + // Flow control + NOP Instruction = 0x61 + JMP Instruction = 0x62 + JMPIF Instruction = 0x63 + JMPIFNOT Instruction = 0x64 + CALL Instruction = 0x65 + RET Instruction = 0x66 + APPCALL Instruction = 0x67 + SYSCALL Instruction = 0x68 + TAILCALL Instruction = 0x69 + + // Stack + DUPFROMALTSTACK Instruction = 0x6A + TOALTSTACK Instruction = 0x6B + FROMALTSTACK Instruction = 0x6C + XDROP Instruction = 0x6D + XSWAP Instruction = 0x72 + XTUCK Instruction = 0x73 + DEPTH Instruction = 0x74 + DROP Instruction = 0x75 + DUP Instruction = 0x76 + NIP Instruction = 0x77 + OVER Instruction = 0x78 + PICK Instruction = 0x79 + ROLL Instruction = 0x7A + ROT Instruction = 0x7B + SWAP Instruction = 0x7C + TUCK Instruction = 0x7D + + // Splice + CAT Instruction = 0x7E + SUBSTR Instruction = 0x7F + LEFT Instruction = 0x80 + RIGHT Instruction = 0x81 + SIZE Instruction = 0x82 + + // Bitwise logic + INVERT Instruction = 0x83 + AND Instruction = 0x84 + OR Instruction = 0x85 + XOR Instruction = 0x86 + EQUAL Instruction = 0x87 + + // Arithmetic + INC Instruction = 0x8B + DEC Instruction = 0x8C + SIGN Instruction = 0x8D + NEGATE Instruction = 0x8F + ABS Instruction = 0x90 + NOT Instruction = 0x91 + NZ Instruction = 0x92 + ADD Instruction = 0x93 + SUB Instruction = 0x94 + MUL Instruction = 0x95 + DIV Instruction = 0x96 + MOD Instruction = 0x97 + SHL Instruction = 0x98 + SHR Instruction = 0x99 + BOOLAND Instruction = 0x9A + BOOLOR Instruction = 0x9B + NUMEQUAL Instruction = 0x9C + NUMNOTEQUAL Instruction = 0x9E + LT Instruction = 0x9F + GT Instruction = 0xA0 + LTE Instruction = 0xA1 + GTE Instruction = 0xA2 + MIN Instruction = 0xA3 + MAX Instruction = 0xA4 + WITHIN Instruction = 0xA5 + + // Crypto + SHA1 Instruction = 0xA7 + SHA256 Instruction = 0xA8 + HASH160 Instruction = 0xA9 + HASH256 Instruction = 0xAA + CHECKSIG Instruction = 0xAC + CHECKMULTISIG Instruction = 0xAE + + // Array + ARRAYSIZE Instruction = 0xC0 + PACK Instruction = 0xC1 + UNPACK Instruction = 0xC2 + PICKITEM Instruction = 0xC3 + SETITEM Instruction = 0xC4 + NEWARRAY Instruction = 0xC5 + NEWSTRUCT Instruction = 0xC6 + APPEND Instruction = 0xC8 + REVERSE Instruction = 0xC9 + REMOVE Instruction = 0xCA + + // Exceptions + THROW Instruction = 0xF0 + THROWIFNOT Instruction = 0xF1 +) + +// Value returns the byte-value of the opcode. +func (i Instruction) Value() byte { + return byte(i) +} From f60d65f1a428b8bb98b05960f228bcc387377dee Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Thu, 28 Feb 2019 13:51:02 +0000 Subject: [PATCH 10/81] - Add guide to stack readme - Add testReadInt64 --- pkg/vm/stack/Readme.md | 24 ++++++++++++++++++++++++ pkg/vm/stack/bytearray.go | 2 -- pkg/vm/stack/testhelper.go | 9 +++++++++ 3 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 pkg/vm/stack/Readme.md diff --git a/pkg/vm/stack/Readme.md b/pkg/vm/stack/Readme.md new file mode 100644 index 000000000..2e1b6ba78 --- /dev/null +++ b/pkg/vm/stack/Readme.md @@ -0,0 +1,24 @@ +## VM - Stack + +- How do i implement a new StackItem? + +Answer: You add it's type to the Item interface, then you implement the default return method on the abstract stack item, this should be the behaviour of the stack item, if it is not the new type. Then you embed the abstract item in the new struct and override the method. + +For example, If I wanted to add a new type called `HashMap` + +type Item interface{ + HashMap()(*HashMap, error) +} + +func (a *abstractItem) HashMap() (*HashMap, error) { + return nil, errors.New(This stack item is not a hashmap) +} + +type HashMap struct { + *abstractItem + // Variables needed for hashmap +} + +func (h *HashMap) HashMap()(*HashMap, error) { + // logic to override default behaviour +} diff --git a/pkg/vm/stack/bytearray.go b/pkg/vm/stack/bytearray.go index 4ff318e2b..7d57b12f0 100644 --- a/pkg/vm/stack/bytearray.go +++ b/pkg/vm/stack/bytearray.go @@ -42,8 +42,6 @@ func (ba *ByteArray) Integer() (*Int, error) { ba.abstractItem, integerVal, }, nil - - // return ba, nil } // Boolean will convert diff --git a/pkg/vm/stack/testhelper.go b/pkg/vm/stack/testhelper.go index 443a522ce..15c6f87de 100644 --- a/pkg/vm/stack/testhelper.go +++ b/pkg/vm/stack/testhelper.go @@ -1,6 +1,8 @@ package stack import ( + "bytes" + "encoding/binary" "math/big" "testing" @@ -33,3 +35,10 @@ func testMakeStackInt(t *testing.T, num int64) *Int { assert.Nil(t, err) return a } + +func testReadInt64(data []byte) int64 { + var ret int64 + buf := bytes.NewBuffer(data) + binary.Read(buf, binary.LittleEndian, &ret) + return ret +} From e2ef6bd2f4d7adc4127b4f972b12537dbefd6034 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Fri, 15 Mar 2019 22:27:34 +0000 Subject: [PATCH 11/81] Add Builder --- pkg/vm/stack/builder.go | 177 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 pkg/vm/stack/builder.go diff --git a/pkg/vm/stack/builder.go b/pkg/vm/stack/builder.go new file mode 100644 index 000000000..e50587aff --- /dev/null +++ b/pkg/vm/stack/builder.go @@ -0,0 +1,177 @@ +package stack + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "math/big" + + "github.com/CityOfZion/neo-go/pkg/wire/util" +) + +// Builder follows the builder pattern and will be used to build scripts +type Builder struct { + w *bytes.Buffer + err error +} + +// NewBuilder returns a new builder object +func NewBuilder() *Builder { + return &Builder{ + w: &bytes.Buffer{}, + err: nil, + } +} + +// Bytes returns the byte representation of the built buffer +func (br *Builder) Bytes() []byte { + return br.w.Bytes() +} + +// Emit a VM Opcode with data to the given buffer. +func (br *Builder) Emit(op Instruction, b []byte) *Builder { + if br.err != nil { + return br + } + br.err = br.w.WriteByte(byte(op)) + _, br.err = br.w.Write(b) + return br +} + +// EmitOpcode emits a single VM Opcode the given buffer. +func (br *Builder) EmitOpcode(op Instruction) *Builder { + if br.err != nil { + return br + } + br.err = br.w.WriteByte(byte(op)) + return br +} + +// EmitBool emits a bool type the given buffer. +func (br *Builder) EmitBool(ok bool) *Builder { + if br.err != nil { + return br + } + op := PUSHT + if !ok { + op = PUSHF + } + return br.EmitOpcode(op) +} + +// EmitInt emits a int type to the given buffer. +func (br *Builder) EmitInt(i int64) *Builder { + if br.err != nil { + return br + } + if i == -1 { + return br.EmitOpcode(PUSHM1) + } + if i == 0 { + return br.EmitOpcode(PUSHF) + } + if i > 0 && i < 16 { + val := Instruction(int(PUSH1) - 1 + int(i)) + return br.EmitOpcode(val) + } + + bInt := big.NewInt(i) + val := reverse(bInt.Bytes()) + return br.EmitBytes(val) +} + +// EmitString emits a string to the given buffer. +func (br *Builder) EmitString(s string) *Builder { + if br.err != nil { + return br + } + return br.EmitBytes([]byte(s)) +} + +// EmitBytes emits a byte array to the given buffer. +func (br *Builder) EmitBytes(b []byte) *Builder { + if br.err != nil { + return br + } + var ( + n = len(b) + ) + + if n <= int(PUSHBYTES75) { + return br.Emit(Instruction(n), b) + } else if n < 0x100 { + br.Emit(PUSHDATA1, []byte{byte(n)}) + } else if n < 0x10000 { + buf := make([]byte, 2) + binary.LittleEndian.PutUint16(buf, uint16(n)) + br.Emit(PUSHDATA2, buf) + } else { + buf := make([]byte, 4) + binary.LittleEndian.PutUint32(buf, uint32(n)) + br.Emit(PUSHDATA4, buf) + } + _, br.err = br.w.Write(b) + return br +} + +// EmitSyscall emits the syscall API to the given buffer. +// Syscall API string cannot be 0. +func (br *Builder) EmitSyscall(api string) *Builder { + if br.err != nil { + return br + } + if len(api) == 0 { + br.err = errors.New("syscall api cannot be of length 0") + } + buf := make([]byte, len(api)+1) + buf[0] = byte(len(api)) + copy(buf[1:], []byte(api)) + return br.Emit(SYSCALL, buf) +} + +// EmitCall emits a call Opcode with label to the given buffer. +func (br *Builder) EmitCall(op Instruction, label int16) *Builder { + return br.EmitJmp(op, label) +} + +// EmitJmp emits a jump Opcode along with label to the given buffer. +func (br *Builder) EmitJmp(op Instruction, label int16) *Builder { + if !isOpcodeJmp(op) { + br.err = fmt.Errorf("opcode %d is not a jump or call type", op) + } + buf := make([]byte, 2) + binary.LittleEndian.PutUint16(buf, uint16(label)) + return br.Emit(op, buf) +} + +// EmitAppCall emits an appcall, if tailCall is true, tailCall opcode will be +// emitted instead. +func (br *Builder) EmitAppCall(scriptHash util.Uint160, tailCall bool) *Builder { + op := APPCALL + if tailCall { + op = TAILCALL + } + return br.Emit(op, scriptHash.Bytes()) +} + +// EmitAppCallWithOperationAndData emits an appcall with the given operation and data. +func (br *Builder) EmitAppCallWithOperationAndData(w *bytes.Buffer, scriptHash util.Uint160, operation string, data []byte) *Builder { + br.EmitBytes(data) + br.EmitString(operation) + return br.EmitAppCall(scriptHash, false) +} + +// EmitAppCallWithOperation emits an appcall with the given operation. +func (br *Builder) EmitAppCallWithOperation(scriptHash util.Uint160, operation string) *Builder { + br.EmitBool(false) + br.EmitString(operation) + return br.EmitAppCall(scriptHash, false) +} + +func isOpcodeJmp(op Instruction) bool { + if op == JMP || op == JMPIFNOT || op == JMPIF || op == CALL { + return true + } + return false +} From 04c56b514c745f1a4d5f64b4c84b4e158fb8e6ab Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Fri, 15 Mar 2019 22:30:25 +0000 Subject: [PATCH 12/81] Refactor Int, Boolean, ByteArray conversion --- pkg/vm/stack/Int.go | 21 +++++++++++---------- pkg/vm/stack/boolean.go | 8 ++++++++ pkg/vm/stack/bytearray.go | 36 ++++++++++++++++++++---------------- pkg/vm/stack/int_test.go | 15 +++++++++++++++ 4 files changed, 54 insertions(+), 26 deletions(-) diff --git a/pkg/vm/stack/Int.go b/pkg/vm/stack/Int.go index 4f1d070a5..08ee41629 100644 --- a/pkg/vm/stack/Int.go +++ b/pkg/vm/stack/Int.go @@ -11,6 +11,8 @@ type Int struct { // NewInt will convert a big integer into // a StackInteger func NewInt(val *big.Int) (*Int, error) { + // TOODO: check it is 32 bytes + return &Int{ abstractItem: &abstractItem{}, val: val, @@ -76,20 +78,19 @@ func (i *Int) Integer() (*Int, error) { // ByteArray override the default ByteArray method // to convert a Integer into a byte Array func (i *Int) ByteArray() (*ByteArray, error) { - return &ByteArray{ - i.abstractItem, - i.val.Bytes(), - }, nil + b := i.val.Bytes() + dest := reverse(b) + return NewByteArray(dest), nil } //Boolean override the default Boolean method // to convert an Integer into a Boolean StackItem func (i *Int) Boolean() (*Boolean, error) { - boolean := (i.val.Int64() != 0) - return &Boolean{ - i.abstractItem, - boolean, - }, nil - + return NewBoolean(boolean) +} + +//Value returns the underlying big.Int +func (i *Int) Value() *big.Int { + return i.val } diff --git a/pkg/vm/stack/boolean.go b/pkg/vm/stack/boolean.go index d6a2c12ac..2441a303c 100644 --- a/pkg/vm/stack/boolean.go +++ b/pkg/vm/stack/boolean.go @@ -6,6 +6,14 @@ type Boolean struct { val bool } +//NewBoolean returns a new boolean stack item +func NewBoolean(val bool) (*Boolean, error) { + return &Boolean{ + &abstractItem{}, + val, + }, nil +} + // Boolean overrides the default implementation // by the abstractItem, returning a Boolean struct func (b *Boolean) Boolean() (*Boolean, error) { diff --git a/pkg/vm/stack/bytearray.go b/pkg/vm/stack/bytearray.go index 7d57b12f0..6b1de0d79 100644 --- a/pkg/vm/stack/bytearray.go +++ b/pkg/vm/stack/bytearray.go @@ -29,29 +29,33 @@ func (ba *ByteArray) ByteArray() (*ByteArray, error) { //Integer overrides the default Integer method to convert an // ByteArray Into an integer func (ba *ByteArray) Integer() (*Int, error) { - - dest := make([]byte, 0) - - for i, j := 0, len(ba.val)-1; i < j+1; i, j = i+1, j-1 { - dest[i], dest[j] = ba.val[j], ba.val[i] - } - + dest := reverse(ba.val) integerVal := new(big.Int).SetBytes(dest) + return NewInt(integerVal) - return &Int{ - ba.abstractItem, - integerVal, - }, nil } -// Boolean will convert +// Boolean will convert a byte array into a boolean stack item func (ba *ByteArray) Boolean() (*Boolean, error) { boolean, err := strconv.ParseBool(string(ba.val)) if err != nil { return nil, errors.New("cannot convert byte array to a boolean") } - return &Boolean{ - ba.abstractItem, - boolean, - }, nil + return NewBoolean(boolean) +} + +// XXX: move this into a pkg/util/slice folder +// Go mod not working +func reverse(b []byte) []byte { + if len(b) < 2 { + return b + } + + dest := make([]byte, len(b)) + + for i, j := 0, len(b)-1; i < j+1; i, j = i+1, j-1 { + dest[i], dest[j] = b[j], b[i] + } + + return dest } diff --git a/pkg/vm/stack/int_test.go b/pkg/vm/stack/int_test.go index 40ef3c8b2..25d360183 100644 --- a/pkg/vm/stack/int_test.go +++ b/pkg/vm/stack/int_test.go @@ -55,3 +55,18 @@ func TestRsh(t *testing.T) { assert.Nil(t, err) assert.Equal(t, true, expected.Equal(c)) } + +func TestByteArrConversion(t *testing.T) { + + var num int64 = 100000 + + a := testMakeStackInt(t, num) + ba, err := a.ByteArray() + assert.Nil(t, err) + + have, err := ba.Integer() + assert.Nil(t, err) + + assert.Equal(t, num, have.val.Int64()) + +} From 1ff0caf40e69239be962263d25888975fe5a9144 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Fri, 15 Mar 2019 22:32:08 +0000 Subject: [PATCH 13/81] Add Context stack Item --- pkg/vm/stack/context.go | 137 +++++++++++++++++++++++++++++++++++ pkg/vm/stack/context_test.go | 20 +++++ pkg/vm/stack/stackitem.go | 7 ++ 3 files changed, 164 insertions(+) create mode 100644 pkg/vm/stack/context.go create mode 100644 pkg/vm/stack/context_test.go diff --git a/pkg/vm/stack/context.go b/pkg/vm/stack/context.go new file mode 100644 index 000000000..37e342739 --- /dev/null +++ b/pkg/vm/stack/context.go @@ -0,0 +1,137 @@ +package stack + +import ( + "encoding/binary" +) + +// Context represent the current execution context of the VM. +// context will be treated as stack item +// and placed onto the invocation stack +type Context struct { + *abstractItem + + // Instruction pointer. + ip int + + // The raw program script. + prog []byte + + // Breakpoints + breakPoints []int + + // Evaluation Stack + Estack RandomAccess +} + +// NewContext return a new Context object. +func NewContext(b []byte) *Context { + return &Context{ + abstractItem: &abstractItem{}, + ip: -1, + prog: b, + breakPoints: []int{}, + } +} + +// Context overrides the default implementation +// to return a context item +func (c *Context) Context() (*Context, error) { + return c, nil +} + +// Next return the next instruction to execute. +func (c *Context) Next() Instruction { + c.ip++ + if c.ip >= len(c.prog) { + return RET + } + return Instruction(c.prog[c.ip]) +} + +// IP returns the absolute instruction without taking 0 into account. +// If that program starts the ip = 0 but IP() will return 1, cause its +// the first instruction. +func (c *Context) IP() int { + return c.ip + 1 +} + +// LenInstr returns the number of instructions loaded. +func (c *Context) LenInstr() int { + return len(c.prog) +} + +// CurrInstr returns the current instruction and opcode. +func (c *Context) CurrInstr() (int, Instruction) { + if c.ip < 0 { + return c.ip, Instruction(0x00) + } + return c.ip, Instruction(c.prog[c.ip]) +} + +// Copy returns an new exact copy of c. +func (c *Context) Copy() *Context { + return &Context{ + ip: c.ip, + prog: c.prog, + breakPoints: c.breakPoints, + } +} + +// Program returns the loaded program. +func (c *Context) Program() []byte { + return c.prog +} + +func (c *Context) atBreakPoint() bool { + for _, n := range c.breakPoints { + if n == c.ip { + return true + } + } + return false +} + +func (c *Context) String() string { + return "execution context" +} + +func (c *Context) readUint32() uint32 { + start, end := c.IP(), c.IP()+4 + if end > len(c.prog) { + return 0 + } + val := binary.LittleEndian.Uint32(c.prog[start:end]) + c.ip += 4 + return val +} + +func (c *Context) readUint16() uint16 { + start, end := c.IP(), c.IP()+2 + if end > len(c.prog) { + return 0 + } + val := binary.LittleEndian.Uint16(c.prog[start:end]) + c.ip += 2 + return val +} + +func (c *Context) readByte() byte { + return c.readBytes(1)[0] +} + +func (c *Context) readBytes(n int) []byte { + start, end := c.IP(), c.IP()+n + if end > len(c.prog) { + return nil + } + + out := make([]byte, n) + copy(out, c.prog[start:end]) + c.ip += n + return out +} + +func (c *Context) readVarBytes() []byte { + n := c.readByte() + return c.readBytes(int(n)) +} diff --git a/pkg/vm/stack/context_test.go b/pkg/vm/stack/context_test.go new file mode 100644 index 000000000..6dbe36bb6 --- /dev/null +++ b/pkg/vm/stack/context_test.go @@ -0,0 +1,20 @@ +package stack + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNextInstruction(t *testing.T) { + // PUSHBYTES1 2 + builder := NewBuilder() + builder.EmitBytes([]byte{0x02}) //[]byte{0x01, 0x02} + + ctx := NewContext(builder.Bytes()) + op := ctx.Next() + byt := ctx.readByte() + + assert.Equal(t, PUSHBYTES1, op) + assert.Equal(t, byte(2), byt) +} diff --git a/pkg/vm/stack/stackitem.go b/pkg/vm/stack/stackitem.go index 9f29f9d3b..beed13363 100644 --- a/pkg/vm/stack/stackitem.go +++ b/pkg/vm/stack/stackitem.go @@ -10,6 +10,7 @@ type Item interface { Boolean() (*Boolean, error) ByteArray() (*ByteArray, error) Array() (*Array, error) + Context() (*Context, error) } // Represents an `abstract` stack item @@ -40,3 +41,9 @@ func (a *abstractItem) ByteArray() (*ByteArray, error) { func (a *abstractItem) Array() (*Array, error) { return nil, errors.New("This stack item is not an array") } + +// Context is the default implementation for a stackItem +// Implements Item interface +func (a *abstractItem) Context() (*Context, error) { + return nil, errors.New("This stack item is not of type context") +} From f954e6f2ca536751f60ee6820c329d9f7be5eccb Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Fri, 15 Mar 2019 22:33:32 +0000 Subject: [PATCH 14/81] Add Invocation stack - convenience RAS --- pkg/vm/stack/Int.go | 2 -- pkg/vm/stack/invocation.go | 53 ++++++++++++++++++++++++++++++++++++++ pkg/vm/stack/stack.go | 12 +++++++++ 3 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 pkg/vm/stack/invocation.go diff --git a/pkg/vm/stack/Int.go b/pkg/vm/stack/Int.go index 08ee41629..2483a78ea 100644 --- a/pkg/vm/stack/Int.go +++ b/pkg/vm/stack/Int.go @@ -11,8 +11,6 @@ type Int struct { // NewInt will convert a big integer into // a StackInteger func NewInt(val *big.Int) (*Int, error) { - // TOODO: check it is 32 bytes - return &Int{ abstractItem: &abstractItem{}, val: val, diff --git a/pkg/vm/stack/invocation.go b/pkg/vm/stack/invocation.go new file mode 100644 index 000000000..991e28231 --- /dev/null +++ b/pkg/vm/stack/invocation.go @@ -0,0 +1,53 @@ +package stack + +import "errors" + +// Invocation embeds a Random Access stack +// Providing helper methods for the context object +type Invocation struct{ RandomAccess } + +//NewInvocation will return a new +// Invocation stack +func NewInvocation() *Invocation { + return &Invocation{ + RandomAccess{ + vals: make([]Item, 0, StackAverageSize), + }, + } +} + +func (i *Invocation) peekContext(n uint16) (*Context, error) { + item, err := i.Peek(n) + if err != nil { + return nil, err + } + ctx, err := item.Context() + if err != nil { + return nil, err + } + return ctx, nil +} + +// CurrentContext returns the current context on the invocation stack +func (i *Invocation) CurrentContext() (*Context, error) { + return i.peekContext(0) +} + +// CallingContext will return the cntext item +// that will be called next. +func (i *Invocation) CallingContext() (*Context, error) { + if i.Len() < 1 { + return nil, errors.New("Length of invocation stack is < 1, no calling context") + } + return i.peekContext(1) +} + +// EntryContext will return the context item that +// started the program +func (i *Invocation) EntryContext() (*Context, error) { + + // firstItemIndex refers to the first item + // that was popped on the stack + firstItemIndex := uint16(i.Len() - 1) // N.B. if this overflows because len is zero, then an error will be returned + return i.peekContext(firstItemIndex) +} diff --git a/pkg/vm/stack/stack.go b/pkg/vm/stack/stack.go index 3c01ad4e7..a298897e4 100644 --- a/pkg/vm/stack/stack.go +++ b/pkg/vm/stack/stack.go @@ -119,3 +119,15 @@ func (ras *RandomAccess) Peek(n uint16) (Item, error) { return ras.vals[index], nil } + +// Convenience Functions + +// PopInt will remove the last stack item that was added +// And cast it to an integer +func (ras *RandomAccess) PopInt() (*Int, error) { + item, err := ras.Pop() + if err != nil { + return nil, err + } + return item.Integer() +} From ce2cad0817e7a9901e01630f7c4b5358e0ca5e48 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Fri, 15 Mar 2019 22:34:04 +0000 Subject: [PATCH 15/81] rename testhelper to test_helper --- pkg/vm/stack/{testhelper.go => test_helper.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename pkg/vm/stack/{testhelper.go => test_helper.go} (100%) diff --git a/pkg/vm/stack/testhelper.go b/pkg/vm/stack/test_helper.go similarity index 100% rename from pkg/vm/stack/testhelper.go rename to pkg/vm/stack/test_helper.go From 101d48cd27f55302e373e2e2e2678bd669d86d1a Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Fri, 15 Mar 2019 22:35:12 +0000 Subject: [PATCH 16/81] Move opcode file --- pkg/vm/{instructions.go => stack/instruction.go} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename pkg/vm/{instructions.go => stack/instruction.go} (99%) diff --git a/pkg/vm/instructions.go b/pkg/vm/stack/instruction.go similarity index 99% rename from pkg/vm/instructions.go rename to pkg/vm/stack/instruction.go index 6a78f6a6a..1317a1e3e 100644 --- a/pkg/vm/instructions.go +++ b/pkg/vm/stack/instruction.go @@ -1,4 +1,4 @@ -package vm +package stack // Instruction represents a operation code in the neovm type Instruction byte From c7fb4c3bdf9ad324a832acbceb9cdef32a513592 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Fri, 15 Mar 2019 22:36:16 +0000 Subject: [PATCH 17/81] - Add `Add` OpCode - Add Opcode Function map --- pkg/vm/vm_ops.go | 7 +++++++ pkg/vm/vm_ops_maths.go | 25 +++++++++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 pkg/vm/vm_ops.go create mode 100644 pkg/vm/vm_ops_maths.go diff --git a/pkg/vm/vm_ops.go b/pkg/vm/vm_ops.go new file mode 100644 index 000000000..456552597 --- /dev/null +++ b/pkg/vm/vm_ops.go @@ -0,0 +1,7 @@ +package vm + +import "github.com/CityOfZion/neo-go/pkg/vm/stack" + +var opFunc = map[stack.Instruction]func(ctx *stack.Context, istack *stack.Invocation) error{ + stack.ADD: Add, +} diff --git a/pkg/vm/vm_ops_maths.go b/pkg/vm/vm_ops_maths.go new file mode 100644 index 000000000..f31aab6ec --- /dev/null +++ b/pkg/vm/vm_ops_maths.go @@ -0,0 +1,25 @@ +package vm + +import "github.com/CityOfZion/neo-go/pkg/vm/stack" + +// Add adds two stack Items together. +// Returns an error if either items cannot be casted to an integer +// or if integers cannot be added together +func Add(ctx *stack.Context, istack *stack.Invocation) error { + operandA, err := ctx.Estack.PopInt() + if err != nil { + return err + } + operandB, err := ctx.Estack.PopInt() + if err != nil { + return err + } + res, err := operandA.Add(operandB) + if err != nil { + return err + } + + ctx.Estack.Push(res) + + return nil +} From baf9d2b76850fb184f30680a06704d996d999468 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Fri, 15 Mar 2019 22:42:35 +0000 Subject: [PATCH 18/81] - Add test for math `Add` opcode - basic opcode execution --- pkg/vm/state.go | 10 ++++++++++ pkg/vm/vm.go | 38 ++++++++++++++++++++++++++++++++++++ pkg/vm/vm_ops_maths_test.go | 39 +++++++++++++++++++++++++++++++++++++ 3 files changed, 87 insertions(+) create mode 100644 pkg/vm/state.go create mode 100644 pkg/vm/vm.go create mode 100644 pkg/vm/vm_ops_maths_test.go diff --git a/pkg/vm/state.go b/pkg/vm/state.go new file mode 100644 index 000000000..4090ec86e --- /dev/null +++ b/pkg/vm/state.go @@ -0,0 +1,10 @@ +package vm + +type vmstate byte + +const ( + NONE = 0 + HALT = 1 << 0 + FAULT = 1 << 1 + BREAK = 1 << 2 +) diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go new file mode 100644 index 000000000..1793be031 --- /dev/null +++ b/pkg/vm/vm.go @@ -0,0 +1,38 @@ +package vm + +import ( + "fmt" + + "github.com/CityOfZion/neo-go/pkg/vm/stack" +) + +// VM represents an instance of a Neo Virtual Machine +type VM struct { + InvocationStack stack.Invocation + state vmstate +} + +//NewVM loads in a script +// uses the script to initiate a Context object +// pushes the context to the invocation stack +func NewVM(script []byte) *VM { + ctx := stack.NewContext(script) + v := &VM{ + state: NONE, + } + v.InvocationStack.Push(ctx) + return v +} + +// ExecuteOp will execute one opcode for a given context +func (v *VM) ExecuteOp(op stack.Instruction, ctx *stack.Context) error { + handleOp, ok := opFunc[op] + if !ok { + return fmt.Errorf("unknown opcode entered %v", op) + } + err := handleOp(ctx, &v.InvocationStack) + if err != nil { + return err + } + return nil +} diff --git a/pkg/vm/vm_ops_maths_test.go b/pkg/vm/vm_ops_maths_test.go new file mode 100644 index 000000000..a9d80373a --- /dev/null +++ b/pkg/vm/vm_ops_maths_test.go @@ -0,0 +1,39 @@ +package vm + +import ( + "math/big" + "testing" + + "github.com/CityOfZion/neo-go/pkg/vm/stack" + "github.com/stretchr/testify/assert" +) + +func TestAddOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(20)) + if err != nil { + t.Fail() + } + b, err := stack.NewInt(big.NewInt(23)) + if err != nil { + t.Fail() + } + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b) + + v.ExecuteOp(stack.ADD, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.PopInt() + if err != nil { + t.Fail() + } + + assert.Equal(t, int64(43), item.Value().Int64()) + +} From abc3b46f1ce9e3b7a9ad575f8c3ffbdaf8b047a4 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Fri, 15 Mar 2019 22:54:52 +0000 Subject: [PATCH 19/81] Add popTwoIntegers convenience func --- pkg/vm/vm_ops_maths.go | 45 +++++++++++++++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/pkg/vm/vm_ops_maths.go b/pkg/vm/vm_ops_maths.go index f31aab6ec..b49c11d3e 100644 --- a/pkg/vm/vm_ops_maths.go +++ b/pkg/vm/vm_ops_maths.go @@ -1,19 +1,16 @@ package vm -import "github.com/CityOfZion/neo-go/pkg/vm/stack" +import ( + "github.com/CityOfZion/neo-go/pkg/vm/stack" +) // Add adds two stack Items together. // Returns an error if either items cannot be casted to an integer // or if integers cannot be added together func Add(ctx *stack.Context, istack *stack.Invocation) error { - operandA, err := ctx.Estack.PopInt() - if err != nil { - return err - } - operandB, err := ctx.Estack.PopInt() - if err != nil { - return err - } + + operandA, operandB, err := popTwoIntegers(ctx) + res, err := operandA.Add(operandB) if err != nil { return err @@ -23,3 +20,33 @@ func Add(ctx *stack.Context, istack *stack.Invocation) error { return nil } + +// Sub subtracts two stack Items. +// Returns an error if either items cannot be casted to an integer +// or if integers cannot be subtracted together +func Sub(ctx *stack.Context, istack *stack.Invocation) error { + + operandA, operandB, err := popTwoIntegers(ctx) + + res, err := operandB.Sub(operandA) + if err != nil { + return err + } + + ctx.Estack.Push(res) + + return nil +} + +func popTwoIntegers(ctx *stack.Context) (*stack.Int, *stack.Int, error) { + operandA, err := ctx.Estack.PopInt() + if err != nil { + return nil, nil, err + } + operandB, err := ctx.Estack.PopInt() + if err != nil { + return nil, nil, err + } + + return operandA, operandB, nil +} From 280d526f417333ed50aebcfa3113cfbeda72142a Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Fri, 15 Mar 2019 22:55:08 +0000 Subject: [PATCH 20/81] Add `SUB` Opcode --- pkg/vm/vm_ops.go | 1 + pkg/vm/vm_ops_maths_test.go | 30 ++++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/pkg/vm/vm_ops.go b/pkg/vm/vm_ops.go index 456552597..36b22e472 100644 --- a/pkg/vm/vm_ops.go +++ b/pkg/vm/vm_ops.go @@ -4,4 +4,5 @@ import "github.com/CityOfZion/neo-go/pkg/vm/stack" var opFunc = map[stack.Instruction]func(ctx *stack.Context, istack *stack.Invocation) error{ stack.ADD: Add, + stack.SUB: Sub, } diff --git a/pkg/vm/vm_ops_maths_test.go b/pkg/vm/vm_ops_maths_test.go index a9d80373a..6b0e13bbe 100644 --- a/pkg/vm/vm_ops_maths_test.go +++ b/pkg/vm/vm_ops_maths_test.go @@ -37,3 +37,33 @@ func TestAddOp(t *testing.T) { assert.Equal(t, int64(43), item.Value().Int64()) } + +func TestSubOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(30)) + if err != nil { + t.Fail() + } + b, err := stack.NewInt(big.NewInt(40)) + if err != nil { + t.Fail() + } + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b) + + v.ExecuteOp(stack.SUB, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.PopInt() + if err != nil { + t.Fail() + } + + assert.Equal(t, int64(-10), item.Value().Int64()) + +} From 9a59755745e35935a96b16c3b91371d1fe2a6218 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Fri, 15 Mar 2019 23:21:30 +0000 Subject: [PATCH 21/81] Export Context Read methods - Return errors where failable --- pkg/vm/stack/context.go | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/pkg/vm/stack/context.go b/pkg/vm/stack/context.go index 37e342739..343800afc 100644 --- a/pkg/vm/stack/context.go +++ b/pkg/vm/stack/context.go @@ -2,6 +2,7 @@ package stack import ( "encoding/binary" + "errors" ) // Context represent the current execution context of the VM. @@ -95,7 +96,8 @@ func (c *Context) String() string { return "execution context" } -func (c *Context) readUint32() uint32 { +// ReadUint32 reads a uint32 from the script +func (c *Context) ReadUint32() uint32 { start, end := c.IP(), c.IP()+4 if end > len(c.prog) { return 0 @@ -105,7 +107,8 @@ func (c *Context) readUint32() uint32 { return val } -func (c *Context) readUint16() uint16 { +// ReadUint16 reads a uint16 from the script +func (c *Context) ReadUint16() uint16 { start, end := c.IP(), c.IP()+2 if end > len(c.prog) { return 0 @@ -115,23 +118,33 @@ func (c *Context) readUint16() uint16 { return val } -func (c *Context) readByte() byte { - return c.readBytes(1)[0] +// ReadByte reads one byte from the script +func (c *Context) ReadByte() (byte, error) { + byt, err := c.ReadBytes(1) + if err != nil { + return 0, err + } + + return byt[0], nil } -func (c *Context) readBytes(n int) []byte { +// ReadBytes will read n bytes from the context +func (c *Context) ReadBytes(n int) ([]byte, error) { start, end := c.IP(), c.IP()+n if end > len(c.prog) { - return nil + return nil, errors.New("Too many bytes to read, pointer goes past end of program") } out := make([]byte, n) copy(out, c.prog[start:end]) c.ip += n - return out + return out, nil } -func (c *Context) readVarBytes() []byte { - n := c.readByte() - return c.readBytes(int(n)) +func (c *Context) readVarBytes() ([]byte, error) { + n, err := c.ReadByte() + if err != nil { + return nil, err + } + return c.ReadBytes(int(n)) } From 80fd427517dc63e3e314bea2be8e8e9c637f8036 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Fri, 15 Mar 2019 23:37:54 +0000 Subject: [PATCH 22/81] - Add `Op` to handleOP func signature - Add PushNBytes OPcode --- pkg/vm/vm.go | 3 ++- pkg/vm/vm_ops.go | 14 +++++++++++--- pkg/vm/vm_ops_maths.go | 4 ++-- pkg/vm/vm_ops_stackmani.go | 19 +++++++++++++++++++ 4 files changed, 34 insertions(+), 6 deletions(-) create mode 100644 pkg/vm/vm_ops_stackmani.go diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index 1793be031..766e43422 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -26,11 +26,12 @@ func NewVM(script []byte) *VM { // ExecuteOp will execute one opcode for a given context func (v *VM) ExecuteOp(op stack.Instruction, ctx *stack.Context) error { + handleOp, ok := opFunc[op] if !ok { return fmt.Errorf("unknown opcode entered %v", op) } - err := handleOp(ctx, &v.InvocationStack) + err := handleOp(op, ctx, &v.InvocationStack) if err != nil { return err } diff --git a/pkg/vm/vm_ops.go b/pkg/vm/vm_ops.go index 36b22e472..f2424d81a 100644 --- a/pkg/vm/vm_ops.go +++ b/pkg/vm/vm_ops.go @@ -2,7 +2,15 @@ package vm import "github.com/CityOfZion/neo-go/pkg/vm/stack" -var opFunc = map[stack.Instruction]func(ctx *stack.Context, istack *stack.Invocation) error{ - stack.ADD: Add, - stack.SUB: Sub, +var opFunc = map[stack.Instruction]func(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation) error{ + stack.ADD: Add, + stack.SUB: Sub, + stack.PUSHBYTES1: PushNBytes, + stack.PUSHBYTES75: PushNBytes, +} + +func init() { + for i := int(stack.PUSHBYTES1); i <= int(stack.PUSHBYTES75); i++ { + opFunc[stack.Instruction(i)] = PushNBytes + } } diff --git a/pkg/vm/vm_ops_maths.go b/pkg/vm/vm_ops_maths.go index b49c11d3e..7d6afad41 100644 --- a/pkg/vm/vm_ops_maths.go +++ b/pkg/vm/vm_ops_maths.go @@ -7,7 +7,7 @@ import ( // Add adds two stack Items together. // Returns an error if either items cannot be casted to an integer // or if integers cannot be added together -func Add(ctx *stack.Context, istack *stack.Invocation) error { +func Add(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation) error { operandA, operandB, err := popTwoIntegers(ctx) @@ -24,7 +24,7 @@ func Add(ctx *stack.Context, istack *stack.Invocation) error { // Sub subtracts two stack Items. // Returns an error if either items cannot be casted to an integer // or if integers cannot be subtracted together -func Sub(ctx *stack.Context, istack *stack.Invocation) error { +func Sub(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation) error { operandA, operandB, err := popTwoIntegers(ctx) diff --git a/pkg/vm/vm_ops_stackmani.go b/pkg/vm/vm_ops_stackmani.go new file mode 100644 index 000000000..c510eb7db --- /dev/null +++ b/pkg/vm/vm_ops_stackmani.go @@ -0,0 +1,19 @@ +package vm + +import ( + "github.com/CityOfZion/neo-go/pkg/vm/stack" +) + +// Stack Manipulation Opcodes + +// PushNBytes will Read N Bytes from the script and push it onto the stack +func PushNBytes(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation) error { + + val, err := ctx.ReadBytes(int(op)) + if err != nil { + return err + } + ba := stack.NewByteArray(val) + ctx.Estack.Push(ba) + return nil +} From 48413900ca000b28469599ec64d7b98a8195e801 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Sat, 16 Mar 2019 21:44:03 +0000 Subject: [PATCH 23/81] remove error on NewBoolean Expose underlying with Getter on Boolean StackItem Add Equals method for ByteArray --- pkg/vm/stack/Int.go | 2 +- pkg/vm/stack/boolean.go | 9 +++++++-- pkg/vm/stack/bytearray.go | 12 +++++++++++- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/pkg/vm/stack/Int.go b/pkg/vm/stack/Int.go index 2483a78ea..ac9a4cdba 100644 --- a/pkg/vm/stack/Int.go +++ b/pkg/vm/stack/Int.go @@ -85,7 +85,7 @@ func (i *Int) ByteArray() (*ByteArray, error) { // to convert an Integer into a Boolean StackItem func (i *Int) Boolean() (*Boolean, error) { boolean := (i.val.Int64() != 0) - return NewBoolean(boolean) + return NewBoolean(boolean), nil } //Value returns the underlying big.Int diff --git a/pkg/vm/stack/boolean.go b/pkg/vm/stack/boolean.go index 2441a303c..66e3647e5 100644 --- a/pkg/vm/stack/boolean.go +++ b/pkg/vm/stack/boolean.go @@ -7,11 +7,11 @@ type Boolean struct { } //NewBoolean returns a new boolean stack item -func NewBoolean(val bool) (*Boolean, error) { +func NewBoolean(val bool) *Boolean { return &Boolean{ &abstractItem{}, val, - }, nil + } } // Boolean overrides the default implementation @@ -19,3 +19,8 @@ func NewBoolean(val bool) (*Boolean, error) { func (b *Boolean) Boolean() (*Boolean, error) { return b, nil } + +// Value returns the underlying boolean value +func (b *Boolean) Value() bool { + return b.val +} diff --git a/pkg/vm/stack/bytearray.go b/pkg/vm/stack/bytearray.go index 6b1de0d79..7d1c3c818 100644 --- a/pkg/vm/stack/bytearray.go +++ b/pkg/vm/stack/bytearray.go @@ -1,6 +1,7 @@ package stack import ( + "bytes" "errors" "math/big" "strconv" @@ -26,6 +27,15 @@ func (ba *ByteArray) ByteArray() (*ByteArray, error) { return ba, nil } +//Equals returns true, if two bytearrays are equal +func (ba *ByteArray) Equals(other *ByteArray) *Boolean { + // If either are nil, return false + if ba == nil || other == nil { + return NewBoolean(false) + } + return NewBoolean(bytes.Equal(ba.val, other.val)) +} + //Integer overrides the default Integer method to convert an // ByteArray Into an integer func (ba *ByteArray) Integer() (*Int, error) { @@ -41,7 +51,7 @@ func (ba *ByteArray) Boolean() (*Boolean, error) { if err != nil { return nil, errors.New("cannot convert byte array to a boolean") } - return NewBoolean(boolean) + return NewBoolean(boolean), nil } // XXX: move this into a pkg/util/slice folder From 9eb11d2822b02bd76754f72ef7388fc9b7bb1432 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Sat, 16 Mar 2019 21:45:48 +0000 Subject: [PATCH 24/81] Make Next() method on Context failable refactor peekContext and Peek --- pkg/vm/stack/context.go | 6 +++--- pkg/vm/stack/invocation.go | 13 ++++++++----- pkg/vm/stack/stack.go | 15 ++++++++++++--- 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/pkg/vm/stack/context.go b/pkg/vm/stack/context.go index 343800afc..ee6cce1a3 100644 --- a/pkg/vm/stack/context.go +++ b/pkg/vm/stack/context.go @@ -41,12 +41,12 @@ func (c *Context) Context() (*Context, error) { } // Next return the next instruction to execute. -func (c *Context) Next() Instruction { +func (c *Context) Next() (Instruction, error) { c.ip++ if c.ip >= len(c.prog) { - return RET + return RET, errors.New("program pointer is more than the length of program. RETURNING") } - return Instruction(c.prog[c.ip]) + return Instruction(c.prog[c.ip]), nil } // IP returns the absolute instruction without taking 0 into account. diff --git a/pkg/vm/stack/invocation.go b/pkg/vm/stack/invocation.go index 991e28231..a3a66b8ec 100644 --- a/pkg/vm/stack/invocation.go +++ b/pkg/vm/stack/invocation.go @@ -21,11 +21,7 @@ func (i *Invocation) peekContext(n uint16) (*Context, error) { if err != nil { return nil, err } - ctx, err := item.Context() - if err != nil { - return nil, err - } - return ctx, nil + return item.Context() } // CurrentContext returns the current context on the invocation stack @@ -33,6 +29,13 @@ func (i *Invocation) CurrentContext() (*Context, error) { return i.peekContext(0) } +// RemoveCurrentContext removes the context on the top of the invocation stack +// This is a convenience method for Pop +func (i *Invocation) RemoveCurrentContext() error { + _, err := i.Pop() + return err +} + // CallingContext will return the cntext item // that will be called next. func (i *Invocation) CallingContext() (*Context, error) { diff --git a/pkg/vm/stack/stack.go b/pkg/vm/stack/stack.go index a298897e4..e857a9ada 100644 --- a/pkg/vm/stack/stack.go +++ b/pkg/vm/stack/stack.go @@ -106,15 +106,14 @@ func (ras *RandomAccess) Peek(n uint16) (Item, error) { return ras.vals[index], nil } - if ras.vals == nil { - return nil, errors.New("Cannot peak at a nil stack") + if ras.Len() < 1 { + return nil, fmt.Errorf("cannot peak at a stack with no item, length of stack is %d", ras.Len()) } // Check that we are not peeking out of the bounds if n > stackSize-1 { return nil, fmt.Errorf("Tried to peek at index %d when length of stack is %d", n, len(ras.vals)) } - index := stackSize - n - 1 return ras.vals[index], nil @@ -131,3 +130,13 @@ func (ras *RandomAccess) PopInt() (*Int, error) { } return item.Integer() } + +// PopByteArray will remove the last stack item that was added +// And cast it to an ByteArray +func (ras *RandomAccess) PopByteArray() (*ByteArray, error) { + item, err := ras.Pop() + if err != nil { + return nil, err + } + return item.ByteArray() +} From a7e973030c6c15a80831889827a14f4df23e66cf Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Sat, 16 Mar 2019 21:52:05 +0000 Subject: [PATCH 25/81] Remove context_test rename invocation to invocationstack export vmstate --- pkg/vm/stack/context_test.go | 20 ------------------- .../{invocation.go => invocationstack.go} | 0 pkg/vm/state.go | 3 ++- 3 files changed, 2 insertions(+), 21 deletions(-) delete mode 100644 pkg/vm/stack/context_test.go rename pkg/vm/stack/{invocation.go => invocationstack.go} (100%) diff --git a/pkg/vm/stack/context_test.go b/pkg/vm/stack/context_test.go deleted file mode 100644 index 6dbe36bb6..000000000 --- a/pkg/vm/stack/context_test.go +++ /dev/null @@ -1,20 +0,0 @@ -package stack - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestNextInstruction(t *testing.T) { - // PUSHBYTES1 2 - builder := NewBuilder() - builder.EmitBytes([]byte{0x02}) //[]byte{0x01, 0x02} - - ctx := NewContext(builder.Bytes()) - op := ctx.Next() - byt := ctx.readByte() - - assert.Equal(t, PUSHBYTES1, op) - assert.Equal(t, byte(2), byt) -} diff --git a/pkg/vm/stack/invocation.go b/pkg/vm/stack/invocationstack.go similarity index 100% rename from pkg/vm/stack/invocation.go rename to pkg/vm/stack/invocationstack.go diff --git a/pkg/vm/state.go b/pkg/vm/state.go index 4090ec86e..64db70f2f 100644 --- a/pkg/vm/state.go +++ b/pkg/vm/state.go @@ -1,6 +1,7 @@ package vm -type vmstate byte +//Vmstate represents all possible states that the neo-vm can be in +type Vmstate byte const ( NONE = 0 From 329f8f388c16088b389d08b5ad2c2fe2f165d78c Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Sat, 16 Mar 2019 22:05:00 +0000 Subject: [PATCH 26/81] Add ExecuteOp, Step and Run methods on the VM --- pkg/vm/vm.go | 52 ++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 40 insertions(+), 12 deletions(-) diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index 766e43422..604348baf 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -9,12 +9,13 @@ import ( // VM represents an instance of a Neo Virtual Machine type VM struct { InvocationStack stack.Invocation - state vmstate + state Vmstate } -//NewVM loads in a script -// uses the script to initiate a Context object -// pushes the context to the invocation stack +// NewVM will: +// Set the state of the VM to NONE +// instantiate a script as a new context +// Push the Context to the Invocation stack func NewVM(script []byte) *VM { ctx := stack.NewContext(script) v := &VM{ @@ -24,16 +25,43 @@ func NewVM(script []byte) *VM { return v } -// ExecuteOp will execute one opcode for a given context -func (v *VM) ExecuteOp(op stack.Instruction, ctx *stack.Context) error { +// Run loops over the current context by continuously steppping. +// Run breaks; once step returns an error or any state that is not NONE +func (v *VM) Run() (Vmstate, error) { + for { + state, err := v.step() + if err != nil || state != NONE { + return state, err + } + } +} +// step will read `one` opcode from the script in the current context +// Then excute that opcode +func (v *VM) step() (Vmstate, error) { + // Get Current Context + ctx, err := v.InvocationStack.CurrentContext() + if err != nil { + return FAULT, err + } + // Read Opcode from context + op, _ := ctx.Next() // The only error that can occur from this, is if the pointer goes over the pointer + // In the NEO-VM specs, this is ignored and we return the RET opcode + // Execute OpCode + state, err := v.executeOp(stack.Instruction(op), ctx) + if err != nil { + return FAULT, err + } + return state, nil +} + +// ExecuteOp will execute one opcode on a given context. +// If the opcode is not registered, then an unknown opcode error will be returned +func (v *VM) executeOp(op stack.Instruction, ctx *stack.Context) (Vmstate, error) { + //Find function which handles that specific opcode handleOp, ok := opFunc[op] if !ok { - return fmt.Errorf("unknown opcode entered %v", op) + return FAULT, fmt.Errorf("unknown opcode entered %v", op) } - err := handleOp(op, ctx, &v.InvocationStack) - if err != nil { - return err - } - return nil + return handleOp(op, ctx, &v.InvocationStack) } From 31511e55d02ffde0400a08aea98d3cae3389d0b6 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Sat, 16 Mar 2019 22:06:32 +0000 Subject: [PATCH 27/81] Add Equal Opcode --- pkg/vm/vm_ops_bitwise.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 pkg/vm/vm_ops_bitwise.go diff --git a/pkg/vm/vm_ops_bitwise.go b/pkg/vm/vm_ops_bitwise.go new file mode 100644 index 000000000..8c73ce25e --- /dev/null +++ b/pkg/vm/vm_ops_bitwise.go @@ -0,0 +1,17 @@ +package vm + +import "github.com/CityOfZion/neo-go/pkg/vm/stack" + +// Bitwise logic + +// EQUAL pushes true to the stack +// If the two top items on the stack are equal +func EQUAL(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation) (Vmstate, error) { + + itemA, itemB, err := popTwoByteArrays(ctx) + if err != nil { + return FAULT, err + } + ctx.Estack.Push(itemA.Equals(itemB)) + return NONE, nil +} From 79e92d5e1494715a080e5805ad35d51234b97b42 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Sat, 16 Mar 2019 22:08:35 +0000 Subject: [PATCH 28/81] Add THROWIFNOT Opcode --- pkg/vm/vm_ops_exceptions.go | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 pkg/vm/vm_ops_exceptions.go diff --git a/pkg/vm/vm_ops_exceptions.go b/pkg/vm/vm_ops_exceptions.go new file mode 100644 index 000000000..bdf45dbfa --- /dev/null +++ b/pkg/vm/vm_ops_exceptions.go @@ -0,0 +1,33 @@ +package vm + +import ( + "errors" + + "github.com/CityOfZion/neo-go/pkg/vm/stack" +) + +// vm exceptions + +// THROWIFNOT faults if the item on the top of the stack +// does not evaluate to true +// For specific logic on how a number of bytearray is evaluated can be seen +// from the boolean conversion methods on the stack items +func THROWIFNOT(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation) (Vmstate, error) { + + // Pop item from top of stack + item, err := ctx.Estack.Pop() + if err != nil { + return FAULT, err + } + // Convert to a boolean + ok, err := item.Boolean() + if err != nil { + return FAULT, err + } + + // If false, throw + if !ok.Value() { + return FAULT, errors.New("Item on top of stack evaluates to false") + } + return NONE, nil +} From 7b519eba0dd6b0efd48ee5e9bc602d48bba000e8 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Sat, 16 Mar 2019 22:09:04 +0000 Subject: [PATCH 29/81] Add RET Opcode --- pkg/vm/vm_ops_flow.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 pkg/vm/vm_ops_flow.go diff --git a/pkg/vm/vm_ops_flow.go b/pkg/vm/vm_ops_flow.go new file mode 100644 index 000000000..7a90638b0 --- /dev/null +++ b/pkg/vm/vm_ops_flow.go @@ -0,0 +1,25 @@ +package vm + +import ( + "github.com/CityOfZion/neo-go/pkg/vm/stack" +) + +// Flow control + +// RET Returns from the current context +// Returns HALT if there are nomore context's to run +func RET(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation) (Vmstate, error) { + + // Pop current context from the Inovation stack + err := istack.RemoveCurrentContext() + if err != nil { + return FAULT, err + } + + // If there are no-more context's left to ran, then we HALT + if istack.Len() == 0 { + return HALT, nil + } + + return NONE, nil +} From c7e32e7eb303a236887faeced5311fda217c9a66 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Sat, 16 Mar 2019 22:09:23 +0000 Subject: [PATCH 30/81] Refactor PushNBytes Opcode --- pkg/vm/vm_ops_stackmani.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/vm/vm_ops_stackmani.go b/pkg/vm/vm_ops_stackmani.go index c510eb7db..366beb15b 100644 --- a/pkg/vm/vm_ops_stackmani.go +++ b/pkg/vm/vm_ops_stackmani.go @@ -7,13 +7,13 @@ import ( // Stack Manipulation Opcodes // PushNBytes will Read N Bytes from the script and push it onto the stack -func PushNBytes(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation) error { +func PushNBytes(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation) (Vmstate, error) { val, err := ctx.ReadBytes(int(op)) if err != nil { - return err + return FAULT, err } ba := stack.NewByteArray(val) ctx.Estack.Push(ba) - return nil + return NONE, nil } From 17c53d10812b31e88e74302a219cf88b623894de Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Sat, 16 Mar 2019 22:09:34 +0000 Subject: [PATCH 31/81] refactor Add, Sub to return VMSTATE add popTwoByteArrays helper function --- pkg/vm/vm_ops_maths.go | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/pkg/vm/vm_ops_maths.go b/pkg/vm/vm_ops_maths.go index 7d6afad41..6085dccb8 100644 --- a/pkg/vm/vm_ops_maths.go +++ b/pkg/vm/vm_ops_maths.go @@ -7,35 +7,39 @@ import ( // Add adds two stack Items together. // Returns an error if either items cannot be casted to an integer // or if integers cannot be added together -func Add(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation) error { +func Add(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation) (Vmstate, error) { operandA, operandB, err := popTwoIntegers(ctx) - + if err != nil { + return FAULT, err + } res, err := operandA.Add(operandB) if err != nil { - return err + return FAULT, err } ctx.Estack.Push(res) - return nil + return NONE, nil } // Sub subtracts two stack Items. // Returns an error if either items cannot be casted to an integer // or if integers cannot be subtracted together -func Sub(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation) error { +func Sub(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation) (Vmstate, error) { operandA, operandB, err := popTwoIntegers(ctx) - + if err != nil { + return FAULT, err + } res, err := operandB.Sub(operandA) if err != nil { - return err + return HALT, err } ctx.Estack.Push(res) - return nil + return NONE, nil } func popTwoIntegers(ctx *stack.Context) (*stack.Int, *stack.Int, error) { @@ -50,3 +54,17 @@ func popTwoIntegers(ctx *stack.Context) (*stack.Int, *stack.Int, error) { return operandA, operandB, nil } + +func popTwoByteArrays(ctx *stack.Context) (*stack.ByteArray, *stack.ByteArray, error) { + // Pop first stack item and cast as byte array + ba1, err := ctx.Estack.PopByteArray() + if err != nil { + return nil, nil, err + } + // Pop second stack item and cast as byte array + ba2, err := ctx.Estack.PopByteArray() + if err != nil { + return nil, nil, err + } + return ba1, ba2, nil +} From ef364900bb6940fd36a176a218ff5f5973cb15f3 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Sat, 16 Mar 2019 22:15:08 +0000 Subject: [PATCH 32/81] Add basic tests for vm --- pkg/vm/vm_ops.go | 5 +- pkg/vm/vm_ops_maths_test.go | 4 +- pkg/vm/vm_test.go | 113 ++++++++++++++++++++++++++++++++++++ 3 files changed, 119 insertions(+), 3 deletions(-) create mode 100644 pkg/vm/vm_test.go diff --git a/pkg/vm/vm_ops.go b/pkg/vm/vm_ops.go index f2424d81a..bf9291933 100644 --- a/pkg/vm/vm_ops.go +++ b/pkg/vm/vm_ops.go @@ -2,11 +2,14 @@ package vm import "github.com/CityOfZion/neo-go/pkg/vm/stack" -var opFunc = map[stack.Instruction]func(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation) error{ +var opFunc = map[stack.Instruction]func(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation) (Vmstate, error){ stack.ADD: Add, stack.SUB: Sub, stack.PUSHBYTES1: PushNBytes, stack.PUSHBYTES75: PushNBytes, + stack.RET: RET, + stack.EQUAL: EQUAL, + stack.THROWIFNOT: THROWIFNOT, } func init() { diff --git a/pkg/vm/vm_ops_maths_test.go b/pkg/vm/vm_ops_maths_test.go index 6b0e13bbe..4964e6923 100644 --- a/pkg/vm/vm_ops_maths_test.go +++ b/pkg/vm/vm_ops_maths_test.go @@ -24,7 +24,7 @@ func TestAddOp(t *testing.T) { ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a).Push(b) - v.ExecuteOp(stack.ADD, ctx) + v.executeOp(stack.ADD, ctx) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -54,7 +54,7 @@ func TestSubOp(t *testing.T) { ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a).Push(b) - v.ExecuteOp(stack.SUB, ctx) + v.executeOp(stack.SUB, ctx) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) diff --git a/pkg/vm/vm_test.go b/pkg/vm/vm_test.go new file mode 100644 index 000000000..db12c014c --- /dev/null +++ b/pkg/vm/vm_test.go @@ -0,0 +1,113 @@ +package vm + +import ( + "fmt" + "testing" + + "github.com/CityOfZion/neo-go/pkg/vm/stack" + "github.com/stretchr/testify/assert" +) + +func TestPushAdd(t *testing.T) { + builder := stack.NewBuilder() + + // PUSH TWO NUMBER + // ADD THEM TOGETHER + builder.EmitInt(20).EmitInt(34).EmitOpcode(stack.ADD) + + // Pass program to VM + vm := NewVM(builder.Bytes()) + + // Execute first OPCODE + // Should be PUSH(20) + state, err := vm.step() + assert.Equal(t, NONE, int(state)) + assert.Nil(t, err) + + // We should have the number 20 on stack + ok := peekTopEStackIsValue(t, vm, 20) + assert.True(t, ok) + + // Excute second OPCODE + // Should be PUSH(34) + state, err = vm.step() + assert.Equal(t, NONE, int(state)) + assert.Nil(t, err) + + // We should have the number 34 at the top of the stack + ok = peekTopEStackIsValue(t, vm, 34) + assert.True(t, ok) + + // Excute third OPCODE + // Should Add both values on the stack + state, err = vm.step() + assert.Equal(t, NONE, int(state)) + assert.Nil(t, err) + + // We should now have one value on the stack + //It should be equal to 20+34 = 54 + ok = EstackLen(t, vm, 1) + assert.True(t, ok) + ok = peekTopEStackIsValue(t, vm, 54) + assert.True(t, ok) + + // If we try to step again, we should get an error and HALT + // because we have gone over the instruction pointer + state, err = vm.step() + assert.Equal(t, HALT, int(state)) + assert.NotNil(t, err) + +} + +func TestSimpleRun(t *testing.T) { + + // Program pushes 20 and 34 to the stack + // Adds them together + // pushes 54 to the stack + // Checks if result of addition and 54 are equal + // Faults if not + + // Push(20) + // Push(34) + // Add + // Push(54) + // Equal + //THROWIFNOT + builder := stack.NewBuilder() + builder.EmitInt(20).EmitInt(34).EmitOpcode(stack.ADD) + builder.EmitInt(54).EmitOpcode(stack.EQUAL).EmitOpcode(stack.THROWIFNOT) + // Pass program to VM + vm := NewVM(builder.Bytes()) + + _, err := vm.Run() + assert.Nil(t, err) + +} + +// returns true if the value at the top of the evaluation stack is a integer +// and equals the value passed in +func peekTopEStackIsValue(t *testing.T, vm *VM, value int64) bool { + item := peakTopEstack(t, vm) + integer, err := item.Integer() + assert.Nil(t, err) + return value == integer.Value().Int64() +} + +// peaks the stack item on the top of the evaluation stack +// if the current context and returns it +func peakTopEstack(t *testing.T, vm *VM) stack.Item { + ctx, err := vm.InvocationStack.CurrentContext() + fmt.Println(err) + assert.Nil(t, err) + item, err := ctx.Estack.Peek(0) + assert.Nil(t, err) + return item +} + +// returns true if the total number of items on the evaluation stack +// is equal to value +func EstackLen(t *testing.T, vm *VM, value int) bool { + ctx, err := vm.InvocationStack.CurrentContext() + assert.Nil(t, err) + return value == ctx.Estack.Len() +} From a7db1ceaa59d9a298934e1fa7c023e32cada9854 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Sun, 17 Mar 2019 18:30:12 +0000 Subject: [PATCH 33/81] minor comment --- pkg/vm/vm_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/vm/vm_test.go b/pkg/vm/vm_test.go index db12c014c..896e528e7 100644 --- a/pkg/vm/vm_test.go +++ b/pkg/vm/vm_test.go @@ -76,9 +76,11 @@ func TestSimpleRun(t *testing.T) { builder := stack.NewBuilder() builder.EmitInt(20).EmitInt(34).EmitOpcode(stack.ADD) builder.EmitInt(54).EmitOpcode(stack.EQUAL).EmitOpcode(stack.THROWIFNOT) + // Pass program to VM vm := NewVM(builder.Bytes()) + // Runs vm with program _, err := vm.Run() assert.Nil(t, err) From 98096f6c20e922b5cbdb82f9063d8791c05c6024 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Sun, 17 Mar 2019 18:31:58 +0000 Subject: [PATCH 34/81] golint --- pkg/vm/state.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/vm/state.go b/pkg/vm/state.go index 64db70f2f..98c5cc912 100644 --- a/pkg/vm/state.go +++ b/pkg/vm/state.go @@ -3,6 +3,7 @@ package vm //Vmstate represents all possible states that the neo-vm can be in type Vmstate byte +// List of possible vm states const ( NONE = 0 HALT = 1 << 0 From 231aa29e5ae20e3f13f2892199d3b778ea5b6ab6 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Sun, 17 Mar 2019 18:38:52 +0000 Subject: [PATCH 35/81] Fix test --- pkg/vm/vm_test.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pkg/vm/vm_test.go b/pkg/vm/vm_test.go index 896e528e7..4ecc44317 100644 --- a/pkg/vm/vm_test.go +++ b/pkg/vm/vm_test.go @@ -51,11 +51,13 @@ func TestPushAdd(t *testing.T) { ok = peekTopEStackIsValue(t, vm, 54) assert.True(t, ok) - // If we try to step again, we should get an error and HALT + // If we try to step again, we should get a nil error and HALT // because we have gone over the instruction pointer + // error is nil because when there are nomore instructions, the vm + // will add a RET opcode and return state, err = vm.step() assert.Equal(t, HALT, int(state)) - assert.NotNil(t, err) + assert.Nil(t, err) } From 9e16bac7d8c31ef701f5f615a76161e9f2e51a16 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Sun, 17 Mar 2019 20:21:48 +0000 Subject: [PATCH 36/81] clarify vm states --- pkg/vm/state.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/pkg/vm/state.go b/pkg/vm/state.go index 98c5cc912..e6760c7c9 100644 --- a/pkg/vm/state.go +++ b/pkg/vm/state.go @@ -5,8 +5,16 @@ type Vmstate byte // List of possible vm states const ( - NONE = 0 - HALT = 1 << 0 + // NONE is the running state of the vm + // NONE signifies that the vm is ready to process an opcode + NONE = 0 + // HALT is a stopped state of the vm + // where the stop was signalled by the program completion + HALT = 1 << 0 + // FAULT is a stopped state of the vm + // where the stop was signalled by an error in the program FAULT = 1 << 1 + // BREAK is a suspended state for the VM + // were the break was signalled by a breakpoint BREAK = 1 << 2 ) From 351f0acdfe612227894e66c9e26ee39f64326115 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Sun, 17 Mar 2019 22:57:36 +0000 Subject: [PATCH 37/81] Add astack --- pkg/vm/stack/context.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pkg/vm/stack/context.go b/pkg/vm/stack/context.go index ee6cce1a3..d381d74cb 100644 --- a/pkg/vm/stack/context.go +++ b/pkg/vm/stack/context.go @@ -6,8 +6,7 @@ import ( ) // Context represent the current execution context of the VM. -// context will be treated as stack item -// and placed onto the invocation stack +// context will be treated as stack item and placed onto the invocation stack type Context struct { *abstractItem @@ -22,6 +21,9 @@ type Context struct { // Evaluation Stack Estack RandomAccess + + // Alternative Stack + Astack RandomAccess } // NewContext return a new Context object. @@ -44,7 +46,7 @@ func (c *Context) Context() (*Context, error) { func (c *Context) Next() (Instruction, error) { c.ip++ if c.ip >= len(c.prog) { - return RET, errors.New("program pointer is more than the length of program. RETURNING") + return RET, errors.New("program pointer is more than the length of program. Returning RET OPCODE") } return Instruction(c.prog[c.ip]), nil } From 38ad4572c4c1f44fbd41e5523230d6f6b1b4ce6f Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Mon, 18 Mar 2019 21:13:08 +0000 Subject: [PATCH 38/81] [VM] Pass ResultStack to the opcode handlers --- pkg/vm/vm.go | 7 ++++++- pkg/vm/vm_ops.go | 4 +++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index 604348baf..1f9fa66df 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -8,6 +8,11 @@ import ( // VM represents an instance of a Neo Virtual Machine type VM struct { + // ResultStack contains the results of + // the last evaluation stack before the program terminated + ResultStack stack.RandomAccess + // InvocationStack contains all of the contexts + // loaded into the vm InvocationStack stack.Invocation state Vmstate } @@ -63,5 +68,5 @@ func (v *VM) executeOp(op stack.Instruction, ctx *stack.Context) (Vmstate, error if !ok { return FAULT, fmt.Errorf("unknown opcode entered %v", op) } - return handleOp(op, ctx, &v.InvocationStack) + return handleOp(op, ctx, &v.InvocationStack, &v.ResultStack) } diff --git a/pkg/vm/vm_ops.go b/pkg/vm/vm_ops.go index bf9291933..39b796389 100644 --- a/pkg/vm/vm_ops.go +++ b/pkg/vm/vm_ops.go @@ -2,7 +2,9 @@ package vm import "github.com/CityOfZion/neo-go/pkg/vm/stack" -var opFunc = map[stack.Instruction]func(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation) (Vmstate, error){ +type stackInfo func(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) + +var opFunc = map[stack.Instruction]stackInfo{ stack.ADD: Add, stack.SUB: Sub, stack.PUSHBYTES1: PushNBytes, From c177e5577ec3874657c18d876ed198a6238cfce7 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Mon, 18 Mar 2019 21:14:03 +0000 Subject: [PATCH 39/81] [VM] refactor handlers to have rstack as argument --- pkg/vm/vm_ops_bitwise.go | 2 +- pkg/vm/vm_ops_exceptions.go | 4 ++-- pkg/vm/vm_ops_flow.go | 12 +++++++----- pkg/vm/vm_ops_maths.go | 4 ++-- pkg/vm/vm_ops_stackmani.go | 2 +- 5 files changed, 13 insertions(+), 11 deletions(-) diff --git a/pkg/vm/vm_ops_bitwise.go b/pkg/vm/vm_ops_bitwise.go index 8c73ce25e..350543fa2 100644 --- a/pkg/vm/vm_ops_bitwise.go +++ b/pkg/vm/vm_ops_bitwise.go @@ -6,7 +6,7 @@ import "github.com/CityOfZion/neo-go/pkg/vm/stack" // EQUAL pushes true to the stack // If the two top items on the stack are equal -func EQUAL(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation) (Vmstate, error) { +func EQUAL(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { itemA, itemB, err := popTwoByteArrays(ctx) if err != nil { diff --git a/pkg/vm/vm_ops_exceptions.go b/pkg/vm/vm_ops_exceptions.go index bdf45dbfa..dd09cfb60 100644 --- a/pkg/vm/vm_ops_exceptions.go +++ b/pkg/vm/vm_ops_exceptions.go @@ -12,7 +12,7 @@ import ( // does not evaluate to true // For specific logic on how a number of bytearray is evaluated can be seen // from the boolean conversion methods on the stack items -func THROWIFNOT(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation) (Vmstate, error) { +func THROWIFNOT(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { // Pop item from top of stack item, err := ctx.Estack.Pop() @@ -27,7 +27,7 @@ func THROWIFNOT(op stack.Instruction, ctx *stack.Context, istack *stack.Invocati // If false, throw if !ok.Value() { - return FAULT, errors.New("Item on top of stack evaluates to false") + return FAULT, errors.New("item on top of stack evaluates to false") } return NONE, nil } diff --git a/pkg/vm/vm_ops_flow.go b/pkg/vm/vm_ops_flow.go index 7a90638b0..67ca4f825 100644 --- a/pkg/vm/vm_ops_flow.go +++ b/pkg/vm/vm_ops_flow.go @@ -8,17 +8,19 @@ import ( // RET Returns from the current context // Returns HALT if there are nomore context's to run -func RET(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation) (Vmstate, error) { +func RET(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { // Pop current context from the Inovation stack - err := istack.RemoveCurrentContext() + ctx, err := istack.PopCurrentContext() if err != nil { return FAULT, err } - - // If there are no-more context's left to ran, then we HALT + // If this was the last context, then we copy over the evaluation stack to the resultstack + // As the program is about to terminate, once we remove the context if istack.Len() == 0 { - return HALT, nil + + err = ctx.Estack.CopyTo(rstack) + return HALT, err } return NONE, nil diff --git a/pkg/vm/vm_ops_maths.go b/pkg/vm/vm_ops_maths.go index 6085dccb8..a15596a30 100644 --- a/pkg/vm/vm_ops_maths.go +++ b/pkg/vm/vm_ops_maths.go @@ -7,7 +7,7 @@ import ( // Add adds two stack Items together. // Returns an error if either items cannot be casted to an integer // or if integers cannot be added together -func Add(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation) (Vmstate, error) { +func Add(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { operandA, operandB, err := popTwoIntegers(ctx) if err != nil { @@ -26,7 +26,7 @@ func Add(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation) (Vm // Sub subtracts two stack Items. // Returns an error if either items cannot be casted to an integer // or if integers cannot be subtracted together -func Sub(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation) (Vmstate, error) { +func Sub(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { operandA, operandB, err := popTwoIntegers(ctx) if err != nil { diff --git a/pkg/vm/vm_ops_stackmani.go b/pkg/vm/vm_ops_stackmani.go index 366beb15b..f5e2ddc24 100644 --- a/pkg/vm/vm_ops_stackmani.go +++ b/pkg/vm/vm_ops_stackmani.go @@ -7,7 +7,7 @@ import ( // Stack Manipulation Opcodes // PushNBytes will Read N Bytes from the script and push it onto the stack -func PushNBytes(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation) (Vmstate, error) { +func PushNBytes(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { val, err := ctx.ReadBytes(int(op)) if err != nil { From da27c2b3f0c8e32b5ff0b64f747da17c6f7c75d8 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Mon, 18 Mar 2019 21:15:09 +0000 Subject: [PATCH 40/81] [Stack] - Change RemoveCurrentContext for PopCurrentContext - Add CopTo method to stack --- pkg/vm/stack/invocationstack.go | 19 ++++++++++++------- pkg/vm/stack/stack.go | 8 ++++++++ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/pkg/vm/stack/invocationstack.go b/pkg/vm/stack/invocationstack.go index a3a66b8ec..49f058f62 100644 --- a/pkg/vm/stack/invocationstack.go +++ b/pkg/vm/stack/invocationstack.go @@ -29,11 +29,17 @@ func (i *Invocation) CurrentContext() (*Context, error) { return i.peekContext(0) } -// RemoveCurrentContext removes the context on the top of the invocation stack -// This is a convenience method for Pop -func (i *Invocation) RemoveCurrentContext() error { - _, err := i.Pop() - return err +// PopCurrentContext Pops a context item from the top of the stack +func (i *Invocation) PopCurrentContext() (*Context, error) { + item, err := i.Pop() + if err != nil { + return nil, err + } + ctx, err := item.Context() + if err != nil { + return nil, err + } + return ctx, err } // CallingContext will return the cntext item @@ -49,8 +55,7 @@ func (i *Invocation) CallingContext() (*Context, error) { // started the program func (i *Invocation) EntryContext() (*Context, error) { - // firstItemIndex refers to the first item - // that was popped on the stack + // firstItemIndex refers to the first item that was popped on the stack firstItemIndex := uint16(i.Len() - 1) // N.B. if this overflows because len is zero, then an error will be returned return i.peekContext(firstItemIndex) } diff --git a/pkg/vm/stack/stack.go b/pkg/vm/stack/stack.go index e857a9ada..8d1ac5b78 100644 --- a/pkg/vm/stack/stack.go +++ b/pkg/vm/stack/stack.go @@ -119,6 +119,14 @@ func (ras *RandomAccess) Peek(n uint16) (Item, error) { return ras.vals[index], nil } +// CopyTo will copy all of the stack items from `ras` into the stack that is passed as an argument +// XXX: once maxstacksize is implemented, we will return error if size goes over +// There will also be additional checks needed once stack isolation is added +func (ras *RandomAccess) CopyTo(stack *RandomAccess) error { + stack.vals = append(stack.vals, ras.vals...) + return nil +} + // Convenience Functions // PopInt will remove the last stack item that was added From 8809be183e265791c8a3cc8dd356f121caa2692a Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Mon, 18 Mar 2019 21:15:40 +0000 Subject: [PATCH 41/81] [VM] Add Result stack len check in simple run test --- pkg/vm/vm_test.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pkg/vm/vm_test.go b/pkg/vm/vm_test.go index 4ecc44317..adb8b5db3 100644 --- a/pkg/vm/vm_test.go +++ b/pkg/vm/vm_test.go @@ -86,6 +86,9 @@ func TestSimpleRun(t *testing.T) { _, err := vm.Run() assert.Nil(t, err) + // ResultStack should be nil + assert.Equal(t, -1, vm.ResultStack.Len()) + } // returns true if the value at the top of the evaluation stack is a integer @@ -108,8 +111,7 @@ func peakTopEstack(t *testing.T, vm *VM) stack.Item { return item } -// returns true if the total number of items on the evaluation stack -// is equal to value +// returns true if the total number of items on the evaluation stack is equal to value func EstackLen(t *testing.T, vm *VM, value int) bool { ctx, err := vm.InvocationStack.CurrentContext() assert.Nil(t, err) From a2bdc076d29d74eb72beed1cd59e1346cd9a11ab Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Mon, 18 Mar 2019 21:17:43 +0000 Subject: [PATCH 42/81] [VM] fix typo --- pkg/vm/vm.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index 1f9fa66df..e207a7ed6 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -30,7 +30,7 @@ func NewVM(script []byte) *VM { return v } -// Run loops over the current context by continuously steppping. +// Run loops over the current context by continuously stepping. // Run breaks; once step returns an error or any state that is not NONE func (v *VM) Run() (Vmstate, error) { for { From 9951f040993a7702c99d771bdfc71a3b6e8d0858 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Mon, 18 Mar 2019 21:33:07 +0000 Subject: [PATCH 43/81] [Stall] Change seconds to milliseconds in test --- pkg/peer/stall/stall_test.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pkg/peer/stall/stall_test.go b/pkg/peer/stall/stall_test.go index b86412b2a..4d5494e12 100644 --- a/pkg/peer/stall/stall_test.go +++ b/pkg/peer/stall/stall_test.go @@ -12,8 +12,8 @@ import ( func TestAddRemoveMessage(t *testing.T) { - responseTime := 2 * time.Second - tickerInterval := 1 * time.Second + responseTime := 2 * time.Millisecond + tickerInterval := 1 * time.Millisecond d := NewDetector(responseTime, tickerInterval) d.AddMessage(command.GetAddr) @@ -51,15 +51,15 @@ loop: } func TestDeadlineWorks(t *testing.T) { - responseTime := 2 * time.Second - tickerInterval := 1 * time.Second + responseTime := 2 * time.Millisecond + tickerInterval := 1 * time.Millisecond d := NewDetector(responseTime, tickerInterval) mp := mockPeer{online: true, detector: d, lock: new(sync.RWMutex)} go mp.loop() d.AddMessage(command.GetAddr) - time.Sleep(responseTime + 1*time.Second) + time.Sleep(responseTime + 1*time.Millisecond) k := make(map[command.Type]time.Time) d.lock.RLock() @@ -70,12 +70,12 @@ func TestDeadlineWorks(t *testing.T) { mp.lock.RUnlock() } func TestDeadlineShouldNotBeEmpty(t *testing.T) { - responseTime := 10 * time.Second - tickerInterval := 1 * time.Second + responseTime := 10 * time.Millisecond + tickerInterval := 1 * time.Millisecond d := NewDetector(responseTime, tickerInterval) d.AddMessage(command.GetAddr) - time.Sleep(1 * time.Second) + time.Sleep(1 * time.Millisecond) k := make(map[command.Type]time.Time) d.lock.RLock() From bab5d370bba6be834477f1a49b8ed835239bed75 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Mon, 18 Mar 2019 21:58:51 +0000 Subject: [PATCH 44/81] Interop --- .../csharp-interop-test/push/pushbytes1.json | 81 +++++++++++++++++++ pkg/vm/csharp-interop-test/readme.md | 6 ++ pkg/vm/csharp-interop-test/testStruct.go | 26 ++++++ 3 files changed, 113 insertions(+) create mode 100644 pkg/vm/csharp-interop-test/push/pushbytes1.json create mode 100644 pkg/vm/csharp-interop-test/readme.md create mode 100644 pkg/vm/csharp-interop-test/testStruct.go diff --git a/pkg/vm/csharp-interop-test/push/pushbytes1.json b/pkg/vm/csharp-interop-test/push/pushbytes1.json new file mode 100644 index 000000000..474944423 --- /dev/null +++ b/pkg/vm/csharp-interop-test/push/pushbytes1.json @@ -0,0 +1,81 @@ +{ + "category": "Push", + "name": "PUSHBYTES1", + "tests": + [ + { + "name": "Good definition", + "script": "0x0100", + "steps": + [ + { + "actions": + [ + "StepInto" + ], + "result": + { + "state": "Break", + "invocationStack": + [ + { + "scriptHash": "0xFBC22D517F38E7612798ECE8E5957CF6C41D8CAF", + "instructionPointer": 2, + "nextInstruction": "RET", + "evaluationStack": + [ + { + "type": "ByteArray", + "value": "0x00" + } + ] + } + ] + } + }, + { + "actions": + [ + "StepInto" + ], + "result": + { + "state": "Halt", + "resultStack": + [ + { + "type": "ByteArray", + "value": "0x00" + } + ] + } + } + ] + }, + { + "name": "Wrong definition (without enough length)", + "script": "0x01", + "steps": + [ + { + "actions": + [ + "StepInto" + ], + "result": + { + "state": "Fault", + "invocationStack": + [ + { + "scriptHash": "0xC51B66BCED5E4491001BD702669770DCCF440982", + "instructionPointer": 1, + "nextInstruction": "RET" + } + ] + } + } + ] + } + ] +} \ No newline at end of file diff --git a/pkg/vm/csharp-interop-test/readme.md b/pkg/vm/csharp-interop-test/readme.md new file mode 100644 index 000000000..0e457c7c0 --- /dev/null +++ b/pkg/vm/csharp-interop-test/readme.md @@ -0,0 +1,6 @@ +## Package VM Interop + + +This package will use the tests in the neo-vm repo to test interopabilty + + diff --git a/pkg/vm/csharp-interop-test/testStruct.go b/pkg/vm/csharp-interop-test/testStruct.go new file mode 100644 index 000000000..c0da0112b --- /dev/null +++ b/pkg/vm/csharp-interop-test/testStruct.go @@ -0,0 +1,26 @@ +package csharpinterop + +// VMUnitTest is a struct for capturing the fields in the json files +type VMUnitTest struct { + Category string `json:"category"` + Name string `json:"name"` + Tests []struct { + Name string `json:"name"` + Script string `json:"script"` + Steps []struct { + Actions []string `json:"actions"` + Result struct { + State string `json:"state"` + InvocationStack []struct { + ScriptHash string `json:"scriptHash"` + InstructionPointer int `json:"instructionPointer"` + NextInstruction string `json:"nextInstruction"` + EvaluationStack []struct { + Type string `json:"type"` + Value string `json:"value"` + } `json:"evaluationStack"` + } `json:"invocationStack"` + } `json:"result"` + } `json:"steps"` + } `json:"tests"` +} From 24cd21bd8cb713e6ffd1d3c93bd2b1059275052c Mon Sep 17 00:00:00 2001 From: dauTT <30392990+dauTT@users.noreply.github.com> Date: Tue, 26 Mar 2019 22:19:41 +0100 Subject: [PATCH 45/81] VM:Implement THROW opcode (#219) [VM] - Changed vmstate from HALT to FAULT in Sub opcode - Implemented THROW opcode + tests - Renamed TestSimpleRun test to TestThrowIfNot --- pkg/vm/vm_ops.go | 1 + pkg/vm/vm_ops_exceptions.go | 6 ++++++ pkg/vm/vm_ops_maths.go | 2 +- pkg/vm/vm_test.go | 29 ++++++++++++++++++++++++++++- 4 files changed, 36 insertions(+), 2 deletions(-) diff --git a/pkg/vm/vm_ops.go b/pkg/vm/vm_ops.go index 39b796389..d8b032ebc 100644 --- a/pkg/vm/vm_ops.go +++ b/pkg/vm/vm_ops.go @@ -12,6 +12,7 @@ var opFunc = map[stack.Instruction]stackInfo{ stack.RET: RET, stack.EQUAL: EQUAL, stack.THROWIFNOT: THROWIFNOT, + stack.THROW: THROW, } func init() { diff --git a/pkg/vm/vm_ops_exceptions.go b/pkg/vm/vm_ops_exceptions.go index dd09cfb60..01dd74616 100644 --- a/pkg/vm/vm_ops_exceptions.go +++ b/pkg/vm/vm_ops_exceptions.go @@ -31,3 +31,9 @@ func THROWIFNOT(op stack.Instruction, ctx *stack.Context, istack *stack.Invocati } return NONE, nil } + +// THROW returns a FAULT VM state. This indicate that there is an error in the +// current context loaded program. +func THROW(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + return FAULT, errors.New("the execution of the script program end with an error") +} diff --git a/pkg/vm/vm_ops_maths.go b/pkg/vm/vm_ops_maths.go index a15596a30..612c7630c 100644 --- a/pkg/vm/vm_ops_maths.go +++ b/pkg/vm/vm_ops_maths.go @@ -34,7 +34,7 @@ func Sub(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rst } res, err := operandB.Sub(operandA) if err != nil { - return HALT, err + return FAULT, err } ctx.Estack.Push(res) diff --git a/pkg/vm/vm_test.go b/pkg/vm/vm_test.go index adb8b5db3..bc38bfaed 100644 --- a/pkg/vm/vm_test.go +++ b/pkg/vm/vm_test.go @@ -61,7 +61,7 @@ func TestPushAdd(t *testing.T) { } -func TestSimpleRun(t *testing.T) { +func TestThrowIfNot(t *testing.T) { // Program pushes 20 and 34 to the stack // Adds them together @@ -89,6 +89,33 @@ func TestSimpleRun(t *testing.T) { // ResultStack should be nil assert.Equal(t, -1, vm.ResultStack.Len()) + // InvocationStack should be empty + assert.Equal(t, 0, vm.InvocationStack.Len()) + +} + +func TestThrow(t *testing.T) { + + // Program pushes 20 to the stack + // exits with an error + + // Push(20) + // THROW + + builder := stack.NewBuilder() + builder.EmitInt(20).EmitOpcode(stack.THROW) + + // Pass program to VM + vm := NewVM(builder.Bytes()) + + // Runs vm with program + _, err := vm.Run() + assert.NotNil(t, err) + + ctx, err := vm.InvocationStack.CurrentContext() + assert.Equal(t, nil, err) + assert.Equal(t, 1, ctx.Estack.Len()) + assert.Equal(t, -1, ctx.Astack.Len()) } // returns true if the value at the top of the evaluation stack is a integer From d8e399f67d6188881090620b7507cf8659e8b99d Mon Sep 17 00:00:00 2001 From: dauTT <30392990+dauTT@users.noreply.github.com> Date: Wed, 27 Mar 2019 00:15:13 +0100 Subject: [PATCH 46/81] VM: Implement INC, DEC opcode (#231) [VM] - Implemented INC, DEC opcode --- pkg/vm/vm_ops.go | 2 ++ pkg/vm/vm_ops_maths.go | 52 +++++++++++++++++++++++++++++++++++++ pkg/vm/vm_ops_maths_test.go | 50 +++++++++++++++++++++++++++++++++++ 3 files changed, 104 insertions(+) diff --git a/pkg/vm/vm_ops.go b/pkg/vm/vm_ops.go index d8b032ebc..016c96389 100644 --- a/pkg/vm/vm_ops.go +++ b/pkg/vm/vm_ops.go @@ -5,6 +5,8 @@ import "github.com/CityOfZion/neo-go/pkg/vm/stack" type stackInfo func(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) var opFunc = map[stack.Instruction]stackInfo{ + stack.INC: Inc, + stack.DEC: Dec, stack.ADD: Add, stack.SUB: Sub, stack.PUSHBYTES1: PushNBytes, diff --git a/pkg/vm/vm_ops_maths.go b/pkg/vm/vm_ops_maths.go index 612c7630c..77a4c2622 100644 --- a/pkg/vm/vm_ops_maths.go +++ b/pkg/vm/vm_ops_maths.go @@ -1,6 +1,8 @@ package vm import ( + "math/big" + "github.com/CityOfZion/neo-go/pkg/vm/stack" ) @@ -42,6 +44,56 @@ func Sub(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rst return NONE, nil } +// Inc increments the stack Item's value by 1. +// Returns an error if the item cannot be casted to an integer +// or if 1 cannot be added to the item +func Inc(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + i, err := ctx.Estack.PopInt() + if err != nil { + return FAULT, err + } + + one, err := stack.NewInt(big.NewInt(1)) + if err != nil { + return FAULT, err + } + + res, err := i.Add(one) + if err != nil { + return FAULT, err + } + + ctx.Estack.Push(res) + + return NONE, nil +} + +// Dec decrements the stack Item's value by 1. +// Returns an error if the item cannot be casted to an integer +// or if 1 cannot be subtracted to the item +func Dec(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + i, err := ctx.Estack.PopInt() + if err != nil { + return FAULT, err + } + + one, err := stack.NewInt(big.NewInt(1)) + if err != nil { + return FAULT, err + } + + res, err := i.Sub(one) + if err != nil { + return FAULT, err + } + + ctx.Estack.Push(res) + + return NONE, nil +} + func popTwoIntegers(ctx *stack.Context) (*stack.Int, *stack.Int, error) { operandA, err := ctx.Estack.PopInt() if err != nil { diff --git a/pkg/vm/vm_ops_maths_test.go b/pkg/vm/vm_ops_maths_test.go index 4964e6923..f50c27916 100644 --- a/pkg/vm/vm_ops_maths_test.go +++ b/pkg/vm/vm_ops_maths_test.go @@ -8,6 +8,56 @@ import ( "github.com/stretchr/testify/assert" ) +func TestIncOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(20)) + if err != nil { + t.Fail() + } + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a) + + v.executeOp(stack.INC, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.PopInt() + if err != nil { + t.Fail() + } + + assert.Equal(t, int64(21), item.Value().Int64()) +} + +func TestDecOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(20)) + if err != nil { + t.Fail() + } + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a) + + v.executeOp(stack.DEC, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.PopInt() + if err != nil { + t.Fail() + } + + assert.Equal(t, int64(19), item.Value().Int64()) +} + func TestAddOp(t *testing.T) { v := VM{} From 14e0ab48dd471c2bf8e4465ab90de0524ac5161c Mon Sep 17 00:00:00 2001 From: dauTT <30392990+dauTT@users.noreply.github.com> Date: Thu, 28 Mar 2019 19:30:36 +0100 Subject: [PATCH 47/81] VM: Implemented SIGN, NEGATE opcode (#232) * Implemented SIGN, NEGATE opcode --- pkg/vm/vm_ops.go | 2 ++ pkg/vm/vm_ops_maths.go | 45 +++++++++++++++++++++++++++++++-- pkg/vm/vm_ops_maths_test.go | 50 +++++++++++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+), 2 deletions(-) diff --git a/pkg/vm/vm_ops.go b/pkg/vm/vm_ops.go index 016c96389..9ba44ccbd 100644 --- a/pkg/vm/vm_ops.go +++ b/pkg/vm/vm_ops.go @@ -7,6 +7,8 @@ type stackInfo func(op stack.Instruction, ctx *stack.Context, istack *stack.Invo var opFunc = map[stack.Instruction]stackInfo{ stack.INC: Inc, stack.DEC: Dec, + stack.SIGN: Sign, + stack.NEGATE: Negate, stack.ADD: Add, stack.SUB: Sub, stack.PUSHBYTES1: PushNBytes, diff --git a/pkg/vm/vm_ops_maths.go b/pkg/vm/vm_ops_maths.go index 77a4c2622..e15e25c3e 100644 --- a/pkg/vm/vm_ops_maths.go +++ b/pkg/vm/vm_ops_maths.go @@ -46,7 +46,7 @@ func Sub(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rst // Inc increments the stack Item's value by 1. // Returns an error if the item cannot be casted to an integer -// or if 1 cannot be added to the item +// or if 1 cannot be added to the item. func Inc(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { i, err := ctx.Estack.PopInt() @@ -71,7 +71,7 @@ func Inc(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rst // Dec decrements the stack Item's value by 1. // Returns an error if the item cannot be casted to an integer -// or if 1 cannot be subtracted to the item +// or if 1 cannot be subtracted to the item. func Dec(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { i, err := ctx.Estack.PopInt() @@ -94,6 +94,47 @@ func Dec(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rst return NONE, nil } +// Sign puts the sign of the top stack Item on top of the stack. +// If value is negative, put -1; +// If positive, put 1; +// If value is zero, put 0. +func Sign(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + i, err := ctx.Estack.PopInt() + if err != nil { + return FAULT, err + } + + s := int64(i.Value().Sign()) + sign, err := stack.NewInt(big.NewInt(s)) + if err != nil { + return FAULT, err + } + + ctx.Estack.Push(sign) + + return NONE, nil +} + +// Negate flips the sign of the stack Item. +func Negate(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + i, err := ctx.Estack.PopInt() + if err != nil { + return FAULT, err + } + + a := big.NewInt(0).Neg(i.Value()) + b, err := stack.NewInt(a) + if err != nil { + return FAULT, err + } + + ctx.Estack.Push(b) + + return NONE, nil +} + func popTwoIntegers(ctx *stack.Context) (*stack.Int, *stack.Int, error) { operandA, err := ctx.Estack.PopInt() if err != nil { diff --git a/pkg/vm/vm_ops_maths_test.go b/pkg/vm/vm_ops_maths_test.go index f50c27916..4324aec97 100644 --- a/pkg/vm/vm_ops_maths_test.go +++ b/pkg/vm/vm_ops_maths_test.go @@ -117,3 +117,53 @@ func TestSubOp(t *testing.T) { assert.Equal(t, int64(-10), item.Value().Int64()) } + +func TestSignOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(-20)) + if err != nil { + t.Fail() + } + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a) + + v.executeOp(stack.SIGN, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.PopInt() + if err != nil { + t.Fail() + } + + assert.Equal(t, int64(-1), item.Value().Int64()) +} + +func TestNegateOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(-20)) + if err != nil { + t.Fail() + } + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a) + + v.executeOp(stack.NEGATE, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.PopInt() + if err != nil { + t.Fail() + } + + assert.Equal(t, int64(20), item.Value().Int64()) +} From afe670f17895dfbadfd9521d4f7e70418d68cde2 Mon Sep 17 00:00:00 2001 From: dauTT <30392990+dauTT@users.noreply.github.com> Date: Thu, 28 Mar 2019 20:26:55 +0100 Subject: [PATCH 48/81] VM: Implement, ABS, NOT opcode (#233) * Implemented, ABS, NOT opcode --- pkg/vm/stack/Int.go | 12 ++++++++++ pkg/vm/stack/boolean.go | 6 +++++ pkg/vm/stack/stack.go | 10 ++++++++ pkg/vm/vm_ops.go | 2 ++ pkg/vm/vm_ops_maths.go | 33 ++++++++++++++++++++++++++ pkg/vm/vm_ops_maths_test.go | 47 +++++++++++++++++++++++++++++++++++++ 6 files changed, 110 insertions(+) diff --git a/pkg/vm/stack/Int.go b/pkg/vm/stack/Int.go index ac9a4cdba..ebd83cea1 100644 --- a/pkg/vm/stack/Int.go +++ b/pkg/vm/stack/Int.go @@ -92,3 +92,15 @@ func (i *Int) Boolean() (*Boolean, error) { func (i *Int) Value() *big.Int { return i.val } + +// Abs returns a stack integer whose underlying value is +// the absolute value of the original stack integer. +func (i *Int) Abs() (*Int, error) { + a := big.NewInt(0).Abs(i.Value()) + b, err := NewInt(a) + if err != nil { + return nil, err + } + + return b, nil +} diff --git a/pkg/vm/stack/boolean.go b/pkg/vm/stack/boolean.go index 66e3647e5..93fabd84e 100644 --- a/pkg/vm/stack/boolean.go +++ b/pkg/vm/stack/boolean.go @@ -24,3 +24,9 @@ func (b *Boolean) Boolean() (*Boolean, error) { func (b *Boolean) Value() bool { return b.val } + +// Not returns a Boolean whose underlying value is flipped. +// If the value is True, it is flipped to False and viceversa +func (b *Boolean) Not() *Boolean { + return NewBoolean(!b.Value()) +} diff --git a/pkg/vm/stack/stack.go b/pkg/vm/stack/stack.go index 8d1ac5b78..c832a9f79 100644 --- a/pkg/vm/stack/stack.go +++ b/pkg/vm/stack/stack.go @@ -148,3 +148,13 @@ func (ras *RandomAccess) PopByteArray() (*ByteArray, error) { } return item.ByteArray() } + +// PopBoolean will remove the last stack item that was added +// and cast it to a Boolean. +func (ras *RandomAccess) PopBoolean() (*Boolean, error) { + item, err := ras.Pop() + if err != nil { + return nil, err + } + return item.Boolean() +} diff --git a/pkg/vm/vm_ops.go b/pkg/vm/vm_ops.go index 9ba44ccbd..135936e05 100644 --- a/pkg/vm/vm_ops.go +++ b/pkg/vm/vm_ops.go @@ -7,6 +7,8 @@ type stackInfo func(op stack.Instruction, ctx *stack.Context, istack *stack.Invo var opFunc = map[stack.Instruction]stackInfo{ stack.INC: Inc, stack.DEC: Dec, + stack.ABS: Abs, + stack.NOT: Not, stack.SIGN: Sign, stack.NEGATE: Negate, stack.ADD: Add, diff --git a/pkg/vm/vm_ops_maths.go b/pkg/vm/vm_ops_maths.go index e15e25c3e..cbd82d670 100644 --- a/pkg/vm/vm_ops_maths.go +++ b/pkg/vm/vm_ops_maths.go @@ -94,6 +94,39 @@ func Dec(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rst return NONE, nil } +// Abs pops an integer off of the stack and pushes its absolute value onto the stack. +// Returns an error if the popped value is not an integer or if the absolute value cannot be taken +func Abs(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + i, err := ctx.Estack.PopInt() + if err != nil { + return FAULT, err + } + + a, err := i.Abs() + if err != nil { + return FAULT, err + } + + ctx.Estack.Push(a) + + return NONE, nil +} + +// Not flips the stack Item's value. +// If the value is True, it is flipped to False and viceversa. +func Not(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + b, err := ctx.Estack.PopBoolean() + if err != nil { + return FAULT, err + } + + ctx.Estack.Push(b.Not()) + + return NONE, nil +} + // Sign puts the sign of the top stack Item on top of the stack. // If value is negative, put -1; // If positive, put 1; diff --git a/pkg/vm/vm_ops_maths_test.go b/pkg/vm/vm_ops_maths_test.go index 4324aec97..68f7ae860 100644 --- a/pkg/vm/vm_ops_maths_test.go +++ b/pkg/vm/vm_ops_maths_test.go @@ -118,6 +118,53 @@ func TestSubOp(t *testing.T) { } +func TestAbsOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(-20)) + if err != nil { + t.Fail() + } + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a) + + v.executeOp(stack.ABS, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.PopInt() + if err != nil { + t.Fail() + } + + assert.Equal(t, int64(20), item.Value().Int64()) +} + +func TestNotOp(t *testing.T) { + + v := VM{} + + b := stack.NewBoolean(false) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(b) + + v.executeOp(stack.NOT, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.PopBoolean() + if err != nil { + t.Fail() + } + + assert.Equal(t, true, item.Value()) +} + func TestSignOp(t *testing.T) { v := VM{} From 139b770712246e9a630c9da950d5fbcf0395e73d Mon Sep 17 00:00:00 2001 From: dauTT <30392990+dauTT@users.noreply.github.com> Date: Thu, 28 Mar 2019 20:47:47 +0100 Subject: [PATCH 49/81] Implemented NZ, MUL opcode (#235) --- pkg/vm/vm_ops.go | 2 ++ pkg/vm/vm_ops_maths.go | 41 +++++++++++++++++++++++++++ pkg/vm/vm_ops_maths_test.go | 56 +++++++++++++++++++++++++++++++++++++ 3 files changed, 99 insertions(+) diff --git a/pkg/vm/vm_ops.go b/pkg/vm/vm_ops.go index 135936e05..5b199baa1 100644 --- a/pkg/vm/vm_ops.go +++ b/pkg/vm/vm_ops.go @@ -7,6 +7,8 @@ type stackInfo func(op stack.Instruction, ctx *stack.Context, istack *stack.Invo var opFunc = map[stack.Instruction]stackInfo{ stack.INC: Inc, stack.DEC: Dec, + stack.NZ: Nz, + stack.MUL: Mul, stack.ABS: Abs, stack.NOT: Not, stack.SIGN: Sign, diff --git a/pkg/vm/vm_ops_maths.go b/pkg/vm/vm_ops_maths.go index cbd82d670..65d6b4af1 100644 --- a/pkg/vm/vm_ops_maths.go +++ b/pkg/vm/vm_ops_maths.go @@ -94,6 +94,47 @@ func Dec(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rst return NONE, nil } +// Nz pops an integer from the stack. +// Then pushes a boolean to the stack which evaluates to true +// iff the integer was not zero. +// Returns an error if the popped item cannot be casted to an integer +// or if we cannot create a boolean. +func Nz(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + i, err := ctx.Estack.PopInt() + if err != nil { + return FAULT, err + } + + b, err := i.Boolean() + if err != nil { + return FAULT, err + } + + ctx.Estack.Push(b) + + return NONE, nil +} + +// Mul multiplies two stack Items together. +// Returns an error if either items cannot be casted to an integer +// or if integers cannot be multiplied together. +func Mul(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + operandA, operandB, err := popTwoIntegers(ctx) + if err != nil { + return FAULT, err + } + res, err := operandA.Mul(operandB) + if err != nil { + return FAULT, err + } + + ctx.Estack.Push(res) + + return NONE, nil +} + // Abs pops an integer off of the stack and pushes its absolute value onto the stack. // Returns an error if the popped value is not an integer or if the absolute value cannot be taken func Abs(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { diff --git a/pkg/vm/vm_ops_maths_test.go b/pkg/vm/vm_ops_maths_test.go index 68f7ae860..700f17bcb 100644 --- a/pkg/vm/vm_ops_maths_test.go +++ b/pkg/vm/vm_ops_maths_test.go @@ -118,6 +118,62 @@ func TestSubOp(t *testing.T) { } +func TestNzOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(20)) + if err != nil { + t.Fail() + } + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a) + + v.executeOp(stack.NZ, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.PopBoolean() + if err != nil { + t.Fail() + } + + assert.Equal(t, true, item.Value()) + +} + +func TestMulOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(20)) + if err != nil { + t.Fail() + } + b, err := stack.NewInt(big.NewInt(20)) + if err != nil { + t.Fail() + } + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b) + + v.executeOp(stack.MUL, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.PopInt() + if err != nil { + t.Fail() + } + + assert.Equal(t, int64(400), item.Value().Int64()) + +} + func TestAbsOp(t *testing.T) { v := VM{} From 84b4b41288113fa613ff9bb7e51469fc49a0c820 Mon Sep 17 00:00:00 2001 From: dauTT <30392990+dauTT@users.noreply.github.com> Date: Thu, 28 Mar 2019 21:32:32 +0100 Subject: [PATCH 50/81] Implemented DIV, MOD opcode (#237) --- pkg/vm/stack/Int.go | 7 +++++ pkg/vm/vm_ops.go | 2 ++ pkg/vm/vm_ops_maths.go | 38 +++++++++++++++++++++++ pkg/vm/vm_ops_maths_test.go | 60 +++++++++++++++++++++++++++++++++++-- 4 files changed, 105 insertions(+), 2 deletions(-) diff --git a/pkg/vm/stack/Int.go b/pkg/vm/stack/Int.go index ebd83cea1..52e6240d4 100644 --- a/pkg/vm/stack/Int.go +++ b/pkg/vm/stack/Int.go @@ -46,6 +46,13 @@ func (i *Int) Mul(s *Int) (*Int, error) { }, nil } +// Div will divide one stackInteger by an other. +func (i *Int) Div(s *Int) (*Int, error) { + return &Int{ + val: new(big.Int).Div(i.val, s.val), + }, nil +} + // Mod will take the mod of two stackIntegers together func (i *Int) Mod(s *Int) (*Int, error) { return &Int{ diff --git a/pkg/vm/vm_ops.go b/pkg/vm/vm_ops.go index 5b199baa1..238d300e0 100644 --- a/pkg/vm/vm_ops.go +++ b/pkg/vm/vm_ops.go @@ -7,6 +7,8 @@ type stackInfo func(op stack.Instruction, ctx *stack.Context, istack *stack.Invo var opFunc = map[stack.Instruction]stackInfo{ stack.INC: Inc, stack.DEC: Dec, + stack.DIV: Div, + stack.MOD: Mod, stack.NZ: Nz, stack.MUL: Mul, stack.ABS: Abs, diff --git a/pkg/vm/vm_ops_maths.go b/pkg/vm/vm_ops_maths.go index 65d6b4af1..345092eea 100644 --- a/pkg/vm/vm_ops_maths.go +++ b/pkg/vm/vm_ops_maths.go @@ -94,6 +94,44 @@ func Dec(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rst return NONE, nil } +// Div divides one stack Item by an other. +// Returns an error if either items cannot be casted to an integer +// or if the division of the integers cannot be performed. +func Div(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + operandA, operandB, err := popTwoIntegers(ctx) + if err != nil { + return FAULT, err + } + res, err := operandB.Div(operandA) + if err != nil { + return FAULT, err + } + + ctx.Estack.Push(res) + + return NONE, nil +} + +// Mod returns the mod of two stack Items. +// Returns an error if either items cannot be casted to an integer +// or if the mode of the integers cannot be performed. +func Mod(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + operandA, operandB, err := popTwoIntegers(ctx) + if err != nil { + return FAULT, err + } + res, err := operandB.Mod(operandA) + if err != nil { + return FAULT, err + } + + ctx.Estack.Push(res) + + return NONE, nil +} + // Nz pops an integer from the stack. // Then pushes a boolean to the stack which evaluates to true // iff the integer was not zero. diff --git a/pkg/vm/vm_ops_maths_test.go b/pkg/vm/vm_ops_maths_test.go index 700f17bcb..613ddefea 100644 --- a/pkg/vm/vm_ops_maths_test.go +++ b/pkg/vm/vm_ops_maths_test.go @@ -118,6 +118,64 @@ func TestSubOp(t *testing.T) { } +func TestDivOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(10)) + if err != nil { + t.Fail() + } + b, err := stack.NewInt(big.NewInt(4)) + if err != nil { + t.Fail() + } + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b) + + v.executeOp(stack.DIV, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.PopInt() + if err != nil { + t.Fail() + } + + assert.Equal(t, int64(2), item.Value().Int64()) +} + +func TestModOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(15)) + if err != nil { + t.Fail() + } + b, err := stack.NewInt(big.NewInt(4)) + if err != nil { + t.Fail() + } + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b) + + v.executeOp(stack.MOD, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.PopInt() + if err != nil { + t.Fail() + } + + assert.Equal(t, int64(3), item.Value().Int64()) +} + func TestNzOp(t *testing.T) { v := VM{} @@ -141,7 +199,6 @@ func TestNzOp(t *testing.T) { } assert.Equal(t, true, item.Value()) - } func TestMulOp(t *testing.T) { @@ -171,7 +228,6 @@ func TestMulOp(t *testing.T) { } assert.Equal(t, int64(400), item.Value().Int64()) - } func TestAbsOp(t *testing.T) { From 955bb373fc4a8b0212df66255a42df059ee59797 Mon Sep 17 00:00:00 2001 From: dauTT <30392990+dauTT@users.noreply.github.com> Date: Thu, 28 Mar 2019 23:28:20 +0100 Subject: [PATCH 51/81] Implemented SHL, SHR opcode (#250) --- pkg/vm/vm_ops.go | 2 ++ pkg/vm/vm_ops_maths.go | 40 +++++++++++++++++++++ pkg/vm/vm_ops_maths_test.go | 70 ++++++++++++++++++++++++++++++++++++- 3 files changed, 111 insertions(+), 1 deletion(-) diff --git a/pkg/vm/vm_ops.go b/pkg/vm/vm_ops.go index 238d300e0..cef46a199 100644 --- a/pkg/vm/vm_ops.go +++ b/pkg/vm/vm_ops.go @@ -5,6 +5,8 @@ import "github.com/CityOfZion/neo-go/pkg/vm/stack" type stackInfo func(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) var opFunc = map[stack.Instruction]stackInfo{ + stack.SHR: Shr, + stack.SHL: Shl, stack.INC: Inc, stack.DEC: Dec, stack.DIV: Div, diff --git a/pkg/vm/vm_ops_maths.go b/pkg/vm/vm_ops_maths.go index 345092eea..5cbe0552a 100644 --- a/pkg/vm/vm_ops_maths.go +++ b/pkg/vm/vm_ops_maths.go @@ -260,6 +260,46 @@ func popTwoIntegers(ctx *stack.Context) (*stack.Int, *stack.Int, error) { return operandA, operandB, nil } +// Shl pops two integers, a and b, off of the stack and pushes an integer to the stack +// whose value is the b's value shift to the left by a's value bits. +// Returns an error if either items cannot be casted to an integer +// or if the left shift operation cannot per performed with the two integer's value. +func Shl(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + a, b, err := popTwoIntegers(ctx) + if err != nil { + return FAULT, err + } + res, err := b.Lsh(a) + if err != nil { + return FAULT, err + } + + ctx.Estack.Push(res) + + return NONE, nil +} + +// Shr pops two integers, a and b, off of the stack and pushes an integer to the stack +// whose value is the b's value shift to the right by a's value bits. +// Returns an error if either items cannot be casted to an integer +// or if the right shift operation cannot per performed with the two integer's value. +func Shr(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + a, b, err := popTwoIntegers(ctx) + if err != nil { + return FAULT, err + } + res, err := b.Rsh(a) + if err != nil { + return FAULT, err + } + + ctx.Estack.Push(res) + + return NONE, nil +} + func popTwoByteArrays(ctx *stack.Context) (*stack.ByteArray, *stack.ByteArray, error) { // Pop first stack item and cast as byte array ba1, err := ctx.Estack.PopByteArray() diff --git a/pkg/vm/vm_ops_maths_test.go b/pkg/vm/vm_ops_maths_test.go index 613ddefea..b703751be 100644 --- a/pkg/vm/vm_ops_maths_test.go +++ b/pkg/vm/vm_ops_maths_test.go @@ -143,7 +143,7 @@ func TestDivOp(t *testing.T) { if err != nil { t.Fail() } - + assert.Equal(t, int64(2), item.Value().Int64()) } @@ -326,3 +326,71 @@ func TestNegateOp(t *testing.T) { assert.Equal(t, int64(20), item.Value().Int64()) } + +func TestShlOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(2)) + if err != nil { + t.Fail() + } + b, err := stack.NewInt(big.NewInt(3)) + if err != nil { + t.Fail() + } + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b) + + // b is the first item pop. + // a is the second item pop. + // we perform a.Lsh(b) and place + // the result on top of the evaluation + // stack + v.executeOp(stack.SHL, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.PopInt() + if err != nil { + t.Fail() + } + + assert.Equal(t, int64(16), item.Value().Int64()) +} + +func TestShrOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(10)) + if err != nil { + t.Fail() + } + b, err := stack.NewInt(big.NewInt(2)) + if err != nil { + t.Fail() + } + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b) + + // b is the first item pop. + // a is the second item pop. + // we perform a.Rsh(b) and place + // the result on top of the evaluation + // stack + v.executeOp(stack.SHR, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.PopInt() + if err != nil { + t.Fail() + } + + assert.Equal(t, int64(2), item.Value().Int64()) +} From d818c162976043aca12ac3eb8bfc8a1622960386 Mon Sep 17 00:00:00 2001 From: DauTT Date: Fri, 29 Mar 2019 17:43:16 +0100 Subject: [PATCH 52/81] Implemented MIN, MAX WITHIN opcode --- pkg/vm/stack/Int.go | 26 +++++++++++ pkg/vm/vm_ops.go | 3 ++ pkg/vm/vm_ops_maths.go | 91 +++++++++++++++++++++++++++++++------ pkg/vm/vm_ops_maths_test.go | 82 +++++++++++++++++++++++++++++++++ 4 files changed, 189 insertions(+), 13 deletions(-) diff --git a/pkg/vm/stack/Int.go b/pkg/vm/stack/Int.go index 52e6240d4..2c32a8b1d 100644 --- a/pkg/vm/stack/Int.go +++ b/pkg/vm/stack/Int.go @@ -111,3 +111,29 @@ func (i *Int) Abs() (*Int, error) { return b, nil } + +// Min returns the mininum between two integers. +func Min(a *Int, b *Int) *Int { + if a.Value().Cmp(b.Value()) == -1 { + return a + } + return b + +} + +// Max returns the maximun between two integers. +func Max(a *Int, b *Int) *Int { + if a.Value().Cmp(b.Value()) == 1 { + return a + } + return b +} + +// Within returns a bool whose value is true +// iff the value of the integer i is within the specified +// range [a,b) (left-inclusive). +func (i *Int) Within(a *Int, b *Int) bool { + // i >= a && i < b + return !(i.Value().Cmp(a.Value()) == -1) && i.Value().Cmp(b.Value()) == -1 + +} diff --git a/pkg/vm/vm_ops.go b/pkg/vm/vm_ops.go index cef46a199..bd2715d72 100644 --- a/pkg/vm/vm_ops.go +++ b/pkg/vm/vm_ops.go @@ -5,6 +5,9 @@ import "github.com/CityOfZion/neo-go/pkg/vm/stack" type stackInfo func(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) var opFunc = map[stack.Instruction]stackInfo{ + stack.MIN: Min, + stack.MAX: Max, + stack.WITHIN: Within, stack.SHR: Shr, stack.SHL: Shl, stack.INC: Inc, diff --git a/pkg/vm/vm_ops_maths.go b/pkg/vm/vm_ops_maths.go index 5cbe0552a..c1e362781 100644 --- a/pkg/vm/vm_ops_maths.go +++ b/pkg/vm/vm_ops_maths.go @@ -173,6 +173,54 @@ func Mul(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rst return NONE, nil } +// Min pops two integers, a and b, off of the stack and pushes an integer to the stack +// whose value is is the minum between a and b's value. +// Returns an error if either items cannot be casted to an integer +func Min(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + operandA, operandB, err := popTwoIntegers(ctx) + if err != nil { + return FAULT, err + } + res := stack.Min(operandA, operandB) + + ctx.Estack.Push(res) + + return NONE, nil +} + +// Max pops two integers, a and b, off of the stack and pushes an integer to the stack +// whose value is is the maximum between a and b's value. +// Returns an error if either items cannot be casted to an integer +func Max(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + operandA, operandB, err := popTwoIntegers(ctx) + if err != nil { + return FAULT, err + } + res := stack.Max(operandA, operandB) + + ctx.Estack.Push(res) + + return NONE, nil +} + +// Within pops three integers, a, b, and c off of the stack and pushes a boolean to the stack +// whose value is true iff c's value is within b's value (include) and a's value. +// Returns an error if at least one item cannot be casted to an boolean. +func Within(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + a, b, c, err := popThreeIntegers(ctx) + if err != nil { + return FAULT, err + } + res := stack.NewBoolean(c.Within(b, a)) + + ctx.Estack.Push(res) + + return NONE, nil +} + // Abs pops an integer off of the stack and pushes its absolute value onto the stack. // Returns an error if the popped value is not an integer or if the absolute value cannot be taken func Abs(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { @@ -247,19 +295,6 @@ func Negate(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, return NONE, nil } -func popTwoIntegers(ctx *stack.Context) (*stack.Int, *stack.Int, error) { - operandA, err := ctx.Estack.PopInt() - if err != nil { - return nil, nil, err - } - operandB, err := ctx.Estack.PopInt() - if err != nil { - return nil, nil, err - } - - return operandA, operandB, nil -} - // Shl pops two integers, a and b, off of the stack and pushes an integer to the stack // whose value is the b's value shift to the left by a's value bits. // Returns an error if either items cannot be casted to an integer @@ -300,6 +335,36 @@ func Shr(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rst return NONE, nil } +func popTwoIntegers(ctx *stack.Context) (*stack.Int, *stack.Int, error) { + operandA, err := ctx.Estack.PopInt() + if err != nil { + return nil, nil, err + } + operandB, err := ctx.Estack.PopInt() + if err != nil { + return nil, nil, err + } + + return operandA, operandB, nil +} + +func popThreeIntegers(ctx *stack.Context) (*stack.Int, *stack.Int, *stack.Int, error) { + operandA, err := ctx.Estack.PopInt() + if err != nil { + return nil, nil, nil, err + } + operandB, err := ctx.Estack.PopInt() + if err != nil { + return nil, nil, nil, err + } + operandC, err := ctx.Estack.PopInt() + if err != nil { + return nil, nil, nil, err + } + + return operandA, operandB, operandC, nil +} + func popTwoByteArrays(ctx *stack.Context) (*stack.ByteArray, *stack.ByteArray, error) { // Pop first stack item and cast as byte array ba1, err := ctx.Estack.PopByteArray() diff --git a/pkg/vm/vm_ops_maths_test.go b/pkg/vm/vm_ops_maths_test.go index b703751be..4671267f7 100644 --- a/pkg/vm/vm_ops_maths_test.go +++ b/pkg/vm/vm_ops_maths_test.go @@ -394,3 +394,85 @@ func TestShrOp(t *testing.T) { assert.Equal(t, int64(2), item.Value().Int64()) } + +func TestMinOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(10)) + assert.NoError(t, err) + + b, err := stack.NewInt(big.NewInt(2)) + assert.NoError(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b) + + v.executeOp(stack.MIN, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.PopInt() + assert.NoError(t, err) + + assert.Equal(t, int64(2), item.Value().Int64()) +} + +func TestMaxOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(10)) + assert.NoError(t, err) + + b, err := stack.NewInt(big.NewInt(2)) + assert.NoError(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b) + + v.executeOp(stack.MAX, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.PopInt() + assert.NoError(t, err) + + assert.Equal(t, int64(10), item.Value().Int64()) +} + +func TestWithinOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(5)) + assert.NoError(t, err) + + b, err := stack.NewInt(big.NewInt(2)) + assert.NoError(t, err) + + c, err := stack.NewInt(big.NewInt(10)) + assert.NoError(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b).Push(c) + + // c is the first item popped. + // b is the second item popped. + // a is the third item popped. + // if a is within [b, c) we place a boolean, + // whose value is true, on top of the evaluation + // stack. Otherwise we place a boolean with + // false value. + v.executeOp(stack.WITHIN, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.PopBoolean() + assert.NoError(t, err) + + assert.Equal(t, true, item.Value()) +} From 9402540c3a3ca471bfd6a301d6764f872cf18cc0 Mon Sep 17 00:00:00 2001 From: dauTT <30392990+dauTT@users.noreply.github.com> Date: Fri, 29 Mar 2019 20:35:16 +0100 Subject: [PATCH 53/81] Implemented LT, GT opcode (#259) * Implemented LT, GT opcode --- pkg/vm/stack/Int.go | 14 +++++++++ pkg/vm/vm_ops.go | 3 ++ pkg/vm/vm_ops_maths.go | 32 ++++++++++++++++++++ pkg/vm/vm_ops_maths_test.go | 58 +++++++++++++++++++++++++++++++++++++ 4 files changed, 107 insertions(+) diff --git a/pkg/vm/stack/Int.go b/pkg/vm/stack/Int.go index 52e6240d4..3226904ee 100644 --- a/pkg/vm/stack/Int.go +++ b/pkg/vm/stack/Int.go @@ -111,3 +111,17 @@ func (i *Int) Abs() (*Int, error) { return b, nil } + +// Lt returns a bool value from the comparison of two integers, a and b. +// value is true if a < b. +// value is false if a >= b. +func (i *Int) Lt(s *Int) bool { + return i.Value().Cmp(s.Value()) == -1 +} + +// Gt returns a bool value from the comparison of two integers, a and b. +// value is true if a > b. +// value is false if a <= b. +func (i *Int) Gt(s *Int) bool { + return i.Value().Cmp(s.Value()) == 1 +} diff --git a/pkg/vm/vm_ops.go b/pkg/vm/vm_ops.go index cef46a199..1854b1634 100644 --- a/pkg/vm/vm_ops.go +++ b/pkg/vm/vm_ops.go @@ -5,6 +5,9 @@ import "github.com/CityOfZion/neo-go/pkg/vm/stack" type stackInfo func(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) var opFunc = map[stack.Instruction]stackInfo{ + + stack.LT: Lt, + stack.GT: Gt, stack.SHR: Shr, stack.SHL: Shl, stack.INC: Inc, diff --git a/pkg/vm/vm_ops_maths.go b/pkg/vm/vm_ops_maths.go index 5cbe0552a..7abd73b9f 100644 --- a/pkg/vm/vm_ops_maths.go +++ b/pkg/vm/vm_ops_maths.go @@ -300,6 +300,38 @@ func Shr(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rst return NONE, nil } +// Lt pops two integers, a and b, off of the stack and pushes a boolean the stack +// whose value is true if a's value is less than b's value. +// Returns an error if either items cannot be casted to an integer +func Lt(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + operandA, operandB, err := popTwoIntegers(ctx) + if err != nil { + return FAULT, err + } + res := operandB.Lt(operandA) + + ctx.Estack.Push(stack.NewBoolean(res)) + + return NONE, nil +} + +// Gt pops two integers, a and b, off of the stack and pushes a boolean the stack +// whose value is true if a's value is greated than b's value. +// Returns an error if either items cannot be casted to an integer +func Gt(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + operandA, operandB, err := popTwoIntegers(ctx) + if err != nil { + return FAULT, err + } + res := operandB.Gt(operandA) + + ctx.Estack.Push(stack.NewBoolean(res)) + + return NONE, nil +} + func popTwoByteArrays(ctx *stack.Context) (*stack.ByteArray, *stack.ByteArray, error) { // Pop first stack item and cast as byte array ba1, err := ctx.Estack.PopByteArray() diff --git a/pkg/vm/vm_ops_maths_test.go b/pkg/vm/vm_ops_maths_test.go index b703751be..29d843999 100644 --- a/pkg/vm/vm_ops_maths_test.go +++ b/pkg/vm/vm_ops_maths_test.go @@ -394,3 +394,61 @@ func TestShrOp(t *testing.T) { assert.Equal(t, int64(2), item.Value().Int64()) } + +func TestLtOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(10)) + assert.NoError(t, err) + + b, err := stack.NewInt(big.NewInt(2)) + assert.NoError(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b) + + // b is the first item pop. + // a is the second item pop. + // we perform a < b and place + // the result on top of the evaluation + // stack + v.executeOp(stack.LT, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.PopBoolean() + assert.NoError(t, err) + + assert.Equal(t, false, item.Value()) +} + +func TestGtOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(10)) + assert.NoError(t, err) + + b, err := stack.NewInt(big.NewInt(2)) + assert.NoError(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b) + + // b is the first item pop. + // a is the second item pop. + // we perform a > b and place + // the result on top of the evaluation + // stack + v.executeOp(stack.GT, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.PopBoolean() + assert.NoError(t, err) + + assert.Equal(t, true, item.Value()) +} From 1fbc0af5db60316c058ff54881daabb094883872 Mon Sep 17 00:00:00 2001 From: dauTT <30392990+dauTT@users.noreply.github.com> Date: Fri, 29 Mar 2019 22:22:45 +0100 Subject: [PATCH 54/81] VM: Implement BOOLAND, BOOLOR opcode (#251) * Implemented BOOLAND, BOOLOR opcode --- pkg/vm/stack/boolean.go | 14 ++++++++++ pkg/vm/vm_ops.go | 3 ++- pkg/vm/vm_ops_maths.go | 51 +++++++++++++++++++++++++++++++++++++ pkg/vm/vm_ops_maths_test.go | 47 ++++++++++++++++++++++++++++++++++ 4 files changed, 114 insertions(+), 1 deletion(-) diff --git a/pkg/vm/stack/boolean.go b/pkg/vm/stack/boolean.go index 93fabd84e..5a5a93207 100644 --- a/pkg/vm/stack/boolean.go +++ b/pkg/vm/stack/boolean.go @@ -30,3 +30,17 @@ func (b *Boolean) Value() bool { func (b *Boolean) Not() *Boolean { return NewBoolean(!b.Value()) } + +// And returns a Boolean whose underlying value is obtained +// by applying the && operator to two Booleans' values. +func (b *Boolean) And(a *Boolean) *Boolean { + c := b.Value() && a.Value() + return NewBoolean(c) +} + +// Or returns a Boolean whose underlying value is obtained +// by applying the || operator to two Booleans' values. +func (b *Boolean) Or(a *Boolean) *Boolean { + c := b.Value() || a.Value() + return NewBoolean(c) +} diff --git a/pkg/vm/vm_ops.go b/pkg/vm/vm_ops.go index 1854b1634..b73b6aa31 100644 --- a/pkg/vm/vm_ops.go +++ b/pkg/vm/vm_ops.go @@ -5,7 +5,8 @@ import "github.com/CityOfZion/neo-go/pkg/vm/stack" type stackInfo func(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) var opFunc = map[stack.Instruction]stackInfo{ - + stack.BOOLAND: BoolAnd, + stack.BOOLOR: BoolOr, stack.LT: Lt, stack.GT: Gt, stack.SHR: Shr, diff --git a/pkg/vm/vm_ops_maths.go b/pkg/vm/vm_ops_maths.go index 7abd73b9f..d9a3a9bbc 100644 --- a/pkg/vm/vm_ops_maths.go +++ b/pkg/vm/vm_ops_maths.go @@ -206,6 +206,44 @@ func Not(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rst return NONE, nil } +// BoolAnd pops two booleans off of the stack and pushes a boolean to the stack +// whose value is true iff both booleans' values are true. +// Returns an error if either items cannot be casted to an boolean +func BoolAnd(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + bool1, bool2, err := popTwoBooleans(ctx) + if err != nil { + return FAULT, err + } + res := bool1.And(bool2) + if err != nil { + return FAULT, err + } + + ctx.Estack.Push(res) + + return NONE, nil +} + +// BoolOr pops two booleans off of the stack and pushes a boolean to the stack +// whose value is true iff at least one of the two booleans' value is true. +// Returns an error if either items cannot be casted to an boolean +func BoolOr(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + bool1, bool2, err := popTwoBooleans(ctx) + if err != nil { + return FAULT, err + } + res := bool1.Or(bool2) + if err != nil { + return FAULT, err + } + + ctx.Estack.Push(res) + + return NONE, nil +} + // Sign puts the sign of the top stack Item on top of the stack. // If value is negative, put -1; // If positive, put 1; @@ -345,3 +383,16 @@ func popTwoByteArrays(ctx *stack.Context) (*stack.ByteArray, *stack.ByteArray, e } return ba1, ba2, nil } + +func popTwoBooleans(ctx *stack.Context) (*stack.Boolean, *stack.Boolean, error) { + bool1, err := ctx.Estack.PopBoolean() + if err != nil { + return nil, nil, err + } + bool2, err := ctx.Estack.PopBoolean() + if err != nil { + return nil, nil, err + } + + return bool1, bool2, nil +} diff --git a/pkg/vm/vm_ops_maths_test.go b/pkg/vm/vm_ops_maths_test.go index 29d843999..3d0163f47 100644 --- a/pkg/vm/vm_ops_maths_test.go +++ b/pkg/vm/vm_ops_maths_test.go @@ -395,6 +395,52 @@ func TestShrOp(t *testing.T) { assert.Equal(t, int64(2), item.Value().Int64()) } +func TestBoolAndOp(t *testing.T) { + + v := VM{} + + a := stack.NewBoolean(true) + b := stack.NewBoolean(true) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b) + + v.executeOp(stack.BOOLAND, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.PopBoolean() + if err != nil { + t.Fail() + } + + assert.Equal(t, true, item.Value()) +} + + func TestBoolOrOp(t *testing.T) { + + v := VM{} + + a := stack.NewBoolean(false) + b := stack.NewBoolean(true) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b) + + v.executeOp(stack.BOOLOR, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.PopBoolean() + if err != nil { + t.Fail() + } + + assert.Equal(t, true, item.Value()) +} + func TestLtOp(t *testing.T) { v := VM{} @@ -452,3 +498,4 @@ func TestGtOp(t *testing.T) { assert.Equal(t, true, item.Value()) } + From 7bf4d691a9f651d12708b44657a5587a8979dc5f Mon Sep 17 00:00:00 2001 From: dauTT <30392990+dauTT@users.noreply.github.com> Date: Sat, 30 Mar 2019 16:01:06 +0100 Subject: [PATCH 55/81] Implemented NUMEQUAL, NUMNOTEQUAL opcode (#256) --- pkg/vm/vm_ops.go | 2 ++ pkg/vm/vm_ops_maths.go | 32 ++++++++++++++++++++ pkg/vm/vm_ops_maths_test.go | 58 +++++++++++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+) diff --git a/pkg/vm/vm_ops.go b/pkg/vm/vm_ops.go index b73b6aa31..d50609462 100644 --- a/pkg/vm/vm_ops.go +++ b/pkg/vm/vm_ops.go @@ -5,6 +5,8 @@ import "github.com/CityOfZion/neo-go/pkg/vm/stack" type stackInfo func(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) var opFunc = map[stack.Instruction]stackInfo{ + stack.NUMEQUAL: NumEqual, + stack.NUMNOTEQUAL: NumNotEqual, stack.BOOLAND: BoolAnd, stack.BOOLOR: BoolOr, stack.LT: Lt, diff --git a/pkg/vm/vm_ops_maths.go b/pkg/vm/vm_ops_maths.go index d9a3a9bbc..e23a7a4a0 100644 --- a/pkg/vm/vm_ops_maths.go +++ b/pkg/vm/vm_ops_maths.go @@ -173,6 +173,38 @@ func Mul(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rst return NONE, nil } +// NumEqual pops two Items off of the stack and pushes a boolean to the stack +// whose value is true iff the the two Items are equal. +// Returns an error if either items cannot be casted to an integer. +func NumEqual(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + operandA, operandB, err := popTwoIntegers(ctx) + if err != nil { + return FAULT, err + } + res := operandA.Equal(operandB) + + ctx.Estack.Push(stack.NewBoolean(res)) + + return NONE, nil +} + +// NumNotEqual pops two Items off of the stack and pushes a boolean to the stack +// whose value is true iff the two Items are not equal. +// Returns an error if either items cannot be casted to an integer. +func NumNotEqual(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + operandA, operandB, err := popTwoIntegers(ctx) + if err != nil { + return FAULT, err + } + res := operandA.Equal(operandB) + + ctx.Estack.Push(stack.NewBoolean(!res)) + + return NONE, nil +} + // Abs pops an integer off of the stack and pushes its absolute value onto the stack. // Returns an error if the popped value is not an integer or if the absolute value cannot be taken func Abs(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { diff --git a/pkg/vm/vm_ops_maths_test.go b/pkg/vm/vm_ops_maths_test.go index 3d0163f47..b8478f29b 100644 --- a/pkg/vm/vm_ops_maths_test.go +++ b/pkg/vm/vm_ops_maths_test.go @@ -277,6 +277,64 @@ func TestNotOp(t *testing.T) { assert.Equal(t, true, item.Value()) } +func TestNumEqual(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(6)) + if err != nil { + t.Fail() + } + b, err := stack.NewInt(big.NewInt(6)) + if err != nil { + t.Fail() + } + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b) + + v.executeOp(stack.NUMEQUAL, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.PopBoolean() + if err != nil { + t.Fail() + } + + assert.Equal(t, true, item.Value()) +} + +func TestNumNotEqual(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(5)) + if err != nil { + t.Fail() + } + b, err := stack.NewInt(big.NewInt(6)) + if err != nil { + t.Fail() + } + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b) + + v.executeOp(stack.NUMNOTEQUAL, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.PopBoolean() + if err != nil { + t.Fail() + } + + assert.Equal(t, true, item.Value()) +} + func TestSignOp(t *testing.T) { v := VM{} From c6cd0e0c21c0b4e5c61d882a1d9f7f63c3d4ce1c Mon Sep 17 00:00:00 2001 From: DauTT Date: Tue, 2 Apr 2019 22:38:41 +0200 Subject: [PATCH 56/81] Implemented Map Stack Item: 1) Added new file map.go, map_test.go 2) Added Map, Hash Method to Item interface 3) Implemented Hash Method for every stack items (Boolean, Array, Int, ...) --- pkg/vm/stack/Int.go | 11 ++- pkg/vm/stack/array.go | 23 +++++ pkg/vm/stack/boolean.go | 10 +++ pkg/vm/stack/bytearray.go | 12 +++ pkg/vm/stack/context.go | 7 ++ pkg/vm/stack/map.go | 166 ++++++++++++++++++++++++++++++++++++ pkg/vm/stack/map_test.go | 141 ++++++++++++++++++++++++++++++ pkg/vm/stack/stackitem.go | 12 +++ pkg/vm/stack/test_helper.go | 12 +++ 9 files changed, 393 insertions(+), 1 deletion(-) create mode 100644 pkg/vm/stack/map.go create mode 100644 pkg/vm/stack/map_test.go diff --git a/pkg/vm/stack/Int.go b/pkg/vm/stack/Int.go index 3226904ee..08e800e36 100644 --- a/pkg/vm/stack/Int.go +++ b/pkg/vm/stack/Int.go @@ -1,6 +1,9 @@ package stack -import "math/big" +import ( + "fmt" + "math/big" +) // Int represents an integer on the stack type Int struct { @@ -125,3 +128,9 @@ func (i *Int) Lt(s *Int) bool { func (i *Int) Gt(s *Int) bool { return i.Value().Cmp(s.Value()) == 1 } + +// Hash overrides the default abstract hash method. +func (i *Int) Hash() (string, error) { + data := fmt.Sprintf("%T %v", i, i.Value()) + return KeyGenerator([]byte(data)) +} diff --git a/pkg/vm/stack/array.go b/pkg/vm/stack/array.go index 96fe876a4..c11730b1d 100644 --- a/pkg/vm/stack/array.go +++ b/pkg/vm/stack/array.go @@ -1,5 +1,9 @@ package stack +import ( + "fmt" +) + // Array represents an Array of stackItems on the stack type Array struct { *abstractItem @@ -11,3 +15,22 @@ type Array struct { func (a *Array) Array() (*Array, error) { return a, nil } + +//Value returns the underlying Array's value +func (a *Array) Value() []Item { + return a.val +} + +// NewArray returns a new Array. +func NewArray(val []Item) *Array { + return &Array{ + &abstractItem{}, + val, + } +} + +// Hash overrides the default abstract hash method. +func (a *Array) Hash() (string, error) { + data := fmt.Sprintf("%T %v", a, a.Value()) + return KeyGenerator([]byte(data)) +} diff --git a/pkg/vm/stack/boolean.go b/pkg/vm/stack/boolean.go index 5a5a93207..5dc1e6e65 100644 --- a/pkg/vm/stack/boolean.go +++ b/pkg/vm/stack/boolean.go @@ -1,5 +1,9 @@ package stack +import ( + "fmt" +) + // Boolean represents a boolean value on the stack type Boolean struct { *abstractItem @@ -44,3 +48,9 @@ func (b *Boolean) Or(a *Boolean) *Boolean { c := b.Value() || a.Value() return NewBoolean(c) } + +// Hash overrides the default abstract hash method. +func (b *Boolean) Hash() (string, error) { + data := fmt.Sprintf("%T %v", b, b.Value()) + return KeyGenerator([]byte(data)) +} diff --git a/pkg/vm/stack/bytearray.go b/pkg/vm/stack/bytearray.go index 7d1c3c818..2b9c7f69f 100644 --- a/pkg/vm/stack/bytearray.go +++ b/pkg/vm/stack/bytearray.go @@ -3,6 +3,7 @@ package stack import ( "bytes" "errors" + "fmt" "math/big" "strconv" ) @@ -69,3 +70,14 @@ func reverse(b []byte) []byte { return dest } + +//Value returns the underlying ByteArray's value. +func (ba *ByteArray) Value() []byte { + return ba.val +} + +// Hash overrides the default abstract hash method. +func (ba *ByteArray) Hash() (string, error) { + data := fmt.Sprintf("%T %v", ba, ba.Value()) + return KeyGenerator([]byte(data)) +} diff --git a/pkg/vm/stack/context.go b/pkg/vm/stack/context.go index d381d74cb..0b8ed8bd2 100644 --- a/pkg/vm/stack/context.go +++ b/pkg/vm/stack/context.go @@ -3,6 +3,7 @@ package stack import ( "encoding/binary" "errors" + "fmt" ) // Context represent the current execution context of the VM. @@ -150,3 +151,9 @@ func (c *Context) readVarBytes() ([]byte, error) { } return c.ReadBytes(int(n)) } + +// Hash overrides the default abstract hash method. +func (c *Context) Hash() (string, error) { + data := c.String() + fmt.Sprintf(" %v-%v-%v-%v-%v", c.ip, c.prog, c.breakPoints, c.Estack, c.Astack) + return KeyGenerator([]byte(data)) +} diff --git a/pkg/vm/stack/map.go b/pkg/vm/stack/map.go new file mode 100644 index 000000000..b8e462c71 --- /dev/null +++ b/pkg/vm/stack/map.go @@ -0,0 +1,166 @@ +package stack + +import ( + "errors" + "fmt" + "sort" + + "github.com/CityOfZion/neo-go/pkg/crypto/hash" +) + +// Map represents a map of key, value pair on the stack. +// Both key and value are stack Items. +type Map struct { + *abstractItem + val map[Item]Item +} + +// NewMap returns a Map stack Item given +// a map whose keys and values are stack Items. +func NewMap(val map[Item]Item) (*Map, error) { + return &Map{ + abstractItem: &abstractItem{}, + val: val, + }, nil +} + +// Map will overwrite the default implementation +// to allow go to cast this item as an Map. +func (m *Map) Map() (*Map, error) { + return m, nil +} + +// Boolean overrides the default Boolean method +// to convert an Map into a Boolean StackItem +func (m *Map) Boolean() (*Boolean, error) { + return NewBoolean(true), nil +} + +// ContainsKey returns a boolean whose value is true +// iff the underlying map value contains the Item i +// as a key. +func (m *Map) ContainsKey(key Item) (*Boolean, error) { + for k := range m.Value() { + if ok, err := CompareHash(k, key); err != nil { + return nil, err + } else if ok.Value() == true { + return ok, nil + } + + } + return NewBoolean(false), nil +} + +// Value returns the underlying map's value +func (m *Map) Value() map[Item]Item { + return m.val +} + +// Remove removes the Item i from the +// underlying map's value. +func (m *Map) Remove(key Item) error { + var d Item + for k := range m.Value() { + if ok, err := CompareHash(k, key); err != nil { + return err + } else if ok.Value() == true { + d = k + } + + } + if d != nil { + delete(m.Value(), d) + } + return nil +} + +// Add inserts a new key, value pair of Items into +// the underlying map's value. +func (m *Map) Add(key Item, value Item) error { + for k := range m.Value() { + if ok, err := CompareHash(k, key); err != nil { + return err + } else if ok.Value() == true { + return errors.New("try to insert duplicate key! ") + } + } + m.Value()[key] = value + return nil +} + +// ValueOfKey tries to get the value of the key Item +// from the map's underlying value. +func (m *Map) ValueOfKey(key Item) (Item, error) { + for k, v := range m.Value() { + if ok, err := CompareHash(k, key); err != nil { + return nil, err + } else if ok.Value() == true { + return v, nil + } + + } + return nil, nil + +} + +// Clear empties the the underlying map's value. +func (m *Map) Clear() { + m.val = map[Item]Item{} +} + +// CompareHash compare the the Hashes of two items. +// If they are equal it returns a true boolean. Otherwise +// it returns false boolean. Item whose hashes are equal are +// to be considered equal. +func CompareHash(i1 Item, i2 Item) (*Boolean, error) { + hash1, err := i1.Hash() + if err != nil { + return nil, err + } + hash2, err := i2.Hash() + if err != nil { + return nil, err + } + if hash1 == hash2 { + return NewBoolean(true), nil + } + + return NewBoolean(false), nil +} + +// Hash overrides the default abstract hash method. +func (m *Map) Hash() (string, error) { + var hashSlice sort.StringSlice = []string{} + var data = fmt.Sprintf("%T ", m) + + for k, v := range m.Value() { + hk, err := k.Hash() + if err != nil { + return "", err + } + hv, err := v.Hash() + + if err != nil { + return "", err + } + + hashSlice = append(hashSlice, hk) + hashSlice = append(hashSlice, hv) + } + hashSlice.Sort() + + for _, h := range hashSlice { + data += h + } + + return KeyGenerator([]byte(data)) +} + +// KeyGenerator hashes a byte slice to obtain a unique identifier. +func KeyGenerator(data []byte) (string, error) { + h, err := hash.Sha256([]byte(data)) + if err != nil { + return "", err + } + return h.String(), nil +} diff --git a/pkg/vm/stack/map_test.go b/pkg/vm/stack/map_test.go new file mode 100644 index 000000000..2c2091dc4 --- /dev/null +++ b/pkg/vm/stack/map_test.go @@ -0,0 +1,141 @@ +package stack + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestMap(t *testing.T) { + // define Map m for testing + var a Item = testMakeStackInt(t, 10) + var b Item = NewBoolean(true) + var c Item = NewByteArray([]byte{1, 2, 34}) + var d Item = testMakeStackMap(t, map[Item]Item{ + a: c, + b: a, + }) + var e = NewContext([]byte{1, 2, 3, 4}) + var f = NewArray([]Item{a, b}) + + val := map[Item]Item{ + a: c, + b: a, + c: b, + d: a, + e: d, + f: e, + } + m := testMakeStackMap(t, val) + + // test ValueOfKey + valueA, _ := m.ValueOfKey(testMakeStackInt(t, 10)) + assert.Equal(t, c, valueA) + + valueB, _ := m.ValueOfKey(b) + assert.Equal(t, a, valueB) + + valueC, _ := m.ValueOfKey(NewByteArray([]byte{1, 2, 34})) + assert.Equal(t, b, valueC) + + valueD, _ := m.ValueOfKey(testMakeStackMap(t, map[Item]Item{ + b: a, + a: c, + })) + assert.Equal(t, a, valueD) + + valueE, _ := m.ValueOfKey(NewContext([]byte{1, 2, 3, 4})) + assert.Equal(t, d, valueE) + + valueF, _ := m.ValueOfKey(NewArray([]Item{a, b})) + assert.Equal(t, e, valueF) + + valueX, _ := m.ValueOfKey(NewByteArray([]byte{1, 2, 35})) + assert.NotEqual(t, b, valueX) + + checkA, err := m.ContainsKey(a) + assert.Nil(t, err) + assert.Equal(t, true, checkA.Value()) + + //test ContainsKey + checkB, err := m.ContainsKey(b) + assert.Nil(t, err) + assert.Equal(t, true, checkB.Value()) + + checkC, err := m.ContainsKey(c) + assert.Nil(t, err) + assert.Equal(t, true, checkC.Value()) + + checkD, err := m.ContainsKey(d) + assert.Nil(t, err) + assert.Equal(t, true, checkD.Value()) + + checkE, err := m.ContainsKey(e) + assert.Nil(t, err) + assert.Equal(t, true, checkE.Value()) + + //test CompareHash + val2 := map[Item]Item{ + f: e, + e: d, + d: a, + c: b, + b: a, + a: c, + } + m2 := testMakeStackMap(t, val2) + checkMap, err := CompareHash(m, m2) + assert.Nil(t, err) + assert.Equal(t, true, checkMap.Value()) + + checkBoolean, err := CompareHash(b, NewBoolean(true)) + assert.Nil(t, err) + assert.Equal(t, true, checkBoolean.Value()) + + checkByteArray, err := CompareHash(c, NewByteArray([]byte{1, 2, 34})) + assert.Nil(t, err) + assert.Equal(t, true, checkByteArray.Value()) + + checkContext, err := CompareHash(e, NewContext([]byte{1, 2, 3, 4})) + assert.Nil(t, err) + assert.Equal(t, true, checkContext.Value()) + + checkArray, err := CompareHash(f, NewArray([]Item{a, b})) + assert.Nil(t, err) + assert.Equal(t, true, checkArray.Value()) +} + +func TestMapAdd(t *testing.T) { + var a Item = testMakeStackInt(t, 10) + var b Item = NewBoolean(true) + var m = testMakeStackMap(t, map[Item]Item{}) + + err := m.Add(a, a) + assert.Nil(t, err) + err = m.Add(b, a) + assert.Nil(t, err) + + assert.Equal(t, 2, len(m.Value())) + + expected := testMakeStackMap(t, map[Item]Item{b: a, a: a}) + check, err := CompareHash(m, expected) + assert.Nil(t, err) + assert.Equal(t, true, check.Value()) + +} + +func TestMapRemove(t *testing.T) { + var a Item = testMakeStackInt(t, 10) + var b Item = NewBoolean(true) + var m = testMakeStackMap(t, map[Item]Item{b: a, a: a}) + + err := m.Remove(a) + assert.Nil(t, err) + assert.Equal(t, 1, len(m.Value())) + + expected := testMakeStackMap(t, map[Item]Item{b: a}) + check, err := CompareHash(m, expected) + assert.Nil(t, err) + assert.Equal(t, true, check.Value()) + +} diff --git a/pkg/vm/stack/stackitem.go b/pkg/vm/stack/stackitem.go index beed13363..d9990adcb 100644 --- a/pkg/vm/stack/stackitem.go +++ b/pkg/vm/stack/stackitem.go @@ -11,6 +11,8 @@ type Item interface { ByteArray() (*ByteArray, error) Array() (*Array, error) Context() (*Context, error) + Map() (*Map, error) + Hash() (string, error) } // Represents an `abstract` stack item @@ -47,3 +49,13 @@ func (a *abstractItem) Array() (*Array, error) { func (a *abstractItem) Context() (*Context, error) { return nil, errors.New("This stack item is not of type context") } + +// Context is the default implementation for a stackItem +// Implements Item interface +func (a *abstractItem) Map() (*Map, error) { + return nil, errors.New("This stack item is not a map") +} + +func (a *abstractItem) Hash() (string, error) { + return "", errors.New("This stack item need to override the Hash Method") +} diff --git a/pkg/vm/stack/test_helper.go b/pkg/vm/stack/test_helper.go index 15c6f87de..b2615141e 100644 --- a/pkg/vm/stack/test_helper.go +++ b/pkg/vm/stack/test_helper.go @@ -42,3 +42,15 @@ func testReadInt64(data []byte) int64 { binary.Read(buf, binary.LittleEndian, &ret) return ret } + +func testMakeStackMap(t *testing.T, m map[Item]Item) *Map { + a, err := NewMap(m) + assert.Nil(t, err) + return a +} + +func testArray(t *testing.T, m map[Item]Item) *Map { + a, err := NewMap(m) + assert.Nil(t, err) + return a +} From 045db09af24ddee70287105685f99d877dd45cf7 Mon Sep 17 00:00:00 2001 From: dauTT <30392990+dauTT@users.noreply.github.com> Date: Wed, 3 Apr 2019 00:43:52 +0200 Subject: [PATCH 57/81] Implemented LTE, GTE opcode (#260) * Implemented LTE, GTE opcode --- pkg/vm/stack/Int.go | 14 ++++++ pkg/vm/vm_ops.go | 2 + pkg/vm/vm_ops_maths.go | 52 +++++++++++++++++----- pkg/vm/vm_ops_maths_test.go | 89 ++++++++++++++++++++++++++++++------- 4 files changed, 131 insertions(+), 26 deletions(-) diff --git a/pkg/vm/stack/Int.go b/pkg/vm/stack/Int.go index 3226904ee..a10c099c7 100644 --- a/pkg/vm/stack/Int.go +++ b/pkg/vm/stack/Int.go @@ -100,6 +100,20 @@ func (i *Int) Value() *big.Int { return i.val } +// Lte returns a bool value from the comparison of two integers, a and b. +// value is true if a <= b. +// value is false if a > b. +func (i *Int) Lte(s *Int) bool { + return i.Value().Cmp(s.Value()) != 1 +} + +// Gte returns a bool value from the comparison of two integers, a and b. +// value is true if a >= b. +// value is false if a < b. +func (i *Int) Gte(s *Int) bool { + return i.Value().Cmp(s.Value()) != -1 +} + // Abs returns a stack integer whose underlying value is // the absolute value of the original stack integer. func (i *Int) Abs() (*Int, error) { diff --git a/pkg/vm/vm_ops.go b/pkg/vm/vm_ops.go index d50609462..0a9c6f173 100644 --- a/pkg/vm/vm_ops.go +++ b/pkg/vm/vm_ops.go @@ -10,7 +10,9 @@ var opFunc = map[stack.Instruction]stackInfo{ stack.BOOLAND: BoolAnd, stack.BOOLOR: BoolOr, stack.LT: Lt, + stack.LTE: Lte, stack.GT: Gt, + stack.GTE: Gte, stack.SHR: Shr, stack.SHL: Shl, stack.INC: Inc, diff --git a/pkg/vm/vm_ops_maths.go b/pkg/vm/vm_ops_maths.go index e23a7a4a0..9bab3e48e 100644 --- a/pkg/vm/vm_ops_maths.go +++ b/pkg/vm/vm_ops_maths.go @@ -317,17 +317,36 @@ func Negate(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, return NONE, nil } -func popTwoIntegers(ctx *stack.Context) (*stack.Int, *stack.Int, error) { - operandA, err := ctx.Estack.PopInt() - if err != nil { - return nil, nil, err - } - operandB, err := ctx.Estack.PopInt() - if err != nil { - return nil, nil, err - } +// Lte pops two integers, a and b, off of the stack and pushes a boolean the stack +// whose value is true if a's value is less than or equal to b's value. +// Returns an error if either items cannot be casted to an integer +func Lte(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { - return operandA, operandB, nil + operandA, operandB, err := popTwoIntegers(ctx) + if err != nil { + return FAULT, err + } + res := operandB.Lte(operandA) + + ctx.Estack.Push(stack.NewBoolean(res)) + + return NONE, nil +} + +// Gte pops two integers, a and b, off of the stack and pushes a boolean the stack +// whose value is true if a's value is greated than or equal to b's value. +// Returns an error if either items cannot be casted to an integer +func Gte(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + operandA, operandB, err := popTwoIntegers(ctx) + if err != nil { + return FAULT, err + } + res := operandB.Gte(operandA) + + ctx.Estack.Push(stack.NewBoolean(res)) + + return NONE, nil } // Shl pops two integers, a and b, off of the stack and pushes an integer to the stack @@ -402,6 +421,19 @@ func Gt(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rsta return NONE, nil } +func popTwoIntegers(ctx *stack.Context) (*stack.Int, *stack.Int, error) { + operandA, err := ctx.Estack.PopInt() + if err != nil { + return nil, nil, err + } + operandB, err := ctx.Estack.PopInt() + if err != nil { + return nil, nil, err + } + + return operandA, operandB, nil +} + func popTwoByteArrays(ctx *stack.Context) (*stack.ByteArray, *stack.ByteArray, error) { // Pop first stack item and cast as byte array ba1, err := ctx.Estack.PopByteArray() diff --git a/pkg/vm/vm_ops_maths_test.go b/pkg/vm/vm_ops_maths_test.go index b8478f29b..14f83b1ec 100644 --- a/pkg/vm/vm_ops_maths_test.go +++ b/pkg/vm/vm_ops_maths_test.go @@ -385,6 +385,64 @@ func TestNegateOp(t *testing.T) { assert.Equal(t, int64(20), item.Value().Int64()) } +func TestLteOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(10)) + assert.Nil(t, err) + + b, err := stack.NewInt(big.NewInt(10)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b) + + // b is the first item pop. + // a is the second item pop. + // we perform a <= b and place + // the result on top of the evaluation + // stack + v.executeOp(stack.LTE, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.PopBoolean() + assert.Nil(t, err) + + assert.Equal(t, true, item.Value()) +} + +func TestGteOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(10)) + assert.Nil(t, err) + + b, err := stack.NewInt(big.NewInt(2)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b) + + // b is the first item pop. + // a is the second item pop. + // we perform a >= b and place + // the result on top of the evaluation + // stack + v.executeOp(stack.GTE, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.PopBoolean() + assert.Nil(t, err) + + assert.Equal(t, true, item.Value()) +} + func TestShlOp(t *testing.T) { v := VM{} @@ -455,48 +513,48 @@ func TestShrOp(t *testing.T) { func TestBoolAndOp(t *testing.T) { - v := VM{} + v := VM{} - a := stack.NewBoolean(true) + a := stack.NewBoolean(true) b := stack.NewBoolean(true) - ctx := stack.NewContext([]byte{}) + ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a).Push(b) - v.executeOp(stack.BOOLAND, ctx) + v.executeOp(stack.BOOLAND, ctx) - // Stack should have one item + // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) - item, err := ctx.Estack.PopBoolean() + item, err := ctx.Estack.PopBoolean() if err != nil { t.Fail() } - assert.Equal(t, true, item.Value()) + assert.Equal(t, true, item.Value()) } - func TestBoolOrOp(t *testing.T) { +func TestBoolOrOp(t *testing.T) { - v := VM{} + v := VM{} - a := stack.NewBoolean(false) + a := stack.NewBoolean(false) b := stack.NewBoolean(true) - ctx := stack.NewContext([]byte{}) + ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a).Push(b) - v.executeOp(stack.BOOLOR, ctx) + v.executeOp(stack.BOOLOR, ctx) - // Stack should have one item + // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) - item, err := ctx.Estack.PopBoolean() + item, err := ctx.Estack.PopBoolean() if err != nil { t.Fail() } - assert.Equal(t, true, item.Value()) + assert.Equal(t, true, item.Value()) } func TestLtOp(t *testing.T) { @@ -556,4 +614,3 @@ func TestGtOp(t *testing.T) { assert.Equal(t, true, item.Value()) } - From de1c4e01a18a4b7d209669979d03892d95049cf6 Mon Sep 17 00:00:00 2001 From: DauTT Date: Thu, 4 Apr 2019 00:34:21 +0200 Subject: [PATCH 58/81] Implemented bitwise opcodes: 1) AND 2) XOR 3) OR 4) INVERT --- pkg/vm/stack/Int.go | 31 ++++++++ pkg/vm/vm_ops.go | 4 + pkg/vm/vm_ops_bitwise.go | 85 +++++++++++++++++++++ pkg/vm/vm_ops_bitwise_test.go | 137 ++++++++++++++++++++++++++++++++++ 4 files changed, 257 insertions(+) create mode 100644 pkg/vm/vm_ops_bitwise_test.go diff --git a/pkg/vm/stack/Int.go b/pkg/vm/stack/Int.go index a10c099c7..9bf2b82ba 100644 --- a/pkg/vm/stack/Int.go +++ b/pkg/vm/stack/Int.go @@ -139,3 +139,34 @@ func (i *Int) Lt(s *Int) bool { func (i *Int) Gt(s *Int) bool { return i.Value().Cmp(s.Value()) == 1 } + +// Invert returns an Integer whose underlying value is the bitwise complement +// of the original value. +func (i *Int) Invert() (*Int, error) { + res := new(big.Int).Not(i.Value()) + return NewInt(res) +} + +// And returns an Integer whose underlying value is the result of the +// application of the bitwise AND operator to the two original integers' +// values. +func (i *Int) And(s *Int) (*Int, error) { + res := new(big.Int).And(i.Value(), s.Value()) + return NewInt(res) +} + +// Or returns an Integer whose underlying value is the result of the +// application of the bitwise OR operator to the two original integers' +// values. +func (i *Int) Or(s *Int) (*Int, error) { + res := new(big.Int).Or(i.Value(), s.Value()) + return NewInt(res) +} + +// Xor returns an Integer whose underlying value is the result of the +// application of the bitwise XOR operator to the two original integers' +// values. +func (i *Int) Xor(s *Int) (*Int, error) { + res := new(big.Int).Xor(i.Value(), s.Value()) + return NewInt(res) +} diff --git a/pkg/vm/vm_ops.go b/pkg/vm/vm_ops.go index 0a9c6f173..5680f463d 100644 --- a/pkg/vm/vm_ops.go +++ b/pkg/vm/vm_ops.go @@ -5,6 +5,10 @@ import "github.com/CityOfZion/neo-go/pkg/vm/stack" type stackInfo func(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) var opFunc = map[stack.Instruction]stackInfo{ + stack.XOR: Xor, + stack.OR: Or, + stack.AND: And, + stack.INVERT: Invert, stack.NUMEQUAL: NumEqual, stack.NUMNOTEQUAL: NumNotEqual, stack.BOOLAND: BoolAnd, diff --git a/pkg/vm/vm_ops_bitwise.go b/pkg/vm/vm_ops_bitwise.go index 350543fa2..4e65cafd4 100644 --- a/pkg/vm/vm_ops_bitwise.go +++ b/pkg/vm/vm_ops_bitwise.go @@ -15,3 +15,88 @@ func EQUAL(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, r ctx.Estack.Push(itemA.Equals(itemB)) return NONE, nil } + +// Invert pops an integer x off of the stack and +// pushes an integer on the stack whose value +// is the bitwise complement of the value of x. +// Returns an error if the popped value is not an integer or +// if the bitwise complement cannot be taken. +func Invert(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + i, err := ctx.Estack.PopInt() + if err != nil { + return FAULT, err + } + + inv, err := i.Invert() + if err != nil { + return FAULT, err + } + + ctx.Estack.Push(inv) + + return NONE, nil +} + +// And pops two integer off of the stack and +// pushes an integer onto the stack whose value +// is the result of the application of the bitwise AND +// operator to the two original integers' values. +// Returns an error if either items cannot be casted to an integer. +func And(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + operandA, operandB, err := popTwoIntegers(ctx) + if err != nil { + return FAULT, err + } + res, err := operandA.And(operandB) + if err != nil { + return FAULT, err + } + + ctx.Estack.Push(res) + + return NONE, nil +} + +// Or pops two integer off of the stack and +// pushes an integer onto the stack whose value +// is the result of the application of the bitwise OR +// operator to the two original integers' values. +// Returns an error if either items cannot be casted to an integer. +func Or(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + operandA, operandB, err := popTwoIntegers(ctx) + if err != nil { + return FAULT, err + } + res, err := operandA.Or(operandB) + if err != nil { + return FAULT, err + } + + ctx.Estack.Push(res) + + return NONE, nil +} + +// Xor pops two integer off of the stack and +// pushes an integer onto the stack whose value +// is the result of the application of the bitwise XOR +// operator to the two original integers' values. +// Returns an error if either items cannot be casted to an integer. +func Xor(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + operandA, operandB, err := popTwoIntegers(ctx) + if err != nil { + return FAULT, err + } + res, err := operandA.Xor(operandB) + if err != nil { + return FAULT, err + } + + ctx.Estack.Push(res) + + return NONE, nil +} diff --git a/pkg/vm/vm_ops_bitwise_test.go b/pkg/vm/vm_ops_bitwise_test.go new file mode 100644 index 000000000..6e92ada2f --- /dev/null +++ b/pkg/vm/vm_ops_bitwise_test.go @@ -0,0 +1,137 @@ +package vm + +import ( + "math/big" + "testing" + + "github.com/CityOfZion/neo-go/pkg/vm/stack" + "github.com/stretchr/testify/assert" +) + +func TestInvertOp(t *testing.T) { + + v := VM{} + + // 0000 00110 = 5 + a, err := stack.NewInt(big.NewInt(5)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a) + + // 1111 11001 = -6 (two complement representation) + v.executeOp(stack.INVERT, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + assert.Equal(t, int64(-6), item.Value().Int64()) +} + +func TestAndOp(t *testing.T) { + + v := VM{} + + // 110001 = 49 + a, err := stack.NewInt(big.NewInt(49)) + assert.Nil(t, err) + + // 100011 = 35 + b, err := stack.NewInt(big.NewInt(35)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b) + + // 100001 = 33 + v.executeOp(stack.AND, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + assert.Equal(t, int64(33), item.Value().Int64()) +} + +func TestOrOp(t *testing.T) { + + v := VM{} + + // 110001 = 49 + a, err := stack.NewInt(big.NewInt(49)) + assert.Nil(t, err) + + // 100011 = 35 + b, err := stack.NewInt(big.NewInt(35)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b) + + // 110011 = 51 (49 OR 35) + v.executeOp(stack.OR, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + assert.Equal(t, int64(51), item.Value().Int64()) +} + +func TestXorOp(t *testing.T) { + + v := VM{} + + // 110001 = 49 + a, err := stack.NewInt(big.NewInt(49)) + assert.Nil(t, err) + + // 100011 = 35 + b, err := stack.NewInt(big.NewInt(35)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b) + + // 010010 = 18 (49 XOR 35) + v.executeOp(stack.XOR, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + assert.Equal(t, int64(18), item.Value().Int64()) +} + +func TestEqualOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(10)) + assert.Nil(t, err) + + b, err := stack.NewInt(big.NewInt(10)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b) + + v.executeOp(stack.EQUAL, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.PopBoolean() + assert.Nil(t, err) + + assert.Equal(t, true, item.Value()) +} From 2897c3cbc1b3a110ce15434403b5d5abd96fddf9 Mon Sep 17 00:00:00 2001 From: DauTT Date: Thu, 4 Apr 2019 22:46:06 +0200 Subject: [PATCH 59/81] Implemented crypto opcodes: 1) SHA1 2) SHA256 3) HASH160 4) HASH256 --- pkg/vm/stack/bytearray.go | 5 ++ pkg/vm/vm_ops.go | 4 ++ pkg/vm/vmopscrypto.go | 126 +++++++++++++++++++++++++++++++++++++ pkg/vm/vmopscrypto_test.go | 101 +++++++++++++++++++++++++++++ 4 files changed, 236 insertions(+) create mode 100644 pkg/vm/vmopscrypto.go create mode 100644 pkg/vm/vmopscrypto_test.go diff --git a/pkg/vm/stack/bytearray.go b/pkg/vm/stack/bytearray.go index 7d1c3c818..cd7a23398 100644 --- a/pkg/vm/stack/bytearray.go +++ b/pkg/vm/stack/bytearray.go @@ -69,3 +69,8 @@ func reverse(b []byte) []byte { return dest } + +// Value returns the underlying ByteArray's value. +func (ba ByteArray) Value() []byte { + return ba.val +} diff --git a/pkg/vm/vm_ops.go b/pkg/vm/vm_ops.go index 0a9c6f173..d87f830b5 100644 --- a/pkg/vm/vm_ops.go +++ b/pkg/vm/vm_ops.go @@ -5,6 +5,10 @@ import "github.com/CityOfZion/neo-go/pkg/vm/stack" type stackInfo func(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) var opFunc = map[stack.Instruction]stackInfo{ + stack.HASH256: HASH256, + stack.HASH160: HASH160, + stack.SHA256: SHA256, + stack.SHA1: SHA1, stack.NUMEQUAL: NumEqual, stack.NUMNOTEQUAL: NumNotEqual, stack.BOOLAND: BoolAnd, diff --git a/pkg/vm/vmopscrypto.go b/pkg/vm/vmopscrypto.go new file mode 100644 index 000000000..9094a6853 --- /dev/null +++ b/pkg/vm/vmopscrypto.go @@ -0,0 +1,126 @@ +package vm + +import ( + "crypto/sha1" + + "github.com/CityOfZion/neo-go/pkg/crypto/hash" + "github.com/CityOfZion/neo-go/pkg/vm/stack" +) + +// SHA1 pops an item off of the stack and +// pushes a bytearray onto the stack whose value +// is obtained by applying the sha1 algorithm to +// the corresponding bytearray representation of the item. +// Returns an error if the Pop method cannot be execute or +// the popped item does not have a concrete bytearray implementation. +func SHA1(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + i, err := ctx.Estack.Pop() + if err != nil { + return FAULT, err + } + + ba, err := i.ByteArray() + if err != nil { + return FAULT, err + } + + alg := sha1.New() + alg.Write(ba.Value()) + hash := alg.Sum(nil) + res := stack.NewByteArray(hash) + + ctx.Estack.Push(res) + + return NONE, nil +} + +// SHA256 pops an item off of the stack and +// pushes a bytearray onto the stack whose value +// is obtained by applying the Sha256 algorithm to +// the corresponding bytearray representation of the item. +// Returns an error if the Pop method cannot be execute or +// the popped item does not have a concrete bytearray implementation. +func SHA256(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + i, err := ctx.Estack.Pop() + if err != nil { + return FAULT, err + } + + ba, err := i.ByteArray() + if err != nil { + return FAULT, err + } + + hash, err := hash.Sha256(ba.Value()) + if err != nil { + return FAULT, err + } + + res := stack.NewByteArray(hash.Bytes()) + + ctx.Estack.Push(res) + + return NONE, nil +} + +// HASH160 pops an item off of the stack and +// pushes a bytearray onto the stack whose value +// is obtained by applying the Hash160 algorithm to +// the corresponding bytearray representation of the item. +// Returns an error if the Pop method cannot be execute or +// the popped item does not have a concrete bytearray implementation. +func HASH160(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + i, err := ctx.Estack.Pop() + if err != nil { + return FAULT, err + } + + ba, err := i.ByteArray() + if err != nil { + return FAULT, err + } + + hash, err := hash.Hash160(ba.Value()) + if err != nil { + return FAULT, err + } + + res := stack.NewByteArray(hash.Bytes()) + + ctx.Estack.Push(res) + + return NONE, nil +} + +// HASH256 pops an item off of the stack and +// pushes a bytearray onto the stack whose value +// is obtained by applying the Hash256 algorithm to +// the corresponding bytearray representation of the item. +// Returns an error if the Pop method cannot be execute or +// the popped item does not have a concrete bytearray implementation. +func HASH256(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + i, err := ctx.Estack.Pop() + if err != nil { + return FAULT, err + } + + ba, err := i.ByteArray() + if err != nil { + return FAULT, err + } + + hash, err := hash.DoubleSha256(ba.Value()) + if err != nil { + return FAULT, err + } + + res := stack.NewByteArray(hash.Bytes()) + + ctx.Estack.Push(res) + + return NONE, nil +} diff --git a/pkg/vm/vmopscrypto_test.go b/pkg/vm/vmopscrypto_test.go new file mode 100644 index 000000000..08e26a786 --- /dev/null +++ b/pkg/vm/vmopscrypto_test.go @@ -0,0 +1,101 @@ +package vm + +import ( + "encoding/hex" + "testing" + + "github.com/CityOfZion/neo-go/pkg/vm/stack" + "github.com/stretchr/testify/assert" +) + +func TestSha1Op(t *testing.T) { + + v := VM{} + + ba1 := stack.NewByteArray([]byte("this is test string")) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(ba1) + + v.executeOp(stack.SHA1, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.Pop() + assert.Nil(t, err) + + ba2, err := item.ByteArray() + assert.Nil(t, err) + + assert.Equal(t, "62d40fe74cf301cbfbe55c2679b96352449fb26d", hex.EncodeToString(ba2.Value())) +} + +func TestSha256Op(t *testing.T) { + + v := VM{} + + ba1 := stack.NewByteArray([]byte("this is test string")) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(ba1) + + v.executeOp(stack.SHA256, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.Pop() + assert.Nil(t, err) + + ba2, err := item.ByteArray() + assert.Nil(t, err) + + assert.Equal(t, "8e76c5b9e6be2559bedccbd0ff104ebe02358ba463a44a68e96caf55f9400de5", hex.EncodeToString(ba2.Value())) +} + +func TestHash160Op(t *testing.T) { + + v := VM{} + + ba1 := stack.NewByteArray([]byte("this is test string")) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(ba1) + + v.executeOp(stack.HASH160, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.Pop() + assert.Nil(t, err) + + ba2, err := item.ByteArray() + assert.Nil(t, err) + + assert.Equal(t, "e9c052b05a762ca9961a975db52e5417d99d958c", hex.EncodeToString(ba2.Value())) +} + +func TestHash256Op(t *testing.T) { + + v := VM{} + + ba1 := stack.NewByteArray([]byte("this is test string")) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(ba1) + + v.executeOp(stack.HASH256, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.Pop() + assert.Nil(t, err) + + ba2, err := item.ByteArray() + assert.Nil(t, err) + + assert.Equal(t, "90ef790ee2557a3f9a1ba0e6910a9ff0ea75af3767ea7380760d729ac9927a60", hex.EncodeToString(ba2.Value())) +} From 3c8448ed40fd77c913ab1b1f4bf7cf80ad82ed39 Mon Sep 17 00:00:00 2001 From: DauTT Date: Fri, 5 Apr 2019 20:34:02 +0200 Subject: [PATCH 60/81] Simplied code by using help method PopByteArray --- pkg/vm/vmopscrypto.go | 28 ++++------------------------ 1 file changed, 4 insertions(+), 24 deletions(-) diff --git a/pkg/vm/vmopscrypto.go b/pkg/vm/vmopscrypto.go index 9094a6853..e38ab7402 100644 --- a/pkg/vm/vmopscrypto.go +++ b/pkg/vm/vmopscrypto.go @@ -15,12 +15,7 @@ import ( // the popped item does not have a concrete bytearray implementation. func SHA1(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { - i, err := ctx.Estack.Pop() - if err != nil { - return FAULT, err - } - - ba, err := i.ByteArray() + ba, err := ctx.Estack.PopByteArray() if err != nil { return FAULT, err } @@ -43,12 +38,7 @@ func SHA1(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rs // the popped item does not have a concrete bytearray implementation. func SHA256(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { - i, err := ctx.Estack.Pop() - if err != nil { - return FAULT, err - } - - ba, err := i.ByteArray() + ba, err := ctx.Estack.PopByteArray() if err != nil { return FAULT, err } @@ -73,12 +63,7 @@ func SHA256(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, // the popped item does not have a concrete bytearray implementation. func HASH160(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { - i, err := ctx.Estack.Pop() - if err != nil { - return FAULT, err - } - - ba, err := i.ByteArray() + ba, err := ctx.Estack.PopByteArray() if err != nil { return FAULT, err } @@ -103,12 +88,7 @@ func HASH160(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, // the popped item does not have a concrete bytearray implementation. func HASH256(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { - i, err := ctx.Estack.Pop() - if err != nil { - return FAULT, err - } - - ba, err := i.ByteArray() + ba, err := ctx.Estack.PopByteArray() if err != nil { return FAULT, err } From 51f835172345ce15fab7410ef288c1a415ea21c1 Mon Sep 17 00:00:00 2001 From: DauTT Date: Fri, 5 Apr 2019 21:13:23 +0200 Subject: [PATCH 61/81] Used consistently assert.Nil for checking absence of error --- pkg/vm/vm_ops_maths_test.go | 209 ++++++++++++------------------------ 1 file changed, 67 insertions(+), 142 deletions(-) diff --git a/pkg/vm/vm_ops_maths_test.go b/pkg/vm/vm_ops_maths_test.go index d685d6cf0..14e8c88a3 100644 --- a/pkg/vm/vm_ops_maths_test.go +++ b/pkg/vm/vm_ops_maths_test.go @@ -13,9 +13,7 @@ func TestIncOp(t *testing.T) { v := VM{} a, err := stack.NewInt(big.NewInt(20)) - if err != nil { - t.Fail() - } + assert.Nil(t, err) ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a) @@ -26,9 +24,7 @@ func TestIncOp(t *testing.T) { assert.Equal(t, 1, ctx.Estack.Len()) item, err := ctx.Estack.PopInt() - if err != nil { - t.Fail() - } + assert.Nil(t, err) assert.Equal(t, int64(21), item.Value().Int64()) } @@ -38,9 +34,7 @@ func TestDecOp(t *testing.T) { v := VM{} a, err := stack.NewInt(big.NewInt(20)) - if err != nil { - t.Fail() - } + assert.Nil(t, err) ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a) @@ -51,9 +45,7 @@ func TestDecOp(t *testing.T) { assert.Equal(t, 1, ctx.Estack.Len()) item, err := ctx.Estack.PopInt() - if err != nil { - t.Fail() - } + assert.Nil(t, err) assert.Equal(t, int64(19), item.Value().Int64()) } @@ -63,13 +55,10 @@ func TestAddOp(t *testing.T) { v := VM{} a, err := stack.NewInt(big.NewInt(20)) - if err != nil { - t.Fail() - } + assert.Nil(t, err) + b, err := stack.NewInt(big.NewInt(23)) - if err != nil { - t.Fail() - } + assert.Nil(t, err) ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a).Push(b) @@ -80,9 +69,7 @@ func TestAddOp(t *testing.T) { assert.Equal(t, 1, ctx.Estack.Len()) item, err := ctx.Estack.PopInt() - if err != nil { - t.Fail() - } + assert.Nil(t, err) assert.Equal(t, int64(43), item.Value().Int64()) @@ -93,13 +80,10 @@ func TestSubOp(t *testing.T) { v := VM{} a, err := stack.NewInt(big.NewInt(30)) - if err != nil { - t.Fail() - } + assert.Nil(t, err) + b, err := stack.NewInt(big.NewInt(40)) - if err != nil { - t.Fail() - } + assert.Nil(t, err) ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a).Push(b) @@ -110,9 +94,7 @@ func TestSubOp(t *testing.T) { assert.Equal(t, 1, ctx.Estack.Len()) item, err := ctx.Estack.PopInt() - if err != nil { - t.Fail() - } + assert.Nil(t, err) assert.Equal(t, int64(-10), item.Value().Int64()) @@ -123,13 +105,10 @@ func TestDivOp(t *testing.T) { v := VM{} a, err := stack.NewInt(big.NewInt(10)) - if err != nil { - t.Fail() - } + assert.Nil(t, err) + b, err := stack.NewInt(big.NewInt(4)) - if err != nil { - t.Fail() - } + assert.Nil(t, err) ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a).Push(b) @@ -140,9 +119,7 @@ func TestDivOp(t *testing.T) { assert.Equal(t, 1, ctx.Estack.Len()) item, err := ctx.Estack.PopInt() - if err != nil { - t.Fail() - } + assert.Nil(t, err) assert.Equal(t, int64(2), item.Value().Int64()) } @@ -152,13 +129,10 @@ func TestModOp(t *testing.T) { v := VM{} a, err := stack.NewInt(big.NewInt(15)) - if err != nil { - t.Fail() - } + assert.Nil(t, err) + b, err := stack.NewInt(big.NewInt(4)) - if err != nil { - t.Fail() - } + assert.Nil(t, err) ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a).Push(b) @@ -169,9 +143,7 @@ func TestModOp(t *testing.T) { assert.Equal(t, 1, ctx.Estack.Len()) item, err := ctx.Estack.PopInt() - if err != nil { - t.Fail() - } + assert.Nil(t, err) assert.Equal(t, int64(3), item.Value().Int64()) } @@ -181,9 +153,7 @@ func TestNzOp(t *testing.T) { v := VM{} a, err := stack.NewInt(big.NewInt(20)) - if err != nil { - t.Fail() - } + assert.Nil(t, err) ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a) @@ -194,9 +164,7 @@ func TestNzOp(t *testing.T) { assert.Equal(t, 1, ctx.Estack.Len()) item, err := ctx.Estack.PopBoolean() - if err != nil { - t.Fail() - } + assert.Nil(t, err) assert.Equal(t, true, item.Value()) } @@ -206,13 +174,10 @@ func TestMulOp(t *testing.T) { v := VM{} a, err := stack.NewInt(big.NewInt(20)) - if err != nil { - t.Fail() - } + assert.Nil(t, err) + b, err := stack.NewInt(big.NewInt(20)) - if err != nil { - t.Fail() - } + assert.Nil(t, err) ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a).Push(b) @@ -223,9 +188,7 @@ func TestMulOp(t *testing.T) { assert.Equal(t, 1, ctx.Estack.Len()) item, err := ctx.Estack.PopInt() - if err != nil { - t.Fail() - } + assert.Nil(t, err) assert.Equal(t, int64(400), item.Value().Int64()) } @@ -235,9 +198,7 @@ func TestAbsOp(t *testing.T) { v := VM{} a, err := stack.NewInt(big.NewInt(-20)) - if err != nil { - t.Fail() - } + assert.Nil(t, err) ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a) @@ -248,9 +209,7 @@ func TestAbsOp(t *testing.T) { assert.Equal(t, 1, ctx.Estack.Len()) item, err := ctx.Estack.PopInt() - if err != nil { - t.Fail() - } + assert.Nil(t, err) assert.Equal(t, int64(20), item.Value().Int64()) } @@ -270,9 +229,7 @@ func TestNotOp(t *testing.T) { assert.Equal(t, 1, ctx.Estack.Len()) item, err := ctx.Estack.PopBoolean() - if err != nil { - t.Fail() - } + assert.Nil(t, err) assert.Equal(t, true, item.Value()) } @@ -282,13 +239,10 @@ func TestNumEqual(t *testing.T) { v := VM{} a, err := stack.NewInt(big.NewInt(6)) - if err != nil { - t.Fail() - } + assert.Nil(t, err) + b, err := stack.NewInt(big.NewInt(6)) - if err != nil { - t.Fail() - } + assert.Nil(t, err) ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a).Push(b) @@ -299,9 +253,7 @@ func TestNumEqual(t *testing.T) { assert.Equal(t, 1, ctx.Estack.Len()) item, err := ctx.Estack.PopBoolean() - if err != nil { - t.Fail() - } + assert.Nil(t, err) assert.Equal(t, true, item.Value()) } @@ -311,13 +263,10 @@ func TestNumNotEqual(t *testing.T) { v := VM{} a, err := stack.NewInt(big.NewInt(5)) - if err != nil { - t.Fail() - } + assert.Nil(t, err) + b, err := stack.NewInt(big.NewInt(6)) - if err != nil { - t.Fail() - } + assert.Nil(t, err) ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a).Push(b) @@ -328,9 +277,7 @@ func TestNumNotEqual(t *testing.T) { assert.Equal(t, 1, ctx.Estack.Len()) item, err := ctx.Estack.PopBoolean() - if err != nil { - t.Fail() - } + assert.Nil(t, err) assert.Equal(t, true, item.Value()) } @@ -340,9 +287,7 @@ func TestSignOp(t *testing.T) { v := VM{} a, err := stack.NewInt(big.NewInt(-20)) - if err != nil { - t.Fail() - } + assert.Nil(t, err) ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a) @@ -353,9 +298,7 @@ func TestSignOp(t *testing.T) { assert.Equal(t, 1, ctx.Estack.Len()) item, err := ctx.Estack.PopInt() - if err != nil { - t.Fail() - } + assert.Nil(t, err) assert.Equal(t, int64(-1), item.Value().Int64()) } @@ -365,9 +308,7 @@ func TestNegateOp(t *testing.T) { v := VM{} a, err := stack.NewInt(big.NewInt(-20)) - if err != nil { - t.Fail() - } + assert.Nil(t, err) ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a) @@ -378,9 +319,7 @@ func TestNegateOp(t *testing.T) { assert.Equal(t, 1, ctx.Estack.Len()) item, err := ctx.Estack.PopInt() - if err != nil { - t.Fail() - } + assert.Nil(t, err) assert.Equal(t, int64(20), item.Value().Int64()) } @@ -448,13 +387,10 @@ func TestShlOp(t *testing.T) { v := VM{} a, err := stack.NewInt(big.NewInt(2)) - if err != nil { - t.Fail() - } + assert.Nil(t, err) + b, err := stack.NewInt(big.NewInt(3)) - if err != nil { - t.Fail() - } + assert.Nil(t, err) ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a).Push(b) @@ -470,9 +406,7 @@ func TestShlOp(t *testing.T) { assert.Equal(t, 1, ctx.Estack.Len()) item, err := ctx.Estack.PopInt() - if err != nil { - t.Fail() - } + assert.Nil(t, err) assert.Equal(t, int64(16), item.Value().Int64()) } @@ -482,13 +416,10 @@ func TestShrOp(t *testing.T) { v := VM{} a, err := stack.NewInt(big.NewInt(10)) - if err != nil { - t.Fail() - } + assert.Nil(t, err) + b, err := stack.NewInt(big.NewInt(2)) - if err != nil { - t.Fail() - } + assert.Nil(t, err) ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a).Push(b) @@ -504,9 +435,7 @@ func TestShrOp(t *testing.T) { assert.Equal(t, 1, ctx.Estack.Len()) item, err := ctx.Estack.PopInt() - if err != nil { - t.Fail() - } + assert.Nil(t, err) assert.Equal(t, int64(2), item.Value().Int64()) } @@ -527,9 +456,7 @@ func TestBoolAndOp(t *testing.T) { assert.Equal(t, 1, ctx.Estack.Len()) item, err := ctx.Estack.PopBoolean() - if err != nil { - t.Fail() - } + assert.Nil(t, err) assert.Equal(t, true, item.Value()) } @@ -550,9 +477,7 @@ func TestBoolOrOp(t *testing.T) { assert.Equal(t, 1, ctx.Estack.Len()) item, err := ctx.Estack.PopBoolean() - if err != nil { - t.Fail() - } + assert.Nil(t, err) assert.Equal(t, true, item.Value()) } @@ -562,10 +487,10 @@ func TestLtOp(t *testing.T) { v := VM{} a, err := stack.NewInt(big.NewInt(10)) - assert.NoError(t, err) + assert.Nil(t, err) b, err := stack.NewInt(big.NewInt(2)) - assert.NoError(t, err) + assert.Nil(t, err) ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a).Push(b) @@ -581,7 +506,7 @@ func TestLtOp(t *testing.T) { assert.Equal(t, 1, ctx.Estack.Len()) item, err := ctx.Estack.PopBoolean() - assert.NoError(t, err) + assert.Nil(t, err) assert.Equal(t, false, item.Value()) } @@ -591,10 +516,10 @@ func TestGtOp(t *testing.T) { v := VM{} a, err := stack.NewInt(big.NewInt(10)) - assert.NoError(t, err) + assert.Nil(t, err) b, err := stack.NewInt(big.NewInt(2)) - assert.NoError(t, err) + assert.Nil(t, err) ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a).Push(b) @@ -610,7 +535,7 @@ func TestGtOp(t *testing.T) { assert.Equal(t, 1, ctx.Estack.Len()) item, err := ctx.Estack.PopBoolean() - assert.NoError(t, err) + assert.Nil(t, err) assert.Equal(t, true, item.Value()) } @@ -620,10 +545,10 @@ func TestMinOp(t *testing.T) { v := VM{} a, err := stack.NewInt(big.NewInt(10)) - assert.NoError(t, err) + assert.Nil(t, err) b, err := stack.NewInt(big.NewInt(2)) - assert.NoError(t, err) + assert.Nil(t, err) ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a).Push(b) @@ -634,7 +559,7 @@ func TestMinOp(t *testing.T) { assert.Equal(t, 1, ctx.Estack.Len()) item, err := ctx.Estack.PopInt() - assert.NoError(t, err) + assert.Nil(t, err) assert.Equal(t, int64(2), item.Value().Int64()) } @@ -644,10 +569,10 @@ func TestMaxOp(t *testing.T) { v := VM{} a, err := stack.NewInt(big.NewInt(10)) - assert.NoError(t, err) + assert.Nil(t, err) b, err := stack.NewInt(big.NewInt(2)) - assert.NoError(t, err) + assert.Nil(t, err) ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a).Push(b) @@ -658,7 +583,7 @@ func TestMaxOp(t *testing.T) { assert.Equal(t, 1, ctx.Estack.Len()) item, err := ctx.Estack.PopInt() - assert.NoError(t, err) + assert.Nil(t, err) assert.Equal(t, int64(10), item.Value().Int64()) } @@ -668,13 +593,13 @@ func TestWithinOp(t *testing.T) { v := VM{} a, err := stack.NewInt(big.NewInt(5)) - assert.NoError(t, err) + assert.Nil(t, err) b, err := stack.NewInt(big.NewInt(2)) - assert.NoError(t, err) + assert.Nil(t, err) c, err := stack.NewInt(big.NewInt(10)) - assert.NoError(t, err) + assert.Nil(t, err) ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a).Push(b).Push(c) @@ -692,7 +617,7 @@ func TestWithinOp(t *testing.T) { assert.Equal(t, 1, ctx.Estack.Len()) item, err := ctx.Estack.PopBoolean() - assert.NoError(t, err) + assert.Nil(t, err) assert.Equal(t, true, item.Value()) } From 4dc11ee48fd5debd9c9dfd7a611e1d0a9eb83c9f Mon Sep 17 00:00:00 2001 From: DauTT Date: Tue, 9 Apr 2019 01:07:15 +0200 Subject: [PATCH 62/81] Implemented following control flow opcodes: 1) NOP 2) JMP 3) JMPIF 4) JMPIFNOT --- pkg/vm/stack/context.go | 15 ++++ pkg/vm/vm_ops.go | 4 + pkg/vm/vm_ops_flow.go | 68 +++++++++++++++ pkg/vm/vm_ops_flow_test.go | 168 +++++++++++++++++++++++++++++++++++++ 4 files changed, 255 insertions(+) create mode 100644 pkg/vm/vm_ops_flow_test.go diff --git a/pkg/vm/stack/context.go b/pkg/vm/stack/context.go index d381d74cb..096481150 100644 --- a/pkg/vm/stack/context.go +++ b/pkg/vm/stack/context.go @@ -120,6 +120,11 @@ func (c *Context) ReadUint16() uint16 { return val } +// ReadInt16 reads a int16 from the script +func (c *Context) ReadInt16() int16 { + return int16(c.ReadUint16()) +} + // ReadByte reads one byte from the script func (c *Context) ReadByte() (byte, error) { byt, err := c.ReadBytes(1) @@ -150,3 +155,13 @@ func (c *Context) readVarBytes() ([]byte, error) { } return c.ReadBytes(int(n)) } + +// SetIP sets the instruction pointer ip to a given integer. +// Returns an error if ip is less than -1 or greater than LenInstr. +func (c *Context) SetIP(ip int) error { + if ok := ip < -1 || ip > c.LenInstr(); ok { + return errors.New("invalid instruction pointer") + } + c.ip = ip + return nil +} diff --git a/pkg/vm/vm_ops.go b/pkg/vm/vm_ops.go index 0a9c6f173..e466c5fae 100644 --- a/pkg/vm/vm_ops.go +++ b/pkg/vm/vm_ops.go @@ -5,6 +5,10 @@ import "github.com/CityOfZion/neo-go/pkg/vm/stack" type stackInfo func(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) var opFunc = map[stack.Instruction]stackInfo{ + stack.JMPIFNOT: JMPIFNOT, + stack.JMPIF: JMPIF, + stack.JMP: JMP, + stack.NOP: NOP, stack.NUMEQUAL: NumEqual, stack.NUMNOTEQUAL: NumNotEqual, stack.BOOLAND: BoolAnd, diff --git a/pkg/vm/vm_ops_flow.go b/pkg/vm/vm_ops_flow.go index 67ca4f825..71f32d42d 100644 --- a/pkg/vm/vm_ops_flow.go +++ b/pkg/vm/vm_ops_flow.go @@ -25,3 +25,71 @@ func RET(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rst return NONE, nil } + +// NOP Returns NONE VMState. +func NOP(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + return NONE, nil +} + +// JMP moves the instruction pointer to an offset which is +// calculated base on the instructionPointerOffset method. +// Returns and error if the offset is out of range. +func JMP(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + offset := instructionPointerOffset(ctx) + if err := ctx.SetIP(offset); err != nil { + return FAULT, err + + } + + return NONE, nil +} + +// JMPIF pops a boolean off of the stack and, +// if the the boolean's value is true, it +// moves the instruction pointer to an offset which is +// calculated base on the instructionPointerOffset method. +// Returns and error if the offset is out of range or +// the popped item is not a boolean. +func JMPIF(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + b, err := ctx.Estack.PopBoolean() + if err != nil { + return FAULT, err + } + + if b.Value() { + offset := instructionPointerOffset(ctx) + if err := ctx.SetIP(offset); err != nil { + return FAULT, err + } + + } + + return NONE, nil +} + +// JMPIFNOT pops a boolean off of the stack and, +// if the the boolean's value is false, it +// moves the instruction pointer to an offset which is +// calculated base on the instructionPointerOffset method. +// Returns and error if the offset is out of range or +// the popped item is not a boolean. +func JMPIFNOT(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + b, err := ctx.Estack.PopBoolean() + if err != nil { + return FAULT, err + } + + if !b.Value() { + offset := instructionPointerOffset(ctx) + if err := ctx.SetIP(offset); err != nil { + return FAULT, err + } + + } + + return NONE, nil +} + +func instructionPointerOffset(ctx *stack.Context) int { + return ctx.IP() + int(ctx.ReadInt16()) - 3 +} diff --git a/pkg/vm/vm_ops_flow_test.go b/pkg/vm/vm_ops_flow_test.go new file mode 100644 index 000000000..2801e51a4 --- /dev/null +++ b/pkg/vm/vm_ops_flow_test.go @@ -0,0 +1,168 @@ +package vm + +import ( + "math/big" + "testing" + + "github.com/CityOfZion/neo-go/pkg/vm/stack" + "github.com/stretchr/testify/assert" +) + +func TestNopOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(10)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a) + + v.executeOp(stack.NOP, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + assert.Equal(t, int64(10), item.Value().Int64()) +} + +func TestJmpOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(10)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{5, 0, 2, 3, 4}) + ctx.Estack.Push(a) + + // ctx.ip = -1 + // ctx.IP() = ctx.ip + 1 + assert.Equal(t, 0, ctx.IP()) + + // ctx.ip will be set to offset. + // offset = ctx.IP() + int(ctx.ReadInt16()) - 3 + // = 0 + 5 -3 = 2 + v.executeOp(stack.JMP, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + // ctx.IP() = ctx.ip + 1 + assert.Equal(t, 3, ctx.IP()) +} + +// test JMPIF instruction with true boolean +// on top of the stack +func TestJmpIfOp1(t *testing.T) { + + v := VM{} + + a := stack.NewBoolean(true) + + ctx := stack.NewContext([]byte{5, 0, 2, 3, 4}) + ctx.Estack.Push(a) + + // ctx.ip = -1 + // ctx.IP() = ctx.ip + 1 + assert.Equal(t, 0, ctx.IP()) + + // ctx.ip will be set to offset + // because the there is a true boolean + // on top of the stack. + // offset = ctx.IP() + int(ctx.ReadInt16()) - 3 + // = 0 + 5 -3 = 2 + v.executeOp(stack.JMPIF, ctx) + + // Stack should have 0 item + assert.Equal(t, 0, ctx.Estack.Len()) + + // ctx.IP() = ctx.ip + 1 + assert.Equal(t, 3, ctx.IP()) +} + +// test JMPIF instruction with false boolean +// on top of the stack +func TestJmpIfOp2(t *testing.T) { + + v := VM{} + + a := stack.NewBoolean(false) + + ctx := stack.NewContext([]byte{5, 0, 2, 3, 4}) + ctx.Estack.Push(a) + + // ctx.ip = -1 + // ctx.IP() = ctx.ip + 1 + assert.Equal(t, 0, ctx.IP()) + + // nothing will happen because + // the value of the boolean on top of the stack + // is false + v.executeOp(stack.JMPIF, ctx) + + // Stack should have 0 item + assert.Equal(t, 0, ctx.Estack.Len()) + + // ctx.IP() = ctx.ip + 1 + assert.Equal(t, 0, ctx.IP()) +} + +// test JMPIFNOT instruction with true boolean +// on top of the stack +func TestJmpIfNotOp1(t *testing.T) { + + v := VM{} + + a := stack.NewBoolean(true) + + ctx := stack.NewContext([]byte{5, 0, 2, 3, 4}) + ctx.Estack.Push(a) + + // ctx.ip = -1 + // ctx.IP() = ctx.ip + 1 + assert.Equal(t, 0, ctx.IP()) + + // nothing will happen because + // the value of the boolean on top of the stack + // is true + v.executeOp(stack.JMPIFNOT, ctx) + + // Stack should have 0 item + assert.Equal(t, 0, ctx.Estack.Len()) + + // ctx.IP() = ctx.ip + 1 + assert.Equal(t, 0, ctx.IP()) +} + +// test JMPIFNOT instruction with false boolean +// on top of the stack +func TestJmpIfNotOp2(t *testing.T) { + + v := VM{} + + a := stack.NewBoolean(false) + + ctx := stack.NewContext([]byte{5, 0, 2, 3, 4}) + ctx.Estack.Push(a) + + // ctx.ip = -1 + // ctx.IP() = ctx.ip + 1 + assert.Equal(t, 0, ctx.IP()) + + // ctx.ip will be set to offset + // because the there is a false boolean + // on top of the stack. + // offset = ctx.IP() + int(ctx.ReadInt16()) - 3 + // = 0 + 5 -3 = 2 + v.executeOp(stack.JMPIFNOT, ctx) + + // Stack should have one item + assert.Equal(t, 0, ctx.Estack.Len()) + + // ctx.IP() = ctx.ip + 1 + assert.Equal(t, 3, ctx.IP()) +} From 7e20b604b4f61dc0c7d6017290d0567df6d5f82b Mon Sep 17 00:00:00 2001 From: DauTT Date: Thu, 11 Apr 2019 00:07:17 +0200 Subject: [PATCH 63/81] Implemented following opcodes: 1) DUPFROMALTSTACK 2) TOALTSTACK 3) FROMALTSTACK 4) XDROP --- pkg/vm/stack/stack.go | 16 ++++ pkg/vm/vm_ops.go | 60 ++++++++------- pkg/vm/vm_ops_stackmani.go | 63 +++++++++++++++ pkg/vm/vm_ops_stackmani_test.go | 131 ++++++++++++++++++++++++++++++++ 4 files changed, 242 insertions(+), 28 deletions(-) create mode 100644 pkg/vm/vm_ops_stackmani_test.go diff --git a/pkg/vm/stack/stack.go b/pkg/vm/stack/stack.go index c832a9f79..996027138 100644 --- a/pkg/vm/stack/stack.go +++ b/pkg/vm/stack/stack.go @@ -158,3 +158,19 @@ func (ras *RandomAccess) PopBoolean() (*Boolean, error) { } return item.Boolean() } + +// Remove removes the n-item from the stack +// starting from the top of the stack. In other words +// the n-item to remove is located at the index "len(stack)-n-1" +func (ras *RandomAccess) Remove(n uint16) (Item, error) { + if int(n) >= len(ras.vals) { + return nil, errors.New("index out of range") + } + + index := uint16(len(ras.vals)) - n - 1 + item := ras.vals[index] + + ras.vals = append(ras.vals[:index], ras.vals[index+1:]...) + + return item, nil +} diff --git a/pkg/vm/vm_ops.go b/pkg/vm/vm_ops.go index 0a9c6f173..57accf126 100644 --- a/pkg/vm/vm_ops.go +++ b/pkg/vm/vm_ops.go @@ -5,34 +5,38 @@ import "github.com/CityOfZion/neo-go/pkg/vm/stack" type stackInfo func(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) var opFunc = map[stack.Instruction]stackInfo{ - stack.NUMEQUAL: NumEqual, - stack.NUMNOTEQUAL: NumNotEqual, - stack.BOOLAND: BoolAnd, - stack.BOOLOR: BoolOr, - stack.LT: Lt, - stack.LTE: Lte, - stack.GT: Gt, - stack.GTE: Gte, - stack.SHR: Shr, - stack.SHL: Shl, - stack.INC: Inc, - stack.DEC: Dec, - stack.DIV: Div, - stack.MOD: Mod, - stack.NZ: Nz, - stack.MUL: Mul, - stack.ABS: Abs, - stack.NOT: Not, - stack.SIGN: Sign, - stack.NEGATE: Negate, - stack.ADD: Add, - stack.SUB: Sub, - stack.PUSHBYTES1: PushNBytes, - stack.PUSHBYTES75: PushNBytes, - stack.RET: RET, - stack.EQUAL: EQUAL, - stack.THROWIFNOT: THROWIFNOT, - stack.THROW: THROW, + stack.XDROP: XDROP, + stack.FROMALTSTACK: FROMALTSTACK, + stack.TOALTSTACK: TOALTSTACK, + stack.DUPFROMALTSTACK: DUPFROMALTSTACK, + stack.NUMEQUAL: NumEqual, + stack.NUMNOTEQUAL: NumNotEqual, + stack.BOOLAND: BoolAnd, + stack.BOOLOR: BoolOr, + stack.LT: Lt, + stack.LTE: Lte, + stack.GT: Gt, + stack.GTE: Gte, + stack.SHR: Shr, + stack.SHL: Shl, + stack.INC: Inc, + stack.DEC: Dec, + stack.DIV: Div, + stack.MOD: Mod, + stack.NZ: Nz, + stack.MUL: Mul, + stack.ABS: Abs, + stack.NOT: Not, + stack.SIGN: Sign, + stack.NEGATE: Negate, + stack.ADD: Add, + stack.SUB: Sub, + stack.PUSHBYTES1: PushNBytes, + stack.PUSHBYTES75: PushNBytes, + stack.RET: RET, + stack.EQUAL: EQUAL, + stack.THROWIFNOT: THROWIFNOT, + stack.THROW: THROW, } func init() { diff --git a/pkg/vm/vm_ops_stackmani.go b/pkg/vm/vm_ops_stackmani.go index f5e2ddc24..ce3af78d6 100644 --- a/pkg/vm/vm_ops_stackmani.go +++ b/pkg/vm/vm_ops_stackmani.go @@ -17,3 +17,66 @@ func PushNBytes(op stack.Instruction, ctx *stack.Context, istack *stack.Invocati ctx.Estack.Push(ba) return NONE, nil } + +// DUPFROMALTSTACK duplicates the item on top of alternative stack and +// puts it on top of evaluation stack. +// Returns an error if the alt stack is empty. +func DUPFROMALTSTACK(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + item, err := ctx.Astack.Peek(0) + if err != nil { + return FAULT, err + } + + ctx.Estack.Push(item) + + return NONE, nil +} + +// TOALTSTACK pops an item off of the evaluation stack and +// pushes it on top of the alternative stack. +// Returns an error if the alternative stack is empty. +func TOALTSTACK(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + item, err := ctx.Estack.Pop() + if err != nil { + return FAULT, err + } + + ctx.Astack.Push(item) + + return NONE, nil +} + +// FROMALTSTACK pops an item off of the alternative stack and +// pushes it on top of the evaluation stack. +// Returns an error if the evaluation stack is empty. +func FROMALTSTACK(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + item, err := ctx.Astack.Pop() + if err != nil { + return FAULT, err + } + + ctx.Estack.Push(item) + + return NONE, nil +} + +// XDROP pops an integer n off of the stack and +// removes the n-item from the stack starting from +// the top of the stack. +func XDROP(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + n, err := ctx.Estack.PopInt() + if err != nil { + return FAULT, err + } + + ctx.Estack.Remove(uint16(n.Value().Uint64())) + if err != nil { + return FAULT, err + } + + return NONE, nil +} diff --git a/pkg/vm/vm_ops_stackmani_test.go b/pkg/vm/vm_ops_stackmani_test.go new file mode 100644 index 000000000..6e257ad99 --- /dev/null +++ b/pkg/vm/vm_ops_stackmani_test.go @@ -0,0 +1,131 @@ +package vm + +import ( + "math/big" + "testing" + + "github.com/CityOfZion/neo-go/pkg/vm/stack" + "github.com/stretchr/testify/assert" +) + +func TestDupFromAltStackOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(10)) + assert.Nil(t, err) + + b, err := stack.NewInt(big.NewInt(2)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a) + ctx.Astack.Push(b) + + v.executeOp(stack.DUPFROMALTSTACK, ctx) + + assert.Equal(t, 1, ctx.Astack.Len()) + assert.Equal(t, 2, ctx.Estack.Len()) + + itemE, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + itemA, err := ctx.Astack.PopInt() + assert.Nil(t, err) + + assert.Equal(t, int64(2), itemE.Value().Int64()) + assert.Equal(t, int64(2), itemA.Value().Int64()) +} + +func TestToAltStackOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(10)) + assert.Nil(t, err) + + b, err := stack.NewInt(big.NewInt(2)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a) + ctx.Astack.Push(b) + + v.executeOp(stack.TOALTSTACK, ctx) + + assert.Equal(t, 2, ctx.Astack.Len()) + assert.Equal(t, 0, ctx.Estack.Len()) + + item, err := ctx.Astack.PopInt() + assert.Nil(t, err) + + assert.Equal(t, int64(10), item.Value().Int64()) +} + +func TestFromAltStackOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(10)) + assert.Nil(t, err) + + b, err := stack.NewInt(big.NewInt(2)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a) + ctx.Astack.Push(b) + + v.executeOp(stack.FROMALTSTACK, ctx) + + assert.Equal(t, 0, ctx.Astack.Len()) + assert.Equal(t, 2, ctx.Estack.Len()) + + item, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + assert.Equal(t, int64(2), item.Value().Int64()) +} + +func TestXDropOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(3)) + assert.Nil(t, err) + + b, err := stack.NewInt(big.NewInt(6)) + assert.Nil(t, err) + + c, err := stack.NewInt(big.NewInt(9)) + assert.Nil(t, err) + + d, err := stack.NewInt(big.NewInt(2)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a) + ctx.Estack.Push(b) + ctx.Estack.Push(c) + ctx.Estack.Push(d) + + // pop n (= d = 2) from the stack. + // we will remove the n-item which + // is located at position + // len(stack)-n-1 = 3-2-1 = 0. + // Therefore a is removed from the stack. + // Only b, c remain on the stack. + v.executeOp(stack.XDROP, ctx) + + assert.Equal(t, 2, ctx.Estack.Len()) + + itemC, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + itemB, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + assert.Equal(t, int64(6), itemB.Value().Int64()) + assert.Equal(t, int64(9), itemC.Value().Int64()) + +} From aa67e98726c03ef62cf04b7eca638da9599f6ca6 Mon Sep 17 00:00:00 2001 From: DauTT Date: Fri, 12 Apr 2019 00:38:57 +0200 Subject: [PATCH 64/81] Implemented following opcodes: 1) XSWAP 2) XTUCK 3) DEPTH 4) DROP --- pkg/vm/stack/stack.go | 15 +++ pkg/vm/vm_ops.go | 4 + pkg/vm/vm_ops_stackmani.go | 83 ++++++++++++++++ pkg/vm/vm_ops_stackmani_test.go | 162 ++++++++++++++++++++++++++++++++ 4 files changed, 264 insertions(+) create mode 100644 pkg/vm/vm_ops_stackmani_test.go diff --git a/pkg/vm/stack/stack.go b/pkg/vm/stack/stack.go index c832a9f79..d42c7b123 100644 --- a/pkg/vm/stack/stack.go +++ b/pkg/vm/stack/stack.go @@ -127,6 +127,21 @@ func (ras *RandomAccess) CopyTo(stack *RandomAccess) error { return nil } +// Set sets the n-item from the stack +// starting from the top of the stack with the new item. +// the n-item to replace is located at the position "len(stack)-index-1". +func (ras *RandomAccess) Set(index uint16, item Item) error { + stackSize := uint16(len(ras.vals)) + if ok := index >= stackSize; ok { + return errors.New("index out of range") + } + + n := stackSize - index - 1 + ras.vals[n] = item + + return nil +} + // Convenience Functions // PopInt will remove the last stack item that was added diff --git a/pkg/vm/vm_ops.go b/pkg/vm/vm_ops.go index 0a9c6f173..e6d61edc9 100644 --- a/pkg/vm/vm_ops.go +++ b/pkg/vm/vm_ops.go @@ -5,6 +5,10 @@ import "github.com/CityOfZion/neo-go/pkg/vm/stack" type stackInfo func(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) var opFunc = map[stack.Instruction]stackInfo{ + stack.DROP: DROP, + stack.DEPTH: DEPTH, + stack.XTUCK: XTUCK, + stack.XSWAP: XSWAP, stack.NUMEQUAL: NumEqual, stack.NUMNOTEQUAL: NumNotEqual, stack.BOOLAND: BoolAnd, diff --git a/pkg/vm/vm_ops_stackmani.go b/pkg/vm/vm_ops_stackmani.go index f5e2ddc24..81ec1d91c 100644 --- a/pkg/vm/vm_ops_stackmani.go +++ b/pkg/vm/vm_ops_stackmani.go @@ -1,6 +1,8 @@ package vm import ( + "math/big" + "github.com/CityOfZion/neo-go/pkg/vm/stack" ) @@ -17,3 +19,84 @@ func PushNBytes(op stack.Instruction, ctx *stack.Context, istack *stack.Invocati ctx.Estack.Push(ba) return NONE, nil } + +// XSWAP pops an integer n off of the stack and +// swaps the n-item from the stack starting from +// the top of the stack with the top stack item. +func XSWAP(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + n, err := ctx.Estack.PopInt() + if err != nil { + return FAULT, err + } + nItem, err := ctx.Estack.Peek(uint16(n.Value().Int64())) + if err != nil { + return FAULT, err + } + + item, err := ctx.Estack.Peek(0) + if err != nil { + return FAULT, err + } + + if err := ctx.Estack.Set(uint16(n.Value().Int64()), item); err != nil { + return FAULT, err + } + + if err := ctx.Estack.Set(0, nItem); err != nil { + return FAULT, err + } + + return NONE, nil +} + +// XTUCK pops an integer n off of the stack and +// inserts the top stack item to the position len(stack)-n in the evaluation stack. +func XTUCK(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + n, err := ctx.Estack.PopInt() + if err != nil || n.Value().Int64() < 0 { + return FAULT, err + } + + item, err := ctx.Estack.Peek(0) + if err != nil { + return FAULT, err + } + + ras, err := ctx.Estack.Insert(uint16(n.Value().Int64()), item) + if err != nil { + return FAULT, err + } + + ctx.Estack = *ras + + return NONE, nil +} + +// DEPTH puts the number of stack items onto the stack. +func DEPTH(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + l := ctx.Estack.Len() + length, err := stack.NewInt(big.NewInt(int64(l))) + if err != nil { + return FAULT, err + } + + ctx.Estack.Push(length) + + return NONE, nil +} + +// DROP removes the the top stack item. +// Returns error if the operation Pop cannot +// be performed. +func DROP(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + _, err := ctx.Estack.Pop() + if err != nil { + return FAULT, err + } + + return NONE, nil +} diff --git a/pkg/vm/vm_ops_stackmani_test.go b/pkg/vm/vm_ops_stackmani_test.go new file mode 100644 index 000000000..eb771df36 --- /dev/null +++ b/pkg/vm/vm_ops_stackmani_test.go @@ -0,0 +1,162 @@ +package vm + +import ( + "math/big" + "testing" + + "github.com/CityOfZion/neo-go/pkg/vm/stack" + "github.com/stretchr/testify/assert" +) + +func TestXswapOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(3)) + assert.Nil(t, err) + + b, err := stack.NewInt(big.NewInt(6)) + assert.Nil(t, err) + + c, err := stack.NewInt(big.NewInt(9)) + assert.Nil(t, err) + + d, err := stack.NewInt(big.NewInt(2)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b).Push(c).Push(d) + + // pop n (= d = 2) from the stack. + // we will swap the n-item which + // is located in position len(stack)-n-1 (= 3-2-1= 0) + // with the top stack item. + // The final stack will be [c,b,a] + v.executeOp(stack.XSWAP, ctx) + + // Stack should have three items + assert.Equal(t, 3, ctx.Estack.Len()) + + itemA, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + itemB, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + itemC, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + assert.Equal(t, int64(3), itemA.Value().Int64()) + assert.Equal(t, int64(6), itemB.Value().Int64()) + assert.Equal(t, int64(9), itemC.Value().Int64()) + +} + +func TestXTuckOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(3)) + assert.Nil(t, err) + + b, err := stack.NewInt(big.NewInt(6)) + assert.Nil(t, err) + + c, err := stack.NewInt(big.NewInt(9)) + assert.Nil(t, err) + + d, err := stack.NewInt(big.NewInt(2)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b).Push(c).Push(d) + + // pop n (= d = 2) from the stack + // and insert the top stack item c + // to the position len(stack)-n (= 3-2 = 1) + // of the stack.The final stack will be [a,c,b,c] + v.executeOp(stack.XTUCK, ctx) + + // Stack should have four items + assert.Equal(t, 4, ctx.Estack.Len()) + + // c + item0, err := ctx.Estack.PopInt() + assert.Nil(t, err) + // b + item1, err := ctx.Estack.PopInt() + assert.Nil(t, err) + // c + item2, err := ctx.Estack.PopInt() + assert.Nil(t, err) + // a + item3, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + assert.Equal(t, int64(9), item0.Value().Int64()) + assert.Equal(t, int64(6), item1.Value().Int64()) + assert.Equal(t, int64(9), item2.Value().Int64()) + assert.Equal(t, int64(3), item3.Value().Int64()) + +} + +func TestXDepthOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(3)) + assert.Nil(t, err) + + b, err := stack.NewInt(big.NewInt(6)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b) + + // push integer whose value is len(stack) (2) + // on top of the stack + v.executeOp(stack.DEPTH, ctx) + + // Stack should have three items + assert.Equal(t, 3, ctx.Estack.Len()) + + // len(stack) + item0, err := ctx.Estack.PopInt() + assert.Nil(t, err) + // b + item1, err := ctx.Estack.PopInt() + assert.Nil(t, err) + // a + item2, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + assert.Equal(t, int64(2), item0.Value().Int64()) + assert.Equal(t, int64(6), item1.Value().Int64()) + assert.Equal(t, int64(3), item2.Value().Int64()) +} + +func TestXDropOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(3)) + assert.Nil(t, err) + + b, err := stack.NewInt(big.NewInt(6)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b) + + // Remove the top stack item from the stack. + // The remaining stack is [a] + v.executeOp(stack.DROP, ctx) + + // Stack should have 2 items + assert.Equal(t, 1, ctx.Estack.Len()) + + itemA, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + assert.Equal(t, int64(3), itemA.Value().Int64()) +} From d43dcf03cb4257b00a0e189145bbcbcdc40fac83 Mon Sep 17 00:00:00 2001 From: DauTT Date: Fri, 12 Apr 2019 20:25:20 +0200 Subject: [PATCH 65/81] Implemented following opcodes: 1) DUP 2) NIP 3) OVER 4) PICK --- pkg/vm/stack/stack.go | 31 +++++ pkg/vm/vm_ops.go | 4 + pkg/vm/vm_ops_stackmani.go | 64 +++++++++++ pkg/vm/vm_ops_stackmani_test.go | 197 ++++++++++++++++++++++++++++++++ 4 files changed, 296 insertions(+) create mode 100644 pkg/vm/vm_ops_stackmani_test.go diff --git a/pkg/vm/stack/stack.go b/pkg/vm/stack/stack.go index c832a9f79..91a888a7d 100644 --- a/pkg/vm/stack/stack.go +++ b/pkg/vm/stack/stack.go @@ -127,6 +127,37 @@ func (ras *RandomAccess) CopyTo(stack *RandomAccess) error { return nil } +// Set sets the n-item from the stack +// starting from the top of the stack with the new item. +// the n-item to replace is located at the position "len(stack)-index-1". +func (ras *RandomAccess) Set(index uint16, item Item) error { + stackSize := uint16(len(ras.vals)) + if ok := index >= stackSize; ok { + return errors.New("index out of range") + } + + n := stackSize - index - 1 + ras.vals[n] = item + + return nil +} + +// Remove removes the n-item from the stack +// starting from the top of the stack. In other words +// the n-item to remove is located at the index "len(stack)-n-1" +func (ras *RandomAccess) Remove(n uint16) (Item, error) { + if int(n) >= len(ras.vals) { + return nil, errors.New("index out of range") + } + + index := uint16(len(ras.vals)) - n - 1 + item := ras.vals[index] + + ras.vals = append(ras.vals[:index], ras.vals[index+1:]...) + + return item, nil +} + // Convenience Functions // PopInt will remove the last stack item that was added diff --git a/pkg/vm/vm_ops.go b/pkg/vm/vm_ops.go index 0a9c6f173..f02d09be8 100644 --- a/pkg/vm/vm_ops.go +++ b/pkg/vm/vm_ops.go @@ -5,6 +5,10 @@ import "github.com/CityOfZion/neo-go/pkg/vm/stack" type stackInfo func(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) var opFunc = map[stack.Instruction]stackInfo{ + stack.PICK: PICK, + stack.OVER: OVER, + stack.NIP: NIP, + stack.DUP: DUP, stack.NUMEQUAL: NumEqual, stack.NUMNOTEQUAL: NumNotEqual, stack.BOOLAND: BoolAnd, diff --git a/pkg/vm/vm_ops_stackmani.go b/pkg/vm/vm_ops_stackmani.go index f5e2ddc24..5eecad252 100644 --- a/pkg/vm/vm_ops_stackmani.go +++ b/pkg/vm/vm_ops_stackmani.go @@ -17,3 +17,67 @@ func PushNBytes(op stack.Instruction, ctx *stack.Context, istack *stack.Invocati ctx.Estack.Push(ba) return NONE, nil } + +// DUP duplicates the top stack item. +// Returns an error if stack is empty. +func DUP(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + item, err := ctx.Estack.Peek(0) + if err != nil { + return FAULT, err + } + + ctx.Estack.Push(item) + + return NONE, nil +} + +// NIP removes the second top stack item. +// Returns error if the stack item contains +// only one element. +func NIP(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + _, err := ctx.Estack.Remove(1) + if err != nil { + return FAULT, err + } + + return NONE, nil +} + +// OVER copies the second-to-top stack item onto the top. +// Returns an error if the stack item contains +// only one element. +func OVER(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + item, err := ctx.Estack.Peek(1) + if err != nil { + return FAULT, err + } + + ctx.Estack.Push(item) + + return NONE, nil +} + +// PICK pops an integer n off of the stack and +// copies the n-item starting from +// the top of the stack onto the top stack item. +// Returns an error if the top stack item is not an +// integer or n-item does not exist. +func PICK(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + n, err := ctx.Estack.PopInt() + if err != nil { + return FAULT, err + } + + nItem, err := ctx.Estack.Peek(uint16(n.Value().Int64())) + if err != nil { + return FAULT, err + } + + ctx.Estack.Push(nItem) + + return NONE, nil +} diff --git a/pkg/vm/vm_ops_stackmani_test.go b/pkg/vm/vm_ops_stackmani_test.go new file mode 100644 index 000000000..45e84ddb7 --- /dev/null +++ b/pkg/vm/vm_ops_stackmani_test.go @@ -0,0 +1,197 @@ +package vm + +import ( + "math/big" + "testing" + + "github.com/CityOfZion/neo-go/pkg/vm/stack" + "github.com/stretchr/testify/assert" +) + +func TestDupOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(3)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a) + + v.executeOp(stack.DUP, ctx) + + // Stack should have two items + assert.Equal(t, 2, ctx.Estack.Len()) + + item1, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + item2, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + assert.Equal(t, int64(3), item1.Value().Int64()) + assert.Equal(t, int64(3), item2.Value().Int64()) + +} + +func TestNipOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(3)) + assert.Nil(t, err) + + b, err := stack.NewInt(big.NewInt(6)) + assert.Nil(t, err) + + c, err := stack.NewInt(big.NewInt(9)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b).Push(c) + + v.executeOp(stack.NIP, ctx) + + // Stack should have two items + assert.Equal(t, 2, ctx.Estack.Len()) + + itemC, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + itemA, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + assert.Equal(t, int64(3), itemA.Value().Int64()) + assert.Equal(t, int64(9), itemC.Value().Int64()) + +} + +func TestOverOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(3)) + assert.Nil(t, err) + + b, err := stack.NewInt(big.NewInt(6)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b) + + // OVER copies the second top stack item a + // onto the top stack item b. + // the new stack will be [a,b,a]. + v.executeOp(stack.OVER, ctx) + + // Stack should have three items + assert.Equal(t, 3, ctx.Estack.Len()) + + itemA, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + itemB, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + itemA2, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + assert.Equal(t, int64(3), itemA.Value().Int64()) + assert.Equal(t, int64(6), itemB.Value().Int64()) + assert.Equal(t, int64(3), itemA2.Value().Int64()) + +} + +func TestPickOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(3)) + assert.Nil(t, err) + + b, err := stack.NewInt(big.NewInt(6)) + assert.Nil(t, err) + + c, err := stack.NewInt(big.NewInt(9)) + assert.Nil(t, err) + + d, err := stack.NewInt(big.NewInt(2)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b).Push(c).Push(d) + + // pop n (= d = 2) from the stack. + // we will copy the n-item which + // has index len(stack)-n-1 (= 3-2-1= 0) + // onto the top stack item. + // The final stack will be [a,b,c,a] + v.executeOp(stack.PICK, ctx) + + // Stack should have four items + assert.Equal(t, 4, ctx.Estack.Len()) + + itemA, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + itemC, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + itemB, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + itemA2, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + assert.Equal(t, int64(3), itemA.Value().Int64()) + assert.Equal(t, int64(9), itemC.Value().Int64()) + assert.Equal(t, int64(6), itemB.Value().Int64()) + assert.Equal(t, int64(3), itemA2.Value().Int64()) + +} + +/* +func TestXswapOp(t *testing.T) { + + v := VM{} + + sert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b).Push(c).Push(d) + + // pop n (= d = 2) from the stack. + // we wa, err := stack.NewInt(big.NewInt(3)) + assert.Nil(t, err) + + b, err := stack.NewInt(big.NewInt(6)) + assert.Nil(t, err) + + c, err := stack.NewInt(big.NewInt(9)) + assert.Nil(t, err) + + d, err := stack.NewInt(big.NewInt(2)) + asill swap the n-item which + // is located in position len(stack)-n-1 (= 3-2-1= 0) + // with the top stack item. + // The final stack will be [c,b,a] + v.executeOp(stack.XSWAP, ctx) + + // Stack should have three items + assert.Equal(t, 3, ctx.Estack.Len()) + + itemA, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + itemB, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + itemC, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + assert.Equal(t, int64(3), itemA.Value().Int64()) + assert.Equal(t, int64(6), itemB.Value().Int64()) + assert.Equal(t, int64(9), itemC.Value().Int64()) + +} +*/ From d36d8b456c1b256faa34534dad4d0de9c15caaec Mon Sep 17 00:00:00 2001 From: DauTT Date: Fri, 12 Apr 2019 22:27:52 +0200 Subject: [PATCH 66/81] Implemented following opcodes: 1) ROLL 2) ROT 3) SWAP 4) TUCK --- pkg/vm/stack/stack.go | 16 ++++ pkg/vm/vm_ops.go | 4 + pkg/vm/vm_ops_stackmani.go | 74 ++++++++++++++ pkg/vm/vm_ops_stackmani_test.go | 164 ++++++++++++++++++++++++++++++++ 4 files changed, 258 insertions(+) create mode 100644 pkg/vm/vm_ops_stackmani_test.go diff --git a/pkg/vm/stack/stack.go b/pkg/vm/stack/stack.go index c832a9f79..5e837258c 100644 --- a/pkg/vm/stack/stack.go +++ b/pkg/vm/stack/stack.go @@ -127,6 +127,22 @@ func (ras *RandomAccess) CopyTo(stack *RandomAccess) error { return nil } +// Remove removes the n-item from the stack +// starting from the top of the stack. In other words +// the n-item to remove is located at the index "len(stack)-n-1" +func (ras *RandomAccess) Remove(n uint16) (Item, error) { + if int(n) >= len(ras.vals) { + return nil, errors.New("index out of range") + } + + index := uint16(len(ras.vals)) - n - 1 + item := ras.vals[index] + + ras.vals = append(ras.vals[:index], ras.vals[index+1:]...) + + return item, nil +} + // Convenience Functions // PopInt will remove the last stack item that was added diff --git a/pkg/vm/vm_ops.go b/pkg/vm/vm_ops.go index 0a9c6f173..83aafda7d 100644 --- a/pkg/vm/vm_ops.go +++ b/pkg/vm/vm_ops.go @@ -5,6 +5,10 @@ import "github.com/CityOfZion/neo-go/pkg/vm/stack" type stackInfo func(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) var opFunc = map[stack.Instruction]stackInfo{ + stack.TUCK: TUCK, + stack.SWAP: SWAP, + stack.ROT: ROT, + stack.ROLL: ROLL, stack.NUMEQUAL: NumEqual, stack.NUMNOTEQUAL: NumNotEqual, stack.BOOLAND: BoolAnd, diff --git a/pkg/vm/vm_ops_stackmani.go b/pkg/vm/vm_ops_stackmani.go index f5e2ddc24..eb8fcc8d4 100644 --- a/pkg/vm/vm_ops_stackmani.go +++ b/pkg/vm/vm_ops_stackmani.go @@ -17,3 +17,77 @@ func PushNBytes(op stack.Instruction, ctx *stack.Context, istack *stack.Invocati ctx.Estack.Push(ba) return NONE, nil } + +// ROLL pops an integer n off of the stack and +// moves the n-item starting from +// the top of the stack onto the top stack item. +// Returns an error if the top stack item is not an +// integer or n-item does not exist. +func ROLL(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + n, err := ctx.Estack.PopInt() + if err != nil { + return FAULT, err + } + + nItem, err := ctx.Estack.Remove(uint16(n.Value().Int64())) + if err != nil { + return FAULT, err + } + + ctx.Estack.Push(nItem) + + return NONE, nil +} + +// ROT moves the third top stack item +// onto the top stack item. +// Returns an error if the third top stack item +// does not exist. +func ROT(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + item, err := ctx.Estack.Remove(2) + if err != nil { + return FAULT, err + } + + ctx.Estack.Push(item) + + return NONE, nil +} + +// SWAP swaps the second top stack item with +// the top stack item. +// Returns an error if the second top stack item +// does not exist. +func SWAP(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + item, err := ctx.Estack.Remove(1) + if err != nil { + return FAULT, err + } + + ctx.Estack.Push(item) + + return NONE, nil +} + +// TUCK copies the top stack item and +// inserts it before the second top stack item. +// Returns an error if the stack is empty or +// len(stack) is less or equal 2. +func TUCK(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + item, err := ctx.Estack.Peek(0) + if err != nil { + return FAULT, err + } + + ras, err := ctx.Estack.Insert(2, item) + if err != nil { + return FAULT, err + } + ctx.Estack = *ras + + return NONE, nil +} diff --git a/pkg/vm/vm_ops_stackmani_test.go b/pkg/vm/vm_ops_stackmani_test.go new file mode 100644 index 000000000..ab80b36d2 --- /dev/null +++ b/pkg/vm/vm_ops_stackmani_test.go @@ -0,0 +1,164 @@ +package vm + +import ( + "math/big" + "testing" + + "github.com/CityOfZion/neo-go/pkg/vm/stack" + "github.com/stretchr/testify/assert" +) + +func TestRollOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(3)) + assert.Nil(t, err) + + b, err := stack.NewInt(big.NewInt(6)) + assert.Nil(t, err) + + c, err := stack.NewInt(big.NewInt(9)) + assert.Nil(t, err) + + d, err := stack.NewInt(big.NewInt(2)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b).Push(c).Push(d) + + // pop n (= d = 2) from the stack + // and move the n-item which + // has index len(stack)-n-1 (= 3-2-1= 0) + // onto the top stack item. + // The final stack will be [b,c,a] + v.executeOp(stack.ROLL, ctx) + + // Stack should have three items + assert.Equal(t, 3, ctx.Estack.Len()) + + itemA, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + itemC, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + itemB, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + assert.Equal(t, int64(3), itemA.Value().Int64()) + assert.Equal(t, int64(9), itemC.Value().Int64()) + assert.Equal(t, int64(6), itemB.Value().Int64()) +} + +func TestRotOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(3)) + assert.Nil(t, err) + + b, err := stack.NewInt(big.NewInt(6)) + assert.Nil(t, err) + + c, err := stack.NewInt(big.NewInt(9)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b).Push(c) + + // move the third top stack a item onto + // the top stack item c. + // The final stack will be [b,c,a] + v.executeOp(stack.ROT, ctx) + + // Stack should have three items + assert.Equal(t, 3, ctx.Estack.Len()) + + itemA, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + itemC, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + itemB, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + assert.Equal(t, int64(3), itemA.Value().Int64()) + assert.Equal(t, int64(9), itemC.Value().Int64()) + assert.Equal(t, int64(6), itemB.Value().Int64()) +} + +func TestSwapOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(3)) + assert.Nil(t, err) + + b, err := stack.NewInt(big.NewInt(6)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b) + + // Swaps the top two stack items. + // The final stack will be [b,a] + v.executeOp(stack.SWAP, ctx) + + // Stack should have two items + assert.Equal(t, 2, ctx.Estack.Len()) + + itemA, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + itemB, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + assert.Equal(t, int64(3), itemA.Value().Int64()) + assert.Equal(t, int64(6), itemB.Value().Int64()) + +} + +func TestTuckOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(3)) + assert.Nil(t, err) + + b, err := stack.NewInt(big.NewInt(6)) + assert.Nil(t, err) + + c, err := stack.NewInt(big.NewInt(9)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b).Push(c) + + // copy the top stack item c and + // inserts it before the second top stack item. + // The final stack will be [a,c,b,c] + v.executeOp(stack.TUCK, ctx) + + // Stack should have four items + assert.Equal(t, 4, ctx.Estack.Len()) + + itemC, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + itemB, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + itemC2, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + itemA, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + assert.Equal(t, int64(9), itemC.Value().Int64()) + assert.Equal(t, int64(6), itemB.Value().Int64()) + assert.Equal(t, int64(9), itemC2.Value().Int64()) + assert.Equal(t, int64(3), itemA.Value().Int64()) + +} From 458e6f922b3c0103e34944155bc6f3e9d93609b7 Mon Sep 17 00:00:00 2001 From: DauTT Date: Fri, 12 Apr 2019 22:34:28 +0200 Subject: [PATCH 67/81] Clean up comments --- pkg/vm/vm_ops_stackmani_test.go | 46 --------------------------------- 1 file changed, 46 deletions(-) diff --git a/pkg/vm/vm_ops_stackmani_test.go b/pkg/vm/vm_ops_stackmani_test.go index 45e84ddb7..eb9bf9a91 100644 --- a/pkg/vm/vm_ops_stackmani_test.go +++ b/pkg/vm/vm_ops_stackmani_test.go @@ -149,49 +149,3 @@ func TestPickOp(t *testing.T) { assert.Equal(t, int64(3), itemA2.Value().Int64()) } - -/* -func TestXswapOp(t *testing.T) { - - v := VM{} - - sert.Nil(t, err) - - ctx := stack.NewContext([]byte{}) - ctx.Estack.Push(a).Push(b).Push(c).Push(d) - - // pop n (= d = 2) from the stack. - // we wa, err := stack.NewInt(big.NewInt(3)) - assert.Nil(t, err) - - b, err := stack.NewInt(big.NewInt(6)) - assert.Nil(t, err) - - c, err := stack.NewInt(big.NewInt(9)) - assert.Nil(t, err) - - d, err := stack.NewInt(big.NewInt(2)) - asill swap the n-item which - // is located in position len(stack)-n-1 (= 3-2-1= 0) - // with the top stack item. - // The final stack will be [c,b,a] - v.executeOp(stack.XSWAP, ctx) - - // Stack should have three items - assert.Equal(t, 3, ctx.Estack.Len()) - - itemA, err := ctx.Estack.PopInt() - assert.Nil(t, err) - - itemB, err := ctx.Estack.PopInt() - assert.Nil(t, err) - - itemC, err := ctx.Estack.PopInt() - assert.Nil(t, err) - - assert.Equal(t, int64(3), itemA.Value().Int64()) - assert.Equal(t, int64(6), itemB.Value().Int64()) - assert.Equal(t, int64(9), itemC.Value().Int64()) - -} -*/ From 0258fa48f857eadf018894040d6ff8dd009aa081 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 12 Aug 2019 16:25:16 +0300 Subject: [PATCH 68/81] pkg/vm: fix all GolangCI warnings about v.executeOp errcheck Fixes lots of warnings like this in the test code: Error return value of v.executeOp is not checked (from errcheck) --- pkg/vm/vm_ops_bitwise_test.go | 15 ++++--- pkg/vm/vm_ops_flow_test.go | 18 +++++--- pkg/vm/vm_ops_maths_test.go | 75 ++++++++++++++++++++++----------- pkg/vm/vm_ops_stackmani_test.go | 45 +++++++++++++------- pkg/vm/vmopscrypto_test.go | 12 ++++-- 5 files changed, 110 insertions(+), 55 deletions(-) diff --git a/pkg/vm/vm_ops_bitwise_test.go b/pkg/vm/vm_ops_bitwise_test.go index 6e92ada2f..9e00a487e 100644 --- a/pkg/vm/vm_ops_bitwise_test.go +++ b/pkg/vm/vm_ops_bitwise_test.go @@ -20,7 +20,8 @@ func TestInvertOp(t *testing.T) { ctx.Estack.Push(a) // 1111 11001 = -6 (two complement representation) - v.executeOp(stack.INVERT, ctx) + _, err = v.executeOp(stack.INVERT, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -47,7 +48,8 @@ func TestAndOp(t *testing.T) { ctx.Estack.Push(a).Push(b) // 100001 = 33 - v.executeOp(stack.AND, ctx) + _, err = v.executeOp(stack.AND, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -74,7 +76,8 @@ func TestOrOp(t *testing.T) { ctx.Estack.Push(a).Push(b) // 110011 = 51 (49 OR 35) - v.executeOp(stack.OR, ctx) + _, err = v.executeOp(stack.OR, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -101,7 +104,8 @@ func TestXorOp(t *testing.T) { ctx.Estack.Push(a).Push(b) // 010010 = 18 (49 XOR 35) - v.executeOp(stack.XOR, ctx) + _, err = v.executeOp(stack.XOR, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -125,7 +129,8 @@ func TestEqualOp(t *testing.T) { ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a).Push(b) - v.executeOp(stack.EQUAL, ctx) + _, err = v.executeOp(stack.EQUAL, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) diff --git a/pkg/vm/vm_ops_flow_test.go b/pkg/vm/vm_ops_flow_test.go index 2801e51a4..0094b4f3f 100644 --- a/pkg/vm/vm_ops_flow_test.go +++ b/pkg/vm/vm_ops_flow_test.go @@ -18,7 +18,8 @@ func TestNopOp(t *testing.T) { ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a) - v.executeOp(stack.NOP, ctx) + _, err = v.executeOp(stack.NOP, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -46,7 +47,8 @@ func TestJmpOp(t *testing.T) { // ctx.ip will be set to offset. // offset = ctx.IP() + int(ctx.ReadInt16()) - 3 // = 0 + 5 -3 = 2 - v.executeOp(stack.JMP, ctx) + _, err = v.executeOp(stack.JMP, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -75,7 +77,8 @@ func TestJmpIfOp1(t *testing.T) { // on top of the stack. // offset = ctx.IP() + int(ctx.ReadInt16()) - 3 // = 0 + 5 -3 = 2 - v.executeOp(stack.JMPIF, ctx) + _, err := v.executeOp(stack.JMPIF, ctx) + assert.Nil(t, err) // Stack should have 0 item assert.Equal(t, 0, ctx.Estack.Len()) @@ -102,7 +105,8 @@ func TestJmpIfOp2(t *testing.T) { // nothing will happen because // the value of the boolean on top of the stack // is false - v.executeOp(stack.JMPIF, ctx) + _, err := v.executeOp(stack.JMPIF, ctx) + assert.Nil(t, err) // Stack should have 0 item assert.Equal(t, 0, ctx.Estack.Len()) @@ -129,7 +133,8 @@ func TestJmpIfNotOp1(t *testing.T) { // nothing will happen because // the value of the boolean on top of the stack // is true - v.executeOp(stack.JMPIFNOT, ctx) + _, err := v.executeOp(stack.JMPIFNOT, ctx) + assert.Nil(t, err) // Stack should have 0 item assert.Equal(t, 0, ctx.Estack.Len()) @@ -158,7 +163,8 @@ func TestJmpIfNotOp2(t *testing.T) { // on top of the stack. // offset = ctx.IP() + int(ctx.ReadInt16()) - 3 // = 0 + 5 -3 = 2 - v.executeOp(stack.JMPIFNOT, ctx) + _, err := v.executeOp(stack.JMPIFNOT, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 0, ctx.Estack.Len()) diff --git a/pkg/vm/vm_ops_maths_test.go b/pkg/vm/vm_ops_maths_test.go index 14e8c88a3..9fb92c203 100644 --- a/pkg/vm/vm_ops_maths_test.go +++ b/pkg/vm/vm_ops_maths_test.go @@ -18,7 +18,8 @@ func TestIncOp(t *testing.T) { ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a) - v.executeOp(stack.INC, ctx) + _, err = v.executeOp(stack.INC, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -39,7 +40,8 @@ func TestDecOp(t *testing.T) { ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a) - v.executeOp(stack.DEC, ctx) + _, err = v.executeOp(stack.DEC, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -63,7 +65,8 @@ func TestAddOp(t *testing.T) { ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a).Push(b) - v.executeOp(stack.ADD, ctx) + _, err = v.executeOp(stack.ADD, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -88,7 +91,8 @@ func TestSubOp(t *testing.T) { ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a).Push(b) - v.executeOp(stack.SUB, ctx) + _, err = v.executeOp(stack.SUB, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -113,7 +117,8 @@ func TestDivOp(t *testing.T) { ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a).Push(b) - v.executeOp(stack.DIV, ctx) + _, err = v.executeOp(stack.DIV, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -137,7 +142,8 @@ func TestModOp(t *testing.T) { ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a).Push(b) - v.executeOp(stack.MOD, ctx) + _, err = v.executeOp(stack.MOD, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -158,7 +164,8 @@ func TestNzOp(t *testing.T) { ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a) - v.executeOp(stack.NZ, ctx) + _, err = v.executeOp(stack.NZ, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -182,7 +189,8 @@ func TestMulOp(t *testing.T) { ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a).Push(b) - v.executeOp(stack.MUL, ctx) + _, err = v.executeOp(stack.MUL, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -203,7 +211,8 @@ func TestAbsOp(t *testing.T) { ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a) - v.executeOp(stack.ABS, ctx) + _, err = v.executeOp(stack.ABS, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -223,7 +232,8 @@ func TestNotOp(t *testing.T) { ctx := stack.NewContext([]byte{}) ctx.Estack.Push(b) - v.executeOp(stack.NOT, ctx) + _, err := v.executeOp(stack.NOT, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -247,7 +257,8 @@ func TestNumEqual(t *testing.T) { ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a).Push(b) - v.executeOp(stack.NUMEQUAL, ctx) + _, err = v.executeOp(stack.NUMEQUAL, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -271,7 +282,8 @@ func TestNumNotEqual(t *testing.T) { ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a).Push(b) - v.executeOp(stack.NUMNOTEQUAL, ctx) + _, err = v.executeOp(stack.NUMNOTEQUAL, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -292,7 +304,8 @@ func TestSignOp(t *testing.T) { ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a) - v.executeOp(stack.SIGN, ctx) + _, err = v.executeOp(stack.SIGN, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -313,7 +326,8 @@ func TestNegateOp(t *testing.T) { ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a) - v.executeOp(stack.NEGATE, ctx) + _, err = v.executeOp(stack.NEGATE, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -342,7 +356,8 @@ func TestLteOp(t *testing.T) { // we perform a <= b and place // the result on top of the evaluation // stack - v.executeOp(stack.LTE, ctx) + _, err = v.executeOp(stack.LTE, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -371,7 +386,8 @@ func TestGteOp(t *testing.T) { // we perform a >= b and place // the result on top of the evaluation // stack - v.executeOp(stack.GTE, ctx) + _, err = v.executeOp(stack.GTE, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -400,7 +416,8 @@ func TestShlOp(t *testing.T) { // we perform a.Lsh(b) and place // the result on top of the evaluation // stack - v.executeOp(stack.SHL, ctx) + _, err = v.executeOp(stack.SHL, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -429,7 +446,8 @@ func TestShrOp(t *testing.T) { // we perform a.Rsh(b) and place // the result on top of the evaluation // stack - v.executeOp(stack.SHR, ctx) + _, err = v.executeOp(stack.SHR, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -450,7 +468,8 @@ func TestBoolAndOp(t *testing.T) { ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a).Push(b) - v.executeOp(stack.BOOLAND, ctx) + _, err := v.executeOp(stack.BOOLAND, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -471,7 +490,8 @@ func TestBoolOrOp(t *testing.T) { ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a).Push(b) - v.executeOp(stack.BOOLOR, ctx) + _, err := v.executeOp(stack.BOOLOR, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -500,7 +520,8 @@ func TestLtOp(t *testing.T) { // we perform a < b and place // the result on top of the evaluation // stack - v.executeOp(stack.LT, ctx) + _, err = v.executeOp(stack.LT, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -529,7 +550,8 @@ func TestGtOp(t *testing.T) { // we perform a > b and place // the result on top of the evaluation // stack - v.executeOp(stack.GT, ctx) + _, err = v.executeOp(stack.GT, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -553,7 +575,8 @@ func TestMinOp(t *testing.T) { ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a).Push(b) - v.executeOp(stack.MIN, ctx) + _, err = v.executeOp(stack.MIN, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -577,7 +600,8 @@ func TestMaxOp(t *testing.T) { ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a).Push(b) - v.executeOp(stack.MAX, ctx) + _, err = v.executeOp(stack.MAX, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -611,7 +635,8 @@ func TestWithinOp(t *testing.T) { // whose value is true, on top of the evaluation // stack. Otherwise we place a boolean with // false value. - v.executeOp(stack.WITHIN, ctx) + _, err = v.executeOp(stack.WITHIN, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) diff --git a/pkg/vm/vm_ops_stackmani_test.go b/pkg/vm/vm_ops_stackmani_test.go index efe554351..669ede0d2 100644 --- a/pkg/vm/vm_ops_stackmani_test.go +++ b/pkg/vm/vm_ops_stackmani_test.go @@ -32,7 +32,8 @@ func TestRollOp(t *testing.T) { // has index len(stack)-n-1 (= 3-2-1= 0) // onto the top stack item. // The final stack will be [b,c,a] - v.executeOp(stack.ROLL, ctx) + _, err = v.executeOp(stack.ROLL, ctx) + assert.Nil(t, err) // Stack should have three items assert.Equal(t, 3, ctx.Estack.Len()) @@ -69,7 +70,8 @@ func TestRotOp(t *testing.T) { // move the third top stack a item onto // the top stack item c. // The final stack will be [b,c,a] - v.executeOp(stack.ROT, ctx) + _, err = v.executeOp(stack.ROT, ctx) + assert.Nil(t, err) // Stack should have three items assert.Equal(t, 3, ctx.Estack.Len()) @@ -103,7 +105,8 @@ func TestSwapOp(t *testing.T) { // Swaps the top two stack items. // The final stack will be [b,a] - v.executeOp(stack.SWAP, ctx) + _, err = v.executeOp(stack.SWAP, ctx) + assert.Nil(t, err) // Stack should have two items assert.Equal(t, 2, ctx.Estack.Len()) @@ -138,7 +141,8 @@ func TestTuckOp(t *testing.T) { // copy the top stack item c and // inserts it before the second top stack item. // The final stack will be [a,c,b,c] - v.executeOp(stack.TUCK, ctx) + _, err = v.executeOp(stack.TUCK, ctx) + assert.Nil(t, err) // Stack should have four items assert.Equal(t, 4, ctx.Estack.Len()) @@ -172,7 +176,8 @@ func TestDupOp(t *testing.T) { ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a) - v.executeOp(stack.DUP, ctx) + _, err = v.executeOp(stack.DUP, ctx) + assert.Nil(t, err) // Stack should have two items assert.Equal(t, 2, ctx.Estack.Len()) @@ -204,7 +209,8 @@ func TestNipOp(t *testing.T) { ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a).Push(b).Push(c) - v.executeOp(stack.NIP, ctx) + _, err = v.executeOp(stack.NIP, ctx) + assert.Nil(t, err) // Stack should have two items assert.Equal(t, 2, ctx.Estack.Len()) @@ -236,7 +242,8 @@ func TestOverOp(t *testing.T) { // OVER copies the second top stack item a // onto the top stack item b. // the new stack will be [a,b,a]. - v.executeOp(stack.OVER, ctx) + _, err = v.executeOp(stack.OVER, ctx) + assert.Nil(t, err) // Stack should have three items assert.Equal(t, 3, ctx.Estack.Len()) @@ -280,7 +287,8 @@ func TestPickOp(t *testing.T) { // has index len(stack)-n-1 (= 3-2-1= 0) // onto the top stack item. // The final stack will be [a,b,c,a] - v.executeOp(stack.PICK, ctx) + _, err = v.executeOp(stack.PICK, ctx) + assert.Nil(t, err) // Stack should have four items assert.Equal(t, 4, ctx.Estack.Len()) @@ -327,7 +335,8 @@ func TestXswapOp(t *testing.T) { // is located in position len(stack)-n-1 (= 3-2-1= 0) // with the top stack item. // The final stack will be [c,b,a] - v.executeOp(stack.XSWAP, ctx) + _, err = v.executeOp(stack.XSWAP, ctx) + assert.Nil(t, err) // Stack should have three items assert.Equal(t, 3, ctx.Estack.Len()) @@ -370,7 +379,8 @@ func TestXTuckOp(t *testing.T) { // and insert the top stack item c // to the position len(stack)-n (= 3-2 = 1) // of the stack.The final stack will be [a,c,b,c] - v.executeOp(stack.XTUCK, ctx) + _, err = v.executeOp(stack.XTUCK, ctx) + assert.Nil(t, err) // Stack should have four items assert.Equal(t, 4, ctx.Estack.Len()) @@ -410,7 +420,8 @@ func TestXDepthOp(t *testing.T) { // push integer whose value is len(stack) (2) // on top of the stack - v.executeOp(stack.DEPTH, ctx) + _, err = v.executeOp(stack.DEPTH, ctx) + assert.Nil(t, err) // Stack should have three items assert.Equal(t, 3, ctx.Estack.Len()) @@ -444,7 +455,8 @@ func TestDupFromAltStackOp(t *testing.T) { ctx.Estack.Push(a) ctx.Astack.Push(b) - v.executeOp(stack.DUPFROMALTSTACK, ctx) + _, err = v.executeOp(stack.DUPFROMALTSTACK, ctx) + assert.Nil(t, err) assert.Equal(t, 1, ctx.Astack.Len()) assert.Equal(t, 2, ctx.Estack.Len()) @@ -473,7 +485,8 @@ func TestToAltStackOp(t *testing.T) { ctx.Estack.Push(a) ctx.Astack.Push(b) - v.executeOp(stack.TOALTSTACK, ctx) + _, err = v.executeOp(stack.TOALTSTACK, ctx) + assert.Nil(t, err) assert.Equal(t, 2, ctx.Astack.Len()) assert.Equal(t, 0, ctx.Estack.Len()) @@ -498,7 +511,8 @@ func TestFromAltStackOp(t *testing.T) { ctx.Estack.Push(a) ctx.Astack.Push(b) - v.executeOp(stack.FROMALTSTACK, ctx) + _, err = v.executeOp(stack.FROMALTSTACK, ctx) + assert.Nil(t, err) assert.Equal(t, 0, ctx.Astack.Len()) assert.Equal(t, 2, ctx.Estack.Len()) @@ -537,7 +551,8 @@ func TestXDropOp(t *testing.T) { // len(stack)-n-1 = 3-2-1 = 0. // Therefore a is removed from the stack. // Only b, c remain on the stack. - v.executeOp(stack.XDROP, ctx) + _, err = v.executeOp(stack.XDROP, ctx) + assert.Nil(t, err) assert.Equal(t, 2, ctx.Estack.Len()) diff --git a/pkg/vm/vmopscrypto_test.go b/pkg/vm/vmopscrypto_test.go index 08e26a786..3ceba1b75 100644 --- a/pkg/vm/vmopscrypto_test.go +++ b/pkg/vm/vmopscrypto_test.go @@ -17,7 +17,8 @@ func TestSha1Op(t *testing.T) { ctx := stack.NewContext([]byte{}) ctx.Estack.Push(ba1) - v.executeOp(stack.SHA1, ctx) + _, err := v.executeOp(stack.SHA1, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -40,7 +41,8 @@ func TestSha256Op(t *testing.T) { ctx := stack.NewContext([]byte{}) ctx.Estack.Push(ba1) - v.executeOp(stack.SHA256, ctx) + _, err := v.executeOp(stack.SHA256, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -63,7 +65,8 @@ func TestHash160Op(t *testing.T) { ctx := stack.NewContext([]byte{}) ctx.Estack.Push(ba1) - v.executeOp(stack.HASH160, ctx) + _, err := v.executeOp(stack.HASH160, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -86,7 +89,8 @@ func TestHash256Op(t *testing.T) { ctx := stack.NewContext([]byte{}) ctx.Estack.Push(ba1) - v.executeOp(stack.HASH256, ctx) + _, err := v.executeOp(stack.HASH256, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) From a976c4d04ff667f921d61edda61460684ff6650e Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 12 Aug 2019 16:29:24 +0300 Subject: [PATCH 69/81] pkg/vm: go fmt Some errors were introduced by github merges. --- pkg/vm/vm_ops.go | 2 +- pkg/vm/vm_ops_stackmani.go | 2 +- pkg/vm/vm_ops_stackmani_test.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/vm/vm_ops.go b/pkg/vm/vm_ops.go index 0800058fe..d26ea5af4 100644 --- a/pkg/vm/vm_ops.go +++ b/pkg/vm/vm_ops.go @@ -36,7 +36,7 @@ var opFunc = map[stack.Instruction]stackInfo{ stack.MIN: Min, stack.MAX: Max, stack.WITHIN: Within, - stack.NUMEQUAL: NumEqual, + stack.NUMEQUAL: NumEqual, stack.NUMNOTEQUAL: NumNotEqual, stack.BOOLAND: BoolAnd, stack.BOOLOR: BoolOr, diff --git a/pkg/vm/vm_ops_stackmani.go b/pkg/vm/vm_ops_stackmani.go index 1b1670a1c..ebeaafca0 100644 --- a/pkg/vm/vm_ops_stackmani.go +++ b/pkg/vm/vm_ops_stackmani.go @@ -226,7 +226,7 @@ func DEPTH(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, r return FAULT, err } - ctx.Estack.Push(length) + ctx.Estack.Push(length) return NONE, nil } diff --git a/pkg/vm/vm_ops_stackmani_test.go b/pkg/vm/vm_ops_stackmani_test.go index 669ede0d2..12e2e176e 100644 --- a/pkg/vm/vm_ops_stackmani_test.go +++ b/pkg/vm/vm_ops_stackmani_test.go @@ -302,7 +302,7 @@ func TestPickOp(t *testing.T) { itemB, err := ctx.Estack.PopInt() assert.Nil(t, err) - itemA2, err := ctx.Estack.PopInt() + itemA2, err := ctx.Estack.PopInt() assert.Nil(t, err) assert.Equal(t, int64(3), itemA.Value().Int64()) From 06f9e1d123a651c7df7c8854965ac9367da63ec9 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 12 Aug 2019 16:36:06 +0300 Subject: [PATCH 70/81] pkg/(crypto|vm): fix GolangCI errcheck warnings Like: Error return value of alg.Write is not checked (from errcheck) Actually even though the hash.Hash implements an io.Writer interface (that return meaningful things on .Write()) it has this comment in its documentation: // Write (via the embedded io.Writer interface) adds more data to the running hash. // It never returns an error. so it should be OK to ignore return results here. --- pkg/crypto/rfc6979/example_test.go | 4 ++-- pkg/vm/vmopscrypto.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/crypto/rfc6979/example_test.go b/pkg/crypto/rfc6979/example_test.go index 29693c272..b756ea80e 100755 --- a/pkg/crypto/rfc6979/example_test.go +++ b/pkg/crypto/rfc6979/example_test.go @@ -26,7 +26,7 @@ func ExampleSignECDSA() { // Hash a message. alg := sha512.New() - alg.Write([]byte("I am a potato.")) + _, _ = alg.Write([]byte("I am a potato.")) hash := alg.Sum(nil) // Sign the message. You don't need a PRNG for this. @@ -59,7 +59,7 @@ func ExampleSignDSA() { // Hash a message. alg := sha1.New() - alg.Write([]byte("I am a potato.")) + _, _ = alg.Write([]byte("I am a potato.")) hash := alg.Sum(nil) // Sign the message. You don't need a PRNG for this. diff --git a/pkg/vm/vmopscrypto.go b/pkg/vm/vmopscrypto.go index e38ab7402..5560c1d80 100644 --- a/pkg/vm/vmopscrypto.go +++ b/pkg/vm/vmopscrypto.go @@ -21,7 +21,7 @@ func SHA1(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rs } alg := sha1.New() - alg.Write(ba.Value()) + _, _ = alg.Write(ba.Value()) hash := alg.Sum(nil) res := stack.NewByteArray(hash) From 3bc195659a7ee2e1a09b0accbf57a77a8f73ef91 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 12 Aug 2019 16:40:19 +0300 Subject: [PATCH 71/81] pkg/vm: fix error handling in XDROP() Found by GolangCI: Error return value of ctx.Estack.Remove is not checked (from errcheck) nilness: impossible condition: nil != nil (from govet) --- pkg/vm/vm_ops_stackmani.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/vm/vm_ops_stackmani.go b/pkg/vm/vm_ops_stackmani.go index ebeaafca0..77448ff2f 100644 --- a/pkg/vm/vm_ops_stackmani.go +++ b/pkg/vm/vm_ops_stackmani.go @@ -291,7 +291,7 @@ func XDROP(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, r return FAULT, err } - ctx.Estack.Remove(uint16(n.Value().Uint64())) + _, err = ctx.Estack.Remove(uint16(n.Value().Uint64())) if err != nil { return FAULT, err } From 1fb66d6b73ae46bbccbadb25fb3d42b696fb6ed2 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 12 Aug 2019 17:12:05 +0300 Subject: [PATCH 72/81] pkg/vm/stack: improve Array testing code slightly GolangCI complained: testArray is unused (from deadcode) But this function was actually wrong being a copy-paste of testMakeStackMap(), it also didn't conform to testMake... naming scheme, so this fixes it. To make thing more uniform NewArray() was also changed to return error, map_test.go code adjusted to this changes and finally array_test.go was added as a stub for future Array testing. --- pkg/vm/stack/array.go | 4 ++-- pkg/vm/stack/array_test.go | 16 ++++++++++++++++ pkg/vm/stack/map_test.go | 6 +++--- pkg/vm/stack/test_helper.go | 4 ++-- 4 files changed, 23 insertions(+), 7 deletions(-) create mode 100644 pkg/vm/stack/array_test.go diff --git a/pkg/vm/stack/array.go b/pkg/vm/stack/array.go index c11730b1d..badb03cb0 100644 --- a/pkg/vm/stack/array.go +++ b/pkg/vm/stack/array.go @@ -22,11 +22,11 @@ func (a *Array) Value() []Item { } // NewArray returns a new Array. -func NewArray(val []Item) *Array { +func NewArray(val []Item) (*Array, error) { return &Array{ &abstractItem{}, val, - } + }, nil } // Hash overrides the default abstract hash method. diff --git a/pkg/vm/stack/array_test.go b/pkg/vm/stack/array_test.go new file mode 100644 index 000000000..92b5b3bd0 --- /dev/null +++ b/pkg/vm/stack/array_test.go @@ -0,0 +1,16 @@ +package stack + +import ( + "testing" + +// it's a stub at the moment, but will need it anyway +// "github.com/stretchr/testify/assert" +) + +func TestArray(t *testing.T) { + var a Item = testMakeStackInt(t, 3) + var b Item = testMakeStackInt(t, 6) + var c Item = testMakeStackInt(t, 9) + var ta = testMakeArray(t, []Item{a, b, c}) + _ = ta +} diff --git a/pkg/vm/stack/map_test.go b/pkg/vm/stack/map_test.go index 2c2091dc4..071e7fcc7 100644 --- a/pkg/vm/stack/map_test.go +++ b/pkg/vm/stack/map_test.go @@ -16,7 +16,7 @@ func TestMap(t *testing.T) { b: a, }) var e = NewContext([]byte{1, 2, 3, 4}) - var f = NewArray([]Item{a, b}) + var f = testMakeArray(t, []Item{a, b}) val := map[Item]Item{ a: c, @@ -47,7 +47,7 @@ func TestMap(t *testing.T) { valueE, _ := m.ValueOfKey(NewContext([]byte{1, 2, 3, 4})) assert.Equal(t, d, valueE) - valueF, _ := m.ValueOfKey(NewArray([]Item{a, b})) + valueF, _ := m.ValueOfKey(testMakeArray(t, []Item{a, b})) assert.Equal(t, e, valueF) valueX, _ := m.ValueOfKey(NewByteArray([]byte{1, 2, 35})) @@ -100,7 +100,7 @@ func TestMap(t *testing.T) { assert.Nil(t, err) assert.Equal(t, true, checkContext.Value()) - checkArray, err := CompareHash(f, NewArray([]Item{a, b})) + checkArray, err := CompareHash(f, testMakeArray(t, []Item{a, b})) assert.Nil(t, err) assert.Equal(t, true, checkArray.Value()) } diff --git a/pkg/vm/stack/test_helper.go b/pkg/vm/stack/test_helper.go index b2615141e..29be378dd 100644 --- a/pkg/vm/stack/test_helper.go +++ b/pkg/vm/stack/test_helper.go @@ -49,8 +49,8 @@ func testMakeStackMap(t *testing.T, m map[Item]Item) *Map { return a } -func testArray(t *testing.T, m map[Item]Item) *Map { - a, err := NewMap(m) +func testMakeArray(t *testing.T, v []Item) *Array { + a, err := NewArray(v) assert.Nil(t, err) return a } From 6be27ad4b0e22404322e3c685d110cc8daebb078 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 12 Aug 2019 17:16:47 +0300 Subject: [PATCH 73/81] pkg/vm/stack: s,testPeakInteger,testPeakInteger, Fix obvious typo. --- pkg/vm/stack/stack_test.go | 4 ++-- pkg/vm/stack/test_helper.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/vm/stack/stack_test.go b/pkg/vm/stack/stack_test.go index 246e983f4..148931a5a 100644 --- a/pkg/vm/stack/stack_test.go +++ b/pkg/vm/stack/stack_test.go @@ -97,7 +97,7 @@ func TestStackPeekMutability(t *testing.T) { testStack.Push(a).Push(b) - peekedItem := testPeakInteger(t, testStack, 0) + peekedItem := testPeekInteger(t, testStack, 0) assert.Equal(t, true, peekedItem.Equal(b)) // Check that by modifying the peeked value, @@ -122,7 +122,7 @@ func TestStackPeek(t *testing.T) { // i starts at 0, j starts at len(values)-1 for i, j := 0, len(values)-1; j >= 0; i, j = i+1, j-1 { - peekedItem := testPeakInteger(t, testStack, uint16(i)) + peekedItem := testPeekInteger(t, testStack, uint16(i)) a := testMakeStackInt(t, values[j]) fmt.Printf("%#v\n", peekedItem.val.Int64()) diff --git a/pkg/vm/stack/test_helper.go b/pkg/vm/stack/test_helper.go index 29be378dd..d12bea71f 100644 --- a/pkg/vm/stack/test_helper.go +++ b/pkg/vm/stack/test_helper.go @@ -10,7 +10,7 @@ import ( ) // helper functions -func testPeakInteger(t *testing.T, tStack *RandomAccess, n uint16) *Int { +func testPeekInteger(t *testing.T, tStack *RandomAccess, n uint16) *Int { stackElement, err := tStack.Peek(n) assert.Nil(t, err) item, err := stackElement.Integer() From d6c3f74e3c6b63c57d62ca1d1e99f688a2848f2e Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 12 Aug 2019 17:53:19 +0300 Subject: [PATCH 74/81] pkg/vm/stack: make some use of testReadInt64() GolangCI complains: testReadInt64 is unused (from deadcode) Fix it to always provide correctly-sized buffer for the binary.Read(). --- pkg/vm/stack/int_test.go | 2 ++ pkg/vm/stack/test_helper.go | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/pkg/vm/stack/int_test.go b/pkg/vm/stack/int_test.go index 25d360183..46a2c302e 100644 --- a/pkg/vm/stack/int_test.go +++ b/pkg/vm/stack/int_test.go @@ -64,6 +64,8 @@ func TestByteArrConversion(t *testing.T) { ba, err := a.ByteArray() assert.Nil(t, err) + assert.Equal(t, num, testReadInt64(ba.val)) + have, err := ba.Integer() assert.Nil(t, err) diff --git a/pkg/vm/stack/test_helper.go b/pkg/vm/stack/test_helper.go index d12bea71f..56a5ff89d 100644 --- a/pkg/vm/stack/test_helper.go +++ b/pkg/vm/stack/test_helper.go @@ -38,7 +38,11 @@ func testMakeStackInt(t *testing.T, num int64) *Int { func testReadInt64(data []byte) int64 { var ret int64 - buf := bytes.NewBuffer(data) + var arr [8]byte + + // expands or shrinks data automatically + copy(arr[:], data) + buf := bytes.NewBuffer(arr[:]) binary.Read(buf, binary.LittleEndian, &ret) return ret } From 613bad36e07716501444235e82f71b5c1b2b19d6 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 12 Aug 2019 17:56:51 +0300 Subject: [PATCH 75/81] pkg/vm/stack: fix unused binary.Read() result in testReadInt64() GolangCI: Error return value of binary.Read is not checked (from errcheck) --- pkg/vm/stack/int_test.go | 2 +- pkg/vm/stack/test_helper.go | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/pkg/vm/stack/int_test.go b/pkg/vm/stack/int_test.go index 46a2c302e..07fee3958 100644 --- a/pkg/vm/stack/int_test.go +++ b/pkg/vm/stack/int_test.go @@ -64,7 +64,7 @@ func TestByteArrConversion(t *testing.T) { ba, err := a.ByteArray() assert.Nil(t, err) - assert.Equal(t, num, testReadInt64(ba.val)) + assert.Equal(t, num, testReadInt64(t, ba.val)) have, err := ba.Integer() assert.Nil(t, err) diff --git a/pkg/vm/stack/test_helper.go b/pkg/vm/stack/test_helper.go index 56a5ff89d..ba497cffc 100644 --- a/pkg/vm/stack/test_helper.go +++ b/pkg/vm/stack/test_helper.go @@ -36,14 +36,15 @@ func testMakeStackInt(t *testing.T, num int64) *Int { return a } -func testReadInt64(data []byte) int64 { +func testReadInt64(t *testing.T, data []byte) int64 { var ret int64 var arr [8]byte // expands or shrinks data automatically copy(arr[:], data) buf := bytes.NewBuffer(arr[:]) - binary.Read(buf, binary.LittleEndian, &ret) + err := binary.Read(buf, binary.LittleEndian, &ret) + assert.Nil(t, err) return ret } From bab187f4a8f59abdca2a9869789677566bec2332 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 12 Aug 2019 17:59:59 +0300 Subject: [PATCH 76/81] pkg/vm: drop bogus err check in BoolAnd() and BoolOr() GolangCI: nilness: impossible condition: nil != nil (from govet) --- pkg/vm/vm_ops_maths.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/pkg/vm/vm_ops_maths.go b/pkg/vm/vm_ops_maths.go index ab4b52f71..264278092 100644 --- a/pkg/vm/vm_ops_maths.go +++ b/pkg/vm/vm_ops_maths.go @@ -296,9 +296,6 @@ func BoolAnd(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, return FAULT, err } res := bool1.And(bool2) - if err != nil { - return FAULT, err - } ctx.Estack.Push(res) @@ -315,9 +312,6 @@ func BoolOr(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, return FAULT, err } res := bool1.Or(bool2) - if err != nil { - return FAULT, err - } ctx.Estack.Push(res) From 4e40467dc38f1261f7523fed7d8731d7e9b6ffdb Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 12 Aug 2019 18:05:04 +0300 Subject: [PATCH 77/81] pkg/vm/stack: fix S1008 gosimple warning from GolangCI S1008: should use 'return ' instead of 'if { return }; return ' (from gosimple) --- pkg/vm/stack/Int.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pkg/vm/stack/Int.go b/pkg/vm/stack/Int.go index 903190541..d1c06e572 100644 --- a/pkg/vm/stack/Int.go +++ b/pkg/vm/stack/Int.go @@ -22,10 +22,7 @@ func NewInt(val *big.Int) (*Int, error) { // Equal will check if two integers hold equal value func (i *Int) Equal(s *Int) bool { - if i.val.Cmp(s.val) != 0 { - return false - } - return true + return i.val.Cmp(s.val) == 0 } // Add will add two stackIntegers together From f4451032609e4fcd5c0b42c26c570d480cd4b43a Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 12 Aug 2019 18:08:39 +0300 Subject: [PATCH 78/81] pkg/vm/stack: fix S1002 gosimple warnings in map.go S1002: should omit comparison to bool constant, can be simplified to ok.Value() (from gosimple) --- pkg/vm/stack/map.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/vm/stack/map.go b/pkg/vm/stack/map.go index b8e462c71..9db2d6e4d 100644 --- a/pkg/vm/stack/map.go +++ b/pkg/vm/stack/map.go @@ -43,7 +43,7 @@ func (m *Map) ContainsKey(key Item) (*Boolean, error) { for k := range m.Value() { if ok, err := CompareHash(k, key); err != nil { return nil, err - } else if ok.Value() == true { + } else if ok.Value() { return ok, nil } @@ -63,7 +63,7 @@ func (m *Map) Remove(key Item) error { for k := range m.Value() { if ok, err := CompareHash(k, key); err != nil { return err - } else if ok.Value() == true { + } else if ok.Value() { d = k } @@ -80,7 +80,7 @@ func (m *Map) Add(key Item, value Item) error { for k := range m.Value() { if ok, err := CompareHash(k, key); err != nil { return err - } else if ok.Value() == true { + } else if ok.Value() { return errors.New("try to insert duplicate key! ") } } @@ -94,7 +94,7 @@ func (m *Map) ValueOfKey(key Item) (Item, error) { for k, v := range m.Value() { if ok, err := CompareHash(k, key); err != nil { return nil, err - } else if ok.Value() == true { + } else if ok.Value() { return v, nil } From 9be53e5961b00745af99863647ec639fab1eba5e Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 12 Aug 2019 18:13:04 +0300 Subject: [PATCH 79/81] pkg/vm/stack: fix SA4006 GolangCI warning in TestStackPushPop() SA4006: this value of stackElement is never used (from staticcheck) --- pkg/vm/stack/stack_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/vm/stack/stack_test.go b/pkg/vm/stack/stack_test.go index 148931a5a..2241782a2 100644 --- a/pkg/vm/stack/stack_test.go +++ b/pkg/vm/stack/stack_test.go @@ -51,7 +51,7 @@ func TestStackPushPop(t *testing.T) { assert.Equal(t, true, item.Equal(a)) // We should get an error as there are nomore items left to pop - stackElement, err = testStack.Pop() + _, err = testStack.Pop() assert.NotNil(t, err) } From bc87d3e8e6f1fb6b6c7ec57358f4fea7398fd5da Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 12 Aug 2019 18:15:48 +0300 Subject: [PATCH 80/81] pkg/vm: fix GolangCI's SA4009 in RET() SA4009: argument ctx is overwritten before first use (from staticcheck) It should be OK here. --- pkg/vm/vm_ops_flow.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/vm/vm_ops_flow.go b/pkg/vm/vm_ops_flow.go index 71f32d42d..45a8248da 100644 --- a/pkg/vm/vm_ops_flow.go +++ b/pkg/vm/vm_ops_flow.go @@ -9,6 +9,7 @@ import ( // RET Returns from the current context // Returns HALT if there are nomore context's to run func RET(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + _ = ctx // fix SA4009 warning // Pop current context from the Inovation stack ctx, err := istack.PopCurrentContext() From f068f4c0eda9ddaf0f4b02aaef3d0bf2bea1e244 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 12 Aug 2019 18:19:26 +0300 Subject: [PATCH 81/81] pkg/vm/stack: drop unused (*RandomAccess).items() GolangCI complains: U1000: func (*RandomAccess).items is unused And it looks like everyone and their dog just use ras.vals without any wrappers when there is a need to access it, so drop the wrapper. --- pkg/vm/stack/stack.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/pkg/vm/stack/stack.go b/pkg/vm/stack/stack.go index 2a1b01693..eb08e5d52 100644 --- a/pkg/vm/stack/stack.go +++ b/pkg/vm/stack/stack.go @@ -23,11 +23,6 @@ func New() *RandomAccess { } } -// Items will return all items in the stack -func (ras *RandomAccess) items() []Item { - return ras.vals -} - //Len will return the length of the stack func (ras *RandomAccess) Len() int { if ras.vals == nil {