core: remove System.Binary.Itoa and System.Binary.Atoi syscalls

And move their tests to native StdLib.
This commit is contained in:
Anna Shaleva 2021-03-04 13:26:16 +03:00
parent 2b90d4455f
commit f65485b735
10 changed files with 117 additions and 241 deletions

View file

@ -2,8 +2,8 @@ package timer
import ( import (
"github.com/nspcc-dev/neo-go/pkg/interop" "github.com/nspcc-dev/neo-go/pkg/interop"
"github.com/nspcc-dev/neo-go/pkg/interop/binary"
"github.com/nspcc-dev/neo-go/pkg/interop/contract" "github.com/nspcc-dev/neo-go/pkg/interop/contract"
"github.com/nspcc-dev/neo-go/pkg/interop/native/std"
"github.com/nspcc-dev/neo-go/pkg/interop/runtime" "github.com/nspcc-dev/neo-go/pkg/interop/runtime"
"github.com/nspcc-dev/neo-go/pkg/interop/storage" "github.com/nspcc-dev/neo-go/pkg/interop/storage"
"github.com/nspcc-dev/neo-go/pkg/interop/util" "github.com/nspcc-dev/neo-go/pkg/interop/util"
@ -35,7 +35,7 @@ func _deploy(_ interface{}, isUpdate bool) {
sh := runtime.GetCallingScriptHash() sh := runtime.GetCallingScriptHash()
storage.Put(ctx, mgmtKey, sh) storage.Put(ctx, mgmtKey, sh)
storage.Put(ctx, ticksKey, defaultTicks) storage.Put(ctx, ticksKey, defaultTicks)
i := binary.Itoa(defaultTicks, 10) i := std.Itoa(defaultTicks, 10)
runtime.Log("Timer set to " + i + " ticks.") runtime.Log("Timer set to " + i + " ticks.")
} }
@ -61,7 +61,7 @@ func Tick() bool {
return contract.Call(runtime.GetExecutingScriptHash(), "selfDestroy", contract.All).(bool) return contract.Call(runtime.GetExecutingScriptHash(), "selfDestroy", contract.All).(bool)
} }
storage.Put(ctx, ticksKey, ticksLeft) storage.Put(ctx, ticksKey, ticksLeft)
i := binary.Itoa(ticksLeft.(int), 10) i := std.Itoa(ticksLeft.(int), 10)
runtime.Log(i + " ticks left.") runtime.Log(i + " ticks left.")
return true return true
} }

View file

@ -118,13 +118,14 @@ func TestInline(t *testing.T) {
func TestInlineInLoop(t *testing.T) { func TestInlineInLoop(t *testing.T) {
t.Run("simple", func(t *testing.T) { t.Run("simple", func(t *testing.T) {
src := `package foo src := `package foo
import "github.com/nspcc-dev/neo-go/pkg/interop/binary" import "github.com/nspcc-dev/neo-go/pkg/interop/storage"
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/inline" import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/inline"
func Main() int { func Main() int {
sum := 0 sum := 0
values := []int{10, 11} values := []int{10, 11}
for _, v := range values { for _, v := range values {
binary.Itoa(v, 10) _ = v // use 'v'
storage.GetContext() // push something on stack to check it's dropped
sum += inline.VarSum(1, 2, 3, 4) sum += inline.VarSum(1, 2, 3, 4)
} }
return sum return sum
@ -133,14 +134,16 @@ func TestInlineInLoop(t *testing.T) {
}) })
t.Run("inlined argument", func(t *testing.T) { t.Run("inlined argument", func(t *testing.T) {
src := `package foo src := `package foo
import "github.com/nspcc-dev/neo-go/pkg/interop/binary" import "github.com/nspcc-dev/neo-go/pkg/interop/runtime"
import "github.com/nspcc-dev/neo-go/pkg/interop/storage"
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/inline" import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/inline"
func Main() int { func Main() int {
sum := 0 sum := 0
values := []int{10, 11} values := []int{10, 11}
for _, v := range values { for _, v := range values {
binary.Itoa(v, 10) _ = v // use 'v'
sum += inline.VarSum(1, 2, 3, binary.Atoi("4", 10)) storage.GetContext() // push something on stack to check it's dropped
sum += inline.VarSum(1, 2, 3, runtime.GetTime()) // runtime.GetTime always returns 4 in these tests
} }
return sum return sum
}` }`
@ -148,12 +151,12 @@ func TestInlineInLoop(t *testing.T) {
}) })
t.Run("check clean stack on return", func(t *testing.T) { t.Run("check clean stack on return", func(t *testing.T) {
src := `package foo src := `package foo
import "github.com/nspcc-dev/neo-go/pkg/interop/binary" import "github.com/nspcc-dev/neo-go/pkg/interop/storage"
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/inline" import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/inline"
func Main() int { func Main() int {
values := []int{10, 11, 12} values := []int{10, 11, 12}
for _, v := range values { for _, v := range values {
binary.Itoa(v, 10) storage.GetContext() // push something on stack to check it's dropped
if v == 11 { if v == 11 {
return inline.VarSum(2, 20, 200) return inline.VarSum(2, 20, 200)
} }

View file

@ -61,13 +61,11 @@ func TestSyscallExecution(t *testing.T) {
sigs := "[]interop.Signature{" + sig + "}" sigs := "[]interop.Signature{" + sig + "}"
sctx := "storage.Context{}" sctx := "storage.Context{}"
interops := map[string]syscallTestCase{ interops := map[string]syscallTestCase{
"binary.Atoi": {interopnames.SystemBinaryAtoi, []string{`"123"`, "10"}, false},
"binary.Base58Decode": {interopnames.SystemBinaryBase58Decode, []string{b}, false}, "binary.Base58Decode": {interopnames.SystemBinaryBase58Decode, []string{b}, false},
"binary.Base58Encode": {interopnames.SystemBinaryBase58Encode, []string{b}, false}, "binary.Base58Encode": {interopnames.SystemBinaryBase58Encode, []string{b}, false},
"binary.Base64Decode": {interopnames.SystemBinaryBase64Decode, []string{b}, false}, "binary.Base64Decode": {interopnames.SystemBinaryBase64Decode, []string{b}, false},
"binary.Base64Encode": {interopnames.SystemBinaryBase64Encode, []string{b}, false}, "binary.Base64Encode": {interopnames.SystemBinaryBase64Encode, []string{b}, false},
"binary.Deserialize": {interopnames.SystemBinaryDeserialize, []string{b}, false}, "binary.Deserialize": {interopnames.SystemBinaryDeserialize, []string{b}, false},
"binary.Itoa": {interopnames.SystemBinaryItoa, []string{"123", "10"}, false},
"binary.Serialize": {interopnames.SystemBinarySerialize, []string{"10"}, false}, "binary.Serialize": {interopnames.SystemBinarySerialize, []string{"10"}, false},
"contract.Call": {interopnames.SystemContractCall, []string{u160, `"m"`, "1", "3"}, false}, "contract.Call": {interopnames.SystemContractCall, []string{u160, `"m"`, "1", "3"}, false},
"contract.CreateMultisigAccount": {interopnames.SystemContractCreateMultisigAccount, []string{"1", pubs}, false}, "contract.CreateMultisigAccount": {interopnames.SystemContractCreateMultisigAccount, []string{"1", pubs}, false},

View file

@ -3,8 +3,6 @@ package compiler_test
import ( import (
"errors" "errors"
"fmt" "fmt"
"math/big"
"strconv"
"strings" "strings"
"testing" "testing"
@ -109,8 +107,7 @@ func newStoragePlugin() *storagePlugin {
s.interops[interopnames.ToID([]byte(interopnames.SystemStoragePut))] = s.Put s.interops[interopnames.ToID([]byte(interopnames.SystemStoragePut))] = s.Put
s.interops[interopnames.ToID([]byte(interopnames.SystemStorageGetContext))] = s.GetContext s.interops[interopnames.ToID([]byte(interopnames.SystemStorageGetContext))] = s.GetContext
s.interops[interopnames.ToID([]byte(interopnames.SystemRuntimeNotify))] = s.Notify s.interops[interopnames.ToID([]byte(interopnames.SystemRuntimeNotify))] = s.Notify
s.interops[interopnames.ToID([]byte(interopnames.SystemBinaryAtoi))] = s.Atoi s.interops[interopnames.ToID([]byte(interopnames.SystemRuntimeGetTime))] = s.GetTime
s.interops[interopnames.ToID([]byte(interopnames.SystemBinaryItoa))] = s.Itoa
return s return s
} }
@ -126,24 +123,6 @@ func (s *storagePlugin) syscallHandler(v *vm.VM, id uint32) error {
return errors.New("syscall not found") return errors.New("syscall not found")
} }
func (s *storagePlugin) Atoi(v *vm.VM) error {
str := v.Estack().Pop().String()
base := v.Estack().Pop().BigInt().Int64()
n, err := strconv.ParseInt(str, int(base), 64)
if err != nil {
return err
}
v.Estack().PushVal(big.NewInt(n))
return nil
}
func (s *storagePlugin) Itoa(v *vm.VM) error {
n := v.Estack().Pop().BigInt()
base := v.Estack().Pop().BigInt()
v.Estack().PushVal(n.Text(int(base.Int64())))
return nil
}
func (s *storagePlugin) Notify(v *vm.VM) error { func (s *storagePlugin) Notify(v *vm.VM) error {
name := v.Estack().Pop().String() name := v.Estack().Pop().String()
item := stackitem.NewArray(v.Estack().Pop().Array()) item := stackitem.NewArray(v.Estack().Pop().Array())
@ -185,3 +164,10 @@ func (s *storagePlugin) GetContext(vm *vm.VM) error {
vm.Estack().PushVal(10) vm.Estack().PushVal(10)
return nil return nil
} }
func (s *storagePlugin) GetTime(vm *vm.VM) error {
// Pushing anything on the stack here will work. This is just to satisfy
// the compiler, thinking it has pushed the context ^^.
vm.Estack().PushVal(4)
return nil
}

View file

@ -1,91 +0,0 @@
package binary
import (
"encoding/hex"
"errors"
"math/big"
"strings"
"github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/encoding/bigint"
)
var (
// ErrInvalidBase is returned when base is invalid.
ErrInvalidBase = errors.New("invalid base")
// ErrInvalidFormat is returned when string is not a number.
ErrInvalidFormat = errors.New("invalid format")
)
// Itoa converts number to string.
func Itoa(ic *interop.Context) error {
num := ic.VM.Estack().Pop().BigInt()
base := ic.VM.Estack().Pop().BigInt()
if !base.IsInt64() {
return ErrInvalidBase
}
var s string
switch b := base.Int64(); b {
case 10:
s = num.Text(10)
case 16:
if num.Sign() == 0 {
s = "0"
break
}
bs := bigint.ToBytes(num)
reverse(bs)
s = hex.EncodeToString(bs)
if pad := bs[0] & 0xF8; pad == 0 || pad == 0xF8 {
s = s[1:]
}
s = strings.ToUpper(s)
default:
return ErrInvalidBase
}
ic.VM.Estack().PushVal(s)
return nil
}
// Atoi converts string to number.
func Atoi(ic *interop.Context) error {
num := ic.VM.Estack().Pop().String()
base := ic.VM.Estack().Pop().BigInt()
if !base.IsInt64() {
return ErrInvalidBase
}
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 {
return ErrInvalidFormat
}
case 16:
changed := len(num)%2 != 0
if changed {
num = "0" + num
}
bs, err := hex.DecodeString(num)
if err != nil {
return ErrInvalidFormat
}
if changed && bs[0]&0x8 != 0 {
bs[0] |= 0xF0
}
reverse(bs)
bi = bigint.FromBytes(bs)
default:
return ErrInvalidBase
}
ic.VM.Estack().PushVal(bi)
return nil
}
func reverse(b []byte) {
l := len(b)
for i := 0; i < l/2; i++ {
b[i], b[l-i-1] = b[l-i-1], b[i]
}
}

View file

@ -1,98 +0,0 @@
package binary
import (
"errors"
"math"
"math/big"
"testing"
"github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/stretchr/testify/require"
)
func TestItoa(t *testing.T) {
var testCases = []struct {
num *big.Int
base *big.Int
result string
}{
{big.NewInt(0), big.NewInt(10), "0"},
{big.NewInt(0), big.NewInt(16), "0"},
{big.NewInt(1), big.NewInt(10), "1"},
{big.NewInt(-1), big.NewInt(10), "-1"},
{big.NewInt(1), big.NewInt(16), "1"},
{big.NewInt(7), big.NewInt(16), "7"},
{big.NewInt(8), big.NewInt(16), "08"},
{big.NewInt(65535), big.NewInt(16), "0FFFF"},
{big.NewInt(15), big.NewInt(16), "0F"},
{big.NewInt(-1), big.NewInt(16), "F"},
}
for _, tc := range testCases {
ic := &interop.Context{VM: vm.New()}
ic.VM.Estack().PushVal(tc.base)
ic.VM.Estack().PushVal(tc.num)
require.NoError(t, Itoa(ic))
require.Equal(t, tc.result, ic.VM.Estack().Pop().String())
ic = &interop.Context{VM: vm.New()}
ic.VM.Estack().PushVal(tc.base)
ic.VM.Estack().PushVal(tc.result)
require.NoError(t, Atoi(ic))
require.Equal(t, tc.num, ic.VM.Estack().Pop().BigInt())
}
t.Run("-1", func(t *testing.T) {
for _, s := range []string{"FF", "FFF", "FFFF"} {
ic := &interop.Context{VM: vm.New()}
ic.VM.Estack().PushVal(16)
ic.VM.Estack().PushVal(s)
require.NoError(t, Atoi(ic))
require.Equal(t, big.NewInt(-1), ic.VM.Estack().Pop().BigInt())
}
})
}
func TestItoaError(t *testing.T) {
var testCases = []struct {
num *big.Int
base *big.Int
err error
}{
{big.NewInt(1), big.NewInt(13), ErrInvalidBase},
{big.NewInt(-1), new(big.Int).Add(big.NewInt(math.MaxInt64), big.NewInt(10)), ErrInvalidBase},
}
for _, tc := range testCases {
ic := &interop.Context{VM: vm.New()}
ic.VM.Estack().PushVal(tc.base)
ic.VM.Estack().PushVal(tc.num)
err := Itoa(ic)
require.True(t, errors.Is(err, tc.err), "got: %v", err)
}
}
func TestAtoiError(t *testing.T) {
var testCases = []struct {
num string
base *big.Int
err error
}{
{"1", big.NewInt(13), ErrInvalidBase},
{"1", new(big.Int).Add(big.NewInt(math.MaxInt64), big.NewInt(16)), ErrInvalidBase},
{"1_000", big.NewInt(10), ErrInvalidFormat},
{"FE", big.NewInt(10), ErrInvalidFormat},
{"XD", big.NewInt(16), ErrInvalidFormat},
}
for _, tc := range testCases {
ic := &interop.Context{VM: vm.New()}
ic.VM.Estack().PushVal(tc.base)
ic.VM.Estack().PushVal(tc.num)
err := Atoi(ic)
require.True(t, errors.Is(err, tc.err), "got: %v", err)
}
}

View file

@ -2,13 +2,11 @@ package interopnames
// Names of all used interops. // Names of all used interops.
const ( const (
SystemBinaryAtoi = "System.Binary.Atoi"
SystemBinaryBase58Decode = "System.Binary.Base58Decode" SystemBinaryBase58Decode = "System.Binary.Base58Decode"
SystemBinaryBase58Encode = "System.Binary.Base58Encode" SystemBinaryBase58Encode = "System.Binary.Base58Encode"
SystemBinaryBase64Decode = "System.Binary.Base64Decode" SystemBinaryBase64Decode = "System.Binary.Base64Decode"
SystemBinaryBase64Encode = "System.Binary.Base64Encode" SystemBinaryBase64Encode = "System.Binary.Base64Encode"
SystemBinaryDeserialize = "System.Binary.Deserialize" SystemBinaryDeserialize = "System.Binary.Deserialize"
SystemBinaryItoa = "System.Binary.Itoa"
SystemBinarySerialize = "System.Binary.Serialize" SystemBinarySerialize = "System.Binary.Serialize"
SystemCallbackCreate = "System.Callback.Create" SystemCallbackCreate = "System.Callback.Create"
SystemCallbackCreateFromMethod = "System.Callback.CreateFromMethod" SystemCallbackCreateFromMethod = "System.Callback.CreateFromMethod"
@ -54,13 +52,11 @@ const (
) )
var names = []string{ var names = []string{
SystemBinaryAtoi,
SystemBinaryBase58Decode, SystemBinaryBase58Decode,
SystemBinaryBase58Encode, SystemBinaryBase58Encode,
SystemBinaryBase64Decode, SystemBinaryBase64Decode,
SystemBinaryBase64Encode, SystemBinaryBase64Encode,
SystemBinaryDeserialize, SystemBinaryDeserialize,
SystemBinaryItoa,
SystemBinarySerialize, SystemBinarySerialize,
SystemCallbackCreate, SystemCallbackCreate,
SystemCallbackCreateFromMethod, SystemCallbackCreateFromMethod,

View file

@ -32,13 +32,11 @@ func SpawnVM(ic *interop.Context) *vm.VM {
// All lists are sorted, keep 'em this way, please. // All lists are sorted, keep 'em this way, please.
var systemInterops = []interop.Function{ var systemInterops = []interop.Function{
{Name: interopnames.SystemBinaryAtoi, Func: binary.Atoi, Price: 1 << 12, ParamCount: 2},
{Name: interopnames.SystemBinaryBase58Decode, Func: binary.DecodeBase58, Price: 1 << 12, ParamCount: 1}, {Name: interopnames.SystemBinaryBase58Decode, Func: binary.DecodeBase58, Price: 1 << 12, ParamCount: 1},
{Name: interopnames.SystemBinaryBase58Encode, Func: binary.EncodeBase58, Price: 1 << 12, ParamCount: 1}, {Name: interopnames.SystemBinaryBase58Encode, Func: binary.EncodeBase58, Price: 1 << 12, ParamCount: 1},
{Name: interopnames.SystemBinaryBase64Decode, Func: binary.DecodeBase64, Price: 1 << 12, ParamCount: 1}, {Name: interopnames.SystemBinaryBase64Decode, Func: binary.DecodeBase64, Price: 1 << 12, ParamCount: 1},
{Name: interopnames.SystemBinaryBase64Encode, Func: binary.EncodeBase64, Price: 1 << 12, ParamCount: 1}, {Name: interopnames.SystemBinaryBase64Encode, Func: binary.EncodeBase64, Price: 1 << 12, ParamCount: 1},
{Name: interopnames.SystemBinaryDeserialize, Func: binary.Deserialize, Price: 1 << 14, ParamCount: 1}, {Name: interopnames.SystemBinaryDeserialize, Func: binary.Deserialize, Price: 1 << 14, ParamCount: 1},
{Name: interopnames.SystemBinaryItoa, Func: binary.Itoa, Price: 1 << 12, ParamCount: 2},
{Name: interopnames.SystemBinarySerialize, Func: binary.Serialize, Price: 1 << 12, ParamCount: 1}, {Name: interopnames.SystemBinarySerialize, Func: binary.Serialize, Price: 1 << 12, ParamCount: 1},
{Name: interopnames.SystemContractCall, Func: contract.Call, Price: 1 << 15, {Name: interopnames.SystemContractCall, Func: contract.Call, Price: 1 << 15,
RequiredFlags: callflag.ReadStates | callflag.AllowCall, ParamCount: 4}, RequiredFlags: callflag.ReadStates | callflag.AllowCall, ParamCount: 4},

View file

@ -0,0 +1,96 @@
package native
import (
"math"
"math/big"
"testing"
"github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/stretchr/testify/require"
)
func TestStdLibItoaAtoi(t *testing.T) {
s := newStd()
ic := &interop.Context{VM: vm.New()}
var actual stackitem.Item
t.Run("itoa-atoi", func(t *testing.T) {
var testCases = []struct {
num *big.Int
base *big.Int
result string
}{
{big.NewInt(0), big.NewInt(10), "0"},
{big.NewInt(0), big.NewInt(16), "0"},
{big.NewInt(1), big.NewInt(10), "1"},
{big.NewInt(-1), big.NewInt(10), "-1"},
{big.NewInt(1), big.NewInt(16), "1"},
{big.NewInt(7), big.NewInt(16), "7"},
{big.NewInt(8), big.NewInt(16), "08"},
{big.NewInt(65535), big.NewInt(16), "0FFFF"},
{big.NewInt(15), big.NewInt(16), "0F"},
{big.NewInt(-1), big.NewInt(16), "F"},
}
for _, tc := range testCases {
require.NotPanics(t, func() {
actual = s.itoa(ic, []stackitem.Item{stackitem.Make(tc.num), stackitem.Make(tc.base)})
})
require.Equal(t, stackitem.Make(tc.result), actual)
require.NotPanics(t, func() {
actual = s.atoi(ic, []stackitem.Item{stackitem.Make(tc.result), stackitem.Make(tc.base)})
})
require.Equal(t, stackitem.Make(tc.num), actual)
}
t.Run("-1", func(t *testing.T) {
for _, str := range []string{"FF", "FFF", "FFFF"} {
require.NotPanics(t, func() {
actual = s.atoi(ic, []stackitem.Item{stackitem.Make(str), stackitem.Make(16)})
})
require.Equal(t, stackitem.Make(-1), actual)
}
})
})
t.Run("itoa error", func(t *testing.T) {
var testCases = []struct {
num *big.Int
base *big.Int
err error
}{
{big.NewInt(1), big.NewInt(13), ErrInvalidBase},
{big.NewInt(-1), new(big.Int).Add(big.NewInt(math.MaxInt64), big.NewInt(10)), ErrInvalidBase},
}
for _, tc := range testCases {
require.PanicsWithError(t, tc.err.Error(), func() {
_ = s.itoa(ic, []stackitem.Item{stackitem.Make(tc.num), stackitem.Make(tc.base)})
})
}
})
t.Run("atoi error", func(t *testing.T) {
var testCases = []struct {
num string
base *big.Int
err error
}{
{"1", big.NewInt(13), ErrInvalidBase},
{"1", new(big.Int).Add(big.NewInt(math.MaxInt64), big.NewInt(16)), ErrInvalidBase},
{"1_000", big.NewInt(10), ErrInvalidFormat},
{"FE", big.NewInt(10), ErrInvalidFormat},
{"XD", big.NewInt(16), ErrInvalidFormat},
}
for _, tc := range testCases {
require.PanicsWithError(t, tc.err.Error(), func() {
_ = s.atoi(ic, []stackitem.Item{stackitem.Make(tc.num), stackitem.Make(tc.base)})
})
}
})
}

View file

@ -44,15 +44,3 @@ func Base58Encode(b []byte) string {
func Base58Decode(b []byte) []byte { func Base58Decode(b []byte) []byte {
return neogointernal.Syscall1("System.Binary.Base58Decode", b).([]byte) return neogointernal.Syscall1("System.Binary.Base58Decode", b).([]byte)
} }
// Itoa converts num in a given base to string. Base should be either 10 or 16.
// It uses `System.Binary.Itoa` syscall.
func Itoa(num int, base int) string {
return neogointernal.Syscall2("System.Binary.Itoa", num, base).(string)
}
// Atoi converts string to a number in a given base. Base should be either 10 or 16.
// It uses `System.Binary.Atoi` syscall.
func Atoi(s string, base int) int {
return neogointernal.Syscall2("System.Binary.Atoi", s, base).(int)
}