Files
mev-beta/orig/pkg/math/precision_test.go
Administrator 803de231ba feat: create v2-prep branch with comprehensive planning
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>
2025-11-10 10:14:26 +01:00

294 lines
7.8 KiB
Go

package math
import (
"math/big"
"math/rand"
"testing"
"time"
)
// TestDecimalPrecisionPreservation tests that decimal operations preserve precision
func TestDecimalPrecisionPreservation(t *testing.T) {
dc := NewDecimalConverter()
testCases := []struct {
name string
value string
decimals uint8
symbol string
}{
{"ETH precision", "1000000000000000000", 18, "ETH"}, // 1 ETH
{"USDC precision", "1000000", 6, "USDC"}, // 1 USDC
{"WBTC precision", "100000000", 8, "WBTC"}, // 1 WBTC
{"Small amount", "1", 18, "ETH"}, // 1 wei
{"Large amount", "1000000000000000000000", 18, "ETH"}, // 1000 ETH
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Create decimal from string
decimal, err := dc.FromString(tc.value, tc.decimals, tc.symbol)
if err != nil {
t.Fatalf("Failed to create decimal: %v", err)
}
// Convert to string and back
humanReadable := dc.ToHumanReadable(decimal)
backToDecimal, err := dc.FromString(humanReadable, tc.decimals, tc.symbol)
if err != nil {
t.Fatalf("Failed to convert back from string: %v", err)
}
// Compare values
if decimal.Value.Cmp(backToDecimal.Value) != 0 {
t.Errorf("Precision lost in round-trip conversion")
t.Errorf("Original: %s", decimal.Value.String())
t.Errorf("Round-trip: %s", backToDecimal.Value.String())
t.Errorf("Human readable: %s", humanReadable)
}
})
}
}
// TestArithmeticOperations tests basic arithmetic with different decimal precisions
func TestArithmeticOperations(t *testing.T) {
dc := NewDecimalConverter()
// Create test values with different precisions
eth1, _ := dc.FromString("1000000000000000000", 18, "ETH") // 1 ETH
eth2, _ := dc.FromString("2000000000000000000", 18, "ETH") // 2 ETH
tests := []struct {
name string
op string
a, b *UniversalDecimal
expected string
}{
{
name: "ETH addition",
op: "add",
a: eth1,
b: eth2,
expected: "3000000000000000000", // 3 ETH
},
{
name: "ETH subtraction",
op: "sub",
a: eth2,
b: eth1,
expected: "1000000000000000000", // 1 ETH
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
var result *UniversalDecimal
var err error
switch test.op {
case "add":
result, err = dc.Add(test.a, test.b)
case "sub":
result, err = dc.Subtract(test.a, test.b)
default:
t.Fatalf("Unknown operation: %s", test.op)
}
if err != nil {
t.Fatalf("Operation failed: %v", err)
}
if result.Value.String() != test.expected {
t.Errorf("Expected %s, got %s", test.expected, result.Value.String())
}
})
}
}
// TestPercentageCalculations tests percentage calculations for precision
func TestPercentageCalculations(t *testing.T) {
dc := NewDecimalConverter()
testCases := []struct {
name string
numerator string
denominator string
decimals uint8
expectedRange [2]float64 // [min, max] acceptable range
}{
{
name: "1% calculation",
numerator: "10000000000000000", // 0.01 ETH
denominator: "1000000000000000000", // 1 ETH
decimals: 18,
expectedRange: [2]float64{0.99, 1.01}, // 1% ± 0.01%
},
{
name: "50% calculation",
numerator: "500000000000000000", // 0.5 ETH
denominator: "1000000000000000000", // 1 ETH
decimals: 18,
expectedRange: [2]float64{49.9, 50.1}, // 50% ± 0.1%
},
{
name: "Small percentage",
numerator: "1000000000000000", // 0.001 ETH
denominator: "1000000000000000000", // 1 ETH
decimals: 18,
expectedRange: [2]float64{0.099, 0.101}, // 0.1% ± 0.001%
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
num, err := dc.FromString(tc.numerator, tc.decimals, "ETH")
if err != nil {
t.Fatalf("Failed to create numerator: %v", err)
}
denom, err := dc.FromString(tc.denominator, tc.decimals, "ETH")
if err != nil {
t.Fatalf("Failed to create denominator: %v", err)
}
percentage, err := dc.CalculatePercentage(num, denom)
if err != nil {
t.Fatalf("Failed to calculate percentage: %v", err)
}
percentageFloat, _ := percentage.Value.Float64()
// Convert from raw value to actual percentage (divide by 10^decimals)
// Since percentage has 4 decimals, divide by 10000 to get actual percentage value
actualPercentage := percentageFloat / 10000.0
t.Logf("Calculated percentage: %.6f%%", actualPercentage)
if actualPercentage < tc.expectedRange[0] || actualPercentage > tc.expectedRange[1] {
t.Errorf("Percentage %.6f%% outside expected range [%.3f%%, %.3f%%]",
actualPercentage, tc.expectedRange[0], tc.expectedRange[1])
}
})
}
}
// PropertyTest tests mathematical properties like commutativity, associativity
func TestMathematicalProperties(t *testing.T) {
dc := NewDecimalConverter()
rand.Seed(time.Now().UnixNano())
// Generate random test values
for i := 0; i < 100; i++ {
// Generate random big integers
val1 := big.NewInt(rand.Int63n(1000000000000000000)) // Up to 1 ETH
val2 := big.NewInt(rand.Int63n(1000000000000000000))
val3 := big.NewInt(rand.Int63n(1000000000000000000))
a, _ := NewUniversalDecimal(val1, 18, "ETH")
b, _ := NewUniversalDecimal(val2, 18, "ETH")
c, _ := NewUniversalDecimal(val3, 18, "ETH")
// Test commutativity: a + b = b + a
ab, err1 := dc.Add(a, b)
ba, err2 := dc.Add(b, a)
if err1 != nil || err2 != nil {
t.Fatalf("Addition failed: %v, %v", err1, err2)
}
if ab.Value.Cmp(ba.Value) != 0 {
t.Errorf("Addition not commutative: %s + %s = %s, %s + %s = %s",
a.Value.String(), b.Value.String(), ab.Value.String(),
b.Value.String(), a.Value.String(), ba.Value.String())
}
// Test associativity: (a + b) + c = a + (b + c)
ab_c, err1 := dc.Add(ab, c)
bc, err2 := dc.Add(b, c)
a_bc, err3 := dc.Add(a, bc)
if err1 != nil || err2 != nil || err3 != nil {
continue // Skip this iteration if any operation fails
}
if ab_c.Value.Cmp(a_bc.Value) != 0 {
t.Errorf("Addition not associative")
}
}
}
// BenchmarkDecimalOperations benchmarks decimal operations
func BenchmarkDecimalOperations(b *testing.B) {
dc := NewDecimalConverter()
val1, _ := dc.FromString("1000000000000000000", 18, "ETH")
val2, _ := dc.FromString("2000000000000000000", 18, "ETH")
b.Run("Addition", func(b *testing.B) {
for i := 0; i < b.N; i++ {
dc.Add(val1, val2)
}
})
b.Run("Subtraction", func(b *testing.B) {
for i := 0; i < b.N; i++ {
dc.Subtract(val2, val1)
}
})
b.Run("Percentage", func(b *testing.B) {
for i := 0; i < b.N; i++ {
dc.CalculatePercentage(val1, val2)
}
})
}
// FuzzDecimalOperations fuzzes decimal operations for edge cases
func FuzzDecimalOperations(f *testing.F) {
// Seed with known values
f.Add(int64(1000000000000000000), int64(2000000000000000000)) // 1 ETH, 2 ETH
f.Add(int64(1), int64(1000000000000000000)) // 1 wei, 1 ETH
f.Add(int64(0), int64(1000000000000000000)) // 0, 1 ETH
f.Fuzz(func(t *testing.T, val1, val2 int64) {
// Ensure positive values
if val1 < 0 {
val1 = -val1
}
if val2 <= 0 {
return // Skip zero/negative denominators
}
dc := NewDecimalConverter()
a, err := NewUniversalDecimal(big.NewInt(val1), 18, "ETH")
if err != nil {
return
}
b, err := NewUniversalDecimal(big.NewInt(val2), 18, "ETH")
if err != nil {
return
}
// Test addition doesn't panic
_, err = dc.Add(a, b)
if err != nil {
t.Errorf("Addition failed: %v", err)
}
// Test subtraction doesn't panic (if a >= b)
if val1 >= val2 {
_, err = dc.Subtract(a, b)
if err != nil {
t.Errorf("Subtraction failed: %v", err)
}
}
// Test percentage calculation doesn't panic
_, err = dc.CalculatePercentage(a, b)
if err != nil {
t.Errorf("Percentage calculation failed: %v", err)
}
})
}