Files
mev-beta/orig/pkg/math/exchange_math.go
Administrator 803de231ba feat: create v2-prep branch with comprehensive planning
Restructured project for V2 refactor:

**Structure Changes:**
- Moved all V1 code to orig/ folder (preserved with git mv)
- Created docs/planning/ directory
- Added orig/README_V1.md explaining V1 preservation

**Planning Documents:**
- 00_V2_MASTER_PLAN.md: Complete architecture overview
  - Executive summary of critical V1 issues
  - High-level component architecture diagrams
  - 5-phase implementation roadmap
  - Success metrics and risk mitigation

- 07_TASK_BREAKDOWN.md: Atomic task breakdown
  - 99+ hours of detailed tasks
  - Every task < 2 hours (atomic)
  - Clear dependencies and success criteria
  - Organized by implementation phase

**V2 Key Improvements:**
- Per-exchange parsers (factory pattern)
- Multi-layer strict validation
- Multi-index pool cache
- Background validation pipeline
- Comprehensive observability

**Critical Issues Addressed:**
- Zero address tokens (strict validation + cache enrichment)
- Parsing accuracy (protocol-specific parsers)
- No audit trail (background validation channel)
- Inefficient lookups (multi-index cache)
- Stats disconnection (event-driven metrics)

Next Steps:
1. Review planning documents
2. Begin Phase 1: Foundation (P1-001 through P1-010)
3. Implement parsers in Phase 2
4. Build cache system in Phase 3
5. Add validation pipeline in Phase 4
6. Migrate and test in Phase 5

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-10 10:14:26 +01:00

1011 lines
32 KiB
Go

package math
import (
"fmt"
"math"
"math/big"
)
// ExchangeMath provides exchange-specific mathematical calculations
type ExchangeMath interface {
CalculateAmountOut(amountIn, reserveIn, reserveOut *big.Int, fee uint32) (*big.Int, error)
CalculateAmountIn(amountOut, reserveIn, reserveOut *big.Int, fee uint32) (*big.Int, error)
CalculatePriceImpact(amountIn, reserveIn, reserveOut *big.Int) (float64, error)
GetSpotPrice(reserveIn, reserveOut *big.Int) (*big.Float, error)
CalculateSlippage(expectedOut, actualOut *big.Int) (float64, error)
}
// UniswapV2Math implements Uniswap V2 constant product formula
type UniswapV2Math struct{}
// UniswapV3Math implements Uniswap V3 concentrated liquidity math
type UniswapV3Math struct{}
// CurveMath implements Curve Finance StableSwap math
type CurveMath struct{}
// BalancerMath implements Balancer weighted pool math
type BalancerMath struct{}
// ConstantSumMath implements basic constant sum AMM math
type ConstantSumMath struct{}
// ========== Uniswap V2 Math ==========
// NewUniswapV2Math creates a new Uniswap V2 math calculator
func NewUniswapV2Math() *UniswapV2Math {
return &UniswapV2Math{}
}
// CalculateAmountOut calculates output amount for Uniswap V2 (x * y = k)
func (u *UniswapV2Math) CalculateAmountOut(amountIn, reserveIn, reserveOut *big.Int, fee uint32) (*big.Int, error) {
if amountIn.Sign() <= 0 || reserveIn.Sign() <= 0 || reserveOut.Sign() <= 0 {
return nil, fmt.Errorf("invalid amounts: amountIn=%s, reserveIn=%s, reserveOut=%s",
amountIn.String(), reserveIn.String(), reserveOut.String())
}
// Calculate fee (default 3000 = 0.3%)
if fee == 0 {
fee = 3000
}
// amountInWithFee = amountIn * (10000 - fee)
feeFactor := big.NewInt(int64(10000 - fee))
amountInWithFee := new(big.Int).Mul(amountIn, feeFactor)
// numerator = amountInWithFee * reserveOut
numerator := new(big.Int).Mul(amountInWithFee, reserveOut)
// denominator = reserveIn * 10000 + amountInWithFee
denominator := new(big.Int).Mul(reserveIn, big.NewInt(10000))
denominator.Add(denominator, amountInWithFee)
// Check for division by zero
if denominator.Sign() == 0 {
return nil, fmt.Errorf("division by zero in amountOut calculation")
}
// amountOut = numerator / denominator
amountOut := new(big.Int).Div(numerator, denominator)
return amountOut, nil
}
// CalculateAmountIn calculates input amount for Uniswap V2
func (u *UniswapV2Math) CalculateAmountIn(amountOut, reserveIn, reserveOut *big.Int, fee uint32) (*big.Int, error) {
if amountOut.Sign() <= 0 || reserveIn.Sign() <= 0 || reserveOut.Sign() <= 0 {
return nil, fmt.Errorf("invalid amounts")
}
if amountOut.Cmp(reserveOut) >= 0 {
return nil, fmt.Errorf("insufficient liquidity")
}
if fee == 0 {
fee = 3000
}
// numerator = reserveIn * amountOut * 10000
numerator := new(big.Int).Mul(reserveIn, amountOut)
numerator.Mul(numerator, big.NewInt(10000))
// denominator = (reserveOut - amountOut) * (10000 - fee)
denominator := new(big.Int).Sub(reserveOut, amountOut)
// Check if the calculation is valid (amountOut must be less than reserveOut)
if denominator.Sign() <= 0 {
return nil, fmt.Errorf("invalid swap: amountOut (%s) >= reserveOut (%s)", amountOut.String(), reserveOut.String())
}
denominator.Mul(denominator, big.NewInt(int64(10000-fee)))
// Check for division by zero
if denominator.Sign() == 0 {
return nil, fmt.Errorf("division by zero in amountIn calculation")
}
// amountIn = numerator / denominator + 1 (round up)
amountIn := new(big.Int).Div(numerator, denominator)
amountIn.Add(amountIn, big.NewInt(1))
return amountIn, nil
}
// CalculatePriceImpact calculates price impact for Uniswap V2
func (u *UniswapV2Math) CalculatePriceImpact(amountIn, reserveIn, reserveOut *big.Int) (float64, error) {
// Check for nil pointers first
if amountIn == nil || reserveIn == nil || reserveOut == nil {
return 0, fmt.Errorf("nil pointer encountered")
}
if amountIn.Sign() <= 0 || reserveIn.Sign() <= 0 || reserveOut.Sign() <= 0 {
return 0, fmt.Errorf("invalid amounts")
}
// Price before = reserveOut / reserveIn
priceBefore := new(big.Float).Quo(new(big.Float).SetInt(reserveOut), new(big.Float).SetInt(reserveIn))
// Calculate amount out
amountOut, err := u.CalculateAmountOut(amountIn, reserveIn, reserveOut, 3000)
if err != nil {
return 0, err
}
// New reserves after swap
newReserveIn := new(big.Int).Add(reserveIn, amountIn)
newReserveOut := new(big.Int).Sub(reserveOut, amountOut)
// Price after = newReserveOut / newReserveIn
priceAfter := new(big.Float).Quo(new(big.Float).SetInt(newReserveOut), new(big.Float).SetInt(newReserveIn))
// Price impact = (priceBefore - priceAfter) / priceBefore
impact := new(big.Float).Sub(priceBefore, priceAfter)
impact.Quo(impact, priceBefore)
impactFloat, _ := impact.Float64()
return math.Abs(impactFloat), nil
}
// GetSpotPrice returns current spot price for Uniswap V2
func (u *UniswapV2Math) GetSpotPrice(reserveIn, reserveOut *big.Int) (*big.Float, error) {
if reserveIn.Sign() <= 0 || reserveOut.Sign() <= 0 {
return nil, fmt.Errorf("invalid reserves")
}
return new(big.Float).Quo(new(big.Float).SetInt(reserveOut), new(big.Float).SetInt(reserveIn)), nil
}
// CalculateSlippage calculates slippage between expected and actual output
func (u *UniswapV2Math) CalculateSlippage(expectedOut, actualOut *big.Int) (float64, error) {
if expectedOut.Sign() <= 0 || actualOut.Sign() <= 0 {
return 0, fmt.Errorf("invalid amounts")
}
// Slippage = (expectedOut - actualOut) / expectedOut
diff := new(big.Float).Sub(new(big.Float).SetInt(expectedOut), new(big.Float).SetInt(actualOut))
slippage := new(big.Float).Quo(diff, new(big.Float).SetInt(expectedOut))
slippageFloat, _ := slippage.Float64()
return math.Abs(slippageFloat), nil
}
// ========== Uniswap V3 Math ==========
// NewUniswapV3Math creates a new Uniswap V3 math calculator
func NewUniswapV3Math() *UniswapV3Math {
return &UniswapV3Math{}
}
// CalculateAmountOut calculates output for Uniswap V3 concentrated liquidity
func (u *UniswapV3Math) CalculateAmountOut(amountIn, sqrtPriceX96, liquidity *big.Int, fee uint32) (*big.Int, error) {
if amountIn.Sign() <= 0 || sqrtPriceX96.Sign() <= 0 || liquidity.Sign() <= 0 {
return nil, fmt.Errorf("invalid parameters")
}
if fee == 0 {
fee = 3000 // Default 0.3%
}
// Apply fee: amountInWithFee = amountIn * (1000000 - fee) / 1000000
feeFactor := big.NewInt(int64(1000000 - fee))
amountInWithFee := new(big.Int).Mul(amountIn, feeFactor)
amountInWithFee.Div(amountInWithFee, big.NewInt(1000000))
// Simplified V3 calculation (for exact implementation, need tick math)
// This approximates the swap for small amounts
// Calculate price change
Q96 := new(big.Int).Lsh(big.NewInt(1), 96)
priceChange := new(big.Int).Mul(amountInWithFee, Q96)
priceChange.Div(priceChange, liquidity)
// New sqrt price
newSqrtPriceX96 := new(big.Int).Add(sqrtPriceX96, priceChange)
// Calculate amount out using price difference
priceDiff := new(big.Int).Sub(newSqrtPriceX96, sqrtPriceX96)
amountOut := new(big.Int).Mul(liquidity, priceDiff)
amountOut.Div(amountOut, sqrtPriceX96)
return amountOut, nil
}
// CalculateAmountIn calculates input for Uniswap V3
func (u *UniswapV3Math) CalculateAmountIn(amountOut, sqrtPriceX96, liquidity *big.Int, fee uint32) (*big.Int, error) {
if amountOut.Sign() <= 0 || sqrtPriceX96.Sign() <= 0 || liquidity.Sign() <= 0 {
return nil, fmt.Errorf("invalid parameters")
}
// Simplified reverse calculation
Q96 := new(big.Int).Lsh(big.NewInt(1), 96)
// Calculate required price change
priceChange := new(big.Int).Mul(amountOut, sqrtPriceX96)
priceChange.Div(priceChange, liquidity)
// Calculate amount in before fees
amountInBeforeFee := new(big.Int).Mul(priceChange, liquidity)
amountInBeforeFee.Div(amountInBeforeFee, Q96)
// Apply fee
if fee == 0 {
fee = 3000
}
feeFactor := big.NewInt(int64(1000000 - fee))
amountIn := new(big.Int).Mul(amountInBeforeFee, big.NewInt(1000000))
amountIn.Div(amountIn, feeFactor)
return amountIn, nil
}
// CalculatePriceImpact calculates price impact for Uniswap V3
func (u *UniswapV3Math) CalculatePriceImpact(amountIn, sqrtPriceX96, liquidity *big.Int) (float64, error) {
// Check for nil pointers first
if amountIn == nil || sqrtPriceX96 == nil || liquidity == nil {
return 0, fmt.Errorf("nil pointer encountered")
}
if amountIn.Sign() <= 0 || sqrtPriceX96.Sign() <= 0 || liquidity.Sign() <= 0 {
return 0, fmt.Errorf("invalid parameters")
}
// Calculate new sqrt price after swap
Q96 := new(big.Int).Lsh(big.NewInt(1), 96)
priceChange := new(big.Int).Mul(amountIn, Q96)
priceChange.Div(priceChange, liquidity)
newSqrtPriceX96 := new(big.Int).Add(sqrtPriceX96, priceChange)
// Convert to regular prices using big.Float for precision
Q96Float := new(big.Float).SetInt(Q96)
// priceBefore = (sqrtPriceX96 / 2^96)^2
priceBefore := new(big.Float).SetInt(sqrtPriceX96)
priceBefore.Quo(priceBefore, Q96Float)
priceBefore.Mul(priceBefore, priceBefore)
// priceAfter = (newSqrtPriceX96 / 2^96)^2
priceAfter := new(big.Float).SetInt(newSqrtPriceX96)
priceAfter.Quo(priceAfter, Q96Float)
priceAfter.Mul(priceAfter, priceAfter)
// Check if priceBefore is zero or very small
if priceBefore.Sign() == 0 {
return 0, fmt.Errorf("price before is zero - invalid calculation")
}
// Check if priceBefore is too small (less than 1e-18)
minPrice := big.NewFloat(1e-18)
if priceBefore.Cmp(minPrice) < 0 {
return 0, fmt.Errorf("price too small for reliable calculation")
}
// Calculate impact = (priceAfter - priceBefore) / priceBefore
impact := new(big.Float).Sub(priceAfter, priceBefore)
impact.Quo(impact, priceBefore)
impactFloat, _ := impact.Float64()
return math.Abs(impactFloat), nil
}
// GetSpotPrice returns current spot price for Uniswap V3
func (u *UniswapV3Math) GetSpotPrice(sqrtPriceX96, _ *big.Int) (*big.Float, error) {
if sqrtPriceX96.Sign() <= 0 {
return nil, fmt.Errorf("invalid sqrt price")
}
// Price = (sqrtPriceX96 / 2^96)^2
Q96 := new(big.Int).Lsh(big.NewInt(1), 96)
price := new(big.Int).Mul(sqrtPriceX96, sqrtPriceX96)
price.Div(price, new(big.Int).Mul(Q96, Q96))
return new(big.Float).SetInt(price), nil
}
// CalculateSlippage calculates slippage for Uniswap V3
func (u *UniswapV3Math) CalculateSlippage(expectedOut, actualOut *big.Int) (float64, error) {
if expectedOut.Sign() <= 0 || actualOut.Sign() <= 0 {
return 0, fmt.Errorf("invalid amounts")
}
diff := new(big.Float).Sub(new(big.Float).SetInt(expectedOut), new(big.Float).SetInt(actualOut))
slippage := new(big.Float).Quo(diff, new(big.Float).SetInt(expectedOut))
slippageFloat, _ := slippage.Float64()
return math.Abs(slippageFloat), nil
}
// ========== Curve Finance Math ==========
// NewCurveMath creates a new Curve math calculator
func NewCurveMath() *CurveMath {
return &CurveMath{}
}
// CalculateAmountOut calculates output for Curve StableSwap
func (c *CurveMath) CalculateAmountOut(amountIn, balance0, balance1 *big.Int, fee uint32) (*big.Int, error) {
if amountIn.Sign() <= 0 || balance0.Sign() <= 0 || balance1.Sign() <= 0 {
return nil, fmt.Errorf("invalid amounts")
}
// Simplified Curve calculation (A = 100 for stable pools)
A := big.NewInt(100)
// Calculate D (total deposit)
D := c.calculateD(balance0, balance1, A)
// New balance after adding amountIn
newBalance0 := new(big.Int).Add(balance0, amountIn)
// Calculate new balance1 using Curve formula
newBalance1 := c.getY(newBalance0, D, A)
// Amount out = balance1 - newBalance1
amountOut := new(big.Int).Sub(balance1, newBalance1)
// Apply fee
if fee == 0 {
fee = 400 // Default 0.04%
}
feeAmount := new(big.Int).Mul(amountOut, big.NewInt(int64(fee)))
feeAmount.Div(feeAmount, big.NewInt(1000000))
amountOut.Sub(amountOut, feeAmount)
return amountOut, nil
}
// calculateD calculates the D invariant for Curve
func (c *CurveMath) calculateD(balance0, balance1, A *big.Int) *big.Int {
// Simplified D calculation for 2-coin pool
// D = 2 * sqrt(x * y) for stable coins (approximation)
sum := new(big.Int).Add(balance0, balance1)
product := new(big.Int).Mul(balance0, balance1)
// Newton's method approximation for D
D := new(big.Int).Set(sum)
for i := 0; i < 10; i++ { // 10 iterations for convergence
// Calculate new D
numerator := new(big.Int).Mul(product, big.NewInt(4))
denominator := new(big.Int).Mul(D, D)
if denominator.Sign() == 0 {
break
}
ratio := new(big.Int).Div(numerator, denominator)
newD := new(big.Int).Add(D, ratio)
newD.Div(newD, big.NewInt(2))
// Check convergence
diff := new(big.Int).Sub(newD, D)
if diff.CmpAbs(big.NewInt(1)) <= 0 {
break
}
D = newD
}
return D
}
// getY calculates the new balance using Curve formula
func (c *CurveMath) getY(newX, D, A *big.Int) *big.Int {
// Simplified calculation for 2-coin pool
// Solve for y in the Curve invariant equation
// For stable coins, approximately: y = D - x
y := new(big.Int).Sub(D, newX)
// Ensure positive result
if y.Sign() <= 0 {
y = big.NewInt(1)
}
return y
}
// CalculateAmountIn calculates input for Curve
func (c *CurveMath) CalculateAmountIn(amountOut, balance0, balance1 *big.Int, fee uint32) (*big.Int, error) {
// Reverse calculation - simplified
A := big.NewInt(100)
D := c.calculateD(balance0, balance1, A)
// Calculate new balance1 after removing amountOut
newBalance1 := new(big.Int).Sub(balance1, amountOut)
// Calculate required balance0
newBalance0 := c.getY(newBalance1, D, A)
// Amount in = newBalance0 - balance0
amountIn := new(big.Int).Sub(newBalance0, balance0)
// Apply fee
if fee == 0 {
fee = 400
}
feeMultiplier := big.NewInt(int64(1000000 + fee))
amountIn.Mul(amountIn, feeMultiplier)
amountIn.Div(amountIn, big.NewInt(1000000))
return amountIn, nil
}
// CalculatePriceImpact calculates price impact for Curve
func (c *CurveMath) CalculatePriceImpact(amountIn, balance0, balance1 *big.Int) (float64, error) {
// Check for nil pointers first
if amountIn == nil || balance0 == nil || balance1 == nil {
return 0, fmt.Errorf("nil pointer encountered")
}
if amountIn.Sign() <= 0 || balance0.Sign() <= 0 || balance1.Sign() <= 0 {
return 0, fmt.Errorf("invalid amounts")
}
// Price before = balance1 / balance0
priceBefore := new(big.Float).Quo(new(big.Float).SetInt(balance1), new(big.Float).SetInt(balance0))
// Calculate amount out
amountOut, err := c.CalculateAmountOut(amountIn, balance0, balance1, 400)
if err != nil {
return 0, err
}
// New balances
newBalance0 := new(big.Int).Add(balance0, amountIn)
newBalance1 := new(big.Int).Sub(balance1, amountOut)
// Price after
priceAfter := new(big.Float).Quo(new(big.Float).SetInt(newBalance1), new(big.Float).SetInt(newBalance0))
// Calculate impact
impact := new(big.Float).Sub(priceBefore, priceAfter)
impact.Quo(impact, priceBefore)
impactFloat, _ := impact.Float64()
return math.Abs(impactFloat), nil
}
// GetSpotPrice returns current spot price for Curve
func (c *CurveMath) GetSpotPrice(balance0, balance1 *big.Int) (*big.Float, error) {
if balance0.Sign() <= 0 || balance1.Sign() <= 0 {
return nil, fmt.Errorf("invalid balances")
}
return new(big.Float).Quo(new(big.Float).SetInt(balance1), new(big.Float).SetInt(balance0)), nil
}
// CalculateSlippage calculates slippage for Curve
func (c *CurveMath) CalculateSlippage(expectedOut, actualOut *big.Int) (float64, error) {
if expectedOut.Sign() <= 0 || actualOut.Sign() <= 0 {
return 0, fmt.Errorf("invalid amounts")
}
diff := new(big.Float).Sub(new(big.Float).SetInt(expectedOut), new(big.Float).SetInt(actualOut))
slippage := new(big.Float).Quo(diff, new(big.Float).SetInt(expectedOut))
slippageFloat, _ := slippage.Float64()
return math.Abs(slippageFloat), nil
}
// ========== Kyber Math ==========
// CalculateAmountOut calculates output for Kyber Elastic
func (k *KyberMath) CalculateAmountOut(amountIn, sqrtPriceX96, liquidity *big.Int, fee uint32) (*big.Int, error) {
if amountIn.Sign() <= 0 || sqrtPriceX96.Sign() <= 0 || liquidity.Sign() <= 0 {
return nil, fmt.Errorf("invalid parameters")
}
// Kyber Elastic uses concentrated liquidity similar to Uniswap V3
// but with different fee structures and mechanisms
if fee == 0 {
fee = 1000 // Default 0.1% for Kyber
}
// Apply fee: amountInWithFee = amountIn * (1000000 - fee) / 1000000
feeFactor := big.NewInt(int64(1000000 - fee))
amountInWithFee := new(big.Int).Mul(amountIn, feeFactor)
amountInWithFee.Div(amountInWithFee, big.NewInt(1000000))
// Calculate price change using liquidity and amountIn
Q96 := new(big.Int).Lsh(big.NewInt(1), 96)
priceChange := new(big.Int).Mul(amountInWithFee, Q96)
priceChange.Div(priceChange, liquidity)
// Calculate new sqrt price after swap
newSqrtPriceX96 := new(big.Int).Add(sqrtPriceX96, priceChange)
// Calculate amount out based on price difference and liquidity
priceDiff := new(big.Int).Sub(newSqrtPriceX96, sqrtPriceX96)
amountOut := new(big.Int).Mul(liquidity, priceDiff)
amountOut.Div(amountOut, sqrtPriceX96)
return amountOut, nil
}
// CalculateAmountIn calculates input for Kyber Elastic
func (k *KyberMath) CalculateAmountIn(amountOut, sqrtPriceX96, liquidity *big.Int, fee uint32) (*big.Int, error) {
if amountOut.Sign() <= 0 || sqrtPriceX96.Sign() <= 0 || liquidity.Sign() <= 0 {
return nil, fmt.Errorf("invalid parameters")
}
if fee == 0 {
fee = 1000
}
// Calculate required price change
Q96 := new(big.Int).Lsh(big.NewInt(1), 96)
priceChange := new(big.Int).Mul(amountOut, sqrtPriceX96)
priceChange.Div(priceChange, liquidity)
// Calculate amount in before fees
amountInBeforeFee := new(big.Int).Mul(priceChange, liquidity)
amountInBeforeFee.Div(amountInBeforeFee, Q96)
// Apply fee
feeFactor := big.NewInt(int64(1000000 - fee))
amountIn := new(big.Int).Mul(amountInBeforeFee, big.NewInt(1000000))
amountIn.Div(amountIn, feeFactor)
return amountIn, nil
}
// CalculatePriceImpact calculates price impact for Kyber
func (k *KyberMath) CalculatePriceImpact(amountIn, sqrtPriceX96, liquidity *big.Int) (float64, error) {
if amountIn.Sign() <= 0 || sqrtPriceX96.Sign() <= 0 || liquidity.Sign() <= 0 {
return 0, fmt.Errorf("invalid parameters")
}
// Calculate new sqrt price after swap
Q96 := new(big.Int).Lsh(big.NewInt(1), 96)
priceChange := new(big.Int).Mul(amountIn, Q96)
priceChange.Div(priceChange, liquidity)
newSqrtPriceX96 := new(big.Int).Add(sqrtPriceX96, priceChange)
// Convert to regular prices using big.Float for precision
Q96Float := new(big.Float).SetInt(Q96)
// priceBefore = (sqrtPriceX96 / 2^96)^2
priceBefore := new(big.Float).SetInt(sqrtPriceX96)
priceBefore.Quo(priceBefore, Q96Float)
priceBefore.Mul(priceBefore, priceBefore)
// priceAfter = (newSqrtPriceX96 / 2^96)^2
priceAfter := new(big.Float).SetInt(newSqrtPriceX96)
priceAfter.Quo(priceAfter, Q96Float)
priceAfter.Mul(priceAfter, priceAfter)
// Check if priceBefore is zero or very small
if priceBefore.Sign() == 0 {
return 0, fmt.Errorf("price before is zero - invalid calculation")
}
// Check if priceBefore is too small (less than 1e-18)
minPrice := big.NewFloat(1e-18)
if priceBefore.Cmp(minPrice) < 0 {
return 0, fmt.Errorf("price too small for reliable calculation")
}
// Calculate impact = (priceAfter - priceBefore) / priceBefore
impact := new(big.Float).Sub(priceAfter, priceBefore)
impact.Quo(impact, priceBefore)
impactFloat, _ := impact.Float64()
return math.Abs(impactFloat), nil
}
// GetSpotPrice returns current spot price for Kyber
func (k *KyberMath) GetSpotPrice(sqrtPriceX96, _ *big.Int) (*big.Float, error) {
if sqrtPriceX96.Sign() <= 0 {
return nil, fmt.Errorf("invalid sqrt price")
}
// Price = (sqrtPriceX96 / 2^96)^2
Q96 := new(big.Int).Lsh(big.NewInt(1), 96)
price := new(big.Int).Mul(sqrtPriceX96, sqrtPriceX96)
price.Div(price, new(big.Int).Mul(Q96, Q96))
return new(big.Float).SetInt(price), nil
}
// CalculateSlippage calculates slippage for Kyber
func (k *KyberMath) CalculateSlippage(expectedOut, actualOut *big.Int) (float64, error) {
if expectedOut.Sign() <= 0 || actualOut.Sign() <= 0 {
return 0, fmt.Errorf("invalid amounts")
}
diff := new(big.Float).Sub(new(big.Float).SetInt(expectedOut), new(big.Float).SetInt(actualOut))
slippage := new(big.Float).Quo(diff, new(big.Float).SetInt(expectedOut))
slippageFloat, _ := slippage.Float64()
return math.Abs(slippageFloat), nil
}
// ========== Balancer Math ==========
// CalculateAmountOut calculates output for Balancer weighted pools
func (b *BalancerMath) CalculateAmountOut(amountIn, reserveIn, reserveOut *big.Int, fee uint32) (*big.Int, error) {
if amountIn.Sign() <= 0 || reserveIn.Sign() <= 0 || reserveOut.Sign() <= 0 {
return nil, fmt.Errorf("invalid amounts")
}
// For Balancer, we use weighted pool formula
// amountOut = reserveOut * (1 - (reserveIn / (reserveIn + amountIn * feeFactor))^weightRatio)
if fee == 0 {
fee = 1000 // Default 0.1% fee
}
// Calculate fee factor
feeFactor := float64(10000-fee) / 10000.0
// Calculate effective input amount after fee
amountInWithFee := new(big.Float).SetInt(amountIn)
amountInWithFee.Mul(amountInWithFee, big.NewFloat(feeFactor))
// Convert to big.Float for precise calculations
reserveInFloat := new(big.Float).SetInt(reserveIn)
reserveOutFloat := new(big.Float).SetInt(reserveOut)
// Calculate numerator: reserveIn + (amountIn * feeFactor)
numerator := new(big.Float).Add(reserveInFloat, amountInWithFee)
// Calculate ratio: reserveIn / numerator
ratio := new(big.Float).Quo(reserveInFloat, numerator)
// For equal weights (simplified approach)
// Calculate amountOut = reserveOut * (1 - ratio)
denominatorFloat := new(big.Float).SetInt(big.NewInt(1))
result := new(big.Float).Sub(denominatorFloat, ratio)
result.Mul(reserveOutFloat, result)
// Convert back to big.Int
amountOut := new(big.Int)
result.Int(amountOut)
return amountOut, nil
}
// CalculateAmountIn calculates input for Balancer weighted pools
func (b *BalancerMath) CalculateAmountIn(amountOut, reserveIn, reserveOut *big.Int, fee uint32) (*big.Int, error) {
if amountOut.Sign() <= 0 || reserveIn.Sign() <= 0 || reserveOut.Sign() <= 0 {
return nil, fmt.Errorf("invalid amounts")
}
if amountOut.Cmp(reserveOut) >= 0 {
return nil, fmt.Errorf("insufficient liquidity")
}
if fee == 0 {
fee = 1000
}
// Calculate fee factor
feeFactor := float64(10000) / float64(10000-fee)
// Calculate reserveOut after swap
newReserveOut := new(big.Int).Sub(reserveOut, amountOut)
if newReserveOut.Sign() <= 0 {
return nil, fmt.Errorf("insufficient liquidity")
}
// Calculate amountIn using weighted pool formula
// Using simplified approach for equal weights
reserveOutFloat := new(big.Float).SetInt(reserveOut)
newReserveOutFloat := new(big.Float).SetInt(newReserveOut)
// Calculate ratio: reserveOut / newReserveOut
ratio := new(big.Float).Quo(reserveOutFloat, newReserveOutFloat)
// For equal weights, we just take the ratio as is and subtract 1
denominatorFloat := new(big.Float).SetInt(big.NewInt(1))
result := new(big.Float).Sub(ratio, denominatorFloat)
// Multiply by reserveIn
reserveInFloat := new(big.Float).SetInt(reserveIn)
result.Mul(reserveInFloat, result)
// Adjust by fee factor
result.Mul(result, big.NewFloat(feeFactor))
// Convert back to big.Int
amountIn := new(big.Int)
result.Int(amountIn)
// Add 1 to ensure we have enough for the swap (rounding up)
amountIn.Add(amountIn, big.NewInt(1))
return amountIn, nil
}
// CalculatePriceImpact calculates price impact for Balancer
func (b *BalancerMath) CalculatePriceImpact(amountIn, reserveIn, reserveOut *big.Int) (float64, error) {
// Check for nil pointers first
if amountIn == nil || reserveIn == nil || reserveOut == nil {
return 0, fmt.Errorf("nil pointer encountered")
}
if amountIn.Sign() <= 0 || reserveIn.Sign() <= 0 || reserveOut.Sign() <= 0 {
return 0, fmt.Errorf("invalid amounts")
}
// Price before = reserveOut / reserveIn
priceBefore := new(big.Float).Quo(new(big.Float).SetInt(reserveOut), new(big.Float).SetInt(reserveIn))
// Calculate amount out
amountOut, err := b.CalculateAmountOut(amountIn, reserveIn, reserveOut, 1000)
if err != nil {
return 0, err
}
// New reserves after swap
newReserveIn := new(big.Int).Add(reserveIn, amountIn)
newReserveOut := new(big.Int).Sub(reserveOut, amountOut)
// Price after = newReserveOut / newReserveIn
priceAfter := new(big.Float).Quo(new(big.Float).SetInt(newReserveOut), new(big.Float).SetInt(newReserveIn))
// Price impact = (priceBefore - priceAfter) / priceBefore
impact := new(big.Float).Sub(priceBefore, priceAfter)
impact.Quo(impact, priceBefore)
impactFloat, _ := impact.Float64()
return math.Abs(impactFloat), nil
}
// GetSpotPrice returns current spot price for Balancer
func (b *BalancerMath) GetSpotPrice(reserveIn, reserveOut *big.Int) (*big.Float, error) {
if reserveIn.Sign() <= 0 || reserveOut.Sign() <= 0 {
return nil, fmt.Errorf("invalid reserves")
}
return new(big.Float).Quo(new(big.Float).SetInt(reserveOut), new(big.Float).SetInt(reserveIn)), nil
}
// CalculateSlippage calculates slippage for Balancer
func (b *BalancerMath) CalculateSlippage(expectedOut, actualOut *big.Int) (float64, error) {
if expectedOut.Sign() <= 0 || actualOut.Sign() <= 0 {
return 0, fmt.Errorf("invalid amounts")
}
// Slippage = (expectedOut - actualOut) / expectedOut
diff := new(big.Float).Sub(new(big.Float).SetInt(expectedOut), new(big.Float).SetInt(actualOut))
slippage := new(big.Float).Quo(diff, new(big.Float).SetInt(expectedOut))
slippageFloat, _ := slippage.Float64()
return math.Abs(slippageFloat), nil
}
// ========== Constant Sum Math ==========
// CalculateAmountOut calculates output for Constant Sum AMM (like Uniswap V1)
func (c *ConstantSumMath) CalculateAmountOut(amountIn, reserveIn, reserveOut *big.Int, fee uint32) (*big.Int, error) {
if amountIn.Sign() <= 0 || reserveIn.Sign() <= 0 || reserveOut.Sign() <= 0 {
return nil, fmt.Errorf("invalid amounts")
}
// In constant sum, price is always 1:1 (ignoring fees)
// amountOut = amountIn * (1 - fee)
if fee == 0 {
fee = 3000 // Default 0.3% fee
}
feeFactor := big.NewInt(int64(10000 - fee))
amountOut := new(big.Int).Mul(amountIn, feeFactor)
amountOut.Div(amountOut, big.NewInt(10000))
return amountOut, nil
}
// CalculateAmountIn calculates input for Constant Sum AMM
func (c *ConstantSumMath) CalculateAmountIn(amountOut, reserveIn, reserveOut *big.Int, fee uint32) (*big.Int, error) {
if amountOut.Sign() <= 0 || reserveIn.Sign() <= 0 || reserveOut.Sign() <= 0 {
return nil, fmt.Errorf("invalid amounts")
}
if fee == 0 {
fee = 3000
}
// amountIn = amountOut / (1 - fee)
feeFactor := big.NewInt(int64(10000 - fee))
amountIn := new(big.Int).Mul(amountOut, big.NewInt(10000))
amountIn.Div(amountIn, feeFactor)
return amountIn, nil
}
// CalculatePriceImpact calculates price impact for Constant Sum (should be 0)
func (c *ConstantSumMath) CalculatePriceImpact(amountIn, reserveIn, reserveOut *big.Int) (float64, error) {
if amountIn.Sign() <= 0 || reserveIn.Sign() <= 0 || reserveOut.Sign() <= 0 {
return 0, fmt.Errorf("invalid amounts")
}
// In constant sum, there is no price impact (ignoring fees)
return 0, nil
}
// GetSpotPrice returns current spot price for Constant Sum (should be 1, ignoring fees)
func (c *ConstantSumMath) GetSpotPrice(reserveIn, reserveOut *big.Int) (*big.Float, error) {
if reserveIn.Sign() <= 0 || reserveOut.Sign() <= 0 {
return nil, fmt.Errorf("invalid reserves")
}
// In constant sum, price is always 1:1 (ignoring fees)
return big.NewFloat(1.0), nil
}
// CalculateSlippage calculates slippage for Constant Sum (same as fees)
func (c *ConstantSumMath) CalculateSlippage(expectedOut, actualOut *big.Int) (float64, error) {
if expectedOut.Sign() <= 0 || actualOut.Sign() <= 0 {
return 0, fmt.Errorf("invalid amounts")
}
// Slippage = (expectedOut - actualOut) / expectedOut
diff := new(big.Float).Sub(new(big.Float).SetInt(expectedOut), new(big.Float).SetInt(actualOut))
slippage := new(big.Float).Quo(diff, new(big.Float).SetInt(expectedOut))
slippageFloat, _ := slippage.Float64()
return math.Abs(slippageFloat), nil
}
// ========== Math Factory ==========
// MathCalculator provides a unified interface for all exchange math
type MathCalculator struct {
uniswapV2 *UniswapV2Math
uniswapV3 *UniswapV3Math
curve *CurveMath
kyber *KyberMath
balancer *BalancerMath
constantSum *ConstantSumMath
}
// NewMathCalculator creates a new unified math calculator
func NewMathCalculator() *MathCalculator {
return &MathCalculator{
uniswapV2: NewUniswapV2Math(),
uniswapV3: NewUniswapV3Math(),
curve: NewCurveMath(),
kyber: &KyberMath{},
balancer: &BalancerMath{},
constantSum: &ConstantSumMath{},
}
}
// GetMathForExchange returns the appropriate math calculator for an exchange
func (mc *MathCalculator) GetMathForExchange(exchangeType string) ExchangeMath {
switch exchangeType {
case "uniswap_v2", "sushiswap":
return mc.uniswapV2
case "uniswap_v3", "camelot_v3":
return mc.uniswapV3
case "curve":
return mc.curve
case "kyber_elastic", "kyber_classic":
return mc.kyber
case "balancer":
return mc.balancer
case "constant_sum":
return mc.constantSum
default:
return mc.uniswapV2 // Default fallback
}
}
// CalculateOptimalArbitrage calculates optimal arbitrage between exchanges
func (mc *MathCalculator) CalculateOptimalArbitrage(
exchangeA, exchangeB string,
reservesA, reservesB [2]*big.Int,
feesA, feesB uint32,
) (*ArbitrageResult, error) {
mathA := mc.GetMathForExchange(exchangeA)
mathB := mc.GetMathForExchange(exchangeB)
// Get spot prices
priceA, err := mathA.GetSpotPrice(reservesA[0], reservesA[1])
if err != nil {
return nil, err
}
priceB, err := mathB.GetSpotPrice(reservesB[0], reservesB[1])
if err != nil {
return nil, err
}
// Calculate price difference
priceDiff := new(big.Float).Sub(priceA, priceB)
priceDiff.Quo(priceDiff, priceA)
priceDiffFloat, _ := priceDiff.Float64()
// Only proceed if price difference > 0.5%
if math.Abs(priceDiffFloat) < 0.005 {
return nil, fmt.Errorf("insufficient price difference: %f", priceDiffFloat)
}
// Find optimal amount using binary search
optimalAmount := mc.findOptimalAmount(mathA, mathB, reservesA, reservesB, feesA, feesB)
// Calculate expected profit
amountOut1, _ := mathA.CalculateAmountOut(optimalAmount, reservesA[0], reservesA[1], feesA)
amountOut2, _ := mathB.CalculateAmountIn(amountOut1, reservesB[1], reservesB[0], feesB)
profit := new(big.Int).Sub(amountOut2, optimalAmount)
return &ArbitrageResult{
AmountIn: optimalAmount,
ExpectedProfit: profit,
PriceDiff: priceDiffFloat,
ExchangeA: exchangeA,
ExchangeB: exchangeB,
}, nil
}
// ArbitrageResult represents the result of arbitrage calculation
type ArbitrageResult struct {
AmountIn *big.Int
ExpectedProfit *big.Int
PriceDiff float64
ExchangeA string
ExchangeB string
}
// findOptimalAmount uses binary search to find optimal arbitrage amount
func (mc *MathCalculator) findOptimalAmount(
mathA, mathB ExchangeMath,
reservesA, reservesB [2]*big.Int,
feesA, feesB uint32,
) *big.Int {
// Binary search for optimal amount
min := big.NewInt(1000000000000000) // 0.001 ETH
max := new(big.Int).Div(reservesA[0], big.NewInt(10)) // 10% of reserve
bestAmount := new(big.Int).Set(min)
bestProfit := big.NewInt(0)
for i := 0; i < 20; i++ { // 20 iterations
mid := new(big.Int).Add(min, max)
mid.Div(mid, big.NewInt(2))
// Calculate profit at this amount
amountOut1, err1 := mathA.CalculateAmountOut(mid, reservesA[0], reservesA[1], feesA)
if err1 != nil {
max = mid
continue
}
amountOut2, err2 := mathB.CalculateAmountIn(amountOut1, reservesB[1], reservesB[0], feesB)
if err2 != nil {
max = mid
continue
}
profit := new(big.Int).Sub(amountOut2, mid)
if profit.Cmp(bestProfit) > 0 {
bestProfit = profit
bestAmount = new(big.Int).Set(mid)
}
// Adjust search range
if profit.Sign() > 0 {
min = mid
} else {
max = mid
}
}
return bestAmount
}