package smartcontract import ( "encoding/hex" "encoding/json" "errors" "fmt" "math/big" "strings" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm/emit" "github.com/nspcc-dev/neo-go/pkg/vm/opcode" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" ) // ParamType represents the Type of the smart contract parameter. type ParamType int // A list of supported smart contract parameter types. const ( UnknownType ParamType = -1 AnyType ParamType = 0x00 BoolType ParamType = 0x10 IntegerType ParamType = 0x11 ByteArrayType ParamType = 0x12 StringType ParamType = 0x13 Hash160Type ParamType = 0x14 Hash256Type ParamType = 0x15 PublicKeyType ParamType = 0x16 SignatureType ParamType = 0x17 ArrayType ParamType = 0x20 MapType ParamType = 0x22 InteropInterfaceType ParamType = 0x30 VoidType ParamType = 0xff ) // Lengths (in bytes) of fixed-size types. const ( Hash160Len = util.Uint160Size Hash256Len = util.Uint256Size PublicKeyLen = 33 SignatureLen = keys.SignatureLen ) // fileBytesParamType is a string representation of `filebytes` parameter type used in cli. const fileBytesParamType string = "filebytes" // validParamTypes contains a map of known ParamTypes. var validParamTypes = map[ParamType]bool{ UnknownType: true, AnyType: true, BoolType: true, IntegerType: true, ByteArrayType: true, StringType: true, Hash160Type: true, Hash256Type: true, PublicKeyType: true, SignatureType: true, ArrayType: true, MapType: true, InteropInterfaceType: true, VoidType: true, } // String implements the stringer interface. 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" case MapType: return "Map" case InteropInterfaceType: return "InteropInterface" case VoidType: return "Void" case AnyType: return "Any" default: return "" } } // MarshalJSON implements the json.Marshaler interface. func (pt ParamType) MarshalJSON() ([]byte, error) { return []byte(`"` + pt.String() + `"`), nil } // UnmarshalJSON implements the json.Unmarshaler interface. func (pt *ParamType) UnmarshalJSON(data []byte) error { var s string if err := json.Unmarshal(data, &s); err != nil { return err } p, err := ParseParamType(s) if err != nil { return err } *pt = p return nil } // MarshalYAML implements the YAML Marshaler interface. func (pt ParamType) MarshalYAML() (any, error) { return pt.String(), nil } // UnmarshalYAML implements the YAML Unmarshaler interface. func (pt *ParamType) UnmarshalYAML(unmarshal func(any) error) error { var name string err := unmarshal(&name) if err != nil { return err } *pt, err = ParseParamType(name) return err } // EncodeBinary implements the io.Serializable interface. func (pt ParamType) EncodeBinary(w *io.BinWriter) { w.WriteB(byte(pt)) } // DecodeBinary implements the io.Serializable interface. func (pt *ParamType) DecodeBinary(r *io.BinReader) { *pt = ParamType(r.ReadB()) } // EncodeDefaultValue writes a script to push the default parameter value onto // the evaluation stack into the given writer. It's mostly useful for constructing // dummy invocation scripts when parameter types are known, but they can't be // filled in. A best effort approach is used, it can't be perfect since for many // types the exact values can be arbitrarily long, but it tries to do something // reasonable in each case. For signatures, strings, arrays and "any" type a 64-byte // zero-filled value is used, hash160 and hash256 use appropriately sized values, // public key is represented by 33-byte value while 32 bytes are used for integer // and a simple push+convert is used for boolean. Other types produce no code at all. func (pt ParamType) EncodeDefaultValue(w *io.BinWriter) { var b [SignatureLen]byte switch pt { case AnyType, SignatureType, StringType, ByteArrayType: emit.Bytes(w, b[:]) case BoolType: emit.Bool(w, true) case IntegerType: emit.Instruction(w, opcode.PUSHINT256, b[:32]) case Hash160Type: emit.Bytes(w, b[:Hash160Len]) case Hash256Type: emit.Bytes(w, b[:Hash256Len]) case PublicKeyType: emit.Bytes(w, b[:PublicKeyLen]) case ArrayType, MapType, InteropInterfaceType, VoidType: } } func checkBytesWithLen(vt stackitem.Type, v stackitem.Item, l int) bool { if vt == stackitem.AnyT { return true } if vt != stackitem.ByteArrayT && vt != stackitem.BufferT { return false } b, _ := v.TryBytes() // Can't fail, we know the type exactly. return len(b) == l } func (pt ParamType) Match(v stackitem.Item) bool { vt := v.Type() // Pointer can't be matched at all. if vt == stackitem.PointerT { return false } switch pt { case AnyType: return true case BoolType: return vt == stackitem.BooleanT case IntegerType: return vt == stackitem.IntegerT case ByteArrayType, StringType: return vt == stackitem.ByteArrayT || vt == stackitem.BufferT || vt == stackitem.AnyT case Hash160Type: return checkBytesWithLen(vt, v, Hash160Len) case Hash256Type: return checkBytesWithLen(vt, v, Hash256Len) case PublicKeyType: return checkBytesWithLen(vt, v, PublicKeyLen) case SignatureType: return checkBytesWithLen(vt, v, SignatureLen) case ArrayType: return vt == stackitem.AnyT || vt == stackitem.ArrayT || vt == stackitem.StructT case MapType: return vt == stackitem.AnyT || vt == stackitem.MapT case InteropInterfaceType: return vt == stackitem.AnyT || vt == stackitem.InteropT default: return false } } // ParseParamType is a user-friendly string to ParamType converter, it's // case-insensitive and makes the following conversions: // // signature -> SignatureType // bool, boolean -> BoolType // int, integer -> IntegerType // hash160 -> Hash160Type // hash256 -> Hash256Type // bytes, bytearray, filebytes -> ByteArrayType // key, publickey -> PublicKeyType // string -> StringType // array, struct -> ArrayType // map -> MapType // interopinterface -> InteropInterfaceType // void -> VoidType // // anything else generates an error. func ParseParamType(typ string) (ParamType, error) { switch strings.ToLower(typ) { case "signature": return SignatureType, nil case "bool", "boolean": return BoolType, nil case "int", "integer": return IntegerType, nil case "hash160": return Hash160Type, nil case "hash256": return Hash256Type, nil case "bytes", "bytearray", "bytestring", fileBytesParamType: return ByteArrayType, nil case "key", "publickey": return PublicKeyType, nil case "string": return StringType, nil case "array", "struct": return ArrayType, nil case "map": return MapType, nil case "interopinterface": return InteropInterfaceType, nil case "void": return VoidType, nil case "any": return AnyType, nil default: return UnknownType, fmt.Errorf("bad parameter type: %s", typ) } } // adjustValToType is a value type-checker and converter. func adjustValToType(typ ParamType, val string) (any, error) { switch typ { case SignatureType: b, err := hex.DecodeString(val) if err != nil { return nil, err } if len(b) != SignatureLen { return nil, errors.New("not a signature") } return b, 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: bi, ok := new(big.Int).SetString(val, 10) if !ok || stackitem.CheckIntegerSize(bi) != nil { return nil, errors.New("invalid integer value") } return bi, nil case Hash160Type: u, err := address.StringToUint160(val) if err == nil { return u, nil } u, err = util.Uint160DecodeStringLE(val) if err != nil { return nil, err } return u, nil case Hash256Type: u, err := util.Uint256DecodeStringLE(val) if err != nil { return nil, err } return u, nil case ByteArrayType: return hex.DecodeString(val) case PublicKeyType: pub, err := keys.NewPublicKeyFromString(val) if err != nil { return nil, err } return pub.Bytes(), nil case StringType: return val, nil case AnyType: return nil, 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 a 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 bi, ok := new(big.Int).SetString(val, 10) if ok && stackitem.CheckIntegerSize(bi) == nil { return IntegerType } if val == "nil" { return AnyType } if val == "true" || val == "false" { return BoolType } _, err = address.StringToUint160(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 Hash160Len: return Hash160Type case Hash256Len: return Hash256Type case SignatureLen: return SignatureType default: return ByteArrayType } } // Anything can be a string. return StringType } // ConvertToParamType converts the provided value to the parameter type if it's a valid type. func ConvertToParamType(val int) (ParamType, error) { if validParamTypes[ParamType(val)] { return ParamType(val), nil } return UnknownType, errors.New("unknown parameter type") } // ConvertToStackitemType converts ParamType to corresponding Stackitem.Type. func (pt ParamType) ConvertToStackitemType() stackitem.Type { switch pt { case SignatureType: return stackitem.ByteArrayT case BoolType: return stackitem.BooleanT case IntegerType: return stackitem.IntegerT case Hash160Type: return stackitem.ByteArrayT case Hash256Type: return stackitem.ByteArrayT case ByteArrayType: return stackitem.ByteArrayT case PublicKeyType: return stackitem.ByteArrayT case StringType: // Do not use BufferT to match System.Runtime.Notify conversion rules. return stackitem.ByteArrayT case ArrayType: return stackitem.ArrayT case MapType: return stackitem.MapT case InteropInterfaceType: return stackitem.InteropT case VoidType: return stackitem.AnyT case AnyType: return stackitem.AnyT default: panic(fmt.Sprintf("unknown param type %d", pt)) } }