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>
This commit is contained in:
Administrator
2025-11-10 10:14:26 +01:00
parent 1773daffe7
commit 803de231ba
411 changed files with 20390 additions and 8680 deletions

337
orig/pkg/dex/balancer.go Normal file
View File

@@ -0,0 +1,337 @@
package dex
import (
"context"
"fmt"
"math/big"
"strings"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
)
// BalancerDecoder implements DEXDecoder for Balancer
type BalancerDecoder struct {
*BaseDecoder
vaultABI abi.ABI
poolABI abi.ABI
}
// Balancer Vault ABI (minimal)
const balancerVaultABI = `[
{
"name": "swap",
"type": "function",
"inputs": [
{
"name": "singleSwap",
"type": "tuple",
"components": [
{"name": "poolId", "type": "bytes32"},
{"name": "kind", "type": "uint8"},
{"name": "assetIn", "type": "address"},
{"name": "assetOut", "type": "address"},
{"name": "amount", "type": "uint256"},
{"name": "userData", "type": "bytes"}
]
},
{
"name": "funds",
"type": "tuple",
"components": [
{"name": "sender", "type": "address"},
{"name": "fromInternalBalance", "type": "bool"},
{"name": "recipient", "type": "address"},
{"name": "toInternalBalance", "type": "bool"}
]
},
{"name": "limit", "type": "uint256"},
{"name": "deadline", "type": "uint256"}
],
"outputs": [{"name": "amountCalculated", "type": "uint256"}]
},
{
"name": "getPoolTokens",
"type": "function",
"inputs": [{"name": "poolId", "type": "bytes32"}],
"outputs": [
{"name": "tokens", "type": "address[]"},
{"name": "balances", "type": "uint256[]"},
{"name": "lastChangeBlock", "type": "uint256"}
],
"stateMutability": "view"
}
]`
// Balancer Pool ABI (minimal)
const balancerPoolABI = `[
{
"name": "getPoolId",
"type": "function",
"inputs": [],
"outputs": [{"name": "", "type": "bytes32"}],
"stateMutability": "view"
},
{
"name": "getNormalizedWeights",
"type": "function",
"inputs": [],
"outputs": [{"name": "", "type": "uint256[]"}],
"stateMutability": "view"
},
{
"name": "getSwapFeePercentage",
"type": "function",
"inputs": [],
"outputs": [{"name": "", "type": "uint256"}],
"stateMutability": "view"
}
]`
// Balancer Vault address on Arbitrum
var BalancerVaultAddress = common.HexToAddress("0xBA12222222228d8Ba445958a75a0704d566BF2C8")
// NewBalancerDecoder creates a new Balancer decoder
func NewBalancerDecoder(client *ethclient.Client) *BalancerDecoder {
vaultABI, _ := abi.JSON(strings.NewReader(balancerVaultABI))
poolABI, _ := abi.JSON(strings.NewReader(balancerPoolABI))
return &BalancerDecoder{
BaseDecoder: NewBaseDecoder(ProtocolBalancer, client),
vaultABI: vaultABI,
poolABI: poolABI,
}
}
// DecodeSwap decodes a Balancer swap transaction
func (d *BalancerDecoder) DecodeSwap(tx *types.Transaction) (*SwapInfo, error) {
data := tx.Data()
if len(data) < 4 {
return nil, fmt.Errorf("transaction data too short")
}
method, err := d.vaultABI.MethodById(data[:4])
if err != nil {
return nil, fmt.Errorf("failed to get method: %w", err)
}
if method.Name != "swap" {
return nil, fmt.Errorf("unsupported method: %s", method.Name)
}
params := make(map[string]interface{})
if err := method.Inputs.UnpackIntoMap(params, data[4:]); err != nil {
return nil, fmt.Errorf("failed to unpack params: %w", err)
}
// Extract singleSwap struct
singleSwap := params["singleSwap"].(struct {
PoolId [32]byte
Kind uint8
AssetIn common.Address
AssetOut common.Address
Amount *big.Int
UserData []byte
})
funds := params["funds"].(struct {
Sender common.Address
FromInternalBalance bool
Recipient common.Address
ToInternalBalance bool
})
return &SwapInfo{
Protocol: ProtocolBalancer,
TokenIn: singleSwap.AssetIn,
TokenOut: singleSwap.AssetOut,
AmountIn: singleSwap.Amount,
AmountOut: params["limit"].(*big.Int),
Recipient: funds.Recipient,
Deadline: params["deadline"].(*big.Int),
Fee: big.NewInt(25), // 0.25% typical
}, nil
}
// GetPoolReserves fetches current pool reserves for Balancer
func (d *BalancerDecoder) GetPoolReserves(ctx context.Context, client *ethclient.Client, poolAddress common.Address) (*PoolReserves, error) {
// Get pool ID
poolIdData, err := client.CallContract(ctx, ethereum.CallMsg{
To: &poolAddress,
Data: d.poolABI.Methods["getPoolId"].ID,
}, nil)
if err != nil {
return nil, fmt.Errorf("failed to get pool ID: %w", err)
}
poolId := [32]byte{}
copy(poolId[:], poolIdData)
// Get pool tokens and balances from Vault
getPoolTokensCalldata, err := d.vaultABI.Pack("getPoolTokens", poolId)
if err != nil {
return nil, fmt.Errorf("failed to pack getPoolTokens: %w", err)
}
tokensData, err := client.CallContract(ctx, ethereum.CallMsg{
To: &BalancerVaultAddress,
Data: getPoolTokensCalldata,
}, nil)
if err != nil {
return nil, fmt.Errorf("failed to get pool tokens: %w", err)
}
var result struct {
Tokens []common.Address
Balances []*big.Int
LastChangeBlock *big.Int
}
if err := d.vaultABI.UnpackIntoInterface(&result, "getPoolTokens", tokensData); err != nil {
return nil, fmt.Errorf("failed to unpack pool tokens: %w", err)
}
if len(result.Tokens) < 2 {
return nil, fmt.Errorf("pool has less than 2 tokens")
}
// Get weights
weightsData, err := client.CallContract(ctx, ethereum.CallMsg{
To: &poolAddress,
Data: d.poolABI.Methods["getNormalizedWeights"].ID,
}, nil)
if err != nil {
return nil, fmt.Errorf("failed to get weights: %w", err)
}
var weights []*big.Int
if err := d.poolABI.UnpackIntoInterface(&weights, "getNormalizedWeights", weightsData); err != nil {
return nil, fmt.Errorf("failed to unpack weights: %w", err)
}
// Get swap fee
feeData, err := client.CallContract(ctx, ethereum.CallMsg{
To: &poolAddress,
Data: d.poolABI.Methods["getSwapFeePercentage"].ID,
}, nil)
if err != nil {
return nil, fmt.Errorf("failed to get swap fee: %w", err)
}
fee := new(big.Int).SetBytes(feeData)
return &PoolReserves{
Token0: result.Tokens[0],
Token1: result.Tokens[1],
Reserve0: result.Balances[0],
Reserve1: result.Balances[1],
Protocol: ProtocolBalancer,
PoolAddress: poolAddress,
Fee: fee,
Weights: weights,
}, nil
}
// CalculateOutput calculates expected output for Balancer weighted pools
func (d *BalancerDecoder) CalculateOutput(amountIn *big.Int, reserves *PoolReserves, tokenIn common.Address) (*big.Int, error) {
if amountIn == nil || amountIn.Sign() <= 0 {
return nil, fmt.Errorf("invalid amountIn")
}
if reserves.Weights == nil || len(reserves.Weights) < 2 {
return nil, fmt.Errorf("missing pool weights")
}
var balanceIn, balanceOut, weightIn, weightOut *big.Int
if tokenIn == reserves.Token0 {
balanceIn = reserves.Reserve0
balanceOut = reserves.Reserve1
weightIn = reserves.Weights[0]
weightOut = reserves.Weights[1]
} else if tokenIn == reserves.Token1 {
balanceIn = reserves.Reserve1
balanceOut = reserves.Reserve0
weightIn = reserves.Weights[1]
weightOut = reserves.Weights[0]
} else {
return nil, fmt.Errorf("tokenIn not in pool")
}
if balanceIn.Sign() == 0 || balanceOut.Sign() == 0 {
return nil, fmt.Errorf("insufficient liquidity")
}
// Balancer weighted pool formula:
// amountOut = balanceOut * (1 - (balanceIn / (balanceIn + amountIn))^(weightIn/weightOut))
// Simplified approximation for demonstration
// Apply fee
fee := reserves.Fee
if fee == nil {
fee = big.NewInt(25) // 0.25% = 25 basis points
}
amountInAfterFee := new(big.Int).Mul(amountIn, new(big.Int).Sub(big.NewInt(10000), fee))
amountInAfterFee.Div(amountInAfterFee, big.NewInt(10000))
// Simplified calculation: use ratio of weights
// amountOut ≈ amountIn * (balanceOut/balanceIn) * (weightOut/weightIn)
amountOut := new(big.Int).Mul(amountInAfterFee, balanceOut)
amountOut.Div(amountOut, balanceIn)
// Adjust by weight ratio (simplified)
amountOut.Mul(amountOut, weightOut)
amountOut.Div(amountOut, weightIn)
// For production: Implement full weighted pool math with exponentiation
// amountOut = balanceOut * (1 - (balanceIn / (balanceIn + amountInAfterFee))^(weightIn/weightOut))
return amountOut, nil
}
// CalculatePriceImpact calculates price impact for Balancer
func (d *BalancerDecoder) CalculatePriceImpact(amountIn *big.Int, reserves *PoolReserves, tokenIn common.Address) (float64, error) {
if amountIn == nil || amountIn.Sign() <= 0 {
return 0, nil
}
var balanceIn *big.Int
if tokenIn == reserves.Token0 {
balanceIn = reserves.Reserve0
} else {
balanceIn = reserves.Reserve1
}
if balanceIn.Sign() == 0 {
return 1.0, nil
}
// Price impact for weighted pools is lower than constant product
amountInFloat := new(big.Float).SetInt(amountIn)
balanceFloat := new(big.Float).SetInt(balanceIn)
ratio := new(big.Float).Quo(amountInFloat, balanceFloat)
// Weighted pools have better capital efficiency
impact := new(big.Float).Mul(ratio, big.NewFloat(0.8))
impactValue, _ := impact.Float64()
return impactValue, nil
}
// GetQuote gets a price quote for Balancer
func (d *BalancerDecoder) GetQuote(ctx context.Context, client *ethclient.Client, tokenIn, tokenOut common.Address, amountIn *big.Int) (*PriceQuote, error) {
// TODO: Implement pool lookup via Balancer subgraph or on-chain registry
return nil, fmt.Errorf("GetQuote not yet implemented for Balancer")
}
// IsValidPool checks if a pool is a valid Balancer pool
func (d *BalancerDecoder) IsValidPool(ctx context.Context, client *ethclient.Client, poolAddress common.Address) (bool, error) {
// Try to call getPoolId() - if it succeeds, it's a Balancer pool
_, err := client.CallContract(ctx, ethereum.CallMsg{
To: &poolAddress,
Data: d.poolABI.Methods["getPoolId"].ID,
}, nil)
return err == nil, nil
}