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/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"` +} diff --git a/pkg/vm/stack/Int.go b/pkg/vm/stack/Int.go index ac9a4cdba..d1c06e572 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 { @@ -19,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 @@ -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{ @@ -92,3 +99,104 @@ 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 +} + +// 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 +} + +// 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 +} + +// 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) +} + +// 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)) +} + +// Min returns the mininum between two integers. +func Min(a *Int, b *Int) *Int { + if a.Lte(b) { + return a + } + return b +} + +// Max returns the maximun between two integers. +func Max(a *Int, b *Int) *Int { + if a.Gte(b) { + 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.Gte(a) && i.Lt(b) +} diff --git a/pkg/vm/stack/array.go b/pkg/vm/stack/array.go index 96fe876a4..badb03cb0 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, error) { + return &Array{ + &abstractItem{}, + val, + }, nil +} + +// 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/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/boolean.go b/pkg/vm/stack/boolean.go index 66e3647e5..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 @@ -24,3 +28,29 @@ 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()) +} + +// 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) +} + +// 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..855c58f73 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. @@ -120,6 +121,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 +156,19 @@ 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 +} + +// 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/int_test.go b/pkg/vm/stack/int_test.go index 25d360183..07fee3958 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(t, ba.val)) + have, err := ba.Integer() assert.Nil(t, err) diff --git a/pkg/vm/stack/map.go b/pkg/vm/stack/map.go new file mode 100644 index 000000000..9db2d6e4d --- /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() { + 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() { + 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() { + 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() { + 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..071e7fcc7 --- /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 = testMakeArray(t, []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(testMakeArray(t, []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, testMakeArray(t, []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/stack.go b/pkg/vm/stack/stack.go index 8d1ac5b78..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 { @@ -127,6 +122,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 @@ -148,3 +158,29 @@ 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() +} + +// 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/stack/stack_test.go b/pkg/vm/stack/stack_test.go index 246e983f4..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) } @@ -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/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..ba497cffc 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() @@ -36,9 +36,26 @@ 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 - buf := bytes.NewBuffer(data) - binary.Read(buf, binary.LittleEndian, &ret) + var arr [8]byte + + // expands or shrinks data automatically + copy(arr[:], data) + buf := bytes.NewBuffer(arr[:]) + err := binary.Read(buf, binary.LittleEndian, &ret) + assert.Nil(t, err) return ret } + +func testMakeStackMap(t *testing.T, m map[Item]Item) *Map { + a, err := NewMap(m) + assert.Nil(t, err) + return a +} + +func testMakeArray(t *testing.T, v []Item) *Array { + a, err := NewArray(v) + assert.Nil(t, err) + return a +} diff --git a/pkg/vm/vm_ops.go b/pkg/vm/vm_ops.go index 39b796389..d26ea5af4 100644 --- a/pkg/vm/vm_ops.go +++ b/pkg/vm/vm_ops.go @@ -5,13 +5,65 @@ 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.ADD: Add, - stack.SUB: Sub, - stack.PUSHBYTES1: PushNBytes, - stack.PUSHBYTES75: PushNBytes, - stack.RET: RET, - stack.EQUAL: EQUAL, - stack.THROWIFNOT: THROWIFNOT, + stack.TUCK: TUCK, + stack.SWAP: SWAP, + stack.ROT: ROT, + stack.ROLL: ROLL, + stack.PICK: PICK, + stack.OVER: OVER, + stack.NIP: NIP, + stack.DUP: DUP, + stack.DROP: DROP, + stack.DEPTH: DEPTH, + stack.XTUCK: XTUCK, + stack.XSWAP: XSWAP, + stack.XDROP: XDROP, + stack.FROMALTSTACK: FROMALTSTACK, + stack.TOALTSTACK: TOALTSTACK, + stack.DUPFROMALTSTACK: DUPFROMALTSTACK, + stack.JMPIFNOT: JMPIFNOT, + stack.JMPIF: JMPIF, + stack.JMP: JMP, + stack.NOP: NOP, + stack.HASH256: HASH256, + stack.HASH160: HASH160, + stack.SHA256: SHA256, + stack.SHA1: SHA1, + stack.XOR: Xor, + stack.OR: Or, + stack.AND: And, + stack.INVERT: Invert, + stack.MIN: Min, + stack.MAX: Max, + stack.WITHIN: Within, + 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_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..9e00a487e --- /dev/null +++ b/pkg/vm/vm_ops_bitwise_test.go @@ -0,0 +1,142 @@ +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) + _, err = v.executeOp(stack.INVERT, ctx) + assert.Nil(t, err) + + // 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 + _, err = v.executeOp(stack.AND, ctx) + assert.Nil(t, err) + + // 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) + _, err = v.executeOp(stack.OR, ctx) + assert.Nil(t, err) + + // 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) + _, err = v.executeOp(stack.XOR, ctx) + assert.Nil(t, err) + + // 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) + + _, err = v.executeOp(stack.EQUAL, ctx) + assert.Nil(t, err) + + // 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()) +} 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_flow.go b/pkg/vm/vm_ops_flow.go index 67ca4f825..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() @@ -25,3 +26,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..0094b4f3f --- /dev/null +++ b/pkg/vm/vm_ops_flow_test.go @@ -0,0 +1,174 @@ +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) + + _, err = v.executeOp(stack.NOP, ctx) + assert.Nil(t, err) + + // 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 + _, err = v.executeOp(stack.JMP, ctx) + assert.Nil(t, err) + + // 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 + _, err := v.executeOp(stack.JMPIF, ctx) + assert.Nil(t, err) + + // 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 + _, err := v.executeOp(stack.JMPIF, ctx) + assert.Nil(t, err) + + // 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 + _, err := v.executeOp(stack.JMPIFNOT, ctx) + assert.Nil(t, err) + + // 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 + _, err := v.executeOp(stack.JMPIFNOT, ctx) + assert.Nil(t, err) + + // Stack should have one item + assert.Equal(t, 0, ctx.Estack.Len()) + + // ctx.IP() = ctx.ip + 1 + assert.Equal(t, 3, ctx.IP()) +} diff --git a/pkg/vm/vm_ops_maths.go b/pkg/vm/vm_ops_maths.go index a15596a30..f6973e83b 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" ) @@ -34,7 +36,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) @@ -42,6 +44,424 @@ 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 +} + +// 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. +// 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 +} + +// 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 +} + +// 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) { + + 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 +} + +// 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) + 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) + + 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; +// 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 +} + +// 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) { + + 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 +// 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 +} + +// 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 popTwoIntegers(ctx *stack.Context) (*stack.Int, *stack.Int, error) { operandA, err := ctx.Estack.PopInt() if err != nil { @@ -55,6 +475,23 @@ func popTwoIntegers(ctx *stack.Context) (*stack.Int, *stack.Int, error) { 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() @@ -68,3 +505,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 4964e6923..9fb92c203 100644 --- a/pkg/vm/vm_ops_maths_test.go +++ b/pkg/vm/vm_ops_maths_test.go @@ -8,31 +8,71 @@ import ( "github.com/stretchr/testify/assert" ) -func TestAddOp(t *testing.T) { +func TestIncOp(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() - } + assert.Nil(t, err) ctx := stack.NewContext([]byte{}) - ctx.Estack.Push(a).Push(b) + ctx.Estack.Push(a) - v.executeOp(stack.ADD, ctx) + _, err = v.executeOp(stack.INC, ctx) + assert.Nil(t, err) // Stack should have one item 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()) +} + +func TestDecOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(20)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a) + + _, err = v.executeOp(stack.DEC, ctx) + assert.Nil(t, err) + + // 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(19), item.Value().Int64()) +} + +func TestAddOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(20)) + assert.Nil(t, err) + + b, err := stack.NewInt(big.NewInt(23)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b) + + _, err = v.executeOp(stack.ADD, ctx) + assert.Nil(t, err) + + // 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(43), item.Value().Int64()) @@ -43,27 +83,566 @@ 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) - 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()) item, err := ctx.Estack.PopInt() - if err != nil { - t.Fail() - } + assert.Nil(t, err) assert.Equal(t, int64(-10), item.Value().Int64()) } + +func TestDivOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(10)) + assert.Nil(t, err) + + b, err := stack.NewInt(big.NewInt(4)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b) + + _, err = v.executeOp(stack.DIV, ctx) + assert.Nil(t, err) + + // 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(2), item.Value().Int64()) +} + +func TestModOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(15)) + assert.Nil(t, err) + + b, err := stack.NewInt(big.NewInt(4)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b) + + _, err = v.executeOp(stack.MOD, ctx) + assert.Nil(t, err) + + // 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(3), item.Value().Int64()) +} + +func TestNzOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(20)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a) + + _, err = v.executeOp(stack.NZ, ctx) + assert.Nil(t, err) + + // 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 TestMulOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(20)) + assert.Nil(t, err) + + b, err := stack.NewInt(big.NewInt(20)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b) + + _, err = v.executeOp(stack.MUL, ctx) + assert.Nil(t, err) + + // 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(400), item.Value().Int64()) +} + +func TestAbsOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(-20)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a) + + _, err = v.executeOp(stack.ABS, ctx) + assert.Nil(t, err) + + // 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(20), item.Value().Int64()) +} + +func TestNotOp(t *testing.T) { + + v := VM{} + + b := stack.NewBoolean(false) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(b) + + _, err := v.executeOp(stack.NOT, ctx) + assert.Nil(t, err) + + // 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 TestNumEqual(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(6)) + 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) + + _, err = v.executeOp(stack.NUMEQUAL, ctx) + assert.Nil(t, err) + + // 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 TestNumNotEqual(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(5)) + 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) + + _, err = v.executeOp(stack.NUMNOTEQUAL, ctx) + assert.Nil(t, err) + + // 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 TestSignOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(-20)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a) + + _, err = v.executeOp(stack.SIGN, ctx) + assert.Nil(t, err) + + // 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(-1), item.Value().Int64()) +} + +func TestNegateOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(-20)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a) + + _, err = v.executeOp(stack.NEGATE, ctx) + assert.Nil(t, err) + + // 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(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 + _, err = v.executeOp(stack.LTE, ctx) + assert.Nil(t, err) + + // 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 + _, err = v.executeOp(stack.GTE, ctx) + assert.Nil(t, err) + + // 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{} + + a, err := stack.NewInt(big.NewInt(2)) + assert.Nil(t, err) + + b, err := stack.NewInt(big.NewInt(3)) + 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.Lsh(b) and place + // the result on top of the evaluation + // stack + _, err = v.executeOp(stack.SHL, ctx) + assert.Nil(t, err) + + // 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(16), item.Value().Int64()) +} + +func TestShrOp(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.Rsh(b) and place + // the result on top of the evaluation + // stack + _, err = v.executeOp(stack.SHR, ctx) + assert.Nil(t, err) + + // 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(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) + + _, err := v.executeOp(stack.BOOLAND, ctx) + assert.Nil(t, err) + + // 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 TestBoolOrOp(t *testing.T) { + + v := VM{} + + a := stack.NewBoolean(false) + b := stack.NewBoolean(true) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b) + + _, err := v.executeOp(stack.BOOLOR, ctx) + assert.Nil(t, err) + + // 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 TestLtOp(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 + _, err = v.executeOp(stack.LT, ctx) + assert.Nil(t, err) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.PopBoolean() + assert.Nil(t, err) + + assert.Equal(t, false, item.Value()) +} + +func TestGtOp(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 + _, err = v.executeOp(stack.GT, ctx) + assert.Nil(t, err) + + // 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 TestMinOp(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) + + _, err = v.executeOp(stack.MIN, ctx) + assert.Nil(t, err) + + // 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(2), item.Value().Int64()) +} + +func TestMaxOp(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) + + _, err = v.executeOp(stack.MAX, ctx) + assert.Nil(t, err) + + // 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 TestWithinOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(5)) + assert.Nil(t, err) + + b, err := stack.NewInt(big.NewInt(2)) + assert.Nil(t, err) + + c, err := stack.NewInt(big.NewInt(10)) + assert.Nil(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. + _, err = v.executeOp(stack.WITHIN, ctx) + assert.Nil(t, err) + + // 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()) +} diff --git a/pkg/vm/vm_ops_stackmani.go b/pkg/vm/vm_ops_stackmani.go index f5e2ddc24..77448ff2f 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,282 @@ 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 +} + +// 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 +} + +// 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 +} + +// 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 +} + +// 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 +} + +// 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 +} + +// 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 +} + +// 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 +} + +// 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 +} + +// 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 +} + +// 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 +} + +// 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 + } + + _, 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..12e2e176e --- /dev/null +++ b/pkg/vm/vm_ops_stackmani_test.go @@ -0,0 +1,568 @@ +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] + _, err = v.executeOp(stack.ROLL, ctx) + assert.Nil(t, err) + + // 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] + _, err = v.executeOp(stack.ROT, ctx) + assert.Nil(t, err) + + // 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] + _, err = v.executeOp(stack.SWAP, ctx) + assert.Nil(t, err) + + // 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] + _, err = v.executeOp(stack.TUCK, ctx) + assert.Nil(t, err) + + // 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()) + +} + +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) + + _, err = v.executeOp(stack.DUP, ctx) + assert.Nil(t, err) + + // 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) + + _, err = v.executeOp(stack.NIP, ctx) + assert.Nil(t, err) + + // 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]. + _, err = v.executeOp(stack.OVER, ctx) + assert.Nil(t, err) + + // 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] + _, err = v.executeOp(stack.PICK, ctx) + assert.Nil(t, err) + + // 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{} + + 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] + _, err = v.executeOp(stack.XSWAP, ctx) + assert.Nil(t, err) + + // 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] + _, err = v.executeOp(stack.XTUCK, ctx) + assert.Nil(t, err) + + // 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 + _, err = v.executeOp(stack.DEPTH, ctx) + assert.Nil(t, err) + + // 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 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) + + _, err = v.executeOp(stack.DUPFROMALTSTACK, ctx) + assert.Nil(t, err) + + 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) + + _, err = v.executeOp(stack.TOALTSTACK, ctx) + assert.Nil(t, err) + + 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) + + _, err = v.executeOp(stack.FROMALTSTACK, ctx) + assert.Nil(t, err) + + 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. + _, err = v.executeOp(stack.XDROP, ctx) + assert.Nil(t, err) + + 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()) + +} 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 diff --git a/pkg/vm/vmopscrypto.go b/pkg/vm/vmopscrypto.go new file mode 100644 index 000000000..5560c1d80 --- /dev/null +++ b/pkg/vm/vmopscrypto.go @@ -0,0 +1,106 @@ +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) { + + ba, err := ctx.Estack.PopByteArray() + 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) { + + ba, err := ctx.Estack.PopByteArray() + 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) { + + ba, err := ctx.Estack.PopByteArray() + 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) { + + ba, err := ctx.Estack.PopByteArray() + 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..3ceba1b75 --- /dev/null +++ b/pkg/vm/vmopscrypto_test.go @@ -0,0 +1,105 @@ +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) + + _, err := v.executeOp(stack.SHA1, ctx) + assert.Nil(t, err) + + // 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) + + _, err := v.executeOp(stack.SHA256, ctx) + assert.Nil(t, err) + + // 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) + + _, err := v.executeOp(stack.HASH160, ctx) + assert.Nil(t, err) + + // 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) + + _, err := v.executeOp(stack.HASH256, ctx) + assert.Nil(t, err) + + // 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())) +}