forked from TrueCloudLab/neoneo-go
Merge pull request #1943 from nspcc-dev/itoa-overload
Add Itoa/Atoi overloads, add string-related methods to Std contract
This commit is contained in:
commit
c47fb6743e
4 changed files with 477 additions and 25 deletions
|
@ -225,7 +225,15 @@ func TestNativeHelpersCompile(t *testing.T) {
|
||||||
{"base58Encode", []string{"[]byte{1, 2, 3}"}},
|
{"base58Encode", []string{"[]byte{1, 2, 3}"}},
|
||||||
{"base58Decode", []string{"[]byte{1, 2, 3}"}},
|
{"base58Decode", []string{"[]byte{1, 2, 3}"}},
|
||||||
{"itoa", []string{"4", "10"}},
|
{"itoa", []string{"4", "10"}},
|
||||||
|
{"itoa10", []string{"4"}},
|
||||||
{"atoi", []string{`"4"`, "10"}},
|
{"atoi", []string{`"4"`, "10"}},
|
||||||
|
{"atoi10", []string{`"4"`}},
|
||||||
|
{"memoryCompare", []string{"[]byte{1}", "[]byte{2}"}},
|
||||||
|
{"memorySearch", []string{"[]byte{1}", "[]byte{2}"}},
|
||||||
|
{"memorySearchIndex", []string{"[]byte{1}", "[]byte{2}", "3"}},
|
||||||
|
{"memorySearchLastIndex", []string{"[]byte{1}", "[]byte{2}", "3"}},
|
||||||
|
{"stringSplit", []string{`"a,b"`, `","`}},
|
||||||
|
{"stringSplitNonEmpty", []string{`"a,b"`, `","`}},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -239,10 +247,33 @@ func runNativeTestCases(t *testing.T, ctr interop.ContractMD, name string, testC
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func runNativeTestCase(t *testing.T, ctr interop.ContractMD, name, method string, params ...string) {
|
func getMethod(t *testing.T, ctr interop.ContractMD, name string, params []string) interop.MethodAndPrice {
|
||||||
md, ok := ctr.GetMethod(strings.TrimSuffix(method, "WithData"), len(params))
|
paramLen := len(params)
|
||||||
require.True(t, ok)
|
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case name == "itoa10" || name == "atoi10":
|
||||||
|
name = name[:4]
|
||||||
|
case strings.HasPrefix(name, "memorySearch"):
|
||||||
|
if strings.HasSuffix(name, "LastIndex") {
|
||||||
|
paramLen += 1 // true should be appended inside of an interop
|
||||||
|
}
|
||||||
|
name = "memorySearch"
|
||||||
|
case strings.HasPrefix(name, "stringSplit"):
|
||||||
|
if strings.HasSuffix(name, "NonEmpty") {
|
||||||
|
paramLen += 1 // true should be appended inside of an interop
|
||||||
|
}
|
||||||
|
name = "stringSplit"
|
||||||
|
default:
|
||||||
|
name = strings.TrimSuffix(name, "WithData")
|
||||||
|
}
|
||||||
|
|
||||||
|
md, ok := ctr.GetMethod(name, paramLen)
|
||||||
|
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
|
isVoid := md.MD.ReturnType == smartcontract.VoidType
|
||||||
srcTmpl := `package foo
|
srcTmpl := `package foo
|
||||||
import "github.com/nspcc-dev/neo-go/pkg/interop/native/%s"
|
import "github.com/nspcc-dev/neo-go/pkg/interop/native/%s"
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package native
|
package native
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
|
@ -22,13 +23,20 @@ type Std struct {
|
||||||
interop.ContractMD
|
interop.ContractMD
|
||||||
}
|
}
|
||||||
|
|
||||||
const stdContractID = -2
|
const (
|
||||||
|
stdContractID = -2
|
||||||
|
|
||||||
|
// stdMaxInputLength is the maximum input length for string-related methods.
|
||||||
|
stdMaxInputLength = 1024
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// ErrInvalidBase is returned when base is invalid.
|
// ErrInvalidBase is returned when base is invalid.
|
||||||
ErrInvalidBase = errors.New("invalid base")
|
ErrInvalidBase = errors.New("invalid base")
|
||||||
// ErrInvalidFormat is returned when string is not a number.
|
// ErrInvalidFormat is returned when string is not a number.
|
||||||
ErrInvalidFormat = errors.New("invalid format")
|
ErrInvalidFormat = errors.New("invalid format")
|
||||||
|
// ErrTooBigInput is returned when input exceeds size limit.
|
||||||
|
ErrTooBigInput = errors.New("input is too big")
|
||||||
)
|
)
|
||||||
|
|
||||||
func newStd() *Std {
|
func newStd() *Std {
|
||||||
|
@ -61,30 +69,80 @@ func newStd() *Std {
|
||||||
md = newMethodAndPrice(s.itoa, 1<<12, callflag.NoneFlag)
|
md = newMethodAndPrice(s.itoa, 1<<12, callflag.NoneFlag)
|
||||||
s.AddMethod(md, desc)
|
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,
|
desc = newDescriptor("atoi", smartcontract.IntegerType,
|
||||||
manifest.NewParameter("value", smartcontract.StringType),
|
manifest.NewParameter("value", smartcontract.StringType),
|
||||||
manifest.NewParameter("base", smartcontract.IntegerType))
|
manifest.NewParameter("base", smartcontract.IntegerType))
|
||||||
md = newMethodAndPrice(s.atoi, 1<<12, callflag.NoneFlag)
|
md = newMethodAndPrice(s.atoi, 1<<6, callflag.NoneFlag)
|
||||||
|
s.AddMethod(md, desc)
|
||||||
|
|
||||||
|
desc = newDescriptor("atoi", smartcontract.IntegerType,
|
||||||
|
manifest.NewParameter("value", smartcontract.StringType))
|
||||||
|
md = newMethodAndPrice(s.atoi10, 1<<6, callflag.NoneFlag)
|
||||||
s.AddMethod(md, desc)
|
s.AddMethod(md, desc)
|
||||||
|
|
||||||
desc = newDescriptor("base64Encode", smartcontract.StringType,
|
desc = newDescriptor("base64Encode", smartcontract.StringType,
|
||||||
manifest.NewParameter("data", smartcontract.ByteArrayType))
|
manifest.NewParameter("data", smartcontract.ByteArrayType))
|
||||||
md = newMethodAndPrice(s.base64Encode, 1<<12, callflag.NoneFlag)
|
md = newMethodAndPrice(s.base64Encode, 1<<5, callflag.NoneFlag)
|
||||||
s.AddMethod(md, desc)
|
s.AddMethod(md, desc)
|
||||||
|
|
||||||
desc = newDescriptor("base64Decode", smartcontract.ByteArrayType,
|
desc = newDescriptor("base64Decode", smartcontract.ByteArrayType,
|
||||||
manifest.NewParameter("s", smartcontract.StringType))
|
manifest.NewParameter("s", smartcontract.StringType))
|
||||||
md = newMethodAndPrice(s.base64Decode, 1<<12, callflag.NoneFlag)
|
md = newMethodAndPrice(s.base64Decode, 1<<5, callflag.NoneFlag)
|
||||||
s.AddMethod(md, desc)
|
s.AddMethod(md, desc)
|
||||||
|
|
||||||
desc = newDescriptor("base58Encode", smartcontract.StringType,
|
desc = newDescriptor("base58Encode", smartcontract.StringType,
|
||||||
manifest.NewParameter("data", smartcontract.ByteArrayType))
|
manifest.NewParameter("data", smartcontract.ByteArrayType))
|
||||||
md = newMethodAndPrice(s.base58Encode, 1<<12, callflag.NoneFlag)
|
md = newMethodAndPrice(s.base58Encode, 1<<13, callflag.NoneFlag)
|
||||||
s.AddMethod(md, desc)
|
s.AddMethod(md, desc)
|
||||||
|
|
||||||
desc = newDescriptor("base58Decode", smartcontract.ByteArrayType,
|
desc = newDescriptor("base58Decode", smartcontract.ByteArrayType,
|
||||||
manifest.NewParameter("s", smartcontract.StringType))
|
manifest.NewParameter("s", smartcontract.StringType))
|
||||||
md = newMethodAndPrice(s.base58Decode, 1<<12, callflag.NoneFlag)
|
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)
|
||||||
|
|
||||||
|
desc = newDescriptor("memorySearch", smartcontract.IntegerType,
|
||||||
|
manifest.NewParameter("mem", smartcontract.ByteArrayType),
|
||||||
|
manifest.NewParameter("value", smartcontract.ByteArrayType))
|
||||||
|
md = newMethodAndPrice(s.memorySearch2, 1<<6, callflag.NoneFlag)
|
||||||
|
s.AddMethod(md, desc)
|
||||||
|
|
||||||
|
desc = newDescriptor("memorySearch", smartcontract.IntegerType,
|
||||||
|
manifest.NewParameter("mem", smartcontract.ByteArrayType),
|
||||||
|
manifest.NewParameter("value", smartcontract.ByteArrayType),
|
||||||
|
manifest.NewParameter("start", smartcontract.IntegerType))
|
||||||
|
md = newMethodAndPrice(s.memorySearch3, 1<<6, callflag.NoneFlag)
|
||||||
|
s.AddMethod(md, desc)
|
||||||
|
|
||||||
|
desc = newDescriptor("memorySearch", smartcontract.IntegerType,
|
||||||
|
manifest.NewParameter("mem", smartcontract.ByteArrayType),
|
||||||
|
manifest.NewParameter("value", smartcontract.ByteArrayType),
|
||||||
|
manifest.NewParameter("start", smartcontract.IntegerType),
|
||||||
|
manifest.NewParameter("backward", smartcontract.BoolType))
|
||||||
|
md = newMethodAndPrice(s.memorySearch4, 1<<6, callflag.NoneFlag)
|
||||||
|
s.AddMethod(md, desc)
|
||||||
|
|
||||||
|
desc = newDescriptor("stringSplit", smartcontract.ArrayType,
|
||||||
|
manifest.NewParameter("str", smartcontract.StringType),
|
||||||
|
manifest.NewParameter("separator", smartcontract.StringType))
|
||||||
|
md = newMethodAndPrice(s.stringSplit2, 1<<8, callflag.NoneFlag)
|
||||||
|
s.AddMethod(md, desc)
|
||||||
|
|
||||||
|
desc = newDescriptor("stringSplit", smartcontract.ArrayType,
|
||||||
|
manifest.NewParameter("str", smartcontract.StringType),
|
||||||
|
manifest.NewParameter("separator", smartcontract.StringType),
|
||||||
|
manifest.NewParameter("removeEmptyEntries", smartcontract.BoolType))
|
||||||
|
md = newMethodAndPrice(s.stringSplit3, 1<<8, callflag.NoneFlag)
|
||||||
s.AddMethod(md, desc)
|
s.AddMethod(md, desc)
|
||||||
|
|
||||||
return s
|
return s
|
||||||
|
@ -142,6 +200,11 @@ func (s *Std) jsonDeserialize(_ *interop.Context, args []stackitem.Item) stackit
|
||||||
return item
|
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 {
|
func (s *Std) itoa(_ *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||||
num := toBigInt(args[0])
|
num := toBigInt(args[0])
|
||||||
base := toBigInt(args[1])
|
base := toBigInt(args[1])
|
||||||
|
@ -170,8 +233,22 @@ func (s *Std) itoa(_ *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||||
return stackitem.NewByteArray([]byte(str))
|
return stackitem.NewByteArray([]byte(str))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Std) atoi10(_ *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||||
|
num := s.toLimitedString(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 {
|
func (s *Std) atoi(_ *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||||
num := toString(args[0])
|
num := s.toLimitedString(args[0])
|
||||||
base := toBigInt(args[1])
|
base := toBigInt(args[1])
|
||||||
if !base.IsInt64() {
|
if !base.IsInt64() {
|
||||||
panic(ErrInvalidBase)
|
panic(ErrInvalidBase)
|
||||||
|
@ -179,11 +256,7 @@ func (s *Std) atoi(_ *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||||
var bi *big.Int
|
var bi *big.Int
|
||||||
switch b := base.Int64(); b {
|
switch b := base.Int64(); b {
|
||||||
case 10:
|
case 10:
|
||||||
var ok bool
|
bi = s.atoi10Aux(num)
|
||||||
bi, ok = new(big.Int).SetString(num, int(b))
|
|
||||||
if !ok {
|
|
||||||
panic(ErrInvalidFormat)
|
|
||||||
}
|
|
||||||
case 16:
|
case 16:
|
||||||
changed := len(num)%2 != 0
|
changed := len(num)%2 != 0
|
||||||
if changed {
|
if changed {
|
||||||
|
@ -213,17 +286,14 @@ func reverse(b []byte) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Std) base64Encode(_ *interop.Context, args []stackitem.Item) stackitem.Item {
|
func (s *Std) base64Encode(_ *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||||
src, err := args[0].TryBytes()
|
src := s.toLimitedBytes(args[0])
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
result := base64.StdEncoding.EncodeToString(src)
|
result := base64.StdEncoding.EncodeToString(src)
|
||||||
|
|
||||||
return stackitem.NewByteArray([]byte(result))
|
return stackitem.NewByteArray([]byte(result))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Std) base64Decode(_ *interop.Context, args []stackitem.Item) stackitem.Item {
|
func (s *Std) base64Decode(_ *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||||
src := toString(args[0])
|
src := s.toLimitedString(args[0])
|
||||||
result, err := base64.StdEncoding.DecodeString(src)
|
result, err := base64.StdEncoding.DecodeString(src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
@ -233,17 +303,14 @@ func (s *Std) base64Decode(_ *interop.Context, args []stackitem.Item) stackitem.
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Std) base58Encode(_ *interop.Context, args []stackitem.Item) stackitem.Item {
|
func (s *Std) base58Encode(_ *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||||
src, err := args[0].TryBytes()
|
src := s.toLimitedBytes(args[0])
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
result := base58.Encode(src)
|
result := base58.Encode(src)
|
||||||
|
|
||||||
return stackitem.NewByteArray([]byte(result))
|
return stackitem.NewByteArray([]byte(result))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Std) base58Decode(_ *interop.Context, args []stackitem.Item) stackitem.Item {
|
func (s *Std) base58Decode(_ *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||||
src := toString(args[0])
|
src := s.toLimitedString(args[0])
|
||||||
result, err := base58.Decode(src)
|
result, err := base58.Decode(src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
@ -252,6 +319,85 @@ func (s *Std) base58Decode(_ *interop.Context, args []stackitem.Item) stackitem.
|
||||||
return stackitem.NewByteArray(result)
|
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))))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Std) memorySearch2(_ *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||||
|
mem := s.toLimitedBytes(args[0])
|
||||||
|
val := s.toLimitedBytes(args[1])
|
||||||
|
index := s.memorySearchAux(mem, val, 0, false)
|
||||||
|
return stackitem.NewBigInteger(big.NewInt(int64(index)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Std) memorySearch3(_ *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||||
|
mem := s.toLimitedBytes(args[0])
|
||||||
|
val := s.toLimitedBytes(args[1])
|
||||||
|
start := toUint32(args[2])
|
||||||
|
index := s.memorySearchAux(mem, val, int(start), false)
|
||||||
|
return stackitem.NewBigInteger(big.NewInt(int64(index)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Std) memorySearch4(_ *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||||
|
mem := s.toLimitedBytes(args[0])
|
||||||
|
val := s.toLimitedBytes(args[1])
|
||||||
|
start := toUint32(args[2])
|
||||||
|
backward, err := args[3].TryBool()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
index := s.memorySearchAux(mem, val, int(start), backward)
|
||||||
|
return stackitem.NewBigInteger(big.NewInt(int64(index)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Std) memorySearchAux(mem, val []byte, start int, backward bool) int {
|
||||||
|
if backward {
|
||||||
|
if start > len(mem) { // panic in case if cap(mem) > len(mem) for some reasons
|
||||||
|
panic("invalid start index")
|
||||||
|
}
|
||||||
|
return bytes.LastIndex(mem[:start], val)
|
||||||
|
}
|
||||||
|
|
||||||
|
index := bytes.Index(mem[start:], val)
|
||||||
|
if index < 0 {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
return index + start
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Std) stringSplit2(_ *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||||
|
str := s.toLimitedString(args[0])
|
||||||
|
sep := toString(args[1])
|
||||||
|
return stackitem.NewArray(s.stringSplitAux(str, sep, false))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Std) stringSplit3(_ *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||||
|
str := s.toLimitedString(args[0])
|
||||||
|
sep := toString(args[1])
|
||||||
|
removeEmpty, err := args[2].TryBool()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return stackitem.NewArray(s.stringSplitAux(str, sep, removeEmpty))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Std) stringSplitAux(str, sep string, removeEmpty bool) []stackitem.Item {
|
||||||
|
var result []stackitem.Item
|
||||||
|
|
||||||
|
arr := strings.Split(str, sep)
|
||||||
|
for i := range arr {
|
||||||
|
if !removeEmpty || len(arr[i]) != 0 {
|
||||||
|
result = append(result, stackitem.Make(arr[i]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
// Metadata implements Contract interface.
|
// Metadata implements Contract interface.
|
||||||
func (s *Std) Metadata() *interop.ContractMD {
|
func (s *Std) Metadata() *interop.ContractMD {
|
||||||
return &s.ContractMD
|
return &s.ContractMD
|
||||||
|
@ -271,3 +417,22 @@ func (s *Std) OnPersist(ic *interop.Context) error {
|
||||||
func (s *Std) PostPersist(ic *interop.Context) error {
|
func (s *Std) PostPersist(ic *interop.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Std) toLimitedBytes(item stackitem.Item) []byte {
|
||||||
|
src, err := item.TryBytes()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if len(src) > stdMaxInputLength {
|
||||||
|
panic(ErrTooBigInput)
|
||||||
|
}
|
||||||
|
return src
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Std) toLimitedString(item stackitem.Item) string {
|
||||||
|
src := toString(item)
|
||||||
|
if len(src) > stdMaxInputLength {
|
||||||
|
panic(ErrTooBigInput)
|
||||||
|
}
|
||||||
|
return src
|
||||||
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"math"
|
"math"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/mr-tron/base58"
|
"github.com/mr-tron/base58"
|
||||||
|
@ -49,6 +50,18 @@ func TestStdLibItoaAtoi(t *testing.T) {
|
||||||
actual = s.atoi(ic, []stackitem.Item{stackitem.Make(tc.result), stackitem.Make(tc.base)})
|
actual = s.atoi(ic, []stackitem.Item{stackitem.Make(tc.result), stackitem.Make(tc.base)})
|
||||||
})
|
})
|
||||||
require.Equal(t, stackitem.Make(tc.num), actual)
|
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) {
|
t.Run("-1", func(t *testing.T) {
|
||||||
|
@ -90,6 +103,7 @@ func TestStdLibItoaAtoi(t *testing.T) {
|
||||||
{"1_000", big.NewInt(10), ErrInvalidFormat},
|
{"1_000", big.NewInt(10), ErrInvalidFormat},
|
||||||
{"FE", big.NewInt(10), ErrInvalidFormat},
|
{"FE", big.NewInt(10), ErrInvalidFormat},
|
||||||
{"XD", big.NewInt(16), ErrInvalidFormat},
|
{"XD", big.NewInt(16), ErrInvalidFormat},
|
||||||
|
{strings.Repeat("0", stdMaxInputLength+1), big.NewInt(10), ErrTooBigInput},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
@ -152,18 +166,28 @@ func TestStdLibEncodeDecode(t *testing.T) {
|
||||||
ic := &interop.Context{VM: vm.New()}
|
ic := &interop.Context{VM: vm.New()}
|
||||||
var actual stackitem.Item
|
var actual stackitem.Item
|
||||||
|
|
||||||
|
bigInputArgs := []stackitem.Item{stackitem.Make(strings.Repeat("6", stdMaxInputLength+1))}
|
||||||
|
|
||||||
t.Run("Encode64", func(t *testing.T) {
|
t.Run("Encode64", func(t *testing.T) {
|
||||||
require.NotPanics(t, func() {
|
require.NotPanics(t, func() {
|
||||||
actual = s.base64Encode(ic, []stackitem.Item{stackitem.Make(original)})
|
actual = s.base64Encode(ic, []stackitem.Item{stackitem.Make(original)})
|
||||||
})
|
})
|
||||||
require.Equal(t, stackitem.Make(encoded64), actual)
|
require.Equal(t, stackitem.Make(encoded64), actual)
|
||||||
})
|
})
|
||||||
|
t.Run("Encode64/error", func(t *testing.T) {
|
||||||
|
require.PanicsWithError(t, ErrTooBigInput.Error(),
|
||||||
|
func() { s.base64Encode(ic, bigInputArgs) })
|
||||||
|
})
|
||||||
t.Run("Encode58", func(t *testing.T) {
|
t.Run("Encode58", func(t *testing.T) {
|
||||||
require.NotPanics(t, func() {
|
require.NotPanics(t, func() {
|
||||||
actual = s.base58Encode(ic, []stackitem.Item{stackitem.Make(original)})
|
actual = s.base58Encode(ic, []stackitem.Item{stackitem.Make(original)})
|
||||||
})
|
})
|
||||||
require.Equal(t, stackitem.Make(encoded58), actual)
|
require.Equal(t, stackitem.Make(encoded58), actual)
|
||||||
})
|
})
|
||||||
|
t.Run("Encode58/error", func(t *testing.T) {
|
||||||
|
require.PanicsWithError(t, ErrTooBigInput.Error(),
|
||||||
|
func() { s.base58Encode(ic, bigInputArgs) })
|
||||||
|
})
|
||||||
t.Run("Decode64/positive", func(t *testing.T) {
|
t.Run("Decode64/positive", func(t *testing.T) {
|
||||||
require.NotPanics(t, func() {
|
require.NotPanics(t, func() {
|
||||||
actual = s.base64Decode(ic, []stackitem.Item{stackitem.Make(encoded64)})
|
actual = s.base64Decode(ic, []stackitem.Item{stackitem.Make(encoded64)})
|
||||||
|
@ -177,6 +201,8 @@ func TestStdLibEncodeDecode(t *testing.T) {
|
||||||
require.Panics(t, func() {
|
require.Panics(t, func() {
|
||||||
_ = s.base64Decode(ic, []stackitem.Item{stackitem.NewInterop(nil)})
|
_ = s.base64Decode(ic, []stackitem.Item{stackitem.NewInterop(nil)})
|
||||||
})
|
})
|
||||||
|
require.PanicsWithError(t, ErrTooBigInput.Error(),
|
||||||
|
func() { s.base64Decode(ic, bigInputArgs) })
|
||||||
})
|
})
|
||||||
t.Run("Decode58/positive", func(t *testing.T) {
|
t.Run("Decode58/positive", func(t *testing.T) {
|
||||||
require.NotPanics(t, func() {
|
require.NotPanics(t, func() {
|
||||||
|
@ -191,6 +217,8 @@ func TestStdLibEncodeDecode(t *testing.T) {
|
||||||
require.Panics(t, func() {
|
require.Panics(t, func() {
|
||||||
_ = s.base58Decode(ic, []stackitem.Item{stackitem.NewInterop(nil)})
|
_ = s.base58Decode(ic, []stackitem.Item{stackitem.NewInterop(nil)})
|
||||||
})
|
})
|
||||||
|
require.PanicsWithError(t, ErrTooBigInput.Error(),
|
||||||
|
func() { s.base58Decode(ic, bigInputArgs) })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -326,3 +354,174 @@ 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}) })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMemorySearch(t *testing.T) {
|
||||||
|
s := newStd()
|
||||||
|
ic := &interop.Context{VM: vm.New()}
|
||||||
|
|
||||||
|
check := func(t *testing.T, result int64, args ...interface{}) {
|
||||||
|
items := make([]stackitem.Item, len(args))
|
||||||
|
for i := range args {
|
||||||
|
items[i] = stackitem.Make(args[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
var actual stackitem.Item
|
||||||
|
switch len(items) {
|
||||||
|
case 2:
|
||||||
|
actual = s.memorySearch2(ic, items)
|
||||||
|
case 3:
|
||||||
|
actual = s.memorySearch3(ic, items)
|
||||||
|
case 4:
|
||||||
|
actual = s.memorySearch4(ic, items)
|
||||||
|
default:
|
||||||
|
panic("invalid args length")
|
||||||
|
}
|
||||||
|
require.Equal(t, big.NewInt(result), actual.Value())
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("C# compatibility", func(t *testing.T) {
|
||||||
|
// These tests are taken from C# node.
|
||||||
|
check(t, 2, "abc", "c", 0)
|
||||||
|
check(t, 2, "abc", "c", 1)
|
||||||
|
check(t, 2, "abc", "c", 2)
|
||||||
|
check(t, -1, "abc", "c", 3)
|
||||||
|
check(t, -1, "abc", "d", 0)
|
||||||
|
|
||||||
|
check(t, 2, "abc", "c", 0, false)
|
||||||
|
check(t, 2, "abc", "c", 1, false)
|
||||||
|
check(t, 2, "abc", "c", 2, false)
|
||||||
|
check(t, -1, "abc", "c", 3, false)
|
||||||
|
check(t, -1, "abc", "d", 0, false)
|
||||||
|
|
||||||
|
check(t, -1, "abc", "c", 0, true)
|
||||||
|
check(t, -1, "abc", "c", 1, true)
|
||||||
|
check(t, -1, "abc", "c", 2, true)
|
||||||
|
check(t, 2, "abc", "c", 3, true)
|
||||||
|
check(t, -1, "abc", "d", 0, true)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("boundary indices", func(t *testing.T) {
|
||||||
|
arg := stackitem.Make("aaa")
|
||||||
|
require.Panics(t, func() {
|
||||||
|
s.memorySearch3(ic, []stackitem.Item{arg, arg, stackitem.Make(-1)})
|
||||||
|
})
|
||||||
|
require.Panics(t, func() {
|
||||||
|
s.memorySearch3(ic, []stackitem.Item{arg, arg, stackitem.Make(4)})
|
||||||
|
})
|
||||||
|
t.Run("still in capacity", func(t *testing.T) {
|
||||||
|
require.Panics(t, func() {
|
||||||
|
arr := stackitem.NewByteArray(make([]byte, 5, 10))
|
||||||
|
s.memorySearch3(ic, []stackitem.Item{arr, arg, stackitem.Make(7)})
|
||||||
|
})
|
||||||
|
require.Panics(t, func() {
|
||||||
|
arr := stackitem.NewByteArray(make([]byte, 5, 10))
|
||||||
|
s.memorySearch4(ic, []stackitem.Item{arr, arg,
|
||||||
|
stackitem.Make(7), stackitem.Make(true)})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("big arguments", func(t *testing.T) {
|
||||||
|
s1 := stackitem.Make(strings.Repeat("x", stdMaxInputLength+1))
|
||||||
|
s2 := stackitem.Make("xxx")
|
||||||
|
start := stackitem.Make(1)
|
||||||
|
b := stackitem.Make(true)
|
||||||
|
|
||||||
|
require.PanicsWithError(t, ErrTooBigInput.Error(),
|
||||||
|
func() { s.memorySearch2(ic, []stackitem.Item{s1, s2}) })
|
||||||
|
|
||||||
|
require.PanicsWithError(t, ErrTooBigInput.Error(),
|
||||||
|
func() { s.memorySearch2(ic, []stackitem.Item{s2, s1}) })
|
||||||
|
|
||||||
|
require.PanicsWithError(t, ErrTooBigInput.Error(),
|
||||||
|
func() { s.memorySearch3(ic, []stackitem.Item{s1, s2, start}) })
|
||||||
|
|
||||||
|
require.PanicsWithError(t, ErrTooBigInput.Error(),
|
||||||
|
func() { s.memorySearch3(ic, []stackitem.Item{s2, s1, start}) })
|
||||||
|
|
||||||
|
require.PanicsWithError(t, ErrTooBigInput.Error(),
|
||||||
|
func() { s.memorySearch4(ic, []stackitem.Item{s1, s2, start, b}) })
|
||||||
|
|
||||||
|
require.PanicsWithError(t, ErrTooBigInput.Error(),
|
||||||
|
func() { s.memorySearch4(ic, []stackitem.Item{s2, s1, start, b}) })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringSplit(t *testing.T) {
|
||||||
|
s := newStd()
|
||||||
|
ic := &interop.Context{VM: vm.New()}
|
||||||
|
|
||||||
|
check := func(t *testing.T, result []string, str, sep string, remove interface{}) {
|
||||||
|
args := []stackitem.Item{stackitem.Make(str), stackitem.Make(sep)}
|
||||||
|
var actual stackitem.Item
|
||||||
|
if remove == nil {
|
||||||
|
actual = s.stringSplit2(ic, args)
|
||||||
|
} else {
|
||||||
|
args = append(args, stackitem.NewBool(remove.(bool)))
|
||||||
|
actual = s.stringSplit3(ic, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
arr, ok := actual.Value().([]stackitem.Item)
|
||||||
|
require.True(t, ok)
|
||||||
|
require.Equal(t, len(result), len(arr))
|
||||||
|
for i := range result {
|
||||||
|
require.Equal(t, stackitem.Make(result[i]), arr[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
check(t, []string{"a", "b", "c"}, "abc", "", nil)
|
||||||
|
check(t, []string{"a", "b", "c"}, "abc", "", true)
|
||||||
|
check(t, []string{"a", "c", "", "", "d"}, "abcbbbd", "b", nil)
|
||||||
|
check(t, []string{"a", "c", "", "", "d"}, "abcbbbd", "b", false)
|
||||||
|
check(t, []string{"a", "c", "d"}, "abcbbbd", "b", true)
|
||||||
|
check(t, []string{""}, "", "abc", nil)
|
||||||
|
check(t, []string{}, "", "abc", true)
|
||||||
|
|
||||||
|
t.Run("C# compatibility", func(t *testing.T) {
|
||||||
|
// These tests are taken from C# node.
|
||||||
|
check(t, []string{"a", "b"}, "a,b", ",", nil)
|
||||||
|
})
|
||||||
|
|
||||||
|
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.stringSplit2(ic, []stackitem.Item{s1, s2}) })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -93,9 +93,66 @@ func Itoa(num int, base int) string {
|
||||||
num, base).(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.
|
// 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.
|
// It uses `atoi` method of StdLib native contract.
|
||||||
func Atoi(s string, base int) int {
|
func Atoi(s string, base int) int {
|
||||||
return contract.Call(interop.Hash160(Hash), "atoi", contract.NoneFlag,
|
return contract.Call(interop.Hash160(Hash), "atoi", contract.NoneFlag,
|
||||||
s, base).(int)
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MemorySearch returns index of the first occurrence of val in mem.
|
||||||
|
// If not found, -1 is returned. It uses `memorySearch` method of StdLib native contract.
|
||||||
|
func MemorySearch(mem, pattern []byte) int {
|
||||||
|
return contract.Call(interop.Hash160(Hash), "memorySearch", contract.NoneFlag,
|
||||||
|
mem, pattern).(int)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MemorySearchIndex returns index of the first occurrence of val in mem starting from start.
|
||||||
|
// If not found, -1 is returned. It uses `memorySearch` method of StdLib native contract.
|
||||||
|
func MemorySearchIndex(mem, pattern []byte, start int) int {
|
||||||
|
return contract.Call(interop.Hash160(Hash), "memorySearch", contract.NoneFlag,
|
||||||
|
mem, pattern, start).(int)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MemorySearchLastIndex returns index of the last occurrence of val in mem ending before start.
|
||||||
|
// If not found, -1 is returned. It uses `memorySearch` method of StdLib native contract.
|
||||||
|
func MemorySearchLastIndex(mem, pattern []byte, start int) int {
|
||||||
|
return contract.Call(interop.Hash160(Hash), "memorySearch", contract.NoneFlag,
|
||||||
|
mem, pattern, start, true).(int)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringSplit splits s by occurrences of sep.
|
||||||
|
// It uses `stringSplit` method of StdLib native contract.
|
||||||
|
func StringSplit(s, sep string) []string {
|
||||||
|
return contract.Call(interop.Hash160(Hash), "stringSplit", contract.NoneFlag,
|
||||||
|
s, sep).([]string)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringSplitNonEmpty splits s by occurrences of sep and returns a list of non-empty items.
|
||||||
|
// It uses `stringSplit` method of StdLib native contract.
|
||||||
|
func StringSplitNonEmpty(s, sep string) []string {
|
||||||
|
return contract.Call(interop.Hash160(Hash), "stringSplit", contract.NoneFlag,
|
||||||
|
s, sep, true).([]string)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue