forked from TrueCloudLab/neoneo-go
vm: implement Buffer stack item
Buffer is a stack item type introduced in NEO3 which represents mutable byte-array.
This commit is contained in:
parent
ee9adcdc5c
commit
c3f7832f3b
5 changed files with 166 additions and 11 deletions
|
@ -89,6 +89,9 @@ func serializeItemTo(item StackItem, w *io.BinWriter, seen map[StackItem]bool) {
|
|||
case *ByteArrayItem:
|
||||
w.WriteBytes([]byte{byte(ByteArrayT)})
|
||||
w.WriteVarBytes(t.value)
|
||||
case *BufferItem:
|
||||
w.WriteBytes([]byte{byte(BufferT)})
|
||||
w.WriteVarBytes(t.value)
|
||||
case *BoolItem:
|
||||
w.WriteBytes([]byte{byte(BooleanT)})
|
||||
w.WriteBool(t.value)
|
||||
|
|
|
@ -124,14 +124,15 @@ func convertPrimitive(item StackItem, typ StackItemType) (StackItem, error) {
|
|||
return nil, err
|
||||
}
|
||||
return NewBigIntegerItem(bi), nil
|
||||
case ByteArrayT:
|
||||
case ByteArrayT, BufferT:
|
||||
b, err := item.TryBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if typ == BufferT {
|
||||
return NewBufferItem(b), nil
|
||||
}
|
||||
return NewByteArrayItem(b), nil
|
||||
case BufferT:
|
||||
panic("TODO") // #877
|
||||
case BooleanT:
|
||||
return NewBoolItem(item.Bool()), nil
|
||||
default:
|
||||
|
@ -941,3 +942,89 @@ func (p *PointerItem) Convert(typ StackItemType) (StackItem, error) {
|
|||
return nil, errInvalidConversion
|
||||
}
|
||||
}
|
||||
|
||||
// BufferItem represents represents Buffer stack item.
|
||||
type BufferItem struct {
|
||||
value []byte
|
||||
}
|
||||
|
||||
// NewBufferItem returns a new BufferItem object.
|
||||
func NewBufferItem(b []byte) *BufferItem {
|
||||
return &BufferItem{
|
||||
value: b,
|
||||
}
|
||||
}
|
||||
|
||||
// Value implements StackItem interface.
|
||||
func (i *BufferItem) Value() interface{} {
|
||||
return i.value
|
||||
}
|
||||
|
||||
// String implements fmt.Stringer interface.
|
||||
func (i *BufferItem) String() string {
|
||||
return "Buffer"
|
||||
}
|
||||
|
||||
// Bool implements StackItem interface.
|
||||
func (i *BufferItem) Bool() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// TryBytes implements StackItem interface.
|
||||
func (i *BufferItem) TryBytes() ([]byte, error) {
|
||||
val := make([]byte, len(i.value))
|
||||
copy(val, i.value)
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// TryInteger implements StackItem interface.
|
||||
func (i *BufferItem) TryInteger() (*big.Int, error) {
|
||||
return nil, errors.New("can't convert Buffer to Integer")
|
||||
}
|
||||
|
||||
// Equals implements StackItem interface.
|
||||
func (i *BufferItem) Equals(s StackItem) bool {
|
||||
return i == s
|
||||
}
|
||||
|
||||
// Dup implements StackItem interface.
|
||||
func (i *BufferItem) Dup() StackItem {
|
||||
return i
|
||||
}
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface.
|
||||
func (i *BufferItem) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(hex.EncodeToString(i.value))
|
||||
}
|
||||
|
||||
// ToContractParameter implements StackItem interface.
|
||||
func (i *BufferItem) ToContractParameter(map[StackItem]bool) smartcontract.Parameter {
|
||||
return smartcontract.Parameter{
|
||||
Type: smartcontract.ByteArrayType,
|
||||
Value: i.value,
|
||||
}
|
||||
}
|
||||
|
||||
// Type implements StackItem interface.
|
||||
func (i *BufferItem) Type() StackItemType { return BufferT }
|
||||
|
||||
// Convert implements StackItem interface.
|
||||
func (i *BufferItem) Convert(typ StackItemType) (StackItem, error) {
|
||||
switch typ {
|
||||
case BooleanT:
|
||||
return NewBoolItem(i.Bool()), nil
|
||||
case BufferT:
|
||||
return i, nil
|
||||
case ByteArrayT:
|
||||
val := make([]byte, len(i.value))
|
||||
copy(val, i.value)
|
||||
return NewByteArrayItem(val), nil
|
||||
case IntegerT:
|
||||
if len(i.value) > MaxBigIntegerSizeBits/8 {
|
||||
return nil, errInvalidConversion
|
||||
}
|
||||
return NewBigIntegerItem(emit.BytesToInt(i.value)), nil
|
||||
default:
|
||||
return nil, errInvalidConversion
|
||||
}
|
||||
}
|
||||
|
|
|
@ -374,6 +374,10 @@ var marshalJSONTestCases = []struct {
|
|||
input: NewByteArrayItem([]byte{1, 2, 3}),
|
||||
result: []byte(`"010203"`),
|
||||
},
|
||||
{
|
||||
input: NewBufferItem([]byte{1, 2, 3}),
|
||||
result: []byte(`"010203"`),
|
||||
},
|
||||
{
|
||||
input: &ArrayItem{value: []StackItem{&BigIntegerItem{value: big.NewInt(3)}, &ByteArrayItem{value: []byte{1, 2, 3}}}},
|
||||
result: []byte(`[3,"010203"]`),
|
||||
|
@ -432,6 +436,10 @@ var toContractParameterTestCases = []struct {
|
|||
input: NewByteArrayItem([]byte{0x01, 0x02, 0x03}),
|
||||
result: smartcontract.Parameter{Type: smartcontract.ByteArrayType, Value: []byte{0x01, 0x02, 0x03}},
|
||||
},
|
||||
{
|
||||
input: NewBufferItem([]byte{0x01, 0x02, 0x03}),
|
||||
result: smartcontract.Parameter{Type: smartcontract.ByteArrayType, Value: []byte{0x01, 0x02, 0x03}},
|
||||
},
|
||||
{
|
||||
input: NewArrayItem([]StackItem{NewBigIntegerItem(big.NewInt(2)), NewBoolItem(true)}),
|
||||
result: smartcontract.Parameter{Type: smartcontract.ArrayType, Value: []smartcontract.Parameter{
|
||||
|
|
45
pkg/vm/vm.go
45
pkg/vm/vm.go
|
@ -5,6 +5,7 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"math/big"
|
||||
"os"
|
||||
"text/tabwriter"
|
||||
|
@ -1100,16 +1101,36 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
|
|||
t.Add(key.value, item)
|
||||
v.refs.Add(item)
|
||||
|
||||
case *BufferItem:
|
||||
index := toInt(key.BigInt())
|
||||
if index < 0 || index >= len(t.value) {
|
||||
panic("invalid index")
|
||||
}
|
||||
bi, err := item.TryInteger()
|
||||
b := toInt(bi)
|
||||
if err != nil || b < math.MinInt8 || b > math.MaxUint8 {
|
||||
panic("invalid value")
|
||||
}
|
||||
t.value[index] = byte(b)
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("SETITEM: invalid item type %s", t))
|
||||
}
|
||||
|
||||
case opcode.REVERSEITEMS:
|
||||
a := v.estack.Pop().Array()
|
||||
if len(a) > 1 {
|
||||
for i, j := 0, len(a)-1; i <= j; i, j = i+1, j-1 {
|
||||
item := v.estack.Pop()
|
||||
switch t := item.value.(type) {
|
||||
case *ArrayItem, *StructItem:
|
||||
a := t.Value().([]StackItem)
|
||||
for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 {
|
||||
a[i], a[j] = a[j], a[i]
|
||||
}
|
||||
case *BufferItem:
|
||||
for i, j := 0, len(t.value)-1; i < j; i, j = i+1, j-1 {
|
||||
t.value[i], t.value[j] = t.value[j], t.value[i]
|
||||
}
|
||||
default:
|
||||
panic(fmt.Sprintf("invalid item type %s", t))
|
||||
}
|
||||
case opcode.REMOVE:
|
||||
key := v.estack.Pop()
|
||||
|
@ -1322,6 +1343,12 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
|
|||
v.estack.PushVal(index < int64(len(c.Array())))
|
||||
case *MapItem:
|
||||
v.estack.PushVal(t.Has(key.Item()))
|
||||
case *BufferItem:
|
||||
index := key.BigInt().Int64()
|
||||
if index < 0 {
|
||||
panic("negative index")
|
||||
}
|
||||
v.estack.PushVal(index < int64(len(t.value)))
|
||||
default:
|
||||
panic("wrong collection type")
|
||||
}
|
||||
|
@ -1576,3 +1603,15 @@ func (v *VM) GetEntryScriptHash() util.Uint160 {
|
|||
func (v *VM) GetCurrentScriptHash() util.Uint160 {
|
||||
return v.getContextScriptHash(0)
|
||||
}
|
||||
|
||||
// toInt converts an item to a 32-bit int.
|
||||
func toInt(i *big.Int) int {
|
||||
if !i.IsInt64() {
|
||||
panic("not an int32")
|
||||
}
|
||||
n := i.Int64()
|
||||
if n < math.MinInt32 || n > math.MaxInt32 {
|
||||
panic("not an int32")
|
||||
}
|
||||
return int(n)
|
||||
}
|
||||
|
|
|
@ -937,6 +937,8 @@ func TestNOT(t *testing.T) {
|
|||
t.Run("ByteArray0", getTestFuncForVM(prog, true, []byte{0, 0}))
|
||||
t.Run("ByteArray1", getTestFuncForVM(prog, false, []byte{0, 1}))
|
||||
t.Run("NoArgument", getTestFuncForVM(prog, nil))
|
||||
t.Run("Buffer0", getTestFuncForVM(prog, false, NewBufferItem([]byte{})))
|
||||
t.Run("Buffer1", getTestFuncForVM(prog, false, NewBufferItem([]byte{1})))
|
||||
}
|
||||
|
||||
// getBigInt returns 2^a+b
|
||||
|
@ -1056,6 +1058,7 @@ func TestEQUALTrue(t *testing.T) {
|
|||
prog := makeProgram(opcode.DUP, opcode.EQUAL)
|
||||
t.Run("Array", getTestFuncForVM(prog, true, []StackItem{}))
|
||||
t.Run("Map", getTestFuncForVM(prog, true, NewMapItem()))
|
||||
t.Run("Buffer", getTestFuncForVM(prog, true, NewBufferItem([]byte{1, 2})))
|
||||
}
|
||||
|
||||
func TestEQUAL(t *testing.T) {
|
||||
|
@ -1066,6 +1069,7 @@ func TestEQUAL(t *testing.T) {
|
|||
t.Run("IntegerByteArray", getTestFuncForVM(prog, true, []byte{16}, 16))
|
||||
t.Run("Map", getTestFuncForVM(prog, false, NewMapItem(), NewMapItem()))
|
||||
t.Run("Array", getTestFuncForVM(prog, false, []StackItem{}, []StackItem{}))
|
||||
t.Run("Buffer", getTestFuncForVM(prog, false, NewBufferItem([]byte{42}), NewBufferItem([]byte{42})))
|
||||
}
|
||||
|
||||
func runWithArgs(t *testing.T, prog []byte, result interface{}, args ...interface{}) {
|
||||
|
@ -1270,6 +1274,7 @@ func TestPICKITEM(t *testing.T) {
|
|||
t.Run("bad index", getTestFuncForVM(prog, nil, []StackItem{}, 0))
|
||||
t.Run("Array", getTestFuncForVM(prog, 2, []StackItem{makeStackItem(1), makeStackItem(2)}, 1))
|
||||
t.Run("ByteArray", getTestFuncForVM(prog, 2, []byte{1, 2}, 1))
|
||||
t.Run("Buffer", getTestFuncForVM(prog, 2, NewBufferItem([]byte{1, 2}), 1))
|
||||
}
|
||||
|
||||
func TestPICKITEMDupArray(t *testing.T) {
|
||||
|
@ -1305,6 +1310,13 @@ func TestPICKITEMMap(t *testing.T) {
|
|||
runWithArgs(t, prog, 3, m, 5)
|
||||
}
|
||||
|
||||
func TestSETITEMBuffer(t *testing.T) {
|
||||
prog := makeProgram(opcode.DUP, opcode.REVERSE4, opcode.SETITEM)
|
||||
t.Run("Good", getTestFuncForVM(prog, NewBufferItem([]byte{0, 42, 2}), 42, 1, NewBufferItem([]byte{0, 1, 2})))
|
||||
t.Run("BadIndex", getTestFuncForVM(prog, nil, 42, -1, NewBufferItem([]byte{0, 1, 2})))
|
||||
t.Run("BadValue", getTestFuncForVM(prog, nil, 256, 1, NewBufferItem([]byte{0, 1, 2})))
|
||||
}
|
||||
|
||||
func TestSETITEMMap(t *testing.T) {
|
||||
prog := makeProgram(opcode.SETITEM, opcode.PICKITEM)
|
||||
m := NewMapItem()
|
||||
|
@ -1341,6 +1353,7 @@ func TestSIZE(t *testing.T) {
|
|||
prog := makeProgram(opcode.SIZE)
|
||||
t.Run("NoArgument", getTestFuncForVM(prog, nil))
|
||||
t.Run("ByteArray", getTestFuncForVM(prog, 2, []byte{0, 1}))
|
||||
t.Run("Buffer", getTestFuncForVM(prog, 2, NewBufferItem([]byte{0, 1})))
|
||||
t.Run("Bool", getTestFuncForVM(prog, 1, false))
|
||||
t.Run("Array", getTestFuncForVM(prog, 2, []StackItem{makeStackItem(1), makeStackItem([]byte{})}))
|
||||
t.Run("Map", func(t *testing.T) {
|
||||
|
@ -1416,6 +1429,12 @@ func TestHASKEY(t *testing.T) {
|
|||
t.Run("True", getTestFuncForVM(prog, true, NewStructItem(arr), 4))
|
||||
t.Run("False", getTestFuncForVM(prog, false, NewStructItem(arr), 5))
|
||||
})
|
||||
|
||||
t.Run("Buffer", func(t *testing.T) {
|
||||
t.Run("True", getTestFuncForVM(prog, true, NewBufferItem([]byte{5, 5, 5}), 2))
|
||||
t.Run("False", getTestFuncForVM(prog, false, NewBufferItem([]byte{5, 5, 5}), 3))
|
||||
t.Run("Negative", getTestFuncForVM(prog, nil, NewBufferItem([]byte{5, 5, 5}), -1))
|
||||
})
|
||||
}
|
||||
|
||||
func TestHASKEYMap(t *testing.T) {
|
||||
|
@ -1838,11 +1857,10 @@ func TestUNPACKGood(t *testing.T) {
|
|||
assert.Equal(t, int64(1), vm.estack.Peek(len(elements)+1).BigInt().Int64())
|
||||
}
|
||||
|
||||
func TestREVERSEITEMSBadNotArray(t *testing.T) {
|
||||
prog := makeProgram(opcode.REVERSEITEMS)
|
||||
vm := load(prog)
|
||||
vm.estack.PushVal(1)
|
||||
checkVMFailed(t, vm)
|
||||
func TestREVERSEITEMS(t *testing.T) {
|
||||
prog := makeProgram(opcode.DUP, opcode.REVERSEITEMS)
|
||||
t.Run("InvalidItem", getTestFuncForVM(prog, nil, 1))
|
||||
t.Run("Buffer", getTestFuncForVM(prog, NewBufferItem([]byte{3, 2, 1}), NewBufferItem([]byte{1, 2, 3})))
|
||||
}
|
||||
|
||||
func testREVERSEITEMSIssue437(t *testing.T, i1, i2 opcode.Opcode, reversed bool) {
|
||||
|
|
Loading…
Reference in a new issue