neo-go/pkg/core/native/std.go
Anna Shaleva 406c9f8b92 core, interop: add strLen method for native StdLib contract
Close #3195.

Signed-off-by: Anna Shaleva <shaleva.ann@nspcc.ru>
2023-11-20 19:23:33 +03:00

478 lines
14 KiB
Go

package native
import (
"bytes"
"encoding/base64"
"encoding/hex"
"errors"
"math/big"
"strings"
"unicode/utf8"
"github.com/mr-tron/base58"
"github.com/nspcc-dev/neo-go/pkg/config"
"github.com/nspcc-dev/neo-go/pkg/core/dao"
"github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
base58neogo "github.com/nspcc-dev/neo-go/pkg/encoding/base58"
"github.com/nspcc-dev/neo-go/pkg/encoding/bigint"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/nspcc-dev/neo-go/pkg/util/slice"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
)
// Std represents an StdLib contract.
type Std struct {
interop.ContractMD
}
const (
stdContractID = -2
// stdMaxInputLength is the maximum input length for string-related methods.
stdMaxInputLength = 1024
)
var (
// ErrInvalidBase is returned when the base is invalid.
ErrInvalidBase = errors.New("invalid base")
// ErrInvalidFormat is returned when the string is not a number.
ErrInvalidFormat = errors.New("invalid format")
// ErrTooBigInput is returned when the input exceeds the size limit.
ErrTooBigInput = errors.New("input is too big")
)
func newStd() *Std {
s := &Std{ContractMD: *interop.NewContractMD(nativenames.StdLib, stdContractID)}
defer s.UpdateHash()
desc := newDescriptor("serialize", smartcontract.ByteArrayType,
manifest.NewParameter("item", smartcontract.AnyType))
md := newMethodAndPrice(s.serialize, 1<<12, callflag.NoneFlag)
s.AddMethod(md, desc)
desc = newDescriptor("deserialize", smartcontract.AnyType,
manifest.NewParameter("data", smartcontract.ByteArrayType))
md = newMethodAndPrice(s.deserialize, 1<<14, callflag.NoneFlag)
s.AddMethod(md, desc)
desc = newDescriptor("jsonSerialize", smartcontract.ByteArrayType,
manifest.NewParameter("item", smartcontract.AnyType))
md = newMethodAndPrice(s.jsonSerialize, 1<<12, callflag.NoneFlag)
s.AddMethod(md, desc)
desc = newDescriptor("jsonDeserialize", smartcontract.AnyType,
manifest.NewParameter("json", smartcontract.ByteArrayType))
md = newMethodAndPrice(s.jsonDeserialize, 1<<14, callflag.NoneFlag)
s.AddMethod(md, desc)
desc = newDescriptor("itoa", smartcontract.StringType,
manifest.NewParameter("value", smartcontract.IntegerType),
manifest.NewParameter("base", smartcontract.IntegerType))
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<<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)
desc = newDescriptor("base64Encode", smartcontract.StringType,
manifest.NewParameter("data", smartcontract.ByteArrayType))
md = newMethodAndPrice(s.base64Encode, 1<<5, callflag.NoneFlag)
s.AddMethod(md, desc)
desc = newDescriptor("base64Decode", smartcontract.ByteArrayType,
manifest.NewParameter("s", smartcontract.StringType))
md = newMethodAndPrice(s.base64Decode, 1<<5, callflag.NoneFlag)
s.AddMethod(md, desc)
desc = newDescriptor("base58Encode", smartcontract.StringType,
manifest.NewParameter("data", smartcontract.ByteArrayType))
md = newMethodAndPrice(s.base58Encode, 1<<13, callflag.NoneFlag)
s.AddMethod(md, desc)
desc = newDescriptor("base58Decode", smartcontract.ByteArrayType,
manifest.NewParameter("s", smartcontract.StringType))
md = newMethodAndPrice(s.base58Decode, 1<<10, callflag.NoneFlag)
s.AddMethod(md, desc)
desc = newDescriptor("base58CheckEncode", smartcontract.StringType,
manifest.NewParameter("data", smartcontract.ByteArrayType))
md = newMethodAndPrice(s.base58CheckEncode, 1<<16, callflag.NoneFlag)
s.AddMethod(md, desc)
desc = newDescriptor("base58CheckDecode", smartcontract.ByteArrayType,
manifest.NewParameter("s", smartcontract.StringType))
md = newMethodAndPrice(s.base58CheckDecode, 1<<16, 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)
desc = newDescriptor("strLen", smartcontract.IntegerType,
manifest.NewParameter("str", smartcontract.StringType))
md = newMethodAndPrice(s.strLen, 1<<8, callflag.NoneFlag)
s.AddMethod(md, desc)
return s
}
func (s *Std) serialize(ic *interop.Context, args []stackitem.Item) stackitem.Item {
data, err := ic.DAO.GetItemCtx().Serialize(args[0], false)
if err != nil {
panic(err)
}
if len(data) > stackitem.MaxSize {
panic(errors.New("too big item"))
}
return stackitem.NewByteArray(slice.Copy(data)) // Serialization context can be reused.
}
func (s *Std) deserialize(_ *interop.Context, args []stackitem.Item) stackitem.Item {
data, err := args[0].TryBytes()
if err != nil {
panic(err)
}
item, err := stackitem.Deserialize(data)
if err != nil {
panic(err)
}
return item
}
func (s *Std) jsonSerialize(_ *interop.Context, args []stackitem.Item) stackitem.Item {
data, err := stackitem.ToJSON(args[0])
if err != nil {
panic(err)
}
if len(data) > stackitem.MaxSize {
panic(errors.New("too big item"))
}
return stackitem.NewByteArray(data)
}
func (s *Std) jsonDeserialize(ic *interop.Context, args []stackitem.Item) stackitem.Item {
data, err := args[0].TryBytes()
if err != nil {
panic(err)
}
item, err := stackitem.FromJSON(data, stackitem.MaxDeserialized, ic.IsHardforkEnabled(config.HFBasilisk))
if err != nil {
panic(err)
}
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])
if !base.IsInt64() {
panic(ErrInvalidBase)
}
var str string
switch b := base.Int64(); b {
case 10:
str = num.Text(10)
case 16:
if num.Sign() == 0 {
str = "0"
break
}
bs := bigint.ToBytes(num)
slice.Reverse(bs)
str = hex.EncodeToString(bs)
if pad := bs[0] & 0xF8; pad == 0 || pad == 0xF8 {
str = str[1:]
}
default:
panic(ErrInvalidBase)
}
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 {
num := s.toLimitedString(args[0])
base := toBigInt(args[1])
if !base.IsInt64() {
panic(ErrInvalidBase)
}
var bi *big.Int
switch b := base.Int64(); b {
case 10:
bi = s.atoi10Aux(num)
case 16:
changed := len(num)%2 != 0
if changed {
num = "0" + num
}
bs, err := hex.DecodeString(num)
if err != nil {
panic(ErrInvalidFormat)
}
if changed && bs[0]&0x8 != 0 {
bs[0] |= 0xF0
}
slice.Reverse(bs)
bi = bigint.FromBytes(bs)
default:
panic(ErrInvalidBase)
}
return stackitem.NewBigInteger(bi)
}
func (s *Std) base64Encode(_ *interop.Context, args []stackitem.Item) stackitem.Item {
src := s.toLimitedBytes(args[0])
result := base64.StdEncoding.EncodeToString(src)
return stackitem.NewByteArray([]byte(result))
}
func (s *Std) base64Decode(_ *interop.Context, args []stackitem.Item) stackitem.Item {
src := s.toLimitedString(args[0])
result, err := base64.StdEncoding.DecodeString(src)
if err != nil {
panic(err)
}
return stackitem.NewByteArray(result)
}
func (s *Std) base58Encode(_ *interop.Context, args []stackitem.Item) stackitem.Item {
src := s.toLimitedBytes(args[0])
result := base58.Encode(src)
return stackitem.NewByteArray([]byte(result))
}
func (s *Std) base58Decode(_ *interop.Context, args []stackitem.Item) stackitem.Item {
src := s.toLimitedString(args[0])
result, err := base58.Decode(src)
if err != nil {
panic(err)
}
return stackitem.NewByteArray(result)
}
func (s *Std) base58CheckEncode(_ *interop.Context, args []stackitem.Item) stackitem.Item {
src := s.toLimitedBytes(args[0])
result := base58neogo.CheckEncode(src)
return stackitem.NewByteArray([]byte(result))
}
func (s *Std) base58CheckDecode(_ *interop.Context, args []stackitem.Item) stackitem.Item {
src := s.toLimitedString(args[0])
result, err := base58neogo.CheckDecode(src)
if err != nil {
panic(err)
}
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
}
func (s *Std) strLen(_ *interop.Context, args []stackitem.Item) stackitem.Item {
str := s.toLimitedString(args[0])
return stackitem.NewBigInteger(big.NewInt(int64(utf8.RuneCountInString(str))))
}
// Metadata implements the Contract interface.
func (s *Std) Metadata() *interop.ContractMD {
return &s.ContractMD
}
// Initialize implements the Contract interface.
func (s *Std) Initialize(ic *interop.Context) error {
return nil
}
// InitializeCache implements the Contract interface.
func (s *Std) InitializeCache(blockHeight uint32, d *dao.Simple) error {
return nil
}
// OnPersist implements the Contract interface.
func (s *Std) OnPersist(ic *interop.Context) error {
return nil
}
// PostPersist implements the Contract interface.
func (s *Std) PostPersist(ic *interop.Context) error {
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
}