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:
408
docs/planning/01_MODULARITY_REQUIREMENTS.md
Normal file
408
docs/planning/01_MODULARITY_REQUIREMENTS.md
Normal file
@@ -0,0 +1,408 @@
|
||||
# V2 Modularity Requirements
|
||||
|
||||
## Core Principle: Standalone Components
|
||||
|
||||
**Every component MUST be able to run independently OR as part of the integrated system.**
|
||||
|
||||
## Component Independence Rules
|
||||
|
||||
### 1. Zero Hard Dependencies
|
||||
- Each component communicates through well-defined interfaces only
|
||||
- NO direct imports between sibling components
|
||||
- NO shared state except through explicit interfaces
|
||||
- Each component has its own configuration
|
||||
|
||||
### 2. Standalone Executability
|
||||
Every component must support:
|
||||
```go
|
||||
// Example: Each parser can be used standalone
|
||||
parser := uniswap_v2.NewParser(logger, cache)
|
||||
event, err := parser.ParseLog(log, tx)
|
||||
|
||||
// OR as part of factory
|
||||
factory := parsers.NewFactory()
|
||||
factory.Register(ProtocolUniswapV2, parser)
|
||||
```
|
||||
|
||||
### 3. Interface-First Design
|
||||
Define interfaces BEFORE implementation:
|
||||
```go
|
||||
// pkg/parsers/interface.go
|
||||
type Parser interface {
|
||||
ParseLog(log *types.Log, tx *types.Transaction) (*Event, error)
|
||||
ParseReceipt(receipt *types.Receipt, tx *types.Transaction) ([]*Event, error)
|
||||
SupportedProtocols() []Protocol
|
||||
ValidateEvent(event *Event) error
|
||||
}
|
||||
|
||||
// Each parser implements this independently
|
||||
```
|
||||
|
||||
## Component Modularity Matrix
|
||||
|
||||
| Component | Standalone Use Case | Integrated Use Case | Interface |
|
||||
|-----------|-------------------|---------------------|-----------|
|
||||
| UniswapV2Parser | Parse individual V2 transactions | Part of ParserFactory | `Parser` |
|
||||
| UniswapV3Parser | Parse individual V3 transactions | Part of ParserFactory | `Parser` |
|
||||
| PoolCache | Standalone pool lookup service | Shared cache for all parsers | `PoolCache` |
|
||||
| EventValidator | Validate any event independently | Part of validation pipeline | `Validator` |
|
||||
| AddressIndex | Standalone address lookup | Part of multi-index cache | `Index` |
|
||||
| TokenPairIndex | Standalone pair lookup | Part of multi-index cache | `Index` |
|
||||
| BackgroundValidator | Standalone validation service | Part of monitoring pipeline | `BackgroundValidator` |
|
||||
|
||||
## Directory Structure for Modularity
|
||||
|
||||
```
|
||||
pkg/
|
||||
├── parsers/
|
||||
│ ├── interface.go # Parser interface (shared)
|
||||
│ ├── factory.go # Factory for integration
|
||||
│ ├── uniswap_v2/ # Standalone package
|
||||
│ │ ├── parser.go # Can be imported independently
|
||||
│ │ ├── parser_test.go # Self-contained tests
|
||||
│ │ └── README.md # Standalone usage docs
|
||||
│ ├── uniswap_v3/ # Standalone package
|
||||
│ │ ├── parser.go
|
||||
│ │ ├── parser_test.go
|
||||
│ │ └── README.md
|
||||
│ └── sushiswap/ # Standalone package
|
||||
│ ├── parser.go
|
||||
│ ├── parser_test.go
|
||||
│ └── README.md
|
||||
│
|
||||
├── cache/
|
||||
│ ├── interface.go # Cache interfaces
|
||||
│ ├── pool_cache.go # Main cache (uses indexes)
|
||||
│ ├── indexes/ # Standalone index packages
|
||||
│ │ ├── address/
|
||||
│ │ │ ├── index.go # Standalone address index
|
||||
│ │ │ └── index_test.go
|
||||
│ │ ├── tokenpair/
|
||||
│ │ │ ├── index.go # Standalone pair index
|
||||
│ │ │ └── index_test.go
|
||||
│ │ └── liquidity/
|
||||
│ │ ├── index.go # Standalone liquidity index
|
||||
│ │ └── index_test.go
|
||||
│
|
||||
├── validation/
|
||||
│ ├── interface.go # Validator interface
|
||||
│ ├── validator.go # Main validator
|
||||
│ ├── rules/ # Standalone rule packages
|
||||
│ │ ├── zero_address/
|
||||
│ │ │ ├── rule.go # Standalone rule
|
||||
│ │ │ └── rule_test.go
|
||||
│ │ ├── zero_amount/
|
||||
│ │ │ ├── rule.go
|
||||
│ │ │ └── rule_test.go
|
||||
│ │ └── pool_cache/
|
||||
│ │ ├── rule.go
|
||||
│ │ └── rule_test.go
|
||||
│ └── background/
|
||||
│ ├── validator.go # Standalone background validator
|
||||
│ └── validator_test.go
|
||||
```
|
||||
|
||||
## Testing Requirements for Modularity
|
||||
|
||||
### 1. Unit Tests (Component Isolation)
|
||||
Each component has 100% independent unit tests:
|
||||
```go
|
||||
// pkg/parsers/uniswap_v2/parser_test.go
|
||||
func TestParser_Standalone(t *testing.T) {
|
||||
// NO dependencies on other parsers
|
||||
// NO dependencies on factory
|
||||
// Uses mocks for interfaces only
|
||||
|
||||
logger := NewMockLogger()
|
||||
cache := NewMockCache()
|
||||
|
||||
parser := NewParser(logger, cache)
|
||||
event, err := parser.ParseLog(mockLog, mockTx)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, event)
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Integration Tests (Component Composition)
|
||||
Test components working together:
|
||||
```go
|
||||
// tests/integration/parser_factory_test.go
|
||||
func TestParserFactory_Integration(t *testing.T) {
|
||||
// Test all parsers working through factory
|
||||
factory := parsers.NewFactory()
|
||||
|
||||
// Each parser registered independently
|
||||
factory.Register(ProtocolUniswapV2, uniswap_v2.NewParser(logger, cache))
|
||||
factory.Register(ProtocolUniswapV3, uniswap_v3.NewParser(logger, cache))
|
||||
|
||||
// Test factory routing
|
||||
parser, err := factory.GetParser(ProtocolUniswapV2)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Standalone Executability Tests
|
||||
Each component has example main:
|
||||
```go
|
||||
// examples/uniswap_v2_parser/main.go
|
||||
func main() {
|
||||
// Demonstrate standalone usage
|
||||
logger := logger.New("info", "text", "")
|
||||
cache := cache.NewPoolCache()
|
||||
|
||||
parser := uniswap_v2.NewParser(logger, cache)
|
||||
|
||||
// Parse single transaction
|
||||
event, err := parser.ParseLog(log, tx)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Printf("Parsed event: %+v\n", event)
|
||||
}
|
||||
```
|
||||
|
||||
## Dependency Injection Pattern
|
||||
|
||||
All components use constructor injection:
|
||||
```go
|
||||
// Bad: Hard dependency
|
||||
type UniswapV2Parser struct {
|
||||
cache *PoolCache // Hard dependency!
|
||||
}
|
||||
|
||||
// Good: Interface dependency
|
||||
type UniswapV2Parser struct {
|
||||
cache PoolCache // Interface - can be mocked or replaced
|
||||
}
|
||||
|
||||
func NewParser(logger Logger, cache PoolCache) *UniswapV2Parser {
|
||||
return &UniswapV2Parser{
|
||||
logger: logger,
|
||||
cache: cache,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Configuration Independence
|
||||
|
||||
Each component has its own config:
|
||||
```go
|
||||
// pkg/parsers/uniswap_v2/config.go
|
||||
type Config struct {
|
||||
EnableValidation bool
|
||||
CacheTimeout time.Duration
|
||||
MaxRetries int
|
||||
}
|
||||
|
||||
// Standalone usage
|
||||
config := uniswap_v2.Config{
|
||||
EnableValidation: true,
|
||||
CacheTimeout: 5 * time.Minute,
|
||||
MaxRetries: 3,
|
||||
}
|
||||
parser := uniswap_v2.NewParserWithConfig(logger, cache, config)
|
||||
|
||||
// Integrated usage
|
||||
factory.RegisterWithConfig(
|
||||
ProtocolUniswapV2,
|
||||
parser,
|
||||
config,
|
||||
)
|
||||
```
|
||||
|
||||
## Interface Contracts
|
||||
|
||||
### Minimal Interface Surface
|
||||
Each interface has 1-3 methods maximum:
|
||||
```go
|
||||
// Good: Focused interface
|
||||
type Parser interface {
|
||||
ParseLog(log *types.Log, tx *types.Transaction) (*Event, error)
|
||||
}
|
||||
|
||||
// Bad: God interface
|
||||
type Parser interface {
|
||||
ParseLog(log *types.Log, tx *types.Transaction) (*Event, error)
|
||||
ParseReceipt(receipt *types.Receipt, tx *types.Transaction) ([]*Event, error)
|
||||
ValidateEvent(event *Event) error
|
||||
GetStats() Stats
|
||||
Configure(config Config) error
|
||||
// ... too many responsibilities
|
||||
}
|
||||
```
|
||||
|
||||
### Interface Segregation
|
||||
Split large interfaces:
|
||||
```go
|
||||
// Split responsibilities
|
||||
type LogParser interface {
|
||||
ParseLog(log *types.Log, tx *types.Transaction) (*Event, error)
|
||||
}
|
||||
|
||||
type ReceiptParser interface {
|
||||
ParseReceipt(receipt *types.Receipt, tx *types.Transaction) ([]*Event, error)
|
||||
}
|
||||
|
||||
type EventValidator interface {
|
||||
ValidateEvent(event *Event) error
|
||||
}
|
||||
|
||||
// Compose as needed
|
||||
type Parser interface {
|
||||
LogParser
|
||||
ReceiptParser
|
||||
EventValidator
|
||||
}
|
||||
```
|
||||
|
||||
## Build Tags for Optional Components
|
||||
|
||||
Use build tags for optional features:
|
||||
```go
|
||||
// pkg/parsers/uniswap_v2/parser.go
|
||||
// +build !minimal
|
||||
|
||||
// Full implementation with all features
|
||||
|
||||
// pkg/parsers/uniswap_v2/parser_minimal.go
|
||||
// +build minimal
|
||||
|
||||
// Minimal implementation for embedded systems
|
||||
```
|
||||
|
||||
Build options:
|
||||
```bash
|
||||
# Full build (all components)
|
||||
go build ./...
|
||||
|
||||
# Minimal build (core only)
|
||||
go build -tags minimal ./...
|
||||
|
||||
# Custom build (specific parsers only)
|
||||
go build -tags "uniswap_v2 uniswap_v3" ./...
|
||||
```
|
||||
|
||||
## Component Communication Patterns
|
||||
|
||||
### 1. Synchronous (Direct Call)
|
||||
For tight coupling when needed:
|
||||
```go
|
||||
event, err := parser.ParseLog(log, tx)
|
||||
```
|
||||
|
||||
### 2. Asynchronous (Channels)
|
||||
For loose coupling:
|
||||
```go
|
||||
eventChan := make(chan *Event, 100)
|
||||
go parser.ParseAsync(logs, eventChan)
|
||||
```
|
||||
|
||||
### 3. Pub/Sub (Event Bus)
|
||||
For many-to-many communication:
|
||||
```go
|
||||
bus := eventbus.New()
|
||||
parser.Subscribe(bus, "parsed")
|
||||
validator.Subscribe(bus, "parsed")
|
||||
|
||||
bus.Publish("parsed", event)
|
||||
```
|
||||
|
||||
### 4. Interface Composition
|
||||
For static composition:
|
||||
```go
|
||||
type CompositeParser struct {
|
||||
v2 *uniswap_v2.Parser
|
||||
v3 *uniswap_v3.Parser
|
||||
}
|
||||
|
||||
func (c *CompositeParser) Parse(log *types.Log) (*Event, error) {
|
||||
// Route to appropriate parser
|
||||
}
|
||||
```
|
||||
|
||||
## Success Criteria for Modularity
|
||||
|
||||
Each component MUST:
|
||||
- [ ] Compile independently (`go build ./pkg/parsers/uniswap_v2`)
|
||||
- [ ] Test independently (`go test ./pkg/parsers/uniswap_v2`)
|
||||
- [ ] Run standalone example (`go run examples/uniswap_v2_parser/main.go`)
|
||||
- [ ] Have zero sibling dependencies
|
||||
- [ ] Communicate only through interfaces
|
||||
- [ ] Include standalone usage documentation
|
||||
- [ ] Pass integration tests when composed
|
||||
- [ ] Support mock implementations for testing
|
||||
|
||||
## Anti-Patterns to Avoid
|
||||
|
||||
### ❌ Circular Dependencies
|
||||
```go
|
||||
// pkg/parsers/uniswap_v2/parser.go
|
||||
import "pkg/parsers/uniswap_v3" // BAD!
|
||||
|
||||
// pkg/parsers/uniswap_v3/parser.go
|
||||
import "pkg/parsers/uniswap_v2" // BAD!
|
||||
```
|
||||
|
||||
### ❌ Shared Mutable State
|
||||
```go
|
||||
// BAD: Global shared state
|
||||
var globalCache *PoolCache
|
||||
|
||||
func (p *Parser) Parse(log *types.Log) (*Event, error) {
|
||||
pool := globalCache.Get(log.Address) // BAD!
|
||||
}
|
||||
```
|
||||
|
||||
### ❌ Hard-coded Dependencies
|
||||
```go
|
||||
// BAD: Creates own dependencies
|
||||
func NewParser() *Parser {
|
||||
return &Parser{
|
||||
cache: NewPoolCache(), // BAD! Should be injected
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### ❌ Leaky Abstractions
|
||||
```go
|
||||
// BAD: Exposes internal structure
|
||||
type Parser interface {
|
||||
GetInternalCache() *PoolCache // BAD! Leaks implementation
|
||||
}
|
||||
```
|
||||
|
||||
## Migration Strategy
|
||||
|
||||
When moving from V1 to V2:
|
||||
|
||||
1. **Extract Interface**: Define interface from V1 implementation
|
||||
2. **Create Package**: Move to standalone package
|
||||
3. **Inject Dependencies**: Replace hard dependencies with interfaces
|
||||
4. **Add Tests**: Unit tests for standalone operation
|
||||
5. **Create Example**: Standalone usage example
|
||||
6. **Document**: README with standalone and integrated usage
|
||||
7. **Verify**: Check all modularity criteria
|
||||
|
||||
## Component Checklist
|
||||
|
||||
Before marking any component as "complete":
|
||||
|
||||
- [ ] Compiles independently
|
||||
- [ ] Tests independently (>90% coverage)
|
||||
- [ ] Has standalone example
|
||||
- [ ] Has README with usage
|
||||
- [ ] Zero sibling dependencies
|
||||
- [ ] All dependencies injected through interfaces
|
||||
- [ ] Can be mocked for testing
|
||||
- [ ] Integrated into factory/orchestrator
|
||||
- [ ] Integration tests pass
|
||||
- [ ] Performance benchmarks exist
|
||||
- [ ] Documentation complete
|
||||
|
||||
---
|
||||
|
||||
**Principle**: If you can't run it standalone, it's not modular enough.
|
||||
**Guideline**: If you can't mock it, it's too coupled.
|
||||
**Rule**: If it has circular dependencies, redesign it.
|
||||
Reference in New Issue
Block a user