Some checks failed
V2 CI/CD Pipeline / Pre-Flight Checks (push) Has been cancelled
V2 CI/CD Pipeline / Build & Dependencies (push) Has been cancelled
V2 CI/CD Pipeline / Code Quality & Linting (push) Has been cancelled
V2 CI/CD Pipeline / Unit Tests (100% Coverage Required) (push) Has been cancelled
V2 CI/CD Pipeline / Integration Tests (push) Has been cancelled
V2 CI/CD Pipeline / Performance Benchmarks (push) Has been cancelled
V2 CI/CD Pipeline / Decimal Precision Validation (push) Has been cancelled
V2 CI/CD Pipeline / Modularity Validation (push) Has been cancelled
V2 CI/CD Pipeline / Final Validation Summary (push) Has been cancelled
Add comprehensive unit tests for all execution engine components: Component Test Coverage: - UniswapV2 encoder: 15 test cases + benchmarks - UniswapV3 encoder: 20 test cases + benchmarks - Curve encoder: 16 test cases + benchmarks - Flashloan manager: 18 test cases + benchmarks - Transaction builder: 15 test cases + benchmarks - Risk manager: 25 test cases + benchmarks - Executor: 20 test cases + benchmarks Test Categories: - Happy path scenarios - Error handling and edge cases - Zero/invalid inputs - Boundary conditions (max amounts, limits) - Concurrent operations (nonce management) - Configuration validation - State management Key Test Features: - Protocol-specific encoding validation - ABI encoding correctness - Gas calculation accuracy - Slippage calculation - Nonce management thread safety - Circuit breaker behavior - Risk assessment rules - Transaction lifecycle Total: 129 test cases + performance benchmarks Target: 100% test coverage for execution engine Related to Phase 4 (Execution Engine) implementation. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
561 lines
18 KiB
Go
561 lines
18 KiB
Go
package execution
|
|
|
|
import (
|
|
"context"
|
|
"log/slog"
|
|
"math/big"
|
|
"os"
|
|
"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"
|
|
mevtypes "github.com/your-org/mev-bot/pkg/types"
|
|
)
|
|
|
|
func TestDefaultTransactionBuilderConfig(t *testing.T) {
|
|
config := DefaultTransactionBuilderConfig()
|
|
|
|
assert.NotNil(t, config)
|
|
assert.Equal(t, uint16(50), config.DefaultSlippageBPS)
|
|
assert.Equal(t, uint16(300), config.MaxSlippageBPS)
|
|
assert.Equal(t, float64(1.2), config.GasLimitMultiplier)
|
|
assert.Equal(t, uint64(3000000), config.MaxGasLimit)
|
|
assert.Equal(t, 5*time.Minute, config.DefaultDeadline)
|
|
}
|
|
|
|
func TestNewTransactionBuilder(t *testing.T) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
chainID := big.NewInt(42161) // Arbitrum
|
|
|
|
builder := NewTransactionBuilder(nil, chainID, logger)
|
|
|
|
assert.NotNil(t, builder)
|
|
assert.NotNil(t, builder.config)
|
|
assert.Equal(t, chainID, builder.chainID)
|
|
assert.NotNil(t, builder.uniswapV2Encoder)
|
|
assert.NotNil(t, builder.uniswapV3Encoder)
|
|
assert.NotNil(t, builder.curveEncoder)
|
|
}
|
|
|
|
func TestTransactionBuilder_BuildTransaction_SingleSwap_UniswapV2(t *testing.T) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
chainID := big.NewInt(42161)
|
|
builder := NewTransactionBuilder(nil, chainID, logger)
|
|
|
|
opp := &arbitrage.Opportunity{
|
|
ID: "test-opp-1",
|
|
Type: arbitrage.OpportunityTypeTwoPool,
|
|
InputToken: common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"),
|
|
InputAmount: big.NewInt(1e18),
|
|
OutputToken: common.HexToAddress("0xFF970a61A04b1cA14834A43f5dE4533eBDDB5CC8"),
|
|
OutputAmount: big.NewInt(1500e6),
|
|
Path: []arbitrage.SwapStep{
|
|
{
|
|
Protocol: mevtypes.ProtocolUniswapV2,
|
|
TokenIn: common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"),
|
|
TokenOut: common.HexToAddress("0xFF970a61A04b1cA14834A43f5dE4533eBDDB5CC8"),
|
|
AmountIn: big.NewInt(1e18),
|
|
AmountOut: big.NewInt(1500e6),
|
|
PoolAddress: common.HexToAddress("0x0000000000000000000000000000000000000001"),
|
|
},
|
|
},
|
|
EstimatedGas: 150000,
|
|
}
|
|
|
|
fromAddress := common.HexToAddress("0x0000000000000000000000000000000000000002")
|
|
|
|
tx, err := builder.BuildTransaction(context.Background(), opp, fromAddress)
|
|
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, tx)
|
|
assert.NotEmpty(t, tx.To)
|
|
assert.NotEmpty(t, tx.Data)
|
|
assert.NotNil(t, tx.Value)
|
|
assert.Greater(t, tx.GasLimit, uint64(0))
|
|
assert.NotNil(t, tx.MaxFeePerGas)
|
|
assert.NotNil(t, tx.MaxPriorityFeePerGas)
|
|
assert.NotNil(t, tx.MinOutput)
|
|
assert.False(t, tx.RequiresFlashloan)
|
|
assert.Equal(t, uint16(50), tx.Slippage) // Default slippage
|
|
}
|
|
|
|
func TestTransactionBuilder_BuildTransaction_SingleSwap_UniswapV3(t *testing.T) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
chainID := big.NewInt(42161)
|
|
builder := NewTransactionBuilder(nil, chainID, logger)
|
|
|
|
opp := &arbitrage.Opportunity{
|
|
ID: "test-opp-2",
|
|
Type: arbitrage.OpportunityTypeTwoPool,
|
|
InputToken: common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"),
|
|
InputAmount: big.NewInt(1e18),
|
|
OutputToken: common.HexToAddress("0xFF970a61A04b1cA14834A43f5dE4533eBDDB5CC8"),
|
|
OutputAmount: big.NewInt(1500e6),
|
|
Path: []arbitrage.SwapStep{
|
|
{
|
|
Protocol: mevtypes.ProtocolUniswapV3,
|
|
TokenIn: common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"),
|
|
TokenOut: common.HexToAddress("0xFF970a61A04b1cA14834A43f5dE4533eBDDB5CC8"),
|
|
AmountIn: big.NewInt(1e18),
|
|
AmountOut: big.NewInt(1500e6),
|
|
PoolAddress: common.HexToAddress("0x0000000000000000000000000000000000000001"),
|
|
Fee: 3000, // 0.3%
|
|
},
|
|
},
|
|
EstimatedGas: 150000,
|
|
}
|
|
|
|
fromAddress := common.HexToAddress("0x0000000000000000000000000000000000000002")
|
|
|
|
tx, err := builder.BuildTransaction(context.Background(), opp, fromAddress)
|
|
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, tx)
|
|
assert.NotEmpty(t, tx.To)
|
|
assert.NotEmpty(t, tx.Data)
|
|
assert.Equal(t, UniswapV3SwapRouterAddress, tx.To)
|
|
}
|
|
|
|
func TestTransactionBuilder_BuildTransaction_MultiHop_UniswapV2(t *testing.T) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
chainID := big.NewInt(42161)
|
|
builder := NewTransactionBuilder(nil, chainID, logger)
|
|
|
|
opp := &arbitrage.Opportunity{
|
|
ID: "test-opp-3",
|
|
Type: arbitrage.OpportunityTypeMultiHop,
|
|
InputToken: common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"),
|
|
InputAmount: big.NewInt(1e18),
|
|
OutputToken: common.HexToAddress("0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f"),
|
|
OutputAmount: big.NewInt(1e7),
|
|
Path: []arbitrage.SwapStep{
|
|
{
|
|
Protocol: mevtypes.ProtocolUniswapV2,
|
|
TokenIn: common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"),
|
|
TokenOut: common.HexToAddress("0xFF970a61A04b1cA14834A43f5dE4533eBDDB5CC8"),
|
|
AmountIn: big.NewInt(1e18),
|
|
AmountOut: big.NewInt(1500e6),
|
|
PoolAddress: common.HexToAddress("0x0000000000000000000000000000000000000001"),
|
|
},
|
|
{
|
|
Protocol: mevtypes.ProtocolUniswapV2,
|
|
TokenIn: common.HexToAddress("0xFF970a61A04b1cA14834A43f5dE4533eBDDB5CC8"),
|
|
TokenOut: common.HexToAddress("0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f"),
|
|
AmountIn: big.NewInt(1500e6),
|
|
AmountOut: big.NewInt(1e7),
|
|
PoolAddress: common.HexToAddress("0x0000000000000000000000000000000000000002"),
|
|
},
|
|
},
|
|
EstimatedGas: 250000,
|
|
}
|
|
|
|
fromAddress := common.HexToAddress("0x0000000000000000000000000000000000000002")
|
|
|
|
tx, err := builder.BuildTransaction(context.Background(), opp, fromAddress)
|
|
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, tx)
|
|
assert.NotEmpty(t, tx.To)
|
|
assert.NotEmpty(t, tx.Data)
|
|
assert.Equal(t, UniswapV2RouterAddress, tx.To)
|
|
}
|
|
|
|
func TestTransactionBuilder_BuildTransaction_MultiHop_UniswapV3(t *testing.T) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
chainID := big.NewInt(42161)
|
|
builder := NewTransactionBuilder(nil, chainID, logger)
|
|
|
|
opp := &arbitrage.Opportunity{
|
|
ID: "test-opp-4",
|
|
Type: arbitrage.OpportunityTypeMultiHop,
|
|
InputToken: common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"),
|
|
InputAmount: big.NewInt(1e18),
|
|
OutputToken: common.HexToAddress("0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f"),
|
|
OutputAmount: big.NewInt(1e7),
|
|
Path: []arbitrage.SwapStep{
|
|
{
|
|
Protocol: mevtypes.ProtocolUniswapV3,
|
|
TokenIn: common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"),
|
|
TokenOut: common.HexToAddress("0xFF970a61A04b1cA14834A43f5dE4533eBDDB5CC8"),
|
|
AmountIn: big.NewInt(1e18),
|
|
AmountOut: big.NewInt(1500e6),
|
|
PoolAddress: common.HexToAddress("0x0000000000000000000000000000000000000001"),
|
|
Fee: 3000,
|
|
},
|
|
{
|
|
Protocol: mevtypes.ProtocolUniswapV3,
|
|
TokenIn: common.HexToAddress("0xFF970a61A04b1cA14834A43f5dE4533eBDDB5CC8"),
|
|
TokenOut: common.HexToAddress("0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f"),
|
|
AmountIn: big.NewInt(1500e6),
|
|
AmountOut: big.NewInt(1e7),
|
|
PoolAddress: common.HexToAddress("0x0000000000000000000000000000000000000002"),
|
|
Fee: 500,
|
|
},
|
|
},
|
|
EstimatedGas: 250000,
|
|
}
|
|
|
|
fromAddress := common.HexToAddress("0x0000000000000000000000000000000000000002")
|
|
|
|
tx, err := builder.BuildTransaction(context.Background(), opp, fromAddress)
|
|
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, tx)
|
|
assert.Equal(t, UniswapV3SwapRouterAddress, tx.To)
|
|
}
|
|
|
|
func TestTransactionBuilder_BuildTransaction_Curve(t *testing.T) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
chainID := big.NewInt(42161)
|
|
builder := NewTransactionBuilder(nil, chainID, logger)
|
|
|
|
opp := &arbitrage.Opportunity{
|
|
ID: "test-opp-5",
|
|
Type: arbitrage.OpportunityTypeTwoPool,
|
|
InputToken: common.HexToAddress("0xFF970a61A04b1cA14834A43f5dE4533eBDDB5CC8"),
|
|
InputAmount: big.NewInt(1500e6),
|
|
OutputToken: common.HexToAddress("0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9"),
|
|
OutputAmount: big.NewInt(1500e6),
|
|
Path: []arbitrage.SwapStep{
|
|
{
|
|
Protocol: mevtypes.ProtocolCurve,
|
|
TokenIn: common.HexToAddress("0xFF970a61A04b1cA14834A43f5dE4533eBDDB5CC8"),
|
|
TokenOut: common.HexToAddress("0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9"),
|
|
AmountIn: big.NewInt(1500e6),
|
|
AmountOut: big.NewInt(1500e6),
|
|
PoolAddress: common.HexToAddress("0x0000000000000000000000000000000000000001"),
|
|
},
|
|
},
|
|
EstimatedGas: 200000,
|
|
}
|
|
|
|
fromAddress := common.HexToAddress("0x0000000000000000000000000000000000000002")
|
|
|
|
tx, err := builder.BuildTransaction(context.Background(), opp, fromAddress)
|
|
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, tx)
|
|
// For Curve, tx.To should be the pool address
|
|
assert.Equal(t, opp.Path[0].PoolAddress, tx.To)
|
|
}
|
|
|
|
func TestTransactionBuilder_BuildTransaction_EmptyPath(t *testing.T) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
chainID := big.NewInt(42161)
|
|
builder := NewTransactionBuilder(nil, chainID, logger)
|
|
|
|
opp := &arbitrage.Opportunity{
|
|
ID: "test-opp-6",
|
|
InputToken: common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"),
|
|
InputAmount: big.NewInt(1e18),
|
|
Path: []arbitrage.SwapStep{},
|
|
}
|
|
|
|
fromAddress := common.HexToAddress("0x0000000000000000000000000000000000000002")
|
|
|
|
_, err := builder.BuildTransaction(context.Background(), opp, fromAddress)
|
|
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "empty swap path")
|
|
}
|
|
|
|
func TestTransactionBuilder_BuildTransaction_UnsupportedProtocol(t *testing.T) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
chainID := big.NewInt(42161)
|
|
builder := NewTransactionBuilder(nil, chainID, logger)
|
|
|
|
opp := &arbitrage.Opportunity{
|
|
ID: "test-opp-7",
|
|
InputToken: common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"),
|
|
InputAmount: big.NewInt(1e18),
|
|
Path: []arbitrage.SwapStep{
|
|
{
|
|
Protocol: "unknown_protocol",
|
|
TokenIn: common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"),
|
|
TokenOut: common.HexToAddress("0xFF970a61A04b1cA14834A43f5dE4533eBDDB5CC8"),
|
|
AmountIn: big.NewInt(1e18),
|
|
AmountOut: big.NewInt(1500e6),
|
|
PoolAddress: common.HexToAddress("0x0000000000000000000000000000000000000001"),
|
|
},
|
|
},
|
|
}
|
|
|
|
fromAddress := common.HexToAddress("0x0000000000000000000000000000000000000002")
|
|
|
|
_, err := builder.BuildTransaction(context.Background(), opp, fromAddress)
|
|
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "unsupported protocol")
|
|
}
|
|
|
|
func TestTransactionBuilder_calculateMinOutput(t *testing.T) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
chainID := big.NewInt(42161)
|
|
builder := NewTransactionBuilder(nil, chainID, logger)
|
|
|
|
tests := []struct {
|
|
name string
|
|
outputAmount *big.Int
|
|
slippageBPS uint16
|
|
expectedMin *big.Int
|
|
}{
|
|
{
|
|
name: "0.5% slippage",
|
|
outputAmount: big.NewInt(1000e6),
|
|
slippageBPS: 50,
|
|
expectedMin: big.NewInt(995e6), // 0.5% less
|
|
},
|
|
{
|
|
name: "1% slippage",
|
|
outputAmount: big.NewInt(1000e6),
|
|
slippageBPS: 100,
|
|
expectedMin: big.NewInt(990e6), // 1% less
|
|
},
|
|
{
|
|
name: "3% slippage",
|
|
outputAmount: big.NewInt(1000e6),
|
|
slippageBPS: 300,
|
|
expectedMin: big.NewInt(970e6), // 3% less
|
|
},
|
|
{
|
|
name: "Zero slippage",
|
|
outputAmount: big.NewInt(1000e6),
|
|
slippageBPS: 0,
|
|
expectedMin: big.NewInt(1000e6), // No change
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
minOutput := builder.calculateMinOutput(tt.outputAmount, tt.slippageBPS)
|
|
assert.Equal(t, tt.expectedMin, minOutput)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTransactionBuilder_calculateGasLimit(t *testing.T) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
chainID := big.NewInt(42161)
|
|
builder := NewTransactionBuilder(nil, chainID, logger)
|
|
|
|
tests := []struct {
|
|
name string
|
|
estimatedGas uint64
|
|
expectedMin uint64
|
|
expectedMax uint64
|
|
}{
|
|
{
|
|
name: "Normal gas estimate",
|
|
estimatedGas: 150000,
|
|
expectedMin: 180000, // 150k * 1.2
|
|
expectedMax: 180001,
|
|
},
|
|
{
|
|
name: "High gas estimate",
|
|
estimatedGas: 2500000,
|
|
expectedMin: 3000000, // Capped at max
|
|
expectedMax: 3000000,
|
|
},
|
|
{
|
|
name: "Zero gas estimate",
|
|
estimatedGas: 0,
|
|
expectedMin: 0, // 0 * 1.2
|
|
expectedMax: 0,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
gasLimit := builder.calculateGasLimit(tt.estimatedGas)
|
|
assert.GreaterOrEqual(t, gasLimit, tt.expectedMin)
|
|
assert.LessOrEqual(t, gasLimit, tt.expectedMax)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTransactionBuilder_SignTransaction(t *testing.T) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
chainID := big.NewInt(42161)
|
|
builder := NewTransactionBuilder(nil, chainID, logger)
|
|
|
|
// Create a test private key
|
|
privateKey, err := crypto.GenerateKey()
|
|
require.NoError(t, err)
|
|
|
|
tx := &SwapTransaction{
|
|
To: common.HexToAddress("0x0000000000000000000000000000000000000001"),
|
|
Data: []byte{0x01, 0x02, 0x03, 0x04},
|
|
Value: big.NewInt(0),
|
|
GasLimit: 180000,
|
|
MaxFeePerGas: big.NewInt(100e9), // 100 gwei
|
|
MaxPriorityFeePerGas: big.NewInt(2e9), // 2 gwei
|
|
}
|
|
|
|
nonce := uint64(5)
|
|
|
|
signedTx, err := builder.SignTransaction(tx, nonce, crypto.FromECDSA(privateKey))
|
|
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, signedTx)
|
|
assert.Equal(t, nonce, signedTx.Nonce())
|
|
assert.Equal(t, tx.To, *signedTx.To())
|
|
assert.Equal(t, tx.GasLimit, signedTx.Gas())
|
|
assert.Equal(t, tx.MaxFeePerGas, signedTx.GasFeeCap())
|
|
assert.Equal(t, tx.MaxPriorityFeePerGas, signedTx.GasTipCap())
|
|
}
|
|
|
|
func TestTransactionBuilder_SignTransaction_InvalidKey(t *testing.T) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
chainID := big.NewInt(42161)
|
|
builder := NewTransactionBuilder(nil, chainID, logger)
|
|
|
|
tx := &SwapTransaction{
|
|
To: common.HexToAddress("0x0000000000000000000000000000000000000001"),
|
|
Data: []byte{0x01, 0x02, 0x03, 0x04},
|
|
Value: big.NewInt(0),
|
|
GasLimit: 180000,
|
|
MaxFeePerGas: big.NewInt(100e9),
|
|
MaxPriorityFeePerGas: big.NewInt(2e9),
|
|
}
|
|
|
|
nonce := uint64(5)
|
|
invalidKey := []byte{0x01, 0x02, 0x03} // Too short
|
|
|
|
_, err := builder.SignTransaction(tx, nonce, invalidKey)
|
|
|
|
assert.Error(t, err)
|
|
}
|
|
|
|
func TestTransactionBuilder_CustomSlippage(t *testing.T) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
chainID := big.NewInt(42161)
|
|
|
|
config := DefaultTransactionBuilderConfig()
|
|
config.DefaultSlippageBPS = 100 // 1% slippage
|
|
|
|
builder := NewTransactionBuilder(config, chainID, logger)
|
|
|
|
opp := &arbitrage.Opportunity{
|
|
ID: "test-opp-8",
|
|
Type: arbitrage.OpportunityTypeTwoPool,
|
|
InputToken: common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"),
|
|
InputAmount: big.NewInt(1e18),
|
|
OutputToken: common.HexToAddress("0xFF970a61A04b1cA14834A43f5dE4533eBDDB5CC8"),
|
|
OutputAmount: big.NewInt(1000e6),
|
|
Path: []arbitrage.SwapStep{
|
|
{
|
|
Protocol: mevtypes.ProtocolUniswapV2,
|
|
TokenIn: common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"),
|
|
TokenOut: common.HexToAddress("0xFF970a61A04b1cA14834A43f5dE4533eBDDB5CC8"),
|
|
AmountIn: big.NewInt(1e18),
|
|
AmountOut: big.NewInt(1000e6),
|
|
PoolAddress: common.HexToAddress("0x0000000000000000000000000000000000000001"),
|
|
},
|
|
},
|
|
EstimatedGas: 150000,
|
|
}
|
|
|
|
fromAddress := common.HexToAddress("0x0000000000000000000000000000000000000002")
|
|
|
|
tx, err := builder.BuildTransaction(context.Background(), opp, fromAddress)
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, uint16(100), tx.Slippage)
|
|
// MinOutput should be 990e6 (1% slippage on 1000e6)
|
|
assert.Equal(t, big.NewInt(990e6), tx.MinOutput)
|
|
}
|
|
|
|
func TestTransactionBuilder_ZeroAmounts(t *testing.T) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
chainID := big.NewInt(42161)
|
|
builder := NewTransactionBuilder(nil, chainID, logger)
|
|
|
|
opp := &arbitrage.Opportunity{
|
|
ID: "test-opp-9",
|
|
InputToken: common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"),
|
|
InputAmount: big.NewInt(0),
|
|
OutputToken: common.HexToAddress("0xFF970a61A04b1cA14834A43f5dE4533eBDDB5CC8"),
|
|
OutputAmount: big.NewInt(0),
|
|
Path: []arbitrage.SwapStep{
|
|
{
|
|
Protocol: mevtypes.ProtocolUniswapV2,
|
|
TokenIn: common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"),
|
|
TokenOut: common.HexToAddress("0xFF970a61A04b1cA14834A43f5dE4533eBDDB5CC8"),
|
|
AmountIn: big.NewInt(0),
|
|
AmountOut: big.NewInt(0),
|
|
PoolAddress: common.HexToAddress("0x0000000000000000000000000000000000000001"),
|
|
},
|
|
},
|
|
EstimatedGas: 150000,
|
|
}
|
|
|
|
fromAddress := common.HexToAddress("0x0000000000000000000000000000000000000002")
|
|
|
|
tx, err := builder.BuildTransaction(context.Background(), opp, fromAddress)
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, big.NewInt(0), tx.MinOutput)
|
|
}
|
|
|
|
// Benchmark tests
|
|
func BenchmarkTransactionBuilder_BuildTransaction(b *testing.B) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
chainID := big.NewInt(42161)
|
|
builder := NewTransactionBuilder(nil, chainID, logger)
|
|
|
|
opp := &arbitrage.Opportunity{
|
|
ID: "bench-opp",
|
|
Type: arbitrage.OpportunityTypeTwoPool,
|
|
InputToken: common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"),
|
|
InputAmount: big.NewInt(1e18),
|
|
OutputToken: common.HexToAddress("0xFF970a61A04b1cA14834A43f5dE4533eBDDB5CC8"),
|
|
OutputAmount: big.NewInt(1500e6),
|
|
Path: []arbitrage.SwapStep{
|
|
{
|
|
Protocol: mevtypes.ProtocolUniswapV2,
|
|
TokenIn: common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"),
|
|
TokenOut: common.HexToAddress("0xFF970a61A04b1cA14834A43f5dE4533eBDDB5CC8"),
|
|
AmountIn: big.NewInt(1e18),
|
|
AmountOut: big.NewInt(1500e6),
|
|
PoolAddress: common.HexToAddress("0x0000000000000000000000000000000000000001"),
|
|
},
|
|
},
|
|
EstimatedGas: 150000,
|
|
}
|
|
|
|
fromAddress := common.HexToAddress("0x0000000000000000000000000000000000000002")
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_, _ = builder.BuildTransaction(context.Background(), opp, fromAddress)
|
|
}
|
|
}
|
|
|
|
func BenchmarkTransactionBuilder_SignTransaction(b *testing.B) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
chainID := big.NewInt(42161)
|
|
builder := NewTransactionBuilder(nil, chainID, logger)
|
|
|
|
privateKey, _ := crypto.GenerateKey()
|
|
|
|
tx := &SwapTransaction{
|
|
To: common.HexToAddress("0x0000000000000000000000000000000000000001"),
|
|
Data: []byte{0x01, 0x02, 0x03, 0x04},
|
|
Value: big.NewInt(0),
|
|
GasLimit: 180000,
|
|
MaxFeePerGas: big.NewInt(100e9),
|
|
MaxPriorityFeePerGas: big.NewInt(2e9),
|
|
}
|
|
|
|
nonce := uint64(5)
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_, _ = builder.SignTransaction(tx, nonce, crypto.FromECDSA(privateKey))
|
|
}
|
|
}
|