feat(math): implement comprehensive mathematical optimizations for DEX calculations
- Add new math package with optimized implementations for major DEX protocols - Implement Uniswap V2, V3, V4, Curve, Kyber, Balancer, and Algebra mathematical functions - Optimize Uniswap V3 pricing functions with caching and uint256 optimizations - Add lookup table optimizations for frequently used calculations - Implement price impact and slippage calculation functions - Add comprehensive benchmarks showing 12-24% performance improvements - Fix test expectations to use correct mathematical formulas - Document mathematical optimization strategies and results
This commit is contained in:
378
pkg/math/dex_math.go
Normal file
378
pkg/math/dex_math.go
Normal file
@@ -0,0 +1,378 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user