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.

ammmath.gno

6.88 Kb · 240 lines
  1// Package ammmath implements integer bonding-curve and constant-product AMM
  2// helpers for the gnomemepad factory (Pump-style virtual CPMM + real CPMM pool).
  3package ammmath
  4
  5import (
  6	"math/bits"
  7	"math/overflow"
  8)
  9
 10// FeeResult is the split of a gross trade fee.
 11type FeeResult struct {
 12	Gross     int64
 13	Net       int64
 14	Fee       int64
 15	Creator   int64
 16	Protocol  int64
 17	Remainder int64
 18}
 19
 20// maxInt64 is 2^63-1; MulDiv results must fit here.
 21const maxInt64 = int64(^uint64(0) >> 1)
 22
 23// MulDiv returns floor((x * y) / d) for x,y >= 0 and d > 0.
 24// Uses a 128-bit intermediate so mainnet-scale k (VU*VT ≈ 1.1e20) does not overflow.
 25// Panics on bad domain or if the quotient does not fit in int64.
 26func MulDiv(x, y, d int64) int64 {
 27	if x < 0 || y < 0 {
 28		panic("ammmath: MulDiv negative operand")
 29	}
 30	if d <= 0 {
 31		panic("ammmath: MulDiv non-positive divisor")
 32	}
 33	if x == 0 || y == 0 {
 34		return 0
 35	}
 36	if prod, ok := overflow.Mul64(x, y); ok {
 37		return prod / d
 38	}
 39	hi, lo := bits.Mul64(uint64(x), uint64(y))
 40	// bits.Div64 panics if d == 0 or uint64(d) <= hi (quotient ≥ 2^64).
 41	quo, _ := bits.Div64(hi, lo, uint64(d))
 42	if quo > uint64(maxInt64) {
 43		panic("ammmath: MulDiv result overflow")
 44	}
 45	return int64(quo)
 46}
 47
 48// ApplyFee takes gross quote paid by the user and feeBPS (e.g. 120 = 1.20%).
 49func ApplyFee(gross, feeBPS, creatorShareBPS, protocolShareBPS int64) FeeResult {
 50	if gross <= 0 {
 51		panic("ammmath: gross must be positive")
 52	}
 53	if feeBPS < 0 || feeBPS >= 10000 {
 54		panic("ammmath: feeBPS out of range")
 55	}
 56	if creatorShareBPS < 0 || protocolShareBPS < 0 || creatorShareBPS+protocolShareBPS > 10000 {
 57		panic("ammmath: fee share BPS invalid")
 58	}
 59	fee := gross * feeBPS / 10000
 60	net := gross - fee
 61	creator := fee * creatorShareBPS / 10000
 62	protocol := fee * protocolShareBPS / 10000
 63	remainder := fee - creator - protocol
 64	return FeeResult{
 65		Gross:     gross,
 66		Net:       net,
 67		Fee:       fee,
 68		Creator:   creator,
 69		Protocol:  protocol,
 70		Remainder: remainder,
 71	}
 72}
 73
 74// ApplyFeeOnOutput charges fee on assets leaving the pool/curve.
 75func ApplyFeeOnOutput(grossOut, feeBPS, creatorShareBPS, protocolShareBPS int64) FeeResult {
 76	if grossOut <= 0 {
 77		panic("ammmath: grossOut must be positive")
 78	}
 79	if feeBPS < 0 || feeBPS >= 10000 {
 80		panic("ammmath: feeBPS out of range")
 81	}
 82	if creatorShareBPS < 0 || protocolShareBPS < 0 || creatorShareBPS+protocolShareBPS > 10000 {
 83		panic("ammmath: fee share BPS invalid")
 84	}
 85	fee := grossOut * feeBPS / 10000
 86	net := grossOut - fee
 87	if net <= 0 {
 88		panic("ammmath: fee consumes entire output")
 89	}
 90	creator := fee * creatorShareBPS / 10000
 91	protocol := fee * protocolShareBPS / 10000
 92	remainder := fee - creator - protocol
 93	return FeeResult{
 94		Gross:     grossOut,
 95		Net:       net,
 96		Fee:       fee,
 97		Creator:   creator,
 98		Protocol:  protocol,
 99		Remainder: remainder,
100	}
101}
102
103// BuyTokens quotes a virtual constant-product buy (net ugnot in).
104func BuyTokens(virtualUgnot, virtualToken, ugnotIn int64) (tokensOut, newVU, newVT int64) {
105	if ugnotIn <= 0 {
106		panic("ammmath: ugnotIn must be positive")
107	}
108	if virtualUgnot <= 0 || virtualToken <= 0 {
109		panic("ammmath: invalid virtual reserves")
110	}
111	newVU, ok := overflow.Add64(virtualUgnot, ugnotIn)
112	if !ok {
113		panic("ammmath: virtual ugnot overflow")
114	}
115	// newVT = floor((VU*VT)/newVU) without materializing k in int64
116	newVT = MulDiv(virtualUgnot, virtualToken, newVU)
117	if newVT <= 0 {
118		panic("ammmath: empty virtual token reserve")
119	}
120	if newVT >= virtualToken {
121		panic("ammmath: zero tokens out")
122	}
123	tokensOut = virtualToken - newVT
124	return tokensOut, newVU, newVT
125}
126
127// MaxNetInForTokenOut is the largest net ugnot in that yields tokensOut ≤ maxTokensOut
128// (integer CPMM, same floor rules as BuyTokens). Used to fill the last curve tokens
129// without panicking when the user sends too much GNOT.
130func MaxNetInForTokenOut(virtualUgnot, virtualToken, maxTokensOut int64) int64 {
131	if maxTokensOut <= 0 || virtualUgnot <= 0 || virtualToken <= 0 {
132		return 0
133	}
134	if maxTokensOut >= virtualToken {
135		return 0
136	}
137	targetNewVT := virtualToken - maxTokensOut
138	// maxNewVU = floor((VU*VT)/targetNewVT)
139	maxNewVU := MulDiv(virtualUgnot, virtualToken, targetNewVT)
140	if maxNewVU <= virtualUgnot {
141		return 0
142	}
143	return maxNewVU - virtualUgnot
144}
145
146// SellTokens quotes a virtual constant-product sell (gross ugnot out).
147func SellTokens(virtualUgnot, virtualToken, tokensIn int64) (ugnotOut, newVU, newVT int64) {
148	if tokensIn <= 0 {
149		panic("ammmath: tokensIn must be positive")
150	}
151	if virtualUgnot <= 0 || virtualToken <= 0 {
152		panic("ammmath: invalid virtual reserves")
153	}
154	newVT, ok := overflow.Add64(virtualToken, tokensIn)
155	if !ok {
156		panic("ammmath: virtual token overflow")
157	}
158	newVU = MulDiv(virtualUgnot, virtualToken, newVT)
159	if newVU <= 0 {
160		panic("ammmath: empty virtual ugnot reserve")
161	}
162	if newVU >= virtualUgnot {
163		panic("ammmath: zero ugnot out")
164	}
165	ugnotOut = virtualUgnot - newVU
166	return ugnotOut, newVU, newVT
167}
168
169// PoolSwapUgnotForToken swaps net ugnot for tokens; remainderToPool adds to ugnot reserve.
170func PoolSwapUgnotForToken(poolUgnot, poolToken, ugnotIn, remainderToPool int64) (tokensOut, newPU, newPT int64) {
171	if ugnotIn <= 0 {
172		panic("ammmath: ugnotIn must be positive")
173	}
174	if poolUgnot <= 0 || poolToken <= 0 {
175		panic("ammmath: invalid pool reserves")
176	}
177	if remainderToPool < 0 {
178		panic("ammmath: negative remainder")
179	}
180	addU, ok := overflow.Add64(ugnotIn, remainderToPool)
181	if !ok {
182		panic("ammmath: add overflow")
183	}
184	newPU, ok = overflow.Add64(poolUgnot, addU)
185	if !ok {
186		panic("ammmath: pool ugnot overflow")
187	}
188	den, ok := overflow.Add64(poolUgnot, ugnotIn)
189	if !ok {
190		panic("ammmath: pool denom overflow")
191	}
192	tokensOut = MulDiv(poolToken, ugnotIn, den)
193	if tokensOut <= 0 {
194		panic("ammmath: zero tokens out of pool")
195	}
196	if tokensOut >= poolToken {
197		panic("ammmath: would drain pool tokens")
198	}
199	newPT = poolToken - tokensOut
200	return tokensOut, newPU, newPT
201}
202
203// PoolSwapTokenForUgnot swaps tokens for gross ugnot out.
204func PoolSwapTokenForUgnot(poolUgnot, poolToken, tokensIn int64) (ugnotOut, newPU, newPT int64) {
205	if tokensIn <= 0 {
206		panic("ammmath: tokensIn must be positive")
207	}
208	if poolUgnot <= 0 || poolToken <= 0 {
209		panic("ammmath: invalid pool reserves")
210	}
211	newPT, ok := overflow.Add64(poolToken, tokensIn)
212	if !ok {
213		panic("ammmath: pool token overflow")
214	}
215	ugnotOut = MulDiv(poolUgnot, tokensIn, newPT)
216	if ugnotOut <= 0 {
217		panic("ammmath: zero ugnot out of pool")
218	}
219	if ugnotOut >= poolUgnot {
220		panic("ammmath: would drain pool ugnot")
221	}
222	newPU = poolUgnot - ugnotOut
223	return ugnotOut, newPU, newPT
224}
225
226// CanGraduate reports whether raised net ugnot meets the threshold.
227func CanGraduate(raisedUgnot, threshold int64) bool {
228	return raisedUgnot >= threshold && threshold > 0
229}
230
231// SpotPriceUgnotPerToken returns ugnot per token scaled by 1e6 (display only).
232func SpotPriceUgnotPerToken(ugnotReserve, tokenReserve int64) int64 {
233	if tokenReserve <= 0 {
234		return 0
235	}
236	if ugnotReserve <= 0 {
237		return 0
238	}
239	return MulDiv(ugnotReserve, 1_000_000, tokenReserve)
240}