forked from TrueCloudLab/neoneo-go
fed95e0069
Neo 3 can emit Null in its transfer notifications in `from` or `to` fields when minting/burning tokens (unlike Neo 2 that emitted util.Uint256{} for this case), then it gets converted to Parameter as AnyType and we have to JSONize it somehow for proper RPC functioning.
429 lines
10 KiB
Go
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, AnyType:
|
|
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, AnyType:
|
|
// 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
|
|
}
|