1// Package ammmath implements integer bonding-curve and constant-product AMM2// helpers for the gnomemepad factory (Pump-style virtual CPMM + real CPMM pool).3package ammmathv245import (6 "math/bits"7 "math/overflow"8)910// FeeResult is the split of a gross trade fee.11type FeeResult struct {12 Gross int6413 Net int6414 Fee int6415 Creator int6416 Protocol int6417 Remainder int6418}1920// maxInt64 is 2^63-1; MulDiv results must fit here.21const maxInt64 = int64(^uint64(0) >> 1)2223// 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 035 }36 if prod, ok := overflow.Mul64(x, y); ok {37 return prod / d38 }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}4748// 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 / 1000060 net := gross - fee61 creator := fee * creatorShareBPS / 1000062 protocol := fee * protocolShareBPS / 1000063 remainder := fee - creator - protocol64 return FeeResult{65 Gross: gross,66 Net: net,67 Fee: fee,68 Creator: creator,69 Protocol: protocol,70 Remainder: remainder,71 }72}7374// 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 / 1000086 net := grossOut - fee87 if net <= 0 {88 panic("ammmath: fee consumes entire output")89 }90 creator := fee * creatorShareBPS / 1000091 protocol := fee * protocolShareBPS / 1000092 remainder := fee - creator - protocol93 return FeeResult{94 Gross: grossOut,95 Net: net,96 Fee: fee,97 Creator: creator,98 Protocol: protocol,99 Remainder: remainder,100 }101}102103// 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 int64116 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 - newVT124 return tokensOut, newVU, newVT125}126127// MaxNetInForTokenOut is the largest net ugnot in that yields tokensOut ≤ maxTokensOut128// (integer CPMM, same floor rules as BuyTokens). Used to fill the last curve tokens129// 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 0133 }134 if maxTokensOut >= virtualToken {135 return 0136 }137 targetNewVT := virtualToken - maxTokensOut138 // maxNewVU = floor((VU*VT)/targetNewVT)139 maxNewVU := MulDiv(virtualUgnot, virtualToken, targetNewVT)140 if maxNewVU <= virtualUgnot {141 return 0142 }143 return maxNewVU - virtualUgnot144}145146// 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 - newVU166 return ugnotOut, newVU, newVT167}168169// 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 - tokensOut200 return tokensOut, newPU, newPT201}202203// 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 - ugnotOut223 return ugnotOut, newPU, newPT224}225226// CanGraduate reports whether raised net ugnot meets the threshold.227func CanGraduate(raisedUgnot, threshold int64) bool {228 return raisedUgnot >= threshold && threshold > 0229}230231// SpotPriceUgnotPerToken returns ugnot per token scaled by 1e6 (display only).232func SpotPriceUgnotPerToken(ugnotReserve, tokenReserve int64) int64 {233 if tokenReserve <= 0 {234 return 0235 }236 if ugnotReserve <= 0 {237 return 0238 }239 return MulDiv(ugnotReserve, 1_000_000, tokenReserve)240}241Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.