neo-go/pkg/core/native/std.go
Anna Shaleva 33c971b0e4 core: add InitializeCache method to Contract interface
Make the contracts cache initialization unified. The order of cache
iniitialization is not important and Nottary contract is added to the
bc.contracts.Contracts wrt P2PSigExtensions setting, thus no functional
changes, just refactoring for future applications.

Signed-off-by: Anna Shaleva <shaleva.ann@nspcc.ru>
2023-04-26 12:57:48 +03:00

465 lines
14 KiB
Go

package native
import (
"bytes"
"encoding/base64"
"encoding/hex"
"errors"
"math/big"
"strings"
"github.com/mr-tron/base58"
"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)
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(_ *interop.Context, args []stackitem.Item) stackitem.Item {
data, err := args[0].TryBytes()
if err != nil {
panic(err)
}
item, err := stackitem.FromJSON(data, stackitem.MaxDeserialized)
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
}
// 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
}