forked from TrueCloudLab/neoneo-go
They were first introduced ina058598ecc
and then carefully moved in648e0bb242
, but it looks like they were never used by any external code. This code can be useful on the server, but the server has its own params package to deal with parameters. Clients usually create Parameters and then get results as stackitem.Items, so they don't use this code either. So there is zero point in keeping it.
281 lines
7.2 KiB
Go
281 lines
7.2 KiB
Go
package smartcontract
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/base64"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"math/big"
|
|
"os"
|
|
"strings"
|
|
"unicode/utf8"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
|
)
|
|
|
|
// Parameter represents a smart contract parameter.
|
|
type Parameter struct {
|
|
// Type of the parameter.
|
|
Type ParamType `json:"type"`
|
|
// The actual value of the parameter.
|
|
Value interface{} `json:"value"`
|
|
}
|
|
|
|
// ParameterPair represents a key-value pair, a slice of which is stored in
|
|
// MapType Parameter.
|
|
type ParameterPair struct {
|
|
Key Parameter `json:"key"`
|
|
Value Parameter `json:"value"`
|
|
}
|
|
|
|
// NewParameter returns a Parameter with a proper initialized Value
|
|
// of the given ParamType.
|
|
func NewParameter(t ParamType) Parameter {
|
|
return Parameter{
|
|
Type: t,
|
|
Value: nil,
|
|
}
|
|
}
|
|
|
|
type rawParameter struct {
|
|
Type ParamType `json:"type"`
|
|
Value json.RawMessage `json:"value,omitempty"`
|
|
}
|
|
|
|
// MarshalJSON implements the Marshaler interface.
|
|
func (p Parameter) MarshalJSON() ([]byte, error) {
|
|
var (
|
|
resultRawValue json.RawMessage
|
|
resultErr error
|
|
)
|
|
if p.Value == nil {
|
|
if _, ok := validParamTypes[p.Type]; ok && p.Type != UnknownType {
|
|
return json.Marshal(rawParameter{Type: p.Type})
|
|
}
|
|
return nil, fmt.Errorf("can't marshal %s", p.Type)
|
|
}
|
|
switch p.Type {
|
|
case BoolType, StringType, Hash160Type, Hash256Type:
|
|
resultRawValue, resultErr = json.Marshal(p.Value)
|
|
case IntegerType:
|
|
val, ok := p.Value.(*big.Int)
|
|
if !ok {
|
|
resultErr = errors.New("invalid integer value")
|
|
break
|
|
}
|
|
resultRawValue = json.RawMessage(`"` + val.String() + `"`)
|
|
case PublicKeyType, ByteArrayType, SignatureType:
|
|
if p.Type == PublicKeyType {
|
|
resultRawValue, resultErr = json.Marshal(hex.EncodeToString(p.Value.([]byte)))
|
|
} else {
|
|
resultRawValue, resultErr = json.Marshal(base64.StdEncoding.EncodeToString(p.Value.([]byte)))
|
|
}
|
|
case ArrayType:
|
|
var value = p.Value.([]Parameter)
|
|
if value == nil {
|
|
resultRawValue, resultErr = json.Marshal([]Parameter{})
|
|
} else {
|
|
resultRawValue, resultErr = json.Marshal(value)
|
|
}
|
|
case MapType:
|
|
ppair := p.Value.([]ParameterPair)
|
|
resultRawValue, resultErr = json.Marshal(ppair)
|
|
case InteropInterfaceType, AnyType:
|
|
resultRawValue = nil
|
|
default:
|
|
resultErr = fmt.Errorf("can't marshal %s", p.Type)
|
|
}
|
|
if resultErr != nil {
|
|
return nil, resultErr
|
|
}
|
|
return json.Marshal(rawParameter{
|
|
Type: p.Type,
|
|
Value: resultRawValue,
|
|
})
|
|
}
|
|
|
|
// UnmarshalJSON implements the Unmarshaler interface.
|
|
func (p *Parameter) UnmarshalJSON(data []byte) (err error) {
|
|
var (
|
|
r rawParameter
|
|
i int64
|
|
s string
|
|
b []byte
|
|
boolean bool
|
|
)
|
|
if err = json.Unmarshal(data, &r); err != nil {
|
|
return
|
|
}
|
|
p.Type = r.Type
|
|
p.Value = nil
|
|
if len(r.Value) == 0 || bytes.Equal(r.Value, []byte("null")) {
|
|
return
|
|
}
|
|
switch r.Type {
|
|
case BoolType:
|
|
if err = json.Unmarshal(r.Value, &boolean); err != nil {
|
|
return
|
|
}
|
|
p.Value = boolean
|
|
case ByteArrayType, PublicKeyType, SignatureType:
|
|
if err = json.Unmarshal(r.Value, &s); err != nil {
|
|
return
|
|
}
|
|
if r.Type == PublicKeyType {
|
|
b, err = hex.DecodeString(s)
|
|
} else {
|
|
b, err = base64.StdEncoding.DecodeString(s)
|
|
}
|
|
if err != nil {
|
|
return
|
|
}
|
|
p.Value = b
|
|
case StringType:
|
|
if err = json.Unmarshal(r.Value, &s); err != nil {
|
|
return
|
|
}
|
|
p.Value = s
|
|
case IntegerType:
|
|
if err = json.Unmarshal(r.Value, &i); err == nil {
|
|
p.Value = big.NewInt(i)
|
|
return
|
|
}
|
|
// sometimes integer comes as string
|
|
if jErr := json.Unmarshal(r.Value, &s); jErr != nil {
|
|
return jErr
|
|
}
|
|
bi, ok := new(big.Int).SetString(s, 10)
|
|
if !ok {
|
|
// In this case previous err should mean string contains non-digit characters.
|
|
return err
|
|
}
|
|
err = stackitem.CheckIntegerSize(bi)
|
|
if err == nil {
|
|
p.Value = bi
|
|
}
|
|
case ArrayType:
|
|
// https://github.com/neo-project/neo/blob/3d59ecca5a8deb057bdad94b3028a6d5e25ac088/neo/Network/RPC/RpcServer.cs#L67
|
|
var rs []Parameter
|
|
if err = json.Unmarshal(r.Value, &rs); err != nil {
|
|
return
|
|
}
|
|
p.Value = rs
|
|
case MapType:
|
|
var ppair []ParameterPair
|
|
if err = json.Unmarshal(r.Value, &ppair); err != nil {
|
|
return
|
|
}
|
|
p.Value = ppair
|
|
case Hash160Type:
|
|
var h util.Uint160
|
|
if err = json.Unmarshal(r.Value, &h); err != nil {
|
|
return
|
|
}
|
|
p.Value = h
|
|
case Hash256Type:
|
|
var h util.Uint256
|
|
if err = json.Unmarshal(r.Value, &h); err != nil {
|
|
return
|
|
}
|
|
p.Value = h
|
|
case InteropInterfaceType, AnyType:
|
|
// stub, ignore value, it can only be null
|
|
p.Value = nil
|
|
default:
|
|
return fmt.Errorf("can't unmarshal %s", p.Type)
|
|
}
|
|
return
|
|
}
|
|
|
|
// NewParameterFromString returns a new Parameter initialized from the given
|
|
// string in neo-go-specific format. It is intended to be used in user-facing
|
|
// interfaces and has some heuristics in it to simplify parameter passing. The exact
|
|
// syntax is documented in the cli documentation.
|
|
func NewParameterFromString(in string) (*Parameter, error) {
|
|
var (
|
|
char rune
|
|
val string
|
|
err error
|
|
r *strings.Reader
|
|
buf strings.Builder
|
|
escaped bool
|
|
hadType bool
|
|
res = &Parameter{}
|
|
typStr string
|
|
)
|
|
r = strings.NewReader(in)
|
|
for char, _, err = r.ReadRune(); err == nil && char != utf8.RuneError; char, _, err = r.ReadRune() {
|
|
if char == '\\' && !escaped {
|
|
escaped = true
|
|
continue
|
|
}
|
|
if char == ':' && !escaped && !hadType {
|
|
typStr = buf.String()
|
|
res.Type, err = ParseParamType(typStr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// We currently do not support following types:
|
|
if res.Type == ArrayType || res.Type == MapType || res.Type == InteropInterfaceType || res.Type == VoidType {
|
|
return nil, fmt.Errorf("unsupported parameter type %s", res.Type)
|
|
}
|
|
buf.Reset()
|
|
hadType = true
|
|
continue
|
|
}
|
|
escaped = false
|
|
// We don't care about length and it never fails.
|
|
_, _ = buf.WriteRune(char)
|
|
}
|
|
if char == utf8.RuneError {
|
|
return nil, errors.New("bad UTF-8 string")
|
|
}
|
|
// The only other error `ReadRune` returns is io.EOF, which is fine and
|
|
// expected, so we don't check err here.
|
|
|
|
val = buf.String()
|
|
if !hadType {
|
|
res.Type = inferParamType(val)
|
|
}
|
|
if res.Type == ByteArrayType && typStr == fileBytesParamType {
|
|
res.Value, err = os.ReadFile(val)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read '%s' parameter from file '%s': %w", fileBytesParamType, val, err)
|
|
}
|
|
return res, nil
|
|
}
|
|
res.Value, err = adjustValToType(res.Type, val)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return res, nil
|
|
}
|
|
|
|
// ExpandParameterToEmitable converts a parameter to a type which can be handled as
|
|
// an array item by emit.Array. It correlates with the way an RPC server handles
|
|
// FuncParams for invoke* calls inside the request.ExpandArrayIntoScript function.
|
|
func ExpandParameterToEmitable(param Parameter) (interface{}, error) {
|
|
var err error
|
|
switch t := param.Type; t {
|
|
case PublicKeyType:
|
|
return param.Value.(*keys.PublicKey).Bytes(), nil
|
|
case ArrayType:
|
|
arr := param.Value.([]Parameter)
|
|
res := make([]interface{}, len(arr))
|
|
for i := range arr {
|
|
res[i], err = ExpandParameterToEmitable(arr[i])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return res, nil
|
|
case MapType, InteropInterfaceType, UnknownType, AnyType, VoidType:
|
|
return nil, fmt.Errorf("unsupported parameter type: %s", t.String())
|
|
default:
|
|
return param.Value, nil
|
|
}
|
|
}
|