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

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}