mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-11-26 19:42:23 +00:00
native: implement StdLib contract
This commit is contained in:
parent
078870fceb
commit
2b90d4455f
5 changed files with 391 additions and 0 deletions
|
@ -19,6 +19,7 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/interop/native/oracle"
|
||||
"github.com/nspcc-dev/neo-go/pkg/interop/native/policy"
|
||||
"github.com/nspcc-dev/neo-go/pkg/interop/native/roles"
|
||||
"github.com/nspcc-dev/neo-go/pkg/interop/native/std"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
|
@ -39,6 +40,7 @@ func TestContractHashes(t *testing.T) {
|
|||
require.Equal(t, []byte(management.Hash), cs.Management.Hash.BytesBE())
|
||||
require.Equal(t, []byte(notary.Hash), cs.Notary.Hash.BytesBE())
|
||||
require.Equal(t, []byte(crypto.Hash), cs.Crypto.Hash.BytesBE())
|
||||
require.Equal(t, []byte(std.Hash), cs.Std.Hash.BytesBE())
|
||||
}
|
||||
|
||||
// testPrintHash is a helper for updating contract hashes.
|
||||
|
@ -189,6 +191,18 @@ func TestNativeHelpersCompile(t *testing.T) {
|
|||
{"ripemd160", []string{"[]byte{1, 2, 3}"}},
|
||||
{"verifyWithECDsa", []string{"[]byte{1, 2, 3}", pub, sig, "crypto.Secp256k1"}},
|
||||
})
|
||||
runNativeTestCases(t, cs.Std.ContractMD, "std", []nativeTestCase{
|
||||
{"serialize", []string{"[]byte{1, 2, 3}"}},
|
||||
{"deserialize", []string{"[]byte{1, 2, 3}"}},
|
||||
{"jsonSerialize", []string{"[]byte{1, 2, 3}"}},
|
||||
{"jsonDeserialize", []string{"[]byte{1, 2, 3}"}},
|
||||
{"base64Encode", []string{"[]byte{1, 2, 3}"}},
|
||||
{"base64Decode", []string{"[]byte{1, 2, 3}"}},
|
||||
{"base58Encode", []string{"[]byte{1, 2, 3}"}},
|
||||
{"base58Decode", []string{"[]byte{1, 2, 3}"}},
|
||||
{"itoa", []string{"4", "10"}},
|
||||
{"atoi", []string{`"4"`, "10"}},
|
||||
})
|
||||
}
|
||||
|
||||
func runNativeTestCases(t *testing.T, ctr interop.ContractMD, name string, testCases []nativeTestCase) {
|
||||
|
@ -218,6 +232,7 @@ func runNativeTestCase(t *testing.T, ctr interop.ContractMD, name, method string
|
|||
}
|
||||
methodUpper := strings.ToUpper(method[:1]) + method[1:] // ASCII only
|
||||
methodUpper = strings.ReplaceAll(methodUpper, "Gas", "GAS")
|
||||
methodUpper = strings.ReplaceAll(methodUpper, "Json", "JSON")
|
||||
src := fmt.Sprintf(srcTmpl, name, name, methodUpper, strings.Join(params, ","))
|
||||
|
||||
v, s := vmAndCompileInterop(t, src)
|
||||
|
|
|
@ -25,6 +25,7 @@ type Contracts struct {
|
|||
NameService *NameService
|
||||
Notary *Notary
|
||||
Crypto *Crypto
|
||||
Std *Std
|
||||
Contracts []interop.Contract
|
||||
// persistScript is vm script which executes "onPersist" method of every native contract.
|
||||
persistScript []byte
|
||||
|
@ -62,6 +63,10 @@ func NewContracts(p2pSigExtensionsEnabled bool) *Contracts {
|
|||
cs.Management = mgmt
|
||||
cs.Contracts = append(cs.Contracts, mgmt)
|
||||
|
||||
s := newStd()
|
||||
cs.Std = s
|
||||
cs.Contracts = append(cs.Contracts, s)
|
||||
|
||||
c := newCrypto()
|
||||
cs.Crypto = c
|
||||
cs.Contracts = append(cs.Contracts, c)
|
||||
|
|
|
@ -12,4 +12,5 @@ const (
|
|||
Notary = "Notary"
|
||||
NameService = "NameService"
|
||||
CryptoLib = "CryptoLib"
|
||||
StdLib = "StdLib"
|
||||
)
|
||||
|
|
273
pkg/core/native/std.go
Normal file
273
pkg/core/native/std.go
Normal file
|
@ -0,0 +1,273 @@
|
|||
package native
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"math/big"
|
||||
"strings"
|
||||
|
||||
"github.com/mr-tron/base58"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
||||
"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/vm/stackitem"
|
||||
)
|
||||
|
||||
// Std represents StdLib contract.
|
||||
type Std struct {
|
||||
interop.ContractMD
|
||||
}
|
||||
|
||||
const stdContractID = -2
|
||||
|
||||
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")
|
||||
)
|
||||
|
||||
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("atoi", smartcontract.IntegerType,
|
||||
manifest.NewParameter("value", smartcontract.StringType),
|
||||
manifest.NewParameter("base", smartcontract.IntegerType))
|
||||
md = newMethodAndPrice(s.atoi, 1<<12, callflag.NoneFlag)
|
||||
s.AddMethod(md, desc)
|
||||
|
||||
desc = newDescriptor("base64Encode", smartcontract.StringType,
|
||||
manifest.NewParameter("data", smartcontract.ByteArrayType))
|
||||
md = newMethodAndPrice(s.base64Encode, 1<<12, callflag.NoneFlag)
|
||||
s.AddMethod(md, desc)
|
||||
|
||||
desc = newDescriptor("base64Decode", smartcontract.ByteArrayType,
|
||||
manifest.NewParameter("s", smartcontract.StringType))
|
||||
md = newMethodAndPrice(s.base64Decode, 1<<12, callflag.NoneFlag)
|
||||
s.AddMethod(md, desc)
|
||||
|
||||
desc = newDescriptor("base58Encode", smartcontract.StringType,
|
||||
manifest.NewParameter("data", smartcontract.ByteArrayType))
|
||||
md = newMethodAndPrice(s.base58Encode, 1<<12, callflag.NoneFlag)
|
||||
s.AddMethod(md, desc)
|
||||
|
||||
desc = newDescriptor("base58Decode", smartcontract.ByteArrayType,
|
||||
manifest.NewParameter("s", smartcontract.StringType))
|
||||
md = newMethodAndPrice(s.base58Decode, 1<<12, callflag.NoneFlag)
|
||||
s.AddMethod(md, desc)
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *Std) serialize(_ *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||
data, err := stackitem.SerializeItem(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) deserialize(_ *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||
data, err := args[0].TryBytes()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
item, err := stackitem.DeserializeItem(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)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return item
|
||||
}
|
||||
|
||||
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)
|
||||
reverse(bs)
|
||||
str = hex.EncodeToString(bs)
|
||||
if pad := bs[0] & 0xF8; pad == 0 || pad == 0xF8 {
|
||||
str = str[1:]
|
||||
}
|
||||
str = strings.ToUpper(str)
|
||||
default:
|
||||
panic(ErrInvalidBase)
|
||||
}
|
||||
return stackitem.NewByteArray([]byte(str))
|
||||
}
|
||||
|
||||
func (s *Std) atoi(_ *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||
num := toString(args[0])
|
||||
base := toBigInt(args[1])
|
||||
if !base.IsInt64() {
|
||||
panic(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 {
|
||||
panic(ErrInvalidFormat)
|
||||
}
|
||||
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
|
||||
}
|
||||
reverse(bs)
|
||||
bi = bigint.FromBytes(bs)
|
||||
default:
|
||||
panic(ErrInvalidBase)
|
||||
}
|
||||
|
||||
return stackitem.NewBigInteger(bi)
|
||||
}
|
||||
|
||||
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]
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Std) base64Encode(_ *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||
src, err := args[0].TryBytes()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
result := base64.StdEncoding.EncodeToString(src)
|
||||
|
||||
return stackitem.NewByteArray([]byte(result))
|
||||
}
|
||||
|
||||
func (s *Std) base64Decode(_ *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||
src := toString(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, err := args[0].TryBytes()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
result := base58.Encode(src)
|
||||
|
||||
return stackitem.NewByteArray([]byte(result))
|
||||
}
|
||||
|
||||
func (s *Std) base58Decode(_ *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||
src := toString(args[0])
|
||||
result, err := base58.Decode(src)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return stackitem.NewByteArray(result)
|
||||
}
|
||||
|
||||
// Metadata implements Contract interface.
|
||||
func (s *Std) Metadata() *interop.ContractMD {
|
||||
return &s.ContractMD
|
||||
}
|
||||
|
||||
// Initialize implements Contract interface.
|
||||
func (s *Std) Initialize(ic *interop.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnPersist implements Contract interface.
|
||||
func (s *Std) OnPersist(ic *interop.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// PostPersist implements Contract interface.
|
||||
func (s *Std) PostPersist(ic *interop.Context) error {
|
||||
return nil
|
||||
}
|
97
pkg/interop/native/std/std.go
Normal file
97
pkg/interop/native/std/std.go
Normal file
|
@ -0,0 +1,97 @@
|
|||
package std
|
||||
|
||||
import (
|
||||
"github.com/nspcc-dev/neo-go/pkg/interop"
|
||||
"github.com/nspcc-dev/neo-go/pkg/interop/contract"
|
||||
)
|
||||
|
||||
// Hash represents StdLib contract hash.
|
||||
const Hash = "\xc0\xef\x39\xce\xe0\xe4\xe9\x25\xc6\xc2\xa0\x6a\x79\xe1\x44\x0d\xd8\x6f\xce\xac"
|
||||
|
||||
// Serialize calls `serialize` method of StdLib native contract and serializes
|
||||
// any given item into a byte slice. It works for all regular VM types (not ones
|
||||
// from interop package) and allows to save them in storage or pass into Notify
|
||||
// and then Deserialize them on the next run or in the external event receiver.
|
||||
func Serialize(item interface{}) []byte {
|
||||
return contract.Call(interop.Hash160(Hash), "serialize", contract.NoneFlag,
|
||||
item).([]byte)
|
||||
}
|
||||
|
||||
// Deserialize calls `deserialize` method of StdLib native contract and unpacks
|
||||
// previously serialized value from a byte slice, it's the opposite of Serialize.
|
||||
func Deserialize(b []byte) interface{} {
|
||||
return contract.Call(interop.Hash160(Hash), "deserialize", contract.NoneFlag,
|
||||
b)
|
||||
}
|
||||
|
||||
// JSONSerialize serializes value to json. It uses `jsonSerialize` method of StdLib native
|
||||
// contract.
|
||||
// Serialization format is the following:
|
||||
// []byte -> base64 string
|
||||
// bool -> json boolean
|
||||
// nil -> Null
|
||||
// string -> base64 encoded sequence of underlying bytes
|
||||
// (u)int* -> integer, only value in -2^53..2^53 are allowed
|
||||
// []interface{} -> json array
|
||||
// map[type1]type2 -> json object with string keys marshaled as strings (not base64).
|
||||
func JSONSerialize(item interface{}) []byte {
|
||||
return contract.Call(interop.Hash160(Hash), "jsonSerialize", contract.NoneFlag,
|
||||
item).([]byte)
|
||||
}
|
||||
|
||||
// JSONDeserialize deserializes value from json. It uses `jsonDeserialize` method of StdLib
|
||||
// native contract.
|
||||
// It performs deserialization as follows:
|
||||
// strings -> []byte (string) from base64
|
||||
// integers -> (u)int* types
|
||||
// null -> interface{}(nil)
|
||||
// arrays -> []interface{}
|
||||
// maps -> map[string]interface{}
|
||||
func JSONDeserialize(data []byte) interface{} {
|
||||
return contract.Call(interop.Hash160(Hash), "jsonDeserialize", contract.NoneFlag,
|
||||
data)
|
||||
}
|
||||
|
||||
// Base64Encode calls `base64Encode` method of StdLib native contract and encodes
|
||||
// given byte slice into a base64 string and returns byte representation of this
|
||||
// string.
|
||||
func Base64Encode(b []byte) string {
|
||||
return contract.Call(interop.Hash160(Hash), "base64Encode", contract.NoneFlag,
|
||||
b).(string)
|
||||
}
|
||||
|
||||
// Base64Decode calls `base64Decode` method of StdLib native contract and decodes
|
||||
// given base64 string represented as a byte slice into byte slice.
|
||||
func Base64Decode(b []byte) []byte {
|
||||
return contract.Call(interop.Hash160(Hash), "base64Decode", contract.NoneFlag,
|
||||
b).([]byte)
|
||||
}
|
||||
|
||||
// Base58Encode calls `base58Encode` method of StdLib native contract and encodes
|
||||
// given byte slice into a base58 string and returns byte representation of this
|
||||
// string.
|
||||
func Base58Encode(b []byte) string {
|
||||
return contract.Call(interop.Hash160(Hash), "base58Encode", contract.NoneFlag,
|
||||
b).(string)
|
||||
}
|
||||
|
||||
// Base58Decode calls `base58Decode` method of StdLib native contract and decodes
|
||||
// given base58 string represented as a byte slice into a new byte slice.
|
||||
func Base58Decode(b []byte) []byte {
|
||||
return contract.Call(interop.Hash160(Hash), "base58Decode", contract.NoneFlag,
|
||||
b).([]byte)
|
||||
}
|
||||
|
||||
// Itoa converts num in a given base to string. Base should be either 10 or 16.
|
||||
// It uses `itoa` method of StdLib native contract.
|
||||
func Itoa(num int, base int) string {
|
||||
return contract.Call(interop.Hash160(Hash), "itoa", contract.NoneFlag,
|
||||
num, base).(string)
|
||||
}
|
||||
|
||||
// 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.
|
||||
func Atoi(s string, base int) int {
|
||||
return contract.Call(interop.Hash160(Hash), "atoi", contract.NoneFlag,
|
||||
s, base).(int)
|
||||
}
|
Loading…
Reference in a new issue