2022-08-12 11:46:50 +00:00
|
|
|
/*
|
|
|
|
Package nep17 contains RPC wrappers to work with NEP-17 contracts.
|
|
|
|
|
|
|
|
Safe methods are encapsulated into TokenReader structure while Token provides
|
|
|
|
various methods to perform the only NEP-17 state-changing call, Transfer.
|
|
|
|
*/
|
|
|
|
package nep17
|
|
|
|
|
|
|
|
import (
|
2022-08-15 07:55:13 +00:00
|
|
|
"errors"
|
2023-05-21 12:08:29 +00:00
|
|
|
"fmt"
|
2022-08-12 11:46:50 +00:00
|
|
|
"math/big"
|
|
|
|
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
2023-05-21 12:08:29 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
2022-08-12 11:46:50 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/neptoken"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
2023-05-21 12:08:29 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
2022-08-12 11:46:50 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// Invoker is used by TokenReader to call various safe methods.
|
|
|
|
type Invoker interface {
|
|
|
|
neptoken.Invoker
|
|
|
|
}
|
|
|
|
|
|
|
|
// Actor is used by Token to create and send transactions.
|
|
|
|
type Actor interface {
|
|
|
|
Invoker
|
|
|
|
|
|
|
|
MakeRun(script []byte) (*transaction.Transaction, error)
|
|
|
|
MakeUnsignedRun(script []byte, attrs []transaction.Attribute) (*transaction.Transaction, error)
|
|
|
|
SendRun(script []byte) (util.Uint256, uint32, error)
|
|
|
|
}
|
|
|
|
|
|
|
|
// TokenReader represents safe (read-only) methods of NEP-17 token. It can be
|
|
|
|
// used to query various data.
|
|
|
|
type TokenReader struct {
|
|
|
|
neptoken.Base
|
|
|
|
}
|
|
|
|
|
2022-09-07 12:05:25 +00:00
|
|
|
// TokenWriter contains NEP-17 token methods that change state. It's not meant
|
|
|
|
// to be used directly (Token that includes it is more convenient) and just
|
|
|
|
// separates one set of methods from another to simplify reusing this package
|
|
|
|
// for other contracts that extend NEP-17 interface.
|
|
|
|
type TokenWriter struct {
|
|
|
|
hash util.Uint160
|
|
|
|
actor Actor
|
|
|
|
}
|
|
|
|
|
2022-08-12 11:46:50 +00:00
|
|
|
// Token provides full NEP-17 interface, both safe and state-changing methods.
|
|
|
|
type Token struct {
|
|
|
|
TokenReader
|
2022-09-07 12:05:25 +00:00
|
|
|
TokenWriter
|
2022-08-12 11:46:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// TransferEvent represents a Transfer event as defined in the NEP-17 standard.
|
|
|
|
type TransferEvent struct {
|
|
|
|
From util.Uint160
|
|
|
|
To util.Uint160
|
|
|
|
Amount *big.Int
|
|
|
|
}
|
|
|
|
|
2022-08-15 07:55:13 +00:00
|
|
|
// TransferParameters is a set of parameters for `transfer` method.
|
|
|
|
type TransferParameters struct {
|
|
|
|
From util.Uint160
|
|
|
|
To util.Uint160
|
|
|
|
Amount *big.Int
|
2023-04-03 10:34:24 +00:00
|
|
|
Data any
|
2022-08-15 07:55:13 +00:00
|
|
|
}
|
|
|
|
|
2022-08-26 18:52:19 +00:00
|
|
|
// NewReader creates an instance of TokenReader for contract with the given
|
|
|
|
// hash using the given Invoker.
|
2022-08-12 11:46:50 +00:00
|
|
|
func NewReader(invoker Invoker, hash util.Uint160) *TokenReader {
|
2022-08-26 18:52:19 +00:00
|
|
|
return &TokenReader{*neptoken.New(invoker, hash)}
|
2022-08-12 11:46:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// New creates an instance of Token for contract with the given hash
|
|
|
|
// using the given Actor.
|
|
|
|
func New(actor Actor, hash util.Uint160) *Token {
|
2022-09-07 12:05:25 +00:00
|
|
|
return &Token{*NewReader(actor, hash), TokenWriter{hash, actor}}
|
2022-08-12 11:46:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Transfer creates and sends a transaction that performs a `transfer` method
|
|
|
|
// call using the given parameters and checks for this call result, failing the
|
|
|
|
// transaction if it's not true. The returned values are transaction hash, its
|
|
|
|
// ValidUntilBlock value and an error if any.
|
2023-04-03 10:34:24 +00:00
|
|
|
func (t *TokenWriter) Transfer(from util.Uint160, to util.Uint160, amount *big.Int, data any) (util.Uint256, uint32, error) {
|
2022-08-15 07:55:13 +00:00
|
|
|
return t.MultiTransfer([]TransferParameters{{from, to, amount, data}})
|
2022-08-12 11:46:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// TransferTransaction creates a transaction that performs a `transfer` method
|
|
|
|
// call using the given parameters and checks for this call result, failing the
|
|
|
|
// transaction if it's not true. This transaction is signed, but not sent to the
|
|
|
|
// network, instead it's returned to the caller.
|
2023-04-03 10:34:24 +00:00
|
|
|
func (t *TokenWriter) TransferTransaction(from util.Uint160, to util.Uint160, amount *big.Int, data any) (*transaction.Transaction, error) {
|
2022-08-15 07:55:13 +00:00
|
|
|
return t.MultiTransferTransaction([]TransferParameters{{from, to, amount, data}})
|
2022-08-12 11:46:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// TransferUnsigned creates a transaction that performs a `transfer` method
|
|
|
|
// call using the given parameters and checks for this call result, failing the
|
|
|
|
// transaction if it's not true. This transaction is not signed and just returned
|
|
|
|
// to the caller.
|
2023-04-03 10:34:24 +00:00
|
|
|
func (t *TokenWriter) TransferUnsigned(from util.Uint160, to util.Uint160, amount *big.Int, data any) (*transaction.Transaction, error) {
|
2022-08-15 07:55:13 +00:00
|
|
|
return t.MultiTransferUnsigned([]TransferParameters{{from, to, amount, data}})
|
|
|
|
}
|
|
|
|
|
2022-09-07 12:05:25 +00:00
|
|
|
func (t *TokenWriter) multiTransferScript(params []TransferParameters) ([]byte, error) {
|
2022-08-15 07:55:13 +00:00
|
|
|
if len(params) == 0 {
|
|
|
|
return nil, errors.New("at least one transfer parameter required")
|
|
|
|
}
|
|
|
|
b := smartcontract.NewBuilder()
|
|
|
|
for i := range params {
|
|
|
|
b.InvokeWithAssert(t.hash, "transfer", params[i].From,
|
|
|
|
params[i].To, params[i].Amount, params[i].Data)
|
|
|
|
}
|
|
|
|
return b.Script()
|
|
|
|
}
|
|
|
|
|
|
|
|
// MultiTransfer is not a real NEP-17 method, but rather a convenient way to
|
|
|
|
// perform multiple transfers (usually from a single account) in one transaction.
|
|
|
|
// It accepts a set of parameters, creates a script that calls `transfer` as
|
|
|
|
// many times as needed (with ASSERTs added, so if any of these transfers fail
|
|
|
|
// whole transaction (with all transfers) fails). The values returned are the
|
|
|
|
// same as in Transfer.
|
2022-09-07 12:05:25 +00:00
|
|
|
func (t *TokenWriter) MultiTransfer(params []TransferParameters) (util.Uint256, uint32, error) {
|
2022-08-15 07:55:13 +00:00
|
|
|
script, err := t.multiTransferScript(params)
|
|
|
|
if err != nil {
|
|
|
|
return util.Uint256{}, 0, err
|
|
|
|
}
|
|
|
|
return t.actor.SendRun(script)
|
|
|
|
}
|
|
|
|
|
|
|
|
// MultiTransferTransaction is similar to MultiTransfer, but returns the same values
|
|
|
|
// as TransferTransaction (signed transaction that is not yet sent).
|
2022-09-07 12:05:25 +00:00
|
|
|
func (t *TokenWriter) MultiTransferTransaction(params []TransferParameters) (*transaction.Transaction, error) {
|
2022-08-15 07:55:13 +00:00
|
|
|
script, err := t.multiTransferScript(params)
|
2022-08-12 11:46:50 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2022-08-15 07:55:13 +00:00
|
|
|
return t.actor.MakeRun(script)
|
2022-08-12 11:46:50 +00:00
|
|
|
}
|
|
|
|
|
2022-08-15 07:55:13 +00:00
|
|
|
// MultiTransferUnsigned is similar to MultiTransfer, but returns the same values
|
|
|
|
// as TransferUnsigned (not yet signed transaction).
|
2022-09-07 12:05:25 +00:00
|
|
|
func (t *TokenWriter) MultiTransferUnsigned(params []TransferParameters) (*transaction.Transaction, error) {
|
2022-08-15 07:55:13 +00:00
|
|
|
script, err := t.multiTransferScript(params)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return t.actor.MakeUnsignedRun(script, nil)
|
2022-08-12 11:46:50 +00:00
|
|
|
}
|
2023-05-21 12:08:29 +00:00
|
|
|
|
|
|
|
// TransferEventsFromApplicationLog retrieves all emitted TransferEvents from the
|
|
|
|
// provided [result.ApplicationLog].
|
|
|
|
func TransferEventsFromApplicationLog(log *result.ApplicationLog) ([]*TransferEvent, error) {
|
|
|
|
if log == nil {
|
|
|
|
return nil, errors.New("nil application log")
|
|
|
|
}
|
|
|
|
var res []*TransferEvent
|
|
|
|
for i, ex := range log.Executions {
|
|
|
|
for j, e := range ex.Events {
|
|
|
|
if e.Name != "Transfer" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
event := new(TransferEvent)
|
|
|
|
err := event.FromStackItem(e.Item)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to decode event from stackitem (event #%d, execution #%d): %w", j, i, err)
|
|
|
|
}
|
|
|
|
res = append(res, event)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return res, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// FromStackItem converts provided [stackitem.Array] to TransferEvent or returns an
|
|
|
|
// error if it's not possible to do to so.
|
|
|
|
func (e *TransferEvent) FromStackItem(item *stackitem.Array) error {
|
|
|
|
if item == nil {
|
|
|
|
return errors.New("nil item")
|
|
|
|
}
|
|
|
|
arr, ok := item.Value().([]stackitem.Item)
|
|
|
|
if !ok {
|
|
|
|
return errors.New("not an array")
|
|
|
|
}
|
|
|
|
if len(arr) != 3 {
|
|
|
|
return errors.New("wrong number of event parameters")
|
|
|
|
}
|
|
|
|
|
|
|
|
b, err := arr[0].TryBytes()
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("invalid From: %w", err)
|
|
|
|
}
|
|
|
|
e.From, err = util.Uint160DecodeBytesBE(b)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to decode From: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
b, err = arr[1].TryBytes()
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("invalid To: %w", err)
|
|
|
|
}
|
|
|
|
e.To, err = util.Uint160DecodeBytesBE(b)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to decode To: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
e.Amount, err = arr[2].TryInteger()
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("field to decode Avount: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|