Merge pull request #90 from flashbots/specs-v2

Group classifying a trace as a `transfer` with the logic to decode a `Transfer` object
This commit is contained in:
Luke Van Seters 2021-10-08 11:36:39 -04:00 committed by GitHub
commit 8c6d7ab889
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 156 additions and 46 deletions

3
.gitignore vendored
View File

@ -19,3 +19,6 @@ cache
# k8s # k8s
.helm .helm
# env
.envrc

View File

@ -1,14 +1,17 @@
from mev_inspect.schemas.classified_traces import ( from mev_inspect.schemas.classified_traces import (
Classification,
ClassifierSpec,
Protocol, Protocol,
) )
from mev_inspect.schemas.classifiers import (
ClassifierSpec,
LiquidationClassifier,
)
AAVE_SPEC = ClassifierSpec( AAVE_SPEC = ClassifierSpec(
abi_name="AaveLendingPool", abi_name="AaveLendingPool",
protocol=Protocol.aave, protocol=Protocol.aave,
classifications={ classifiers={
"liquidationCall(address,address,address,uint256,bool)": Classification.liquidate, "liquidationCall(address,address,address,uint256,bool)": LiquidationClassifier,
}, },
) )

View File

@ -1,16 +1,19 @@
from mev_inspect.schemas.classified_traces import ( from mev_inspect.schemas.classified_traces import (
Classification,
ClassifierSpec,
Protocol, Protocol,
) )
from mev_inspect.schemas.classifiers import (
ClassifierSpec,
SwapClassifier,
)
BALANCER_V1_SPECS = [ BALANCER_V1_SPECS = [
ClassifierSpec( ClassifierSpec(
abi_name="BPool", abi_name="BPool",
protocol=Protocol.balancer_v1, protocol=Protocol.balancer_v1,
classifications={ classifiers={
"swapExactAmountIn(address,uint256,address,uint256,uint256)": Classification.swap, "swapExactAmountIn(address,uint256,address,uint256,uint256)": SwapClassifier,
"swapExactAmountOut(address,uint256,address,uint256,uint256)": Classification.swap, "swapExactAmountOut(address,uint256,address,uint256,uint256)": SwapClassifier,
}, },
), ),
] ]

View File

@ -1,8 +1,11 @@
from mev_inspect.schemas.classified_traces import ( from mev_inspect.schemas.classified_traces import (
ClassifierSpec,
Protocol, Protocol,
) )
from mev_inspect.schemas.classifiers import (
ClassifierSpec,
)
""" """
Deployment addresses found here Deployment addresses found here
https://curve.readthedocs.io/ref-addresses.html https://curve.readthedocs.io/ref-addresses.html

View File

@ -1,15 +1,30 @@
from mev_inspect.schemas.classified_traces import ( from mev_inspect.schemas.classified_traces import DecodedCallTrace
Classification, from mev_inspect.schemas.classifiers import (
ClassifierSpec, ClassifierSpec,
TransferClassifier,
) )
from mev_inspect.schemas.transfers import ERC20Transfer
class ERC20TransferClassifier(TransferClassifier):
@staticmethod
def get_transfer(trace: DecodedCallTrace) -> ERC20Transfer:
return ERC20Transfer(
block_number=trace.block_number,
transaction_hash=trace.transaction_hash,
trace_address=trace.trace_address,
amount=trace.inputs["amount"],
to_address=trace.inputs["recipient"],
from_address=trace.inputs.get("sender", trace.from_address),
token_address=trace.to_address,
)
ERC20_SPEC = ClassifierSpec( ERC20_SPEC = ClassifierSpec(
abi_name="ERC20", abi_name="ERC20",
classifications={ classifiers={
"transferFrom(address,address,uint256)": Classification.transfer, "transferFrom(address,address,uint256)": ERC20TransferClassifier,
"transfer(address,uint256)": Classification.transfer, "transfer(address,uint256)": ERC20TransferClassifier,
"burn(address)": Classification.burn,
}, },
) )

View File

@ -1,8 +1,10 @@
from mev_inspect.schemas.classified_traces import ( from mev_inspect.schemas.classified_traces import (
Classification,
ClassifierSpec,
Protocol, Protocol,
) )
from mev_inspect.schemas.classifiers import (
ClassifierSpec,
SwapClassifier,
)
UNISWAP_V3_CONTRACT_SPECS = [ UNISWAP_V3_CONTRACT_SPECS = [
@ -66,8 +68,8 @@ UNISWAP_V3_CONTRACT_SPECS = [
UNISWAP_V3_GENERAL_SPECS = [ UNISWAP_V3_GENERAL_SPECS = [
ClassifierSpec( ClassifierSpec(
abi_name="UniswapV3Pool", abi_name="UniswapV3Pool",
classifications={ classifiers={
"swap(address,bool,int256,uint160,bytes)": Classification.swap, "swap(address,bool,int256,uint160,bytes)": SwapClassifier,
}, },
), ),
ClassifierSpec( ClassifierSpec(
@ -97,8 +99,8 @@ UNISWAPPY_V2_CONTRACT_SPECS = [
UNISWAPPY_V2_PAIR_SPEC = ClassifierSpec( UNISWAPPY_V2_PAIR_SPEC = ClassifierSpec(
abi_name="UniswapV2Pair", abi_name="UniswapV2Pair",
classifications={ classifiers={
"swap(uint256,uint256,address,bytes)": Classification.swap, "swap(uint256,uint256,address,bytes)": SwapClassifier,
}, },
) )

View File

@ -1,16 +1,35 @@
from mev_inspect.schemas.classified_traces import ( from mev_inspect.schemas.classified_traces import (
Classification,
ClassifierSpec,
Protocol, Protocol,
) )
from mev_inspect.schemas.classifiers import (
ClassifierSpec,
DecodedCallTrace,
TransferClassifier,
)
from mev_inspect.schemas.transfers import ERC20Transfer
class WethTransferClassifier(TransferClassifier):
@staticmethod
def get_transfer(trace: DecodedCallTrace) -> ERC20Transfer:
return ERC20Transfer(
block_number=trace.block_number,
transaction_hash=trace.transaction_hash,
trace_address=trace.trace_address,
amount=trace.inputs["wad"],
to_address=trace.inputs["dst"],
from_address=trace.from_address,
token_address=trace.to_address,
)
WETH_SPEC = ClassifierSpec( WETH_SPEC = ClassifierSpec(
abi_name="WETH9", abi_name="WETH9",
protocol=Protocol.weth, protocol=Protocol.weth,
valid_contract_addresses=["0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"], valid_contract_addresses=["0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"],
classifications={ classifiers={
"transferFrom(address,address,uint256)": Classification.transfer, "transferFrom(address,address,uint256)": WethTransferClassifier,
"transfer(address,uint256)": Classification.transfer, "transfer(address,uint256)": WethTransferClassifier,
}, },
) )

View File

@ -1,7 +1,9 @@
from mev_inspect.schemas.classified_traces import ( from mev_inspect.schemas.classified_traces import (
ClassifierSpec,
Protocol, Protocol,
) )
from mev_inspect.schemas.classifiers import (
ClassifierSpec,
)
ZEROX_CONTRACT_SPECS = [ ZEROX_CONTRACT_SPECS = [

View File

@ -67,8 +67,11 @@ class TraceClassifier:
if call_data is not None: if call_data is not None:
signature = call_data.function_signature signature = call_data.function_signature
classification = spec.classifications.get( classifier = spec.classifiers.get(signature)
signature, Classification.unknown classification = (
Classification.unknown
if classifier is None
else classifier.get_classification()
) )
return DecodedCallTrace( return DecodedCallTrace(

View File

@ -64,6 +64,8 @@ def inspect_block(
write_classified_traces(db_session, classified_traces) write_classified_traces(db_session, classified_traces)
transfers = get_transfers(classified_traces) transfers = get_transfers(classified_traces)
logger.info(f"Found {len(transfers)} transfers")
if should_write_transfers: if should_write_transfers:
delete_transfers_for_block(db_session, block_number) delete_transfers_for_block(db_session, block_number)
write_transfers(db_session, transfers) write_transfers(db_session, transfers)

View File

@ -1,15 +1,12 @@
from enum import Enum from enum import Enum
from typing import Any, Dict, List, Optional from typing import Any, Dict, List, Optional
from pydantic import BaseModel
from .blocks import Trace from .blocks import Trace
class Classification(Enum): class Classification(Enum):
unknown = "unknown" unknown = "unknown"
swap = "swap" swap = "swap"
burn = "burn"
transfer = "transfer" transfer = "transfer"
liquidate = "liquidate" liquidate = "liquidate"
@ -62,12 +59,5 @@ class DecodedCallTrace(CallTrace):
protocol: Optional[Protocol] protocol: Optional[Protocol]
gas: Optional[int] gas: Optional[int]
gas_used: Optional[int] gas_used: Optional[int]
function_name: Optional[str] function_name: str
function_signature: Optional[str] function_signature: str
class ClassifierSpec(BaseModel):
abi_name: str
protocol: Optional[Protocol] = None
valid_contract_addresses: Optional[List[str]] = None
classifications: Dict[str, Classification] = {}

View File

@ -0,0 +1,44 @@
from abc import ABC, abstractmethod
from typing import Dict, List, Optional, Type
from pydantic import BaseModel
from .classified_traces import Classification, DecodedCallTrace, Protocol
from .transfers import ERC20Transfer
class Classifier(ABC):
@staticmethod
@abstractmethod
def get_classification() -> Classification:
raise NotImplementedError()
class TransferClassifier(Classifier):
@staticmethod
def get_classification() -> Classification:
return Classification.transfer
@staticmethod
@abstractmethod
def get_transfer(trace: DecodedCallTrace) -> ERC20Transfer:
raise NotImplementedError()
class SwapClassifier(Classifier):
@staticmethod
def get_classification() -> Classification:
return Classification.swap
class LiquidationClassifier(Classifier):
@staticmethod
def get_classification() -> Classification:
return Classification.liquidate
class ClassifierSpec(BaseModel):
abi_name: str
protocol: Optional[Protocol] = None
valid_contract_addresses: Optional[List[str]] = None
classifiers: Dict[str, Type[Classifier]] = {}

View File

@ -1,6 +1,13 @@
from typing import Dict, List, Optional, Sequence from typing import Dict, List, Optional, Sequence, Tuple
from mev_inspect.schemas.classified_traces import Classification, ClassifiedTrace from mev_inspect.classifiers.specs import ALL_CLASSIFIER_SPECS
from mev_inspect.schemas.classifiers import ClassifierSpec, TransferClassifier
from mev_inspect.schemas.classified_traces import (
Classification,
ClassifiedTrace,
DecodedCallTrace,
Protocol,
)
from mev_inspect.schemas.transfers import ERC20Transfer, EthTransfer, TransferGeneric from mev_inspect.schemas.transfers import ERC20Transfer, EthTransfer, TransferGeneric
from mev_inspect.traces import is_child_trace_address, get_child_traces from mev_inspect.traces import is_child_trace_address, get_child_traces
@ -18,9 +25,21 @@ def get_eth_transfers(traces: List[ClassifiedTrace]) -> List[EthTransfer]:
def get_transfers(traces: List[ClassifiedTrace]) -> List[ERC20Transfer]: def get_transfers(traces: List[ClassifiedTrace]) -> List[ERC20Transfer]:
transfers = [] transfers = []
specs_by_abi_name_and_protocol: Dict[
Tuple[str, Optional[Protocol]], ClassifierSpec
] = {(spec.abi_name, spec.protocol): spec for spec in ALL_CLASSIFIER_SPECS}
for trace in traces: for trace in traces:
if trace.classification == Classification.transfer: if not isinstance(trace, DecodedCallTrace):
transfers.append(ERC20Transfer.from_trace(trace)) continue
abi_name_and_protocol = (trace.abi_name, trace.protocol)
spec = specs_by_abi_name_and_protocol.get(abi_name_and_protocol)
if spec is not None:
classifier = spec.classifiers.get(trace.function_signature)
if classifier is not None and issubclass(classifier, TransferClassifier):
transfers.append(classifier.get_transfer(trace))
return transfers return transfers

View File

@ -56,6 +56,8 @@ def make_swap_trace(
classification=Classification.swap, classification=Classification.swap,
from_address=from_address, from_address=from_address,
to_address=pool_address, to_address=pool_address,
function_name="swap",
function_signature="swap()",
inputs={recipient_input_key: recipient_address}, inputs={recipient_input_key: recipient_address},
abi_name=abi_name, abi_name=abi_name,
block_hash=str(block_number), block_hash=str(block_number),