diff --git a/pkg/core/interop/binary/encode.go b/pkg/core/interop/binary/encode.go new file mode 100644 index 000000000..0cc294fef --- /dev/null +++ b/pkg/core/interop/binary/encode.go @@ -0,0 +1,57 @@ +package binary + +import ( + "encoding/base64" + + "github.com/mr-tron/base58" + "github.com/nspcc-dev/neo-go/pkg/core/interop" + "github.com/nspcc-dev/neo-go/pkg/vm" +) + +// Serialize serializes top stack item into a ByteArray. +func Serialize(ic *interop.Context) error { + return vm.RuntimeSerialize(ic.VM) +} + +// Deserialize deserializes ByteArray from a stack into an item. +func Deserialize(ic *interop.Context) error { + return vm.RuntimeDeserialize(ic.VM) +} + +// EncodeBase64 encodes top stack item into a base64 string. +func EncodeBase64(ic *interop.Context) error { + src := ic.VM.Estack().Pop().Bytes() + result := base64.StdEncoding.EncodeToString(src) + ic.VM.Estack().PushVal([]byte(result)) + return nil +} + +// DecodeBase64 decodes top stack item from base64 string to byte array. +func DecodeBase64(ic *interop.Context) error { + src := ic.VM.Estack().Pop().String() + result, err := base64.StdEncoding.DecodeString(src) + if err != nil { + return err + } + ic.VM.Estack().PushVal(result) + return nil +} + +// EncodeBase58 encodes top stack item into a base58 string. +func EncodeBase58(ic *interop.Context) error { + src := ic.VM.Estack().Pop().Bytes() + result := base58.Encode(src) + ic.VM.Estack().PushVal([]byte(result)) + return nil +} + +// DecodeBase58 decodes top stack item from base58 string to byte array. +func DecodeBase58(ic *interop.Context) error { + src := ic.VM.Estack().Pop().String() + result, err := base58.Decode(src) + if err != nil { + return err + } + ic.VM.Estack().PushVal(result) + return nil +} diff --git a/pkg/core/interop/binary/encode_test.go b/pkg/core/interop/binary/encode_test.go new file mode 100644 index 000000000..eeda9f3a2 --- /dev/null +++ b/pkg/core/interop/binary/encode_test.go @@ -0,0 +1,92 @@ +package binary + +import ( + "encoding/base64" + "math/big" + "testing" + + "github.com/mr-tron/base58" + "github.com/nspcc-dev/neo-go/pkg/core/interop" + "github.com/nspcc-dev/neo-go/pkg/io" + "github.com/nspcc-dev/neo-go/pkg/vm" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" + "github.com/stretchr/testify/require" +) + +func TestRuntimeSerialize(t *testing.T) { + t.Run("recursive", func(t *testing.T) { + arr := stackitem.NewArray(nil) + arr.Append(arr) + ic := &interop.Context{VM: vm.New()} + ic.VM.Estack().PushVal(arr) + require.Error(t, Serialize(ic)) + }) + t.Run("big item", func(t *testing.T) { + ic := &interop.Context{VM: vm.New()} + ic.VM.Estack().PushVal(make([]byte, stackitem.MaxSize)) + require.Error(t, Serialize(ic)) + }) + t.Run("good", func(t *testing.T) { + ic := &interop.Context{VM: vm.New()} + ic.VM.Estack().PushVal(42) + require.NoError(t, Serialize(ic)) + + w := io.NewBufBinWriter() + stackitem.EncodeBinaryStackItem(stackitem.Make(42), w.BinWriter) + require.NoError(t, w.Err) + + encoded := w.Bytes() + require.Equal(t, encoded, ic.VM.Estack().Pop().Bytes()) + + ic.VM.Estack().PushVal(encoded) + require.NoError(t, Deserialize(ic)) + require.Equal(t, big.NewInt(42), ic.VM.Estack().Pop().BigInt()) + + t.Run("bad", func(t *testing.T) { + encoded[0] ^= 0xFF + ic.VM.Estack().PushVal(encoded) + require.Error(t, Deserialize(ic)) + }) + }) +} + +func TestRuntimeEncodeDecode(t *testing.T) { + original := []byte("my pretty string") + encoded64 := base64.StdEncoding.EncodeToString(original) + encoded58 := base58.Encode(original) + v := vm.New() + ic := &interop.Context{VM: v} + + t.Run("Encode64", func(t *testing.T) { + v.Estack().PushVal(original) + require.NoError(t, EncodeBase64(ic)) + actual := v.Estack().Pop().Bytes() + require.Equal(t, []byte(encoded64), actual) + }) + t.Run("Encode58", func(t *testing.T) { + v.Estack().PushVal(original) + require.NoError(t, EncodeBase58(ic)) + actual := v.Estack().Pop().Bytes() + require.Equal(t, []byte(encoded58), actual) + }) + t.Run("Decode64/positive", func(t *testing.T) { + v.Estack().PushVal(encoded64) + require.NoError(t, DecodeBase64(ic)) + actual := v.Estack().Pop().Bytes() + require.Equal(t, original, actual) + }) + t.Run("Decode64/error", func(t *testing.T) { + v.Estack().PushVal(encoded64 + "%") + require.Error(t, DecodeBase64(ic)) + }) + t.Run("Decode58/positive", func(t *testing.T) { + v.Estack().PushVal(encoded58) + require.NoError(t, DecodeBase58(ic)) + actual := v.Estack().Pop().Bytes() + require.Equal(t, original, actual) + }) + t.Run("Decode58/error", func(t *testing.T) { + v.Estack().PushVal(encoded58 + "%") + require.Error(t, DecodeBase58(ic)) + }) +} diff --git a/pkg/core/interop_neo.go b/pkg/core/interop_neo.go index 47d643731..67a8c3d6f 100644 --- a/pkg/core/interop_neo.go +++ b/pkg/core/interop_neo.go @@ -2,14 +2,12 @@ package core import ( "bytes" - "encoding/base64" "encoding/json" "errors" "fmt" "math" "sort" - "github.com/mr-tron/base58" "github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/core/interop/contract" "github.com/nspcc-dev/neo-go/pkg/core/state" @@ -193,51 +191,3 @@ func callDeploy(ic *interop.Context, cs *state.Contract, isUpdate bool) error { } return nil } - -// runtimeSerialize serializes top stack item into a ByteArray. -func runtimeSerialize(ic *interop.Context) error { - return vm.RuntimeSerialize(ic.VM) -} - -// runtimeDeserialize deserializes ByteArray from a stack into an item. -func runtimeDeserialize(ic *interop.Context) error { - return vm.RuntimeDeserialize(ic.VM) -} - -// runtimeEncodeBase64 encodes top stack item into a base64 string. -func runtimeEncodeBase64(ic *interop.Context) error { - src := ic.VM.Estack().Pop().Bytes() - result := base64.StdEncoding.EncodeToString(src) - ic.VM.Estack().PushVal([]byte(result)) - return nil -} - -// runtimeDecodeBase64 decodes top stack item from base64 string to byte array. -func runtimeDecodeBase64(ic *interop.Context) error { - src := ic.VM.Estack().Pop().String() - result, err := base64.StdEncoding.DecodeString(src) - if err != nil { - return err - } - ic.VM.Estack().PushVal(result) - return nil -} - -// runtimeEncodeBase58 encodes top stack item into a base58 string. -func runtimeEncodeBase58(ic *interop.Context) error { - src := ic.VM.Estack().Pop().Bytes() - result := base58.Encode(src) - ic.VM.Estack().PushVal([]byte(result)) - return nil -} - -// runtimeDecodeBase58 decodes top stack item from base58 string to byte array. -func runtimeDecodeBase58(ic *interop.Context) error { - src := ic.VM.Estack().Pop().String() - result, err := base58.Decode(src) - if err != nil { - return err - } - ic.VM.Estack().PushVal(result) - return nil -} diff --git a/pkg/core/interop_neo_test.go b/pkg/core/interop_neo_test.go index 816c89728..5f4462db4 100644 --- a/pkg/core/interop_neo_test.go +++ b/pkg/core/interop_neo_test.go @@ -1,11 +1,9 @@ package core import ( - "encoding/base64" "fmt" "testing" - "github.com/mr-tron/base58" "github.com/nspcc-dev/neo-go/pkg/config/netmode" "github.com/nspcc-dev/neo-go/pkg/core/block" "github.com/nspcc-dev/neo-go/pkg/core/dao" @@ -213,48 +211,6 @@ func TestECDSAVerify(t *testing.T) { }) } -func TestRuntimeEncodeDecode(t *testing.T) { - original := []byte("my pretty string") - encoded64 := base64.StdEncoding.EncodeToString(original) - encoded58 := base58.Encode(original) - v, ic, bc := createVM(t) - defer bc.Close() - - t.Run("Encode64", func(t *testing.T) { - v.Estack().PushVal(original) - require.NoError(t, runtimeEncodeBase64(ic)) - actual := v.Estack().Pop().Bytes() - require.Equal(t, []byte(encoded64), actual) - }) - - t.Run("Encode58", func(t *testing.T) { - v.Estack().PushVal(original) - require.NoError(t, runtimeEncodeBase58(ic)) - actual := v.Estack().Pop().Bytes() - require.Equal(t, []byte(encoded58), actual) - }) - t.Run("Decode64/positive", func(t *testing.T) { - v.Estack().PushVal(encoded64) - require.NoError(t, runtimeDecodeBase64(ic)) - actual := v.Estack().Pop().Bytes() - require.Equal(t, original, actual) - }) - t.Run("Decode64/error", func(t *testing.T) { - v.Estack().PushVal(encoded64 + "%") - require.Error(t, runtimeDecodeBase64(ic)) - }) - t.Run("Decode58/positive", func(t *testing.T) { - v.Estack().PushVal(encoded58) - require.NoError(t, runtimeDecodeBase58(ic)) - actual := v.Estack().Pop().Bytes() - require.Equal(t, original, actual) - }) - t.Run("Decode58/error", func(t *testing.T) { - v.Estack().PushVal(encoded58 + "%") - require.Error(t, runtimeDecodeBase58(ic)) - }) -} - // Helper functions to create VM, InteropContext, TX, Account, Contract. func createVM(t *testing.T) (*vm.VM, *interop.Context, *Blockchain) { diff --git a/pkg/core/interops.go b/pkg/core/interops.go index a8dbd95d9..03d8023c8 100644 --- a/pkg/core/interops.go +++ b/pkg/core/interops.go @@ -34,13 +34,13 @@ func SpawnVM(ic *interop.Context) *vm.VM { // All lists are sorted, keep 'em this way, please. var systemInterops = []interop.Function{ {Name: interopnames.SystemBinaryAtoi, Func: binary.Atoi, Price: 100000, ParamCount: 2}, - {Name: interopnames.SystemBinaryBase58Decode, Func: runtimeDecodeBase58, Price: 100000, ParamCount: 1}, - {Name: interopnames.SystemBinaryBase58Encode, Func: runtimeEncodeBase58, Price: 100000, ParamCount: 1}, - {Name: interopnames.SystemBinaryBase64Decode, Func: runtimeDecodeBase64, Price: 100000, ParamCount: 1}, - {Name: interopnames.SystemBinaryBase64Encode, Func: runtimeEncodeBase64, Price: 100000, ParamCount: 1}, - {Name: interopnames.SystemBinaryDeserialize, Func: runtimeDeserialize, Price: 500000, ParamCount: 1}, + {Name: interopnames.SystemBinaryBase58Decode, Func: binary.DecodeBase58, Price: 100000, ParamCount: 1}, + {Name: interopnames.SystemBinaryBase58Encode, Func: binary.EncodeBase58, Price: 100000, ParamCount: 1}, + {Name: interopnames.SystemBinaryBase64Decode, Func: binary.DecodeBase64, Price: 100000, ParamCount: 1}, + {Name: interopnames.SystemBinaryBase64Encode, Func: binary.EncodeBase64, Price: 100000, ParamCount: 1}, + {Name: interopnames.SystemBinaryDeserialize, Func: binary.Deserialize, Price: 500000, ParamCount: 1}, {Name: interopnames.SystemBinaryItoa, Func: binary.Itoa, Price: 100000, ParamCount: 2}, - {Name: interopnames.SystemBinarySerialize, Func: runtimeSerialize, Price: 100000, ParamCount: 1}, + {Name: interopnames.SystemBinarySerialize, Func: binary.Serialize, Price: 100000, ParamCount: 1}, {Name: interopnames.SystemBlockchainGetBlock, Func: bcGetBlock, Price: 2500000, RequiredFlags: smartcontract.AllowStates, ParamCount: 1}, {Name: interopnames.SystemBlockchainGetContract, Func: bcGetContract, Price: 1000000,