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  }