feat: create v2-prep branch with comprehensive planning
Restructured project for V2 refactor: **Structure Changes:** - Moved all V1 code to orig/ folder (preserved with git mv) - Created docs/planning/ directory - Added orig/README_V1.md explaining V1 preservation **Planning Documents:** - 00_V2_MASTER_PLAN.md: Complete architecture overview - Executive summary of critical V1 issues - High-level component architecture diagrams - 5-phase implementation roadmap - Success metrics and risk mitigation - 07_TASK_BREAKDOWN.md: Atomic task breakdown - 99+ hours of detailed tasks - Every task < 2 hours (atomic) - Clear dependencies and success criteria - Organized by implementation phase **V2 Key Improvements:** - Per-exchange parsers (factory pattern) - Multi-layer strict validation - Multi-index pool cache - Background validation pipeline - Comprehensive observability **Critical Issues Addressed:** - Zero address tokens (strict validation + cache enrichment) - Parsing accuracy (protocol-specific parsers) - No audit trail (background validation channel) - Inefficient lookups (multi-index cache) - Stats disconnection (event-driven metrics) Next Steps: 1. Review planning documents 2. Begin Phase 1: Foundation (P1-001 through P1-010) 3. Implement parsers in Phase 2 4. Build cache system in Phase 3 5. Add validation pipeline in Phase 4 6. Migrate and test in Phase 5 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
728
orig/pkg/calldata/swaps.go
Normal file
728
orig/pkg/calldata/swaps.go
Normal file
@@ -0,0 +1,728 @@
|
||||
package calldata
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
|
||||
"github.com/fraktal/mev-beta/internal/validation"
|
||||
"github.com/fraktal/mev-beta/pkg/common/selectors"
|
||||
"github.com/fraktal/mev-beta/pkg/uniswap"
|
||||
)
|
||||
|
||||
// safeConvertUint64ToInt is defined in multicall.go to avoid duplication
|
||||
|
||||
// SwapCall represents a decoded swap-like call discovered inside a multicall payload.
|
||||
type SwapCall struct {
|
||||
Selector string
|
||||
Protocol string
|
||||
TokenIn common.Address
|
||||
TokenOut common.Address
|
||||
Recipient common.Address
|
||||
AmountIn *big.Int
|
||||
AmountOut *big.Int
|
||||
AmountOutMinimum *big.Int
|
||||
Fee uint32
|
||||
Path []common.Address
|
||||
Factory common.Address
|
||||
PoolAddress common.Address
|
||||
Pools []common.Address
|
||||
Fees []uint32
|
||||
Deadline *big.Int
|
||||
Raw []byte
|
||||
nested bool
|
||||
heuristicFallback bool
|
||||
}
|
||||
|
||||
// DecodeSwapCallsFromMulticall walks a multicall payload and returns decoded swap calls.
|
||||
func DecodeSwapCallsFromMulticall(data []byte, ctx *MulticallContext) ([]*SwapCall, error) {
|
||||
calls, err := decodeMulticallCalls(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
validator := getAddressValidator()
|
||||
results := make([]*SwapCall, 0)
|
||||
for _, call := range calls {
|
||||
results = append(results, decodeSwapCallRecursive(call, ctx, validator, 0)...)
|
||||
}
|
||||
|
||||
if len(results) == 0 && ctx == nil {
|
||||
heuristicTokens := heuristicExtractTokens(calls, validator)
|
||||
if len(heuristicTokens) >= 2 {
|
||||
for i := 0; i+1 < len(heuristicTokens); i += 2 {
|
||||
swap := &SwapCall{
|
||||
Selector: "heuristic",
|
||||
Protocol: "Unclassified",
|
||||
TokenIn: heuristicTokens[i],
|
||||
TokenOut: heuristicTokens[i+1],
|
||||
heuristicFallback: true,
|
||||
}
|
||||
results = append(results, swap)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// DecodeSwapCall attempts to decode a single swap call payload.
|
||||
func DecodeSwapCall(data []byte, ctx *MulticallContext) (*SwapCall, error) {
|
||||
if len(data) < 4 {
|
||||
return nil, fmt.Errorf("payload too short to contain function selector")
|
||||
}
|
||||
|
||||
selector := normalizedSelector(hex.EncodeToString(data[:4]))
|
||||
swap := decodeDirectSwap(selector, data[4:], ctx)
|
||||
if swap == nil {
|
||||
return nil, fmt.Errorf("unsupported swap selector 0x%s", selector)
|
||||
}
|
||||
|
||||
swap.Raw = append([]byte(nil), data...)
|
||||
return swap, nil
|
||||
}
|
||||
|
||||
func normalizedSelector(sel string) string {
|
||||
return strings.TrimPrefix(strings.ToLower(sel), "0x")
|
||||
}
|
||||
|
||||
func decodeSwapCallRecursive(call []byte, ctx *MulticallContext, validator *validation.AddressValidator, depth int) []*SwapCall {
|
||||
if len(call) < 4 || depth > 6 {
|
||||
return nil
|
||||
}
|
||||
|
||||
selector := strings.ToLower(hex.EncodeToString(call[:4]))
|
||||
payload := call[4:]
|
||||
|
||||
switch selector {
|
||||
case normalizedSelector(selectors.UniswapV3MulticallWithDeadline),
|
||||
normalizedSelector(selectors.UniswapV3MulticallWithBlockhash),
|
||||
normalizedSelector(selectors.UniswapV3Multicall):
|
||||
nested, err := DecodeSwapCallsFromMulticall(payload, ctx)
|
||||
if err != nil {
|
||||
recordSuspiciousMulticall(payload, 0, ctx)
|
||||
return nil
|
||||
}
|
||||
markNested(nested)
|
||||
return nested
|
||||
case normalizedSelector(selectors.UniversalRouterExecute), normalizedSelector(selectors.UniversalRouterExecutePayable):
|
||||
return decodeUniversalRouterExecute(payload, ctx, validator, depth+1)
|
||||
}
|
||||
|
||||
if swap := decodeDirectSwap(selector, payload, ctx); swap != nil {
|
||||
swap.Raw = call
|
||||
return []*SwapCall{swap}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func decodeDirectSwap(selector string, payload []byte, ctx *MulticallContext) *SwapCall {
|
||||
switch selector {
|
||||
case normalizedSelector(selectors.UniswapV3ExactInputSingle), normalizedSelector(selectors.UniswapV3ExactInputSingleLegacy):
|
||||
return decodeExactInputSingle(payload, ctx)
|
||||
case normalizedSelector(selectors.UniswapV3ExactOutputSingle):
|
||||
return decodeExactOutputSingle(payload, ctx)
|
||||
case normalizedSelector(selectors.UniswapV3ExactInput), normalizedSelector(selectors.UniswapV3ExactInputLegacy):
|
||||
return decodeExactInput(payload, ctx)
|
||||
case normalizedSelector(selectors.UniswapV3ExactOutput), normalizedSelector(selectors.UniswapV3ExactOutputBytes):
|
||||
return decodeExactOutput(payload, ctx)
|
||||
case normalizedSelector(selectors.UniswapV2SwapExactTokensForTokens),
|
||||
normalizedSelector(selectors.UniswapV2SwapTokensForExactTokens),
|
||||
normalizedSelector(selectors.UniswapV2SwapExactETHForTokens),
|
||||
normalizedSelector(selectors.UniswapV2SwapExactTokensForETH),
|
||||
normalizedSelector(selectors.UniswapV2SwapExactTokensForTokensSupportingFee),
|
||||
normalizedSelector(selectors.UniswapV2SwapExactTokensForTokensSupportingFeeLegacy),
|
||||
normalizedSelector(selectors.UniswapV2SwapExactETHForTokensSupportingFee),
|
||||
normalizedSelector(selectors.UniswapV2SwapExactTokensForETHSupportingFee):
|
||||
return decodeUniswapV2Swap(selector, payload, ctx)
|
||||
case normalizedSelector(selectors.NonfungiblePositionManagerMint):
|
||||
return decodePositionManagerMint(payload, ctx)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func decodeUniversalRouterExecute(payload []byte, ctx *MulticallContext, validator *validation.AddressValidator, depth int) []*SwapCall {
|
||||
if len(payload) < 96 {
|
||||
return nil
|
||||
}
|
||||
|
||||
commandsOffset, ok := readInt(payload[0:32])
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
inputsOffset, ok := readInt(payload[32:64])
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
commands, ok := readBytesAt(payload, commandsOffset)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
inputs, ok := readBytesArrayAt(payload, inputsOffset)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
var results []*SwapCall
|
||||
for idx, input := range inputs {
|
||||
if len(commands) > idx {
|
||||
// command interpretation can refine protocol selection later if needed.
|
||||
}
|
||||
results = append(results, decodeSwapCallRecursive(input, ctx, validator, depth+1)...)
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
func decodeExactInputSingle(payload []byte, ctx *MulticallContext) *SwapCall {
|
||||
if len(payload) < 256 {
|
||||
return nil
|
||||
}
|
||||
|
||||
tokenIn := common.BytesToAddress(payload[12:32])
|
||||
tokenOut := common.BytesToAddress(payload[44:64])
|
||||
fee := binary.BigEndian.Uint32(append(make([]byte, 1), payload[93:96]...))
|
||||
recipient := common.BytesToAddress(payload[108:128])
|
||||
deadline := new(big.Int).SetBytes(payload[128:160])
|
||||
amountIn := new(big.Int).SetBytes(payload[160:192])
|
||||
amountOutMin := new(big.Int).SetBytes(payload[192:224])
|
||||
|
||||
swap := &SwapCall{
|
||||
Selector: selectors.UniswapV3ExactInputSingle,
|
||||
Protocol: defaultProtocol(contextProtocol(ctx), "UniswapV3"),
|
||||
TokenIn: tokenIn,
|
||||
TokenOut: tokenOut,
|
||||
Recipient: recipient,
|
||||
Deadline: deadline,
|
||||
AmountIn: amountIn,
|
||||
AmountOutMinimum: amountOutMin,
|
||||
Fee: uint32(fee),
|
||||
Path: []common.Address{tokenIn, tokenOut},
|
||||
}
|
||||
|
||||
swap.Factory = common.HexToAddress("0x1F98431c8aD98523631AE4a59f267346ea31F984")
|
||||
swap.ensurePoolAddress()
|
||||
swap.Fees = []uint32{uint32(fee)}
|
||||
swap.Fees = []uint32{uint32(fee)}
|
||||
return swap
|
||||
}
|
||||
|
||||
func decodeExactInput(payload []byte, ctx *MulticallContext) *SwapCall {
|
||||
if len(payload) < 160 {
|
||||
return nil
|
||||
}
|
||||
|
||||
pathOffset, ok := readInt(payload[0:32])
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
recipient := common.BytesToAddress(payload[44:64])
|
||||
deadline := new(big.Int).SetBytes(payload[64:96])
|
||||
amountIn := new(big.Int).SetBytes(payload[96:128])
|
||||
amountOutMin := new(big.Int).SetBytes(payload[128:160])
|
||||
|
||||
pathBytes, ok := readBytesAt(payload, pathOffset)
|
||||
if !ok || len(pathBytes) < 40 {
|
||||
return nil
|
||||
}
|
||||
|
||||
tokens, fees := parseUniswapV3FullPath(pathBytes)
|
||||
tokenIn := common.Address{}
|
||||
tokenOut := common.Address{}
|
||||
if len(tokens) >= 1 {
|
||||
tokenIn = tokens[0]
|
||||
}
|
||||
if len(tokens) >= 1 {
|
||||
tokenOut = tokens[len(tokens)-1]
|
||||
}
|
||||
fee := uint32(0)
|
||||
if len(fees) > 0 {
|
||||
fee = fees[0]
|
||||
}
|
||||
|
||||
swap := &SwapCall{
|
||||
Selector: selectors.UniswapV3ExactInput,
|
||||
Protocol: defaultProtocol(contextProtocol(ctx), "UniswapV3"),
|
||||
TokenIn: tokenIn,
|
||||
TokenOut: tokenOut,
|
||||
Recipient: recipient,
|
||||
Deadline: deadline,
|
||||
AmountIn: amountIn,
|
||||
AmountOutMinimum: amountOutMin,
|
||||
Fee: fee,
|
||||
}
|
||||
|
||||
swap.Factory = common.HexToAddress("0x1F98431c8aD98523631AE4a59f267346ea31F984")
|
||||
swap.Path = tokens
|
||||
swap.Fees = fees
|
||||
swap.ensurePoolAddress()
|
||||
return swap
|
||||
}
|
||||
|
||||
func decodeExactOutput(payload []byte, ctx *MulticallContext) *SwapCall {
|
||||
if len(payload) < 160 {
|
||||
return nil
|
||||
}
|
||||
|
||||
pathOffset, ok := readInt(payload[0:32])
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
recipient := common.BytesToAddress(payload[44:64])
|
||||
deadline := new(big.Int).SetBytes(payload[64:96])
|
||||
amountOut := new(big.Int).SetBytes(payload[96:128])
|
||||
amountInMax := new(big.Int).SetBytes(payload[128:160])
|
||||
|
||||
pathBytes, ok := readBytesAt(payload, pathOffset)
|
||||
if !ok || len(pathBytes) < 40 {
|
||||
return nil
|
||||
}
|
||||
|
||||
tokens, fees := parseUniswapV3FullPath(pathBytes)
|
||||
tokenOut := common.Address{}
|
||||
tokenIn := common.Address{}
|
||||
if len(tokens) >= 1 {
|
||||
tokenOut = tokens[0]
|
||||
}
|
||||
if len(tokens) >= 2 {
|
||||
tokenIn = tokens[len(tokens)-1]
|
||||
}
|
||||
fee := uint32(0)
|
||||
if len(fees) > 0 {
|
||||
fee = fees[len(fees)-1]
|
||||
}
|
||||
|
||||
swap := &SwapCall{
|
||||
Selector: selectors.UniswapV3ExactOutput,
|
||||
Protocol: defaultProtocol(contextProtocol(ctx), "UniswapV3"),
|
||||
TokenIn: tokenIn,
|
||||
TokenOut: tokenOut,
|
||||
Recipient: recipient,
|
||||
Deadline: deadline,
|
||||
AmountIn: amountInMax,
|
||||
AmountOut: amountOut,
|
||||
Fee: fee,
|
||||
}
|
||||
swap.Factory = common.HexToAddress("0x1F98431c8aD98523631AE4a59f267346ea31F984")
|
||||
swap.Path = tokens
|
||||
swap.Fees = fees
|
||||
swap.ensurePoolAddress()
|
||||
return swap
|
||||
}
|
||||
|
||||
func decodeExactOutputSingle(payload []byte, ctx *MulticallContext) *SwapCall {
|
||||
if len(payload) < 256 {
|
||||
return nil
|
||||
}
|
||||
|
||||
tokenIn := common.BytesToAddress(payload[12:32])
|
||||
tokenOut := common.BytesToAddress(payload[44:64])
|
||||
fee := binary.BigEndian.Uint32(append(make([]byte, 1), payload[93:96]...))
|
||||
recipient := common.BytesToAddress(payload[108:128])
|
||||
deadline := new(big.Int).SetBytes(payload[128:160])
|
||||
amountOut := new(big.Int).SetBytes(payload[160:192])
|
||||
amountInMax := new(big.Int).SetBytes(payload[192:224])
|
||||
|
||||
swap := &SwapCall{
|
||||
Selector: selectors.UniswapV3ExactOutputSingle,
|
||||
Protocol: defaultProtocol(contextProtocol(ctx), "UniswapV3"),
|
||||
TokenIn: tokenIn,
|
||||
TokenOut: tokenOut,
|
||||
Recipient: recipient,
|
||||
Deadline: deadline,
|
||||
AmountIn: amountInMax,
|
||||
AmountOut: amountOut,
|
||||
Fee: uint32(fee),
|
||||
Path: []common.Address{tokenIn, tokenOut},
|
||||
}
|
||||
|
||||
swap.Factory = common.HexToAddress("0x1F98431c8aD98523631AE4a59f267346ea31F984")
|
||||
swap.Fees = []uint32{uint32(fee)}
|
||||
swap.ensurePoolAddress()
|
||||
return swap
|
||||
}
|
||||
|
||||
func decodeUniswapV2Swap(selector string, payload []byte, ctx *MulticallContext) *SwapCall {
|
||||
if len(payload) < 160 {
|
||||
return nil
|
||||
}
|
||||
|
||||
pathOffset, ok := readInt(payload[64:96])
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
path, ok := readAddressArray(payload, pathOffset)
|
||||
if !ok || len(path) < 2 {
|
||||
return nil
|
||||
}
|
||||
|
||||
tokenIn := path[0]
|
||||
tokenOut := path[len(path)-1]
|
||||
protocol := defaultProtocol(contextProtocol(ctx), "UniswapV2")
|
||||
|
||||
amountA := new(big.Int).SetBytes(payload[0:32])
|
||||
amountB := new(big.Int).SetBytes(payload[32:64])
|
||||
|
||||
swap := &SwapCall{
|
||||
Selector: selector,
|
||||
Protocol: protocol,
|
||||
TokenIn: tokenIn,
|
||||
TokenOut: tokenOut,
|
||||
Path: path,
|
||||
}
|
||||
|
||||
switch selector {
|
||||
case normalizedSelector(selectors.UniswapV2SwapExactTokensForTokens),
|
||||
normalizedSelector(selectors.UniswapV2SwapExactETHForTokens),
|
||||
normalizedSelector(selectors.UniswapV2SwapExactTokensForTokensSupportingFee),
|
||||
normalizedSelector(selectors.UniswapV2SwapExactTokensForTokensSupportingFeeLegacy),
|
||||
normalizedSelector(selectors.UniswapV2SwapExactETHForTokensSupportingFee),
|
||||
normalizedSelector(selectors.UniswapV2SwapExactTokensForETHSupportingFee):
|
||||
swap.AmountIn = amountA
|
||||
swap.AmountOutMinimum = amountB
|
||||
case normalizedSelector(selectors.UniswapV2SwapTokensForExactTokens),
|
||||
normalizedSelector(selectors.UniswapV2SwapTokensForExactETH),
|
||||
normalizedSelector(selectors.UniswapV2SwapETHForExactTokens):
|
||||
swap.AmountOut = amountA
|
||||
swap.AmountIn = amountB
|
||||
default:
|
||||
swap.AmountIn = amountA
|
||||
swap.AmountOut = amountB
|
||||
}
|
||||
|
||||
swap.Recipient = common.BytesToAddress(payload[96+12 : 128])
|
||||
swap.Deadline = new(big.Int).SetBytes(payload[128:160])
|
||||
|
||||
if strings.Contains(strings.ToLower(protocol), "sushi") {
|
||||
swap.Factory = common.HexToAddress("0xC0AEe478e3658e2610c5F7A4A2E1777cE9e4f2Ac")
|
||||
} else {
|
||||
// Default to canonical Arbitrum Uniswap V2 factory (f1d7cc...) set in parser.
|
||||
swap.Factory = common.HexToAddress("0xf1D7CC64Fb4452F05c498126312eBE29f30Fbcf9")
|
||||
}
|
||||
|
||||
swap.ensureV2PoolAddress()
|
||||
return swap
|
||||
}
|
||||
|
||||
func decodePositionManagerMint(payload []byte, ctx *MulticallContext) *SwapCall {
|
||||
if len(payload) < 320 {
|
||||
return nil
|
||||
}
|
||||
|
||||
paramsOffset, ok := readInt(payload[0:32])
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
paramsStart := paramsOffset
|
||||
if paramsStart+352 > len(payload) {
|
||||
return nil
|
||||
}
|
||||
params := payload[paramsStart : paramsStart+352]
|
||||
|
||||
token0 := common.BytesToAddress(params[12:32])
|
||||
token1 := common.BytesToAddress(params[44:64])
|
||||
fee := binary.BigEndian.Uint32(append(make([]byte, 1), params[93:96]...))
|
||||
amount0Desired := new(big.Int).SetBytes(params[160:192])
|
||||
amount1Desired := new(big.Int).SetBytes(params[192:224])
|
||||
recipient := common.BytesToAddress(params[276:296])
|
||||
deadline := new(big.Int).SetBytes(params[296:328])
|
||||
|
||||
swap := &SwapCall{
|
||||
Selector: selectors.NonfungiblePositionManagerMint,
|
||||
Protocol: defaultProtocol(contextProtocol(ctx), "UniswapV3"),
|
||||
TokenIn: token0,
|
||||
TokenOut: token1,
|
||||
Fee: fee,
|
||||
AmountIn: amount0Desired,
|
||||
AmountOut: amount1Desired,
|
||||
Recipient: recipient,
|
||||
Deadline: deadline,
|
||||
}
|
||||
swap.Factory = common.HexToAddress("0x1F98431c8aD98523631AE4a59f267346ea31F984")
|
||||
swap.ensurePoolAddress()
|
||||
return swap
|
||||
}
|
||||
|
||||
func (sc *SwapCall) ensurePoolAddress() {
|
||||
if sc.PoolAddress != (common.Address{}) && len(sc.Pools) > 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if sc.Factory == (common.Address{}) {
|
||||
return
|
||||
}
|
||||
|
||||
tokens := sc.Path
|
||||
fees := sc.Fees
|
||||
if len(tokens) < 2 {
|
||||
tokens = []common.Address{sc.TokenIn, sc.TokenOut}
|
||||
}
|
||||
|
||||
pools := make([]common.Address, 0)
|
||||
for i := 0; i+1 < len(tokens); i++ {
|
||||
fee := int64(0)
|
||||
if len(fees) > i {
|
||||
fee = int64(fees[i])
|
||||
} else if sc.Fee != 0 {
|
||||
fee = int64(sc.Fee)
|
||||
} else {
|
||||
fee = 3000
|
||||
}
|
||||
|
||||
pool := uniswap.CalculatePoolAddress(sc.Factory, tokens[i], tokens[i+1], fee)
|
||||
pools = append(pools, pool)
|
||||
if sc.PoolAddress == (common.Address{}) {
|
||||
sc.PoolAddress = pool
|
||||
}
|
||||
}
|
||||
|
||||
sc.Pools = pools
|
||||
}
|
||||
|
||||
func (sc *SwapCall) ensureV2PoolAddress() {
|
||||
if sc.PoolAddress != (common.Address{}) && len(sc.Pools) > 0 {
|
||||
return
|
||||
}
|
||||
if sc.Factory == (common.Address{}) || sc.TokenIn == (common.Address{}) || sc.TokenOut == (common.Address{}) {
|
||||
return
|
||||
}
|
||||
|
||||
tokens := sc.Path
|
||||
if len(tokens) < 2 {
|
||||
tokens = []common.Address{sc.TokenIn, sc.TokenOut}
|
||||
}
|
||||
|
||||
pools := make([]common.Address, 0)
|
||||
for i := 0; i+1 < len(tokens); i++ {
|
||||
token0 := tokens[i]
|
||||
token1 := tokens[i+1]
|
||||
if token0.Big().Cmp(token1.Big()) > 0 {
|
||||
token0, token1 = token1, token0
|
||||
}
|
||||
|
||||
keccakInput := append(token0.Bytes(), token1.Bytes()...)
|
||||
salt := crypto.Keccak256(keccakInput)
|
||||
initCodeHash := common.HexToHash("0x96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f")
|
||||
|
||||
data := make([]byte, 0, 85)
|
||||
data = append(data, 0xff)
|
||||
data = append(data, sc.Factory.Bytes()...)
|
||||
data = append(data, salt...)
|
||||
data = append(data, initCodeHash.Bytes()...)
|
||||
|
||||
hash := crypto.Keccak256(data)
|
||||
var addr common.Address
|
||||
copy(addr[:], hash[12:])
|
||||
pools = append(pools, addr)
|
||||
if sc.PoolAddress == (common.Address{}) {
|
||||
sc.PoolAddress = addr
|
||||
}
|
||||
}
|
||||
|
||||
sc.Pools = pools
|
||||
}
|
||||
|
||||
func markNested(calls []*SwapCall) {
|
||||
for _, c := range calls {
|
||||
if c != nil {
|
||||
c.nested = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func contextProtocol(ctx *MulticallContext) string {
|
||||
if ctx == nil {
|
||||
return ""
|
||||
}
|
||||
return ctx.Protocol
|
||||
}
|
||||
|
||||
func defaultProtocol(primary, fallback string) string {
|
||||
if strings.TrimSpace(primary) != "" {
|
||||
return primary
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
func parseUniswapV3Path(path []byte) (common.Address, common.Address, uint32) {
|
||||
if len(path) < 40 {
|
||||
return common.Address{}, common.Address{}, 0
|
||||
}
|
||||
|
||||
tokenIn := common.BytesToAddress(path[:20])
|
||||
tokenOut := common.BytesToAddress(path[len(path)-20:])
|
||||
fee := uint32(0)
|
||||
if len(path) >= 23 {
|
||||
feeBytes := path[20:23]
|
||||
fee = uint32(feeBytes[0])<<16 | uint32(feeBytes[1])<<8 | uint32(feeBytes[2])
|
||||
}
|
||||
return tokenIn, tokenOut, fee
|
||||
}
|
||||
|
||||
func parseUniswapV3FullPath(path []byte) ([]common.Address, []uint32) {
|
||||
tokens := make([]common.Address, 0)
|
||||
fees := make([]uint32, 0)
|
||||
|
||||
if len(path) < 20 {
|
||||
return tokens, fees
|
||||
}
|
||||
|
||||
cursor := 0
|
||||
token := common.BytesToAddress(path[cursor : cursor+20])
|
||||
tokens = append(tokens, token)
|
||||
cursor += 20
|
||||
|
||||
for cursor+3+20 <= len(path) {
|
||||
feeBytes := path[cursor : cursor+3]
|
||||
fee := uint32(feeBytes[0])<<16 | uint32(feeBytes[1])<<8 | uint32(feeBytes[2])
|
||||
fees = append(fees, fee)
|
||||
cursor += 3
|
||||
|
||||
token = common.BytesToAddress(path[cursor : cursor+20])
|
||||
tokens = append(tokens, token)
|
||||
cursor += 20
|
||||
}
|
||||
|
||||
return tokens, fees
|
||||
}
|
||||
|
||||
func readInt(word []byte) (int, bool) {
|
||||
if len(word) != 32 {
|
||||
return 0, false
|
||||
}
|
||||
val := new(big.Int).SetBytes(word)
|
||||
if !val.IsUint64() {
|
||||
return 0, false
|
||||
}
|
||||
return safeConvertUint64ToInt(val.Uint64()), true
|
||||
}
|
||||
|
||||
func readBytesAt(payload []byte, offset int) ([]byte, bool) {
|
||||
cur := offset
|
||||
for depth := 0; depth < 4; depth++ {
|
||||
if cur < 0 || cur+32 > len(payload) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
word := new(big.Int).SetBytes(payload[cur : cur+32])
|
||||
if !word.IsUint64() {
|
||||
return nil, false
|
||||
}
|
||||
val := safeConvertUint64ToInt(word.Uint64())
|
||||
|
||||
// Attempt to treat the value as a pointer to the actual data block.
|
||||
if val > 0 {
|
||||
offsets := []int{}
|
||||
candidate := val + cur
|
||||
if candidate >= 0 {
|
||||
offsets = append(offsets, candidate)
|
||||
}
|
||||
offsets = append(offsets, val)
|
||||
followed := false
|
||||
for _, ptr := range offsets {
|
||||
if ptr < 0 || ptr+32 > len(payload) {
|
||||
continue
|
||||
}
|
||||
lengthWord := new(big.Int).SetBytes(payload[ptr : ptr+32])
|
||||
if !lengthWord.IsUint64() {
|
||||
continue
|
||||
}
|
||||
size := safeConvertUint64ToInt(lengthWord.Uint64())
|
||||
start := ptr + 32
|
||||
end := start + size
|
||||
if size >= 0 && end <= len(payload) {
|
||||
return payload[start:end], true
|
||||
}
|
||||
cur = ptr
|
||||
followed = true
|
||||
break
|
||||
}
|
||||
if followed {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: treat the value as the length relative to the current offset.
|
||||
size := val
|
||||
start := cur + 32
|
||||
end := start + size
|
||||
if size >= 0 && end <= len(payload) {
|
||||
return payload[start:end], true
|
||||
}
|
||||
|
||||
if val == cur {
|
||||
break
|
||||
}
|
||||
cur = val
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func readBytesArrayAt(payload []byte, offset int) ([][]byte, bool) {
|
||||
if offset < 0 || offset+32 > len(payload) {
|
||||
return nil, false
|
||||
}
|
||||
length := new(big.Int).SetBytes(payload[offset : offset+32])
|
||||
if !length.IsUint64() {
|
||||
return nil, false
|
||||
}
|
||||
count := safeConvertUint64ToInt(length.Uint64())
|
||||
result := make([][]byte, 0, count)
|
||||
|
||||
for i := 0; i < count; i++ {
|
||||
entryOffsetWord := payload[offset+32+i*32 : offset+32+(i+1)*32]
|
||||
entryOffset, ok := readInt(entryOffsetWord)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
start := offset + entryOffset
|
||||
entry, ok := readBytesAt(payload, start)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
result = append(result, entry)
|
||||
}
|
||||
|
||||
return result, true
|
||||
}
|
||||
|
||||
func readAddressArray(payload []byte, offset int) ([]common.Address, bool) {
|
||||
if offset < 0 || offset+32 > len(payload) {
|
||||
return nil, false
|
||||
}
|
||||
length := new(big.Int).SetBytes(payload[offset : offset+32])
|
||||
if !length.IsUint64() {
|
||||
return nil, false
|
||||
}
|
||||
count := safeConvertUint64ToInt(length.Uint64())
|
||||
start := offset + 32
|
||||
end := start + count*32
|
||||
if count <= 0 || end > len(payload) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
addresses := make([]common.Address, 0, count)
|
||||
for i := 0; i < count; i++ {
|
||||
word := payload[start+i*32 : start+(i+1)*32]
|
||||
addresses = append(addresses, common.BytesToAddress(word[12:]))
|
||||
}
|
||||
return addresses, true
|
||||
}
|
||||
|
||||
func (sc *SwapCall) String() string {
|
||||
return fmt.Sprintf("%s %s->%s selector=%s", sc.Protocol, sc.TokenIn.Hex(), sc.TokenOut.Hex(), sc.Selector)
|
||||
}
|
||||
Reference in New Issue
Block a user