715 lines
25 KiB
Go
715 lines
25 KiB
Go
package arbitrum
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"fmt"
|
|
"math/big"
|
|
"strings"
|
|
|
|
"github.com/ethereum/go-ethereum/accounts/abi"
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/crypto"
|
|
)
|
|
|
|
// ABIDecoder handles proper ABI decoding for DEX transactions
|
|
type ABIDecoder struct {
|
|
// Function signatures for major DEX protocols
|
|
uniswapV2ABI abi.ABI
|
|
uniswapV3ABI abi.ABI
|
|
sushiswapABI abi.ABI
|
|
camelotABI abi.ABI
|
|
balancerABI abi.ABI
|
|
curveABI abi.ABI
|
|
oneInchABI abi.ABI
|
|
|
|
// Function signature mappings
|
|
functionSignatures map[string]string
|
|
}
|
|
|
|
// SwapParams represents decoded swap parameters
|
|
type SwapParams struct {
|
|
AmountIn *big.Int
|
|
AmountOut *big.Int
|
|
MinAmountOut *big.Int
|
|
TokenIn common.Address
|
|
TokenOut common.Address
|
|
Recipient common.Address
|
|
Pool common.Address
|
|
Path []common.Address
|
|
Deadline *big.Int
|
|
Fee *big.Int
|
|
}
|
|
|
|
// NewABIDecoder creates a new ABI decoder with protocol definitions
|
|
func NewABIDecoder() (*ABIDecoder, error) {
|
|
decoder := &ABIDecoder{
|
|
functionSignatures: make(map[string]string),
|
|
}
|
|
|
|
// Initialize known function signatures
|
|
if err := decoder.initializeFunctionSignatures(); err != nil {
|
|
return nil, fmt.Errorf("failed to initialize function signatures: %w", err)
|
|
}
|
|
|
|
return decoder, nil
|
|
}
|
|
|
|
// initializeFunctionSignatures sets up known DEX function signatures
|
|
func (d *ABIDecoder) initializeFunctionSignatures() error {
|
|
// Uniswap V2 signatures
|
|
d.functionSignatures["0x38ed1739"] = "swapExactTokensForTokens(uint256,uint256,address[],address,uint256)"
|
|
d.functionSignatures["0x8803dbee"] = "swapTokensForExactTokens(uint256,uint256,address[],address,uint256)"
|
|
d.functionSignatures["0x7ff36ab5"] = "swapExactETHForTokens(uint256,address[],address,uint256)"
|
|
d.functionSignatures["0x4a25d94a"] = "swapTokensForExactETH(uint256,uint256,address[],address,uint256)"
|
|
d.functionSignatures["0x18cbafe5"] = "swapExactTokensForETH(uint256,uint256,address[],address,uint256)"
|
|
d.functionSignatures["0xfb3bdb41"] = "swapETHForExactTokens(uint256,address[],address,uint256)"
|
|
|
|
// Uniswap V3 signatures
|
|
d.functionSignatures["0x414bf389"] = "exactInputSingle((address,address,uint24,address,uint256,uint256,uint256,uint160))"
|
|
d.functionSignatures["0xc04b8d59"] = "exactInput((bytes,address,uint256,uint256,uint256))"
|
|
d.functionSignatures["0xdb3e2198"] = "exactOutputSingle((address,address,uint24,address,uint256,uint256,uint256,uint160))"
|
|
d.functionSignatures["0xf28c0498"] = "exactOutput((bytes,address,uint256,uint256,uint256))"
|
|
|
|
// SushiSwap (same as Uniswap V2)
|
|
// Camelot V3 (same as Uniswap V3)
|
|
|
|
// 1inch aggregator signatures
|
|
d.functionSignatures["0x7c025200"] = "swap(address,(address,address,address,address,uint256,uint256,uint256),bytes,bytes)"
|
|
d.functionSignatures["0xe449022e"] = "uniswapV3Swap(uint256,uint256,uint256[])"
|
|
|
|
// Balancer V2 signatures
|
|
d.functionSignatures["0x52bbbe29"] = "swap((bytes32,uint8,address,address,uint256,bytes),(address,bool,address,bool),uint256,uint256)"
|
|
|
|
// Radiant (lending protocol with swap functionality)
|
|
d.functionSignatures["0xa9059cbb"] = "transfer(address,uint256)" // ERC-20 transfer
|
|
d.functionSignatures["0x23b872dd"] = "transferFrom(address,address,uint256)" // ERC-20 transferFrom
|
|
d.functionSignatures["0xe8e33700"] = "deposit(address,uint256,address,uint16)" // Aave-style deposit
|
|
d.functionSignatures["0x69328dec"] = "withdraw(address,uint256,address)" // Aave-style withdraw
|
|
d.functionSignatures["0xa415bcad"] = "borrow(address,uint256,uint256,uint16,address)" // Aave-style borrow
|
|
d.functionSignatures["0x563dd613"] = "repay(address,uint256,uint256,address)" // Aave-style repay
|
|
|
|
// Curve Finance signatures
|
|
d.functionSignatures["0x3df02124"] = "exchange(int128,int128,uint256,uint256)" // Curve exchange
|
|
d.functionSignatures["0xa6417ed6"] = "exchange_underlying(int128,int128,uint256,uint256)" // Curve exchange underlying
|
|
|
|
// Trader Joe (Arbitrum DEX)
|
|
d.functionSignatures["0x18cbafe5"] = "swapExactTokensForTokens(uint256,uint256,address[],address,uint256)" // Same as Uniswap V2
|
|
d.functionSignatures["0x791ac947"] = "swapExactTokensForTokensSupportingFeeOnTransferTokens(uint256,uint256,address[],address,uint256)"
|
|
|
|
// GMX (perpetual trading)
|
|
d.functionSignatures["0x0809dd62"] = "swap(address[],uint256,uint256,address)" // GMX swap
|
|
d.functionSignatures["0x29a5408c"] = "increasePosition(address[],address,uint256,uint256,uint256,bool,uint256)" // GMX position
|
|
|
|
// Arbitrum-specific multicall patterns
|
|
d.functionSignatures["0xac9650d8"] = "multicall(bytes[])" // Common multicall
|
|
d.functionSignatures["0x1f0464d1"] = "multicall(bytes[])" // Alternative multicall
|
|
|
|
return nil
|
|
}
|
|
|
|
// DecodeSwapTransaction decodes a swap transaction based on protocol and function signature
|
|
func (d *ABIDecoder) DecodeSwapTransaction(protocol, txData string) (*SwapParams, error) {
|
|
if len(txData) < 10 { // 0x + 4 bytes function selector
|
|
return nil, fmt.Errorf("transaction data too short")
|
|
}
|
|
|
|
// Remove 0x prefix if present
|
|
if strings.HasPrefix(txData, "0x") {
|
|
txData = txData[2:]
|
|
}
|
|
|
|
data, err := hex.DecodeString(txData)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to decode hex data: %w", err)
|
|
}
|
|
|
|
if len(data) < 4 {
|
|
return nil, fmt.Errorf("insufficient data for function selector")
|
|
}
|
|
|
|
// Extract function selector
|
|
selector := "0x" + hex.EncodeToString(data[:4])
|
|
|
|
// Get function signature
|
|
functionSig, exists := d.functionSignatures[selector]
|
|
if !exists {
|
|
// Try to decode as generic swap if signature unknown
|
|
return d.decodeGenericSwap(data, protocol)
|
|
}
|
|
|
|
// Handle multicall transactions first
|
|
if strings.Contains(functionSig, "multicall") {
|
|
return d.decodeMulticall(data, protocol)
|
|
}
|
|
|
|
// Decode based on protocol and function
|
|
switch protocol {
|
|
case "uniswap_v2", "sushiswap", "camelot_v2", "trader_joe":
|
|
return d.decodeUniswapV2Swap(data, functionSig)
|
|
case "uniswap_v3", "camelot_v3", "algebra":
|
|
return d.decodeUniswapV3Swap(data, functionSig)
|
|
case "1inch":
|
|
return d.decode1inchSwap(data, functionSig)
|
|
case "balancer_v2":
|
|
return d.decodeBalancerSwap(data, functionSig)
|
|
case "curve":
|
|
return d.decodeCurveSwap(data, functionSig)
|
|
case "radiant", "aave", "compound":
|
|
return d.decodeLendingSwap(data, functionSig)
|
|
case "gmx":
|
|
return d.decodeGMXSwap(data, functionSig)
|
|
default:
|
|
return d.decodeGenericSwap(data, protocol)
|
|
}
|
|
}
|
|
|
|
// decodeUniswapV2Swap decodes Uniswap V2 style swap transactions
|
|
func (d *ABIDecoder) decodeUniswapV2Swap(data []byte, functionSig string) (*SwapParams, error) {
|
|
if len(data) < 4 {
|
|
return nil, fmt.Errorf("insufficient data")
|
|
}
|
|
|
|
params := &SwapParams{}
|
|
|
|
// Skip function selector (first 4 bytes)
|
|
data = data[4:]
|
|
|
|
// Parse based on function type
|
|
if strings.Contains(functionSig, "swapExactTokensForTokens") {
|
|
if len(data) < 160 { // 5 * 32 bytes minimum
|
|
return nil, fmt.Errorf("insufficient data for swapExactTokensForTokens")
|
|
}
|
|
|
|
// amountIn (32 bytes)
|
|
params.AmountIn = new(big.Int).SetBytes(data[0:32])
|
|
|
|
// amountOutMin (32 bytes)
|
|
params.MinAmountOut = new(big.Int).SetBytes(data[32:64])
|
|
|
|
// path offset (32 bytes) - skip this, get actual path
|
|
pathOffset := new(big.Int).SetBytes(data[64:96]).Uint64()
|
|
|
|
// recipient (32 bytes, but address is last 20 bytes)
|
|
params.Recipient = common.BytesToAddress(data[96:128])
|
|
|
|
// deadline (32 bytes)
|
|
params.Deadline = new(big.Int).SetBytes(data[128:160])
|
|
|
|
// Parse path array
|
|
if pathOffset < uint64(len(data)) && pathOffset+32 < uint64(len(data)) {
|
|
pathLength := new(big.Int).SetBytes(data[pathOffset : pathOffset+32]).Uint64()
|
|
if pathLength >= 2 && pathOffset+32+pathLength*32 <= uint64(len(data)) {
|
|
params.Path = make([]common.Address, pathLength)
|
|
for i := uint64(0); i < pathLength; i++ {
|
|
start := pathOffset + 32 + i*32
|
|
params.Path[i] = common.BytesToAddress(data[start : start+32])
|
|
}
|
|
|
|
if len(params.Path) >= 2 {
|
|
params.TokenIn = params.Path[0]
|
|
params.TokenOut = params.Path[len(params.Path)-1]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Handle other Uniswap V2 functions similarly...
|
|
|
|
return params, nil
|
|
}
|
|
|
|
// decodeUniswapV3Swap decodes Uniswap V3 style swap transactions
|
|
func (d *ABIDecoder) decodeUniswapV3Swap(data []byte, functionSig string) (*SwapParams, error) {
|
|
if len(data) < 4 {
|
|
return nil, fmt.Errorf("insufficient data")
|
|
}
|
|
|
|
params := &SwapParams{}
|
|
data = data[4:] // Skip function selector
|
|
|
|
if strings.Contains(functionSig, "exactInputSingle") {
|
|
// ExactInputSingle struct: (tokenIn, tokenOut, fee, recipient, deadline, amountIn, amountOutMinimum, sqrtPriceLimitX96)
|
|
if len(data) < 256 { // 8 * 32 bytes
|
|
return nil, fmt.Errorf("insufficient data for exactInputSingle")
|
|
}
|
|
|
|
params.TokenIn = common.BytesToAddress(data[0:32])
|
|
params.TokenOut = common.BytesToAddress(data[32:64])
|
|
params.Fee = new(big.Int).SetBytes(data[64:96])
|
|
params.Recipient = common.BytesToAddress(data[96:128])
|
|
params.Deadline = new(big.Int).SetBytes(data[128:160])
|
|
params.AmountIn = new(big.Int).SetBytes(data[160:192])
|
|
params.MinAmountOut = new(big.Int).SetBytes(data[192:224])
|
|
// sqrtPriceLimitX96 at data[224:256] - skip for now
|
|
}
|
|
|
|
// Handle other Uniswap V3 functions...
|
|
|
|
return params, nil
|
|
}
|
|
|
|
// decode1inchSwap decodes 1inch aggregator swap transactions
|
|
func (d *ABIDecoder) decode1inchSwap(data []byte, functionSig string) (*SwapParams, error) {
|
|
params := &SwapParams{}
|
|
data = data[4:] // Skip function selector
|
|
|
|
// 1inch has complex swap data, extract what we can
|
|
if len(data) >= 64 {
|
|
// First parameter is usually the caller/executor
|
|
// Second parameter contains swap description with tokens and amounts
|
|
if len(data) >= 160 {
|
|
// Try to extract token addresses from swap description
|
|
// This is a simplified extraction - 1inch has complex encoding
|
|
params.TokenIn = common.BytesToAddress(data[32:64])
|
|
params.TokenOut = common.BytesToAddress(data[64:96])
|
|
params.AmountIn = new(big.Int).SetBytes(data[96:128])
|
|
params.MinAmountOut = new(big.Int).SetBytes(data[128:160])
|
|
}
|
|
}
|
|
|
|
return params, nil
|
|
}
|
|
|
|
// decodeBalancerSwap decodes Balancer V2 swap transactions
|
|
func (d *ABIDecoder) decodeBalancerSwap(data []byte, functionSig string) (*SwapParams, error) {
|
|
params := &SwapParams{}
|
|
data = data[4:] // Skip function selector
|
|
|
|
// Balancer has complex swap structure with pool ID, tokens, amounts
|
|
if len(data) >= 128 {
|
|
// Extract basic information - Balancer encoding is complex
|
|
// This is a simplified version
|
|
params.AmountIn = new(big.Int).SetBytes(data[64:96])
|
|
params.MinAmountOut = new(big.Int).SetBytes(data[96:128])
|
|
}
|
|
|
|
return params, nil
|
|
}
|
|
|
|
// decodeGenericSwap provides fallback decoding for unknown protocols
|
|
func (d *ABIDecoder) decodeGenericSwap(data []byte, protocol string) (*SwapParams, error) {
|
|
params := &SwapParams{}
|
|
|
|
if len(data) < 4 {
|
|
return params, nil
|
|
}
|
|
|
|
data = data[4:] // Skip function selector
|
|
|
|
// Try to extract common ERC-20 swap patterns
|
|
if len(data) >= 128 { // Minimum for token addresses and amounts
|
|
// Try different common patterns for token addresses
|
|
|
|
// Pattern 1: Direct address parameters at start
|
|
if len(data) >= 64 {
|
|
tokenIn := common.BytesToAddress(data[0:32])
|
|
tokenOut := common.BytesToAddress(data[32:64])
|
|
|
|
// Check if these look like valid token addresses (not zero)
|
|
if d.isValidTokenAddress(tokenIn) && d.isValidTokenAddress(tokenOut) {
|
|
params.TokenIn = tokenIn
|
|
params.TokenOut = tokenOut
|
|
}
|
|
}
|
|
|
|
// Pattern 2: Try offset-based token extraction (common in complex calls)
|
|
if params.TokenIn == (common.Address{}) && len(data) >= 96 {
|
|
// Sometimes tokens are at different offsets
|
|
for offset := 0; offset < 128 && offset+32 <= len(data); offset += 32 {
|
|
addr := common.BytesToAddress(data[offset : offset+32])
|
|
if d.isValidTokenAddress(addr) {
|
|
if params.TokenIn == (common.Address{}) {
|
|
params.TokenIn = addr
|
|
} else if params.TokenOut == (common.Address{}) && addr != params.TokenIn {
|
|
params.TokenOut = addr
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Pattern 3: Look for array patterns (common in path-based swaps)
|
|
if params.TokenIn == (common.Address{}) && len(data) >= 160 {
|
|
// Look for dynamic arrays which often contain token paths
|
|
for offset := 32; offset+64 <= len(data); offset += 32 {
|
|
// Check if this looks like an array offset
|
|
possibleOffset := new(big.Int).SetBytes(data[offset : offset+32]).Uint64()
|
|
if possibleOffset > 32 && possibleOffset < uint64(len(data)-64) {
|
|
// Check if there's an array length at this offset
|
|
arrayLen := new(big.Int).SetBytes(data[possibleOffset : possibleOffset+32]).Uint64()
|
|
if arrayLen >= 2 && arrayLen <= 10 && possibleOffset+32+arrayLen*32 <= uint64(len(data)) {
|
|
// Extract first and last elements as token addresses
|
|
firstToken := common.BytesToAddress(data[possibleOffset+32 : possibleOffset+64])
|
|
lastToken := common.BytesToAddress(data[possibleOffset+32+(arrayLen-1)*32 : possibleOffset+32+arrayLen*32])
|
|
|
|
if d.isValidTokenAddress(firstToken) && d.isValidTokenAddress(lastToken) {
|
|
params.TokenIn = firstToken
|
|
params.TokenOut = lastToken
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Extract amounts from common positions
|
|
if len(data) >= 64 {
|
|
// Try first amount
|
|
params.AmountIn = new(big.Int).SetBytes(data[0:32])
|
|
if params.AmountIn.Cmp(big.NewInt(0)) == 0 && len(data) >= 96 {
|
|
params.AmountIn = new(big.Int).SetBytes(data[32:64])
|
|
}
|
|
if params.AmountIn.Cmp(big.NewInt(0)) == 0 && len(data) >= 128 {
|
|
params.AmountIn = new(big.Int).SetBytes(data[64:96])
|
|
}
|
|
|
|
// Try to find amount out (usually after amount in)
|
|
if len(data) >= 96 && params.AmountIn.Cmp(big.NewInt(0)) > 0 {
|
|
params.MinAmountOut = new(big.Int).SetBytes(data[64:96])
|
|
if params.MinAmountOut.Cmp(big.NewInt(0)) == 0 && len(data) >= 128 {
|
|
params.MinAmountOut = new(big.Int).SetBytes(data[96:128])
|
|
}
|
|
}
|
|
}
|
|
|
|
// Try to find recipient address (often near the end)
|
|
if len(data) >= 160 {
|
|
// Check last few 32-byte slots for address patterns
|
|
for i := len(data) - 32; i >= len(data)-96 && i >= 0; i -= 32 {
|
|
addr := common.BytesToAddress(data[i : i+32])
|
|
if d.isValidTokenAddress(addr) {
|
|
params.Recipient = addr
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return params, nil
|
|
}
|
|
|
|
// isValidTokenAddress checks if an address looks like a valid token address
|
|
func (d *ABIDecoder) isValidTokenAddress(addr common.Address) bool {
|
|
// Check if not zero address
|
|
if addr == (common.Address{}) {
|
|
return false
|
|
}
|
|
|
|
// Check if not a common non-token address
|
|
// Exclude known router/factory addresses that aren't tokens
|
|
knownContracts := []common.Address{
|
|
common.HexToAddress("0x4752ba5dbc23f44d87826276bf6fd6b1c372ad24"), // Uniswap V2 Router
|
|
common.HexToAddress("0xE592427A0AEce92De3Edee1F18E0157C05861564"), // Uniswap V3 Router
|
|
common.HexToAddress("0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45"), // Uniswap V3 Router02
|
|
common.HexToAddress("0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506"), // SushiSwap Router
|
|
common.HexToAddress("0x1F98431c8aD98523631AE4a59f267346ea31F984"), // Uniswap V3 Factory
|
|
common.HexToAddress("0xc35DADB65012eC5796536bD9864eD8773aBc74C4"), // SushiSwap Factory
|
|
}
|
|
|
|
for _, contract := range knownContracts {
|
|
if addr == contract {
|
|
return false
|
|
}
|
|
}
|
|
|
|
// Additional heuristics: valid token addresses typically have:
|
|
// - Non-zero value
|
|
// - Not ending in many zeros (contracts often do)
|
|
// - Not matching common EOA patterns
|
|
|
|
addrBytes := addr.Bytes()
|
|
zeroCount := 0
|
|
for i := len(addrBytes) - 4; i < len(addrBytes); i++ {
|
|
if addrBytes[i] == 0 {
|
|
zeroCount++
|
|
}
|
|
}
|
|
|
|
// If last 4 bytes are all zeros, likely not a token
|
|
if zeroCount >= 3 {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// CalculatePoolAddress calculates pool address using CREATE2 for known factories
|
|
func (d *ABIDecoder) CalculatePoolAddress(tokenA, tokenB common.Address, fee *big.Int, protocol string) (common.Address, error) {
|
|
// Arbitrum factory addresses
|
|
factories := map[string]common.Address{
|
|
"uniswap_v3": common.HexToAddress("0x1F98431c8aD98523631AE4a59f267346ea31F984"),
|
|
"camelot_v3": common.HexToAddress("0x1a3c9B1d2F0529D97f2afC5136Cc23e58f1FD35B"),
|
|
"sushiswap": common.HexToAddress("0xc35DADB65012eC5796536bD9864eD8773aBc74C4"),
|
|
"algebra": common.HexToAddress("0x411b0fAcC3489691f28ad58c47006AF5E3Ab3A28"),
|
|
}
|
|
|
|
factory, exists := factories[protocol]
|
|
if !exists {
|
|
return common.Address{}, fmt.Errorf("unknown protocol: %s", protocol)
|
|
}
|
|
|
|
// Ensure token addresses are not zero addresses
|
|
if tokenA == (common.Address{}) || tokenB == (common.Address{}) {
|
|
return common.Address{}, fmt.Errorf("token addresses cannot be zero")
|
|
}
|
|
|
|
// Ensure token order (tokenA < tokenB)
|
|
if tokenA.Big().Cmp(tokenB.Big()) > 0 {
|
|
tokenA, tokenB = tokenB, tokenA
|
|
}
|
|
|
|
switch protocol {
|
|
case "uniswap_v3", "camelot_v3":
|
|
return d.calculateUniswapV3PoolAddress(factory, tokenA, tokenB, fee)
|
|
case "sushiswap":
|
|
return d.calculateUniswapV2PoolAddress(factory, tokenA, tokenB)
|
|
default:
|
|
return d.calculateUniswapV2PoolAddress(factory, tokenA, tokenB)
|
|
}
|
|
}
|
|
|
|
// calculateUniswapV3PoolAddress calculates Uniswap V3 pool address
|
|
func (d *ABIDecoder) calculateUniswapV3PoolAddress(factory, tokenA, tokenB common.Address, fee *big.Int) (common.Address, error) {
|
|
// Check if fee is nil and handle appropriately
|
|
if fee == nil {
|
|
fee = big.NewInt(0) // Use 0 as default fee if nil
|
|
}
|
|
|
|
// Ensure token addresses are not zero addresses
|
|
if tokenA == (common.Address{}) || tokenB == (common.Address{}) {
|
|
return common.Address{}, fmt.Errorf("token addresses cannot be zero")
|
|
}
|
|
|
|
// Ensure fee is never nil when calling fee.Bytes()
|
|
feeBytes := common.LeftPadBytes(fee.Bytes(), 32)
|
|
|
|
// Uniswap V3 CREATE2 salt: keccak256(abi.encode(token0, token1, fee))
|
|
salt := crypto.Keccak256(
|
|
common.LeftPadBytes(tokenA.Bytes(), 32),
|
|
common.LeftPadBytes(tokenB.Bytes(), 32),
|
|
feeBytes,
|
|
)
|
|
|
|
// Uniswap V3 pool init code hash
|
|
initCodeHash := common.HexToHash("0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54")
|
|
|
|
// CREATE2 address calculation: keccak256(0xff ++ factory ++ salt ++ keccak256(initCode))[12:]
|
|
data := make([]byte, 1+20+32+32)
|
|
data[0] = 0xff
|
|
copy(data[1:21], factory.Bytes())
|
|
copy(data[21:53], salt)
|
|
copy(data[53:85], initCodeHash.Bytes())
|
|
|
|
hash := crypto.Keccak256(data)
|
|
return common.BytesToAddress(hash[12:]), nil
|
|
}
|
|
|
|
// calculateUniswapV2PoolAddress calculates Uniswap V2 style pool address
|
|
func (d *ABIDecoder) calculateUniswapV2PoolAddress(factory, tokenA, tokenB common.Address) (common.Address, error) {
|
|
// Uniswap V2 CREATE2 salt: keccak256(abi.encodePacked(token0, token1))
|
|
salt := crypto.Keccak256(append(tokenA.Bytes(), tokenB.Bytes()...))
|
|
|
|
// SushiSwap init code hash (example)
|
|
initCodeHash := common.HexToHash("0xe18a34eb0e04b04f7a0ac29a6e80748dca96319b42c54d679cb821dca90c6303")
|
|
|
|
// CREATE2 address calculation
|
|
data := make([]byte, 1+20+32+32)
|
|
data[0] = 0xff
|
|
copy(data[1:21], factory.Bytes())
|
|
copy(data[21:53], salt)
|
|
copy(data[53:85], initCodeHash.Bytes())
|
|
|
|
hash := crypto.Keccak256(data)
|
|
return common.BytesToAddress(hash[12:]), nil
|
|
}
|
|
|
|
// decodeCurveSwap decodes Curve Finance swap transactions
|
|
func (d *ABIDecoder) decodeCurveSwap(data []byte, functionSig string) (*SwapParams, error) {
|
|
params := &SwapParams{}
|
|
data = data[4:] // Skip function selector
|
|
|
|
if strings.Contains(functionSig, "exchange") {
|
|
// Curve exchange(int128,int128,uint256,uint256) or exchange_underlying
|
|
if len(data) >= 128 {
|
|
// Skip token indices (first 64 bytes) and get amounts
|
|
params.AmountIn = new(big.Int).SetBytes(data[64:96])
|
|
params.MinAmountOut = new(big.Int).SetBytes(data[96:128])
|
|
|
|
// For Curve, token addresses would need to be looked up from pool contract
|
|
// For now, use placeholder logic from generic decoder
|
|
return d.decodeGenericSwap(append([]byte{0, 0, 0, 0}, data...), "curve")
|
|
}
|
|
}
|
|
|
|
return params, nil
|
|
}
|
|
|
|
// decodeLendingSwap decodes lending protocol transactions (Radiant, Aave, etc.)
|
|
func (d *ABIDecoder) decodeLendingSwap(data []byte, functionSig string) (*SwapParams, error) {
|
|
params := &SwapParams{}
|
|
data = data[4:] // Skip function selector
|
|
|
|
if strings.Contains(functionSig, "deposit") || strings.Contains(functionSig, "withdraw") ||
|
|
strings.Contains(functionSig, "borrow") || strings.Contains(functionSig, "repay") {
|
|
// Standard lending operations: (asset, amount, onBehalfOf, referralCode)
|
|
if len(data) >= 96 {
|
|
params.TokenIn = common.BytesToAddress(data[0:32]) // asset
|
|
params.AmountIn = new(big.Int).SetBytes(data[32:64]) // amount
|
|
params.Recipient = common.BytesToAddress(data[64:96]) // onBehalfOf
|
|
|
|
// For lending, TokenOut would be the receipt token (aToken, etc.)
|
|
// This would need protocol-specific logic to determine
|
|
}
|
|
} else if strings.Contains(functionSig, "transfer") {
|
|
// ERC-20 transfer/transferFrom
|
|
if strings.Contains(functionSig, "transferFrom") && len(data) >= 96 {
|
|
// transferFrom(from, to, amount)
|
|
params.Recipient = common.BytesToAddress(data[32:64]) // to
|
|
params.AmountIn = new(big.Int).SetBytes(data[64:96]) // amount
|
|
} else if len(data) >= 64 {
|
|
// transfer(to, amount)
|
|
params.Recipient = common.BytesToAddress(data[0:32]) // to
|
|
params.AmountIn = new(big.Int).SetBytes(data[32:64]) // amount
|
|
}
|
|
}
|
|
|
|
return params, nil
|
|
}
|
|
|
|
// decodeGMXSwap decodes GMX protocol transactions
|
|
func (d *ABIDecoder) decodeGMXSwap(data []byte, functionSig string) (*SwapParams, error) {
|
|
params := &SwapParams{}
|
|
data = data[4:] // Skip function selector
|
|
|
|
if strings.Contains(functionSig, "swap") {
|
|
// GMX swap(address[],uint256,uint256,address)
|
|
if len(data) >= 128 {
|
|
// First parameter is path array offset, skip to amount
|
|
params.AmountIn = new(big.Int).SetBytes(data[32:64])
|
|
params.MinAmountOut = new(big.Int).SetBytes(data[64:96])
|
|
params.Recipient = common.BytesToAddress(data[96:128])
|
|
|
|
// Extract token path from the dynamic array
|
|
// This would need more complex parsing for the path array
|
|
}
|
|
}
|
|
|
|
// Fall back to generic decoding for complex GMX transactions
|
|
return d.decodeGenericSwap(append([]byte{0, 0, 0, 0}, data...), "gmx")
|
|
}
|
|
|
|
// decodeMulticall decodes multicall transactions by extracting individual calls
|
|
func (d *ABIDecoder) decodeMulticall(data []byte, protocol string) (*SwapParams, error) {
|
|
if len(data) < 4 {
|
|
return nil, fmt.Errorf("insufficient data for multicall")
|
|
}
|
|
|
|
// Skip function selector
|
|
data = data[4:]
|
|
|
|
if len(data) < 32 {
|
|
return nil, fmt.Errorf("insufficient data for multicall array offset")
|
|
}
|
|
|
|
// Get array offset
|
|
arrayOffset := new(big.Int).SetBytes(data[0:32]).Uint64()
|
|
|
|
if arrayOffset >= uint64(len(data)) || arrayOffset+32 >= uint64(len(data)) {
|
|
return nil, fmt.Errorf("invalid array offset in multicall")
|
|
}
|
|
|
|
// Get array length
|
|
arrayLength := new(big.Int).SetBytes(data[arrayOffset : arrayOffset+32]).Uint64()
|
|
|
|
if arrayLength == 0 {
|
|
return nil, fmt.Errorf("empty multicall array")
|
|
}
|
|
|
|
// Parse individual calls in the multicall
|
|
bestParams := &SwapParams{}
|
|
callsFound := 0
|
|
|
|
for i := uint64(0); i < arrayLength; i++ {
|
|
// Each call has an offset pointer
|
|
offsetPos := arrayOffset + 32 + i*32
|
|
if offsetPos+32 > uint64(len(data)) {
|
|
break
|
|
}
|
|
|
|
callOffset := arrayOffset + new(big.Int).SetBytes(data[offsetPos:offsetPos+32]).Uint64()
|
|
if callOffset+32 > uint64(len(data)) {
|
|
continue
|
|
}
|
|
|
|
// Get call data length
|
|
callDataLength := new(big.Int).SetBytes(data[callOffset : callOffset+32]).Uint64()
|
|
if callOffset+32+callDataLength > uint64(len(data)) {
|
|
continue
|
|
}
|
|
|
|
// Extract call data
|
|
callData := data[callOffset+32 : callOffset+32+callDataLength]
|
|
if len(callData) < 4 {
|
|
continue
|
|
}
|
|
|
|
// Try to decode this individual call
|
|
params, err := d.decodeIndividualCall(callData, protocol)
|
|
if err == nil && params != nil {
|
|
callsFound++
|
|
// Use the first successfully decoded call or the one with most complete data
|
|
if bestParams.TokenIn == (common.Address{}) ||
|
|
(params.TokenIn != (common.Address{}) && params.TokenOut != (common.Address{})) {
|
|
bestParams = params
|
|
}
|
|
}
|
|
}
|
|
|
|
if callsFound == 0 {
|
|
return nil, fmt.Errorf("no decodable calls found in multicall")
|
|
}
|
|
|
|
return bestParams, nil
|
|
}
|
|
|
|
// decodeIndividualCall decodes an individual call within a multicall
|
|
func (d *ABIDecoder) decodeIndividualCall(callData []byte, protocol string) (*SwapParams, error) {
|
|
if len(callData) < 4 {
|
|
return nil, fmt.Errorf("insufficient call data")
|
|
}
|
|
|
|
// Extract function selector
|
|
selector := "0x" + hex.EncodeToString(callData[:4])
|
|
|
|
// Check if this is a known swap function
|
|
functionSig, exists := d.functionSignatures[selector]
|
|
if !exists {
|
|
// Try generic decoding for unknown functions
|
|
return d.decodeGenericSwap(callData, protocol)
|
|
}
|
|
|
|
// Decode based on function signature
|
|
if strings.Contains(functionSig, "swapExact") || strings.Contains(functionSig, "exactInput") {
|
|
// This is a swap function, decode it appropriately
|
|
switch {
|
|
case strings.Contains(functionSig, "exactInputSingle"):
|
|
return d.decodeUniswapV3Swap(callData, functionSig)
|
|
case strings.Contains(functionSig, "exactInput"):
|
|
return d.decodeUniswapV3Swap(callData, functionSig)
|
|
case strings.Contains(functionSig, "swapExactTokensForTokens"):
|
|
return d.decodeUniswapV2Swap(callData, functionSig)
|
|
case strings.Contains(functionSig, "swapTokensForExactTokens"):
|
|
return d.decodeUniswapV2Swap(callData, functionSig)
|
|
case strings.Contains(functionSig, "swapExactETHForTokens"):
|
|
return d.decodeUniswapV2Swap(callData, functionSig)
|
|
case strings.Contains(functionSig, "swapExactTokensForETH"):
|
|
return d.decodeUniswapV2Swap(callData, functionSig)
|
|
default:
|
|
return d.decodeGenericSwap(callData, protocol)
|
|
}
|
|
}
|
|
|
|
// Not a swap function, try generic decoding
|
|
return d.decodeGenericSwap(callData, protocol)
|
|
}
|