Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

buy.gno

3.19 Kb · 119 lines
  1package skins
  2
  3import (
  4	"chain"
  5	"chain/banker"
  6
  7	// Only for OriginSend, in Buy. Caller identity always comes from
  8	// cur.Previous(), never from this package.
  9	"chain/runtime/unsafe"
 10
 11	"gno.land/p/nt/grc721/v0"
 12)
 13
 14// Buy buys one token of a skin design for the caller and returns its id. The
 15// exact price must be attached to the transaction (-send "<price>ugnot") and
 16// is forwarded to the treasury of the game realm before the call returns.
 17//
 18// An address owns at most one token of a design: a skin cannot be given
 19// away, so a second copy would be of no use. It must be called directly by a
 20// user (maketx call): a call relayed by another realm or by a maketx run
 21// script is refused.
 22func Buy(cur realm, skinID string) int64 {
 23	buyer := callerOf(0, cur)
 24
 25	// Paid-call guard: two checks that are only correct together.
 26	//
 27	// (a) The caller is a plain user account. Only then is the -send
 28	//     envelope guaranteed to have landed at this realm's address.
 29	if !cur.Previous().IsUserCall() {
 30		panic(errNotUserCall)
 31	}
 32	d := getDesign(skinID)
 33	if d == nil {
 34		panic(errUnknownSkin)
 35	}
 36	if !d.listed {
 37		panic(errSkinUnlisted)
 38	}
 39	if d.maxSupply > 0 && d.minted >= d.maxSupply {
 40		panic(errSoldOut)
 41	}
 42	key := ownedKey(buyer, skinID)
 43	if owned.Has(key) {
 44		panic(errAlreadyOwned)
 45	}
 46
 47	// (b) The envelope is exactly the price: one coin, the right denom, the
 48	//     right amount.
 49	sent := unsafe.OriginSend()
 50	if len(sent) != 1 || sent[0].Denom != payDenom || sent[0].Amount != d.price {
 51		panic("gnofly: wrong payment, send exactly " + itoa(d.price) + payDenom)
 52	}
 53	treasury := roles.Treasury()
 54	payment := chain.NewCoins(chain.NewCoin(payDenom, d.price))
 55	banker.NewBanker(banker.BankerTypeOriginSend, cur).SendCoins(cur.Address(), treasury, payment)
 56
 57	id := nextID
 58	nextID++
 59	tid := grc721.TokenID(itoa(id))
 60	if err := ledger.Mint(buyer, tid); err != nil {
 61		panic(err)
 62	}
 63	tokenSkin.Set(string(tid), skinID)
 64	owned.Set(key, id)
 65	d.minted++
 66
 67	chain.Emit("SkinBought",
 68		"id", itoa(id),
 69		"skin", skinID,
 70		"buyer", buyer.String(),
 71		"price", itoa(d.price),
 72		"treasury", treasury.String(),
 73	)
 74	return id
 75}
 76
 77// Burn destroys a skin token. Only its owner can. The storage deposit paid at
 78// purchase comes back; the price does not. The owner can buy the design
 79// again, while it is listed.
 80func Burn(cur realm, id int64) {
 81	caller := callerOf(0, cur)
 82	skinID := skinOf(id)
 83	if skinID == "" {
 84		panic(errNotMinted)
 85	}
 86	if ownerOf(id) != caller {
 87		panic(errNotSkinOwner)
 88	}
 89	tid := grc721.TokenID(itoa(id))
 90	if err := ledger.Burn(tid); err != nil {
 91		panic(err)
 92	}
 93	tokenSkin.Remove(string(tid))
 94	owned.Remove(ownedKey(caller, skinID))
 95	chain.Emit("SkinBurned", "id", itoa(id), "skin", skinID, "owner", caller.String())
 96}
 97
 98// ownedKey is the key of owned: the address, then the design.
 99func ownedKey(addr address, skinID string) string {
100	return addr.String() + "/" + skinID
101}
102
103// skinOf returns the design of token id, "" when there is no such token.
104func skinOf(id int64) string {
105	v := tokenSkin.Get(itoa(id))
106	if v == nil {
107		return ""
108	}
109	return v.(string)
110}
111
112// ownerOf returns who owns token id, "" for none.
113func ownerOf(id int64) address {
114	owner, err := token.OwnerOf(grc721.TokenID(itoa(id)))
115	if err != nil {
116		return ""
117	}
118	return owner
119}