- 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>
409 lines
11 KiB
Markdown
409 lines
11 KiB
Markdown
# 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.
|