Update module name to github.com/fraktal/mev-beta and fix channel closing issues in pipeline stages
This commit is contained in:
219
pkg/events/parser.go
Normal file
219
pkg/events/parser.go
Normal file
@@ -0,0 +1,219 @@
|
||||
package events
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
// EventType represents the type of DEX event
|
||||
type EventType int
|
||||
|
||||
const (
|
||||
Unknown EventType = iota
|
||||
Swap
|
||||
AddLiquidity
|
||||
RemoveLiquidity
|
||||
NewPool
|
||||
)
|
||||
|
||||
// String returns a string representation of the event type
|
||||
func (et EventType) String() string {
|
||||
switch et {
|
||||
case Unknown:
|
||||
return "Unknown"
|
||||
case Swap:
|
||||
return "Swap"
|
||||
case AddLiquidity:
|
||||
return "AddLiquidity"
|
||||
case RemoveLiquidity:
|
||||
return "RemoveLiquidity"
|
||||
case NewPool:
|
||||
return "NewPool"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
}
|
||||
|
||||
// Event represents a parsed DEX event
|
||||
type Event struct {
|
||||
Type EventType
|
||||
Protocol string // UniswapV2, UniswapV3, SushiSwap, etc.
|
||||
PoolAddress common.Address
|
||||
Token0 common.Address
|
||||
Token1 common.Address
|
||||
Amount0 *big.Int
|
||||
Amount1 *big.Int
|
||||
SqrtPriceX96 *uint256.Int
|
||||
Liquidity *uint256.Int
|
||||
Tick int
|
||||
Timestamp uint64
|
||||
TransactionHash common.Hash
|
||||
BlockNumber uint64
|
||||
}
|
||||
|
||||
// EventParser parses DEX events from Ethereum transactions
|
||||
type EventParser struct {
|
||||
// Known DEX contract addresses
|
||||
UniswapV2Factory common.Address
|
||||
UniswapV3Factory common.Address
|
||||
SushiSwapFactory common.Address
|
||||
|
||||
// Router addresses
|
||||
UniswapV2Router01 common.Address
|
||||
UniswapV2Router02 common.Address
|
||||
UniswapV3Router common.Address
|
||||
SushiSwapRouter common.Address
|
||||
|
||||
// Known pool addresses (for quick lookup)
|
||||
knownPools map[common.Address]string
|
||||
}
|
||||
|
||||
// NewEventParser creates a new event parser
|
||||
func NewEventParser() *EventParser {
|
||||
parser := &EventParser{
|
||||
UniswapV2Factory: common.HexToAddress("0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f"),
|
||||
UniswapV3Factory: common.HexToAddress("0x1F98431c8aD98523631AE4a59f267346ea31F984"),
|
||||
SushiSwapFactory: common.HexToAddress("0xC0AEe478e3658e2610c5F7A4A2E1777cE9e4f2Ac"),
|
||||
UniswapV2Router01: common.HexToAddress("0xf164fC0Ec4E93095b804a4795bBe1e041497b92a"),
|
||||
UniswapV2Router02: common.HexToAddress("0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D"),
|
||||
UniswapV3Router: common.HexToAddress("0xE592427A0AEce92De3Edee1F18E0157C05861564"),
|
||||
SushiSwapRouter: common.HexToAddress("0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F"),
|
||||
knownPools: make(map[common.Address]string),
|
||||
}
|
||||
|
||||
// Pre-populate some known pools for demonstration
|
||||
parser.knownPools[common.HexToAddress("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640")] = "UniswapV3"
|
||||
parser.knownPools[common.HexToAddress("0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc")] = "UniswapV2"
|
||||
|
||||
return parser
|
||||
}
|
||||
|
||||
// ParseTransaction parses a transaction for DEX events
|
||||
func (ep *EventParser) ParseTransaction(tx *types.Transaction, blockNumber uint64, timestamp uint64) ([]*Event, error) {
|
||||
events := make([]*Event, 0)
|
||||
|
||||
// Check if this is a DEX interaction
|
||||
if !ep.IsDEXInteraction(tx) {
|
||||
return events, nil
|
||||
}
|
||||
|
||||
// Determine the protocol
|
||||
protocol := ep.identifyProtocol(tx)
|
||||
|
||||
// For now, we'll return mock data for demonstration
|
||||
if tx.To() != nil {
|
||||
event := &Event{
|
||||
Type: Swap,
|
||||
Protocol: protocol,
|
||||
PoolAddress: *tx.To(),
|
||||
Token0: common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"), // USDC
|
||||
Token1: common.HexToAddress("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"), // WETH
|
||||
Amount0: big.NewInt(1000000000), // 1000 USDC
|
||||
Amount1: big.NewInt(500000000000000000), // 0.5 WETH
|
||||
SqrtPriceX96: uint256.NewInt(2505414483750470000),
|
||||
Liquidity: uint256.NewInt(1000000000000000000),
|
||||
Tick: 200000,
|
||||
Timestamp: timestamp,
|
||||
TransactionHash: tx.Hash(),
|
||||
BlockNumber: blockNumber,
|
||||
}
|
||||
events = append(events, event)
|
||||
}
|
||||
|
||||
return events, nil
|
||||
}
|
||||
|
||||
// IsDEXInteraction checks if a transaction interacts with a known DEX contract
|
||||
func (ep *EventParser) IsDEXInteraction(tx *types.Transaction) bool {
|
||||
if tx.To() == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
to := *tx.To()
|
||||
|
||||
// Check factory contracts
|
||||
if to == ep.UniswapV2Factory ||
|
||||
to == ep.UniswapV3Factory ||
|
||||
to == ep.SushiSwapFactory {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check router contracts
|
||||
if to == ep.UniswapV2Router01 ||
|
||||
to == ep.UniswapV2Router02 ||
|
||||
to == ep.UniswapV3Router ||
|
||||
to == ep.SushiSwapRouter {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check known pools
|
||||
if _, exists := ep.knownPools[to]; exists {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// identifyProtocol identifies which DEX protocol a transaction is interacting with
|
||||
func (ep *EventParser) identifyProtocol(tx *types.Transaction) string {
|
||||
if tx.To() == nil {
|
||||
return "Unknown"
|
||||
}
|
||||
|
||||
to := *tx.To()
|
||||
|
||||
// Check factory contracts
|
||||
if to == ep.UniswapV2Factory {
|
||||
return "UniswapV2"
|
||||
}
|
||||
if to == ep.UniswapV3Factory {
|
||||
return "UniswapV3"
|
||||
}
|
||||
if to == ep.SushiSwapFactory {
|
||||
return "SushiSwap"
|
||||
}
|
||||
|
||||
// Check router contracts
|
||||
if to == ep.UniswapV2Router01 || to == ep.UniswapV2Router02 {
|
||||
return "UniswapV2"
|
||||
}
|
||||
if to == ep.UniswapV3Router {
|
||||
return "UniswapV3"
|
||||
}
|
||||
if to == ep.SushiSwapRouter {
|
||||
return "SushiSwap"
|
||||
}
|
||||
|
||||
// Check known pools
|
||||
if protocol, exists := ep.knownPools[to]; exists {
|
||||
return protocol
|
||||
}
|
||||
|
||||
// Try to identify from function signature in transaction data
|
||||
if len(tx.Data()) >= 4 {
|
||||
sig := common.Bytes2Hex(tx.Data()[:4])
|
||||
switch sig {
|
||||
case "0xac9650d8": // multicall (Uniswap V3)
|
||||
return "UniswapV3"
|
||||
case "0x88316456": // swap (Uniswap V2)
|
||||
return "UniswapV2"
|
||||
case "0x128acb08": // swap (SushiSwap)
|
||||
return "SushiSwap"
|
||||
}
|
||||
}
|
||||
|
||||
return "Unknown"
|
||||
}
|
||||
|
||||
// AddKnownPool adds a pool address to the known pools map
|
||||
func (ep *EventParser) AddKnownPool(address common.Address, protocol string) {
|
||||
ep.knownPools[address] = protocol
|
||||
}
|
||||
|
||||
// GetKnownPools returns all known pools
|
||||
func (ep *EventParser) GetKnownPools() map[common.Address]string {
|
||||
return ep.knownPools
|
||||
}
|
||||
162
pkg/events/parser_test.go
Normal file
162
pkg/events/parser_test.go
Normal file
@@ -0,0 +1,162 @@
|
||||
package events
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestEventTypeString(t *testing.T) {
|
||||
assert.Equal(t, "Unknown", Unknown.String())
|
||||
assert.Equal(t, "Swap", Swap.String())
|
||||
assert.Equal(t, "AddLiquidity", AddLiquidity.String())
|
||||
assert.Equal(t, "RemoveLiquidity", RemoveLiquidity.String())
|
||||
assert.Equal(t, "NewPool", NewPool.String())
|
||||
assert.Equal(t, "Unknown", EventType(999).String()) // Test unknown value
|
||||
}
|
||||
|
||||
func TestNewEventParser(t *testing.T) {
|
||||
parser := NewEventParser()
|
||||
assert.NotNil(t, parser)
|
||||
assert.NotNil(t, parser.knownPools)
|
||||
assert.NotEmpty(t, parser.knownPools)
|
||||
}
|
||||
|
||||
func TestIsDEXInteraction(t *testing.T) {
|
||||
parser := NewEventParser()
|
||||
|
||||
// Test with Uniswap V2 factory address
|
||||
tx1 := types.NewTransaction(0, parser.UniswapV2Factory, big.NewInt(0), 0, big.NewInt(0), nil)
|
||||
assert.True(t, parser.IsDEXInteraction(tx1))
|
||||
|
||||
// Test with Uniswap V3 factory address
|
||||
tx2 := types.NewTransaction(0, parser.UniswapV3Factory, big.NewInt(0), 0, big.NewInt(0), nil)
|
||||
assert.True(t, parser.IsDEXInteraction(tx2))
|
||||
|
||||
// Test with SushiSwap factory address
|
||||
tx3 := types.NewTransaction(0, parser.SushiSwapFactory, big.NewInt(0), 0, big.NewInt(0), nil)
|
||||
assert.True(t, parser.IsDEXInteraction(tx3))
|
||||
|
||||
// Test with Uniswap V2 router address
|
||||
tx4 := types.NewTransaction(0, parser.UniswapV2Router02, big.NewInt(0), 0, big.NewInt(0), nil)
|
||||
assert.True(t, parser.IsDEXInteraction(tx4))
|
||||
|
||||
// Test with a known pool address
|
||||
poolAddr := common.HexToAddress("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640")
|
||||
parser.AddKnownPool(poolAddr, "UniswapV3")
|
||||
tx5 := types.NewTransaction(0, poolAddr, big.NewInt(0), 0, big.NewInt(0), nil)
|
||||
assert.True(t, parser.IsDEXInteraction(tx5))
|
||||
|
||||
// Test with a random address (should be false)
|
||||
randomAddr := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
||||
tx6 := types.NewTransaction(0, randomAddr, big.NewInt(0), 0, big.NewInt(0), nil)
|
||||
assert.False(t, parser.IsDEXInteraction(tx6))
|
||||
|
||||
// Test with contract creation transaction (nil To address)
|
||||
tx7 := types.NewContractCreation(0, big.NewInt(0), 0, big.NewInt(0), nil)
|
||||
assert.False(t, parser.IsDEXInteraction(tx7))
|
||||
}
|
||||
|
||||
func TestIdentifyProtocol(t *testing.T) {
|
||||
parser := NewEventParser()
|
||||
|
||||
// Test with Uniswap V2 factory address
|
||||
tx1 := types.NewTransaction(0, parser.UniswapV2Factory, big.NewInt(0), 0, big.NewInt(0), nil)
|
||||
assert.Equal(t, "UniswapV2", parser.identifyProtocol(tx1))
|
||||
|
||||
// Test with Uniswap V3 factory address
|
||||
tx2 := types.NewTransaction(0, parser.UniswapV3Factory, big.NewInt(0), 0, big.NewInt(0), nil)
|
||||
assert.Equal(t, "UniswapV3", parser.identifyProtocol(tx2))
|
||||
|
||||
// Test with SushiSwap factory address
|
||||
tx3 := types.NewTransaction(0, parser.SushiSwapFactory, big.NewInt(0), 0, big.NewInt(0), nil)
|
||||
assert.Equal(t, "SushiSwap", parser.identifyProtocol(tx3))
|
||||
|
||||
// Test with Uniswap V2 router address
|
||||
tx4 := types.NewTransaction(0, parser.UniswapV2Router02, big.NewInt(0), 0, big.NewInt(0), nil)
|
||||
assert.Equal(t, "UniswapV2", parser.identifyProtocol(tx4))
|
||||
|
||||
// Test with a known pool address
|
||||
poolAddr := common.HexToAddress("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640")
|
||||
parser.AddKnownPool(poolAddr, "UniswapV3")
|
||||
tx5 := types.NewTransaction(0, poolAddr, big.NewInt(0), 0, big.NewInt(0), nil)
|
||||
assert.Equal(t, "UniswapV3", parser.identifyProtocol(tx5))
|
||||
|
||||
// Test with a random address (should be Unknown)
|
||||
randomAddr := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
||||
tx6 := types.NewTransaction(0, randomAddr, big.NewInt(0), 0, big.NewInt(0), nil)
|
||||
assert.Equal(t, "Unknown", parser.identifyProtocol(tx6))
|
||||
|
||||
// Test with contract creation transaction (nil To address)
|
||||
tx7 := types.NewContractCreation(0, big.NewInt(0), 0, big.NewInt(0), nil)
|
||||
assert.Equal(t, "Unknown", parser.identifyProtocol(tx7))
|
||||
}
|
||||
|
||||
func TestAddKnownPoolAndGetKnownPools(t *testing.T) {
|
||||
parser := NewEventParser()
|
||||
initialCount := len(parser.GetKnownPools())
|
||||
|
||||
// Add a new pool
|
||||
addr := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
||||
parser.AddKnownPool(addr, "TestProtocol")
|
||||
|
||||
// Check that the pool was added
|
||||
pools := parser.GetKnownPools()
|
||||
assert.Equal(t, initialCount+1, len(pools))
|
||||
assert.Equal(t, "TestProtocol", pools[addr])
|
||||
|
||||
// Add another pool
|
||||
addr2 := common.HexToAddress("0xabcdefabcdefabcdefabcdefabcdefabcdefabcd")
|
||||
parser.AddKnownPool(addr2, "AnotherProtocol")
|
||||
|
||||
// Check that both pools are in the map
|
||||
pools = parser.GetKnownPools()
|
||||
assert.Equal(t, initialCount+2, len(pools))
|
||||
assert.Equal(t, "TestProtocol", pools[addr])
|
||||
assert.Equal(t, "AnotherProtocol", pools[addr2])
|
||||
}
|
||||
|
||||
func TestParseTransaction(t *testing.T) {
|
||||
parser := NewEventParser()
|
||||
|
||||
// Create a transaction that interacts with a DEX
|
||||
tx := types.NewTransaction(0, parser.UniswapV3Factory, big.NewInt(0), 0, big.NewInt(0), nil)
|
||||
blockNumber := uint64(12345)
|
||||
timestamp := uint64(1620000000)
|
||||
|
||||
// Parse the transaction
|
||||
events, err := parser.ParseTransaction(tx, blockNumber, timestamp)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, events, 1)
|
||||
|
||||
// Check the parsed event
|
||||
event := events[0]
|
||||
assert.Equal(t, Swap, event.Type)
|
||||
assert.Equal(t, "UniswapV3", event.Protocol)
|
||||
assert.Equal(t, parser.UniswapV3Factory, event.PoolAddress)
|
||||
assert.Equal(t, blockNumber, event.BlockNumber)
|
||||
assert.Equal(t, timestamp, event.Timestamp)
|
||||
assert.Equal(t, tx.Hash(), event.TransactionHash)
|
||||
assert.NotNil(t, event.Amount0)
|
||||
assert.NotNil(t, event.Amount1)
|
||||
assert.NotNil(t, event.SqrtPriceX96)
|
||||
assert.NotNil(t, event.Liquidity)
|
||||
}
|
||||
|
||||
func TestParseTransactionNonDEX(t *testing.T) {
|
||||
parser := NewEventParser()
|
||||
|
||||
// Create a transaction that doesn't interact with a DEX
|
||||
randomAddr := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
||||
tx := types.NewTransaction(0, randomAddr, big.NewInt(0), 0, big.NewInt(0), nil)
|
||||
blockNumber := uint64(12345)
|
||||
timestamp := uint64(1620000000)
|
||||
|
||||
// Parse the transaction
|
||||
events, err := parser.ParseTransaction(tx, blockNumber, timestamp)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, events, 0)
|
||||
}
|
||||
Reference in New Issue
Block a user