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.

ticket.gno

5.28 Kb · 206 lines
  1package arena
  2
  3import (
  4	"chain"
  5	"math/overflow"
  6	"strconv"
  7)
  8
  9// QuoteBuy is the exact ugnot a buyer must attach to mint amount tickets.
 10// due = curve + creator fee + protocol fee + referral fee.
 11// curve is what stays in the reserve. Fees are paid on top and forwarded.
 12func QuoteBuy(handle string, amount int) (due, curve, creatorFee, protocolFee, referralFee int64) {
 13	p := lookup(handle)
 14	if p == nil {
 15		panic("unknown handle")
 16	}
 17	curve = curveCost(bookOf(p.Addr).Supply, amount)
 18	creatorFee, protocolFee, referralFee = splitFees(curve, p.Referrer.IsValid())
 19	due = overflow.Add64p(curve, overflow.Add64p(creatorFee, overflow.Add64p(protocolFee, referralFee)))
 20	return
 21}
 22
 23// QuoteSell is what a seller receives, and the fees taken out of the curve refund.
 24// The reserve drops by the full curve amount, so later sells stay solvent.
 25func QuoteSell(handle string, amount int) (proceeds, curve, creatorFee, protocolFee, referralFee int64) {
 26	p := lookup(handle)
 27	if p == nil {
 28		panic("unknown handle")
 29	}
 30	b := bookOf(p.Addr)
 31	if amount < 1 || amount > MaxTrade {
 32		panic("amount")
 33	}
 34	if amount > b.Supply {
 35		panic("not enough tickets")
 36	}
 37	curve = curveCost(b.Supply-amount, amount)
 38	creatorFee, protocolFee, referralFee = splitFees(curve, p.Referrer.IsValid())
 39	proceeds = curve - creatorFee - protocolFee - referralFee
 40	if proceeds <= 0 {
 41		panic("proceeds")
 42	}
 43	return
 44}
 45
 46// Buy mints amount tickets of handle. Attach exactly QuoteBuy's due in ugnot.
 47func Buy(cur realm, handle string, amount int) {
 48	who := caller(cur)
 49	if state.paused {
 50		panic("paused")
 51	}
 52	mustMember(who)
 53	due, curve, creatorFee, protocolFee, referralFee := QuoteBuy(handle, amount)
 54	sent := takeUgnot(cur)
 55	if sent != due {
 56		panic("send exactly " + strconv.FormatInt(due, 10) + "ugnot")
 57	}
 58	p := lookup(handle)
 59	b := bookOf(p.Addr)
 60	b.Supply += amount
 61	b.Reserve = overflow.Add64p(b.Reserve, curve)
 62	state.totalReserve = overflow.Add64p(state.totalReserve, curve)
 63	addShares(who, p.Addr, amount)
 64	forwardFees(cur, p, creatorFee, protocolFee, referralFee)
 65	chain.Emit(
 66		"ArenaBuy",
 67		"buyer", who.String(),
 68		"creator", p.Addr.String(),
 69		"handle", p.Handle,
 70		"amount", strconv.Itoa(amount),
 71		"curve", strconv.FormatInt(curve, 10),
 72		"due", strconv.FormatInt(due, 10),
 73	)
 74}
 75
 76// Sell burns amount tickets and pays the caller from the reserve, minus fees.
 77// Sells stay available while the realm is paused.
 78func Sell(cur realm, handle string, amount int) {
 79	who := caller(cur)
 80	mustNoPay()
 81	mustMember(who)
 82	proceeds, curve, creatorFee, protocolFee, referralFee := QuoteSell(handle, amount)
 83	if getShares(who, lookup(handle).Addr) < amount {
 84		panic("not enough tickets")
 85	}
 86	p := lookup(handle)
 87	b := bookOf(p.Addr)
 88	if b.Reserve < curve || state.totalReserve < curve {
 89		panic("insolvent")
 90	}
 91	b.Supply -= amount
 92	b.Reserve = overflow.Sub64p(b.Reserve, curve)
 93	state.totalReserve = overflow.Sub64p(state.totalReserve, curve)
 94	subShares(who, p.Addr, amount)
 95	payout(cur, who, proceeds, "seller")
 96	forwardFees(cur, p, creatorFee, protocolFee, referralFee)
 97	chain.Emit(
 98		"ArenaSell",
 99		"seller", who.String(),
100		"creator", p.Addr.String(),
101		"handle", p.Handle,
102		"amount", strconv.Itoa(amount),
103		"curve", strconv.FormatInt(curve, 10),
104		"proceeds", strconv.FormatInt(proceeds, 10),
105	)
106}
107
108func forwardFees(cur realm, creator *Profile, creatorFee, protocolFee, referralFee int64) {
109	if protocolFee > 0 && !state.treasury.IsValid() {
110		panic("treasury not set")
111	}
112	payout(cur, creator.Addr, creatorFee, "creator")
113	payout(cur, state.treasury, protocolFee, "treasury")
114	if referralFee > 0 {
115		payout(cur, creator.Referrer, referralFee, "referrer")
116	}
117}
118
119func addShares(holder, creator address, n int) {
120	if n < 1 {
121		panic("amount")
122	}
123	had := getShares(holder, creator)
124	setShares(holder, creator, had+n)
125	if had == 0 {
126		bookOf(creator).Holders++
127	}
128}
129
130func subShares(holder, creator address, n int) {
131	had := getShares(holder, creator)
132	if n < 1 || had < n {
133		panic("not enough tickets")
134	}
135	setShares(holder, creator, had-n)
136	if had == n {
137		b := bookOf(creator)
138		if b.Holders > 0 {
139			b.Holders--
140		}
141	}
142}
143
144func splitFees(curve int64, referred bool) (creator, protocol, referral int64) {
145	creator = bps(curve, CreatorFeeBps)
146	protocol = bps(curve, ProtocolFeeBps)
147	if referred {
148		referral = bps(curve, ReferralFeeBps)
149	}
150	return
151}
152
153func bps(amount, points int64) int64 {
154	if amount == 0 || points == 0 {
155		return 0
156	}
157	if amount < 0 || points < 0 {
158		panic("fee")
159	}
160	return overflow.Mul64p(amount, points) / 10_000
161}
162
163// curveCost is the ugnot reserve needed to move `amount` tickets starting at supply.
164// Ticket i (1-based) is priced i² * TicketUnit. The sum of squares is exact.
165func curveCost(supply, amount int) int64 {
166	if amount < 1 || amount > MaxTrade {
167		panic("amount")
168	}
169	if supply < 0 {
170		panic("supply")
171	}
172	from := int64(supply)
173	to := from + int64(amount)
174	if to > int64(MaxSupply) {
175		panic("supply cap")
176	}
177	return overflow.Mul64p(sumSq(to)-sumSq(from), TicketUnit)
178}
179
180func sumSq(n int64) int64 {
181	if n <= 0 {
182		return 0
183	}
184	if n > int64(MaxSupply) {
185		panic("supply cap")
186	}
187	// n*(n+1)*(2n+1)/6, dividing 2 and 3 out before multiplying.
188	a := n
189	b := n + 1
190	if a%2 == 0 {
191		a /= 2
192	} else {
193		b /= 2
194	}
195	c := 2*n + 1
196	if a%3 == 0 {
197		a /= 3
198	} else if b%3 == 0 {
199		b /= 3
200	} else if c%3 == 0 {
201		c /= 3
202	} else {
203		panic("sumSq")
204	}
205	return overflow.Mul64p(overflow.Mul64p(a, b), c)
206}