neoneo-go/pkg/smartcontract/parameter.go
Roman Khimov 03ecab5eec smartcontract: add JSON marshal/unmarshal for InteropType
We actually have to do that in order to answer getapplicationlog requests for
transactions that leave some interop items on the stack. It follows the same
logic our binary serializer/deserializes does leaving the type and stripping
the value (whatever that is).
2020-05-25 00:27:39 +03:00

429 lines
10 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"`
}
// ParameterPair represents 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 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"`
}
// 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 = p.Value.([]Parameter)
resultRawValue, resultErr = json.Marshal(value)
case MapType:
ppair := p.Value.([]ParameterPair)
resultRawValue, resultErr = json.Marshal(ppair)
case InteropInterfaceType:
resultRawValue = []byte("null")
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 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:
// stub, ignore value, it can only be null
p.Value = nil
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:
w.WriteArray(p.Value.([]ParameterPair))
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:
ps := []ParameterPair{}
r.ReadArray(&ps)
p.Value = ps
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)
}
}
// EncodeBinary implements io.Serializable interface.
func (p *ParameterPair) EncodeBinary(w *io.BinWriter) {
p.Key.EncodeBinary(w)
p.Value.EncodeBinary(w)
}
// DecodeBinary implements io.Serializable interface.
func (p *ParameterPair) DecodeBinary(r *io.BinReader) {
p.Key.DecodeBinary(r)
p.Value.DecodeBinary(r)
}
// 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
}