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

     1  /*
     2  Package nep17 contains RPC wrappers to work with NEP-17 contracts.
     3  
     4  Safe methods are encapsulated into TokenReader structure while Token provides
     5  various methods to perform the only NEP-17 state-changing call, Transfer.
     6  */
     7  package nep17
     8  
     9  import (
    10  	"errors"
    11  	"fmt"
    12  	"math/big"
    13  
    14  	"github.com/nspcc-dev/neo-go/pkg/core/transaction"
    15  	"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
    16  	"github.com/nspcc-dev/neo-go/pkg/rpcclient/neptoken"
    17  	"github.com/nspcc-dev/neo-go/pkg/smartcontract"
    18  	"github.com/nspcc-dev/neo-go/pkg/util"
    19  	"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
    20  )
    21  
    22  // Invoker is used by TokenReader to call various safe methods.
    23  type Invoker interface {
    24  	neptoken.Invoker
    25  }
    26  
    27  // Actor is used by Token to create and send transactions.
    28  type Actor interface {
    29  	Invoker
    30  
    31  	MakeRun(script []byte) (*transaction.Transaction, error)
    32  	MakeUnsignedRun(script []byte, attrs []transaction.Attribute) (*transaction.Transaction, error)
    33  	SendRun(script []byte) (util.Uint256, uint32, error)
    34  }
    35  
    36  // TokenReader represents safe (read-only) methods of NEP-17 token. It can be
    37  // used to query various data.
    38  type TokenReader struct {
    39  	neptoken.Base
    40  }
    41  
    42  // TokenWriter contains NEP-17 token methods that change state. It's not meant
    43  // to be used directly (Token that includes it is more convenient) and just
    44  // separates one set of methods from another to simplify reusing this package
    45  // for other contracts that extend NEP-17 interface.
    46  type TokenWriter struct {
    47  	hash  util.Uint160
    48  	actor Actor
    49  }
    50  
    51  // Token provides full NEP-17 interface, both safe and state-changing methods.
    52  type Token struct {
    53  	TokenReader
    54  	TokenWriter
    55  }
    56  
    57  // TransferEvent represents a Transfer event as defined in the NEP-17 standard.
    58  type TransferEvent struct {
    59  	From   util.Uint160
    60  	To     util.Uint160
    61  	Amount *big.Int
    62  }
    63  
    64  // TransferParameters is a set of parameters for `transfer` method.
    65  type TransferParameters struct {
    66  	From   util.Uint160
    67  	To     util.Uint160
    68  	Amount *big.Int
    69  	Data   any
    70  }
    71  
    72  // NewReader creates an instance of TokenReader for contract with the given
    73  // hash using the given Invoker.
    74  func NewReader(invoker Invoker, hash util.Uint160) *TokenReader {
    75  	return &TokenReader{*neptoken.New(invoker, hash)}
    76  }
    77  
    78  // New creates an instance of Token for contract with the given hash
    79  // using the given Actor.
    80  func New(actor Actor, hash util.Uint160) *Token {
    81  	return &Token{*NewReader(actor, hash), TokenWriter{hash, actor}}
    82  }
    83  
    84  // Transfer creates and sends a transaction that performs a `transfer` method
    85  // call using the given parameters and checks for this call result, failing the
    86  // transaction if it's not true. The returned values are transaction hash, its
    87  // ValidUntilBlock value and an error if any.
    88  func (t *TokenWriter) Transfer(from util.Uint160, to util.Uint160, amount *big.Int, data any) (util.Uint256, uint32, error) {
    89  	return t.MultiTransfer([]TransferParameters{{from, to, amount, data}})
    90  }
    91  
    92  // TransferTransaction creates a transaction that performs a `transfer` method
    93  // call using the given parameters and checks for this call result, failing the
    94  // transaction if it's not true. This transaction is signed, but not sent to the
    95  // network, instead it's returned to the caller.
    96  func (t *TokenWriter) TransferTransaction(from util.Uint160, to util.Uint160, amount *big.Int, data any) (*transaction.Transaction, error) {
    97  	return t.MultiTransferTransaction([]TransferParameters{{from, to, amount, data}})
    98  }
    99  
   100  // TransferUnsigned creates a transaction that performs a `transfer` method
   101  // call using the given parameters and checks for this call result, failing the
   102  // transaction if it's not true. This transaction is not signed and just returned
   103  // to the caller.
   104  func (t *TokenWriter) TransferUnsigned(from util.Uint160, to util.Uint160, amount *big.Int, data any) (*transaction.Transaction, error) {
   105  	return t.MultiTransferUnsigned([]TransferParameters{{from, to, amount, data}})
   106  }
   107  
   108  func (t *TokenWriter) multiTransferScript(params []TransferParameters) ([]byte, error) {
   109  	if len(params) == 0 {
   110  		return nil, errors.New("at least one transfer parameter required")
   111  	}
   112  	b := smartcontract.NewBuilder()
   113  	for i := range params {
   114  		b.InvokeWithAssert(t.hash, "transfer", params[i].From,
   115  			params[i].To, params[i].Amount, params[i].Data)
   116  	}
   117  	return b.Script()
   118  }
   119  
   120  // MultiTransfer is not a real NEP-17 method, but rather a convenient way to
   121  // perform multiple transfers (usually from a single account) in one transaction.
   122  // It accepts a set of parameters, creates a script that calls `transfer` as
   123  // many times as needed (with ASSERTs added, so if any of these transfers fail
   124  // whole transaction (with all transfers) fails). The values returned are the
   125  // same as in Transfer.
   126  func (t *TokenWriter) MultiTransfer(params []TransferParameters) (util.Uint256, uint32, error) {
   127  	script, err := t.multiTransferScript(params)
   128  	if err != nil {
   129  		return util.Uint256{}, 0, err
   130  	}
   131  	return t.actor.SendRun(script)
   132  }
   133  
   134  // MultiTransferTransaction is similar to MultiTransfer, but returns the same values
   135  // as TransferTransaction (signed transaction that is not yet sent).
   136  func (t *TokenWriter) MultiTransferTransaction(params []TransferParameters) (*transaction.Transaction, error) {
   137  	script, err := t.multiTransferScript(params)
   138  	if err != nil {
   139  		return nil, err
   140  	}
   141  	return t.actor.MakeRun(script)
   142  }
   143  
   144  // MultiTransferUnsigned is similar to MultiTransfer, but returns the same values
   145  // as TransferUnsigned (not yet signed transaction).
   146  func (t *TokenWriter) MultiTransferUnsigned(params []TransferParameters) (*transaction.Transaction, error) {
   147  	script, err := t.multiTransferScript(params)
   148  	if err != nil {
   149  		return nil, err
   150  	}
   151  	return t.actor.MakeUnsignedRun(script, nil)
   152  }
   153  
   154  // TransferEventsFromApplicationLog retrieves all emitted TransferEvents from the
   155  // provided [result.ApplicationLog].
   156  func TransferEventsFromApplicationLog(log *result.ApplicationLog) ([]*TransferEvent, error) {
   157  	if log == nil {
   158  		return nil, errors.New("nil application log")
   159  	}
   160  	var res []*TransferEvent
   161  	for i, ex := range log.Executions {
   162  		for j, e := range ex.Events {
   163  			if e.Name != "Transfer" {
   164  				continue
   165  			}
   166  			event := new(TransferEvent)
   167  			err := event.FromStackItem(e.Item)
   168  			if err != nil {
   169  				return nil, fmt.Errorf("failed to decode event from stackitem (event #%d, execution #%d): %w", j, i, err)
   170  			}
   171  			res = append(res, event)
   172  		}
   173  	}
   174  	return res, nil
   175  }
   176  
   177  // FromStackItem converts provided [stackitem.Array] to TransferEvent or returns an
   178  // error if it's not possible to do to so.
   179  func (e *TransferEvent) FromStackItem(item *stackitem.Array) error {
   180  	if item == nil {
   181  		return errors.New("nil item")
   182  	}
   183  	arr, ok := item.Value().([]stackitem.Item)
   184  	if !ok {
   185  		return errors.New("not an array")
   186  	}
   187  	if len(arr) != 3 {
   188  		return errors.New("wrong number of event parameters")
   189  	}
   190  
   191  	b, err := arr[0].TryBytes()
   192  	if err != nil {
   193  		return fmt.Errorf("invalid From: %w", err)
   194  	}
   195  	e.From, err = util.Uint160DecodeBytesBE(b)
   196  	if err != nil {
   197  		return fmt.Errorf("failed to decode From: %w", err)
   198  	}
   199  
   200  	b, err = arr[1].TryBytes()
   201  	if err != nil {
   202  		return fmt.Errorf("invalid To: %w", err)
   203  	}
   204  	e.To, err = util.Uint160DecodeBytesBE(b)
   205  	if err != nil {
   206  		return fmt.Errorf("failed to decode To: %w", err)
   207  	}
   208  
   209  	e.Amount, err = arr[2].TryInteger()
   210  	if err != nil {
   211  		return fmt.Errorf("field to decode Avount: %w", err)
   212  	}
   213  
   214  	return nil
   215  }