diff --git a/pkg/vm/interop.go b/pkg/vm/interop.go index e2288b793..8e83e0f11 100644 --- a/pkg/vm/interop.go +++ b/pkg/vm/interop.go @@ -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 +} diff --git a/pkg/vm/serialization.go b/pkg/vm/serialization.go new file mode 100644 index 000000000..5918745ef --- /dev/null +++ b/pkg/vm/serialization.go @@ -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 + } +} diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index 568e47110..0fb917883 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -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 } diff --git a/pkg/vm/vm_test.go b/pkg/vm/vm_test.go index 7eeb04a63..a8d94f1e1 100644 --- a/pkg/vm/vm_test.go +++ b/pkg/vm/vm_test.go @@ -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