docs: add comprehensive V2 requirements documentation
- Created MODULARITY_REQUIREMENTS.md with component independence rules - Created PROTOCOL_SUPPORT_REQUIREMENTS.md covering 13+ protocols - Created TESTING_REQUIREMENTS.md enforcing 100% coverage - Updated CLAUDE.md with strict feature/v2/* branch strategy Requirements documented: - Component modularity (standalone + integrated) - 100% test coverage enforcement (non-negotiable) - All DEX protocols (Uniswap V2/V3/V4, Curve, Balancer V2/V3, Kyber Classic/Elastic, Camelot V2/V3 with all Algebra variants) - Proper decimal handling (critical for calculations) - Pool caching with multi-index and O(1) mappings - Market building with essential arbitrage detection values - Price movement detection with decimal precision - Transaction building (single and batch execution) - Pool discovery and caching - Comprehensive validation at all layers 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
590
docs/planning/02_PROTOCOL_SUPPORT_REQUIREMENTS.md
Normal file
590
docs/planning/02_PROTOCOL_SUPPORT_REQUIREMENTS.md
Normal file
@@ -0,0 +1,590 @@
|
||||
# V2 Protocol Support Requirements
|
||||
|
||||
## Critical Requirement: Complete Protocol Coverage
|
||||
|
||||
**Every protocol MUST be parsed correctly with 100% accuracy and 100% test coverage.**
|
||||
|
||||
## Supported DEX Protocols (Complete List)
|
||||
|
||||
### Uniswap Family
|
||||
1. **Uniswap V2**
|
||||
- Constant product AMM (x * y = k)
|
||||
- Event: `Swap(address indexed sender, uint amount0In, uint amount1In, uint amount0Out, uint amount1Out, address indexed to)`
|
||||
- Pool info: token0, token1, reserves
|
||||
- Fee: 0.3% (30 basis points)
|
||||
|
||||
2. **Uniswap V3**
|
||||
- Concentrated liquidity AMM
|
||||
- Event: `Swap(address indexed sender, address indexed recipient, int256 amount0, int256 amount1, uint160 sqrtPriceX96, uint128 liquidity, int24 tick)`
|
||||
- Pool info: token0, token1, fee (500/3000/10000), tickSpacing, sqrtPriceX96, liquidity, tick
|
||||
- CRITICAL: Amounts are signed (int256), handle negative values correctly
|
||||
|
||||
3. **Uniswap V4** (planned)
|
||||
- Hooks-based architecture
|
||||
- Event: TBD (monitor for mainnet deployment)
|
||||
- Pool info: Dynamic based on hooks
|
||||
|
||||
### Curve Finance
|
||||
4. **Curve StableSwap**
|
||||
- Stable asset AMM
|
||||
- Event: `TokenExchange(address indexed buyer, int128 sold_id, uint256 tokens_sold, int128 bought_id, uint256 tokens_bought)`
|
||||
- Pool info: coins array, A (amplification coefficient), fee
|
||||
- CRITICAL: Use int128 for token IDs, proper decimal handling
|
||||
|
||||
### Balancer
|
||||
5. **Balancer V2**
|
||||
- Weighted pool AMM
|
||||
- Event: `Swap(bytes32 indexed poolId, address indexed tokenIn, address indexed tokenOut, uint256 amountIn, uint256 amountOut)`
|
||||
- Pool info: poolId, tokens array, weights, swapFee
|
||||
- CRITICAL: Uses poolId instead of pool address
|
||||
|
||||
6. **Balancer V3** (if deployed on Arbitrum)
|
||||
- Next-gen weighted pools
|
||||
- Event: Monitor for deployment
|
||||
- Pool info: TBD
|
||||
|
||||
### Kyber Network
|
||||
7. **Kyber Classic**
|
||||
- Dynamic reserve AMM
|
||||
- Event: `KyberTrade(address indexed src, address indexed dest, uint srcAmount, uint dstAmount)`
|
||||
- Pool info: reserveId, tokens, rate
|
||||
|
||||
8. **Kyber Elastic**
|
||||
- Concentrated liquidity (similar to Uniswap V3)
|
||||
- Event: `Swap(address indexed sender, address indexed recipient, int256 deltaQty0, int256 deltaQty1, uint160 sqrtP, uint128 liquidity, int24 currentTick)`
|
||||
- Pool info: token0, token1, swapFeeUnits, tickDistance
|
||||
- CRITICAL: Different field names than Uniswap V3 but similar math
|
||||
|
||||
### Camelot (Arbitrum Native)
|
||||
9. **Camelot V2**
|
||||
- Uniswap V2 fork with dynamic fees
|
||||
- Event: `Swap(address indexed sender, uint amount0In, uint amount1In, uint amount0Out, uint amount1Out, address indexed to)`
|
||||
- Pool info: token0, token1, stableSwap (boolean), fee0, fee1
|
||||
- CRITICAL: Fees can be different for token0 and token1
|
||||
|
||||
10. **Camelot V3 (Algebra V1)**
|
||||
- Event: `Swap(address indexed sender, address indexed recipient, int256 amount0, int256 amount1, uint160 price, uint128 liquidity, int24 tick)`
|
||||
- Pool info: token0, token1, fee, tickSpacing (from factory)
|
||||
- Algebra V1 specific
|
||||
|
||||
11. **Camelot V3 (Algebra V1.9)**
|
||||
- Enhanced Algebra with adaptive fees
|
||||
- Event: Same as Algebra V1 but with `communityFee` field
|
||||
- Pool info: token0, token1, fee, communityFee, tickSpacing
|
||||
- CRITICAL: Fee can be dynamic
|
||||
|
||||
12. **Camelot V3 (Algebra Integral)**
|
||||
- Latest Algebra version with plugins
|
||||
- Event: `Swap(address indexed sender, address indexed recipient, int256 amount0, int256 amount1, uint160 price, uint128 liquidity, int24 tick, uint16 fee)`
|
||||
- Pool info: token0, token1, fee (in event!), tickSpacing, plugin address
|
||||
- CRITICAL: Fee is emitted in event, not stored in pool
|
||||
|
||||
13. **Camelot V3 (Algebra Directional - All Versions)**
|
||||
- Directional liquidity (different fees for buy/sell)
|
||||
- Event: `Swap(address indexed sender, address indexed recipient, int256 amount0, int256 amount1, uint160 price, uint128 liquidity, int24 tick, uint16 feeZeroToOne, uint16 feeOneToZero)`
|
||||
- Pool info: token0, token1, feeZeroToOne, feeOneToZero, tickSpacing
|
||||
- CRITICAL: Two separate fees based on direction
|
||||
|
||||
## Required Pool Information Extraction
|
||||
|
||||
For EVERY pool discovered, we MUST extract:
|
||||
|
||||
### Essential Fields
|
||||
- `address` - Pool contract address
|
||||
- `token0` - First token address (MUST NOT be zero address)
|
||||
- `token1` - Second token address (MUST NOT be zero address)
|
||||
- `protocol` - Protocol type (UniswapV2, UniswapV3, etc.)
|
||||
- `poolType` - Pool type (ConstantProduct, Concentrated, StableSwap, etc.)
|
||||
|
||||
### Protocol-Specific Fields
|
||||
|
||||
#### V2-Style (Uniswap V2, SushiSwap, Camelot V2)
|
||||
- `reserve0` - Token0 reserves
|
||||
- `reserve1` - Token1 reserves
|
||||
- `fee` - Fee in basis points (usually 30 = 0.3%)
|
||||
|
||||
#### V3-Style (Uniswap V3, Kyber Elastic, Camelot V3)
|
||||
- `sqrtPriceX96` - Current price (Q64.96 format)
|
||||
- `liquidity` - Current liquidity
|
||||
- `tick` - Current tick
|
||||
- `tickSpacing` - Tick spacing (from factory)
|
||||
- `fee` - Fee tier (500/3000/10000) OR dynamic fee
|
||||
|
||||
#### Curve
|
||||
- `A` - Amplification coefficient
|
||||
- `fee` - Fee in basis points
|
||||
- `coins` - Array of coin addresses (can be > 2)
|
||||
|
||||
#### Balancer
|
||||
- `poolId` - Vault pool ID (bytes32)
|
||||
- `tokens` - Array of token addresses
|
||||
- `weights` - Array of token weights
|
||||
- `swapFee` - Swap fee percentage
|
||||
|
||||
### Metadata Fields
|
||||
- `factory` - Factory contract that created this pool
|
||||
- `createdBlock` - Block number when pool was created
|
||||
- `createdTx` - Transaction hash of pool creation
|
||||
- `lastUpdated` - Timestamp of last update
|
||||
- `token0Decimals` - Decimals for token0 (CRITICAL for calculations)
|
||||
- `token1Decimals` - Decimals for token1 (CRITICAL for calculations)
|
||||
- `token0Symbol` - Symbol for token0 (for logging)
|
||||
- `token1Symbol` - Symbol for token1 (for logging)
|
||||
|
||||
## Parsing Requirements
|
||||
|
||||
### 1. Sequencer Event Reading
|
||||
```go
|
||||
type SequencerReader interface {
|
||||
// Subscribe to new blocks
|
||||
Subscribe(ctx context.Context) (<-chan *types.Block, error)
|
||||
|
||||
// Get full transaction receipts
|
||||
GetReceipts(ctx context.Context, txHashes []common.Hash) ([]*types.Receipt, error)
|
||||
|
||||
// Parse block for DEX transactions
|
||||
ParseBlock(block *types.Block) ([]*Transaction, error)
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Multi-Protocol Parser
|
||||
```go
|
||||
type ProtocolParser interface {
|
||||
// Identify if transaction is for this protocol
|
||||
IsProtocolTransaction(tx *types.Transaction) bool
|
||||
|
||||
// Parse swap event
|
||||
ParseSwapEvent(log *types.Log) (*SwapEvent, error)
|
||||
|
||||
// Parse mint/burn events
|
||||
ParseLiquidityEvent(log *types.Log) (*LiquidityEvent, error)
|
||||
|
||||
// Extract pool info from logs
|
||||
ExtractPoolInfo(logs []*types.Log) (*PoolInfo, error)
|
||||
|
||||
// Validate parsed data
|
||||
Validate(event *SwapEvent) error
|
||||
}
|
||||
|
||||
type SwapEvent struct {
|
||||
PoolAddress common.Address
|
||||
Token0 common.Address // MUST NOT be zero
|
||||
Token1 common.Address // MUST NOT be zero
|
||||
Amount0In *big.Int // MUST NOT be nil or zero (one of In/Out)
|
||||
Amount0Out *big.Int
|
||||
Amount1In *big.Int // MUST NOT be nil or zero (one of In/Out)
|
||||
Amount1Out *big.Int
|
||||
Sender common.Address
|
||||
Recipient common.Address
|
||||
TxHash common.Hash
|
||||
BlockNumber uint64
|
||||
LogIndex uint
|
||||
Timestamp uint64
|
||||
|
||||
// V3-specific
|
||||
SqrtPriceX96 *big.Int
|
||||
Liquidity *big.Int
|
||||
Tick int24
|
||||
|
||||
// Protocol identification
|
||||
Protocol Protocol
|
||||
PoolType PoolType
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Amount Parsing Rules
|
||||
|
||||
**CRITICAL: Proper Decimal Handling**
|
||||
|
||||
```go
|
||||
// Example: Parse Uniswap V2 swap
|
||||
func (p *UniswapV2Parser) ParseSwap(log *types.Log) (*SwapEvent, error) {
|
||||
// Decode event
|
||||
event := new(UniswapV2SwapEvent)
|
||||
err := p.abi.UnpackIntoInterface(event, "Swap", log.Data)
|
||||
|
||||
// Get token decimals (CRITICAL!)
|
||||
poolInfo := p.cache.GetPool(log.Address)
|
||||
token0Decimals := poolInfo.Token0Decimals
|
||||
token1Decimals := poolInfo.Token1Decimals
|
||||
|
||||
// MUST use proper decimal scaling
|
||||
amount0In := ScaleAmount(event.Amount0In, token0Decimals)
|
||||
amount0Out := ScaleAmount(event.Amount0Out, token0Decimals)
|
||||
amount1In := ScaleAmount(event.Amount1In, token1Decimals)
|
||||
amount1Out := ScaleAmount(event.Amount1Out, token1Decimals)
|
||||
|
||||
return &SwapEvent{
|
||||
Amount0In: amount0In,
|
||||
Amount0Out: amount0Out,
|
||||
Amount1In: amount1In,
|
||||
Amount1Out: amount1Out,
|
||||
}
|
||||
}
|
||||
|
||||
// Decimal scaling helper
|
||||
func ScaleAmount(amount *big.Int, decimals uint8) *big.Int {
|
||||
// Scale to 18 decimals for internal representation
|
||||
scale := new(big.Int).Exp(
|
||||
big.NewInt(10),
|
||||
big.NewInt(int64(18 - decimals)),
|
||||
nil,
|
||||
)
|
||||
return new(big.Int).Mul(amount, scale)
|
||||
}
|
||||
```
|
||||
|
||||
## Pool Discovery Requirements
|
||||
|
||||
### 1. Factory Event Monitoring
|
||||
```go
|
||||
type PoolDiscovery interface {
|
||||
// Monitor factory for pool creation
|
||||
MonitorFactory(ctx context.Context, factoryAddress common.Address) error
|
||||
|
||||
// Discover pools from transaction
|
||||
DiscoverFromTransaction(tx *types.Transaction, receipt *types.Receipt) ([]*PoolInfo, error)
|
||||
|
||||
// Verify pool exists and get info
|
||||
VerifyPool(ctx context.Context, poolAddress common.Address) (*PoolInfo, error)
|
||||
|
||||
// Save discovered pool
|
||||
SavePool(pool *PoolInfo) error
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Pool Caching Strategy
|
||||
```go
|
||||
type PoolCache interface {
|
||||
// Add pool to cache
|
||||
Add(pool *PoolInfo) error
|
||||
|
||||
// Get pool by address (O(1))
|
||||
Get(address common.Address) (*PoolInfo, error)
|
||||
|
||||
// Get pools by token pair (O(1))
|
||||
GetByTokenPair(token0, token1 common.Address) ([]*PoolInfo, error)
|
||||
|
||||
// Get pools by protocol (O(1))
|
||||
GetByProtocol(protocol Protocol) ([]*PoolInfo, error)
|
||||
|
||||
// Get top pools by liquidity
|
||||
GetTopByLiquidity(limit int) ([]*PoolInfo, error)
|
||||
|
||||
// Update pool data
|
||||
Update(address common.Address, updates *PoolUpdates) error
|
||||
|
||||
// Save to persistent storage
|
||||
SaveToDisk(path string) error
|
||||
|
||||
// Load from persistent storage
|
||||
LoadFromDisk(path string) error
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Market Building with Mapping
|
||||
```go
|
||||
type MarketBuilder interface {
|
||||
// Build market from pools
|
||||
BuildMarket(pools []*PoolInfo) (*Market, error)
|
||||
|
||||
// Update market on new swap
|
||||
UpdateOnSwap(market *Market, swap *SwapEvent) (*PriceMovement, error)
|
||||
|
||||
// Get market by token pair (using mapping for O(1) access)
|
||||
GetMarket(token0, token1 common.Address) (*Market, error)
|
||||
}
|
||||
|
||||
type Market struct {
|
||||
Token0 common.Address
|
||||
Token1 common.Address
|
||||
Pools map[common.Address]*PoolState // Mapping for O(1) access
|
||||
BestBid *big.Float // Best price to buy token0
|
||||
BestAsk *big.Float // Best price to sell token0
|
||||
MidPrice *big.Float // Mid-market price
|
||||
Liquidity *big.Int // Total liquidity
|
||||
LastUpdate uint64 // Timestamp
|
||||
}
|
||||
|
||||
type PoolState struct {
|
||||
Address common.Address
|
||||
Protocol Protocol
|
||||
CurrentPrice *big.Float // With proper decimals
|
||||
Reserve0 *big.Int
|
||||
Reserve1 *big.Int
|
||||
Fee uint32
|
||||
|
||||
// V3-specific
|
||||
SqrtPriceX96 *big.Int
|
||||
Liquidity *big.Int
|
||||
Tick int24
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Price Movement Detection
|
||||
```go
|
||||
type PriceMovement struct {
|
||||
Market *Market
|
||||
OldPrice *big.Float // Before swap
|
||||
NewPrice *big.Float // After swap
|
||||
PriceChange *big.Float // Absolute change
|
||||
PercentMove float64 // Percentage movement
|
||||
TriggeredBy *SwapEvent
|
||||
Timestamp uint64
|
||||
|
||||
// Arbitrage opportunity flag
|
||||
IsArbitrageOpportunity bool
|
||||
ExpectedProfit *big.Float
|
||||
}
|
||||
|
||||
// CRITICAL: Proper decimal handling in price calculation
|
||||
func CalculatePriceMovement(market *Market, swap *SwapEvent) (*PriceMovement, error) {
|
||||
oldPrice := market.MidPrice
|
||||
|
||||
// Update pool state with proper decimals
|
||||
pool := market.Pools[swap.PoolAddress]
|
||||
pool.Reserve0 = new(big.Int).Sub(pool.Reserve0, swap.Amount0Out)
|
||||
pool.Reserve0 = new(big.Int).Add(pool.Reserve0, swap.Amount0In)
|
||||
pool.Reserve1 = new(big.Int).Sub(pool.Reserve1, swap.Amount1Out)
|
||||
pool.Reserve1 = new(big.Int).Add(pool.Reserve1, swap.Amount1In)
|
||||
|
||||
// Calculate new price with EXACT decimal precision
|
||||
newPrice := CalculatePrice(pool.Reserve0, pool.Reserve1,
|
||||
market.Token0Decimals, market.Token1Decimals)
|
||||
|
||||
// Calculate percentage movement
|
||||
priceChange := new(big.Float).Sub(newPrice, oldPrice)
|
||||
percentMove := new(big.Float).Quo(priceChange, oldPrice)
|
||||
percentMove.Mul(percentMove, big.NewFloat(100))
|
||||
|
||||
percent, _ := percentMove.Float64()
|
||||
|
||||
return &PriceMovement{
|
||||
Market: market,
|
||||
OldPrice: oldPrice,
|
||||
NewPrice: newPrice,
|
||||
PriceChange: priceChange,
|
||||
PercentMove: percent,
|
||||
TriggeredBy: swap,
|
||||
Timestamp: swap.Timestamp,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Arbitrage Detection Requirements
|
||||
|
||||
### 1. Essential Market Values
|
||||
```go
|
||||
type ArbitrageMarket struct {
|
||||
// Token pair
|
||||
TokenA common.Address
|
||||
TokenB common.Address
|
||||
|
||||
// All pools for this pair
|
||||
Pools map[common.Address]*PoolState // O(1) access
|
||||
|
||||
// Price quotes from each pool
|
||||
Quotes map[common.Address]*Quote
|
||||
|
||||
// Liquidity depth
|
||||
LiquidityDepth map[common.Address]*LiquidityBracket
|
||||
|
||||
// Best execution path
|
||||
BestBuyPool common.Address
|
||||
BestSellPool common.Address
|
||||
|
||||
// Arbitrage opportunity
|
||||
SpreadPercent float64
|
||||
ExpectedProfit *big.Float
|
||||
OptimalAmount *big.Int
|
||||
}
|
||||
|
||||
type Quote struct {
|
||||
Pool common.Address
|
||||
InputAmount *big.Int
|
||||
OutputAmount *big.Int
|
||||
Price *big.Float // With exact decimals
|
||||
Fee uint32
|
||||
Slippage float64 // Expected slippage %
|
||||
}
|
||||
|
||||
type LiquidityBracket struct {
|
||||
Pool common.Address
|
||||
Amounts []*big.Int // Different trade sizes
|
||||
Outputs []*big.Int // Expected outputs
|
||||
Slippages []float64 // Slippage at each amount
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Arbitrage Calculator
|
||||
```go
|
||||
type ArbitrageCalculator interface {
|
||||
// Find arbitrage opportunities
|
||||
FindOpportunities(market *ArbitrageMarket) ([]*Opportunity, error)
|
||||
|
||||
// Calculate optimal trade size
|
||||
CalculateOptimalSize(opp *Opportunity) (*big.Int, error)
|
||||
|
||||
// Calculate expected profit (after gas)
|
||||
CalculateProfit(opp *Opportunity, tradeSize *big.Int) (*big.Float, error)
|
||||
|
||||
// Build execution transaction
|
||||
BuildTransaction(opp *Opportunity, tradeSize *big.Int) (*types.Transaction, error)
|
||||
}
|
||||
|
||||
type Opportunity struct {
|
||||
Market *ArbitrageMarket
|
||||
BuyPool common.Address
|
||||
SellPool common.Address
|
||||
BuyPrice *big.Float // Exact decimals
|
||||
SellPrice *big.Float // Exact decimals
|
||||
Spread float64 // Percentage
|
||||
OptimalAmount *big.Int
|
||||
ExpectedProfit *big.Float // After fees and gas
|
||||
GasCost *big.Int
|
||||
NetProfit *big.Float // After ALL costs
|
||||
Confidence float64 // 0-1 confidence score
|
||||
}
|
||||
```
|
||||
|
||||
## Transaction Building Requirements
|
||||
|
||||
### 1. Single Execution
|
||||
```go
|
||||
type SingleExecutor interface {
|
||||
// Execute single arbitrage trade
|
||||
Execute(ctx context.Context, opp *Opportunity) (*types.Transaction, error)
|
||||
|
||||
// Build transaction data
|
||||
BuildTxData(opp *Opportunity) ([]byte, error)
|
||||
|
||||
// Estimate gas
|
||||
EstimateGas(ctx context.Context, txData []byte) (uint64, error)
|
||||
|
||||
// Sign and send
|
||||
SignAndSend(ctx context.Context, tx *types.Transaction) (common.Hash, error)
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Batch Execution
|
||||
```go
|
||||
type BatchExecutor interface {
|
||||
// Execute multiple arbitrage trades in one transaction
|
||||
BatchExecute(ctx context.Context, opps []*Opportunity) (*types.Transaction, error)
|
||||
|
||||
// Build multicall data
|
||||
BuildMulticall(opps []*Opportunity) ([]byte, error)
|
||||
|
||||
// Optimize batch order for maximum profit
|
||||
OptimizeBatchOrder(opps []*Opportunity) []*Opportunity
|
||||
|
||||
// Calculate batch gas savings
|
||||
CalculateGasSavings(opps []*Opportunity) (*big.Int, error)
|
||||
}
|
||||
|
||||
// Example multicall structure
|
||||
type Multicall struct {
|
||||
Targets []common.Address // Contract addresses
|
||||
Calldatas [][]byte // Call data for each
|
||||
Values []*big.Int // ETH value for each
|
||||
}
|
||||
```
|
||||
|
||||
## Validation Requirements
|
||||
|
||||
### 1. Pool Data Validation
|
||||
```go
|
||||
// MUST validate ALL fields
|
||||
func ValidatePoolInfo(pool *PoolInfo) error {
|
||||
if pool.Address == (common.Address{}) {
|
||||
return errors.New("pool address is zero")
|
||||
}
|
||||
if pool.Token0 == (common.Address{}) {
|
||||
return errors.New("token0 is zero address")
|
||||
}
|
||||
if pool.Token1 == (common.Address{}) {
|
||||
return errors.New("token1 is zero address")
|
||||
}
|
||||
if pool.Token0 == pool.Token1 {
|
||||
return errors.New("token0 and token1 are the same")
|
||||
}
|
||||
if pool.Token0Decimals == 0 || pool.Token0Decimals > 18 {
|
||||
return errors.New("invalid token0 decimals")
|
||||
}
|
||||
if pool.Token1Decimals == 0 || pool.Token1Decimals > 18 {
|
||||
return errors.New("invalid token1 decimals")
|
||||
}
|
||||
|
||||
// Protocol-specific validation
|
||||
switch pool.PoolType {
|
||||
case PoolTypeConstantProduct:
|
||||
if pool.Reserve0 == nil || pool.Reserve0.Sign() <= 0 {
|
||||
return errors.New("invalid reserve0")
|
||||
}
|
||||
if pool.Reserve1 == nil || pool.Reserve1.Sign() <= 0 {
|
||||
return errors.New("invalid reserve1")
|
||||
}
|
||||
case PoolTypeConcentrated:
|
||||
if pool.SqrtPriceX96 == nil || pool.SqrtPriceX96.Sign() <= 0 {
|
||||
return errors.New("invalid sqrtPriceX96")
|
||||
}
|
||||
if pool.Liquidity == nil || pool.Liquidity.Sign() < 0 {
|
||||
return errors.New("invalid liquidity")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Swap Event Validation
|
||||
```go
|
||||
func ValidateSwapEvent(event *SwapEvent) error {
|
||||
// Zero address checks
|
||||
if event.Token0 == (common.Address{}) {
|
||||
return errors.New("token0 is zero address")
|
||||
}
|
||||
if event.Token1 == (common.Address{}) {
|
||||
return errors.New("token1 is zero address")
|
||||
}
|
||||
if event.PoolAddress == (common.Address{}) {
|
||||
return errors.New("pool address is zero")
|
||||
}
|
||||
|
||||
// Amount validation (at least one must be non-zero)
|
||||
hasAmount0 := (event.Amount0In != nil && event.Amount0In.Sign() > 0) ||
|
||||
(event.Amount0Out != nil && event.Amount0Out.Sign() > 0)
|
||||
hasAmount1 := (event.Amount1In != nil && event.Amount1In.Sign() > 0) ||
|
||||
(event.Amount1Out != nil && event.Amount1Out.Sign() > 0)
|
||||
|
||||
if !hasAmount0 {
|
||||
return errors.New("both amount0In and amount0Out are zero")
|
||||
}
|
||||
if !hasAmount1 {
|
||||
return errors.New("both amount1In and amount1Out are zero")
|
||||
}
|
||||
|
||||
// Logical validation (can't have both in and out for same token)
|
||||
if event.Amount0In != nil && event.Amount0In.Sign() > 0 &&
|
||||
event.Amount0Out != nil && event.Amount0Out.Sign() > 0 {
|
||||
return errors.New("amount0In and amount0Out both positive")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
## Testing Requirements
|
||||
|
||||
See `03_TESTING_REQUIREMENTS.md` for comprehensive testing strategy.
|
||||
|
||||
Each parser MUST have:
|
||||
- Unit tests for all event types (100% coverage)
|
||||
- Integration tests with real Arbiscan data
|
||||
- Edge case tests (zero amounts, max values, etc.)
|
||||
- Decimal precision tests
|
||||
- Gas estimation tests
|
||||
|
||||
---
|
||||
|
||||
**CRITICAL**: All protocols must be supported. All decimals must be handled correctly. All validation must pass. No exceptions.
|
||||
Reference in New Issue
Block a user