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}