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

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