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 }