ChainVM Compatibility
Provides compatibility layer for legacy Avalanche chain-EVM chains migrating to Lux Network
LP-118: chain-EVM Compatibility Layer
Status: Final Type: Standards Track Category: Interface Created: 2025-01-15
Abstract
LP-118 provides a compatibility layer for legacy Avalanche chain-EVM chains migrating to the Lux Network. It ensures seamless transition while maintaining deterministic behavior and state consistency.
Motivation
Many existing blockchain applications are built on Avalanche's chain-EVM. LP-118 enables these chains to migrate to Lux without requiring application-level changes, preserving:
- Existing State: All account balances and contract storage
- Transaction Format: Compatible with existing wallets and tools
- Contract ABIs: No redeployment required
- Historical Data: Complete blockchain history preserved
Specification
Compatibility Scope
LP-118 ensures compatibility with:
- chain-EVM v0.5.x: Primary target version
- Precompiles: All standard chain-EVM precompiles
- Warp Messaging: Cross-chain communication
- Upgrades: Durango, Etna network upgrades
Activation
Compatibility mode is enabled via network configuration:
type ChainConfig struct {
// ... existing fields
LP118Timestamp *uint64 `json:"lp118Timestamp,omitempty"`
}
When LP118Timestamp is set:
- Enables chain-EVM transaction format parsing
- Activates compatible precompile set
- Maintains chain-EVM fee calculation rules
- Preserves chain-EVM block format
Migration Path
Phase 1: Read-only compatibility
- Lux nodes can read chain-EVM chain state
- Historical blocks validated correctly
- No state modifications
Phase 2: Full compatibility
- New blocks follow Lux format
- Transactions compatible with both formats
- Gradual transition over N blocks
Phase 3: Native Lux
- LP-118 compatibility can be deprecated
- Full Lux native features enabled
- Legacy format support optional
Rationale
Design Decisions
1. Opt-in Compatibility: Rather than forcing all chains to support legacy formats, LP-118 is activated per-chain via LP118Timestamp. This keeps the core protocol clean while enabling migration.
2. Phased Migration: The three-phase approach allows for gradual transition without network disruption:
- Phase 1 enables validation without risk (read-only)
- Phase 2 enables full operation with both formats
- Phase 3 allows clean deprecation when ready
3. Precompile Mapping: Direct address mapping ensures existing contracts work without modification. Enhanced precompiles in Lux (like Warp) maintain backward compatibility while offering new features.
4. Transaction Format Detection: The format detection order (chain-EVM first, then Lux) ensures legacy transactions are always correctly parsed while allowing native transactions.
Alternatives Considered
- Full Emulation: Rejected due to complexity and performance overhead
- One-Time Migration: Rejected due to risk of data loss and downtime
- Parallel Networks: Rejected due to liquidity fragmentation
Implementation
Location
Plugin Interface: geth/plugin/evm/upgrade/lp118/
Files:
params.go- Activation parameters
Integration Points: evm/plugin/evm/
Key areas:
vm.go- VM initialization with compat modeconfig.go- chain-EVM config parsingupgrade/- Legacy upgrade handling
Key Components
1. Transaction Format Compatibility
func parseTransaction(data []byte, isLP118Active bool) (*Transaction, error) {
if isLP118Active {
// Try chain-EVM format first
if tx, err := parseChainVMTx(data); err == nil {
return tx, nil
}
}
// Fall back to Lux native format
return parseLuxTx(data)
}
2. Precompile Mapping
| chain-EVM Address | Lux Equivalent | Notes |
|---|---|---|
0x0200000000000000000000000000000000000000 | Native Minter | Mapped 1:1 |
0x0200000000000000000000000000000000000001 | Contract Deployer Allowlist | Mapped 1:1 |
0x0200000000000000000000000000000000000002 | Tx Allowlist | Mapped 1:1 |
0x0200000000000000000000000000000000000003 | Fee Manager | Mapped 1:1 |
0x0200000000000000000000000000000000000004 | Reward Manager | Mapped 1:1 |
0x0200000000000000000000000000000000000005 | Warp Messenger | Enhanced in Lux |
3. Block Format Translation
chain-EVM blocks are translated to Lux format:
type ChainVMBlock struct {
Header ChainVMHeader
Transactions []*ChainVMTx
// ... chain-EVM specific fields
}
func (b *ChainVMBlock) ToLuxBlock() *LuxBlock {
return &LuxBlock{
Header: translateHeader(b.Header),
Transactions: translateTxs(b.Transactions),
// ... map remaining fields
}
}
Test Cases
Compatibility Tests
Location: tests/e2e/ChainVM_compat_test.go
Test scenarios:
- Import existing chain-EVM genesis
- Validate historical blocks
- Execute legacy transactions
- Call chain-EVM precompiles
- Warp message compatibility
Migration Tests
Scenarios:
- Genesis Import: Load chain-EVM genesis into Lux
- Chain Replay: Replay chain-EVM blocks
- Mixed Transactions: Process both formats in same block
- Precompile Calls: Verify identical behavior
- State Continuity: Ensure no state divergence
Unit Tests
// Test: Transaction format detection
func TestTransactionFormatDetection(t *testing.T) {
// chain-EVM format transaction
chainTx := createChainVMTx()
tx, err := parseTransaction(chainTx.Bytes(), true)
require.NoError(t, err)
require.Equal(t, chainTx.Hash(), tx.Hash())
// Lux native format transaction
luxTx := createLuxTx()
tx, err = parseTransaction(luxTx.Bytes(), true)
require.NoError(t, err)
require.Equal(t, luxTx.Hash(), tx.Hash())
}
// Test: Precompile compatibility
func TestPrecompileMapping(t *testing.T) {
addresses := []common.Address{
common.HexToAddress("0x0200000000000000000000000000000000000000"),
common.HexToAddress("0x0200000000000000000000000000000000000001"),
common.HexToAddress("0x0200000000000000000000000000000000000005"),
}
for _, addr := range addresses {
// Verify precompile exists
precompile := GetPrecompile(addr)
require.NotNil(t, precompile)
// Verify behavior matches chain-EVM
result, err := precompile.Run(testInput)
require.NoError(t, err)
require.Equal(t, expectedChainVMResult, result)
}
}
// Test: Block format translation
func TestBlockTranslation(t *testing.T) {
chainBlock := loadChainVMBlock(t, "testdata/chain_block.json")
luxBlock := chainBlock.ToLuxBlock()
// Verify state root matches
require.Equal(t, chainBlock.Root(), luxBlock.Root())
// Verify transaction count matches
require.Equal(t, len(chainBlock.Transactions), len(luxBlock.Transactions))
// Verify deterministic translation
luxBlock2 := chainBlock.ToLuxBlock()
require.Equal(t, luxBlock.Hash(), luxBlock2.Hash())
}
// Test: LP-118 activation
func TestLP118Activation(t *testing.T) {
config := &ChainConfig{
LP118Timestamp: ptr(uint64(1000)),
}
// Before activation
require.False(t, config.IsLP118Active(999))
// At activation
require.True(t, config.IsLP118Active(1000))
// After activation
require.True(t, config.IsLP118Active(1001))
}
Security Considerations
Compatibility Risks
-
Format Ambiguity: Transaction format detection must be unambiguous
- Mitigation: Strict format validation, version markers
-
Precompile Divergence: Behavior differences in precompiles
- Mitigation: Extensive test coverage, formal verification
-
State Transition Bugs: Migration edge cases
- Mitigation: Comprehensive migration testing, rollback plan
Audits
- Internal security review: 2025-01-12
- Migration testing: Ongoing
- External audit: Scheduled
Backwards Compatibility
LP-118 is designed for forward compatibility only. chain-EVM chains can migrate to Lux, but not vice versa. This is intentional to prevent fragmentation.
References
Copyright
Copyright (C) 2025 Lux Partners Limited. All rights reserved.