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

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

market.gno

6.93 Kb · 232 lines
  1package gnofly
  2
  3import (
  4	"chain"
  5	"chain/banker"
  6	"strconv"
  7
  8	// Only for OriginSend, in Buy. Caller identity always comes from cur,
  9	// never from this package.
 10	"chain/runtime/unsafe"
 11
 12	"gno.land/p/nt/bptree/v0"
 13	"gno.land/p/nt/grc721/v0"
 14
 15	"gno.land/r/g1t2kg2vtr3fukg43eujkn6x53gfdyakhngt4sfd/gnofly/nfts/planes"
 16)
 17
 18const (
 19	payDenom = "ugnot"
 20
 21	// minPrice keeps the royalty above zero after rounding: 5% of 1 GNOT.
 22	minPrice = int64(1_000_000)
 23	// maxPrice keeps price * royalty bps far below an int64.
 24	maxPrice = int64(1_000_000_000_000)
 25
 26	supply = 777 // plane ids run from 1 to 777
 27)
 28
 29// listing is one plane for sale. The plane itself is held by this realm
 30// (escrow) until it is bought or the listing is cancelled.
 31type listing struct {
 32	seller address
 33	price  int64 // ugnot
 34}
 35
 36var (
 37	// listings by plane id, as a three-digit key ("042") so that iteration
 38	// runs in id order.
 39	listings bptree.BPTree
 40	// paused stops new listings and price changes, for a move to a newer
 41	// version of the market. Cancel and Buy keep working: nothing listed here
 42	// can be stuck.
 43	paused bool
 44	// self is this realm's address, recorded at init: what sellers approve
 45	// on the planes realm.
 46	self address
 47)
 48
 49func init(cur realm) {
 50	self = cur.Address()
 51}
 52
 53// List puts plane id up for sale at priceUgnot. The caller must own it and
 54// have approved this realm on the planes realm (Approve for this plane, or
 55// SetApprovalForAll): the plane moves here, in escrow, until it is bought or
 56// the listing is cancelled.
 57func List(cur realm, id int, priceUgnot int64) {
 58	seller := userOf(0, cur)
 59	if paused {
 60		panic(errPaused)
 61	}
 62	checkPrice(priceUgnot)
 63	if listings.Has(key(id)) {
 64		panic(errListed)
 65	}
 66	if owner := ownerOf(id); owner != seller {
 67		panic(errNotOwner)
 68	}
 69	if !planes.IsApprovedForAll(seller, self) && planes.GetApproved(int64(id)) != self {
 70		panic(errNotApproved)
 71	}
 72	listings.Set(key(id), &listing{seller: seller, price: priceUgnot})
 73	planes.TransferFrom(cross(cur), seller, self, int64(id))
 74	chain.Emit("List", "id", strconv.Itoa(id), "seller", seller.String(), "price", strconv.FormatInt(priceUgnot, 10))
 75}
 76
 77// UpdatePrice changes the price of a plane the caller listed.
 78func UpdatePrice(cur realm, id int, priceUgnot int64) {
 79	seller := userOf(0, cur)
 80	if paused {
 81		panic(errPaused)
 82	}
 83	checkPrice(priceUgnot)
 84	l := mustListing(id)
 85	if l.seller != seller {
 86		panic(errNotSeller)
 87	}
 88	l.price = priceUgnot
 89	chain.Emit("UpdatePrice", "id", strconv.Itoa(id), "seller", seller.String(), "price", strconv.FormatInt(priceUgnot, 10))
 90}
 91
 92// Cancel takes a listing down and gives the plane back to its seller. It
 93// works while the market is paused.
 94func Cancel(cur realm, id int) {
 95	seller := userOf(0, cur)
 96	l := mustListing(id)
 97	if l.seller != seller {
 98		panic(errNotSeller)
 99	}
100	listings.Remove(key(id))
101	planes.TransferFrom(cross(cur), cur.Address(), seller, int64(id))
102	chain.Emit("Cancel", "id", strconv.Itoa(id), "seller", seller.String())
103}
104
105// Buy buys a listed plane: send exactly its price in ugnot. The royalty of
106// the planes realm (RoyaltyInfo: 5% to the gnofly treasury) is paid out of
107// the price, the seller receives the rest, and the plane goes to the buyer.
108// It works while the market is paused.
109func Buy(cur realm, id int) {
110	// A direct user call: only then has the -send envelope landed at this
111	// realm's address, which the OriginSend banker below pays out of.
112	buyer := userOf(0, cur)
113	l := mustListing(id)
114	if l.seller == buyer {
115		panic(errOwnListing)
116	}
117	sent := unsafe.OriginSend()
118	if len(sent) != 1 || sent[0].Denom != payDenom || sent[0].Amount != l.price {
119		panic("gnofly market: wrong payment, send exactly " + strconv.FormatInt(l.price, 10) + payDenom)
120	}
121	// The listing goes before any coin or plane moves.
122	listings.Remove(key(id))
123
124	receiver, royalty := planes.RoyaltyInfo(int64(id), l.price)
125	if royalty < 0 || royalty > l.price {
126		royalty = 0
127	}
128	bank := banker.NewBanker(banker.BankerTypeOriginSend, cur)
129	if royalty > 0 && receiver.IsValid() {
130		bank.SendCoins(cur.Address(), receiver, chain.NewCoins(chain.NewCoin(payDenom, royalty)))
131	} else {
132		royalty = 0
133	}
134	bank.SendCoins(cur.Address(), l.seller, chain.NewCoins(chain.NewCoin(payDenom, l.price-royalty)))
135
136	planes.TransferFrom(cross(cur), cur.Address(), buyer, int64(id))
137	chain.Emit("Buy", "id", strconv.Itoa(id), "seller", l.seller.String(), "buyer", buyer.String(),
138		"price", strconv.FormatInt(l.price, 10), "royalty", strconv.FormatInt(royalty, 10))
139}
140
141// Transfer gives a plane the caller owns, not listed, to another address,
142// through this realm: the call Bazaar's site makes. It needs the same
143// approval as List. A plane can always be moved on the planes realm itself.
144func Transfer(cur realm, to address, id int) {
145	from := userOf(0, cur)
146	if !to.IsValid() {
147		panic(errBadRecipient)
148	}
149	if listings.Has(key(id)) {
150		panic(errListed)
151	}
152	if owner := ownerOf(id); owner != from {
153		panic(errNotOwner)
154	}
155	planes.TransferFrom(cross(cur), from, to, int64(id))
156	chain.Emit("Transfer", "id", strconv.Itoa(id), "from", from.String(), "to", to.String())
157}
158
159// SetPaused stops (or resumes) new listings and price changes. Admin of the
160// game realm only. Cancel and Buy keep working, so a move to a newer market
161// never strands a plane: sellers cancel here and list there.
162func SetPaused(cur realm, p bool) {
163	assertAdmin(0, cur)
164	paused = p
165	chain.Emit("Paused", "paused", strconv.FormatBool(p))
166}
167
168// ReturnStray gives back a plane this realm holds outside any listing: one
169// someone sent here by mistake. Admin of the game realm only; to is who it
170// came from, as the chain's transfer events show.
171func ReturnStray(cur realm, id int, to address) {
172	assertAdmin(0, cur)
173	if !to.IsValid() {
174		panic(errBadRecipient)
175	}
176	if listings.Has(key(id)) || ownerOf(id) != cur.Address() {
177		panic(errNotStray)
178	}
179	planes.TransferFrom(cross(cur), cur.Address(), to, int64(id))
180	chain.Emit("ReturnStray", "id", strconv.Itoa(id), "to", to.String())
181}
182
183// Paused reports whether new listings are stopped.
184func Paused() bool { return paused }
185
186// checkPrice panics unless p is a price this market takes.
187func checkPrice(p int64) {
188	if p < minPrice || p > maxPrice {
189		panic(errBadPrice)
190	}
191}
192
193// mustListing returns the listing of plane id, or panics.
194func mustListing(id int) *listing {
195	v := listings.Get(key(id))
196	if v == nil {
197		panic(errNotListed)
198	}
199	return v.(*listing)
200}
201
202// getListing returns the listing of plane id, or nil.
203func getListing(id int) *listing {
204	if v := listings.Get(key(id)); v != nil {
205		return v.(*listing)
206	}
207	return nil
208}
209
210// ownerOf returns the owner of plane id on the planes realm, or panics for
211// an id that is not a minted plane.
212func ownerOf(id int) address {
213	if id < 1 || id > supply {
214		panic(errUnknownPlane)
215	}
216	owner, err := planes.OwnerOf(tid(id))
217	if err != nil {
218		panic(errUnknownPlane)
219	}
220	return owner
221}
222
223// key is the listings key of plane id: three digits, "042".
224func key(id int) string {
225	s := strconv.Itoa(id)
226	for len(s) < 3 {
227		s = "0" + s
228	}
229	return s
230}
231
232func tid(id int) grc721.TokenID { return grc721.TokenID(strconv.Itoa(id)) }