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

ammmath.gno

6.13 Kb · 213 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 ammmathv2
  4
  5import "math/overflow"
  6
  7// FeeResult is the split of a gross trade fee.
  8type FeeResult struct {
  9	Gross     int64
 10	Net       int64
 11	Fee       int64
 12	Creator   int64
 13	Protocol  int64
 14	Remainder int64
 15}
 16
 17// ApplyFee takes gross quote paid by the user and feeBPS (e.g. 120 = 1.20%).
 18func ApplyFee(gross, feeBPS, creatorShareBPS, protocolShareBPS int64) FeeResult {
 19	if gross <= 0 {
 20		panic("ammmath: gross must be positive")
 21	}
 22	if feeBPS < 0 || feeBPS >= 10000 {
 23		panic("ammmath: feeBPS out of range")
 24	}
 25	if creatorShareBPS < 0 || protocolShareBPS < 0 || creatorShareBPS+protocolShareBPS > 10000 {
 26		panic("ammmath: fee share BPS invalid")
 27	}
 28	fee := gross * feeBPS / 10000
 29	net := gross - fee
 30	creator := fee * creatorShareBPS / 10000
 31	protocol := fee * protocolShareBPS / 10000
 32	remainder := fee - creator - protocol
 33	return FeeResult{
 34		Gross:     gross,
 35		Net:       net,
 36		Fee:       fee,
 37		Creator:   creator,
 38		Protocol:  protocol,
 39		Remainder: remainder,
 40	}
 41}
 42
 43// ApplyFeeOnOutput charges fee on assets leaving the pool/curve.
 44func ApplyFeeOnOutput(grossOut, feeBPS, creatorShareBPS, protocolShareBPS int64) FeeResult {
 45	if grossOut <= 0 {
 46		panic("ammmath: grossOut must be positive")
 47	}
 48	if feeBPS < 0 || feeBPS >= 10000 {
 49		panic("ammmath: feeBPS out of range")
 50	}
 51	if creatorShareBPS < 0 || protocolShareBPS < 0 || creatorShareBPS+protocolShareBPS > 10000 {
 52		panic("ammmath: fee share BPS invalid")
 53	}
 54	fee := grossOut * feeBPS / 10000
 55	net := grossOut - fee
 56	if net <= 0 {
 57		panic("ammmath: fee consumes entire output")
 58	}
 59	creator := fee * creatorShareBPS / 10000
 60	protocol := fee * protocolShareBPS / 10000
 61	remainder := fee - creator - protocol
 62	return FeeResult{
 63		Gross:     grossOut,
 64		Net:       net,
 65		Fee:       fee,
 66		Creator:   creator,
 67		Protocol:  protocol,
 68		Remainder: remainder,
 69	}
 70}
 71
 72// BuyTokens quotes a virtual constant-product buy (net ugnot in).
 73func BuyTokens(virtualUgnot, virtualToken, ugnotIn int64) (tokensOut, newVU, newVT int64) {
 74	if ugnotIn <= 0 {
 75		panic("ammmath: ugnotIn must be positive")
 76	}
 77	if virtualUgnot <= 0 || virtualToken <= 0 {
 78		panic("ammmath: invalid virtual reserves")
 79	}
 80	newVU, ok := overflow.Add64(virtualUgnot, ugnotIn)
 81	if !ok {
 82		panic("ammmath: virtual ugnot overflow")
 83	}
 84	k, ok := overflow.Mul64(virtualUgnot, virtualToken)
 85	if !ok {
 86		panic("ammmath: k overflow")
 87	}
 88	newVT = k / newVU
 89	if newVT <= 0 {
 90		panic("ammmath: empty virtual token reserve")
 91	}
 92	if newVT >= virtualToken {
 93		panic("ammmath: zero tokens out")
 94	}
 95	tokensOut = virtualToken - newVT
 96	return tokensOut, newVU, newVT
 97}
 98
 99// MaxNetInForTokenOut is the largest net ugnot in that yields tokensOut ≤ maxTokensOut
100// (integer CPMM, same floor rules as BuyTokens). Used to fill the last curve tokens
101// without panicking when the user sends too much GNOT.
102func MaxNetInForTokenOut(virtualUgnot, virtualToken, maxTokensOut int64) int64 {
103	if maxTokensOut <= 0 || virtualUgnot <= 0 || virtualToken <= 0 {
104		return 0
105	}
106	if maxTokensOut >= virtualToken {
107		return 0
108	}
109	targetNewVT := virtualToken - maxTokensOut
110	k, ok := overflow.Mul64(virtualUgnot, virtualToken)
111	if !ok {
112		panic("ammmath: k overflow")
113	}
114	// tokensOut ≤ max  ⇔  k/(vu+net) ≥ targetNewVT  ⇔  vu+net ≤ k/targetNewVT (floor)
115	maxNewVU := k / targetNewVT
116	if maxNewVU <= virtualUgnot {
117		return 0
118	}
119	return maxNewVU - virtualUgnot
120}
121
122// SellTokens quotes a virtual constant-product sell (gross ugnot out).
123func SellTokens(virtualUgnot, virtualToken, tokensIn int64) (ugnotOut, newVU, newVT int64) {
124	if tokensIn <= 0 {
125		panic("ammmath: tokensIn must be positive")
126	}
127	if virtualUgnot <= 0 || virtualToken <= 0 {
128		panic("ammmath: invalid virtual reserves")
129	}
130	newVT, ok := overflow.Add64(virtualToken, tokensIn)
131	if !ok {
132		panic("ammmath: virtual token overflow")
133	}
134	k, ok := overflow.Mul64(virtualUgnot, virtualToken)
135	if !ok {
136		panic("ammmath: k overflow")
137	}
138	newVU = k / newVT
139	if newVU <= 0 {
140		panic("ammmath: empty virtual ugnot reserve")
141	}
142	if newVU >= virtualUgnot {
143		panic("ammmath: zero ugnot out")
144	}
145	ugnotOut = virtualUgnot - newVU
146	return ugnotOut, newVU, newVT
147}
148
149// PoolSwapUgnotForToken swaps net ugnot for tokens; remainderToPool adds to ugnot reserve.
150func PoolSwapUgnotForToken(poolUgnot, poolToken, ugnotIn, remainderToPool int64) (tokensOut, newPU, newPT int64) {
151	if ugnotIn <= 0 {
152		panic("ammmath: ugnotIn must be positive")
153	}
154	if poolUgnot <= 0 || poolToken <= 0 {
155		panic("ammmath: invalid pool reserves")
156	}
157	if remainderToPool < 0 {
158		panic("ammmath: negative remainder")
159	}
160	addU, ok := overflow.Add64(ugnotIn, remainderToPool)
161	if !ok {
162		panic("ammmath: add overflow")
163	}
164	newPU, ok = overflow.Add64(poolUgnot, addU)
165	if !ok {
166		panic("ammmath: pool ugnot overflow")
167	}
168	tokensOut = poolToken * ugnotIn / (poolUgnot + ugnotIn)
169	if tokensOut <= 0 {
170		panic("ammmath: zero tokens out of pool")
171	}
172	if tokensOut >= poolToken {
173		panic("ammmath: would drain pool tokens")
174	}
175	newPT = poolToken - tokensOut
176	return tokensOut, newPU, newPT
177}
178
179// PoolSwapTokenForUgnot swaps tokens for gross ugnot out.
180func PoolSwapTokenForUgnot(poolUgnot, poolToken, tokensIn int64) (ugnotOut, newPU, newPT int64) {
181	if tokensIn <= 0 {
182		panic("ammmath: tokensIn must be positive")
183	}
184	if poolUgnot <= 0 || poolToken <= 0 {
185		panic("ammmath: invalid pool reserves")
186	}
187	newPT, ok := overflow.Add64(poolToken, tokensIn)
188	if !ok {
189		panic("ammmath: pool token overflow")
190	}
191	ugnotOut = poolUgnot * tokensIn / (poolToken + tokensIn)
192	if ugnotOut <= 0 {
193		panic("ammmath: zero ugnot out of pool")
194	}
195	if ugnotOut >= poolUgnot {
196		panic("ammmath: would drain pool ugnot")
197	}
198	newPU = poolUgnot - ugnotOut
199	return ugnotOut, newPU, newPT
200}
201
202// CanGraduate reports whether raised net ugnot meets the threshold.
203func CanGraduate(raisedUgnot, threshold int64) bool {
204	return raisedUgnot >= threshold && threshold > 0
205}
206
207// SpotPriceUgnotPerToken returns ugnot per token scaled by 1e6 (display only).
208func SpotPriceUgnotPerToken(ugnotReserve, tokenReserve int64) int64 {
209	if tokenReserve <= 0 {
210		return 0
211	}
212	return ugnotReserve * 1000000 / tokenReserve
213}