From 978f4dfbc5e613fa4f883c81860a0417885988b5 Mon Sep 17 00:00:00 2001 From: Evgeniy Stratonikov Date: Fri, 30 Apr 2021 15:02:44 +0300 Subject: [PATCH] native/std: add `memoryCompare` method --- pkg/compiler/native_test.go | 1 + pkg/core/native/std.go | 13 +++++++++++++ pkg/core/native/std_test.go | 35 +++++++++++++++++++++++++++++++++++ pkg/interop/native/std/std.go | 8 ++++++++ 4 files changed, 57 insertions(+) diff --git a/pkg/compiler/native_test.go b/pkg/compiler/native_test.go index 23f6aa037..af7e9ba54 100644 --- a/pkg/compiler/native_test.go +++ b/pkg/compiler/native_test.go @@ -228,6 +228,7 @@ func TestNativeHelpersCompile(t *testing.T) { {"itoa10", []string{"4"}}, {"atoi", []string{`"4"`, "10"}}, {"atoi10", []string{`"4"`}}, + {"memoryCompare", []string{"[]byte{1}", "[]byte{2}"}}, }) } diff --git a/pkg/core/native/std.go b/pkg/core/native/std.go index 96bd3bc78..253b91037 100644 --- a/pkg/core/native/std.go +++ b/pkg/core/native/std.go @@ -1,6 +1,7 @@ package native import ( + "bytes" "encoding/base64" "encoding/hex" "errors" @@ -104,6 +105,12 @@ func newStd() *Std { md = newMethodAndPrice(s.base58Decode, 1<<10, callflag.NoneFlag) s.AddMethod(md, desc) + desc = newDescriptor("memoryCompare", smartcontract.IntegerType, + manifest.NewParameter("str1", smartcontract.ByteArrayType), + manifest.NewParameter("str2", smartcontract.ByteArrayType)) + md = newMethodAndPrice(s.memoryCompare, 1<<5, callflag.NoneFlag) + s.AddMethod(md, desc) + return s } @@ -278,6 +285,12 @@ func (s *Std) base58Decode(_ *interop.Context, args []stackitem.Item) stackitem. return stackitem.NewByteArray(result) } +func (s *Std) memoryCompare(_ *interop.Context, args []stackitem.Item) stackitem.Item { + s1 := s.toLimitedBytes(args[0]) + s2 := s.toLimitedBytes(args[1]) + return stackitem.NewBigInteger(big.NewInt(int64(bytes.Compare(s1, s2)))) +} + // Metadata implements Contract interface. func (s *Std) Metadata() *interop.ContractMD { return &s.ContractMD diff --git a/pkg/core/native/std_test.go b/pkg/core/native/std_test.go index 49a24b37f..7640c81cd 100644 --- a/pkg/core/native/std_test.go +++ b/pkg/core/native/std_test.go @@ -354,3 +354,38 @@ func TestStdLibSerializeDeserialize(t *testing.T) { }) }) } + +func TestMemoryCompare(t *testing.T) { + s := newStd() + ic := &interop.Context{VM: vm.New()} + + check := func(t *testing.T, result int64, s1, s2 string) { + actual := s.memoryCompare(ic, []stackitem.Item{stackitem.Make(s1), stackitem.Make(s2)}) + require.Equal(t, big.NewInt(result), actual.Value()) + } + + check(t, -1, "a", "ab") + check(t, 1, "ab", "a") + check(t, 0, "ab", "ab") + check(t, -1, "", "a") + check(t, 0, "", "") + + t.Run("C# compatibility", func(t *testing.T) { + // These tests are taken from C# node. + check(t, -1, "abc", "c") + check(t, -1, "abc", "d") + check(t, 0, "abc", "abc") + check(t, -1, "abc", "abcd") + }) + + t.Run("big arguments", func(t *testing.T) { + s1 := stackitem.Make(strings.Repeat("x", stdMaxInputLength+1)) + s2 := stackitem.Make("xxx") + + require.PanicsWithError(t, ErrTooBigInput.Error(), + func() { s.memoryCompare(ic, []stackitem.Item{s1, s2}) }) + + require.PanicsWithError(t, ErrTooBigInput.Error(), + func() { s.memoryCompare(ic, []stackitem.Item{s2, s1}) }) + }) +} diff --git a/pkg/interop/native/std/std.go b/pkg/interop/native/std/std.go index e20ce2219..f405a24ec 100644 --- a/pkg/interop/native/std/std.go +++ b/pkg/interop/native/std/std.go @@ -113,3 +113,11 @@ func Atoi10(s string) int { return contract.Call(interop.Hash160(Hash), "atoi", contract.NoneFlag, s).(int) } + +// MemoryCompare is similar to bytes.Compare: +// The result will be 0 if a==b, -1 if a < b, and +1 if a > b. +// It uses `memoryCompare` method of StdLib native contract. +func MemoryCompare(s1, s2 []byte) int { + return contract.Call(interop.Hash160(Hash), "memoryCompare", contract.NoneFlag, + s1, s2).(int) +}