forked from TrueCloudLab/neoneo-go
5a62eb923e
There's a bug after #785: smartcontract.Parameter of type hash160 should be marshalled in LE (as default marshaller for uint160 does) instead of BE, so fixed.
465 lines
11 KiB
Go
465 lines
11 KiB
Go
package smartcontract
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"fmt"
|
|
"math"
|
|
"math/bits"
|
|
"strconv"
|
|
"strings"
|
|
"unicode/utf8"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
// 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"`
|
|
}
|
|
|
|
// NewParameter returns a Parameter with 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"`
|
|
}
|
|
|
|
type keyValuePair struct {
|
|
Key rawParameter `json:"key"`
|
|
Value rawParameter `json:"value"`
|
|
}
|
|
|
|
type rawKeyValuePair struct {
|
|
Key json.RawMessage `json:"key"`
|
|
Value json.RawMessage `json:"value"`
|
|
}
|
|
|
|
// MarshalJSON implements Marshaler interface.
|
|
func (p *Parameter) MarshalJSON() ([]byte, error) {
|
|
var (
|
|
resultRawValue json.RawMessage
|
|
resultErr error
|
|
)
|
|
switch p.Type {
|
|
case BoolType, StringType, Hash160Type, Hash256Type:
|
|
resultRawValue, resultErr = json.Marshal(p.Value)
|
|
case IntegerType:
|
|
val, ok := p.Value.(int64)
|
|
if !ok {
|
|
resultErr = errors.New("invalid integer value")
|
|
break
|
|
}
|
|
valStr := strconv.FormatInt(val, 10)
|
|
resultRawValue = json.RawMessage(`"` + valStr + `"`)
|
|
case PublicKeyType, ByteArrayType, SignatureType:
|
|
if p.Value == nil {
|
|
resultRawValue = []byte("null")
|
|
} else {
|
|
resultRawValue, resultErr = json.Marshal(hex.EncodeToString(p.Value.([]byte)))
|
|
}
|
|
case ArrayType:
|
|
var value = make([]json.RawMessage, 0)
|
|
for _, parameter := range p.Value.([]Parameter) {
|
|
rawValue, err := json.Marshal(¶meter)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
value = append(value, rawValue)
|
|
}
|
|
resultRawValue, resultErr = json.Marshal(value)
|
|
case MapType:
|
|
var value []keyValuePair
|
|
for key, val := range p.Value.(map[Parameter]Parameter) {
|
|
rawKey, err := json.Marshal(key.Value)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
rawValue, err := json.Marshal(val.Value)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
value = append(value, keyValuePair{
|
|
Key: rawParameter{
|
|
Type: key.Type,
|
|
Value: rawKey,
|
|
},
|
|
Value: rawParameter{
|
|
Type: val.Type,
|
|
Value: rawValue,
|
|
},
|
|
})
|
|
}
|
|
resultRawValue, resultErr = json.Marshal(value)
|
|
default:
|
|
resultErr = errors.Errorf("Marshaller for type %s not implemented", p.Type)
|
|
}
|
|
if resultErr != nil {
|
|
return nil, resultErr
|
|
}
|
|
return json.Marshal(rawParameter{
|
|
Type: p.Type,
|
|
Value: resultRawValue,
|
|
})
|
|
}
|
|
|
|
// UnmarshalJSON implements 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
|
|
}
|
|
switch p.Type = r.Type; 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 b, err = hex.DecodeString(s); 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 = i
|
|
return
|
|
}
|
|
// sometimes integer comes as string
|
|
if err = json.Unmarshal(r.Value, &s); err != nil {
|
|
return
|
|
}
|
|
if i, err = strconv.ParseInt(s, 10, 64); err != nil {
|
|
return
|
|
}
|
|
p.Value = i
|
|
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 rawMap []rawKeyValuePair
|
|
if err = json.Unmarshal(r.Value, &rawMap); err != nil {
|
|
return
|
|
}
|
|
rs := make(map[Parameter]Parameter)
|
|
for _, p := range rawMap {
|
|
var key, value Parameter
|
|
if err = json.Unmarshal(p.Key, &key); err != nil {
|
|
return
|
|
}
|
|
if err = json.Unmarshal(p.Value, &value); err != nil {
|
|
return
|
|
}
|
|
rs[key] = value
|
|
}
|
|
p.Value = rs
|
|
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
|
|
default:
|
|
return errors.Errorf("Unmarshaller for type %s not implemented", p.Type)
|
|
}
|
|
return
|
|
}
|
|
|
|
// EncodeBinary implements io.Serializable interface.
|
|
func (p *Parameter) EncodeBinary(w *io.BinWriter) {
|
|
w.WriteB(byte(p.Type))
|
|
switch p.Type {
|
|
case BoolType:
|
|
w.WriteBool(p.Value.(bool))
|
|
case ByteArrayType, PublicKeyType, SignatureType:
|
|
if p.Value == nil {
|
|
w.WriteVarUint(math.MaxUint64)
|
|
} else {
|
|
w.WriteVarBytes(p.Value.([]byte))
|
|
}
|
|
case StringType:
|
|
w.WriteString(p.Value.(string))
|
|
case IntegerType:
|
|
w.WriteU64LE(uint64(p.Value.(int64)))
|
|
case ArrayType:
|
|
w.WriteArray(p.Value.([]Parameter))
|
|
case MapType:
|
|
m := p.Value.(map[Parameter]Parameter)
|
|
w.WriteVarUint(uint64(len(m)))
|
|
for k := range m {
|
|
v := m[k]
|
|
k.EncodeBinary(w)
|
|
v.EncodeBinary(w)
|
|
}
|
|
case Hash160Type:
|
|
w.WriteBytes(p.Value.(util.Uint160).BytesBE())
|
|
case Hash256Type:
|
|
w.WriteBytes(p.Value.(util.Uint256).BytesBE())
|
|
case InteropInterfaceType:
|
|
default:
|
|
w.Err = fmt.Errorf("unknown type: %x", p.Type)
|
|
}
|
|
}
|
|
|
|
// DecodeBinary implements io.Serializable interface.
|
|
func (p *Parameter) DecodeBinary(r *io.BinReader) {
|
|
p.Type = ParamType(r.ReadB())
|
|
switch p.Type {
|
|
case BoolType:
|
|
p.Value = r.ReadBool()
|
|
case ByteArrayType, PublicKeyType, SignatureType:
|
|
ln := r.ReadVarUint()
|
|
if ln != math.MaxUint64 {
|
|
b := make([]byte, ln)
|
|
r.ReadBytes(b)
|
|
p.Value = b
|
|
}
|
|
case StringType:
|
|
p.Value = r.ReadString()
|
|
case IntegerType:
|
|
p.Value = int64(r.ReadU64LE())
|
|
case ArrayType:
|
|
ps := []Parameter{}
|
|
r.ReadArray(&ps)
|
|
p.Value = ps
|
|
case MapType:
|
|
ln := r.ReadVarUint()
|
|
m := make(map[Parameter]Parameter, ln)
|
|
for i := uint64(0); i < ln; i++ {
|
|
var k, v Parameter
|
|
k.DecodeBinary(r)
|
|
v.DecodeBinary(r)
|
|
m[k] = v
|
|
}
|
|
p.Value = m
|
|
case Hash160Type:
|
|
var u util.Uint160
|
|
r.ReadBytes(u[:])
|
|
p.Value = u
|
|
case Hash256Type:
|
|
var u util.Uint256
|
|
r.ReadBytes(u[:])
|
|
p.Value = u
|
|
case InteropInterfaceType:
|
|
default:
|
|
r.Err = fmt.Errorf("unknown type: %x", p.Type)
|
|
}
|
|
}
|
|
|
|
// Params is an array of Parameter (TODO: drop it?).
|
|
type Params []Parameter
|
|
|
|
// TryParseArray converts an array of Parameter into an array of more appropriate things.
|
|
func (p Params) TryParseArray(vals ...interface{}) error {
|
|
var (
|
|
err error
|
|
i int
|
|
par Parameter
|
|
)
|
|
if len(p) != len(vals) {
|
|
return errors.New("receiver array doesn't fit the Params length")
|
|
}
|
|
for i, par = range p {
|
|
if err = par.TryParse(vals[i]); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// TryParse converts one Parameter into something more appropriate.
|
|
func (p Parameter) TryParse(dest interface{}) error {
|
|
var (
|
|
err error
|
|
ok bool
|
|
data []byte
|
|
)
|
|
switch p.Type {
|
|
case ByteArrayType:
|
|
if data, ok = p.Value.([]byte); !ok {
|
|
return errors.Errorf("failed to cast %s to []byte", p.Value)
|
|
}
|
|
switch dest := dest.(type) {
|
|
case *util.Uint160:
|
|
if *dest, err = util.Uint160DecodeBytesBE(data); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
case *[]byte:
|
|
*dest = data
|
|
return nil
|
|
case *util.Uint256:
|
|
if *dest, err = util.Uint256DecodeBytesLE(data); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
case *int64, *int32, *int16, *int8, *int, *uint64, *uint32, *uint16, *uint8, *uint:
|
|
var size int
|
|
switch dest.(type) {
|
|
case *int64, *uint64:
|
|
size = 64
|
|
case *int32, *uint32:
|
|
size = 32
|
|
case *int16, *uint16:
|
|
size = 16
|
|
case *int8, *uint8:
|
|
size = 8
|
|
case *int, *uint:
|
|
size = bits.UintSize
|
|
}
|
|
|
|
i, err := bytesToUint64(data, size)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
switch dest := dest.(type) {
|
|
case *int64:
|
|
*dest = int64(i)
|
|
case *int32:
|
|
*dest = int32(i)
|
|
case *int16:
|
|
*dest = int16(i)
|
|
case *int8:
|
|
*dest = int8(i)
|
|
case *int:
|
|
*dest = int(i)
|
|
case *uint64:
|
|
*dest = i
|
|
case *uint32:
|
|
*dest = uint32(i)
|
|
case *uint16:
|
|
*dest = uint16(i)
|
|
case *uint8:
|
|
*dest = uint8(i)
|
|
case *uint:
|
|
*dest = uint(i)
|
|
}
|
|
case *string:
|
|
*dest = string(data)
|
|
return nil
|
|
default:
|
|
return errors.Errorf("cannot cast param of type %s to type %s", p.Type, dest)
|
|
}
|
|
default:
|
|
return errors.New("cannot define param type")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func bytesToUint64(b []byte, size int) (uint64, error) {
|
|
var length = size / 8
|
|
if len(b) > length {
|
|
return 0, errors.Errorf("input doesn't fit into %d bits", size)
|
|
}
|
|
if len(b) < length {
|
|
data := make([]byte, length)
|
|
copy(data, b)
|
|
return binary.LittleEndian.Uint64(data), nil
|
|
}
|
|
return binary.LittleEndian.Uint64(b), nil
|
|
}
|
|
|
|
// 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
|
|
}
|
|
// We currently do not support following types:
|
|
if res.Type == ArrayType || res.Type == MapType || res.Type == InteropInterfaceType || res.Type == VoidType {
|
|
return nil, errors.Errorf("Unsupported contract 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)
|
|
}
|
|
res.Value, err = adjustValToType(res.Type, val)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return res, nil
|
|
}
|