github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/core/native/std.go (about)

     1  package native
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/base64"
     6  	"encoding/hex"
     7  	"errors"
     8  	"math/big"
     9  	"strings"
    10  	"unicode/utf8"
    11  
    12  	"github.com/mr-tron/base58"
    13  	"github.com/nspcc-dev/neo-go/pkg/config"
    14  	"github.com/nspcc-dev/neo-go/pkg/core/dao"
    15  	"github.com/nspcc-dev/neo-go/pkg/core/interop"
    16  	"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
    17  	base58neogo "github.com/nspcc-dev/neo-go/pkg/encoding/base58"
    18  	"github.com/nspcc-dev/neo-go/pkg/encoding/bigint"
    19  	"github.com/nspcc-dev/neo-go/pkg/smartcontract"
    20  	"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
    21  	"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
    22  	"github.com/nspcc-dev/neo-go/pkg/util/slice"
    23  	"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
    24  )
    25  
    26  // Std represents an StdLib contract.
    27  type Std struct {
    28  	interop.ContractMD
    29  }
    30  
    31  const (
    32  	stdContractID = -2
    33  
    34  	// stdMaxInputLength is the maximum input length for string-related methods.
    35  	stdMaxInputLength = 1024
    36  )
    37  
    38  var (
    39  	// ErrInvalidBase is returned when the base is invalid.
    40  	ErrInvalidBase = errors.New("invalid base")
    41  	// ErrInvalidFormat is returned when the string is not a number.
    42  	ErrInvalidFormat = errors.New("invalid format")
    43  	// ErrTooBigInput is returned when the input exceeds the size limit.
    44  	ErrTooBigInput = errors.New("input is too big")
    45  )
    46  
    47  func newStd() *Std {
    48  	s := &Std{ContractMD: *interop.NewContractMD(nativenames.StdLib, stdContractID)}
    49  	defer s.BuildHFSpecificMD(s.ActiveIn())
    50  
    51  	desc := newDescriptor("serialize", smartcontract.ByteArrayType,
    52  		manifest.NewParameter("item", smartcontract.AnyType))
    53  	md := newMethodAndPrice(s.serialize, 1<<12, callflag.NoneFlag)
    54  	s.AddMethod(md, desc)
    55  
    56  	desc = newDescriptor("deserialize", smartcontract.AnyType,
    57  		manifest.NewParameter("data", smartcontract.ByteArrayType))
    58  	md = newMethodAndPrice(s.deserialize, 1<<14, callflag.NoneFlag)
    59  	s.AddMethod(md, desc)
    60  
    61  	desc = newDescriptor("jsonSerialize", smartcontract.ByteArrayType,
    62  		manifest.NewParameter("item", smartcontract.AnyType))
    63  	md = newMethodAndPrice(s.jsonSerialize, 1<<12, callflag.NoneFlag)
    64  	s.AddMethod(md, desc)
    65  
    66  	desc = newDescriptor("jsonDeserialize", smartcontract.AnyType,
    67  		manifest.NewParameter("json", smartcontract.ByteArrayType))
    68  	md = newMethodAndPrice(s.jsonDeserialize, 1<<14, callflag.NoneFlag)
    69  	s.AddMethod(md, desc)
    70  
    71  	desc = newDescriptor("itoa", smartcontract.StringType,
    72  		manifest.NewParameter("value", smartcontract.IntegerType),
    73  		manifest.NewParameter("base", smartcontract.IntegerType))
    74  	md = newMethodAndPrice(s.itoa, 1<<12, callflag.NoneFlag)
    75  	s.AddMethod(md, desc)
    76  
    77  	desc = newDescriptor("itoa", smartcontract.StringType,
    78  		manifest.NewParameter("value", smartcontract.IntegerType))
    79  	md = newMethodAndPrice(s.itoa10, 1<<12, callflag.NoneFlag)
    80  	s.AddMethod(md, desc)
    81  
    82  	desc = newDescriptor("atoi", smartcontract.IntegerType,
    83  		manifest.NewParameter("value", smartcontract.StringType),
    84  		manifest.NewParameter("base", smartcontract.IntegerType))
    85  	md = newMethodAndPrice(s.atoi, 1<<6, callflag.NoneFlag)
    86  	s.AddMethod(md, desc)
    87  
    88  	desc = newDescriptor("atoi", smartcontract.IntegerType,
    89  		manifest.NewParameter("value", smartcontract.StringType))
    90  	md = newMethodAndPrice(s.atoi10, 1<<6, callflag.NoneFlag)
    91  	s.AddMethod(md, desc)
    92  
    93  	desc = newDescriptor("base64Encode", smartcontract.StringType,
    94  		manifest.NewParameter("data", smartcontract.ByteArrayType))
    95  	md = newMethodAndPrice(s.base64Encode, 1<<5, callflag.NoneFlag)
    96  	s.AddMethod(md, desc)
    97  
    98  	desc = newDescriptor("base64Decode", smartcontract.ByteArrayType,
    99  		manifest.NewParameter("s", smartcontract.StringType))
   100  	md = newMethodAndPrice(s.base64Decode, 1<<5, callflag.NoneFlag)
   101  	s.AddMethod(md, desc)
   102  
   103  	desc = newDescriptor("base58Encode", smartcontract.StringType,
   104  		manifest.NewParameter("data", smartcontract.ByteArrayType))
   105  	md = newMethodAndPrice(s.base58Encode, 1<<13, callflag.NoneFlag)
   106  	s.AddMethod(md, desc)
   107  
   108  	desc = newDescriptor("base58Decode", smartcontract.ByteArrayType,
   109  		manifest.NewParameter("s", smartcontract.StringType))
   110  	md = newMethodAndPrice(s.base58Decode, 1<<10, callflag.NoneFlag)
   111  	s.AddMethod(md, desc)
   112  
   113  	desc = newDescriptor("base58CheckEncode", smartcontract.StringType,
   114  		manifest.NewParameter("data", smartcontract.ByteArrayType))
   115  	md = newMethodAndPrice(s.base58CheckEncode, 1<<16, callflag.NoneFlag)
   116  	s.AddMethod(md, desc)
   117  
   118  	desc = newDescriptor("base58CheckDecode", smartcontract.ByteArrayType,
   119  		manifest.NewParameter("s", smartcontract.StringType))
   120  	md = newMethodAndPrice(s.base58CheckDecode, 1<<16, callflag.NoneFlag)
   121  	s.AddMethod(md, desc)
   122  
   123  	desc = newDescriptor("memoryCompare", smartcontract.IntegerType,
   124  		manifest.NewParameter("str1", smartcontract.ByteArrayType),
   125  		manifest.NewParameter("str2", smartcontract.ByteArrayType))
   126  	md = newMethodAndPrice(s.memoryCompare, 1<<5, callflag.NoneFlag)
   127  	s.AddMethod(md, desc)
   128  
   129  	desc = newDescriptor("memorySearch", smartcontract.IntegerType,
   130  		manifest.NewParameter("mem", smartcontract.ByteArrayType),
   131  		manifest.NewParameter("value", smartcontract.ByteArrayType))
   132  	md = newMethodAndPrice(s.memorySearch2, 1<<6, callflag.NoneFlag)
   133  	s.AddMethod(md, desc)
   134  
   135  	desc = newDescriptor("memorySearch", smartcontract.IntegerType,
   136  		manifest.NewParameter("mem", smartcontract.ByteArrayType),
   137  		manifest.NewParameter("value", smartcontract.ByteArrayType),
   138  		manifest.NewParameter("start", smartcontract.IntegerType))
   139  	md = newMethodAndPrice(s.memorySearch3, 1<<6, callflag.NoneFlag)
   140  	s.AddMethod(md, desc)
   141  
   142  	desc = newDescriptor("memorySearch", smartcontract.IntegerType,
   143  		manifest.NewParameter("mem", smartcontract.ByteArrayType),
   144  		manifest.NewParameter("value", smartcontract.ByteArrayType),
   145  		manifest.NewParameter("start", smartcontract.IntegerType),
   146  		manifest.NewParameter("backward", smartcontract.BoolType))
   147  	md = newMethodAndPrice(s.memorySearch4, 1<<6, callflag.NoneFlag)
   148  	s.AddMethod(md, desc)
   149  
   150  	desc = newDescriptor("stringSplit", smartcontract.ArrayType,
   151  		manifest.NewParameter("str", smartcontract.StringType),
   152  		manifest.NewParameter("separator", smartcontract.StringType))
   153  	md = newMethodAndPrice(s.stringSplit2, 1<<8, callflag.NoneFlag)
   154  	s.AddMethod(md, desc)
   155  
   156  	desc = newDescriptor("stringSplit", smartcontract.ArrayType,
   157  		manifest.NewParameter("str", smartcontract.StringType),
   158  		manifest.NewParameter("separator", smartcontract.StringType),
   159  		manifest.NewParameter("removeEmptyEntries", smartcontract.BoolType))
   160  	md = newMethodAndPrice(s.stringSplit3, 1<<8, callflag.NoneFlag)
   161  	s.AddMethod(md, desc)
   162  
   163  	desc = newDescriptor("strLen", smartcontract.IntegerType,
   164  		manifest.NewParameter("str", smartcontract.StringType))
   165  	md = newMethodAndPrice(s.strLen, 1<<8, callflag.NoneFlag)
   166  	s.AddMethod(md, desc)
   167  
   168  	return s
   169  }
   170  
   171  func (s *Std) serialize(ic *interop.Context, args []stackitem.Item) stackitem.Item {
   172  	data, err := ic.DAO.GetItemCtx().Serialize(args[0], false)
   173  	if err != nil {
   174  		panic(err)
   175  	}
   176  	if len(data) > stackitem.MaxSize {
   177  		panic(errors.New("too big item"))
   178  	}
   179  
   180  	return stackitem.NewByteArray(bytes.Clone(data)) // Serialization context can be reused.
   181  }
   182  
   183  func (s *Std) deserialize(_ *interop.Context, args []stackitem.Item) stackitem.Item {
   184  	data, err := args[0].TryBytes()
   185  	if err != nil {
   186  		panic(err)
   187  	}
   188  
   189  	item, err := stackitem.Deserialize(data)
   190  	if err != nil {
   191  		panic(err)
   192  	}
   193  
   194  	return item
   195  }
   196  
   197  func (s *Std) jsonSerialize(_ *interop.Context, args []stackitem.Item) stackitem.Item {
   198  	data, err := stackitem.ToJSON(args[0])
   199  	if err != nil {
   200  		panic(err)
   201  	}
   202  	if len(data) > stackitem.MaxSize {
   203  		panic(errors.New("too big item"))
   204  	}
   205  
   206  	return stackitem.NewByteArray(data)
   207  }
   208  
   209  func (s *Std) jsonDeserialize(ic *interop.Context, args []stackitem.Item) stackitem.Item {
   210  	data, err := args[0].TryBytes()
   211  	if err != nil {
   212  		panic(err)
   213  	}
   214  
   215  	item, err := stackitem.FromJSON(data, stackitem.MaxDeserialized, ic.IsHardforkEnabled(config.HFBasilisk))
   216  	if err != nil {
   217  		panic(err)
   218  	}
   219  
   220  	return item
   221  }
   222  
   223  func (s *Std) itoa10(_ *interop.Context, args []stackitem.Item) stackitem.Item {
   224  	num := toBigInt(args[0])
   225  	return stackitem.NewByteArray([]byte(num.Text(10)))
   226  }
   227  
   228  func (s *Std) itoa(_ *interop.Context, args []stackitem.Item) stackitem.Item {
   229  	num := toBigInt(args[0])
   230  	base := toBigInt(args[1])
   231  	if !base.IsInt64() {
   232  		panic(ErrInvalidBase)
   233  	}
   234  	var str string
   235  	switch b := base.Int64(); b {
   236  	case 10:
   237  		str = num.Text(10)
   238  	case 16:
   239  		if num.Sign() == 0 {
   240  			str = "0"
   241  			break
   242  		}
   243  		bs := bigint.ToBytes(num)
   244  		slice.Reverse(bs)
   245  		str = hex.EncodeToString(bs)
   246  		if pad := bs[0] & 0xF8; pad == 0 || pad == 0xF8 {
   247  			str = str[1:]
   248  		}
   249  	default:
   250  		panic(ErrInvalidBase)
   251  	}
   252  	return stackitem.NewByteArray([]byte(str))
   253  }
   254  
   255  func (s *Std) atoi10(_ *interop.Context, args []stackitem.Item) stackitem.Item {
   256  	num := s.toLimitedString(args[0])
   257  	res := s.atoi10Aux(num)
   258  	return stackitem.NewBigInteger(res)
   259  }
   260  
   261  func (s *Std) atoi10Aux(num string) *big.Int {
   262  	bi, ok := new(big.Int).SetString(num, 10)
   263  	if !ok {
   264  		panic(ErrInvalidFormat)
   265  	}
   266  	return bi
   267  }
   268  
   269  func (s *Std) atoi(_ *interop.Context, args []stackitem.Item) stackitem.Item {
   270  	num := s.toLimitedString(args[0])
   271  	base := toBigInt(args[1])
   272  	if !base.IsInt64() {
   273  		panic(ErrInvalidBase)
   274  	}
   275  	var bi *big.Int
   276  	switch b := base.Int64(); b {
   277  	case 10:
   278  		bi = s.atoi10Aux(num)
   279  	case 16:
   280  		changed := len(num)%2 != 0
   281  		if changed {
   282  			num = "0" + num
   283  		}
   284  		bs, err := hex.DecodeString(num)
   285  		if err != nil {
   286  			panic(ErrInvalidFormat)
   287  		}
   288  		if changed && bs[0]&0x8 != 0 {
   289  			bs[0] |= 0xF0
   290  		}
   291  		slice.Reverse(bs)
   292  		bi = bigint.FromBytes(bs)
   293  	default:
   294  		panic(ErrInvalidBase)
   295  	}
   296  
   297  	return stackitem.NewBigInteger(bi)
   298  }
   299  
   300  func (s *Std) base64Encode(_ *interop.Context, args []stackitem.Item) stackitem.Item {
   301  	src := s.toLimitedBytes(args[0])
   302  	result := base64.StdEncoding.EncodeToString(src)
   303  
   304  	return stackitem.NewByteArray([]byte(result))
   305  }
   306  
   307  func (s *Std) base64Decode(_ *interop.Context, args []stackitem.Item) stackitem.Item {
   308  	src := s.toLimitedString(args[0])
   309  	result, err := base64.StdEncoding.DecodeString(src)
   310  	if err != nil {
   311  		panic(err)
   312  	}
   313  
   314  	return stackitem.NewByteArray(result)
   315  }
   316  
   317  func (s *Std) base58Encode(_ *interop.Context, args []stackitem.Item) stackitem.Item {
   318  	src := s.toLimitedBytes(args[0])
   319  	result := base58.Encode(src)
   320  
   321  	return stackitem.NewByteArray([]byte(result))
   322  }
   323  
   324  func (s *Std) base58Decode(_ *interop.Context, args []stackitem.Item) stackitem.Item {
   325  	src := s.toLimitedString(args[0])
   326  	result, err := base58.Decode(src)
   327  	if err != nil {
   328  		panic(err)
   329  	}
   330  
   331  	return stackitem.NewByteArray(result)
   332  }
   333  
   334  func (s *Std) base58CheckEncode(_ *interop.Context, args []stackitem.Item) stackitem.Item {
   335  	src := s.toLimitedBytes(args[0])
   336  	result := base58neogo.CheckEncode(src)
   337  
   338  	return stackitem.NewByteArray([]byte(result))
   339  }
   340  
   341  func (s *Std) base58CheckDecode(_ *interop.Context, args []stackitem.Item) stackitem.Item {
   342  	src := s.toLimitedString(args[0])
   343  	result, err := base58neogo.CheckDecode(src)
   344  	if err != nil {
   345  		panic(err)
   346  	}
   347  
   348  	return stackitem.NewByteArray(result)
   349  }
   350  
   351  func (s *Std) memoryCompare(_ *interop.Context, args []stackitem.Item) stackitem.Item {
   352  	s1 := s.toLimitedBytes(args[0])
   353  	s2 := s.toLimitedBytes(args[1])
   354  	return stackitem.NewBigInteger(big.NewInt(int64(bytes.Compare(s1, s2))))
   355  }
   356  
   357  func (s *Std) memorySearch2(_ *interop.Context, args []stackitem.Item) stackitem.Item {
   358  	mem := s.toLimitedBytes(args[0])
   359  	val := s.toLimitedBytes(args[1])
   360  	index := s.memorySearchAux(mem, val, 0, false)
   361  	return stackitem.NewBigInteger(big.NewInt(int64(index)))
   362  }
   363  
   364  func (s *Std) memorySearch3(_ *interop.Context, args []stackitem.Item) stackitem.Item {
   365  	mem := s.toLimitedBytes(args[0])
   366  	val := s.toLimitedBytes(args[1])
   367  	start := toUint32(args[2])
   368  	index := s.memorySearchAux(mem, val, int(start), false)
   369  	return stackitem.NewBigInteger(big.NewInt(int64(index)))
   370  }
   371  
   372  func (s *Std) memorySearch4(_ *interop.Context, args []stackitem.Item) stackitem.Item {
   373  	mem := s.toLimitedBytes(args[0])
   374  	val := s.toLimitedBytes(args[1])
   375  	start := toUint32(args[2])
   376  	backward, err := args[3].TryBool()
   377  	if err != nil {
   378  		panic(err)
   379  	}
   380  
   381  	index := s.memorySearchAux(mem, val, int(start), backward)
   382  	return stackitem.NewBigInteger(big.NewInt(int64(index)))
   383  }
   384  
   385  func (s *Std) memorySearchAux(mem, val []byte, start int, backward bool) int {
   386  	if backward {
   387  		if start > len(mem) { // panic in case if cap(mem) > len(mem) for some reasons
   388  			panic("invalid start index")
   389  		}
   390  		return bytes.LastIndex(mem[:start], val)
   391  	}
   392  
   393  	index := bytes.Index(mem[start:], val)
   394  	if index < 0 {
   395  		return -1
   396  	}
   397  	return index + start
   398  }
   399  
   400  func (s *Std) stringSplit2(_ *interop.Context, args []stackitem.Item) stackitem.Item {
   401  	str := s.toLimitedString(args[0])
   402  	sep := toString(args[1])
   403  	return stackitem.NewArray(s.stringSplitAux(str, sep, false))
   404  }
   405  
   406  func (s *Std) stringSplit3(_ *interop.Context, args []stackitem.Item) stackitem.Item {
   407  	str := s.toLimitedString(args[0])
   408  	sep := toString(args[1])
   409  	removeEmpty, err := args[2].TryBool()
   410  	if err != nil {
   411  		panic(err)
   412  	}
   413  
   414  	return stackitem.NewArray(s.stringSplitAux(str, sep, removeEmpty))
   415  }
   416  
   417  func (s *Std) stringSplitAux(str, sep string, removeEmpty bool) []stackitem.Item {
   418  	var result []stackitem.Item
   419  
   420  	arr := strings.Split(str, sep)
   421  	for i := range arr {
   422  		if !removeEmpty || len(arr[i]) != 0 {
   423  			result = append(result, stackitem.Make(arr[i]))
   424  		}
   425  	}
   426  
   427  	return result
   428  }
   429  
   430  func (s *Std) strLen(_ *interop.Context, args []stackitem.Item) stackitem.Item {
   431  	str := s.toLimitedString(args[0])
   432  
   433  	return stackitem.NewBigInteger(big.NewInt(int64(utf8.RuneCountInString(str))))
   434  }
   435  
   436  // Metadata implements the Contract interface.
   437  func (s *Std) Metadata() *interop.ContractMD {
   438  	return &s.ContractMD
   439  }
   440  
   441  // Initialize implements the Contract interface.
   442  func (s *Std) Initialize(ic *interop.Context, hf *config.Hardfork, newMD *interop.HFSpecificContractMD) error {
   443  	return nil
   444  }
   445  
   446  // InitializeCache implements the Contract interface.
   447  func (s *Std) InitializeCache(blockHeight uint32, d *dao.Simple) error {
   448  	return nil
   449  }
   450  
   451  // OnPersist implements the Contract interface.
   452  func (s *Std) OnPersist(ic *interop.Context) error {
   453  	return nil
   454  }
   455  
   456  // PostPersist implements the Contract interface.
   457  func (s *Std) PostPersist(ic *interop.Context) error {
   458  	return nil
   459  }
   460  
   461  // ActiveIn implements the Contract interface.
   462  func (s *Std) ActiveIn() *config.Hardfork {
   463  	return nil
   464  }
   465  
   466  func (s *Std) toLimitedBytes(item stackitem.Item) []byte {
   467  	src, err := item.TryBytes()
   468  	if err != nil {
   469  		panic(err)
   470  	}
   471  	if len(src) > stdMaxInputLength {
   472  		panic(ErrTooBigInput)
   473  	}
   474  	return src
   475  }
   476  
   477  func (s *Std) toLimitedString(item stackitem.Item) string {
   478  	src := toString(item)
   479  	if len(src) > stdMaxInputLength {
   480  		panic(ErrTooBigInput)
   481  	}
   482  	return src
   483  }