Files
mev-beta/orig/pkg/math/dex_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

379 lines
13 KiB
Go

package math
import (
"fmt"
"math"
"math/big"
"github.com/holiman/uint256"
)
// UniswapV4Math implements Uniswap V4 mathematical calculations
type UniswapV4Math struct{}
// AlgebraV1Math implements Algebra V1.9 mathematical calculations
type AlgebraV1Math struct{}
// IntegralMath implements Integral mathematical calculations
type IntegralMath struct{}
// KyberMath implements Kyber mathematical calculations
type KyberMath struct{}
// OneInchMath implements 1Inch mathematical calculations
type OneInchMath struct{}
// ========== Uniswap V4 Math =========
// NewUniswapV4Math creates a new Uniswap V4 math calculator
func NewUniswapV4Math() *UniswapV4Math {
return &UniswapV4Math{}
}
// CalculateAmountOutV4 calculates output amount for Uniswap V4
// Uniswap V4 uses hooks and pre/post-swap hooks for additional functionality
func (u *UniswapV4Math) CalculateAmountOutV4(amountIn, sqrtPriceX96, liquidity, currentTick, tickSpacing, fee uint256.Int) (*uint256.Int, error) {
if amountIn.IsZero() || sqrtPriceX96.IsZero() || liquidity.IsZero() {
return nil, fmt.Errorf("invalid parameters")
}
// For Uniswap V4, we reuse V3 calculations with hook considerations
// In practice, V4 introduces hooks which can modify the calculation
// This is a simplified implementation based on V3
// Apply fee: amountInWithFee = amountIn * (1000000 - fee) / 1000000
feeFactor := uint256.NewInt(1000000).Sub(uint256.NewInt(1000000), &fee)
amountInWithFee := new(uint256.Int).Mul(&amountIn, feeFactor)
amountInWithFee.Div(amountInWithFee, uint256.NewInt(1000000))
// Calculate price change using liquidity and amountIn
Q96 := uint256.NewInt(1).Lsh(uint256.NewInt(1), 96)
priceChange := new(uint256.Int).Mul(amountInWithFee, Q96)
priceChange.Div(priceChange, &liquidity)
// Calculate new sqrt price after swap
newSqrtPriceX96 := new(uint256.Int).Add(&sqrtPriceX96, priceChange)
// Calculate amount out based on price difference and liquidity
priceDiff := new(uint256.Int).Sub(newSqrtPriceX96, &sqrtPriceX96)
amountOut := new(uint256.Int).Mul(&liquidity, priceDiff)
amountOut.Div(amountOut, &sqrtPriceX96)
return amountOut, nil
}
// ========== Algebra V1.9 Math ==========
// NewAlgebraV1Math creates a new Algebra V1.9 math calculator
func NewAlgebraV1Math() *AlgebraV1Math {
return &AlgebraV1Math{}
}
// CalculateAmountOutAlgebra calculates output amount for Algebra V1.9
func (a *AlgebraV1Math) CalculateAmountOutAlgebra(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")
}
// Algebra uses a dynamic fee model based on volatility
if fee == 0 {
fee = 500 // Default 0.05% for Algebra
}
// Calculate fee amount (10000 = 100%)
feeFactor := big.NewInt(int64(10000 - fee))
amountInWithFee := new(big.Int).Mul(amountIn, feeFactor)
// For Algebra, we also consider dynamic fees and volatility
// This is a simplified implementation based on Uniswap V2 with dynamic fee consideration
numerator := new(big.Int).Mul(amountInWithFee, reserveOut)
denominator := new(big.Int).Mul(reserveIn, big.NewInt(10000))
denominator.Add(denominator, amountInWithFee)
if denominator.Sign() == 0 {
return nil, fmt.Errorf("division by zero in amountOut calculation")
}
amountOut := new(big.Int).Div(numerator, denominator)
return amountOut, nil
}
// CalculatePriceImpactAlgebra calculates price impact for Algebra V1.9
func (a *AlgebraV1Math) CalculatePriceImpactAlgebra(amountIn, reserveIn, reserveOut *big.Int) (float64, error) {
if amountIn.Sign() <= 0 || reserveIn.Sign() <= 0 || reserveOut.Sign() <= 0 {
return 0, fmt.Errorf("invalid amounts")
}
// Calculate new reserves after swap
amountOut, err := a.CalculateAmountOutAlgebra(amountIn, reserveIn, reserveOut, 500)
if err != nil {
return 0, err
}
newReserveIn := new(big.Int).Add(reserveIn, amountIn)
newReserveOut := new(big.Int).Sub(reserveOut, amountOut)
// Calculate price before and after swap
priceBefore := new(big.Float).Quo(new(big.Float).SetInt(reserveOut), new(big.Float).SetInt(reserveIn))
priceAfter := new(big.Float).Quo(new(big.Float).SetInt(newReserveOut), new(big.Float).SetInt(newReserveIn))
// Calculate price impact
impact := new(big.Float).Sub(priceBefore, priceAfter)
impact.Quo(impact, priceBefore)
impactFloat, _ := impact.Float64()
return math.Abs(impactFloat), nil
}
// ========== Integral Math ==========
// NewIntegralMath creates a new Integral math calculator
func NewIntegralMath() *IntegralMath {
return &IntegralMath{}
}
// CalculateAmountOutIntegral calculates output for Integral with base fee model
func (i *IntegralMath) CalculateAmountOutIntegral(amountIn, reserveIn, reserveOut *big.Int, baseFee uint32) (*big.Int, error) {
if amountIn.Sign() <= 0 || reserveIn.Sign() <= 0 || reserveOut.Sign() <= 0 {
return nil, fmt.Errorf("invalid amounts")
}
// Integral uses a base fee model for more efficient gas usage
// Calculate effective fee based on base fee and market conditions
if baseFee == 0 {
baseFee = 100 // Default base fee of 0.01%
}
// For Integral, we implement the base fee model
feeFactor := big.NewInt(int64(10000 - baseFee))
amountInWithFee := new(big.Int).Mul(amountIn, feeFactor)
// Calculate amount out with base fee
numerator := new(big.Int).Mul(amountInWithFee, reserveOut)
denominator := new(big.Int).Mul(reserveIn, big.NewInt(10000))
denominator.Add(denominator, amountInWithFee)
if denominator.Sign() == 0 {
return nil, fmt.Errorf("division by zero in amountOut calculation")
}
amountOut := new(big.Int).Div(numerator, denominator)
return amountOut, nil
}
// ========== Kyber Math ==========
// NewKyberMath creates a new Kyber math calculator
func NewKyberMath() *KyberMath {
return &KyberMath{}
}
// CalculateAmountOutKyber calculates output for Kyber Elastic and Classic
func (k *KyberMath) CalculateAmountOutKyber(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
}
// CalculateAmountOutKyberClassic calculates output for Kyber Classic reserves
func (k *KyberMath) CalculateAmountOutKyberClassic(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")
}
// Kyber Classic has a different mechanism than Elastic
// This is a simplified implementation based on Kyber Classic formula
if fee == 0 {
fee = 2500 // Default 0.25% for Kyber Classic
}
// Calculate fee amount
feeFactor := big.NewInt(int64(10000 - fee))
amountInWithFee := new(big.Int).Mul(amountIn, feeFactor)
// Calculate amount out with consideration for Kyber's amplification factor
numerator := new(big.Int).Mul(amountInWithFee, reserveOut)
denominator := new(big.Int).Mul(reserveIn, big.NewInt(10000))
denominator.Add(denominator, amountInWithFee)
if denominator.Sign() == 0 {
return nil, fmt.Errorf("division by zero in amountOut calculation")
}
amountOut := new(big.Int).Div(numerator, denominator)
return amountOut, nil
}
// ========== 1Inch Math ==========
// NewOneInchMath creates a new 1Inch math calculator
func NewOneInchMath() *OneInchMath {
return &OneInchMath{}
}
// CalculateAmountOutOneInch calculates output for 1Inch aggregation
func (o *OneInchMath) CalculateAmountOutOneInch(amountIn *big.Int, multiHopPath []PathElement) (*big.Int, error) {
if amountIn.Sign() <= 0 {
return nil, fmt.Errorf("invalid amountIn")
}
result := new(big.Int).Set(amountIn)
// 1Inch aggregates multiple DEXs with different routing algorithms
// This is a simplified multi-hop calculation
for _, pathElement := range multiHopPath {
var amountOut *big.Int
var err error
switch pathElement.Protocol {
case "uniswap_v2":
amountOut, err = NewUniswapV2Math().CalculateAmountOut(result, pathElement.ReserveIn, pathElement.ReserveOut, pathElement.Fee)
case "uniswap_v3":
amountOut, err = NewUniswapV3Math().CalculateAmountOut(result, pathElement.SqrtPriceX96, pathElement.Liquidity, pathElement.Fee)
case "kyber_elastic", "kyber_classic":
amountOut, err = NewKyberMath().CalculateAmountOut(result, pathElement.SqrtPriceX96, pathElement.Liquidity, pathElement.Fee)
case "curve":
amountOut, err = NewCurveMath().CalculateAmountOut(result, pathElement.ReserveIn, pathElement.ReserveOut, pathElement.Fee)
default:
return nil, fmt.Errorf("unsupported protocol: %s", pathElement.Protocol)
}
if err != nil {
return nil, err
}
result = amountOut
}
return result, nil
}
// PathElement represents a single step in a multi-hop path
type PathElement struct {
Protocol string
ReserveIn *big.Int
ReserveOut *big.Int
SqrtPriceX96 *big.Int
Liquidity *big.Int
Fee uint32
}
// ========== Price Movement Detection Functions ==========
// WillSwapMovePrice determines if a swap will significantly move the price of a pool
func WillSwapMovePrice(amountIn, reserveIn, reserveOut *big.Int, threshold float64) (bool, float64, error) {
if amountIn.Sign() <= 0 || reserveIn.Sign() <= 0 || reserveOut.Sign() <= 0 {
return false, 0, fmt.Errorf("invalid parameters")
}
// Calculate price impact
priceBefore := new(big.Float).Quo(new(big.Float).SetInt(reserveOut), new(big.Float).SetInt(reserveIn))
// Calculate output for the proposed swap
amountOut, err := NewUniswapV2Math().CalculateAmountOut(amountIn, reserveIn, reserveOut, 3000)
if err != nil {
return false, 0, err
}
newReserveIn := new(big.Int).Add(reserveIn, amountIn)
newReserveOut := new(big.Int).Sub(reserveOut, amountOut)
priceAfter := new(big.Float).Quo(new(big.Float).SetInt(newReserveOut), new(big.Float).SetInt(newReserveIn))
// Calculate price impact as percentage
impact := new(big.Float).Sub(priceBefore, priceAfter)
impact.Quo(impact, priceBefore)
impact.Abs(impact)
impactFloat, _ := impact.Float64()
// Check if price impact exceeds threshold (e.g., 1%)
movesPrice := impactFloat >= threshold
return movesPrice, impactFloat, nil
}
// WillLiquidityMovePrice determines if a liquidity addition/removal will significantly move the price
func WillLiquidityMovePrice(amount0, amount1, reserve0, reserve1 *big.Int, threshold float64) (bool, float64, error) {
if reserve0.Sign() <= 0 || reserve1.Sign() <= 0 {
return false, 0, fmt.Errorf("invalid reserves")
}
// Check if amounts are valid for the provided reserves
if (amount0.Sign() < 0 && new(big.Int).Abs(amount0).Cmp(reserve0) > 0) ||
(amount1.Sign() < 0 && new(big.Int).Abs(amount1).Cmp(reserve1) > 0) {
return false, 0, fmt.Errorf("removing more liquidity than available")
}
// Calculate price before liquidity change
priceBefore := new(big.Float).Quo(new(big.Float).SetInt(reserve1), new(big.Float).SetInt(reserve0))
// Calculate new reserves after liquidity change
newReserve0 := new(big.Int).Add(reserve0, amount0)
newReserve1 := new(big.Int).Add(reserve1, amount1)
// Ensure reserves don't go negative
if newReserve0.Sign() <= 0 || newReserve1.Sign() <= 0 {
return false, 0, fmt.Errorf("liquidity change would result in negative reserves")
}
priceAfter := new(big.Float).Quo(new(big.Float).SetInt(newReserve1), new(big.Float).SetInt(newReserve0))
// Calculate price impact as percentage
impact := new(big.Float).Sub(priceBefore, priceAfter)
impact.Quo(impact, priceBefore)
impact.Abs(impact)
impactFloat, _ := impact.Float64()
// Check if price impact exceeds threshold
movesPrice := impactFloat >= threshold
return movesPrice, impactFloat, nil
}
// CalculateRequiredAmountForPriceMove calculates how much would need to be swapped to move price by a certain percentage
func CalculateRequiredAmountForPriceMove(targetPriceMove float64, reserveIn, reserveOut *big.Int) (*big.Int, error) {
if targetPriceMove <= 0 || reserveIn.Sign() <= 0 || reserveOut.Sign() <= 0 {
return nil, fmt.Errorf("invalid parameters")
}
// This is a simplified calculation - in practice this would require more complex math
// using binary search or other numerical methods
// This is an estimation, for exact calculation, we'd need to use more sophisticated methods
// such as binary search to find the exact amount required
estimatedAmount := new(big.Int).Div(reserveIn, big.NewInt(100)) // 1% of reserve as estimation
estimatedAmount.Mul(estimatedAmount, big.NewInt(int64(targetPriceMove*100)))
return estimatedAmount, nil
}