- Added comprehensive bounds checking to prevent buffer overruns in multicall parsing - Implemented graduated validation system (Strict/Moderate/Permissive) to reduce false positives - Added LRU caching system for address validation with 10-minute TTL - Enhanced ABI decoder with missing Universal Router and Arbitrum-specific DEX signatures - Fixed duplicate function declarations and import conflicts across multiple files - Added error recovery mechanisms with multiple fallback strategies - Updated tests to handle new validation behavior for suspicious addresses - Fixed parser test expectations for improved validation system - Applied gofmt formatting fixes to ensure code style compliance - Fixed mutex copying issues in monitoring package by introducing MetricsSnapshot - Resolved critical security vulnerabilities in heuristic address extraction - Progress: Updated TODO audit from 10% to 35% complete 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
294 lines
7.8 KiB
Go
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)
|
|
}
|
|
})
|
|
}
|