forked from TrueCloudLab/neoneo-go
Merge pull request #1530 from nspcc-dev/syscall/atoi
core: implement `System.Binary.Atoi/Itoa` syscalls
This commit is contained in:
commit
62d6f3ba22
6 changed files with 210 additions and 0 deletions
|
@ -5,11 +5,13 @@ import "github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames"
|
||||||
// All lists are sorted, keep 'em this way, please.
|
// All lists are sorted, keep 'em this way, please.
|
||||||
var syscalls = map[string]map[string]string{
|
var syscalls = map[string]map[string]string{
|
||||||
"binary": {
|
"binary": {
|
||||||
|
"Atoi": interopnames.SystemBinaryAtoi,
|
||||||
"Base58Decode": interopnames.SystemBinaryBase58Decode,
|
"Base58Decode": interopnames.SystemBinaryBase58Decode,
|
||||||
"Base58Encode": interopnames.SystemBinaryBase58Encode,
|
"Base58Encode": interopnames.SystemBinaryBase58Encode,
|
||||||
"Base64Decode": interopnames.SystemBinaryBase64Decode,
|
"Base64Decode": interopnames.SystemBinaryBase64Decode,
|
||||||
"Base64Encode": interopnames.SystemBinaryBase64Encode,
|
"Base64Encode": interopnames.SystemBinaryBase64Encode,
|
||||||
"Deserialize": interopnames.SystemBinaryDeserialize,
|
"Deserialize": interopnames.SystemBinaryDeserialize,
|
||||||
|
"Itoa": interopnames.SystemBinaryItoa,
|
||||||
"Serialize": interopnames.SystemBinarySerialize,
|
"Serialize": interopnames.SystemBinarySerialize,
|
||||||
},
|
},
|
||||||
"blockchain": {
|
"blockchain": {
|
||||||
|
|
91
pkg/core/interop/binary/itoa.go
Normal file
91
pkg/core/interop/binary/itoa.go
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
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]
|
||||||
|
}
|
||||||
|
}
|
98
pkg/core/interop/binary/itoa_test.go
Normal file
98
pkg/core/interop/binary/itoa_test.go
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,11 +2,13 @@ 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"
|
||||||
SystemBlockchainGetBlock = "System.Blockchain.GetBlock"
|
SystemBlockchainGetBlock = "System.Blockchain.GetBlock"
|
||||||
SystemBlockchainGetContract = "System.Blockchain.GetContract"
|
SystemBlockchainGetContract = "System.Blockchain.GetContract"
|
||||||
|
@ -69,11 +71,13 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
var names = []string{
|
var names = []string{
|
||||||
|
SystemBinaryAtoi,
|
||||||
SystemBinaryBase58Decode,
|
SystemBinaryBase58Decode,
|
||||||
SystemBinaryBase58Encode,
|
SystemBinaryBase58Encode,
|
||||||
SystemBinaryBase64Decode,
|
SystemBinaryBase64Decode,
|
||||||
SystemBinaryBase64Encode,
|
SystemBinaryBase64Encode,
|
||||||
SystemBinaryDeserialize,
|
SystemBinaryDeserialize,
|
||||||
|
SystemBinaryItoa,
|
||||||
SystemBinarySerialize,
|
SystemBinarySerialize,
|
||||||
SystemBlockchainGetBlock,
|
SystemBlockchainGetBlock,
|
||||||
SystemBlockchainGetContract,
|
SystemBlockchainGetContract,
|
||||||
|
|
|
@ -9,6 +9,7 @@ package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/interop/binary"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/callback"
|
"github.com/nspcc-dev/neo-go/pkg/core/interop/callback"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/contract"
|
"github.com/nspcc-dev/neo-go/pkg/core/interop/contract"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/crypto"
|
"github.com/nspcc-dev/neo-go/pkg/core/interop/crypto"
|
||||||
|
@ -32,11 +33,13 @@ 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: 100000, ParamCount: 2},
|
||||||
{Name: interopnames.SystemBinaryBase58Decode, Func: runtimeDecodeBase58, Price: 100000, ParamCount: 1},
|
{Name: interopnames.SystemBinaryBase58Decode, Func: runtimeDecodeBase58, Price: 100000, ParamCount: 1},
|
||||||
{Name: interopnames.SystemBinaryBase58Encode, Func: runtimeEncodeBase58, Price: 100000, ParamCount: 1},
|
{Name: interopnames.SystemBinaryBase58Encode, Func: runtimeEncodeBase58, Price: 100000, ParamCount: 1},
|
||||||
{Name: interopnames.SystemBinaryBase64Decode, Func: runtimeDecodeBase64, Price: 100000, ParamCount: 1},
|
{Name: interopnames.SystemBinaryBase64Decode, Func: runtimeDecodeBase64, Price: 100000, ParamCount: 1},
|
||||||
{Name: interopnames.SystemBinaryBase64Encode, Func: runtimeEncodeBase64, Price: 100000, ParamCount: 1},
|
{Name: interopnames.SystemBinaryBase64Encode, Func: runtimeEncodeBase64, Price: 100000, ParamCount: 1},
|
||||||
{Name: interopnames.SystemBinaryDeserialize, Func: runtimeDeserialize, Price: 500000, ParamCount: 1},
|
{Name: interopnames.SystemBinaryDeserialize, Func: runtimeDeserialize, 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: runtimeSerialize, Price: 100000, ParamCount: 1},
|
||||||
{Name: interopnames.SystemBlockchainGetBlock, Func: bcGetBlock, Price: 2500000,
|
{Name: interopnames.SystemBlockchainGetBlock, Func: bcGetBlock, Price: 2500000,
|
||||||
RequiredFlags: smartcontract.AllowStates, ParamCount: 1},
|
RequiredFlags: smartcontract.AllowStates, ParamCount: 1},
|
||||||
|
|
|
@ -40,3 +40,15 @@ func Base58Encode(b []byte) string {
|
||||||
func Base58Decode(b []byte) []byte {
|
func Base58Decode(b []byte) []byte {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 0
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue