Files
mev-beta/orig/pkg/uniswap/multicall.go
Administrator 803de231ba 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>
2025-11-10 10:14:26 +01:00

249 lines
6.2 KiB
Go

package uniswap
import (
"context"
"fmt"
"strings"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
)
// Multicall3 address on Arbitrum
var Multicall3Address = common.HexToAddress("0xcA11bde05977b3631167028862bE2a173976CA11")
// Multicall3 ABI (simplified - only aggregate3 function)
const Multicall3ABI = `[{
"inputs": [{
"components": [{
"internalType": "address",
"name": "target",
"type": "address"
}, {
"internalType": "bool",
"name": "allowFailure",
"type": "bool"
}, {
"internalType": "bytes",
"name": "callData",
"type": "bytes"
}],
"internalType": "struct Multicall3.Call3[]",
"name": "calls",
"type": "tuple[]"
}],
"name": "aggregate3",
"outputs": [{
"components": [{
"internalType": "bool",
"name": "success",
"type": "bool"
}, {
"internalType": "bytes",
"name": "returnData",
"type": "bytes"
}],
"internalType": "struct Multicall3.Result[]",
"name": "returnData",
"type": "tuple[]"
}],
"stateMutability": "payable",
"type": "function"
}]`
// Call3 represents a single call in Multicall3
type Call3 struct {
Target common.Address
AllowFailure bool
CallData []byte
}
// Result3 represents the result of a Multicall3 call
type Result3 struct {
Success bool
ReturnData []byte
}
// MulticallBatcher batches multiple RPC calls into a single Multicall3 transaction
type MulticallBatcher struct {
client *ethclient.Client
multicallABI abi.ABI
}
// NewMulticallBatcher creates a new multicall batcher
func NewMulticallBatcher(client *ethclient.Client) (*MulticallBatcher, error) {
parsedABI, err := abi.JSON(strings.NewReader(Multicall3ABI))
if err != nil {
return nil, fmt.Errorf("failed to parse Multicall3 ABI: %w", err)
}
return &MulticallBatcher{
client: client,
multicallABI: parsedABI,
}, nil
}
// ExecuteMulticall executes multiple calls in a single transaction
func (m *MulticallBatcher) ExecuteMulticall(ctx context.Context, calls []Call3) ([]Result3, error) {
if len(calls) == 0 {
return []Result3{}, nil
}
// Pack the aggregate3 call
data, err := m.multicallABI.Pack("aggregate3", calls)
if err != nil {
return nil, fmt.Errorf("failed to pack multicall data: %w", err)
}
// Create the call message
msg := ethereum.CallMsg{
To: &Multicall3Address,
Data: data,
}
// Execute the call
result, err := m.client.CallContract(ctx, msg, nil)
if err != nil {
return nil, fmt.Errorf("failed to execute multicall: %w", err)
}
// Unpack the results
var results []Result3
err = m.multicallABI.UnpackIntoInterface(&results, "aggregate3", result)
if err != nil {
return nil, fmt.Errorf("failed to unpack multicall results: %w", err)
}
return results, nil
}
// BatchPoolDataCalls batches slot0, liquidity, token0, token1, and fee calls for a pool
func (m *MulticallBatcher) BatchPoolDataCalls(ctx context.Context, poolAddress common.Address, poolABI abi.ABI) (
slot0Data []byte,
liquidityData []byte,
token0Data []byte,
token1Data []byte,
feeData []byte,
err error,
) {
// Prepare call data for each function
slot0CallData, err := poolABI.Pack("slot0")
if err != nil {
return nil, nil, nil, nil, nil, fmt.Errorf("failed to pack slot0: %w", err)
}
liquidityCallData, err := poolABI.Pack("liquidity")
if err != nil {
return nil, nil, nil, nil, nil, fmt.Errorf("failed to pack liquidity: %w", err)
}
token0CallData, err := poolABI.Pack("token0")
if err != nil {
return nil, nil, nil, nil, nil, fmt.Errorf("failed to pack token0: %w", err)
}
token1CallData, err := poolABI.Pack("token1")
if err != nil {
return nil, nil, nil, nil, nil, fmt.Errorf("failed to pack token1: %w", err)
}
feeCallData, err := poolABI.Pack("fee")
if err != nil {
return nil, nil, nil, nil, nil, fmt.Errorf("failed to pack fee: %w", err)
}
// Create multicall calls
calls := []Call3{
{Target: poolAddress, AllowFailure: true, CallData: slot0CallData},
{Target: poolAddress, AllowFailure: true, CallData: liquidityCallData},
{Target: poolAddress, AllowFailure: true, CallData: token0CallData},
{Target: poolAddress, AllowFailure: true, CallData: token1CallData},
{Target: poolAddress, AllowFailure: true, CallData: feeCallData},
}
// Execute multicall
results, err := m.ExecuteMulticall(ctx, calls)
if err != nil {
return nil, nil, nil, nil, nil, err
}
// Extract results
if len(results) != 5 {
return nil, nil, nil, nil, nil, fmt.Errorf("expected 5 results, got %d", len(results))
}
// Return raw data (caller will unpack based on success flags)
return results[0].ReturnData,
results[1].ReturnData,
results[2].ReturnData,
results[3].ReturnData,
results[4].ReturnData,
nil
}
// BatchMultiplePoolData batches pool data calls for multiple pools
func (m *MulticallBatcher) BatchMultiplePoolData(ctx context.Context, pools []common.Address, poolABI abi.ABI) (
results map[common.Address]map[string][]byte,
err error,
) {
results = make(map[common.Address]map[string][]byte)
if len(pools) == 0 {
return results, nil
}
// Prepare all calls
var allCalls []Call3
callMap := make(map[int]struct {
poolAddr common.Address
funcName string
})
functions := []string{"slot0", "liquidity", "token0", "token1", "fee"}
callIndex := 0
for _, poolAddr := range pools {
for _, funcName := range functions {
callData, err := poolABI.Pack(funcName)
if err != nil {
// Skip this call if packing fails
continue
}
allCalls = append(allCalls, Call3{
Target: poolAddr,
AllowFailure: true,
CallData: callData,
})
callMap[callIndex] = struct {
poolAddr common.Address
funcName string
}{poolAddr, funcName}
callIndex++
}
}
// Execute multicall
multicallResults, err := m.ExecuteMulticall(ctx, allCalls)
if err != nil {
return nil, fmt.Errorf("failed to execute multicall: %w", err)
}
// Parse results
for i, result := range multicallResults {
if info, ok := callMap[i]; ok {
if _, exists := results[info.poolAddr]; !exists {
results[info.poolAddr] = make(map[string][]byte)
}
if result.Success {
results[info.poolAddr][info.funcName] = result.ReturnData
}
}
}
return results, nil
}