refactor: move all remaining files to orig/ directory
Completed clean root directory structure: - Root now contains only: .git, .env, docs/, orig/ - Moved all remaining files and directories to orig/: - Config files (.claude, .dockerignore, .drone.yml, etc.) - All .env variants (except active .env) - Git config (.gitconfig, .github, .gitignore, etc.) - Tool configs (.golangci.yml, .revive.toml, etc.) - Documentation (*.md files, @prompts) - Build files (Dockerfiles, Makefile, go.mod, go.sum) - Docker compose files - All source directories (scripts, tests, tools, etc.) - Runtime directories (logs, monitoring, reports) - Dependency files (node_modules, lib, cache) - Special files (--delete) - Removed empty runtime directories (bin/, data/) V2 structure is now clean: - docs/planning/ - V2 planning documents - orig/ - Complete V1 codebase preserved - .env - Active environment config (not in git) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
322
orig/tools/math-audit/regression_test.go
Normal file
322
orig/tools/math-audit/regression_test.go
Normal file
@@ -0,0 +1,322 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
pkgmath "github.com/fraktal/mev-beta/pkg/math"
|
||||
)
|
||||
|
||||
// ProfitThresholdTestCase defines a test case for profit threshold validation
|
||||
type ProfitThresholdTestCase struct {
|
||||
Name string
|
||||
Exchange string
|
||||
Reserve0 string
|
||||
Reserve1 string
|
||||
AmountIn string
|
||||
ExpectedProfitBP float64 // Expected profit in basis points
|
||||
ExpectedSpreadBP float64 // Expected spread in basis points
|
||||
MinProfitThresholdBP float64 // Minimum profit threshold in basis points
|
||||
Tolerance float64 // Error tolerance in basis points
|
||||
}
|
||||
|
||||
// TestProfitThresholdRegression ensures profit calculations remain stable
|
||||
func TestProfitThresholdRegression(t *testing.T) {
|
||||
// Initialize decimal converter
|
||||
converter := pkgmath.NewDecimalConverter()
|
||||
|
||||
testCases := []ProfitThresholdTestCase{
|
||||
{
|
||||
Name: "ETH_USDC_Small_Profit",
|
||||
Exchange: "uniswap_v2",
|
||||
Reserve0: "1000000000000000000000", // 1000 ETH
|
||||
Reserve1: "2000000000000", // 2M USDC (6 decimals)
|
||||
AmountIn: "1000000000000000000", // 1 ETH
|
||||
ExpectedProfitBP: 25.0, // 0.25% profit expected
|
||||
ExpectedSpreadBP: 50.0, // 0.5% spread expected
|
||||
MinProfitThresholdBP: 10.0, // 0.1% minimum profit
|
||||
Tolerance: 2.0, // 2bp tolerance
|
||||
},
|
||||
{
|
||||
Name: "WBTC_ETH_Medium_Profit",
|
||||
Exchange: "uniswap_v2",
|
||||
Reserve0: "50000000000", // 500 WBTC (8 decimals)
|
||||
Reserve1: "10000000000000000000000", // 10000 ETH
|
||||
AmountIn: "100000000", // 1 WBTC
|
||||
ExpectedProfitBP: 35.0, // 0.35% profit expected
|
||||
ExpectedSpreadBP: 70.0, // 0.7% spread expected
|
||||
MinProfitThresholdBP: 15.0, // 0.15% minimum profit
|
||||
Tolerance: 3.0, // 3bp tolerance
|
||||
},
|
||||
{
|
||||
Name: "ETH_USDC_V3_Concentrated",
|
||||
Exchange: "uniswap_v3",
|
||||
Reserve0: "1000000000000000000000", // 1000 ETH
|
||||
Reserve1: "2000000000000", // 2M USDC
|
||||
AmountIn: "500000000000000000", // 0.5 ETH
|
||||
ExpectedProfitBP: 15.0, // 0.15% profit expected
|
||||
ExpectedSpreadBP: 25.0, // 0.25% spread expected
|
||||
MinProfitThresholdBP: 5.0, // 0.05% minimum profit
|
||||
Tolerance: 1.5, // 1.5bp tolerance
|
||||
},
|
||||
{
|
||||
Name: "USDC_USDT_Stable_Spread",
|
||||
Exchange: "curve",
|
||||
Reserve0: "1000000000000", // 1M USDC (6 decimals)
|
||||
Reserve1: "1000000000000", // 1M USDT (6 decimals)
|
||||
AmountIn: "10000000000", // 10k USDC
|
||||
ExpectedProfitBP: 2.0, // 0.02% profit expected
|
||||
ExpectedSpreadBP: 5.0, // 0.05% spread expected
|
||||
MinProfitThresholdBP: 1.0, // 0.01% minimum profit
|
||||
Tolerance: 0.5, // 0.5bp tolerance
|
||||
},
|
||||
{
|
||||
Name: "ETH_USDC_80_20_Weighted",
|
||||
Exchange: "balancer",
|
||||
Reserve0: "800000000000000000000", // 800 ETH
|
||||
Reserve1: "400000000000", // 400k USDC
|
||||
AmountIn: "2000000000000000000", // 2 ETH
|
||||
ExpectedProfitBP: 40.0, // 0.4% profit expected
|
||||
ExpectedSpreadBP: 80.0, // 0.8% spread expected
|
||||
MinProfitThresholdBP: 20.0, // 0.2% minimum profit
|
||||
Tolerance: 5.0, // 5bp tolerance
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.Name, func(t *testing.T) {
|
||||
// Calculate actual profit and spread
|
||||
actualProfitBP, actualSpreadBP, err := calculateProfitAndSpread(converter, tc)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to calculate profit and spread: %v", err)
|
||||
}
|
||||
|
||||
// Validate profit meets minimum threshold
|
||||
if actualProfitBP < tc.MinProfitThresholdBP {
|
||||
t.Errorf("Profit %.4f bp below minimum threshold %.4f bp",
|
||||
actualProfitBP, tc.MinProfitThresholdBP)
|
||||
}
|
||||
|
||||
// Validate profit within expected range
|
||||
profitError := abs(actualProfitBP - tc.ExpectedProfitBP)
|
||||
if profitError > tc.Tolerance {
|
||||
t.Errorf("Profit error %.4f bp exceeds tolerance %.4f bp (expected %.4f bp, got %.4f bp)",
|
||||
profitError, tc.Tolerance, tc.ExpectedProfitBP, actualProfitBP)
|
||||
}
|
||||
|
||||
// Validate spread within expected range
|
||||
spreadError := abs(actualSpreadBP - tc.ExpectedSpreadBP)
|
||||
if spreadError > tc.Tolerance*2 { // Allow 2x tolerance for spread
|
||||
t.Errorf("Spread error %.4f bp exceeds tolerance %.4f bp (expected %.4f bp, got %.4f bp)",
|
||||
spreadError, tc.Tolerance*2, tc.ExpectedSpreadBP, actualSpreadBP)
|
||||
}
|
||||
|
||||
t.Logf("✓ %s: Profit=%.4f bp, Spread=%.4f bp (within tolerance)",
|
||||
tc.Name, actualProfitBP, actualSpreadBP)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// calculateProfitAndSpread calculates profit and spread for a test case
|
||||
func calculateProfitAndSpread(converter *pkgmath.DecimalConverter, tc ProfitThresholdTestCase) (profitBP, spreadBP float64, err error) {
|
||||
// Determine decimals based on exchange and tokens
|
||||
reserve0Decimals, reserve1Decimals := getTokenDecimals(tc.Exchange, tc.Name)
|
||||
|
||||
// Convert amounts to UniversalDecimal
|
||||
reserve0, err := converter.FromString(tc.Reserve0, reserve0Decimals, "TOKEN0")
|
||||
if err != nil {
|
||||
return 0, 0, fmt.Errorf("invalid reserve0: %w", err)
|
||||
}
|
||||
|
||||
reserve1, err := converter.FromString(tc.Reserve1, reserve1Decimals, "TOKEN1")
|
||||
if err != nil {
|
||||
return 0, 0, fmt.Errorf("invalid reserve1: %w", err)
|
||||
}
|
||||
|
||||
amountIn, err := converter.FromString(tc.AmountIn, reserve0Decimals, "TOKEN0")
|
||||
if err != nil {
|
||||
return 0, 0, fmt.Errorf("invalid amountIn: %w", err)
|
||||
}
|
||||
|
||||
// Calculate spot price
|
||||
var spotPrice *pkgmath.UniversalDecimal
|
||||
switch tc.Exchange {
|
||||
case "uniswap_v2":
|
||||
spotPrice, err = calculateUniswapV2Price(converter, reserve0, reserve1)
|
||||
case "uniswap_v3":
|
||||
spotPrice, err = calculateUniswapV3EstimatedPrice(converter, reserve0, reserve1)
|
||||
case "curve":
|
||||
spotPrice, err = calculateCurvePrice(converter, reserve0, reserve1)
|
||||
case "balancer":
|
||||
spotPrice, err = calculateBalancerPrice(converter, reserve0, reserve1)
|
||||
default:
|
||||
return 0, 0, fmt.Errorf("unsupported exchange: %s", tc.Exchange)
|
||||
}
|
||||
if err != nil {
|
||||
return 0, 0, fmt.Errorf("failed to calculate spot price: %w", err)
|
||||
}
|
||||
|
||||
// Calculate amount out using AMM formula
|
||||
amountOut, err := calculateAMMAmountOut(converter, tc.Exchange, reserve0, reserve1, amountIn)
|
||||
if err != nil {
|
||||
return 0, 0, fmt.Errorf("failed to calculate amount out: %w", err)
|
||||
}
|
||||
|
||||
// Calculate execution price
|
||||
executionPrice := calculateExecutionPrice(converter, amountIn, amountOut, reserve0Decimals, reserve1Decimals)
|
||||
|
||||
// Calculate profit (difference from spot price)
|
||||
profitBP = calculateRelativeErrorBP(converter, spotPrice, executionPrice)
|
||||
|
||||
// Calculate spread (bid-ask spread approximation)
|
||||
spreadBP = profitBP * 2.0 // Simple approximation: spread = 2 * price impact
|
||||
|
||||
return profitBP, spreadBP, nil
|
||||
}
|
||||
|
||||
// Helper functions for price calculations
|
||||
func calculateUniswapV2Price(converter *pkgmath.DecimalConverter, reserve0, reserve1 *pkgmath.UniversalDecimal) (*pkgmath.UniversalDecimal, error) {
|
||||
// Price = reserve1 / reserve0 (normalized to 18 decimals)
|
||||
priceInt := new(big.Int).Mul(reserve1.Value, new(big.Int).Exp(big.NewInt(10), big.NewInt(18), nil))
|
||||
priceInt.Div(priceInt, reserve0.Value)
|
||||
return pkgmath.NewUniversalDecimal(priceInt, 18, "PRICE")
|
||||
}
|
||||
|
||||
func calculateUniswapV3EstimatedPrice(converter *pkgmath.DecimalConverter, reserve0, reserve1 *pkgmath.UniversalDecimal) (*pkgmath.UniversalDecimal, error) {
|
||||
// For simplicity, use the same formula as V2 for testing
|
||||
return calculateUniswapV2Price(converter, reserve0, reserve1)
|
||||
}
|
||||
|
||||
func calculateCurvePrice(converter *pkgmath.DecimalConverter, reserve0, reserve1 *pkgmath.UniversalDecimal) (*pkgmath.UniversalDecimal, error) {
|
||||
// For stable swaps, price is typically 1:1 with small variations
|
||||
return calculateUniswapV2Price(converter, reserve0, reserve1)
|
||||
}
|
||||
|
||||
func calculateBalancerPrice(converter *pkgmath.DecimalConverter, reserve0, reserve1 *pkgmath.UniversalDecimal) (*pkgmath.UniversalDecimal, error) {
|
||||
// Weighted pool: price = (reserve1/weight1) / (reserve0/weight0)
|
||||
// Assuming 80/20 weights: weight0=0.8, weight1=0.2
|
||||
// price = (reserve1/0.2) / (reserve0/0.8) = (reserve1 * 0.8) / (reserve0 * 0.2) = (reserve1 * 4) / reserve0
|
||||
priceInt := new(big.Int).Mul(reserve1.Value, big.NewInt(4))
|
||||
priceInt.Mul(priceInt, new(big.Int).Exp(big.NewInt(10), big.NewInt(18), nil))
|
||||
priceInt.Div(priceInt, reserve0.Value)
|
||||
return pkgmath.NewUniversalDecimal(priceInt, 18, "PRICE")
|
||||
}
|
||||
|
||||
func calculateAMMAmountOut(converter *pkgmath.DecimalConverter, exchange string, reserve0, reserve1, amountIn *pkgmath.UniversalDecimal) (*pkgmath.UniversalDecimal, error) {
|
||||
// Simplified AMM calculation: k = x * y, amountOut = y - k / (x + amountIn)
|
||||
// k = reserve0 * reserve1
|
||||
k := new(big.Int).Mul(reserve0.Value, reserve1.Value)
|
||||
|
||||
// newReserve0 = reserve0 + amountIn
|
||||
newReserve0 := new(big.Int).Add(reserve0.Value, amountIn.Value)
|
||||
|
||||
// newReserve1 = k / newReserve0
|
||||
newReserve1 := new(big.Int).Div(k, newReserve0)
|
||||
|
||||
// amountOut = reserve1 - newReserve1
|
||||
amountOut := new(big.Int).Sub(reserve1.Value, newReserve1)
|
||||
|
||||
// Apply 0.3% fee for most exchanges
|
||||
fee := new(big.Int).Div(amountOut, big.NewInt(333)) // ~0.3%
|
||||
amountOut.Sub(amountOut, fee)
|
||||
|
||||
return pkgmath.NewUniversalDecimal(amountOut, reserve1.Decimals, "TOKEN1")
|
||||
}
|
||||
|
||||
func calculateExecutionPrice(converter *pkgmath.DecimalConverter, amountIn, amountOut *pkgmath.UniversalDecimal, decimalsIn, decimalsOut uint8) *pkgmath.UniversalDecimal {
|
||||
// Execution price = amountOut / amountIn (normalized to 18 decimals)
|
||||
priceInt := new(big.Int).Mul(amountOut.Value, new(big.Int).Exp(big.NewInt(10), big.NewInt(18), nil))
|
||||
priceInt.Div(priceInt, amountIn.Value)
|
||||
|
||||
// Adjust for decimal differences
|
||||
if decimalsOut != decimalsIn {
|
||||
decimalDiff := int64(decimalsIn) - int64(decimalsOut)
|
||||
if decimalDiff > 0 {
|
||||
adjustment := new(big.Int).Exp(big.NewInt(10), big.NewInt(decimalDiff), nil)
|
||||
priceInt.Mul(priceInt, adjustment)
|
||||
} else {
|
||||
adjustment := new(big.Int).Exp(big.NewInt(10), big.NewInt(-decimalDiff), nil)
|
||||
priceInt.Div(priceInt, adjustment)
|
||||
}
|
||||
}
|
||||
|
||||
result, _ := pkgmath.NewUniversalDecimal(priceInt, 18, "EXECUTION_PRICE")
|
||||
return result
|
||||
}
|
||||
|
||||
func calculateRelativeErrorBP(converter *pkgmath.DecimalConverter, expected, actual *pkgmath.UniversalDecimal) float64 {
|
||||
if expected.Value.Cmp(big.NewInt(0)) == 0 {
|
||||
return 10000.0 // Max error if expected is zero
|
||||
}
|
||||
|
||||
// Calculate relative error: |actual - expected| / expected * 10000
|
||||
diff := new(big.Int).Sub(actual.Value, expected.Value)
|
||||
if diff.Sign() < 0 {
|
||||
diff.Neg(diff)
|
||||
}
|
||||
|
||||
// Convert to float64 for percentage calculation
|
||||
diffFloat, _ := new(big.Float).SetInt(diff).Float64()
|
||||
expectedFloat, _ := new(big.Float).SetInt(expected.Value).Float64()
|
||||
|
||||
if expectedFloat == 0 {
|
||||
return 10000.0
|
||||
}
|
||||
|
||||
return (diffFloat / expectedFloat) * 10000.0
|
||||
}
|
||||
|
||||
func getTokenDecimals(exchange, testName string) (uint8, uint8) {
|
||||
switch {
|
||||
case contains(testName, "ETH") && contains(testName, "USDC"):
|
||||
return 18, 6 // ETH=18, USDC=6
|
||||
case contains(testName, "WBTC") && contains(testName, "ETH"):
|
||||
return 8, 18 // WBTC=8, ETH=18
|
||||
case contains(testName, "USDC") && contains(testName, "USDT"):
|
||||
return 6, 6 // USDC=6, USDT=6
|
||||
default:
|
||||
return 18, 18 // Default to 18 decimals
|
||||
}
|
||||
}
|
||||
|
||||
func contains(s, substr string) bool {
|
||||
for i := 0; i <= len(s)-len(substr); i++ {
|
||||
if s[i:i+len(substr)] == substr {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func abs(x float64) float64 {
|
||||
if x < 0 {
|
||||
return -x
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// BenchmarkProfitCalculation benchmarks profit calculation performance
|
||||
func BenchmarkProfitCalculation(b *testing.B) {
|
||||
converter := pkgmath.NewDecimalConverter()
|
||||
|
||||
tc := ProfitThresholdTestCase{
|
||||
Name: "ETH_USDC_Benchmark",
|
||||
Exchange: "uniswap_v2",
|
||||
Reserve0: "1000000000000000000000",
|
||||
Reserve1: "2000000000000",
|
||||
AmountIn: "1000000000000000000",
|
||||
ExpectedProfitBP: 25.0,
|
||||
ExpectedSpreadBP: 50.0,
|
||||
MinProfitThresholdBP: 10.0,
|
||||
Tolerance: 2.0,
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _, err := calculateProfitAndSpread(converter, tc)
|
||||
if err != nil {
|
||||
b.Fatalf("Benchmark failed: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user