Files
mev-beta/docs/planning/01_MODULARITY_REQUIREMENTS.md
Administrator f41adbe575 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>
2025-11-10 14:26:56 +01:00

11 KiB

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:

// 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:

// 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:

// 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:

// 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:

// 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:

// 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:

// 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:

// 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:

// 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:

// 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:

# 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:

event, err := parser.ParseLog(log, tx)

2. Asynchronous (Channels)

For loose coupling:

eventChan := make(chan *Event, 100)
go parser.ParseAsync(logs, eventChan)

3. Pub/Sub (Event Bus)

For many-to-many communication:

bus := eventbus.New()
parser.Subscribe(bus, "parsed")
validator.Subscribe(bus, "parsed")

bus.Publish("parsed", event)

4. Interface Composition

For static composition:

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

// 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

// 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

// BAD: Creates own dependencies
func NewParser() *Parser {
    return &Parser{
        cache: NewPoolCache(),  // BAD! Should be injected
    }
}

Leaky Abstractions

// 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.