redeem.gno
3.33 Kb · 118 lines
1package kourtv3
2import (
3 "chain"
4 "chain/banker"
5 "chain/runtime/unsafe"
6 "strconv"
7 "gno.land/p/g1leu8d2vsplhehcfkjg50mwgdpxdkt8tztu95wr/grc20votes/v0"
8)
9const (
10 burnBpsInit = int64(1000)
11 minBurnBps = int64(500)
12 maxBurnBps = int64(5000)
13 bpsDenom = grc20votes.Bps
14 coinUnit = int64(1_000_000)
15 boughtEvent = "Bought"
16 redeemedEvent = "Redeemed"
17)
18var burnBps = burnBpsInit
19var totalReserve int64
20func BurnBps() int64 { return burnBps }
21func SetBurnBps(cur realm, bps int64) {
22 if !cur.IsCurrent() {
23 panic(errStaleRealm)
24 }
25 d := ensureGlobalDAO()
26 if cur.Previous().Address() != d.admin {
27 panic("kourtv3: only the global DAO admin sets the burn share")
28 }
29 mustSaneBurnBps(bps)
30 old := burnBps
31 burnBps = bps
32 chain.Emit(globalActEvent,
33 "court", "*",
34 "claim", "0",
35 "act", "set-burn-bps:"+strconv.FormatInt(old, 10)+"->"+strconv.FormatInt(bps, 10),
36 "by", cur.Previous().Address().String(),
37 "height", eventHeight(),
38 )
39}
40func mustSaneBurnBps(bps int64) {
41 if bps < minBurnBps || bps > maxBurnBps {
42 panic("kourtv3: the burn share must be between " + strconv.FormatInt(minBurnBps, 10) +
43 " and " + strconv.FormatInt(maxBurnBps, 10) + " bps")
44 }
45}
46func splitPayment(c *Court, spent int64) (burned, reserved int64) {
47 if c.oneWay {
48 return spent, 0
49 }
50 reserved = mulDiv128(spent, bpsDenom-burnBps, bpsDenom)
51 return spent - reserved, reserved
52}
53func redeemQuote(c *Court, amount int64) (payout, supply int64) {
54 supply = c.coin.TotalSupply()
55 if c.oneWay || amount <= 0 || c.reserve <= 0 || supply <= 0 || amount > supply {
56 return 0, supply
57 }
58 return mulDiv128(c.reserve, amount, supply), supply
59}
60func mustReturnAMicroGNOT(c *Court, payout, supply int64) {
61 if payout > 0 {
62 return
63 }
64 least := supply / c.reserve
65 if supply%c.reserve != 0 {
66 least++
67 }
68 panic("kourtv3: too small to return a µGNOT — redeem at least " + ccAmt(least) + " CC")
69}
70func Redeem(cur realm, courtSlug string, amount int64) int64 {
71 if !cur.IsCurrent() {
72 panic(errStaleRealm)
73 }
74 if amount <= 0 {
75 panic("kourtv3: redeem amount must be positive")
76 }
77 if unsafe.OriginSend().AmountOf(gnotDenom) > 0 {
78 panic("kourtv3: Redeem takes no GNOT")
79 }
80 who := cur.Previous().Address()
81 c := mustCourt(courtSlug)
82 if c.oneWay {
83 panic("kourtv3: this court's coin is one-way; nothing returns GNOT")
84 }
85 if who == c.escrow {
86 panic("kourtv3: the court escrow cannot redeem")
87 }
88 touch(c)
89 if c.reserve <= 0 {
90 panic("kourtv3: this court holds no reserve")
91 }
92 mustSpendable(c, who, amount)
93 payout, supply := redeemQuote(c, amount)
94 mustReturnAMicroGNOT(c, payout, supply)
95 c.coin.Burn(who, amount)
96 if payout > c.reserve {
97 panic("kourtv3: reserve accounting")
98 }
99 c.reserve -= payout
100 totalReserve -= payout
101 c.redeemedGNOT = mustAdd(c.redeemedGNOT, payout)
102 b := banker.NewBanker(banker.BankerTypeRealmSend, cur)
103 b.SendCoins(cur.Address(), who, chain.Coins{chain.NewCoin(gnotDenom, payout)})
104 chain.Emit(redeemedEvent,
105 "court", c.id,
106 "who", who.String(),
107 "amount", strconv.FormatInt(amount, 10),
108 "ugnot", strconv.FormatInt(payout, 10),
109 )
110 return payout
111}
112func ReserveGNOT(courtSlug string) int64 { return mustCourt(courtSlug).reserve }
113func TotalReserveGNOT() int64 { return totalReserve }
114func RedeemedGNOT(courtSlug string) int64 { return mustCourt(courtSlug).redeemedGNOT }
115func RedeemValue(courtSlug string, amount int64) int64 {
116 p, _ := redeemQuote(mustCourt(courtSlug), amount)
117 return p
118}