github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/smartcontract/parameter.go (about)

     1  package smartcontract
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/base64"
     6  	"encoding/hex"
     7  	"encoding/json"
     8  	"errors"
     9  	"fmt"
    10  	"math/big"
    11  	"os"
    12  	"strings"
    13  	"unicode/utf8"
    14  
    15  	"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
    16  	"github.com/nspcc-dev/neo-go/pkg/util"
    17  	"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
    18  )
    19  
    20  // Parameter represents a smart contract parameter.
    21  type Parameter struct {
    22  	// Type of the parameter.
    23  	Type ParamType `json:"type"`
    24  	// The actual value of the parameter.
    25  	Value any `json:"value"`
    26  }
    27  
    28  // Convertible is something that can be converted to Parameter.
    29  type Convertible interface {
    30  	ToSCParameter() (Parameter, error)
    31  }
    32  
    33  // ParameterPair represents a key-value pair, a slice of which is stored in
    34  // MapType Parameter.
    35  type ParameterPair struct {
    36  	Key   Parameter `json:"key"`
    37  	Value Parameter `json:"value"`
    38  }
    39  
    40  // NewParameter returns a Parameter with a proper initialized Value
    41  // of the given ParamType.
    42  func NewParameter(t ParamType) Parameter {
    43  	return Parameter{
    44  		Type:  t,
    45  		Value: nil,
    46  	}
    47  }
    48  
    49  type rawParameter struct {
    50  	Type  ParamType       `json:"type"`
    51  	Value json.RawMessage `json:"value,omitempty"`
    52  }
    53  
    54  // MarshalJSON implements the Marshaler interface.
    55  func (p Parameter) MarshalJSON() ([]byte, error) {
    56  	var (
    57  		resultRawValue json.RawMessage
    58  		resultErr      error
    59  	)
    60  	if p.Value == nil {
    61  		if _, ok := validParamTypes[p.Type]; ok && p.Type != UnknownType {
    62  			return json.Marshal(rawParameter{Type: p.Type})
    63  		}
    64  		return nil, fmt.Errorf("can't marshal %s", p.Type)
    65  	}
    66  	switch p.Type {
    67  	case BoolType, StringType, Hash160Type, Hash256Type:
    68  		resultRawValue, resultErr = json.Marshal(p.Value)
    69  	case IntegerType:
    70  		val, ok := p.Value.(*big.Int)
    71  		if !ok {
    72  			resultErr = errors.New("invalid integer value")
    73  			break
    74  		}
    75  		resultRawValue = json.RawMessage(`"` + val.String() + `"`)
    76  	case PublicKeyType, ByteArrayType, SignatureType:
    77  		if p.Type == PublicKeyType {
    78  			resultRawValue, resultErr = json.Marshal(hex.EncodeToString(p.Value.([]byte)))
    79  		} else {
    80  			resultRawValue, resultErr = json.Marshal(base64.StdEncoding.EncodeToString(p.Value.([]byte)))
    81  		}
    82  	case ArrayType:
    83  		var value = p.Value.([]Parameter)
    84  		if value == nil {
    85  			resultRawValue, resultErr = json.Marshal([]Parameter{})
    86  		} else {
    87  			resultRawValue, resultErr = json.Marshal(value)
    88  		}
    89  	case MapType:
    90  		ppair := p.Value.([]ParameterPair)
    91  		resultRawValue, resultErr = json.Marshal(ppair)
    92  	case InteropInterfaceType, AnyType:
    93  		resultRawValue = nil
    94  	default:
    95  		resultErr = fmt.Errorf("can't marshal %s", p.Type)
    96  	}
    97  	if resultErr != nil {
    98  		return nil, resultErr
    99  	}
   100  	return json.Marshal(rawParameter{
   101  		Type:  p.Type,
   102  		Value: resultRawValue,
   103  	})
   104  }
   105  
   106  // UnmarshalJSON implements the Unmarshaler interface.
   107  func (p *Parameter) UnmarshalJSON(data []byte) (err error) {
   108  	var (
   109  		r       rawParameter
   110  		i       int64
   111  		s       string
   112  		b       []byte
   113  		boolean bool
   114  	)
   115  	if err = json.Unmarshal(data, &r); err != nil {
   116  		return
   117  	}
   118  	p.Type = r.Type
   119  	p.Value = nil
   120  	if len(r.Value) == 0 || bytes.Equal(r.Value, []byte("null")) {
   121  		return
   122  	}
   123  	switch r.Type {
   124  	case BoolType:
   125  		if err = json.Unmarshal(r.Value, &boolean); err != nil {
   126  			return
   127  		}
   128  		p.Value = boolean
   129  	case ByteArrayType, PublicKeyType, SignatureType:
   130  		if err = json.Unmarshal(r.Value, &s); err != nil {
   131  			return
   132  		}
   133  		if r.Type == PublicKeyType {
   134  			b, err = hex.DecodeString(s)
   135  		} else {
   136  			b, err = base64.StdEncoding.DecodeString(s)
   137  		}
   138  		if err != nil {
   139  			return
   140  		}
   141  		p.Value = b
   142  	case StringType:
   143  		if err = json.Unmarshal(r.Value, &s); err != nil {
   144  			return
   145  		}
   146  		p.Value = s
   147  	case IntegerType:
   148  		if err = json.Unmarshal(r.Value, &i); err == nil {
   149  			p.Value = big.NewInt(i)
   150  			return
   151  		}
   152  		// sometimes integer comes as string
   153  		if jErr := json.Unmarshal(r.Value, &s); jErr != nil {
   154  			return jErr
   155  		}
   156  		bi, ok := new(big.Int).SetString(s, 10)
   157  		if !ok {
   158  			// In this case previous err should mean string contains non-digit characters.
   159  			return err
   160  		}
   161  		err = stackitem.CheckIntegerSize(bi)
   162  		if err == nil {
   163  			p.Value = bi
   164  		}
   165  	case ArrayType:
   166  		// https://github.com/neo-project/neo/blob/3d59ecca5a8deb057bdad94b3028a6d5e25ac088/neo/Network/RPC/RpcServer.cs#L67
   167  		var rs []Parameter
   168  		if err = json.Unmarshal(r.Value, &rs); err != nil {
   169  			return
   170  		}
   171  		p.Value = rs
   172  	case MapType:
   173  		var ppair []ParameterPair
   174  		if err = json.Unmarshal(r.Value, &ppair); err != nil {
   175  			return
   176  		}
   177  		p.Value = ppair
   178  	case Hash160Type:
   179  		var h util.Uint160
   180  		if err = json.Unmarshal(r.Value, &h); err != nil {
   181  			return
   182  		}
   183  		p.Value = h
   184  	case Hash256Type:
   185  		var h util.Uint256
   186  		if err = json.Unmarshal(r.Value, &h); err != nil {
   187  			return
   188  		}
   189  		p.Value = h
   190  	case InteropInterfaceType, AnyType:
   191  		// stub, ignore value, it can only be null
   192  		p.Value = nil
   193  	default:
   194  		return fmt.Errorf("can't unmarshal %s", p.Type)
   195  	}
   196  	return
   197  }
   198  
   199  // NewParameterFromString returns a new Parameter initialized from the given
   200  // string in neo-go-specific format. It is intended to be used in user-facing
   201  // interfaces and has some heuristics in it to simplify parameter passing. The exact
   202  // syntax is documented in the cli documentation.
   203  func NewParameterFromString(in string) (*Parameter, error) {
   204  	var (
   205  		char    rune
   206  		val     string
   207  		err     error
   208  		r       *strings.Reader
   209  		buf     strings.Builder
   210  		escaped bool
   211  		hadType bool
   212  		res     = &Parameter{}
   213  		typStr  string
   214  	)
   215  	r = strings.NewReader(in)
   216  	for char, _, err = r.ReadRune(); err == nil && char != utf8.RuneError; char, _, err = r.ReadRune() {
   217  		if char == '\\' && !escaped {
   218  			escaped = true
   219  			continue
   220  		}
   221  		if char == ':' && !escaped && !hadType {
   222  			typStr = buf.String()
   223  			res.Type, err = ParseParamType(typStr)
   224  			if err != nil {
   225  				return nil, err
   226  			}
   227  			// We currently do not support following types:
   228  			if res.Type == ArrayType || res.Type == MapType || res.Type == InteropInterfaceType || res.Type == VoidType {
   229  				return nil, fmt.Errorf("unsupported parameter type %s", res.Type)
   230  			}
   231  			buf.Reset()
   232  			hadType = true
   233  			continue
   234  		}
   235  		escaped = false
   236  		// We don't care about length and it never fails.
   237  		_, _ = buf.WriteRune(char)
   238  	}
   239  	if char == utf8.RuneError {
   240  		return nil, errors.New("bad UTF-8 string")
   241  	}
   242  	// The only other error `ReadRune` returns is io.EOF, which is fine and
   243  	// expected, so we don't check err here.
   244  
   245  	val = buf.String()
   246  	if !hadType {
   247  		res.Type = inferParamType(val)
   248  	}
   249  	if res.Type == ByteArrayType && typStr == fileBytesParamType {
   250  		res.Value, err = os.ReadFile(val)
   251  		if err != nil {
   252  			return nil, fmt.Errorf("failed to read '%s' parameter from file '%s': %w", fileBytesParamType, val, err)
   253  		}
   254  		return res, nil
   255  	}
   256  	res.Value, err = adjustValToType(res.Type, val)
   257  	if err != nil {
   258  		return nil, err
   259  	}
   260  	return res, nil
   261  }
   262  
   263  // NewParameterFromValue infers Parameter type from the value given and adjusts
   264  // the value if needed. It does not copy the value if it can avoid doing so. All
   265  // regular integers, util.*, keys.PublicKey*, string and bool types are supported,
   266  // slice of byte slices is accepted and converted as well.
   267  func NewParameterFromValue(value any) (Parameter, error) {
   268  	var result = Parameter{
   269  		Value: value,
   270  	}
   271  
   272  	switch v := value.(type) {
   273  	case []byte:
   274  		result.Type = ByteArrayType
   275  	case string:
   276  		result.Type = StringType
   277  	case bool:
   278  		result.Type = BoolType
   279  	case *big.Int:
   280  		result.Type = IntegerType
   281  	case int8:
   282  		result.Type = IntegerType
   283  		result.Value = big.NewInt(int64(v))
   284  	case byte:
   285  		result.Type = IntegerType
   286  		result.Value = big.NewInt(int64(v))
   287  	case int16:
   288  		result.Type = IntegerType
   289  		result.Value = big.NewInt(int64(v))
   290  	case uint16:
   291  		result.Type = IntegerType
   292  		result.Value = big.NewInt(int64(v))
   293  	case int32:
   294  		result.Type = IntegerType
   295  		result.Value = big.NewInt(int64(v))
   296  	case uint32:
   297  		result.Type = IntegerType
   298  		result.Value = big.NewInt(int64(v))
   299  	case int:
   300  		result.Type = IntegerType
   301  		result.Value = big.NewInt(int64(v))
   302  	case uint:
   303  		result.Type = IntegerType
   304  		result.Value = new(big.Int).SetUint64(uint64(v))
   305  	case int64:
   306  		result.Type = IntegerType
   307  		result.Value = big.NewInt(v)
   308  	case uint64:
   309  		result.Type = IntegerType
   310  		result.Value = new(big.Int).SetUint64(v)
   311  	case *Parameter:
   312  		result = *v
   313  	case Parameter:
   314  		result = v
   315  	case Convertible:
   316  		var err error
   317  		result, err = v.ToSCParameter()
   318  		if err != nil {
   319  			return result, fmt.Errorf("failed to convert smartcontract.Convertible (%T) to Parameter: %w", v, err)
   320  		}
   321  	case util.Uint160:
   322  		result.Type = Hash160Type
   323  	case util.Uint256:
   324  		result.Type = Hash256Type
   325  	case *util.Uint160:
   326  		if v != nil {
   327  			return NewParameterFromValue(*v)
   328  		}
   329  		result.Type = AnyType
   330  		result.Value = nil
   331  	case *util.Uint256:
   332  		if v != nil {
   333  			return NewParameterFromValue(*v)
   334  		}
   335  		result.Type = AnyType
   336  		result.Value = nil
   337  	case keys.PublicKey:
   338  		return NewParameterFromValue(&v)
   339  	case *keys.PublicKey:
   340  		result.Type = PublicKeyType
   341  		result.Value = v.Bytes()
   342  	case [][]byte:
   343  		arr := make([]Parameter, 0, len(v))
   344  		for i := range v {
   345  			// We know the type exactly, so error is not possible.
   346  			elem, _ := NewParameterFromValue(v[i])
   347  			arr = append(arr, elem)
   348  		}
   349  		result.Type = ArrayType
   350  		result.Value = arr
   351  	case []Parameter:
   352  		arr := make([]Parameter, len(v))
   353  		copy(arr, v)
   354  		result.Type = ArrayType
   355  		result.Value = arr
   356  	case []*keys.PublicKey:
   357  		return NewParameterFromValue(keys.PublicKeys(v))
   358  	case keys.PublicKeys:
   359  		arr := make([]Parameter, 0, len(v))
   360  		for i := range v {
   361  			// We know the type exactly, so error is not possible.
   362  			elem, _ := NewParameterFromValue(v[i])
   363  			arr = append(arr, elem)
   364  		}
   365  		result.Type = ArrayType
   366  		result.Value = arr
   367  	case []any:
   368  		arr, err := NewParametersFromValues(v...)
   369  		if err != nil {
   370  			return result, err
   371  		}
   372  		result.Type = ArrayType
   373  		result.Value = arr
   374  	case nil:
   375  		result.Type = AnyType
   376  	default:
   377  		return result, fmt.Errorf("unsupported parameter %T", value)
   378  	}
   379  
   380  	return result, nil
   381  }
   382  
   383  // NewParametersFromValues is similar to NewParameterFromValue except that it
   384  // works with multiple values and returns a simple slice of Parameter.
   385  func NewParametersFromValues(values ...any) ([]Parameter, error) {
   386  	res := make([]Parameter, 0, len(values))
   387  	for i := range values {
   388  		elem, err := NewParameterFromValue(values[i])
   389  		if err != nil {
   390  			return nil, err
   391  		}
   392  		res = append(res, elem)
   393  	}
   394  	return res, nil
   395  }
   396  
   397  // ExpandParameterToEmitable converts a parameter to a type which can be handled as
   398  // an array item by emit.Array. It correlates with the way an RPC server handles
   399  // FuncParams for invoke* calls inside the request.ExpandArrayIntoScript function.
   400  func ExpandParameterToEmitable(param Parameter) (any, error) {
   401  	var err error
   402  	switch t := param.Type; t {
   403  	case ArrayType:
   404  		arr := param.Value.([]Parameter)
   405  		res := make([]any, len(arr))
   406  		for i := range arr {
   407  			res[i], err = ExpandParameterToEmitable(arr[i])
   408  			if err != nil {
   409  				return nil, err
   410  			}
   411  		}
   412  		return res, nil
   413  	case MapType, InteropInterfaceType, UnknownType, VoidType:
   414  		return nil, fmt.Errorf("unsupported parameter type: %s", t.String())
   415  	default:
   416  		return param.Value, nil
   417  	}
   418  }
   419  
   420  // ToStackItem converts smartcontract parameter to stackitem.Item.
   421  func (p *Parameter) ToStackItem() (stackitem.Item, error) {
   422  	e, err := ExpandParameterToEmitable(*p)
   423  	if err != nil {
   424  		return nil, err
   425  	}
   426  	return stackitem.Make(e), nil
   427  }