132531fabe
We have smartcontract.ParameterPair structure that can be properly marshalled and passed to RPC server as an element of smartcontract.Map structure. However, RPC server can't unmarshal map values properly without this change. This change is compatible with C# node. Signed-off-by: Anna Shaleva <shaleva.ann@nspcc.ru>
470 lines
10 KiB
Go
470 lines
10 KiB
Go
package params
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/base64"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"math/big"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
|
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
|
"github.com/nspcc-dev/neo-go/pkg/neorpc"
|
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
|
)
|
|
|
|
type (
|
|
// Param represents a param either passed to
|
|
// the server or to be sent to a server using
|
|
// the client.
|
|
Param struct {
|
|
json.RawMessage
|
|
cache any
|
|
}
|
|
|
|
// FuncParam represents a function argument parameter used in the
|
|
// invokefunction RPC method.
|
|
FuncParam struct {
|
|
Type smartcontract.ParamType `json:"type"`
|
|
Value Param `json:"value"`
|
|
}
|
|
|
|
// FuncParamKV represents a pair of function argument parameters
|
|
// a slice of which is stored in FuncParam of [smartcontract.MapType] type.
|
|
FuncParamKV struct {
|
|
Key FuncParam `json:"key"`
|
|
Value FuncParam `json:"value"`
|
|
}
|
|
)
|
|
|
|
var (
|
|
jsonNullBytes = []byte("null")
|
|
jsonFalseBytes = []byte("false")
|
|
jsonTrueBytes = []byte("true")
|
|
errMissingParameter = errors.New("parameter is missing")
|
|
errNotAString = errors.New("not a string")
|
|
errNotAnInt = errors.New("not an integer")
|
|
errNotABool = errors.New("not a boolean")
|
|
errNotAnArray = errors.New("not an array")
|
|
)
|
|
|
|
func (p Param) String() string {
|
|
str, _ := p.GetString()
|
|
return str
|
|
}
|
|
|
|
// GetStringStrict returns a string value of the parameter.
|
|
func (p *Param) GetStringStrict() (string, error) {
|
|
if p == nil {
|
|
return "", errMissingParameter
|
|
}
|
|
if p.IsNull() {
|
|
return "", errNotAString
|
|
}
|
|
if p.cache == nil {
|
|
var s string
|
|
err := json.Unmarshal(p.RawMessage, &s)
|
|
if err != nil {
|
|
return "", errNotAString
|
|
}
|
|
p.cache = s
|
|
}
|
|
if s, ok := p.cache.(string); ok {
|
|
return s, nil
|
|
}
|
|
return "", errNotAString
|
|
}
|
|
|
|
// GetString returns a string value of the parameter or tries to cast the parameter to a string value.
|
|
func (p *Param) GetString() (string, error) {
|
|
if p == nil {
|
|
return "", errMissingParameter
|
|
}
|
|
if p.IsNull() {
|
|
return "", errNotAString
|
|
}
|
|
if p.cache == nil {
|
|
var s string
|
|
err := json.Unmarshal(p.RawMessage, &s)
|
|
if err == nil {
|
|
p.cache = s
|
|
} else {
|
|
var i int64
|
|
err = json.Unmarshal(p.RawMessage, &i)
|
|
if err == nil {
|
|
p.cache = i
|
|
} else {
|
|
var b bool
|
|
err = json.Unmarshal(p.RawMessage, &b)
|
|
if err == nil {
|
|
p.cache = b
|
|
} else {
|
|
return "", errNotAString
|
|
}
|
|
}
|
|
}
|
|
}
|
|
switch t := p.cache.(type) {
|
|
case string:
|
|
return t, nil
|
|
case int64:
|
|
return strconv.FormatInt(t, 10), nil
|
|
case bool:
|
|
if t {
|
|
return "true", nil
|
|
}
|
|
return "false", nil
|
|
default:
|
|
return "", errNotAString
|
|
}
|
|
}
|
|
|
|
// GetBooleanStrict returns boolean value of the parameter.
|
|
func (p *Param) GetBooleanStrict() (bool, error) {
|
|
if p == nil {
|
|
return false, errMissingParameter
|
|
}
|
|
if bytes.Equal(p.RawMessage, jsonTrueBytes) {
|
|
p.cache = true
|
|
return true, nil
|
|
}
|
|
if bytes.Equal(p.RawMessage, jsonFalseBytes) {
|
|
p.cache = false
|
|
return false, nil
|
|
}
|
|
return false, errNotABool
|
|
}
|
|
|
|
// GetBoolean returns a boolean value of the parameter or tries to cast the parameter to a bool value.
|
|
func (p *Param) GetBoolean() (bool, error) {
|
|
if p == nil {
|
|
return false, errMissingParameter
|
|
}
|
|
if p.IsNull() {
|
|
return false, errNotABool
|
|
}
|
|
var b bool
|
|
if p.cache == nil {
|
|
err := json.Unmarshal(p.RawMessage, &b)
|
|
if err == nil {
|
|
p.cache = b
|
|
} else {
|
|
var s string
|
|
err = json.Unmarshal(p.RawMessage, &s)
|
|
if err == nil {
|
|
p.cache = s
|
|
} else {
|
|
var i int64
|
|
err = json.Unmarshal(p.RawMessage, &i)
|
|
if err == nil {
|
|
p.cache = i
|
|
} else {
|
|
return false, errNotABool
|
|
}
|
|
}
|
|
}
|
|
}
|
|
switch t := p.cache.(type) {
|
|
case bool:
|
|
return t, nil
|
|
case string:
|
|
return t != "", nil
|
|
case int64:
|
|
return t != 0, nil
|
|
default:
|
|
return false, errNotABool
|
|
}
|
|
}
|
|
|
|
// GetIntStrict returns an int value of the parameter if the parameter is an integer.
|
|
func (p *Param) GetIntStrict() (int, error) {
|
|
if p == nil {
|
|
return 0, errMissingParameter
|
|
}
|
|
if p.IsNull() {
|
|
return 0, errNotAnInt
|
|
}
|
|
value, err := p.fillIntCache()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if i, ok := value.(int64); ok && i == int64(int(i)) {
|
|
return int(i), nil
|
|
}
|
|
return 0, errNotAnInt
|
|
}
|
|
|
|
func (p *Param) fillIntCache() (any, error) {
|
|
if p.cache != nil {
|
|
return p.cache, nil
|
|
}
|
|
|
|
// We could also try unmarshalling to uint64, but JSON reliably supports numbers
|
|
// up to 53 bits in size.
|
|
var i int64
|
|
err := json.Unmarshal(p.RawMessage, &i)
|
|
if err == nil {
|
|
p.cache = i
|
|
return i, nil
|
|
}
|
|
|
|
var s string
|
|
err = json.Unmarshal(p.RawMessage, &s)
|
|
if err == nil {
|
|
p.cache = s
|
|
return s, nil
|
|
}
|
|
|
|
var b bool
|
|
err = json.Unmarshal(p.RawMessage, &b)
|
|
if err == nil {
|
|
p.cache = b
|
|
return b, nil
|
|
}
|
|
return nil, errNotAnInt
|
|
}
|
|
|
|
// GetInt returns an int value of the parameter or tries to cast the parameter to an int value.
|
|
func (p *Param) GetInt() (int, error) {
|
|
if p == nil {
|
|
return 0, errMissingParameter
|
|
}
|
|
if p.IsNull() {
|
|
return 0, errNotAnInt
|
|
}
|
|
value, err := p.fillIntCache()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
switch t := value.(type) {
|
|
case int64:
|
|
if t == int64(int(t)) {
|
|
return int(t), nil
|
|
}
|
|
return 0, errNotAnInt
|
|
case string:
|
|
return strconv.Atoi(t)
|
|
case bool:
|
|
if t {
|
|
return 1, nil
|
|
}
|
|
return 0, nil
|
|
default:
|
|
panic("unreachable")
|
|
}
|
|
}
|
|
|
|
// GetBigInt returns a big-integer value of the parameter.
|
|
func (p *Param) GetBigInt() (*big.Int, error) {
|
|
if p == nil {
|
|
return nil, errMissingParameter
|
|
}
|
|
if p.IsNull() {
|
|
return nil, errNotAnInt
|
|
}
|
|
value, err := p.fillIntCache()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
switch t := value.(type) {
|
|
case int64:
|
|
return big.NewInt(t), nil
|
|
case string:
|
|
bi, ok := new(big.Int).SetString(t, 10)
|
|
if !ok {
|
|
return nil, errNotAnInt
|
|
}
|
|
return bi, nil
|
|
case bool:
|
|
if t {
|
|
return big.NewInt(1), nil
|
|
}
|
|
return new(big.Int), nil
|
|
default:
|
|
panic("unreachable")
|
|
}
|
|
}
|
|
|
|
// GetArray returns a slice of Params stored in the parameter.
|
|
func (p *Param) GetArray() ([]Param, error) {
|
|
if p == nil {
|
|
return nil, errMissingParameter
|
|
}
|
|
if p.IsNull() {
|
|
return nil, errNotAnArray
|
|
}
|
|
if p.cache == nil {
|
|
a := []Param{}
|
|
err := json.Unmarshal(p.RawMessage, &a)
|
|
if err != nil {
|
|
return nil, errNotAnArray
|
|
}
|
|
p.cache = a
|
|
}
|
|
if a, ok := p.cache.([]Param); ok {
|
|
return a, nil
|
|
}
|
|
return nil, errNotAnArray
|
|
}
|
|
|
|
// GetUint256 returns a Uint256 value of the parameter.
|
|
func (p *Param) GetUint256() (util.Uint256, error) {
|
|
s, err := p.GetString()
|
|
if err != nil {
|
|
return util.Uint256{}, err
|
|
}
|
|
|
|
return util.Uint256DecodeStringLE(strings.TrimPrefix(s, "0x"))
|
|
}
|
|
|
|
// GetUint160FromHex returns a Uint160 value of the parameter encoded in hex.
|
|
func (p *Param) GetUint160FromHex() (util.Uint160, error) {
|
|
s, err := p.GetString()
|
|
if err != nil {
|
|
return util.Uint160{}, err
|
|
}
|
|
|
|
return util.Uint160DecodeStringLE(strings.TrimPrefix(s, "0x"))
|
|
}
|
|
|
|
// GetUint160FromAddress returns a Uint160 value of the parameter that was
|
|
// supplied as an address.
|
|
func (p *Param) GetUint160FromAddress() (util.Uint160, error) {
|
|
s, err := p.GetString()
|
|
if err != nil {
|
|
return util.Uint160{}, err
|
|
}
|
|
|
|
return address.StringToUint160(s)
|
|
}
|
|
|
|
// GetUint160FromAddressOrHex returns a Uint160 value of the parameter that was
|
|
// supplied either as raw hex or as an address.
|
|
func (p *Param) GetUint160FromAddressOrHex() (util.Uint160, error) {
|
|
u, err := p.GetUint160FromHex()
|
|
if err == nil {
|
|
return u, err
|
|
}
|
|
return p.GetUint160FromAddress()
|
|
}
|
|
|
|
// GetFuncParam returns the current parameter as a function call parameter.
|
|
func (p *Param) GetFuncParam() (FuncParam, error) {
|
|
if p == nil {
|
|
return FuncParam{}, errMissingParameter
|
|
}
|
|
// This one doesn't need to be cached, it's used only once.
|
|
fp := FuncParam{}
|
|
err := json.Unmarshal(p.RawMessage, &fp)
|
|
return fp, err
|
|
}
|
|
|
|
// GetFuncParamPair returns a pair of function call parameters.
|
|
func (p *Param) GetFuncParamPair() (FuncParamKV, error) {
|
|
if p == nil {
|
|
return FuncParamKV{}, errMissingParameter
|
|
}
|
|
// This one doesn't need to be cached, it's used only once.
|
|
fpp := FuncParamKV{}
|
|
err := json.Unmarshal(p.RawMessage, &fpp)
|
|
if err != nil {
|
|
return FuncParamKV{}, err
|
|
}
|
|
|
|
return fpp, nil
|
|
}
|
|
|
|
// GetBytesHex returns a []byte value of the parameter if
|
|
// it is a hex-encoded string.
|
|
func (p *Param) GetBytesHex() ([]byte, error) {
|
|
s, err := p.GetString()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return hex.DecodeString(s)
|
|
}
|
|
|
|
// GetBytesBase64 returns a []byte value of the parameter if
|
|
// it is a base64-encoded string.
|
|
func (p *Param) GetBytesBase64() ([]byte, error) {
|
|
s, err := p.GetString()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return base64.StdEncoding.DecodeString(s)
|
|
}
|
|
|
|
// GetSignerWithWitness returns a neorpc.SignerWithWitness value of the parameter.
|
|
func (p *Param) GetSignerWithWitness() (neorpc.SignerWithWitness, error) {
|
|
// This one doesn't need to be cached, it's used only once.
|
|
c := neorpc.SignerWithWitness{}
|
|
err := json.Unmarshal(p.RawMessage, &c)
|
|
if err != nil {
|
|
return neorpc.SignerWithWitness{}, fmt.Errorf("not a signer: %w", err)
|
|
}
|
|
return c, nil
|
|
}
|
|
|
|
// GetSignersWithWitnesses returns a slice of SignerWithWitness with CalledByEntry
|
|
// scope from an array of Uint160 or an array of serialized transaction.Signer stored
|
|
// in the parameter.
|
|
func (p *Param) GetSignersWithWitnesses() ([]transaction.Signer, []transaction.Witness, error) {
|
|
hashes, err := p.GetArray()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
if len(hashes) > transaction.MaxAttributes {
|
|
return nil, nil, errors.New("too many signers")
|
|
}
|
|
signers := make([]transaction.Signer, len(hashes))
|
|
witnesses := make([]transaction.Witness, len(hashes))
|
|
// try to extract hashes first
|
|
for i, h := range hashes {
|
|
var u util.Uint160
|
|
u, err = h.GetUint160FromHex()
|
|
if err != nil {
|
|
break
|
|
}
|
|
signers[i] = transaction.Signer{
|
|
Account: u,
|
|
Scopes: transaction.CalledByEntry,
|
|
}
|
|
}
|
|
if err != nil {
|
|
for i, h := range hashes {
|
|
signerWithWitness, err := h.GetSignerWithWitness()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
signers[i] = signerWithWitness.Signer
|
|
witnesses[i] = signerWithWitness.Witness
|
|
}
|
|
}
|
|
return signers, witnesses, nil
|
|
}
|
|
|
|
// IsNull returns whether the parameter represents JSON nil value.
|
|
func (p *Param) IsNull() bool {
|
|
return bytes.Equal(p.RawMessage, jsonNullBytes)
|
|
}
|
|
|
|
// GetUUID returns UUID from parameter.
|
|
func (p *Param) GetUUID() (uuid.UUID, error) {
|
|
s, err := p.GetString()
|
|
if err != nil {
|
|
return uuid.UUID{}, err
|
|
}
|
|
id, err := uuid.Parse(s)
|
|
if err != nil {
|
|
return uuid.UUID{}, fmt.Errorf("not a valid UUID: %w", err)
|
|
}
|
|
return id, nil
|
|
}
|