-
Notifications
You must be signed in to change notification settings - Fork 79
/
nep17.go
215 lines (189 loc) · 7.14 KB
/
nep17.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
/*
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 (
"errors"
"fmt"
"math/big"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
"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"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
)
// 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
}
// 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
}
// Token provides full NEP-17 interface, both safe and state-changing methods.
type Token struct {
TokenReader
TokenWriter
}
// TransferEvent represents a Transfer event as defined in the NEP-17 standard.
type TransferEvent struct {
From util.Uint160
To util.Uint160
Amount *big.Int
}
// TransferParameters is a set of parameters for `transfer` method.
type TransferParameters struct {
From util.Uint160
To util.Uint160
Amount *big.Int
Data any
}
// NewReader creates an instance of TokenReader for contract with the given
// hash using the given Invoker.
func NewReader(invoker Invoker, hash util.Uint160) *TokenReader {
return &TokenReader{*neptoken.New(invoker, hash)}
}
// New creates an instance of Token for contract with the given hash
// using the given Actor.
func New(actor Actor, hash util.Uint160) *Token {
return &Token{*NewReader(actor, hash), TokenWriter{hash, actor}}
}
// 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.
func (t *TokenWriter) Transfer(from util.Uint160, to util.Uint160, amount *big.Int, data any) (util.Uint256, uint32, error) {
return t.MultiTransfer([]TransferParameters{{from, to, amount, data}})
}
// 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.
func (t *TokenWriter) TransferTransaction(from util.Uint160, to util.Uint160, amount *big.Int, data any) (*transaction.Transaction, error) {
return t.MultiTransferTransaction([]TransferParameters{{from, to, amount, data}})
}
// 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.
func (t *TokenWriter) TransferUnsigned(from util.Uint160, to util.Uint160, amount *big.Int, data any) (*transaction.Transaction, error) {
return t.MultiTransferUnsigned([]TransferParameters{{from, to, amount, data}})
}
func (t *TokenWriter) multiTransferScript(params []TransferParameters) ([]byte, error) {
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.
func (t *TokenWriter) MultiTransfer(params []TransferParameters) (util.Uint256, uint32, error) {
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).
func (t *TokenWriter) MultiTransferTransaction(params []TransferParameters) (*transaction.Transaction, error) {
script, err := t.multiTransferScript(params)
if err != nil {
return nil, err
}
return t.actor.MakeRun(script)
}
// MultiTransferUnsigned is similar to MultiTransfer, but returns the same values
// as TransferUnsigned (not yet signed transaction).
func (t *TokenWriter) MultiTransferUnsigned(params []TransferParameters) (*transaction.Transaction, error) {
script, err := t.multiTransferScript(params)
if err != nil {
return nil, err
}
return t.actor.MakeUnsignedRun(script, nil)
}
// 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
}