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}