ammmath.gno
5.34 Kb · 190 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// SellTokens quotes a virtual constant-product sell (gross ugnot out).
100func SellTokens(virtualUgnot, virtualToken, tokensIn int64) (ugnotOut, newVU, newVT int64) {
101 if tokensIn <= 0 {
102 panic("ammmath: tokensIn must be positive")
103 }
104 if virtualUgnot <= 0 || virtualToken <= 0 {
105 panic("ammmath: invalid virtual reserves")
106 }
107 newVT, ok := overflow.Add64(virtualToken, tokensIn)
108 if !ok {
109 panic("ammmath: virtual token overflow")
110 }
111 k, ok := overflow.Mul64(virtualUgnot, virtualToken)
112 if !ok {
113 panic("ammmath: k overflow")
114 }
115 newVU = k / newVT
116 if newVU <= 0 {
117 panic("ammmath: empty virtual ugnot reserve")
118 }
119 if newVU >= virtualUgnot {
120 panic("ammmath: zero ugnot out")
121 }
122 ugnotOut = virtualUgnot - newVU
123 return ugnotOut, newVU, newVT
124}
125
126// PoolSwapUgnotForToken swaps net ugnot for tokens; remainderToPool adds to ugnot reserve.
127func PoolSwapUgnotForToken(poolUgnot, poolToken, ugnotIn, remainderToPool int64) (tokensOut, newPU, newPT int64) {
128 if ugnotIn <= 0 {
129 panic("ammmath: ugnotIn must be positive")
130 }
131 if poolUgnot <= 0 || poolToken <= 0 {
132 panic("ammmath: invalid pool reserves")
133 }
134 if remainderToPool < 0 {
135 panic("ammmath: negative remainder")
136 }
137 addU, ok := overflow.Add64(ugnotIn, remainderToPool)
138 if !ok {
139 panic("ammmath: add overflow")
140 }
141 newPU, ok = overflow.Add64(poolUgnot, addU)
142 if !ok {
143 panic("ammmath: pool ugnot overflow")
144 }
145 tokensOut = poolToken * ugnotIn / (poolUgnot + ugnotIn)
146 if tokensOut <= 0 {
147 panic("ammmath: zero tokens out of pool")
148 }
149 if tokensOut >= poolToken {
150 panic("ammmath: would drain pool tokens")
151 }
152 newPT = poolToken - tokensOut
153 return tokensOut, newPU, newPT
154}
155
156// PoolSwapTokenForUgnot swaps tokens for gross ugnot out.
157func PoolSwapTokenForUgnot(poolUgnot, poolToken, tokensIn int64) (ugnotOut, newPU, newPT int64) {
158 if tokensIn <= 0 {
159 panic("ammmath: tokensIn must be positive")
160 }
161 if poolUgnot <= 0 || poolToken <= 0 {
162 panic("ammmath: invalid pool reserves")
163 }
164 newPT, ok := overflow.Add64(poolToken, tokensIn)
165 if !ok {
166 panic("ammmath: pool token overflow")
167 }
168 ugnotOut = poolUgnot * tokensIn / (poolToken + tokensIn)
169 if ugnotOut <= 0 {
170 panic("ammmath: zero ugnot out of pool")
171 }
172 if ugnotOut >= poolUgnot {
173 panic("ammmath: would drain pool ugnot")
174 }
175 newPU = poolUgnot - ugnotOut
176 return ugnotOut, newPU, newPT
177}
178
179// CanGraduate reports whether raised net ugnot meets the threshold.
180func CanGraduate(raisedUgnot, threshold int64) bool {
181 return raisedUgnot >= threshold && threshold > 0
182}
183
184// SpotPriceUgnotPerToken returns ugnot per token scaled by 1e6 (display only).
185func SpotPriceUgnotPerToken(ugnotReserve, tokenReserve int64) int64 {
186 if tokenReserve <= 0 {
187 return 0
188 }
189 return ugnotReserve * 1000000 / tokenReserve
190}