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 }