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>
384 lines
9.1 KiB
Go
384 lines
9.1 KiB
Go
package tokens
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
"github.com/fraktal/mev-beta/internal/logger"
|
|
)
|
|
|
|
func TestNewMetadataCache(t *testing.T) {
|
|
log := logger.New("info", "text", "")
|
|
cache := NewMetadataCache(log)
|
|
|
|
assert.NotNil(t, cache)
|
|
assert.NotNil(t, cache.cache)
|
|
assert.Equal(t, log, cache.logger)
|
|
assert.Equal(t, "data/tokens.json", cache.cacheFile)
|
|
}
|
|
|
|
func TestTokenMetadataCreation(t *testing.T) {
|
|
metadata := &TokenMetadata{
|
|
Address: common.HexToAddress("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"),
|
|
Symbol: "USDC",
|
|
Name: "USD Coin",
|
|
Decimals: 6,
|
|
Verified: true,
|
|
FirstSeen: time.Now(),
|
|
LastSeen: time.Now(),
|
|
SeenCount: 1,
|
|
}
|
|
|
|
assert.Equal(t, "USDC", metadata.Symbol)
|
|
assert.Equal(t, "USD Coin", metadata.Name)
|
|
assert.Equal(t, uint8(6), metadata.Decimals)
|
|
assert.True(t, metadata.Verified)
|
|
assert.Equal(t, uint64(1), metadata.SeenCount)
|
|
}
|
|
|
|
func TestCacheGetMissing(t *testing.T) {
|
|
log := logger.New("info", "text", "")
|
|
cache := NewMetadataCache(log)
|
|
|
|
address := common.HexToAddress("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48")
|
|
metadata, exists := cache.Get(address)
|
|
|
|
assert.False(t, exists)
|
|
assert.Nil(t, metadata)
|
|
}
|
|
|
|
func TestCacheSetAndGet(t *testing.T) {
|
|
log := logger.New("info", "text", "")
|
|
cache := NewMetadataCache(log)
|
|
|
|
metadata := &TokenMetadata{
|
|
Address: common.HexToAddress("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"),
|
|
Symbol: "USDC",
|
|
Name: "USD Coin",
|
|
Decimals: 6,
|
|
Verified: true,
|
|
SeenCount: 1,
|
|
}
|
|
|
|
cache.Set(metadata)
|
|
|
|
retrieved, exists := cache.Get(metadata.Address)
|
|
assert.True(t, exists)
|
|
assert.NotNil(t, retrieved)
|
|
assert.Equal(t, "USDC", retrieved.Symbol)
|
|
assert.Equal(t, uint8(6), retrieved.Decimals)
|
|
}
|
|
|
|
func TestCacheSetFirstSeen(t *testing.T) {
|
|
log := logger.New("info", "text", "")
|
|
cache := NewMetadataCache(log)
|
|
|
|
metadata := &TokenMetadata{
|
|
Address: common.HexToAddress("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"),
|
|
Symbol: "USDC",
|
|
Name: "USD Coin",
|
|
Decimals: 6,
|
|
}
|
|
|
|
before := time.Now()
|
|
cache.Set(metadata)
|
|
after := time.Now()
|
|
|
|
retrieved, exists := cache.Get(metadata.Address)
|
|
assert.True(t, exists)
|
|
assert.NotNil(t, retrieved.FirstSeen)
|
|
assert.True(t, retrieved.FirstSeen.After(before.Add(-1*time.Second)) || retrieved.FirstSeen.Equal(before))
|
|
assert.True(t, retrieved.FirstSeen.Before(after.Add(1*time.Second)) || retrieved.FirstSeen.Equal(after))
|
|
}
|
|
|
|
func TestCacheMultipleTokens(t *testing.T) {
|
|
log := logger.New("info", "text", "")
|
|
cache := NewMetadataCache(log)
|
|
|
|
tokens := []struct {
|
|
address string
|
|
symbol string
|
|
decimals uint8
|
|
}{
|
|
{"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", "USDC", 6},
|
|
{"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", "WETH", 18},
|
|
{"0xdAC17F958D2ee523a2206206994597C13D831ec7", "USDT", 6},
|
|
}
|
|
|
|
for _, token := range tokens {
|
|
metadata := &TokenMetadata{
|
|
Address: common.HexToAddress(token.address),
|
|
Symbol: token.symbol,
|
|
Decimals: token.decimals,
|
|
Verified: true,
|
|
SeenCount: 1,
|
|
}
|
|
cache.Set(metadata)
|
|
}
|
|
|
|
assert.Equal(t, 3, len(cache.cache))
|
|
|
|
for _, token := range tokens {
|
|
retrieved, exists := cache.Get(common.HexToAddress(token.address))
|
|
assert.True(t, exists)
|
|
assert.Equal(t, token.symbol, retrieved.Symbol)
|
|
assert.Equal(t, token.decimals, retrieved.Decimals)
|
|
}
|
|
}
|
|
|
|
func TestGetOrCreateMissing(t *testing.T) {
|
|
log := logger.New("info", "text", "")
|
|
cache := NewMetadataCache(log)
|
|
|
|
address := common.HexToAddress("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48")
|
|
metadata := cache.GetOrCreate(address)
|
|
|
|
assert.NotNil(t, metadata)
|
|
assert.Equal(t, "UNKNOWN", metadata.Symbol)
|
|
assert.Equal(t, "Unknown Token", metadata.Name)
|
|
assert.Equal(t, uint8(18), metadata.Decimals) // Default assumption
|
|
assert.False(t, metadata.Verified)
|
|
assert.Equal(t, address, metadata.Address)
|
|
}
|
|
|
|
func TestGetOrCreateExisting(t *testing.T) {
|
|
log := logger.New("info", "text", "")
|
|
cache := NewMetadataCache(log)
|
|
|
|
address := common.HexToAddress("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48")
|
|
metadata := &TokenMetadata{
|
|
Address: address,
|
|
Symbol: "USDC",
|
|
Decimals: 6,
|
|
Verified: true,
|
|
SeenCount: 1,
|
|
}
|
|
|
|
cache.Set(metadata)
|
|
|
|
retrieved := cache.GetOrCreate(address)
|
|
assert.Equal(t, "USDC", retrieved.Symbol)
|
|
assert.Equal(t, uint8(6), retrieved.Decimals)
|
|
assert.True(t, retrieved.Verified)
|
|
}
|
|
|
|
func TestSeenCountIncrement(t *testing.T) {
|
|
log := logger.New("info", "text", "")
|
|
cache := NewMetadataCache(log)
|
|
|
|
address := common.HexToAddress("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48")
|
|
metadata := &TokenMetadata{
|
|
Address: address,
|
|
Symbol: "USDC",
|
|
Decimals: 6,
|
|
SeenCount: 1,
|
|
}
|
|
|
|
// First set
|
|
cache.Set(metadata)
|
|
retrieved, _ := cache.Get(address)
|
|
assert.Equal(t, uint64(1), retrieved.SeenCount)
|
|
|
|
// Second set - should increment
|
|
metadata2 := &TokenMetadata{
|
|
Address: address,
|
|
Symbol: "USDC",
|
|
Decimals: 6,
|
|
SeenCount: 1,
|
|
}
|
|
cache.Set(metadata2)
|
|
retrieved2, _ := cache.Get(address)
|
|
assert.Equal(t, uint64(2), retrieved2.SeenCount)
|
|
}
|
|
|
|
func TestLastSeenUpdate(t *testing.T) {
|
|
log := logger.New("info", "text", "")
|
|
cache := NewMetadataCache(log)
|
|
|
|
address := common.HexToAddress("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48")
|
|
metadata := &TokenMetadata{
|
|
Address: address,
|
|
Symbol: "USDC",
|
|
Decimals: 6,
|
|
}
|
|
|
|
cache.Set(metadata)
|
|
firstLastSeen := cache.cache[address].LastSeen
|
|
|
|
time.Sleep(10 * time.Millisecond)
|
|
|
|
metadata2 := &TokenMetadata{
|
|
Address: address,
|
|
Symbol: "USDC",
|
|
Decimals: 6,
|
|
}
|
|
cache.Set(metadata2)
|
|
secondLastSeen := cache.cache[address].LastSeen
|
|
|
|
assert.True(t, secondLastSeen.After(firstLastSeen))
|
|
}
|
|
|
|
func TestFirstSeenPreserved(t *testing.T) {
|
|
log := logger.New("info", "text", "")
|
|
cache := NewMetadataCache(log)
|
|
|
|
address := common.HexToAddress("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48")
|
|
metadata := &TokenMetadata{
|
|
Address: address,
|
|
Symbol: "USDC",
|
|
Decimals: 6,
|
|
}
|
|
|
|
cache.Set(metadata)
|
|
firstFirstSeen := cache.cache[address].FirstSeen
|
|
|
|
time.Sleep(10 * time.Millisecond)
|
|
|
|
metadata2 := &TokenMetadata{
|
|
Address: address,
|
|
Symbol: "USDC",
|
|
Decimals: 6,
|
|
}
|
|
cache.Set(metadata2)
|
|
secondFirstSeen := cache.cache[address].FirstSeen
|
|
|
|
assert.Equal(t, firstFirstSeen, secondFirstSeen)
|
|
}
|
|
|
|
func TestTokenMetadataVerified(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
verified bool
|
|
symbol string
|
|
}{
|
|
{"Verified USDC", true, "USDC"},
|
|
{"Verified WETH", true, "WETH"},
|
|
{"Unverified token", false, "UNKNOWN"},
|
|
{"Unverified custom", false, "CUSTOM"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
metadata := &TokenMetadata{
|
|
Address: common.HexToAddress("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"),
|
|
Symbol: tt.symbol,
|
|
Verified: tt.verified,
|
|
SeenCount: 1,
|
|
}
|
|
assert.Equal(t, tt.verified, metadata.Verified)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTotalSupplyMetadata(t *testing.T) {
|
|
metadata := &TokenMetadata{
|
|
Address: common.HexToAddress("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"),
|
|
Symbol: "USDC",
|
|
TotalSupply: "30000000000000000", // 30M USDC
|
|
Decimals: 6,
|
|
Verified: true,
|
|
SeenCount: 1,
|
|
}
|
|
|
|
assert.NotEmpty(t, metadata.TotalSupply)
|
|
assert.Equal(t, "30000000000000000", metadata.TotalSupply)
|
|
}
|
|
|
|
func TestCacheConcurrency(t *testing.T) {
|
|
log := logger.New("info", "text", "")
|
|
cache := NewMetadataCache(log)
|
|
|
|
done := make(chan bool, 10)
|
|
errors := make(chan error, 10)
|
|
|
|
// Test concurrent writes
|
|
for i := 0; i < 5; i++ {
|
|
go func(index int) {
|
|
addr := common.HexToAddress("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48")
|
|
metadata := &TokenMetadata{
|
|
Address: addr,
|
|
Symbol: "USDC",
|
|
Decimals: 6,
|
|
SeenCount: uint64(index),
|
|
}
|
|
cache.Set(metadata)
|
|
done <- true
|
|
}(i)
|
|
}
|
|
|
|
// Test concurrent reads
|
|
for i := 0; i < 5; i++ {
|
|
go func() {
|
|
addr := common.HexToAddress("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48")
|
|
_, _ = cache.Get(addr)
|
|
done <- true
|
|
}()
|
|
}
|
|
|
|
// Wait for all goroutines
|
|
for i := 0; i < 10; i++ {
|
|
select {
|
|
case <-done:
|
|
// Success
|
|
case <-errors:
|
|
t.Fatal("Concurrent operation failed")
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCacheSize(t *testing.T) {
|
|
log := logger.New("info", "text", "")
|
|
cache := NewMetadataCache(log)
|
|
|
|
addresses := []string{
|
|
"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
|
|
"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
|
|
"0xdAC17F958D2ee523a2206206994597C13D831ec7",
|
|
"0x6B175474E89094C44Da98b954EedeAC495271d0F",
|
|
"0x2260FAC5E5542a773Aa44fBCfeDd66150d0310be",
|
|
}
|
|
|
|
for _, addr := range addresses {
|
|
metadata := &TokenMetadata{
|
|
Address: common.HexToAddress(addr),
|
|
Symbol: "TEST",
|
|
Decimals: 18,
|
|
SeenCount: 1,
|
|
}
|
|
cache.Set(metadata)
|
|
}
|
|
|
|
assert.Equal(t, len(addresses), len(cache.cache))
|
|
}
|
|
|
|
func TestMetadataDecimalVariations(t *testing.T) {
|
|
tests := []struct {
|
|
symbol string
|
|
decimals uint8
|
|
expected uint8
|
|
}{
|
|
{"USDC", 6, 6},
|
|
{"USDT", 6, 6},
|
|
{"WETH", 18, 18},
|
|
{"DAI", 18, 18},
|
|
{"WBTC", 8, 8},
|
|
{"LINK", 18, 18},
|
|
{"AAVE", 18, 18},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.symbol, func(t *testing.T) {
|
|
metadata := &TokenMetadata{
|
|
Address: common.HexToAddress("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"),
|
|
Symbol: tt.symbol,
|
|
Decimals: tt.decimals,
|
|
SeenCount: 1,
|
|
}
|
|
assert.Equal(t, tt.expected, metadata.Decimals)
|
|
})
|
|
}
|
|
}
|