mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-12-25 03:56:34 +00:00
Merge pull request #265 from dauTT/dauTT/vm-implement-Map-Struct-167
VM: Implement Map, Struct Stack Item: Closes #167, merging as per discussion in #283.
This commit is contained in:
commit
ce0d6d97dc
9 changed files with 393 additions and 3 deletions
|
@ -1,6 +1,9 @@
|
||||||
package stack
|
package stack
|
||||||
|
|
||||||
import "math/big"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
)
|
||||||
|
|
||||||
// Int represents an integer on the stack
|
// Int represents an integer on the stack
|
||||||
type Int struct {
|
type Int struct {
|
||||||
|
@ -140,7 +143,12 @@ func (i *Int) Gt(s *Int) bool {
|
||||||
return i.Value().Cmp(s.Value()) == 1
|
return i.Value().Cmp(s.Value()) == 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Min returns the mininum between two integers.
|
// 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 {
|
func Min(a *Int, b *Int) *Int {
|
||||||
if a.Lte(b) {
|
if a.Lte(b) {
|
||||||
return a
|
return a
|
||||||
|
@ -163,5 +171,4 @@ func Max(a *Int, b *Int) *Int {
|
||||||
func (i *Int) Within(a *Int, b *Int) bool {
|
func (i *Int) Within(a *Int, b *Int) bool {
|
||||||
// i >= a && i < b
|
// i >= a && i < b
|
||||||
return i.Gte(a) && i.Lt(b)
|
return i.Gte(a) && i.Lt(b)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
package stack
|
package stack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
// Array represents an Array of stackItems on the stack
|
// Array represents an Array of stackItems on the stack
|
||||||
type Array struct {
|
type Array struct {
|
||||||
*abstractItem
|
*abstractItem
|
||||||
|
@ -11,3 +15,22 @@ type Array struct {
|
||||||
func (a *Array) Array() (*Array, error) {
|
func (a *Array) Array() (*Array, error) {
|
||||||
return a, nil
|
return a, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Value returns the underlying Array's value
|
||||||
|
func (a *Array) Value() []Item {
|
||||||
|
return a.val
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewArray returns a new Array.
|
||||||
|
func NewArray(val []Item) *Array {
|
||||||
|
return &Array{
|
||||||
|
&abstractItem{},
|
||||||
|
val,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash overrides the default abstract hash method.
|
||||||
|
func (a *Array) Hash() (string, error) {
|
||||||
|
data := fmt.Sprintf("%T %v", a, a.Value())
|
||||||
|
return KeyGenerator([]byte(data))
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
package stack
|
package stack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
// Boolean represents a boolean value on the stack
|
// Boolean represents a boolean value on the stack
|
||||||
type Boolean struct {
|
type Boolean struct {
|
||||||
*abstractItem
|
*abstractItem
|
||||||
|
@ -44,3 +48,9 @@ func (b *Boolean) Or(a *Boolean) *Boolean {
|
||||||
c := b.Value() || a.Value()
|
c := b.Value() || a.Value()
|
||||||
return NewBoolean(c)
|
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))
|
||||||
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package stack
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
@ -69,3 +70,14 @@ func reverse(b []byte) []byte {
|
||||||
|
|
||||||
return dest
|
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))
|
||||||
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package stack
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Context represent the current execution context of the VM.
|
// Context represent the current execution context of the VM.
|
||||||
|
@ -150,3 +151,9 @@ func (c *Context) readVarBytes() ([]byte, error) {
|
||||||
}
|
}
|
||||||
return c.ReadBytes(int(n))
|
return c.ReadBytes(int(n))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hash overrides the default abstract hash method.
|
||||||
|
func (c *Context) Hash() (string, error) {
|
||||||
|
data := c.String() + fmt.Sprintf(" %v-%v-%v-%v-%v", c.ip, c.prog, c.breakPoints, c.Estack, c.Astack)
|
||||||
|
return KeyGenerator([]byte(data))
|
||||||
|
}
|
||||||
|
|
166
pkg/vm/stack/map.go
Normal file
166
pkg/vm/stack/map.go
Normal file
|
@ -0,0 +1,166 @@
|
||||||
|
package stack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/crypto/hash"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Map represents a map of key, value pair on the stack.
|
||||||
|
// Both key and value are stack Items.
|
||||||
|
type Map struct {
|
||||||
|
*abstractItem
|
||||||
|
val map[Item]Item
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMap returns a Map stack Item given
|
||||||
|
// a map whose keys and values are stack Items.
|
||||||
|
func NewMap(val map[Item]Item) (*Map, error) {
|
||||||
|
return &Map{
|
||||||
|
abstractItem: &abstractItem{},
|
||||||
|
val: val,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map will overwrite the default implementation
|
||||||
|
// to allow go to cast this item as an Map.
|
||||||
|
func (m *Map) Map() (*Map, error) {
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Boolean overrides the default Boolean method
|
||||||
|
// to convert an Map into a Boolean StackItem
|
||||||
|
func (m *Map) Boolean() (*Boolean, error) {
|
||||||
|
return NewBoolean(true), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContainsKey returns a boolean whose value is true
|
||||||
|
// iff the underlying map value contains the Item i
|
||||||
|
// as a key.
|
||||||
|
func (m *Map) ContainsKey(key Item) (*Boolean, error) {
|
||||||
|
for k := range m.Value() {
|
||||||
|
if ok, err := CompareHash(k, key); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if ok.Value() == true {
|
||||||
|
return ok, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return NewBoolean(false), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value returns the underlying map's value
|
||||||
|
func (m *Map) Value() map[Item]Item {
|
||||||
|
return m.val
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove removes the Item i from the
|
||||||
|
// underlying map's value.
|
||||||
|
func (m *Map) Remove(key Item) error {
|
||||||
|
var d Item
|
||||||
|
for k := range m.Value() {
|
||||||
|
if ok, err := CompareHash(k, key); err != nil {
|
||||||
|
return err
|
||||||
|
} else if ok.Value() == true {
|
||||||
|
d = k
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
if d != nil {
|
||||||
|
delete(m.Value(), d)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add inserts a new key, value pair of Items into
|
||||||
|
// the underlying map's value.
|
||||||
|
func (m *Map) Add(key Item, value Item) error {
|
||||||
|
for k := range m.Value() {
|
||||||
|
if ok, err := CompareHash(k, key); err != nil {
|
||||||
|
return err
|
||||||
|
} else if ok.Value() == true {
|
||||||
|
return errors.New("try to insert duplicate key! ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m.Value()[key] = value
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValueOfKey tries to get the value of the key Item
|
||||||
|
// from the map's underlying value.
|
||||||
|
func (m *Map) ValueOfKey(key Item) (Item, error) {
|
||||||
|
for k, v := range m.Value() {
|
||||||
|
if ok, err := CompareHash(k, key); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if ok.Value() == true {
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear empties the the underlying map's value.
|
||||||
|
func (m *Map) Clear() {
|
||||||
|
m.val = map[Item]Item{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompareHash compare the the Hashes of two items.
|
||||||
|
// If they are equal it returns a true boolean. Otherwise
|
||||||
|
// it returns false boolean. Item whose hashes are equal are
|
||||||
|
// to be considered equal.
|
||||||
|
func CompareHash(i1 Item, i2 Item) (*Boolean, error) {
|
||||||
|
hash1, err := i1.Hash()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
hash2, err := i2.Hash()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if hash1 == hash2 {
|
||||||
|
return NewBoolean(true), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewBoolean(false), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash overrides the default abstract hash method.
|
||||||
|
func (m *Map) Hash() (string, error) {
|
||||||
|
var hashSlice sort.StringSlice = []string{}
|
||||||
|
var data = fmt.Sprintf("%T ", m)
|
||||||
|
|
||||||
|
for k, v := range m.Value() {
|
||||||
|
hk, err := k.Hash()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
hv, err := v.Hash()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
hashSlice = append(hashSlice, hk)
|
||||||
|
hashSlice = append(hashSlice, hv)
|
||||||
|
}
|
||||||
|
hashSlice.Sort()
|
||||||
|
|
||||||
|
for _, h := range hashSlice {
|
||||||
|
data += h
|
||||||
|
}
|
||||||
|
|
||||||
|
return KeyGenerator([]byte(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeyGenerator hashes a byte slice to obtain a unique identifier.
|
||||||
|
func KeyGenerator(data []byte) (string, error) {
|
||||||
|
h, err := hash.Sha256([]byte(data))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return h.String(), nil
|
||||||
|
}
|
141
pkg/vm/stack/map_test.go
Normal file
141
pkg/vm/stack/map_test.go
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
package stack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMap(t *testing.T) {
|
||||||
|
// define Map m for testing
|
||||||
|
var a Item = testMakeStackInt(t, 10)
|
||||||
|
var b Item = NewBoolean(true)
|
||||||
|
var c Item = NewByteArray([]byte{1, 2, 34})
|
||||||
|
var d Item = testMakeStackMap(t, map[Item]Item{
|
||||||
|
a: c,
|
||||||
|
b: a,
|
||||||
|
})
|
||||||
|
var e = NewContext([]byte{1, 2, 3, 4})
|
||||||
|
var f = NewArray([]Item{a, b})
|
||||||
|
|
||||||
|
val := map[Item]Item{
|
||||||
|
a: c,
|
||||||
|
b: a,
|
||||||
|
c: b,
|
||||||
|
d: a,
|
||||||
|
e: d,
|
||||||
|
f: e,
|
||||||
|
}
|
||||||
|
m := testMakeStackMap(t, val)
|
||||||
|
|
||||||
|
// test ValueOfKey
|
||||||
|
valueA, _ := m.ValueOfKey(testMakeStackInt(t, 10))
|
||||||
|
assert.Equal(t, c, valueA)
|
||||||
|
|
||||||
|
valueB, _ := m.ValueOfKey(b)
|
||||||
|
assert.Equal(t, a, valueB)
|
||||||
|
|
||||||
|
valueC, _ := m.ValueOfKey(NewByteArray([]byte{1, 2, 34}))
|
||||||
|
assert.Equal(t, b, valueC)
|
||||||
|
|
||||||
|
valueD, _ := m.ValueOfKey(testMakeStackMap(t, map[Item]Item{
|
||||||
|
b: a,
|
||||||
|
a: c,
|
||||||
|
}))
|
||||||
|
assert.Equal(t, a, valueD)
|
||||||
|
|
||||||
|
valueE, _ := m.ValueOfKey(NewContext([]byte{1, 2, 3, 4}))
|
||||||
|
assert.Equal(t, d, valueE)
|
||||||
|
|
||||||
|
valueF, _ := m.ValueOfKey(NewArray([]Item{a, b}))
|
||||||
|
assert.Equal(t, e, valueF)
|
||||||
|
|
||||||
|
valueX, _ := m.ValueOfKey(NewByteArray([]byte{1, 2, 35}))
|
||||||
|
assert.NotEqual(t, b, valueX)
|
||||||
|
|
||||||
|
checkA, err := m.ContainsKey(a)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, true, checkA.Value())
|
||||||
|
|
||||||
|
//test ContainsKey
|
||||||
|
checkB, err := m.ContainsKey(b)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, true, checkB.Value())
|
||||||
|
|
||||||
|
checkC, err := m.ContainsKey(c)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, true, checkC.Value())
|
||||||
|
|
||||||
|
checkD, err := m.ContainsKey(d)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, true, checkD.Value())
|
||||||
|
|
||||||
|
checkE, err := m.ContainsKey(e)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, true, checkE.Value())
|
||||||
|
|
||||||
|
//test CompareHash
|
||||||
|
val2 := map[Item]Item{
|
||||||
|
f: e,
|
||||||
|
e: d,
|
||||||
|
d: a,
|
||||||
|
c: b,
|
||||||
|
b: a,
|
||||||
|
a: c,
|
||||||
|
}
|
||||||
|
m2 := testMakeStackMap(t, val2)
|
||||||
|
checkMap, err := CompareHash(m, m2)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, true, checkMap.Value())
|
||||||
|
|
||||||
|
checkBoolean, err := CompareHash(b, NewBoolean(true))
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, true, checkBoolean.Value())
|
||||||
|
|
||||||
|
checkByteArray, err := CompareHash(c, NewByteArray([]byte{1, 2, 34}))
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, true, checkByteArray.Value())
|
||||||
|
|
||||||
|
checkContext, err := CompareHash(e, NewContext([]byte{1, 2, 3, 4}))
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, true, checkContext.Value())
|
||||||
|
|
||||||
|
checkArray, err := CompareHash(f, NewArray([]Item{a, b}))
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, true, checkArray.Value())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMapAdd(t *testing.T) {
|
||||||
|
var a Item = testMakeStackInt(t, 10)
|
||||||
|
var b Item = NewBoolean(true)
|
||||||
|
var m = testMakeStackMap(t, map[Item]Item{})
|
||||||
|
|
||||||
|
err := m.Add(a, a)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
err = m.Add(b, a)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, 2, len(m.Value()))
|
||||||
|
|
||||||
|
expected := testMakeStackMap(t, map[Item]Item{b: a, a: a})
|
||||||
|
check, err := CompareHash(m, expected)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, true, check.Value())
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMapRemove(t *testing.T) {
|
||||||
|
var a Item = testMakeStackInt(t, 10)
|
||||||
|
var b Item = NewBoolean(true)
|
||||||
|
var m = testMakeStackMap(t, map[Item]Item{b: a, a: a})
|
||||||
|
|
||||||
|
err := m.Remove(a)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, 1, len(m.Value()))
|
||||||
|
|
||||||
|
expected := testMakeStackMap(t, map[Item]Item{b: a})
|
||||||
|
check, err := CompareHash(m, expected)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, true, check.Value())
|
||||||
|
|
||||||
|
}
|
|
@ -11,6 +11,8 @@ type Item interface {
|
||||||
ByteArray() (*ByteArray, error)
|
ByteArray() (*ByteArray, error)
|
||||||
Array() (*Array, error)
|
Array() (*Array, error)
|
||||||
Context() (*Context, error)
|
Context() (*Context, error)
|
||||||
|
Map() (*Map, error)
|
||||||
|
Hash() (string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Represents an `abstract` stack item
|
// Represents an `abstract` stack item
|
||||||
|
@ -47,3 +49,13 @@ func (a *abstractItem) Array() (*Array, error) {
|
||||||
func (a *abstractItem) Context() (*Context, error) {
|
func (a *abstractItem) Context() (*Context, error) {
|
||||||
return nil, errors.New("This stack item is not of type context")
|
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")
|
||||||
|
}
|
||||||
|
|
|
@ -42,3 +42,15 @@ func testReadInt64(data []byte) int64 {
|
||||||
binary.Read(buf, binary.LittleEndian, &ret)
|
binary.Read(buf, binary.LittleEndian, &ret)
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testMakeStackMap(t *testing.T, m map[Item]Item) *Map {
|
||||||
|
a, err := NewMap(m)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func testArray(t *testing.T, m map[Item]Item) *Map {
|
||||||
|
a, err := NewMap(m)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue