package arbitrage import ( "math/big" "testing" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/fraktal/mev-beta/bindings/contracts" "github.com/fraktal/mev-beta/internal/logger" pkgtypes "github.com/fraktal/mev-beta/pkg/types" ) type mockArbitrageLogParser struct { event *contracts.ArbitrageExecutorArbitrageExecuted err error } func (m *mockArbitrageLogParser) ParseArbitrageExecuted(types.Log) (*contracts.ArbitrageExecutorArbitrageExecuted, error) { if m.err != nil { return nil, m.err } return m.event, nil } func newTestLogger() *logger.Logger { return logger.New("error", "text", "") } func TestCalculateActualProfit_UsesArbitrageEvent(t *testing.T) { arbitrageAddr := common.HexToAddress("0x1234567890123456789012345678901234567890") executor := NewFlashSwapExecutor(nil, newTestLogger(), nil, nil, common.Address{}, arbitrageAddr, ExecutionConfig{}) profit := new(big.Int).Mul(big.NewInt(2), powerOfTenInt(18)) gasPrice := big.NewInt(1_000_000_000) // 1 gwei receipt := &types.Receipt{ Logs: []*types.Log{{Address: arbitrageAddr}}, GasUsed: 100000, EffectiveGasPrice: gasPrice, } event := &contracts.ArbitrageExecutorArbitrageExecuted{ Tokens: []common.Address{executor.ethReferenceToken}, Amounts: []*big.Int{profit}, Profit: profit, } executor.arbitrageBinding = &mockArbitrageLogParser{event: event} opportunity := &pkgtypes.ArbitrageOpportunity{ TokenOut: executor.ethReferenceToken, Quantities: &pkgtypes.OpportunityQuantities{ NetProfit: pkgtypes.DecimalAmount{Symbol: "WETH", Decimals: 18}, }, } actual, err := executor.calculateActualProfit(receipt, opportunity) if err != nil { t.Fatalf("calculateActualProfit returned error: %v", err) } gasCost := new(big.Int).Mul(big.NewInt(0).SetUint64(receipt.GasUsed), gasPrice) expected := new(big.Int).Sub(profit, gasCost) if actual.Value.Cmp(expected) != 0 { t.Fatalf("expected profit %s, got %s", expected.String(), actual.Value.String()) } if actual.Decimals != 18 { t.Fatalf("expected decimals 18, got %d", actual.Decimals) } if actual.Symbol != "WETH" { t.Fatalf("expected symbol WETH, got %s", actual.Symbol) } } func TestCalculateActualProfit_FallbackToOpportunity(t *testing.T) { arbitrageAddr := common.HexToAddress("0x1234567890123456789012345678901234567891") executor := NewFlashSwapExecutor(nil, newTestLogger(), nil, nil, common.Address{}, arbitrageAddr, ExecutionConfig{}) profit := big.NewInt(1_500_000) // 1.5 USDC with 6 decimals gasPrice := big.NewInt(1_000_000_000) // 1 gwei receipt := &types.Receipt{ Logs: []*types.Log{{Address: arbitrageAddr}}, GasUsed: 100000, EffectiveGasPrice: gasPrice, } opportunity := &pkgtypes.ArbitrageOpportunity{ TokenOut: common.HexToAddress("0xaF88d065e77c8cC2239327C5EDb3A432268e5831"), // USDC NetProfit: profit, Quantities: &pkgtypes.OpportunityQuantities{ NetProfit: pkgtypes.DecimalAmount{Symbol: "USDC", Decimals: 6}, }, } actual, err := executor.calculateActualProfit(receipt, opportunity) if err != nil { t.Fatalf("calculateActualProfit returned error: %v", err) } gasCostEth := new(big.Int).Mul(big.NewInt(0).SetUint64(receipt.GasUsed), gasPrice) // Gas cost conversion: 0.0001 ETH * 2000 USD / 1 USD = 0.2 USDC => 200000 units gasCostUSDC := big.NewInt(200000) expected := new(big.Int).Sub(profit, gasCostUSDC) if actual.Value.Cmp(expected) != 0 { t.Fatalf("expected profit %s, got %s", expected.String(), actual.Value.String()) } if actual.Decimals != 6 { t.Fatalf("expected decimals 6, got %d", actual.Decimals) } if actual.Symbol != "USDC" { t.Fatalf("expected symbol USDC, got %s", actual.Symbol) } // Ensure ETH gas cost unchanged for reference if gasCostEth.Sign() == 0 { t.Fatalf("expected non-zero gas cost") } } func TestCalculateActualProfit_NoPriceData(t *testing.T) { arbitrageAddr := common.HexToAddress("0x1234567890123456789012345678901234567892") executor := NewFlashSwapExecutor(nil, newTestLogger(), nil, nil, common.Address{}, arbitrageAddr, ExecutionConfig{}) profit := new(big.Int).Mul(big.NewInt(3), powerOfTenInt(17)) // 0.3 units with 18 decimals receipt := &types.Receipt{ Logs: []*types.Log{{Address: arbitrageAddr}}, GasUsed: 50000, EffectiveGasPrice: big.NewInt(2_000_000_000), } unknownToken := common.HexToAddress("0x9b8D58d870495459c1004C34357F3bf06c0dB0b3") opportunity := &pkgtypes.ArbitrageOpportunity{ TokenOut: unknownToken, NetProfit: profit, Quantities: &pkgtypes.OpportunityQuantities{ NetProfit: pkgtypes.DecimalAmount{Symbol: "XYZ", Decimals: 18}, }, } actual, err := executor.calculateActualProfit(receipt, opportunity) if err != nil { t.Fatalf("calculateActualProfit returned error: %v", err) } if actual.Value.Cmp(profit) != 0 { t.Fatalf("expected profit %s, got %s", profit.String(), actual.Value.String()) } if actual.Symbol != "XYZ" { t.Fatalf("expected symbol XYZ, got %s", actual.Symbol) } } func TestParseRevertReason_ErrorString(t *testing.T) { strType, err := abi.NewType("string", "", nil) if err != nil { t.Fatalf("failed to create ABI type: %v", err) } args := abi.Arguments{{Type: strType}} payload, err := args.Pack("execution reverted: slippage limit") if err != nil { t.Fatalf("failed to pack revert reason: %v", err) } data := append([]byte{0x08, 0xc3, 0x79, 0xa0}, payload...) reason := parseRevertReason(data) if reason != "execution reverted: slippage limit" { t.Fatalf("expected revert reason, got %q", reason) } } func TestParseRevertReason_PanicCode(t *testing.T) { uintType, err := abi.NewType("uint256", "", nil) if err != nil { t.Fatalf("failed to create uint256 ABI type: %v", err) } args := abi.Arguments{{Type: uintType}} payload, err := args.Pack(big.NewInt(0x41)) if err != nil { t.Fatalf("failed to pack panic code: %v", err) } data := append([]byte{0x4e, 0x48, 0x7b, 0x71}, payload...) reason := parseRevertReason(data) if reason != "panic code 0x41" { t.Fatalf("expected panic code, got %q", reason) } } func TestParseRevertReason_Unknown(t *testing.T) { reason := parseRevertReason([]byte{0x00, 0x01, 0x02, 0x03}) if reason != "" { t.Fatalf("expected empty reason, got %q", reason) } }