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.31 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 ammmath
  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}