basic_nft.gno
8.92 Kb ยท 404 lines
1package grc721
2
3import (
4 "math/overflow"
5 "std"
6 "strconv"
7
8 "gno.land/p/demo/avl"
9 "gno.land/p/demo/ufmt"
10)
11
12type basicNFT struct {
13 name string
14 symbol string
15 owners avl.Tree // tokenId -> OwnerAddress
16 balances avl.Tree // OwnerAddress -> TokenCount
17 tokenApprovals avl.Tree // TokenId -> ApprovedAddress
18 tokenURIs avl.Tree // TokenId -> URIs
19 operatorApprovals avl.Tree // "OwnerAddress:OperatorAddress" -> bool
20}
21
22// Returns new basic NFT
23func NewBasicNFT(name string, symbol string) *basicNFT {
24 return &basicNFT{
25 name: name,
26 symbol: symbol,
27
28 owners: avl.Tree{},
29 balances: avl.Tree{},
30 tokenApprovals: avl.Tree{},
31 tokenURIs: avl.Tree{},
32 operatorApprovals: avl.Tree{},
33 }
34}
35
36func (s *basicNFT) Name() string { return s.name }
37func (s *basicNFT) Symbol() string { return s.symbol }
38func (s *basicNFT) TokenCount() int64 { return int64(s.owners.Size()) }
39
40// BalanceOf returns balance of input address
41func (s *basicNFT) BalanceOf(addr std.Address) (int64, error) {
42 if err := isValidAddress(addr); err != nil {
43 return 0, err
44 }
45
46 balance, found := s.balances.Get(addr.String())
47 if !found {
48 return 0, nil
49 }
50
51 return balance.(int64), nil
52}
53
54// OwnerOf returns owner of input token id
55func (s *basicNFT) OwnerOf(tid TokenID) (std.Address, error) {
56 owner, found := s.owners.Get(string(tid))
57 if !found {
58 return "", ErrInvalidTokenId
59 }
60
61 return owner.(std.Address), nil
62}
63
64// TokenURI returns the URI of input token id
65func (s *basicNFT) TokenURI(tid TokenID) (string, error) {
66 uri, found := s.tokenURIs.Get(string(tid))
67 if !found {
68 return "", ErrInvalidTokenId
69 }
70
71 return uri.(string), nil
72}
73
74func (s *basicNFT) SetTokenURI(tid TokenID, tURI TokenURI) (bool, error) {
75 // check for invalid TokenID
76 if !s.exists(tid) {
77 return false, ErrInvalidTokenId
78 }
79
80 // check for the right owner
81 owner, err := s.OwnerOf(tid)
82 if err != nil {
83 return false, err
84 }
85 caller := std.OriginCaller()
86 if caller != owner {
87 return false, ErrCallerIsNotOwner
88 }
89 s.tokenURIs.Set(string(tid), string(tURI))
90 return true, nil
91}
92
93// IsApprovedForAll returns true if operator is approved for all by the owner.
94// Otherwise, returns false
95func (s *basicNFT) IsApprovedForAll(owner, operator std.Address) bool {
96 key := owner.String() + ":" + operator.String()
97 approved, found := s.operatorApprovals.Get(key)
98 if !found {
99 return false
100 }
101
102 return approved.(bool)
103}
104
105// Approve approves the input address for particular token
106func (s *basicNFT) Approve(to std.Address, tid TokenID) error {
107 if err := isValidAddress(to); err != nil {
108 return err
109 }
110
111 owner, err := s.OwnerOf(tid)
112 if err != nil {
113 return err
114 }
115 if owner == to {
116 return ErrApprovalToCurrentOwner
117 }
118
119 caller := std.OriginCaller()
120 if caller != owner && !s.IsApprovedForAll(owner, caller) {
121 return ErrCallerIsNotOwnerOrApproved
122 }
123
124 s.tokenApprovals.Set(string(tid), to.String())
125 std.Emit(
126 ApprovalEvent,
127 "owner", string(owner),
128 "to", string(to),
129 "tokenId", string(tid),
130 )
131
132 return nil
133}
134
135// GetApproved return the approved address for token
136func (s *basicNFT) GetApproved(tid TokenID) (std.Address, error) {
137 addr, found := s.tokenApprovals.Get(string(tid))
138 if !found {
139 return zeroAddress, ErrTokenIdNotHasApproved
140 }
141
142 return std.Address(addr.(string)), nil
143}
144
145// SetApprovalForAll can approve the operator to operate on all tokens
146func (s *basicNFT) SetApprovalForAll(operator std.Address, approved bool) error {
147 if err := isValidAddress(operator); err != nil {
148 return ErrInvalidAddress
149 }
150
151 caller := std.OriginCaller()
152 return s.setApprovalForAll(caller, operator, approved)
153}
154
155// Safely transfers `tokenId` token from `from` to `to`, checking that
156// contract recipients are aware of the GRC721 protocol to prevent
157// tokens from being forever locked.
158func (s *basicNFT) SafeTransferFrom(from, to std.Address, tid TokenID) error {
159 caller := std.OriginCaller()
160 if !s.isApprovedOrOwner(caller, tid) {
161 return ErrCallerIsNotOwnerOrApproved
162 }
163
164 err := s.transfer(from, to, tid)
165 if err != nil {
166 return err
167 }
168
169 if !s.checkOnGRC721Received(from, to, tid) {
170 return ErrTransferToNonGRC721Receiver
171 }
172
173 return nil
174}
175
176// Transfers `tokenId` token from `from` to `to`.
177func (s *basicNFT) TransferFrom(from, to std.Address, tid TokenID) error {
178 caller := std.OriginCaller()
179 if !s.isApprovedOrOwner(caller, tid) {
180 return ErrCallerIsNotOwnerOrApproved
181 }
182
183 err := s.transfer(from, to, tid)
184 if err != nil {
185 return err
186 }
187
188 return nil
189}
190
191// Mints `tokenId` and transfers it to `to`.
192func (s *basicNFT) Mint(to std.Address, tid TokenID) error {
193 return s.mint(to, tid)
194}
195
196// Mints `tokenId` and transfers it to `to`. Also checks that
197// contract recipients are using GRC721 protocol
198func (s *basicNFT) SafeMint(to std.Address, tid TokenID) error {
199 err := s.mint(to, tid)
200 if err != nil {
201 return err
202 }
203
204 if !s.checkOnGRC721Received(zeroAddress, to, tid) {
205 return ErrTransferToNonGRC721Receiver
206 }
207
208 return nil
209}
210
211func (s *basicNFT) Burn(tid TokenID) error {
212 owner, err := s.OwnerOf(tid)
213 if err != nil {
214 return err
215 }
216
217 s.beforeTokenTransfer(owner, zeroAddress, tid, 1)
218
219 s.tokenApprovals.Remove(string(tid))
220 balance, err := s.BalanceOf(owner)
221 if err != nil {
222 return err
223 }
224 balance = overflow.Sub64p(balance, 1)
225 s.balances.Set(owner.String(), balance)
226 s.owners.Remove(string(tid))
227
228 std.Emit(
229 BurnEvent,
230 "from", string(owner),
231 "tokenId", string(tid),
232 )
233
234 s.afterTokenTransfer(owner, zeroAddress, tid, 1)
235
236 return nil
237}
238
239/* Helper methods */
240
241// Helper for SetApprovalForAll()
242func (s *basicNFT) setApprovalForAll(owner, operator std.Address, approved bool) error {
243 if owner == operator {
244 return ErrApprovalToCurrentOwner
245 }
246
247 key := owner.String() + ":" + operator.String()
248 s.operatorApprovals.Set(key, approved)
249
250 std.Emit(
251 ApprovalForAllEvent,
252 "owner", string(owner),
253 "to", string(operator),
254 "approved", strconv.FormatBool(approved),
255 )
256
257 return nil
258}
259
260// Helper for TransferFrom() and SafeTransferFrom()
261func (s *basicNFT) transfer(from, to std.Address, tid TokenID) error {
262 if err := isValidAddress(from); err != nil {
263 return ErrInvalidAddress
264 }
265 if err := isValidAddress(to); err != nil {
266 return ErrInvalidAddress
267 }
268
269 if from == to {
270 return ErrCannotTransferToSelf
271 }
272
273 owner, err := s.OwnerOf(tid)
274 if err != nil {
275 return err
276 }
277 if owner != from {
278 return ErrTransferFromIncorrectOwner
279 }
280
281 s.beforeTokenTransfer(from, to, tid, 1)
282
283 // Check that tokenId was not transferred by `beforeTokenTransfer`
284 owner, err = s.OwnerOf(tid)
285 if err != nil {
286 return err
287 }
288 if owner != from {
289 return ErrTransferFromIncorrectOwner
290 }
291
292 s.tokenApprovals.Remove(string(tid))
293 fromBalance, err := s.BalanceOf(from)
294 if err != nil {
295 return err
296 }
297 toBalance, err := s.BalanceOf(to)
298 if err != nil {
299 return err
300 }
301 fromBalance = overflow.Sub64p(fromBalance, 1)
302 toBalance = overflow.Add64p(toBalance, 1)
303 s.balances.Set(from.String(), fromBalance)
304 s.balances.Set(to.String(), toBalance)
305 s.owners.Set(string(tid), to)
306
307 std.Emit(
308 TransferEvent,
309 "from", string(from),
310 "to", string(to),
311 "tokenId", string(tid),
312 )
313
314 s.afterTokenTransfer(from, to, tid, 1)
315
316 return nil
317}
318
319// Helper for Mint() and SafeMint()
320func (s *basicNFT) mint(to std.Address, tid TokenID) error {
321 if err := isValidAddress(to); err != nil {
322 return err
323 }
324
325 if s.exists(tid) {
326 return ErrTokenIdAlreadyExists
327 }
328
329 s.beforeTokenTransfer(zeroAddress, to, tid, 1)
330
331 // Check that tokenId was not minted by `beforeTokenTransfer`
332 if s.exists(tid) {
333 return ErrTokenIdAlreadyExists
334 }
335
336 toBalance, err := s.BalanceOf(to)
337 if err != nil {
338 return err
339 }
340 toBalance = overflow.Add64p(toBalance, 1)
341 s.balances.Set(to.String(), toBalance)
342 s.owners.Set(string(tid), to)
343
344 std.Emit(
345 MintEvent,
346 "to", string(to),
347 "tokenId", string(tid),
348 )
349
350 s.afterTokenTransfer(zeroAddress, to, tid, 1)
351
352 return nil
353}
354
355func (s *basicNFT) isApprovedOrOwner(addr std.Address, tid TokenID) bool {
356 owner, found := s.owners.Get(string(tid))
357 if !found {
358 return false
359 }
360
361 if addr == owner.(std.Address) || s.IsApprovedForAll(owner.(std.Address), addr) {
362 return true
363 }
364
365 approved, err := s.GetApproved(tid)
366 if err != nil {
367 return false
368 }
369
370 return approved == addr
371}
372
373// Checks if token id already exists
374func (s *basicNFT) exists(tid TokenID) bool {
375 _, found := s.owners.Get(string(tid))
376 return found
377}
378
379func (s *basicNFT) beforeTokenTransfer(from, to std.Address, firstTokenId TokenID, batchSize int64) {
380 // TODO: Implementation
381}
382
383func (s *basicNFT) afterTokenTransfer(from, to std.Address, firstTokenId TokenID, batchSize int64) {
384 // TODO: Implementation
385}
386
387func (s *basicNFT) checkOnGRC721Received(from, to std.Address, tid TokenID) bool {
388 // TODO: Implementation
389 return true
390}
391
392func (s *basicNFT) RenderHome() (str string) {
393 str += ufmt.Sprintf("# %s ($%s)\n\n", s.name, s.symbol)
394 str += ufmt.Sprintf("* **Total supply**: %d\n", s.TokenCount())
395 str += ufmt.Sprintf("* **Known accounts**: %d\n", s.balances.Size())
396
397 return
398}
399
400func (n *basicNFT) Getter() NFTGetter {
401 return func() IGRC721 {
402 return n
403 }
404}