mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-12-25 03:56:34 +00:00
f6cb698cd1
Inspired by https://github.com/neo-project/neo-modules/pull/845. Signed-off-by: Roman Khimov <roman@nspcc.ru>
448 lines
9.8 KiB
Go
448 lines
9.8 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"`
|
|
}
|
|
)
|
|
|
|
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
|
|
}
|
|
|
|
// 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
|
|
}
|