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>
379 lines
13 KiB
Go
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
|
|
}
|