240 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			240 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
from typing import List, Optional
 | 
						|
 | 
						|
from mev_inspect.schemas.blocks import Block
 | 
						|
from mev_inspect.schemas.traces import Trace, TraceType
 | 
						|
 | 
						|
weth_address = "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"
 | 
						|
 | 
						|
cache_directory = "./cache"
 | 
						|
 | 
						|
 | 
						|
def is_stablecoin_address(address):
 | 
						|
    # to look for stablecoin inflow/outflows
 | 
						|
    stablecoin_addresses = [
 | 
						|
        "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",  # USDC
 | 
						|
        "0xdac17f958d2ee523a2206206994597c13d831ec7",  # USDT
 | 
						|
        "0x6b175474e89094c44da98b954eedeac495271d0f",  # DAI
 | 
						|
        "0x0000000000085d4780b73119b644ae5ecd22b376",  # TUSD
 | 
						|
        "0x4fabb145d64652a948d72533023f6e7a623c7c53",  # BUSD
 | 
						|
        "0x8e870d67f660d95d5be530380d0ec0bd388289e1",  # PAX
 | 
						|
        "0x956F47F50A910163D8BF957Cf5846D573E7f87CA",  # FEI
 | 
						|
        "0x853d955aCEf822Db058eb8505911ED77F175b99e",  # FRAX
 | 
						|
        "0xBC6DA0FE9aD5f3b0d58160288917AA56653660E9",  # alUSD
 | 
						|
        "0x57Ab1ec28D129707052df4dF418D58a2D46d5f51",  # sUSD
 | 
						|
        "0x5f98805A4E8be255a32880FDeC7F6728C6568bA0",  # lUSD
 | 
						|
        "0x674C6Ad92Fd080e4004b2312b45f796a192D27a0",  # USDN
 | 
						|
    ]
 | 
						|
    return address in stablecoin_addresses
 | 
						|
 | 
						|
 | 
						|
def is_known_router_address(address):
 | 
						|
    # to exclude known router addresses from token flow analysis
 | 
						|
    known_router_addresses = [
 | 
						|
        "0x3D71d79C224998E608d03C5Ec9B405E7a38505F0",  # keeper dao, whitelists extraction
 | 
						|
        "0x11111254369792b2Ca5d084aB5eEA397cA8fa48B",  # 1inch v1 router
 | 
						|
        "0x111111125434b319222cdbf8c261674adb56f3ae",  # 1inch v2 router
 | 
						|
        "0x11111112542d85b3ef69ae05771c2dccff4faa26",  # 1inch v3 router
 | 
						|
        "0xa356867fdcea8e71aeaf87805808803806231fdc",  # DODO
 | 
						|
        "0xdef1c0ded9bec7f1a1670819833240f027b25eff",  # 0x proxy
 | 
						|
        "0x90f765f63e7dc5ae97d6c576bf693fb6af41c129",  # Set Trade
 | 
						|
        "0x7113dd99c79aff93d54cfa4b2885576535a132de",  # Totle exchange
 | 
						|
        "0x9509665d015bfe3c77aa5ad6ca20c8afa1d98989",  # Paraswap
 | 
						|
        "0x86969d29F5fd327E1009bA66072BE22DB6017cC6",  # Paraswap v2
 | 
						|
        "0xf90e98f3d8dce44632e5020abf2e122e0f99dfab",  # Paraswap v3
 | 
						|
        "0x57805e5a227937bac2b0fdacaa30413ddac6b8e1",  # Furucombo
 | 
						|
        "0x17e8ca1b4798b97602895f63206afcd1fc90ca5f",  # Furucombo proxy
 | 
						|
        "0x881d40237659c251811cec9c364ef91dc08d300c",  # Metamask swap
 | 
						|
        "0x745daa146934b27e3f0b6bff1a6e36b9b90fb131",  # DEX.ag
 | 
						|
        "0xb2be281e8b11b47fec825973fc8bb95332022a54",  # Zerion SDK
 | 
						|
        "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D",  # UniswapV2Router02
 | 
						|
        "0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F",  # SushiswapV2Router02
 | 
						|
        "0xE592427A0AEce92De3Edee1F18E0157C05861564",  # Uniswap v3 router
 | 
						|
        "0x3E66B66Fd1d0b02fDa6C811Da9E0547970DB2f21",  # Balance exchange proxy
 | 
						|
        "0x1bD435F3C054b6e901B7b108a0ab7617C808677b",  # Paraswap v4
 | 
						|
        "0xC011a73ee8576Fb46F5E1c5751cA3B9Fe0af2a6F",  # SNX proxy synth issuer
 | 
						|
    ]
 | 
						|
    return address in known_router_addresses
 | 
						|
 | 
						|
 | 
						|
# we're interested in the to address to run token flow on it as well
 | 
						|
def get_tx_to_address(tx_hash, block) -> Optional[str]:
 | 
						|
    for receipt in block.receipts:
 | 
						|
        if receipt.transaction_hash == tx_hash:
 | 
						|
            return receipt.to
 | 
						|
 | 
						|
    return None
 | 
						|
 | 
						|
 | 
						|
def get_tx_proxies(tx_traces: List[Trace], to_address: Optional[str]):
 | 
						|
    proxies = []
 | 
						|
 | 
						|
    for trace in tx_traces:
 | 
						|
        if (
 | 
						|
            trace.type == TraceType.call
 | 
						|
            and trace.action["callType"] == "delegatecall"
 | 
						|
            and trace.action["from"] == to_address
 | 
						|
        ):
 | 
						|
            proxies.append(trace.action["to"])
 | 
						|
 | 
						|
    return proxies
 | 
						|
 | 
						|
 | 
						|
def get_net_gas_used(tx_hash, block):
 | 
						|
    gas_used = 0
 | 
						|
    for trace in block.traces:
 | 
						|
        if trace.transaction_hash == tx_hash:
 | 
						|
            gas_used += int(trace.result["gasUsed"], 16)
 | 
						|
 | 
						|
    return gas_used
 | 
						|
 | 
						|
 | 
						|
def get_ether_flows(tx_traces, addresses_to_check):
 | 
						|
    eth_inflow = 0
 | 
						|
    eth_outflow = 0
 | 
						|
 | 
						|
    for trace in tx_traces:
 | 
						|
        if trace.type == TraceType.call:
 | 
						|
            value = int(
 | 
						|
                trace.action["value"], 16
 | 
						|
            )  # converting from 0x prefix to decimal
 | 
						|
            # ETH_GET
 | 
						|
            if (
 | 
						|
                trace.action["callType"] != "delegatecall"
 | 
						|
                and trace.action["from"] != weth_address
 | 
						|
                and value > 0
 | 
						|
                and trace.action["to"] in addresses_to_check
 | 
						|
            ):
 | 
						|
                eth_inflow = eth_inflow + value
 | 
						|
 | 
						|
            # ETH_GIVE
 | 
						|
            if (
 | 
						|
                trace.action["callType"] != "delegatecall"
 | 
						|
                and trace.action["to"] != weth_address
 | 
						|
                and value > 0
 | 
						|
                and trace.action["from"] in addresses_to_check
 | 
						|
            ):
 | 
						|
                eth_outflow = eth_outflow + value
 | 
						|
 | 
						|
            if trace.action["to"] == weth_address:
 | 
						|
                # WETH_GET1 & WETH_GET2 (to account for both 'transfer' and 'transferFrom' methods)
 | 
						|
                # WETH_GIVE1 & WETH_GIVE2
 | 
						|
 | 
						|
                # transfer(address to,uint256 value) with args
 | 
						|
                if len(trace.action["input"]) == 138:
 | 
						|
                    if trace.action["input"][2:10] == "a9059cbb":
 | 
						|
                        transfer_to = "0x" + trace.action["input"][34:74]
 | 
						|
                        transfer_value = int("0x" + trace.action["input"][74:138], 16)
 | 
						|
                        if transfer_to in addresses_to_check:
 | 
						|
                            eth_inflow = eth_inflow + transfer_value
 | 
						|
                        elif trace.action["from"] in addresses_to_check:
 | 
						|
                            eth_outflow = eth_outflow + transfer_value
 | 
						|
 | 
						|
                # transferFrom(address from,address to,uint256 value )
 | 
						|
                if len(trace.action["input"]) == 202:
 | 
						|
                    if trace.action["input"][2:10] == "23b872dd":
 | 
						|
                        transfer_from = "0x" + trace.action["input"][34:74]
 | 
						|
                        transfer_to = "0x" + trace.action["input"][98:138]
 | 
						|
                        transfer_value = int("0x" + trace.action["input"][138:202], 16)
 | 
						|
                        if transfer_to in addresses_to_check:
 | 
						|
                            eth_inflow = eth_inflow + transfer_value
 | 
						|
                        elif transfer_from in addresses_to_check:
 | 
						|
                            eth_outflow = eth_outflow + transfer_value
 | 
						|
 | 
						|
        if trace.type == TraceType.suicide:
 | 
						|
            if trace.action["refundAddress"] in addresses_to_check:
 | 
						|
                refund_value = int("0x" + trace.action["balance"], 16)
 | 
						|
                eth_inflow = eth_inflow + refund_value
 | 
						|
 | 
						|
    return [eth_inflow, eth_outflow]
 | 
						|
 | 
						|
 | 
						|
def get_dollar_flows(tx_traces, addresses_to_check):
 | 
						|
    dollar_inflow = 0
 | 
						|
    dollar_outflow = 0
 | 
						|
    for trace in tx_traces:
 | 
						|
        if trace.type == TraceType.call and is_stablecoin_address(trace.action["to"]):
 | 
						|
            _ = int(trace.action["value"], 16)  # converting from 0x prefix to decimal
 | 
						|
 | 
						|
            # USD_GET1 & USD_GET2 (to account for both 'transfer' and 'transferFrom' methods)
 | 
						|
            # USD_GIVE1 & USD_GIVE2
 | 
						|
 | 
						|
            # transfer(address to,uint256 value) with args
 | 
						|
            if len(trace.action["input"]) == 138:
 | 
						|
                if trace.action["input"][2:10] == "a9059cbb":
 | 
						|
                    transfer_to = "0x" + trace.action["input"][34:74]
 | 
						|
                    transfer_value = int("0x" + trace.action["input"][74:138], 16)
 | 
						|
                    if transfer_to in addresses_to_check:
 | 
						|
                        dollar_inflow = dollar_inflow + transfer_value
 | 
						|
                    elif trace.action["from"] in addresses_to_check:
 | 
						|
                        dollar_outflow = dollar_outflow + transfer_value
 | 
						|
 | 
						|
            # transferFrom(address from,address to,uint256 value )
 | 
						|
            if len(trace.action["input"]) == 202:
 | 
						|
                if trace.action["input"][2:10] == "23b872dd":
 | 
						|
                    transfer_from = "0x" + trace.action["input"][34:74]
 | 
						|
                    transfer_to = "0x" + trace.action["input"][98:138]
 | 
						|
                    transfer_value = int("0x" + trace.action["input"][138:202], 16)
 | 
						|
                    if transfer_to in addresses_to_check:
 | 
						|
                        dollar_inflow = dollar_inflow + transfer_value
 | 
						|
                    elif transfer_from in addresses_to_check:
 | 
						|
                        dollar_outflow = dollar_outflow + transfer_value
 | 
						|
    return [dollar_inflow, dollar_outflow]
 | 
						|
 | 
						|
 | 
						|
def run_tokenflow(tx_hash: str, block: Block):
 | 
						|
    tx_traces = block.get_filtered_traces(tx_hash)
 | 
						|
    to_address = get_tx_to_address(tx_hash, block)
 | 
						|
 | 
						|
    if to_address is None:
 | 
						|
        raise ValueError("No to address found")
 | 
						|
 | 
						|
    addresses_to_check = []
 | 
						|
 | 
						|
    # check for proxies, add them to addresses to check
 | 
						|
    proxies = get_tx_proxies(tx_traces, to_address)
 | 
						|
 | 
						|
    for proxy in proxies:
 | 
						|
        addresses_to_check.append(proxy.lower())
 | 
						|
 | 
						|
    # check if the 'to' field is a known aggregator/router
 | 
						|
    # if not, add to relevant addresses to run TF on
 | 
						|
    if not is_known_router_address(to_address):
 | 
						|
        addresses_to_check.append(
 | 
						|
            to_address.lower()
 | 
						|
        )  # traces need lowercase addresses to match
 | 
						|
 | 
						|
    ether_flows = get_ether_flows(tx_traces, addresses_to_check)
 | 
						|
    dollar_flows = get_dollar_flows(tx_traces, addresses_to_check)
 | 
						|
    # print(addresses_to_check)
 | 
						|
    # print('net eth flow', ether_flows[0] - ether_flows[1])
 | 
						|
    # print('net dollar flow', dollar_flows )
 | 
						|
    return {"ether_flows": ether_flows, "dollar_flows": dollar_flows}
 | 
						|
 | 
						|
 | 
						|
# note: not the gas set by user, only gas consumed upon execution
 | 
						|
# def get_gas_used_by_tx(tx_hash):
 | 
						|
#     # tx_receipt = w3.eth.getTransactionReceipt(tx_hash)
 | 
						|
#     return tx_receipt["gasUsed"]
 | 
						|
 | 
						|
 | 
						|
# tx_traces = get_tx_traces('0x4121ce805d33e952b2e6103a5024f70c118432fd0370128d6d7845f9b2987922', 11930296)
 | 
						|
# print(tx_traces)
 | 
						|
 | 
						|
# print(type(known_router_addresses))
 | 
						|
# print(is_stablecoin_address("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"))
 | 
						|
 | 
						|
# run_tokenflow("0x4121ce805d33e952b2e6103a5024f70c118432fd0370128d6d7845f9b2987922", 11930296)
 | 
						|
 | 
						|
# delegate call test
 | 
						|
# run_tokenflow("0x9007b339c81de366cd53539edc15c86ffc87542c65f374c0d4d1f8823a3ccf60", 12051659)
 | 
						|
 | 
						|
# stable flow test
 | 
						|
# res  = run_tokenflow("0x496836e0bd1520388e36c79d587a31d4b3306e4f25352164178ca0667c7f9c29", 11935012)
 | 
						|
# print(res)
 | 
						|
 | 
						|
# complex arb test
 | 
						|
# res = run_tokenflow("0x5ab21bfba50ad3993528c2828c63e311aafe93b40ee934790e545e150cb6ca73", 11931272)
 | 
						|
# print(res)
 | 
						|
 | 
						|
# get_gas_used_by_tx("0x4121ce805d33e952b2e6103a5024f70c118432fd0370128d6d7845f9b2987922")
 |