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

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)) }