vm: implement Boolean and ByteArray item serialization

This commit is contained in:
Evgenii Stratonikov 2019-11-05 11:36:13 +03:00
parent f48b19a305
commit 25f77257ce
4 changed files with 172 additions and 0 deletions

View file

@ -1,6 +1,7 @@
package vm
import (
"errors"
"fmt"
)
@ -20,3 +21,32 @@ func runtimeNotify(vm *VM) error {
fmt.Printf("NEO-GO-VM (notify) > %s\n", item.Value())
return nil
}
// runtimeSerialize handles syscalls System.Runtime.Serialize and Neo.Runtime.Serialize.
func runtimeSerialize(vm *VM) error {
item := vm.Estack().Pop()
data, err := serializeItem(item.value)
if err != nil {
return err
} else if len(data) > MaxItemSize {
return errors.New("too big item")
}
vm.Estack().PushVal(data)
return nil
}
// runtimeDeserialize handles syscalls System.Runtime.Deserialize and Neo.Runtime.Deserialize.
func runtimeDeserialize(vm *VM) error {
data := vm.Estack().Pop().Bytes()
item, err := deserializeItem(data)
if err != nil {
return err
}
vm.Estack().Push(&Element{value: item})
return nil
}

90
pkg/vm/serialization.go Normal file
View file

@ -0,0 +1,90 @@
package vm
import (
"errors"
"github.com/CityOfZion/neo-go/pkg/io"
)
type stackItemType byte
const (
byteArrayT stackItemType = 0x00
booleanT stackItemType = 0x01
integerT stackItemType = 0x02
arrayT stackItemType = 0x80
structT stackItemType = 0x81
mapT stackItemType = 0x82
)
func serializeItem(item StackItem) ([]byte, error) {
w := io.NewBufBinWriter()
serializeItemTo(item, w.BinWriter)
if w.Err != nil {
return nil, w.Err
}
return w.Bytes(), nil
}
func serializeItemTo(item StackItem, w *io.BinWriter) {
switch t := item.(type) {
case *ByteArrayItem:
w.WriteLE(byte(byteArrayT))
w.WriteBytes(t.value)
case *BoolItem:
w.WriteLE(byte(booleanT))
w.WriteLE(t.value)
case *BigIntegerItem:
w.Err = errors.New("not implemented")
case *InteropItem:
w.Err = errors.New("not supported")
case *ArrayItem:
w.Err = errors.New("not implemented")
case *StructItem:
w.Err = errors.New("not implemented")
case *MapItem:
w.Err = errors.New("not implemented")
}
}
func deserializeItem(data []byte) (StackItem, error) {
r := io.NewBinReaderFromBuf(data)
item := deserializeItemFrom(r)
if r.Err != nil {
return nil, r.Err
}
return item, nil
}
func deserializeItemFrom(r *io.BinReader) StackItem {
var t byte
r.ReadLE(&t)
if r.Err != nil {
return nil
}
switch stackItemType(t) {
case byteArrayT:
data := r.ReadBytes()
return NewByteArrayItem(data)
case booleanT:
var b bool
r.ReadLE(&b)
return NewBoolItem(b)
case integerT:
r.Err = errors.New("not implemented")
return nil
case arrayT:
r.Err = errors.New("not implemented")
return nil
case structT:
r.Err = errors.New("not implemented")
return nil
case mapT:
r.Err = errors.New("not implemented")
return nil
default:
r.Err = errors.New("unknown type")
return nil
}
}

View file

@ -86,6 +86,10 @@ func New() *VM {
// Register native interop hooks.
vm.RegisterInteropFunc("Neo.Runtime.Log", runtimeLog, 1)
vm.RegisterInteropFunc("Neo.Runtime.Notify", runtimeNotify, 1)
vm.RegisterInteropFunc("Neo.Runtime.Serialize", runtimeSerialize, 1)
vm.RegisterInteropFunc("System.Runtime.Serialize", runtimeSerialize, 1)
vm.RegisterInteropFunc("Neo.Runtime.Deserialize", runtimeDeserialize, 1)
vm.RegisterInteropFunc("System.Runtime.Deserialize", runtimeDeserialize, 1)
return vm
}

View file

@ -197,6 +197,54 @@ func TestPushData4Good(t *testing.T) {
assert.Equal(t, []byte{1, 2, 3}, vm.estack.Pop().Bytes())
}
func getSyscallProg(name string) (prog []byte) {
prog = []byte{byte(SYSCALL)}
prog = append(prog, byte(len(name)))
prog = append(prog, name...)
return
}
func getSerializeProg() (prog []byte) {
prog = append(prog, getSyscallProg("Neo.Runtime.Serialize")...)
prog = append(prog, getSyscallProg("Neo.Runtime.Deserialize")...)
prog = append(prog, byte(RET))
return
}
func testSerialize(t *testing.T, vm *VM) {
err := vm.Step()
require.NoError(t, err)
require.Equal(t, 1, vm.estack.Len())
require.IsType(t, (*ByteArrayItem)(nil), vm.estack.Top().value)
err = vm.Step()
require.NoError(t, err)
require.Equal(t, 1, vm.estack.Len())
}
func TestSerializeBool(t *testing.T) {
vm := load(getSerializeProg())
vm.estack.PushVal(true)
testSerialize(t, vm)
require.IsType(t, (*BoolItem)(nil), vm.estack.Top().value)
require.Equal(t, true, vm.estack.Top().Bool())
}
func TestSerializeByteArray(t *testing.T) {
vm := load(getSerializeProg())
value := []byte{1, 2, 3}
vm.estack.PushVal(value)
testSerialize(t, vm)
require.IsType(t, (*ByteArrayItem)(nil), vm.estack.Top().value)
require.Equal(t, value, vm.estack.Top().Bytes())
}
func callNTimes(n uint16) []byte {
return makeProgram(
PUSHBYTES2, Instruction(n), Instruction(n>>8), // little-endian