neoneo-go/pkg/smartcontract/param_context.go
Roman Khimov e63b25d5ad smartcontract: add user-facing testinvokefunction command
With a very special syntax.
2019-11-27 15:12:15 +03:00

311 lines
7 KiB
Go

package smartcontract
import (
"encoding/hex"
"errors"
"strconv"
"strings"
"unicode/utf8"
"github.com/CityOfZion/neo-go/pkg/crypto"
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
"github.com/CityOfZion/neo-go/pkg/io"
"github.com/CityOfZion/neo-go/pkg/util"
)
// ParamType represents the Type of the contract parameter.
type ParamType byte
// A list of supported smart contract parameter types.
const (
SignatureType ParamType = iota
BoolType
IntegerType
Hash160Type
Hash256Type
ByteArrayType
PublicKeyType
StringType
ArrayType
)
// PropertyState represents contract properties (flags).
type PropertyState byte
// List of supported properties.
const (
HasStorage PropertyState = 1 << iota
HasDynamicInvoke
IsPayable
NoProperties = 0
)
// 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"`
}
func (pt ParamType) String() string {
switch pt {
case SignatureType:
return "Signature"
case BoolType:
return "Boolean"
case IntegerType:
return "Integer"
case Hash160Type:
return "Hash160"
case Hash256Type:
return "Hash256"
case ByteArrayType:
return "ByteArray"
case PublicKeyType:
return "PublicKey"
case StringType:
return "String"
case ArrayType:
return "Array"
default:
return ""
}
}
// MarshalJSON implements the json.Marshaler interface.
func (pt ParamType) MarshalJSON() ([]byte, error) {
return []byte(`"` + pt.String() + `"`), nil
}
// EncodeBinary implements io.Serializable interface.
func (pt ParamType) EncodeBinary(w *io.BinWriter) {
w.WriteLE(pt)
}
// DecodeBinary implements io.Serializable interface.
func (pt *ParamType) DecodeBinary(r *io.BinReader) {
r.ReadLE(pt)
}
// NewParameter returns a Parameter with proper initialized Value
// of the given ParamType.
func NewParameter(t ParamType) Parameter {
return Parameter{
Type: t,
Value: nil,
}
}
// parseParamType is a user-friendly string to ParamType converter, it's
// case-insensitive and makes the following conversions:
// signature -> SignatureType
// bool -> BoolType
// int -> IntegerType
// hash160 -> Hash160Type
// hash256 -> Hash256Type
// bytes -> ByteArrayType
// key -> PublicKeyType
// string -> StringType
// anything else generates an error.
func parseParamType(typ string) (ParamType, error) {
switch strings.ToLower(typ) {
case "signature":
return SignatureType, nil
case "bool":
return BoolType, nil
case "int":
return IntegerType, nil
case "hash160":
return Hash160Type, nil
case "hash256":
return Hash256Type, nil
case "bytes":
return ByteArrayType, nil
case "key":
return PublicKeyType, nil
case "string":
return StringType, nil
default:
// We deliberately don't support array here.
return 0, errors.New("wrong or unsupported parameter type")
}
}
// adjustValToType is a value type-checker and converter.
func adjustValToType(typ ParamType, val string) (interface{}, error) {
switch typ {
case SignatureType:
b, err := hex.DecodeString(val)
if err != nil {
return nil, err
}
if len(b) != 64 {
return nil, errors.New("not a signature")
}
return val, nil
case BoolType:
switch val {
case "true":
return true, nil
case "false":
return false, nil
default:
return nil, errors.New("invalid boolean value")
}
case IntegerType:
return strconv.Atoi(val)
case Hash160Type:
u, err := crypto.Uint160DecodeAddress(val)
if err == nil {
return hex.EncodeToString(u.Bytes()), nil
}
b, err := hex.DecodeString(val)
if err != nil {
return nil, err
}
if len(b) != 20 {
return nil, errors.New("not a hash160")
}
return val, nil
case Hash256Type:
b, err := hex.DecodeString(val)
if err != nil {
return nil, err
}
if len(b) != 32 {
return nil, errors.New("not a hash256")
}
return val, nil
case ByteArrayType:
_, err := hex.DecodeString(val)
if err != nil {
return nil, err
}
return val, nil
case PublicKeyType:
_, err := keys.NewPublicKeyFromString(val)
if err != nil {
return nil, err
}
return val, nil
case StringType:
return val, nil
default:
return nil, errors.New("unsupported parameter type")
}
}
// inferParamType tries to infer the value type from its contents. It returns
// IntegerType for anything that looks like decimal integer (can be converted
// with strconv.Atoi), BoolType for true and false values, Hash160Type for
// addresses and hex strings encoding 20 bytes long values, PublicKeyType for
// valid hex-encoded public keys, Hash256Type for hex-encoded 32 bytes values,
// SignatureType for hex-encoded 64 bytes values, ByteArrayType for any other
// valid hex-encoded values and StringType for anything else.
func inferParamType(val string) ParamType {
var err error
_, err = strconv.Atoi(val)
if err == nil {
return IntegerType
}
if val == "true" || val == "false" {
return BoolType
}
_, err = crypto.Uint160DecodeAddress(val)
if err == nil {
return Hash160Type
}
_, err = keys.NewPublicKeyFromString(val)
if err == nil {
return PublicKeyType
}
unhexed, err := hex.DecodeString(val)
if err == nil {
switch len(unhexed) {
case 20:
return Hash160Type
case 32:
return Hash256Type
case 64:
return SignatureType
default:
return ByteArrayType
}
}
// Anything can be a string.
return StringType
}
// 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. 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{}
)
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
}
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)
}
res.Value, err = adjustValToType(res.Type, val)
if err != nil {
return nil, err
}
return res, nil
}
// ContextItem represents a transaction context item.
type ContextItem struct {
Script util.Uint160
Parameters []Parameter
Signatures []Signature
}
// Signature represents a transaction signature.
type Signature struct {
Data []byte
PublicKey []byte
}
// ParameterContext holds the parameter context.
type ParameterContext struct{}