From e4b34833da5ef498f0d102b58fe6ebfb049c89d6 Mon Sep 17 00:00:00 2001 From: Evgeniy Stratonikov Date: Fri, 30 Apr 2021 11:22:00 +0300 Subject: [PATCH] native/std: add overloads for `itoa` and `atoi` --- pkg/compiler/native_test.go | 19 ++++++++++++++++--- pkg/core/native/std.go | 35 ++++++++++++++++++++++++++++++----- pkg/core/native/std_test.go | 12 ++++++++++++ pkg/interop/native/std/std.go | 14 ++++++++++++++ 4 files changed, 72 insertions(+), 8 deletions(-) diff --git a/pkg/compiler/native_test.go b/pkg/compiler/native_test.go index 4f7ae97e2..23f6aa037 100644 --- a/pkg/compiler/native_test.go +++ b/pkg/compiler/native_test.go @@ -225,7 +225,9 @@ func TestNativeHelpersCompile(t *testing.T) { {"base58Encode", []string{"[]byte{1, 2, 3}"}}, {"base58Decode", []string{"[]byte{1, 2, 3}"}}, {"itoa", []string{"4", "10"}}, + {"itoa10", []string{"4"}}, {"atoi", []string{`"4"`, "10"}}, + {"atoi10", []string{`"4"`}}, }) } @@ -239,10 +241,21 @@ func runNativeTestCases(t *testing.T, ctr interop.ContractMD, name string, testC }) } -func runNativeTestCase(t *testing.T, ctr interop.ContractMD, name, method string, params ...string) { - md, ok := ctr.GetMethod(strings.TrimSuffix(method, "WithData"), len(params)) - require.True(t, ok) +func getMethod(t *testing.T, ctr interop.ContractMD, name string, params []string) interop.MethodAndPrice { + switch { + case name == "itoa10" || name == "atoi10": + name = name[:4] + default: + name = strings.TrimSuffix(name, "WithData") + } + md, ok := ctr.GetMethod(name, len(params)) + require.True(t, ok) + return md +} + +func runNativeTestCase(t *testing.T, ctr interop.ContractMD, name, method string, params ...string) { + md := getMethod(t, ctr, method, params) isVoid := md.MD.ReturnType == smartcontract.VoidType srcTmpl := `package foo import "github.com/nspcc-dev/neo-go/pkg/interop/native/%s" diff --git a/pkg/core/native/std.go b/pkg/core/native/std.go index ed559000f..8c386c4c9 100644 --- a/pkg/core/native/std.go +++ b/pkg/core/native/std.go @@ -61,12 +61,22 @@ func newStd() *Std { md = newMethodAndPrice(s.itoa, 1<<12, callflag.NoneFlag) s.AddMethod(md, desc) + desc = newDescriptor("itoa", smartcontract.StringType, + manifest.NewParameter("value", smartcontract.IntegerType)) + md = newMethodAndPrice(s.itoa10, 1<<12, callflag.NoneFlag) + s.AddMethod(md, desc) + desc = newDescriptor("atoi", smartcontract.IntegerType, manifest.NewParameter("value", smartcontract.StringType), manifest.NewParameter("base", smartcontract.IntegerType)) md = newMethodAndPrice(s.atoi, 1<<12, callflag.NoneFlag) s.AddMethod(md, desc) + desc = newDescriptor("atoi", smartcontract.IntegerType, + manifest.NewParameter("value", smartcontract.StringType)) + md = newMethodAndPrice(s.atoi10, 1<<12, callflag.NoneFlag) + s.AddMethod(md, desc) + desc = newDescriptor("base64Encode", smartcontract.StringType, manifest.NewParameter("data", smartcontract.ByteArrayType)) md = newMethodAndPrice(s.base64Encode, 1<<12, callflag.NoneFlag) @@ -142,6 +152,11 @@ func (s *Std) jsonDeserialize(_ *interop.Context, args []stackitem.Item) stackit return item } +func (s *Std) itoa10(_ *interop.Context, args []stackitem.Item) stackitem.Item { + num := toBigInt(args[0]) + return stackitem.NewByteArray([]byte(num.Text(10))) +} + func (s *Std) itoa(_ *interop.Context, args []stackitem.Item) stackitem.Item { num := toBigInt(args[0]) base := toBigInt(args[1]) @@ -170,6 +185,20 @@ func (s *Std) itoa(_ *interop.Context, args []stackitem.Item) stackitem.Item { return stackitem.NewByteArray([]byte(str)) } +func (s *Std) atoi10(_ *interop.Context, args []stackitem.Item) stackitem.Item { + num := toString(args[0]) + res := s.atoi10Aux(num) + return stackitem.NewBigInteger(res) +} + +func (s *Std) atoi10Aux(num string) *big.Int { + bi, ok := new(big.Int).SetString(num, 10) + if !ok { + panic(ErrInvalidFormat) + } + return bi +} + func (s *Std) atoi(_ *interop.Context, args []stackitem.Item) stackitem.Item { num := toString(args[0]) base := toBigInt(args[1]) @@ -179,11 +208,7 @@ func (s *Std) atoi(_ *interop.Context, args []stackitem.Item) stackitem.Item { var bi *big.Int switch b := base.Int64(); b { case 10: - var ok bool - bi, ok = new(big.Int).SetString(num, int(b)) - if !ok { - panic(ErrInvalidFormat) - } + bi = s.atoi10Aux(num) case 16: changed := len(num)%2 != 0 if changed { diff --git a/pkg/core/native/std_test.go b/pkg/core/native/std_test.go index f5a3bf15c..bfe4e3f43 100644 --- a/pkg/core/native/std_test.go +++ b/pkg/core/native/std_test.go @@ -49,6 +49,18 @@ func TestStdLibItoaAtoi(t *testing.T) { actual = s.atoi(ic, []stackitem.Item{stackitem.Make(tc.result), stackitem.Make(tc.base)}) }) require.Equal(t, stackitem.Make(tc.num), actual) + + if tc.base.Int64() == 10 { + require.NotPanics(t, func() { + actual = s.itoa10(ic, []stackitem.Item{stackitem.Make(tc.num)}) + }) + require.Equal(t, stackitem.Make(tc.result), actual) + + require.NotPanics(t, func() { + actual = s.atoi10(ic, []stackitem.Item{stackitem.Make(tc.result)}) + }) + require.Equal(t, stackitem.Make(tc.num), actual) + } } t.Run("-1", func(t *testing.T) { diff --git a/pkg/interop/native/std/std.go b/pkg/interop/native/std/std.go index fa5f4dadc..e20ce2219 100644 --- a/pkg/interop/native/std/std.go +++ b/pkg/interop/native/std/std.go @@ -93,9 +93,23 @@ func Itoa(num int, base int) string { num, base).(string) } +// Itoa10 converts num in a base 10 to string. +// It uses `itoa` method of StdLib native contract. +func Itoa10(num int) string { + return contract.Call(interop.Hash160(Hash), "itoa", contract.NoneFlag, + num).(string) +} + // Atoi converts string to a number in a given base. Base should be either 10 or 16. // It uses `atoi` method of StdLib native contract. func Atoi(s string, base int) int { return contract.Call(interop.Hash160(Hash), "atoi", contract.NoneFlag, s, base).(int) } + +// Atoi10 converts string to a number in a base 10. +// It uses `atoi` method of StdLib native contract. +func Atoi10(s string) int { + return contract.Call(interop.Hash160(Hash), "atoi", contract.NoneFlag, + s).(int) +}