payment.gno
3.34 Kb · 99 lines
1package market
2
3import (
4 "chain"
5 "chain/banker"
6 "strconv"
7
8 "gno.land/p/samcrew/launchpad/currency/v1"
9 sm "gno.land/p/samcrew/launchpad/safemath/v1"
10 "gno.land/r/nt/grc20reg/v0"
11 "gno.land/r/samcrew/launchpad/config/v1"
12 "gno.land/r/samcrew/launchpad/nft/v1"
13)
14
15// escrow is, per currency, the total of open offers: everything this realm
16// owes. Currencies are few (config allows sixteen), so a map is enough.
17var escrow = map[string]int64{}
18
19// terms is what this realm needs from config.
20type terms struct {
21 treasury address
22 feeBPS int64 // protocol share of each sale price
23}
24
25// policy returns the current terms after checking that the market lane is
26// open for the currency. It is a variable only so that unit tests can stand in
27// for the config realm, whose admin cannot be set from another package's tests.
28var policy = func(currencyKey string) terms {
29 config.AssertLaneOpen(config.LaneNFTMarket, currencyKey)
30 p := config.GetVersion(config.GetCurrentVersion())
31 return terms{treasury: p.Treasury, feeBPS: p.NFTSecondaryFeeBPS}
32}
33
34func balance(cur realm, key string) int64 {
35 if key == config.NativeCurrency {
36 return banker.NewReadonlyBanker().GetCoin(cur.Address(), key)
37 }
38 return grc20reg.MustGet(key).BalanceOf(cur.Address())
39}
40
41// surplus is what the realm holds beyond its open offers. Every entry point
42// must leave it exactly as it found it.
43func surplus(cur realm, key string) int64 { return sm.Sub(balance(cur, key), escrow[key]) }
44
45// take collects exactly amount from the direct caller, attached for native
46// coins and pulled for a token, and returns the surplus before the payment.
47func take(cur realm, key string, amount int64) int64 {
48 if key == config.NativeCurrency {
49 currency.CollectNative(0, cur, amount)
50 return sm.Sub(surplus(cur, key), amount)
51 }
52 before := surplus(cur, key)
53 currency.CollectToken(0, cur, grc20reg.MustGet(key), amount)
54 return before
55}
56
57// untouched is take for an entry point that receives nothing.
58func untouched(cur realm, key string) int64 {
59 currency.AssertNoCoins(0, cur)
60 return surplus(cur, key)
61}
62
63func pay(cur realm, key string, to address, amount int64) {
64 if amount == 0 {
65 return
66 }
67 if key == config.NativeCurrency {
68 currency.PayNative(0, cur, to, amount)
69 } else {
70 currency.PayToken(0, cur, grc20reg.MustGet(key), to, amount)
71 }
72}
73
74// settle splits a sale price: the protocol fee to the treasury, each royalty
75// to its receiver, the rest, rounding included, to the seller.
76func settle(cur realm, key, collection string, price, feeBPS int64, seller, treasury address) {
77 fee := sm.BPS(price, feeBPS)
78 rest := sm.Sub(price, fee)
79 pay(cur, key, treasury, fee)
80 var royalties int64
81 for _, r := range nft.RoyaltyQuote(collection, price) {
82 pay(cur, key, r.Account, r.Amount)
83 rest = sm.Sub(rest, r.Amount)
84 royalties += r.Amount
85 }
86 pay(cur, key, seller, rest)
87 chain.Emit("Settled", "collection", collection, "currency", key,
88 "price", strconv.FormatInt(price, 10), "seller", strconv.FormatInt(rest, 10),
89 "fee", strconv.FormatInt(fee, 10), "royalties", strconv.FormatInt(royalties, 10))
90}
91
92// settled aborts the transaction unless the realm holds exactly its open
93// offers plus the surplus it had before the call: nothing taken is left
94// behind, and nothing owed has been spent.
95func settled(cur realm, key string, before int64) {
96 if surplus(cur, key) != before {
97 panic("launchpad/market: payment not fully settled")
98 }
99}