e63b25d5ad
With a very special syntax.
311 lines
7 KiB
Go
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{}
|