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:
349
orig/internal/contracts/detector.go
Normal file
349
orig/internal/contracts/detector.go
Normal file
@@ -0,0 +1,349 @@
|
||||
package contracts
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/ethclient"
|
||||
|
||||
"github.com/fraktal/mev-beta/internal/logger"
|
||||
)
|
||||
|
||||
// ContractType represents the detected type of a contract
|
||||
type ContractType int
|
||||
|
||||
const (
|
||||
ContractTypeUnknown ContractType = iota
|
||||
ContractTypeERC20Token
|
||||
ContractTypeUniswapV2Pool
|
||||
ContractTypeUniswapV3Pool
|
||||
ContractTypeUniswapV2Router
|
||||
ContractTypeUniswapV3Router
|
||||
ContractTypeUniversalRouter
|
||||
ContractTypeFactory
|
||||
ContractTypeEOA // Externally Owned Account
|
||||
)
|
||||
|
||||
// String returns the string representation of the contract type
|
||||
func (ct ContractType) String() string {
|
||||
switch ct {
|
||||
case ContractTypeERC20Token:
|
||||
return "ERC-20"
|
||||
case ContractTypeUniswapV2Pool:
|
||||
return "UniswapV2Pool"
|
||||
case ContractTypeUniswapV3Pool:
|
||||
return "UniswapV3Pool"
|
||||
case ContractTypeUniswapV2Router:
|
||||
return "UniswapV2Router"
|
||||
case ContractTypeUniswapV3Router:
|
||||
return "UniswapV3Router"
|
||||
case ContractTypeUniversalRouter:
|
||||
return "UniversalRouter"
|
||||
case ContractTypeFactory:
|
||||
return "Factory"
|
||||
case ContractTypeEOA:
|
||||
return "EOA"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
}
|
||||
|
||||
// DetectionResult contains the result of contract type detection
|
||||
type DetectionResult struct {
|
||||
ContractType ContractType
|
||||
IsContract bool
|
||||
HasCode bool
|
||||
SupportedFunctions []string
|
||||
Confidence float64 // 0.0 to 1.0
|
||||
Error error
|
||||
Warnings []string
|
||||
}
|
||||
|
||||
// ContractDetector provides runtime contract type detection
|
||||
type ContractDetector struct {
|
||||
client *ethclient.Client
|
||||
logger *logger.Logger
|
||||
cache map[common.Address]*DetectionResult
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
// NewContractDetector creates a new contract detector
|
||||
func NewContractDetector(client *ethclient.Client, logger *logger.Logger) *ContractDetector {
|
||||
return &ContractDetector{
|
||||
client: client,
|
||||
logger: logger,
|
||||
cache: make(map[common.Address]*DetectionResult),
|
||||
timeout: 5 * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
// DetectContractType determines the type of contract at the given address
|
||||
func (cd *ContractDetector) DetectContractType(ctx context.Context, address common.Address) *DetectionResult {
|
||||
// Check cache first
|
||||
if result, exists := cd.cache[address]; exists {
|
||||
return result
|
||||
}
|
||||
|
||||
result := &DetectionResult{
|
||||
ContractType: ContractTypeUnknown,
|
||||
IsContract: false,
|
||||
HasCode: false,
|
||||
SupportedFunctions: []string{},
|
||||
Confidence: 0.0,
|
||||
Warnings: []string{},
|
||||
}
|
||||
|
||||
// Create context with timeout
|
||||
ctxWithTimeout, cancel := context.WithTimeout(ctx, cd.timeout)
|
||||
defer cancel()
|
||||
|
||||
// Check if address has code (is a contract)
|
||||
code, err := cd.client.CodeAt(ctxWithTimeout, address, nil)
|
||||
if err != nil {
|
||||
result.Error = fmt.Errorf("failed to get code at address: %w", err)
|
||||
cd.cache[address] = result
|
||||
return result
|
||||
}
|
||||
|
||||
// If no code, it's an EOA
|
||||
if len(code) == 0 {
|
||||
result.ContractType = ContractTypeEOA
|
||||
result.IsContract = false
|
||||
result.Confidence = 1.0
|
||||
cd.cache[address] = result
|
||||
return result
|
||||
}
|
||||
|
||||
result.IsContract = true
|
||||
result.HasCode = true
|
||||
|
||||
// Detect contract type by testing function signatures
|
||||
contractType, confidence, supportedFunctions := cd.detectByFunctionSignatures(ctxWithTimeout, address)
|
||||
result.ContractType = contractType
|
||||
result.Confidence = confidence
|
||||
result.SupportedFunctions = supportedFunctions
|
||||
|
||||
// Additional validation for high-confidence detection
|
||||
if confidence > 0.8 {
|
||||
if err := cd.validateContractType(ctxWithTimeout, address, contractType); err != nil {
|
||||
result.Warnings = append(result.Warnings, fmt.Sprintf("validation warning: %v", err))
|
||||
result.Confidence *= 0.8 // Reduce confidence
|
||||
}
|
||||
}
|
||||
|
||||
cd.cache[address] = result
|
||||
return result
|
||||
}
|
||||
|
||||
// detectByFunctionSignatures detects contract type by testing known function signatures
|
||||
func (cd *ContractDetector) detectByFunctionSignatures(ctx context.Context, address common.Address) (ContractType, float64, []string) {
|
||||
supportedFunctions := []string{}
|
||||
scores := make(map[ContractType]float64)
|
||||
|
||||
// Test ERC-20 functions
|
||||
erc20Functions := map[string][]byte{
|
||||
"name()": {0x06, 0xfd, 0xde, 0x03},
|
||||
"symbol()": {0x95, 0xd8, 0x9b, 0x41},
|
||||
"decimals()": {0x31, 0x3c, 0xe5, 0x67},
|
||||
"totalSupply()": {0x18, 0x16, 0x0d, 0xdd},
|
||||
"balanceOf()": {0x70, 0xa0, 0x82, 0x31},
|
||||
}
|
||||
|
||||
erc20Score := cd.testFunctionSignatures(ctx, address, erc20Functions, &supportedFunctions)
|
||||
if erc20Score > 0.6 {
|
||||
scores[ContractTypeERC20Token] = erc20Score
|
||||
}
|
||||
|
||||
// Test Uniswap V2 Pool functions
|
||||
v2PoolFunctions := map[string][]byte{
|
||||
"token0()": {0x0d, 0xfe, 0x16, 0x81},
|
||||
"token1()": {0xd2, 0x12, 0x20, 0xa7},
|
||||
"getReserves()": {0x09, 0x02, 0xf1, 0xac},
|
||||
"price0CumulativeLast()": {0x54, 0x1c, 0x5c, 0xfa},
|
||||
"kLast()": {0x7d, 0xc0, 0xd1, 0xd0},
|
||||
}
|
||||
|
||||
v2PoolScore := cd.testFunctionSignatures(ctx, address, v2PoolFunctions, &supportedFunctions)
|
||||
if v2PoolScore > 0.6 {
|
||||
scores[ContractTypeUniswapV2Pool] = v2PoolScore
|
||||
}
|
||||
|
||||
// Test Uniswap V3 Pool functions
|
||||
v3PoolFunctions := map[string][]byte{
|
||||
"token0()": {0x0d, 0xfe, 0x16, 0x81},
|
||||
"token1()": {0xd2, 0x12, 0x20, 0xa7},
|
||||
"fee()": {0xdd, 0xca, 0x3f, 0x43},
|
||||
"slot0()": {0x38, 0x50, 0xc7, 0xbd},
|
||||
"liquidity()": {0x1a, 0x68, 0x65, 0x0f},
|
||||
"tickSpacing()": {0xd0, 0xc9, 0x32, 0x07},
|
||||
}
|
||||
|
||||
v3PoolScore := cd.testFunctionSignatures(ctx, address, v3PoolFunctions, &supportedFunctions)
|
||||
if v3PoolScore > 0.6 {
|
||||
scores[ContractTypeUniswapV3Pool] = v3PoolScore
|
||||
}
|
||||
|
||||
// Test Router functions
|
||||
routerFunctions := map[string][]byte{
|
||||
"WETH()": {0xad, 0x5c, 0x46, 0x48},
|
||||
"swapExactTokensForTokens()": {0x38, 0xed, 0x17, 0x39},
|
||||
"factory()": {0xc4, 0x5a, 0x01, 0x55},
|
||||
}
|
||||
|
||||
routerScore := cd.testFunctionSignatures(ctx, address, routerFunctions, &supportedFunctions)
|
||||
if routerScore > 0.5 {
|
||||
scores[ContractTypeUniswapV2Router] = routerScore
|
||||
}
|
||||
|
||||
// Find highest scoring type
|
||||
var bestType ContractType = ContractTypeUnknown
|
||||
var bestScore float64 = 0.0
|
||||
|
||||
for contractType, score := range scores {
|
||||
if score > bestScore {
|
||||
bestScore = score
|
||||
bestType = contractType
|
||||
}
|
||||
}
|
||||
|
||||
return bestType, bestScore, supportedFunctions
|
||||
}
|
||||
|
||||
// testFunctionSignatures tests if a contract supports given function signatures
|
||||
func (cd *ContractDetector) testFunctionSignatures(ctx context.Context, address common.Address, functions map[string][]byte, supportedFunctions *[]string) float64 {
|
||||
supported := 0
|
||||
total := len(functions)
|
||||
|
||||
for funcName, signature := range functions {
|
||||
// Test the function call
|
||||
_, err := cd.client.CallContract(ctx, ethereum.CallMsg{
|
||||
To: &address,
|
||||
Data: signature,
|
||||
}, nil)
|
||||
|
||||
if err == nil {
|
||||
supported++
|
||||
*supportedFunctions = append(*supportedFunctions, funcName)
|
||||
} else if !strings.Contains(err.Error(), "execution reverted") {
|
||||
// If it's not a revert, it might be a network error, so we don't count it against
|
||||
total--
|
||||
}
|
||||
}
|
||||
|
||||
if total == 0 {
|
||||
return 0.0
|
||||
}
|
||||
|
||||
return float64(supported) / float64(total)
|
||||
}
|
||||
|
||||
// validateContractType performs additional validation for detected contract types
|
||||
func (cd *ContractDetector) validateContractType(ctx context.Context, address common.Address, contractType ContractType) error {
|
||||
switch contractType {
|
||||
case ContractTypeERC20Token:
|
||||
return cd.validateERC20(ctx, address)
|
||||
case ContractTypeUniswapV2Pool:
|
||||
return cd.validateUniswapV2Pool(ctx, address)
|
||||
case ContractTypeUniswapV3Pool:
|
||||
return cd.validateUniswapV3Pool(ctx, address)
|
||||
default:
|
||||
return nil // No additional validation for other types
|
||||
}
|
||||
}
|
||||
|
||||
// validateERC20 validates that a contract is actually an ERC-20 token
|
||||
func (cd *ContractDetector) validateERC20(ctx context.Context, address common.Address) error {
|
||||
// Test decimals() - should return a reasonable value (0-18)
|
||||
decimalsData := []byte{0x31, 0x3c, 0xe5, 0x67} // decimals()
|
||||
result, err := cd.client.CallContract(ctx, ethereum.CallMsg{
|
||||
To: &address,
|
||||
Data: decimalsData,
|
||||
}, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("decimals() call failed: %w", err)
|
||||
}
|
||||
|
||||
if len(result) == 32 {
|
||||
decimals := new(big.Int).SetBytes(result).Uint64()
|
||||
if decimals > 18 {
|
||||
return fmt.Errorf("unrealistic decimals value: %d", decimals)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateUniswapV2Pool validates that a contract is actually a Uniswap V2 pool
|
||||
func (cd *ContractDetector) validateUniswapV2Pool(ctx context.Context, address common.Address) error {
|
||||
// Test getReserves() - should return 3 values
|
||||
getReservesData := []byte{0x09, 0x02, 0xf1, 0xac} // getReserves()
|
||||
result, err := cd.client.CallContract(ctx, ethereum.CallMsg{
|
||||
To: &address,
|
||||
Data: getReservesData,
|
||||
}, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("getReserves() call failed: %w", err)
|
||||
}
|
||||
|
||||
// Should return 3 uint112 values (reserves + timestamp)
|
||||
if len(result) != 96 { // 3 * 32 bytes
|
||||
return fmt.Errorf("unexpected getReserves() return length: %d", len(result))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateUniswapV3Pool validates that a contract is actually a Uniswap V3 pool
|
||||
func (cd *ContractDetector) validateUniswapV3Pool(ctx context.Context, address common.Address) error {
|
||||
// Test slot0() - should return current state
|
||||
slot0Data := []byte{0x38, 0x50, 0xc7, 0xbd} // slot0()
|
||||
result, err := cd.client.CallContract(ctx, ethereum.CallMsg{
|
||||
To: &address,
|
||||
Data: slot0Data,
|
||||
}, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("slot0() call failed: %w", err)
|
||||
}
|
||||
|
||||
// Should return multiple values including sqrtPriceX96
|
||||
if len(result) < 32 {
|
||||
return fmt.Errorf("unexpected slot0() return length: %d", len(result))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsERC20Token checks if an address is an ERC-20 token
|
||||
func (cd *ContractDetector) IsERC20Token(ctx context.Context, address common.Address) bool {
|
||||
result := cd.DetectContractType(ctx, address)
|
||||
return result.ContractType == ContractTypeERC20Token && result.Confidence > 0.7
|
||||
}
|
||||
|
||||
// IsUniswapPool checks if an address is a Uniswap pool (V2 or V3)
|
||||
func (cd *ContractDetector) IsUniswapPool(ctx context.Context, address common.Address) bool {
|
||||
result := cd.DetectContractType(ctx, address)
|
||||
return (result.ContractType == ContractTypeUniswapV2Pool || result.ContractType == ContractTypeUniswapV3Pool) && result.Confidence > 0.7
|
||||
}
|
||||
|
||||
// IsRouter checks if an address is a router contract
|
||||
func (cd *ContractDetector) IsRouter(ctx context.Context, address common.Address) bool {
|
||||
result := cd.DetectContractType(ctx, address)
|
||||
return (result.ContractType == ContractTypeUniswapV2Router ||
|
||||
result.ContractType == ContractTypeUniswapV3Router ||
|
||||
result.ContractType == ContractTypeUniversalRouter) && result.Confidence > 0.7
|
||||
}
|
||||
|
||||
// ClearCache clears the detection cache
|
||||
func (cd *ContractDetector) ClearCache() {
|
||||
cd.cache = make(map[common.Address]*DetectionResult)
|
||||
}
|
||||
|
||||
// GetCacheSize returns the number of cached results
|
||||
func (cd *ContractDetector) GetCacheSize() int {
|
||||
return len(cd.cache)
|
||||
}
|
||||
239
orig/internal/contracts/signature_validator.go
Normal file
239
orig/internal/contracts/signature_validator.go
Normal file
@@ -0,0 +1,239 @@
|
||||
package contracts
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
|
||||
"github.com/fraktal/mev-beta/internal/logger"
|
||||
)
|
||||
|
||||
// FunctionSignature represents a known function signature
|
||||
type FunctionSignature struct {
|
||||
Name string
|
||||
Selector []byte
|
||||
AllowedTypes []ContractType
|
||||
}
|
||||
|
||||
// SignatureValidator validates function calls against contract types
|
||||
type SignatureValidator struct {
|
||||
detector *ContractDetector
|
||||
logger *logger.Logger
|
||||
signatures map[string]*FunctionSignature
|
||||
}
|
||||
|
||||
// NewSignatureValidator creates a new function signature validator
|
||||
func NewSignatureValidator(detector *ContractDetector, logger *logger.Logger) *SignatureValidator {
|
||||
sv := &SignatureValidator{
|
||||
detector: detector,
|
||||
logger: logger,
|
||||
signatures: make(map[string]*FunctionSignature),
|
||||
}
|
||||
|
||||
// Initialize known function signatures
|
||||
sv.initializeSignatures()
|
||||
return sv
|
||||
}
|
||||
|
||||
// initializeSignatures initializes the known function signatures and their allowed contract types
|
||||
func (sv *SignatureValidator) initializeSignatures() {
|
||||
// ERC-20 token functions
|
||||
sv.signatures["name()"] = &FunctionSignature{
|
||||
Name: "name()",
|
||||
Selector: []byte{0x06, 0xfd, 0xde, 0x03},
|
||||
AllowedTypes: []ContractType{ContractTypeERC20Token},
|
||||
}
|
||||
sv.signatures["symbol()"] = &FunctionSignature{
|
||||
Name: "symbol()",
|
||||
Selector: []byte{0x95, 0xd8, 0x9b, 0x41},
|
||||
AllowedTypes: []ContractType{ContractTypeERC20Token},
|
||||
}
|
||||
sv.signatures["decimals()"] = &FunctionSignature{
|
||||
Name: "decimals()",
|
||||
Selector: []byte{0x31, 0x3c, 0xe5, 0x67},
|
||||
AllowedTypes: []ContractType{ContractTypeERC20Token},
|
||||
}
|
||||
sv.signatures["totalSupply()"] = &FunctionSignature{
|
||||
Name: "totalSupply()",
|
||||
Selector: []byte{0x18, 0x16, 0x0d, 0xdd},
|
||||
AllowedTypes: []ContractType{ContractTypeERC20Token},
|
||||
}
|
||||
sv.signatures["balanceOf()"] = &FunctionSignature{
|
||||
Name: "balanceOf()",
|
||||
Selector: []byte{0x70, 0xa0, 0x82, 0x31},
|
||||
AllowedTypes: []ContractType{ContractTypeERC20Token},
|
||||
}
|
||||
|
||||
// Uniswap V2 Pool functions
|
||||
sv.signatures["token0()"] = &FunctionSignature{
|
||||
Name: "token0()",
|
||||
Selector: []byte{0x0d, 0xfe, 0x16, 0x81},
|
||||
AllowedTypes: []ContractType{
|
||||
ContractTypeUniswapV2Pool,
|
||||
ContractTypeUniswapV3Pool,
|
||||
},
|
||||
}
|
||||
sv.signatures["token1()"] = &FunctionSignature{
|
||||
Name: "token1()",
|
||||
Selector: []byte{0xd2, 0x12, 0x20, 0xa7},
|
||||
AllowedTypes: []ContractType{
|
||||
ContractTypeUniswapV2Pool,
|
||||
ContractTypeUniswapV3Pool,
|
||||
},
|
||||
}
|
||||
sv.signatures["getReserves()"] = &FunctionSignature{
|
||||
Name: "getReserves()",
|
||||
Selector: []byte{0x09, 0x02, 0xf1, 0xac},
|
||||
AllowedTypes: []ContractType{ContractTypeUniswapV2Pool},
|
||||
}
|
||||
|
||||
// Uniswap V3 Pool specific functions
|
||||
sv.signatures["slot0()"] = &FunctionSignature{
|
||||
Name: "slot0()",
|
||||
Selector: []byte{0x38, 0x50, 0xc7, 0xbd},
|
||||
AllowedTypes: []ContractType{ContractTypeUniswapV3Pool},
|
||||
}
|
||||
sv.signatures["fee()"] = &FunctionSignature{
|
||||
Name: "fee()",
|
||||
Selector: []byte{0xdd, 0xca, 0x3f, 0x43},
|
||||
AllowedTypes: []ContractType{ContractTypeUniswapV3Pool},
|
||||
}
|
||||
sv.signatures["liquidity()"] = &FunctionSignature{
|
||||
Name: "liquidity()",
|
||||
Selector: []byte{0x1a, 0x68, 0x65, 0x0f},
|
||||
AllowedTypes: []ContractType{ContractTypeUniswapV3Pool},
|
||||
}
|
||||
sv.signatures["tickSpacing()"] = &FunctionSignature{
|
||||
Name: "tickSpacing()",
|
||||
Selector: []byte{0xd0, 0xc9, 0x32, 0x07},
|
||||
AllowedTypes: []ContractType{ContractTypeUniswapV3Pool},
|
||||
}
|
||||
|
||||
// Router functions
|
||||
sv.signatures["WETH()"] = &FunctionSignature{
|
||||
Name: "WETH()",
|
||||
Selector: []byte{0xad, 0x5c, 0x46, 0x48},
|
||||
AllowedTypes: []ContractType{
|
||||
ContractTypeUniswapV2Router,
|
||||
ContractTypeUniswapV3Router,
|
||||
},
|
||||
}
|
||||
sv.signatures["factory()"] = &FunctionSignature{
|
||||
Name: "factory()",
|
||||
Selector: []byte{0xc4, 0x5a, 0x01, 0x55},
|
||||
AllowedTypes: []ContractType{
|
||||
ContractTypeUniswapV2Router,
|
||||
ContractTypeUniswapV3Router,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// ValidationResult contains the result of function signature validation
|
||||
type ValidationResult struct {
|
||||
IsValid bool
|
||||
FunctionName string
|
||||
ContractType ContractType
|
||||
Error error
|
||||
Warnings []string
|
||||
}
|
||||
|
||||
// ValidateFunctionCall validates if a function can be called on a contract
|
||||
func (sv *SignatureValidator) ValidateFunctionCall(ctx context.Context, contractAddress common.Address, functionSelector []byte) *ValidationResult {
|
||||
result := &ValidationResult{
|
||||
IsValid: false,
|
||||
Warnings: []string{},
|
||||
}
|
||||
|
||||
// Detect contract type
|
||||
detection := sv.detector.DetectContractType(ctx, contractAddress)
|
||||
result.ContractType = detection.ContractType
|
||||
|
||||
if detection.Error != nil {
|
||||
result.Error = fmt.Errorf("contract type detection failed: %w", detection.Error)
|
||||
return result
|
||||
}
|
||||
|
||||
// Find matching function signature
|
||||
var matchedSignature *FunctionSignature
|
||||
for _, sig := range sv.signatures {
|
||||
if len(sig.Selector) >= 4 && len(functionSelector) >= 4 {
|
||||
if sig.Selector[0] == functionSelector[0] &&
|
||||
sig.Selector[1] == functionSelector[1] &&
|
||||
sig.Selector[2] == functionSelector[2] &&
|
||||
sig.Selector[3] == functionSelector[3] {
|
||||
matchedSignature = sig
|
||||
result.FunctionName = sig.Name
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If no signature match found, warn but allow (could be unknown function)
|
||||
if matchedSignature == nil {
|
||||
result.IsValid = true
|
||||
result.Warnings = append(result.Warnings, fmt.Sprintf("unknown function selector: %x", functionSelector))
|
||||
return result
|
||||
}
|
||||
|
||||
// Check if the detected contract type is allowed for this function
|
||||
allowed := false
|
||||
for _, allowedType := range matchedSignature.AllowedTypes {
|
||||
if detection.ContractType == allowedType {
|
||||
allowed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !allowed {
|
||||
result.Error = fmt.Errorf("function %s cannot be called on contract type %s (allowed types: %v)",
|
||||
matchedSignature.Name, detection.ContractType.String(), matchedSignature.AllowedTypes)
|
||||
return result
|
||||
}
|
||||
|
||||
// Check confidence level
|
||||
if detection.Confidence < 0.7 {
|
||||
result.Warnings = append(result.Warnings, fmt.Sprintf("low confidence in contract type detection: %.2f", detection.Confidence))
|
||||
}
|
||||
|
||||
result.IsValid = true
|
||||
return result
|
||||
}
|
||||
|
||||
// ValidateToken0Call specifically validates token0() function calls
|
||||
func (sv *SignatureValidator) ValidateToken0Call(ctx context.Context, contractAddress common.Address) *ValidationResult {
|
||||
token0Selector := []byte{0x0d, 0xfe, 0x16, 0x81}
|
||||
return sv.ValidateFunctionCall(ctx, contractAddress, token0Selector)
|
||||
}
|
||||
|
||||
// ValidateToken1Call specifically validates token1() function calls
|
||||
func (sv *SignatureValidator) ValidateToken1Call(ctx context.Context, contractAddress common.Address) *ValidationResult {
|
||||
token1Selector := []byte{0xd2, 0x12, 0x20, 0xa7}
|
||||
return sv.ValidateFunctionCall(ctx, contractAddress, token1Selector)
|
||||
}
|
||||
|
||||
// ValidateGetReservesCall specifically validates getReserves() function calls
|
||||
func (sv *SignatureValidator) ValidateGetReservesCall(ctx context.Context, contractAddress common.Address) *ValidationResult {
|
||||
getReservesSelector := []byte{0x09, 0x02, 0xf1, 0xac}
|
||||
return sv.ValidateFunctionCall(ctx, contractAddress, getReservesSelector)
|
||||
}
|
||||
|
||||
// ValidateSlot0Call specifically validates slot0() function calls for Uniswap V3
|
||||
func (sv *SignatureValidator) ValidateSlot0Call(ctx context.Context, contractAddress common.Address) *ValidationResult {
|
||||
slot0Selector := []byte{0x38, 0x50, 0xc7, 0xbd}
|
||||
return sv.ValidateFunctionCall(ctx, contractAddress, slot0Selector)
|
||||
}
|
||||
|
||||
// GetSupportedFunctions returns the functions supported by a contract type
|
||||
func (sv *SignatureValidator) GetSupportedFunctions(contractType ContractType) []string {
|
||||
var functions []string
|
||||
for _, sig := range sv.signatures {
|
||||
for _, allowedType := range sig.AllowedTypes {
|
||||
if allowedType == contractType {
|
||||
functions = append(functions, sig.Name)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return functions
|
||||
}
|
||||
Reference in New Issue
Block a user