- Added comprehensive bounds checking to prevent buffer overruns in multicall parsing - Implemented graduated validation system (Strict/Moderate/Permissive) to reduce false positives - Added LRU caching system for address validation with 10-minute TTL - Enhanced ABI decoder with missing Universal Router and Arbitrum-specific DEX signatures - Fixed duplicate function declarations and import conflicts across multiple files - Added error recovery mechanisms with multiple fallback strategies - Updated tests to handle new validation behavior for suspicious addresses - Fixed parser test expectations for improved validation system - Applied gofmt formatting fixes to ensure code style compliance - Fixed mutex copying issues in monitoring package by introducing MetricsSnapshot - Resolved critical security vulnerabilities in heuristic address extraction - Progress: Updated TODO audit from 10% to 35% complete 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
298 lines
10 KiB
Go
298 lines
10 KiB
Go
package exchanges
|
|
|
|
import (
|
|
"fmt"
|
|
"math/big"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/ethclient"
|
|
|
|
"github.com/fraktal/mev-beta/internal/logger"
|
|
"github.com/fraktal/mev-beta/pkg/math"
|
|
)
|
|
|
|
// BalancerPoolDetector implements PoolDetector for Balancer
|
|
type BalancerPoolDetector struct {
|
|
client *ethclient.Client
|
|
logger *logger.Logger
|
|
config *ExchangeConfig
|
|
}
|
|
|
|
// NewBalancerPoolDetector creates a new Balancer pool detector
|
|
func NewBalancerPoolDetector(client *ethclient.Client, logger *logger.Logger, config *ExchangeConfig) *BalancerPoolDetector {
|
|
return &BalancerPoolDetector{
|
|
client: client,
|
|
logger: logger,
|
|
config: config,
|
|
}
|
|
}
|
|
|
|
// GetAllPools returns all pools containing the specified tokens
|
|
func (d *BalancerPoolDetector) GetAllPools(token0, token1 common.Address) ([]common.Address, error) {
|
|
// In a real implementation, this would query the Vault or pool registry contract
|
|
// For now, we'll return an empty slice
|
|
return []common.Address{}, nil
|
|
}
|
|
|
|
// GetPoolForPair returns the pool address for a specific token pair
|
|
func (d *BalancerPoolDetector) GetPoolForPair(token0, token1 common.Address) (common.Address, error) {
|
|
// In a real implementation, this would query the registry for pools
|
|
// containing both tokens
|
|
poolAddress := common.HexToAddress("0x0") // Placeholder
|
|
|
|
// For now, return empty address to indicate pool not found
|
|
return poolAddress, nil
|
|
}
|
|
|
|
// GetSupportedFeeTiers returns supported fee tiers for Balancer (varies by pool)
|
|
func (d *BalancerPoolDetector) GetSupportedFeeTiers() []int64 {
|
|
// Balancer pools can have different fee tiers
|
|
return []int64{100, 500, 1000, 5000} // 0.01%, 0.5%, 1%, 5% in basis points
|
|
}
|
|
|
|
// GetPoolType returns the pool type
|
|
func (d *BalancerPoolDetector) GetPoolType() string {
|
|
return "balancer_weighted_or_stable"
|
|
}
|
|
|
|
// BalancerLiquidityFetcher implements LiquidityFetcher for Balancer
|
|
type BalancerLiquidityFetcher struct {
|
|
client *ethclient.Client
|
|
logger *logger.Logger
|
|
config *ExchangeConfig
|
|
engine *math.ExchangePricingEngine
|
|
}
|
|
|
|
// NewBalancerLiquidityFetcher creates a new Balancer liquidity fetcher
|
|
func NewBalancerLiquidityFetcher(client *ethclient.Client, logger *logger.Logger, config *ExchangeConfig, engine *math.ExchangePricingEngine) *BalancerLiquidityFetcher {
|
|
return &BalancerLiquidityFetcher{
|
|
client: client,
|
|
logger: logger,
|
|
config: config,
|
|
engine: engine,
|
|
}
|
|
}
|
|
|
|
// GetPoolData fetches pool information for Balancer
|
|
func (f *BalancerLiquidityFetcher) GetPoolData(poolAddress common.Address) (*math.PoolData, error) {
|
|
// In a real implementation, this would call the pool contract to get reserves and weights
|
|
// For now, return a placeholder pool data with Balancer-specific fields
|
|
|
|
fee, err := math.NewUniversalDecimal(big.NewInt(250), 4, "FEE")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error creating fee decimal: %w", err)
|
|
}
|
|
|
|
reserve0Value := new(big.Int)
|
|
reserve0Value.SetString("1000000000000000000000", 10) // WETH - 1000 ETH in wei
|
|
reserve0, err := math.NewUniversalDecimal(reserve0Value, 18, "RESERVE0")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error creating reserve0 decimal: %w", err)
|
|
}
|
|
|
|
reserve1Value := new(big.Int)
|
|
reserve1Value.SetString("1000000000000", 10) // USDC - 1000000 USDC in smallest units
|
|
reserve1, err := math.NewUniversalDecimal(reserve1Value, 6, "RESERVE1")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error creating reserve1 decimal: %w", err)
|
|
}
|
|
|
|
weightsValue0 := new(big.Int)
|
|
weightsValue0.SetString("5000", 10) // 50% weight
|
|
weightsValue1 := new(big.Int)
|
|
weightsValue1.SetString("5000", 10) // 50% weight
|
|
|
|
weight0, err := math.NewUniversalDecimal(weightsValue0, 4, "WEIGHT0")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error creating weight0 decimal: %w", err)
|
|
}
|
|
|
|
weight1, err := math.NewUniversalDecimal(weightsValue1, 4, "WEIGHT1")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error creating weight1 decimal: %w", err)
|
|
}
|
|
|
|
swapFeeRateValue := new(big.Int)
|
|
swapFeeRateValue.SetString("250", 10) // 0.25%
|
|
swapFeeRate, err := math.NewUniversalDecimal(swapFeeRateValue, 4, "SWAP_FEE_RATE")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error creating swap fee rate decimal: %w", err)
|
|
}
|
|
|
|
return &math.PoolData{
|
|
Address: poolAddress.Hex(),
|
|
ExchangeType: math.ExchangeBalancer,
|
|
Fee: fee,
|
|
Token0: math.TokenInfo{Address: "0x0", Symbol: "WETH", Decimals: 18},
|
|
Token1: math.TokenInfo{Address: "0x1", Symbol: "USDC", Decimals: 6},
|
|
Reserve0: reserve0,
|
|
Reserve1: reserve1,
|
|
Weights: []*math.UniversalDecimal{
|
|
weight0, // 50% weight
|
|
weight1, // 50% weight
|
|
},
|
|
SwapFeeRate: swapFeeRate,
|
|
}, nil
|
|
}
|
|
|
|
// GetTokenReserves fetches reserves for a specific token pair in a pool
|
|
func (f *BalancerLiquidityFetcher) GetTokenReserves(poolAddress, token0, token1 common.Address) (*big.Int, *big.Int, error) {
|
|
// In a real implementation, this would query the pool contract
|
|
// For now, return placeholder values
|
|
reserve0 := new(big.Int)
|
|
reserve0.SetString("1000000000000000000000", 10) // WETH - 1000 ETH in wei
|
|
reserve1 := new(big.Int)
|
|
reserve1.SetString("1000000000000", 10) // USDC - 1000000 USDC in smallest units
|
|
return reserve0, reserve1, nil
|
|
}
|
|
|
|
// GetPoolPrice calculates the price of token1 in terms of token0 using Balancer's weighted formula
|
|
func (f *BalancerLiquidityFetcher) GetPoolPrice(poolAddress common.Address) (*big.Float, error) {
|
|
poolData, err := f.GetPoolData(poolAddress)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
pricer, err := f.engine.GetExchangePricer(poolData.ExchangeType)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
spotPrice, err := pricer.GetSpotPrice(poolData)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Convert the UniversalDecimal Value to a *big.Float
|
|
result := new(big.Float).SetInt(spotPrice.Value)
|
|
return result, nil
|
|
}
|
|
|
|
// GetLiquidityDepth calculates the liquidity depth for an amount using Balancer's weighted formula
|
|
func (f *BalancerLiquidityFetcher) GetLiquidityDepth(poolAddress, tokenIn common.Address, amount *big.Int) (*big.Int, error) {
|
|
// In a real implementation, this would calculate liquidity with Balancer's weighted formula
|
|
return amount, nil
|
|
}
|
|
|
|
// BalancerSwapRouter implements SwapRouter for Balancer
|
|
type BalancerSwapRouter struct {
|
|
client *ethclient.Client
|
|
logger *logger.Logger
|
|
config *ExchangeConfig
|
|
engine *math.ExchangePricingEngine
|
|
}
|
|
|
|
// NewBalancerSwapRouter creates a new Balancer swap router
|
|
func NewBalancerSwapRouter(client *ethclient.Client, logger *logger.Logger, config *ExchangeConfig, engine *math.ExchangePricingEngine) *BalancerSwapRouter {
|
|
return &BalancerSwapRouter{
|
|
client: client,
|
|
logger: logger,
|
|
config: config,
|
|
engine: engine,
|
|
}
|
|
}
|
|
|
|
// CalculateSwap calculates the expected output amount for a swap using Balancer's weighted formula
|
|
func (r *BalancerSwapRouter) CalculateSwap(tokenIn, tokenOut common.Address, amountIn *big.Int) (*big.Int, error) {
|
|
// Find pool for the token pair
|
|
poolAddress, err := r.findPoolForPair(tokenIn, tokenOut)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to find pool for pair: %w", err)
|
|
}
|
|
|
|
// Get pool data
|
|
poolData, err := r.GetPoolData(poolAddress)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get pool data: %w", err)
|
|
}
|
|
|
|
// Create a UniversalDecimal from the amountIn
|
|
decimalAmountIn, err := math.NewUniversalDecimal(amountIn, 18, "AMOUNT_IN")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error creating amount in decimal: %w", err)
|
|
}
|
|
|
|
// Get the pricer
|
|
pricer, err := r.engine.GetExchangePricer(poolData.ExchangeType)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Calculate amount out using Balancer's weighted formula
|
|
amountOut, err := pricer.CalculateAmountOut(decimalAmountIn, poolData)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return amountOut.Value, nil
|
|
}
|
|
|
|
// findPoolForPair finds the pool address for a given token pair
|
|
func (r *BalancerSwapRouter) findPoolForPair(token0, token1 common.Address) (common.Address, error) {
|
|
// In a real implementation, this would query the Balancer Vault or registry contract
|
|
// For now, return a placeholder address
|
|
return common.HexToAddress("0x0"), nil
|
|
}
|
|
|
|
// GetPoolData is a helper to fetch pool data (for internal use)
|
|
func (r *BalancerSwapRouter) GetPoolData(poolAddress common.Address) (*math.PoolData, error) {
|
|
fetcher := NewBalancerLiquidityFetcher(r.client, r.logger, r.config, r.engine)
|
|
return fetcher.GetPoolData(poolAddress)
|
|
}
|
|
|
|
// GenerateSwapData generates the calldata for a swap transaction using Balancer's batchSwap
|
|
func (r *BalancerSwapRouter) GenerateSwapData(tokenIn, tokenOut common.Address, amountIn, minAmountOut *big.Int, deadline *big.Int) ([]byte, error) {
|
|
// In a real implementation, this would generate the encoded function call
|
|
// For Balancer, this would typically be batchSwap or singleSwap
|
|
return []byte{}, nil
|
|
}
|
|
|
|
// GetSwapRoute returns the route for a swap (for Balancer, this can involve multiple tokens in a pool)
|
|
func (r *BalancerSwapRouter) GetSwapRoute(tokenIn, tokenOut common.Address) ([]common.Address, error) {
|
|
// For Balancer, the route could be within a multi-token pool
|
|
// For now, return the token pair as a direct route in the pool
|
|
return []common.Address{tokenIn, tokenOut}, nil
|
|
}
|
|
|
|
// ValidateSwap validates a Balancer swap before execution
|
|
func (r *BalancerSwapRouter) ValidateSwap(tokenIn, tokenOut common.Address, amountIn *big.Int) error {
|
|
if amountIn.Sign() <= 0 {
|
|
return fmt.Errorf("amountIn must be positive")
|
|
}
|
|
|
|
if tokenIn == tokenOut {
|
|
return fmt.Errorf("tokenIn and tokenOut cannot be the same")
|
|
}
|
|
|
|
if tokenIn == common.HexToAddress("0x0") || tokenOut == common.HexToAddress("0x0") {
|
|
return fmt.Errorf("invalid token addresses")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// RegisterBalancerWithRegistry registers Balancer implementation with the exchange registry
|
|
func RegisterBalancerWithRegistry(registry *ExchangeRegistry) error {
|
|
config := &ExchangeConfig{
|
|
Type: math.ExchangeBalancer,
|
|
Name: "Balancer",
|
|
FactoryAddress: common.HexToAddress("0x47b489bf5836f83abd8a1514a8b289b4f5a8a189"), // Balancer V2 Weighted Pool Factory placeholder
|
|
RouterAddress: common.HexToAddress("0xba12222222228d8ba445958a75a0704d566bf2c8"), // Balancer Vault
|
|
PoolInitCodeHash: "",
|
|
SwapSelector: []byte{0x3e, 0x2, 0x76, 0x5e}, // swap function
|
|
StableSwapSelector: []byte{},
|
|
ChainID: 1, // Ethereum mainnet
|
|
SupportsFlashSwaps: true,
|
|
RequiresApproval: true,
|
|
MaxHops: 3,
|
|
DefaultSlippagePercent: 0.3,
|
|
Url: "https://balancer.fi",
|
|
ApiUrl: "https://api.balancer.fi",
|
|
}
|
|
|
|
registry.exchanges[math.ExchangeBalancer] = config
|
|
|
|
// Register the implementations as well
|
|
return nil
|
|
}
|