github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/rpcclient/unwrap/unwrap.go (about) 1 /* 2 Package unwrap provides a set of proxy methods to process invocation results. 3 4 Functions implemented there are intended to be used as wrappers for other 5 functions that return (*result.Invoke, error) pair (of which there are many). 6 These functions will check for error, check for VM state, check the number 7 of results, cast them to appropriate type (if everything is OK) and then 8 return a result or error. They're mostly useful for other higher-level 9 contract-specific packages. 10 */ 11 package unwrap 12 13 import ( 14 "crypto/elliptic" 15 "errors" 16 "fmt" 17 "math/big" 18 "unicode/utf8" 19 20 "github.com/google/uuid" 21 "github.com/nspcc-dev/neo-go/pkg/crypto/keys" 22 "github.com/nspcc-dev/neo-go/pkg/neorpc/result" 23 "github.com/nspcc-dev/neo-go/pkg/util" 24 "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" 25 "github.com/nspcc-dev/neo-go/pkg/vm/vmstate" 26 ) 27 28 // Exception is a type used for VM fault messages (aka exceptions). If any of 29 // unwrapper functions encounters a FAULT VM state it creates an instance of 30 // this type as an error using exception string. It can be used with [errors.As] 31 // to get the exact message from VM and compare with known contract-specific 32 // errors. 33 type Exception string 34 35 // ErrNoSessionID is returned from the SessionIterator when the server does not 36 // have sessions enabled and does not perform automatic iterator expansion. It 37 // means you have no way to get the data from returned iterators using this 38 // server, other than expanding it in the VM script. 39 var ErrNoSessionID = errors.New("server returned iterator ID, but no session ID") 40 41 // Error implements the error interface. 42 func (e Exception) Error() string { 43 return string(e) 44 } 45 46 // BigInt expects correct execution (HALT state) with a single stack item 47 // returned. A big.Int is extracted from this item and returned. 48 func BigInt(r *result.Invoke, err error) (*big.Int, error) { 49 itm, err := Item(r, err) 50 if err != nil { 51 return nil, err 52 } 53 return itm.TryInteger() 54 } 55 56 // Bool expects correct execution (HALT state) with a single stack item 57 // returned. A bool is extracted from this item and returned. 58 func Bool(r *result.Invoke, err error) (bool, error) { 59 itm, err := Item(r, err) 60 if err != nil { 61 return false, err 62 } 63 return itm.TryBool() 64 } 65 66 // Int64 expects correct execution (HALT state) with a single stack item 67 // returned. An int64 is extracted from this item and returned. 68 func Int64(r *result.Invoke, err error) (int64, error) { 69 itm, err := Item(r, err) 70 if err != nil { 71 return 0, err 72 } 73 i, err := itm.TryInteger() 74 if err != nil { 75 return 0, err 76 } 77 if !i.IsInt64() { 78 return 0, errors.New("int64 overflow") 79 } 80 return i.Int64(), nil 81 } 82 83 // LimitedInt64 is similar to Int64 except it allows to set minimum and maximum 84 // limits to be checked, so if it doesn't return an error the value is more than 85 // min and less than max. 86 func LimitedInt64(r *result.Invoke, err error, min int64, max int64) (int64, error) { 87 i, err := Int64(r, err) 88 if err != nil { 89 return 0, err 90 } 91 if i < min { 92 return 0, errors.New("too small value") 93 } 94 if i > max { 95 return 0, errors.New("too big value") 96 } 97 return i, nil 98 } 99 100 // Bytes expects correct execution (HALT state) with a single stack item 101 // returned. A slice of bytes is extracted from this item and returned. 102 func Bytes(r *result.Invoke, err error) ([]byte, error) { 103 itm, err := Item(r, err) 104 if err != nil { 105 return nil, err 106 } 107 return itm.TryBytes() 108 } 109 110 // UTF8String expects correct execution (HALT state) with a single stack item 111 // returned. A string is extracted from this item and checked for UTF-8 112 // correctness, valid strings are then returned. 113 func UTF8String(r *result.Invoke, err error) (string, error) { 114 b, err := Bytes(r, err) 115 if err != nil { 116 return "", err 117 } 118 if !utf8.Valid(b) { 119 return "", errors.New("not a UTF-8 string") 120 } 121 return string(b), nil 122 } 123 124 // PrintableASCIIString expects correct execution (HALT state) with a single 125 // stack item returned. A string is extracted from this item and checked to 126 // only contain ASCII characters in printable range, valid strings are then 127 // returned. 128 func PrintableASCIIString(r *result.Invoke, err error) (string, error) { 129 s, err := UTF8String(r, err) 130 if err != nil { 131 return "", err 132 } 133 for _, c := range s { 134 if c < 32 || c >= 127 { 135 return "", errors.New("not a printable ASCII string") 136 } 137 } 138 return s, nil 139 } 140 141 // Uint160 expects correct execution (HALT state) with a single stack item 142 // returned. An util.Uint160 is extracted from this item and returned. 143 func Uint160(r *result.Invoke, err error) (util.Uint160, error) { 144 b, err := Bytes(r, err) 145 if err != nil { 146 return util.Uint160{}, err 147 } 148 return util.Uint160DecodeBytesBE(b) 149 } 150 151 // Uint256 expects correct execution (HALT state) with a single stack item 152 // returned. An util.Uint256 is extracted from this item and returned. 153 func Uint256(r *result.Invoke, err error) (util.Uint256, error) { 154 b, err := Bytes(r, err) 155 if err != nil { 156 return util.Uint256{}, err 157 } 158 return util.Uint256DecodeBytesBE(b) 159 } 160 161 // PublicKey expects correct execution (HALT state) with a single stack item 162 // returned. A public key is extracted from this item and returned. 163 func PublicKey(r *result.Invoke, err error) (*keys.PublicKey, error) { 164 b, err := Bytes(r, err) 165 if err != nil { 166 return nil, err 167 } 168 return keys.NewPublicKeyFromBytes(b, elliptic.P256()) 169 } 170 171 // SessionIterator expects correct execution (HALT state) with a single stack 172 // item returned. If this item is an iterator it's returned to the caller along 173 // with the session ID. Notice that this function also returns successfully 174 // with zero session ID (but an appropriate Iterator holding all the data 175 // received) when RPC server performs (limited) iterator expansion which is the 176 // default behavior for NeoGo servers with SessionEnabled set to false. 177 func SessionIterator(r *result.Invoke, err error) (uuid.UUID, result.Iterator, error) { 178 itm, err := Item(r, err) 179 if err != nil { 180 return uuid.UUID{}, result.Iterator{}, err 181 } 182 iter, err := itemToSessionIterator(itm) 183 if err != nil { 184 return uuid.UUID{}, result.Iterator{}, err 185 } 186 if (r.Session == uuid.UUID{}) && iter.ID != nil { 187 return uuid.UUID{}, result.Iterator{}, ErrNoSessionID 188 } 189 return r.Session, iter, nil 190 } 191 192 // ArrayAndSessionIterator expects correct execution (HALT state) with one or two stack 193 // items returned. If there is 1 item, it must be an array. If there is a second item, 194 // it must be an iterator. This is exactly the result of smartcontract.CreateCallAndPrefetchIteratorScript. 195 // Sessions must be enabled on the RPC server for this to function correctly. 196 func ArrayAndSessionIterator(r *result.Invoke, err error) ([]stackitem.Item, uuid.UUID, result.Iterator, error) { 197 if err := checkResOK(r, err); err != nil { 198 return nil, uuid.UUID{}, result.Iterator{}, err 199 } 200 if len(r.Stack) == 0 { 201 return nil, uuid.UUID{}, result.Iterator{}, errors.New("result stack is empty") 202 } 203 if len(r.Stack) != 1 && len(r.Stack) != 2 { 204 return nil, uuid.UUID{}, result.Iterator{}, fmt.Errorf("expected 1 or 2 result items, got %d", len(r.Stack)) 205 } 206 207 // Unwrap array. 208 itm := r.Stack[0] 209 arr, ok := itm.Value().([]stackitem.Item) 210 if !ok { 211 return nil, uuid.UUID{}, result.Iterator{}, errors.New("not an array") 212 } 213 214 // Check whether iterator exists and unwrap it. 215 if len(r.Stack) == 1 { 216 return arr, uuid.UUID{}, result.Iterator{}, nil 217 } 218 219 iter, err := itemToSessionIterator(r.Stack[1]) 220 if err != nil { 221 return nil, uuid.UUID{}, result.Iterator{}, err 222 } 223 if (r.Session == uuid.UUID{}) { 224 return nil, uuid.UUID{}, result.Iterator{}, ErrNoSessionID 225 } 226 return arr, r.Session, iter, nil 227 } 228 229 func itemToSessionIterator(itm stackitem.Item) (result.Iterator, error) { 230 if t := itm.Type(); t != stackitem.InteropT { 231 return result.Iterator{}, fmt.Errorf("expected InteropInterface, got %s", t) 232 } 233 iter, ok := itm.Value().(result.Iterator) 234 if !ok { 235 return result.Iterator{}, errors.New("the item is InteropInterface, but not an Iterator") 236 } 237 return iter, nil 238 } 239 240 // Array expects correct execution (HALT state) with a single array stack item 241 // returned. This item is returned to the caller. Notice that this function can 242 // be used for structures as well since they're also represented as slices of 243 // stack items (the number of them and their types are structure-specific). 244 func Array(r *result.Invoke, err error) ([]stackitem.Item, error) { 245 itm, err := Item(r, err) 246 if err != nil { 247 return nil, err 248 } 249 arr, ok := itm.Value().([]stackitem.Item) 250 if !ok { 251 return nil, errors.New("not an array") 252 } 253 return arr, nil 254 } 255 256 // ArrayOfBools checks the result for correct state (HALT) and then extracts a 257 // slice of boolean values from the returned stack item. 258 func ArrayOfBools(r *result.Invoke, err error) ([]bool, error) { 259 a, err := Array(r, err) 260 if err != nil { 261 return nil, err 262 } 263 res := make([]bool, len(a)) 264 for i := range a { 265 b, err := a[i].TryBool() 266 if err != nil { 267 return nil, fmt.Errorf("element %d is not a boolean: %w", i, err) 268 } 269 res[i] = b 270 } 271 return res, nil 272 } 273 274 // ArrayOfBigInts checks the result for correct state (HALT) and then extracts a 275 // slice of (big) integer values from the returned stack item. 276 func ArrayOfBigInts(r *result.Invoke, err error) ([]*big.Int, error) { 277 a, err := Array(r, err) 278 if err != nil { 279 return nil, err 280 } 281 res := make([]*big.Int, len(a)) 282 for i := range a { 283 v, err := a[i].TryInteger() 284 if err != nil { 285 return nil, fmt.Errorf("element %d is not an integer: %w", i, err) 286 } 287 res[i] = v 288 } 289 return res, nil 290 } 291 292 // ArrayOfBytes checks the result for correct state (HALT) and then extracts a 293 // slice of byte slices from the returned stack item. 294 func ArrayOfBytes(r *result.Invoke, err error) ([][]byte, error) { 295 a, err := Array(r, err) 296 if err != nil { 297 return nil, err 298 } 299 res := make([][]byte, len(a)) 300 for i := range a { 301 b, err := a[i].TryBytes() 302 if err != nil { 303 return nil, fmt.Errorf("element %d is not a byte string: %w", i, err) 304 } 305 res[i] = b 306 } 307 return res, nil 308 } 309 310 // ArrayOfUTB8Strings checks the result for correct state (HALT) and then extracts a 311 // slice of UTF-8 strings from the returned stack item. 312 func ArrayOfUTF8Strings(r *result.Invoke, err error) ([]string, error) { 313 a, err := Array(r, err) 314 if err != nil { 315 return nil, err 316 } 317 res := make([]string, len(a)) 318 for i := range a { 319 b, err := a[i].TryBytes() 320 if err != nil { 321 return nil, fmt.Errorf("element %d is not a byte string: %w", i, err) 322 } 323 if !utf8.Valid(b) { 324 return nil, fmt.Errorf("element %d is not a UTF-8 string", i) 325 } 326 res[i] = string(b) 327 } 328 return res, nil 329 } 330 331 // ArrayOfUint160 checks the result for correct state (HALT) and then extracts a 332 // slice of util.Uint160 from the returned stack item. 333 func ArrayOfUint160(r *result.Invoke, err error) ([]util.Uint160, error) { 334 a, err := Array(r, err) 335 if err != nil { 336 return nil, err 337 } 338 res := make([]util.Uint160, len(a)) 339 for i := range a { 340 b, err := a[i].TryBytes() 341 if err != nil { 342 return nil, fmt.Errorf("element %d is not a byte string: %w", i, err) 343 } 344 u, err := util.Uint160DecodeBytesBE(b) 345 if err != nil { 346 return nil, fmt.Errorf("element %d is not a uint160: %w", i, err) 347 } 348 res[i] = u 349 } 350 return res, nil 351 } 352 353 // ArrayOfUint256 checks the result for correct state (HALT) and then extracts a 354 // slice of util.Uint256 from the returned stack item. 355 func ArrayOfUint256(r *result.Invoke, err error) ([]util.Uint256, error) { 356 a, err := Array(r, err) 357 if err != nil { 358 return nil, err 359 } 360 res := make([]util.Uint256, len(a)) 361 for i := range a { 362 b, err := a[i].TryBytes() 363 if err != nil { 364 return nil, fmt.Errorf("element %d is not a byte string: %w", i, err) 365 } 366 u, err := util.Uint256DecodeBytesBE(b) 367 if err != nil { 368 return nil, fmt.Errorf("element %d is not a uint256: %w", i, err) 369 } 370 res[i] = u 371 } 372 return res, nil 373 } 374 375 // ArrayOfPublicKeys checks the result for correct state (HALT) and then 376 // extracts a slice of public keys from the returned stack item. 377 func ArrayOfPublicKeys(r *result.Invoke, err error) (keys.PublicKeys, error) { 378 arr, err := Array(r, err) 379 if err != nil { 380 return nil, err 381 } 382 pks := make(keys.PublicKeys, len(arr)) 383 for i, item := range arr { 384 val, err := item.TryBytes() 385 if err != nil { 386 return nil, fmt.Errorf("invalid array element #%d: %s", i, item.Type()) 387 } 388 pks[i], err = keys.NewPublicKeyFromBytes(val, elliptic.P256()) 389 if err != nil { 390 return nil, fmt.Errorf("array element #%d in not a key: %w", i, err) 391 } 392 } 393 return pks, nil 394 } 395 396 // Map expects correct execution (HALT state) with a single stack item 397 // returned. A stackitem.Map is extracted from this item and returned. 398 func Map(r *result.Invoke, err error) (*stackitem.Map, error) { 399 itm, err := Item(r, err) 400 if err != nil { 401 return nil, err 402 } 403 if t := itm.Type(); t != stackitem.MapT { 404 return nil, fmt.Errorf("%s is not a map", t.String()) 405 } 406 return itm.(*stackitem.Map), nil 407 } 408 409 func checkResOK(r *result.Invoke, err error) error { 410 if err != nil { 411 return err 412 } 413 if r.State != vmstate.Halt.String() { 414 return fmt.Errorf("invocation failed: %w", Exception(r.FaultException)) 415 } 416 if r.FaultException != "" { 417 return fmt.Errorf("inconsistent result, HALTed with exception: %w", Exception(r.FaultException)) 418 } 419 return nil 420 } 421 422 // Item returns a stack item from the result if execution was successful (HALT 423 // state) and if it's the only element on the result stack. 424 func Item(r *result.Invoke, err error) (stackitem.Item, error) { 425 err = checkResOK(r, err) 426 if err != nil { 427 return nil, err 428 } 429 if len(r.Stack) == 0 { 430 return nil, errors.New("result stack is empty") 431 } 432 if len(r.Stack) > 1 { 433 return nil, fmt.Errorf("too many (%d) result items", len(r.Stack)) 434 } 435 return r.Stack[0], nil 436 } 437 438 // Nothing expects zero stack items and a successful invocation (HALT state). 439 func Nothing(r *result.Invoke, err error) error { 440 err = checkResOK(r, err) 441 if err != nil { 442 return err 443 } 444 if len(r.Stack) != 0 { 445 return errors.New("result stack is not empty") 446 } 447 return nil 448 }