package execution import ( "math/big" "testing" "time" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/your-org/mev-bot/pkg/arbitrage" "github.com/your-org/mev-bot/pkg/cache" ) func TestNewUniswapV3Encoder(t *testing.T) { encoder := NewUniswapV3Encoder() assert.NotNil(t, encoder) assert.Equal(t, UniswapV3SwapRouterAddress, encoder.swapRouterAddress) } func TestUniswapV3Encoder_EncodeSwap(t *testing.T) { encoder := NewUniswapV3Encoder() tokenIn := common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1") // WETH tokenOut := common.HexToAddress("0xFF970a61A04b1cA14834A43f5dE4533eBDDB5CC8") // USDC amountIn := big.NewInt(1e18) minAmountOut := big.NewInt(1500e6) poolAddress := common.HexToAddress("0x0000000000000000000000000000000000000001") fee := uint32(3000) // 0.3% recipient := common.HexToAddress("0x0000000000000000000000000000000000000002") deadline := time.Now().Add(5 * time.Minute) to, data, err := encoder.EncodeSwap( tokenIn, tokenOut, amountIn, minAmountOut, poolAddress, fee, recipient, deadline, ) require.NoError(t, err) assert.Equal(t, encoder.swapRouterAddress, to) assert.NotEmpty(t, data) // Check method ID (first 4 bytes) // exactInputSingle((address,address,uint24,address,uint256,uint256,uint256,uint160)) assert.GreaterOrEqual(t, len(data), 4) } func TestUniswapV3Encoder_EncodeMultiHopSwap(t *testing.T) { encoder := NewUniswapV3Encoder() opp := &arbitrage.Opportunity{ InputAmount: big.NewInt(1e18), Path: []arbitrage.SwapStep{ { TokenIn: common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"), TokenOut: common.HexToAddress("0xFF970a61A04b1cA14834A43f5dE4533eBDDB5CC8"), Fee: 3000, PoolAddress: common.HexToAddress("0x0000000000000000000000000000000000000001"), }, { TokenIn: common.HexToAddress("0xFF970a61A04b1cA14834A43f5dE4533eBDDB5CC8"), TokenOut: common.HexToAddress("0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f"), Fee: 3000, PoolAddress: common.HexToAddress("0x0000000000000000000000000000000000000002"), }, }, } recipient := common.HexToAddress("0x0000000000000000000000000000000000000003") minAmountOut := big.NewInt(1e7) deadline := time.Now().Add(5 * time.Minute) to, data, err := encoder.EncodeMultiHopSwap( opp, recipient, minAmountOut, deadline, ) require.NoError(t, err) assert.Equal(t, encoder.swapRouterAddress, to) assert.NotEmpty(t, data) // Verify method ID for exactInput assert.GreaterOrEqual(t, len(data), 4) } func TestUniswapV3Encoder_EncodeMultiHopSwap_SingleStep(t *testing.T) { encoder := NewUniswapV3Encoder() opp := &arbitrage.Opportunity{ InputAmount: big.NewInt(1e18), Path: []arbitrage.SwapStep{ { TokenIn: common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"), TokenOut: common.HexToAddress("0xFF970a61A04b1cA14834A43f5dE4533eBDDB5CC8"), Fee: 3000, PoolAddress: common.HexToAddress("0x0000000000000000000000000000000000000001"), }, }, } recipient := common.HexToAddress("0x0000000000000000000000000000000000000003") minAmountOut := big.NewInt(1500e6) deadline := time.Now().Add(5 * time.Minute) _, _, err := encoder.EncodeMultiHopSwap( opp, recipient, minAmountOut, deadline, ) assert.Error(t, err) assert.Contains(t, err.Error(), "multi-hop requires at least 2 steps") } func TestUniswapV3Encoder_EncodeMultiHopSwap_EmptyPath(t *testing.T) { encoder := NewUniswapV3Encoder() opp := &arbitrage.Opportunity{ InputAmount: big.NewInt(1e18), Path: []arbitrage.SwapStep{}, } recipient := common.HexToAddress("0x0000000000000000000000000000000000000003") minAmountOut := big.NewInt(1500e6) deadline := time.Now().Add(5 * time.Minute) _, _, err := encoder.EncodeMultiHopSwap( opp, recipient, minAmountOut, deadline, ) assert.Error(t, err) assert.Contains(t, err.Error(), "multi-hop requires at least 2 steps") } func TestUniswapV3Encoder_buildEncodedPath(t *testing.T) { encoder := NewUniswapV3Encoder() opp := &arbitrage.Opportunity{ Path: []arbitrage.SwapStep{ { TokenIn: common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"), TokenOut: common.HexToAddress("0xFF970a61A04b1cA14834A43f5dE4533eBDDB5CC8"), Fee: 3000, }, { TokenIn: common.HexToAddress("0xFF970a61A04b1cA14834A43f5dE4533eBDDB5CC8"), TokenOut: common.HexToAddress("0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f"), Fee: 500, }, }, } path := encoder.buildEncodedPath(opp) // Path should be: token (20) + fee (3) + token (20) + fee (3) + token (20) = 66 bytes assert.Len(t, path, 66) // First 20 bytes should be first token assert.Equal(t, opp.Path[0].TokenIn.Bytes(), path[:20]) // Bytes 20-23 should be first fee (3000 = 0x000BB8) assert.Equal(t, []byte{0x00, 0x0B, 0xB8}, path[20:23]) // Bytes 23-43 should be second token assert.Equal(t, opp.Path[0].TokenOut.Bytes(), path[23:43]) // Bytes 43-46 should be second fee (500 = 0x0001F4) assert.Equal(t, []byte{0x00, 0x01, 0xF4}, path[43:46]) // Bytes 46-66 should be third token assert.Equal(t, opp.Path[1].TokenOut.Bytes(), path[46:66]) } func TestUniswapV3Encoder_buildEncodedPath_SingleStep(t *testing.T) { encoder := NewUniswapV3Encoder() opp := &arbitrage.Opportunity{ Path: []arbitrage.SwapStep{ { TokenIn: common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"), TokenOut: common.HexToAddress("0xFF970a61A04b1cA14834A43f5dE4533eBDDB5CC8"), Fee: 3000, }, }, } path := encoder.buildEncodedPath(opp) // Path should be: token (20) + fee (3) + token (20) = 43 bytes assert.Len(t, path, 43) } func TestUniswapV3Encoder_EncodeExactOutput(t *testing.T) { encoder := NewUniswapV3Encoder() tokenIn := common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1") tokenOut := common.HexToAddress("0xFF970a61A04b1cA14834A43f5dE4533eBDDB5CC8") amountOut := big.NewInt(1500e6) maxAmountIn := big.NewInt(2e18) fee := uint32(3000) recipient := common.HexToAddress("0x0000000000000000000000000000000000000002") deadline := time.Now().Add(5 * time.Minute) to, data, err := encoder.EncodeExactOutput( tokenIn, tokenOut, amountOut, maxAmountIn, fee, recipient, deadline, ) require.NoError(t, err) assert.Equal(t, encoder.swapRouterAddress, to) assert.NotEmpty(t, data) assert.GreaterOrEqual(t, len(data), 4) } func TestUniswapV3Encoder_EncodeMulticall(t *testing.T) { encoder := NewUniswapV3Encoder() call1 := []byte{0x01, 0x02, 0x03, 0x04} call2 := []byte{0x05, 0x06, 0x07, 0x08} calls := [][]byte{call1, call2} deadline := time.Now().Add(5 * time.Minute) to, data, err := encoder.EncodeMulticall(calls, deadline) require.NoError(t, err) assert.Equal(t, encoder.swapRouterAddress, to) assert.NotEmpty(t, data) assert.GreaterOrEqual(t, len(data), 4) } func TestUniswapV3Encoder_EncodeMulticall_EmptyCalls(t *testing.T) { encoder := NewUniswapV3Encoder() calls := [][]byte{} deadline := time.Now().Add(5 * time.Minute) to, data, err := encoder.EncodeMulticall(calls, deadline) require.NoError(t, err) assert.Equal(t, encoder.swapRouterAddress, to) assert.NotEmpty(t, data) } func TestUniswapV3Encoder_EncodeMulticall_SingleCall(t *testing.T) { encoder := NewUniswapV3Encoder() call := []byte{0x01, 0x02, 0x03, 0x04} calls := [][]byte{call} deadline := time.Now().Add(5 * time.Minute) to, data, err := encoder.EncodeMulticall(calls, deadline) require.NoError(t, err) assert.Equal(t, encoder.swapRouterAddress, to) assert.NotEmpty(t, data) } func TestUniswapV3Encoder_DifferentFees(t *testing.T) { encoder := NewUniswapV3Encoder() fees := []uint32{ 100, // 0.01% 500, // 0.05% 3000, // 0.3% 10000, // 1% } for _, fee := range fees { t.Run(string(rune(fee)), func(t *testing.T) { tokenIn := common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1") tokenOut := common.HexToAddress("0xFF970a61A04b1cA14834A43f5dE4533eBDDB5CC8") amountIn := big.NewInt(1e18) minAmountOut := big.NewInt(1500e6) poolAddress := common.HexToAddress("0x0000000000000000000000000000000000000001") recipient := common.HexToAddress("0x0000000000000000000000000000000000000002") deadline := time.Now().Add(5 * time.Minute) to, data, err := encoder.EncodeSwap( tokenIn, tokenOut, amountIn, minAmountOut, poolAddress, fee, recipient, deadline, ) require.NoError(t, err) assert.NotEmpty(t, to) assert.NotEmpty(t, data) }) } } func TestUniswapV3Encoder_ZeroAmounts(t *testing.T) { encoder := NewUniswapV3Encoder() tokenIn := common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1") tokenOut := common.HexToAddress("0xFF970a61A04b1cA14834A43f5dE4533eBDDB5CC8") amountIn := big.NewInt(0) minAmountOut := big.NewInt(0) poolAddress := common.HexToAddress("0x0000000000000000000000000000000000000001") fee := uint32(3000) recipient := common.HexToAddress("0x0000000000000000000000000000000000000002") deadline := time.Now().Add(5 * time.Minute) to, data, err := encoder.EncodeSwap( tokenIn, tokenOut, amountIn, minAmountOut, poolAddress, fee, recipient, deadline, ) require.NoError(t, err) assert.NotEmpty(t, to) assert.NotEmpty(t, data) } func TestUniswapV3Encoder_LargeAmounts(t *testing.T) { encoder := NewUniswapV3Encoder() tokenIn := common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1") tokenOut := common.HexToAddress("0xFF970a61A04b1cA14834A43f5dE4533eBDDB5CC8") // Max uint256 amountIn := new(big.Int) amountIn.SetString("115792089237316195423570985008687907853269984665640564039457584007913129639935", 10) minAmountOut := new(big.Int) minAmountOut.SetString("115792089237316195423570985008687907853269984665640564039457584007913129639935", 10) poolAddress := common.HexToAddress("0x0000000000000000000000000000000000000001") fee := uint32(3000) recipient := common.HexToAddress("0x0000000000000000000000000000000000000002") deadline := time.Now().Add(5 * time.Minute) to, data, err := encoder.EncodeSwap( tokenIn, tokenOut, amountIn, minAmountOut, poolAddress, fee, recipient, deadline, ) require.NoError(t, err) assert.NotEmpty(t, to) assert.NotEmpty(t, data) } func TestUniswapV3Encoder_LongPath(t *testing.T) { encoder := NewUniswapV3Encoder() // Create a 5-hop path opp := &arbitrage.Opportunity{ InputAmount: big.NewInt(1e18), Path: []arbitrage.SwapStep{ { TokenIn: common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"), TokenOut: common.HexToAddress("0xFF970a61A04b1cA14834A43f5dE4533eBDDB5CC8"), Fee: 3000, PoolAddress: common.HexToAddress("0x0000000000000000000000000000000000000001"), }, { TokenIn: common.HexToAddress("0xFF970a61A04b1cA14834A43f5dE4533eBDDB5CC8"), TokenOut: common.HexToAddress("0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f"), Fee: 500, PoolAddress: common.HexToAddress("0x0000000000000000000000000000000000000002"), }, { TokenIn: common.HexToAddress("0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f"), TokenOut: common.HexToAddress("0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9"), Fee: 3000, PoolAddress: common.HexToAddress("0x0000000000000000000000000000000000000003"), }, { TokenIn: common.HexToAddress("0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9"), TokenOut: common.HexToAddress("0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1"), Fee: 500, PoolAddress: common.HexToAddress("0x0000000000000000000000000000000000000004"), }, { TokenIn: common.HexToAddress("0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1"), TokenOut: common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"), Fee: 3000, PoolAddress: common.HexToAddress("0x0000000000000000000000000000000000000005"), }, }, } recipient := common.HexToAddress("0x0000000000000000000000000000000000000003") minAmountOut := big.NewInt(1e7) deadline := time.Now().Add(5 * time.Minute) to, data, err := encoder.EncodeMultiHopSwap( opp, recipient, minAmountOut, deadline, ) require.NoError(t, err) assert.Equal(t, encoder.swapRouterAddress, to) assert.NotEmpty(t, data) // Path should be: 20 + (23 * 5) = 135 bytes path := encoder.buildEncodedPath(opp) assert.Len(t, path, 135) } // Benchmark tests func BenchmarkUniswapV3Encoder_EncodeSwap(b *testing.B) { encoder := NewUniswapV3Encoder() tokenIn := common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1") tokenOut := common.HexToAddress("0xFF970a61A04b1cA14834A43f5dE4533eBDDB5CC8") amountIn := big.NewInt(1e18) minAmountOut := big.NewInt(1500e6) poolAddress := common.HexToAddress("0x0000000000000000000000000000000000000001") fee := uint32(3000) recipient := common.HexToAddress("0x0000000000000000000000000000000000000002") deadline := time.Now().Add(5 * time.Minute) b.ResetTimer() for i := 0; i < b.N; i++ { _, _, _ = encoder.EncodeSwap( tokenIn, tokenOut, amountIn, minAmountOut, poolAddress, fee, recipient, deadline, ) } } func BenchmarkUniswapV3Encoder_buildEncodedPath(b *testing.B) { encoder := NewUniswapV3Encoder() opp := &arbitrage.Opportunity{ Path: []arbitrage.SwapStep{ { TokenIn: common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"), TokenOut: common.HexToAddress("0xFF970a61A04b1cA14834A43f5dE4533eBDDB5CC8"), Fee: 3000, }, { TokenIn: common.HexToAddress("0xFF970a61A04b1cA14834A43f5dE4533eBDDB5CC8"), TokenOut: common.HexToAddress("0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f"), Fee: 500, }, }, } b.ResetTimer() for i := 0; i < b.N; i++ { _ = encoder.buildEncodedPath(opp) } }