Merge 4f2d178ddd2ba8158def2be3affe87d579bbcc5d into ce8179f07e4fb8740b43570aa2c5826447c2af26
This commit is contained in:
commit
30bb1ac402
43
alembic/versions/a46974a623b3_add_jit_liquidity_table.py
Normal file
43
alembic/versions/a46974a623b3_add_jit_liquidity_table.py
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
"""add_jit_liquidity_table
|
||||||
|
|
||||||
|
Revision ID: a46974a623b3
|
||||||
|
Revises: 5c5375de15fd
|
||||||
|
Create Date: 2022-05-10 12:36:57.139209
|
||||||
|
|
||||||
|
"""
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from alembic import op
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = "a46974a623b3"
|
||||||
|
down_revision = "5c5375de15fd"
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
op.create_table(
|
||||||
|
"jit_liquidity",
|
||||||
|
sa.Column("id", sa.String, primary_key=True),
|
||||||
|
sa.Column("block_number", sa.Numeric(), nullable=False),
|
||||||
|
sa.Column("bot_address", sa.String(42), nullable=True),
|
||||||
|
sa.Column("pool_address", sa.String(42), nullable=False),
|
||||||
|
sa.Column("token0_address", sa.String(42), nullable=True),
|
||||||
|
sa.Column("token1_address", sa.String(42), nullable=True),
|
||||||
|
sa.Column("mint_transaction_hash", sa.String(66), nullable=False),
|
||||||
|
sa.Column("mint_transaction_trace", sa.ARRAY(sa.Integer)),
|
||||||
|
sa.Column("burn_transaction_hash", sa.String(66), nullable=False),
|
||||||
|
sa.Column("burn_transaction_trace", sa.ARRAY(sa.Integer)),
|
||||||
|
sa.Column("mint_token0_amount", sa.Numeric),
|
||||||
|
sa.Column("mint_token1_amount", sa.Numeric),
|
||||||
|
sa.Column("burn_token0_amount", sa.Numeric),
|
||||||
|
sa.Column("burn_token1_amount", sa.Numeric),
|
||||||
|
sa.Column("token0_swap_volume", sa.Numeric),
|
||||||
|
sa.Column("token1_swap_volume", sa.Numeric),
|
||||||
|
)
|
||||||
|
op.create_index("ix_jit_liquidity_block_number", "jit_liquidity", ["block_number"])
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
op.drop_index("ix_jit_liquidity_block_number")
|
||||||
|
op.drop_table("jit_liquidity")
|
@ -0,0 +1,32 @@
|
|||||||
|
"""add_jit_liquidity_swaps_join_table
|
||||||
|
|
||||||
|
Revision ID: c77f5db6105e
|
||||||
|
Revises: a46974a623b3
|
||||||
|
Create Date: 2022-05-10 12:37:25.275799
|
||||||
|
|
||||||
|
"""
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from alembic import op
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = "c77f5db6105e"
|
||||||
|
down_revision = "a46974a623b3"
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
op.create_table(
|
||||||
|
"jit_liquidity_swaps",
|
||||||
|
sa.Column("created_at", sa.TIMESTAMP, server_default=sa.func.now()),
|
||||||
|
sa.Column("jit_liquidity_id", sa.String(1024), primary_key=True),
|
||||||
|
sa.Column("swap_transaction_hash", sa.String(66), primary_key=True),
|
||||||
|
sa.Column("swap_trace_address", sa.ARRAY(sa.Integer), primary_key=True),
|
||||||
|
sa.ForeignKeyConstraint(
|
||||||
|
["jit_liquidity_id"], ["jit_liquidity.id"], ondelete="CASCADE"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
op.drop_table("jit_liquidity_swaps")
|
@ -1,9 +1,9 @@
|
|||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
from mev_inspect.classifiers.helpers import create_swap_from_pool_transfers
|
from mev_inspect.classifiers.helpers import create_swap_from_pool_transfers
|
||||||
from mev_inspect.schemas.classifiers import ClassifierSpec, SwapClassifier
|
from mev_inspect.schemas.classifiers import Classifier, ClassifierSpec, SwapClassifier
|
||||||
from mev_inspect.schemas.swaps import Swap
|
from mev_inspect.schemas.swaps import Swap
|
||||||
from mev_inspect.schemas.traces import DecodedCallTrace, Protocol
|
from mev_inspect.schemas.traces import Classification, DecodedCallTrace, Protocol
|
||||||
from mev_inspect.schemas.transfers import Transfer
|
from mev_inspect.schemas.transfers import Transfer
|
||||||
|
|
||||||
UNISWAP_V2_PAIR_ABI_NAME = "UniswapV2Pair"
|
UNISWAP_V2_PAIR_ABI_NAME = "UniswapV2Pair"
|
||||||
@ -42,6 +42,18 @@ class UniswapV2SwapClassifier(SwapClassifier):
|
|||||||
return swap
|
return swap
|
||||||
|
|
||||||
|
|
||||||
|
class LiquidityMintClassifier(Classifier):
|
||||||
|
@staticmethod
|
||||||
|
def get_classification() -> Classification:
|
||||||
|
return Classification.liquidity_mint
|
||||||
|
|
||||||
|
|
||||||
|
class LiquidityBurnClassifier(Classifier):
|
||||||
|
@staticmethod
|
||||||
|
def get_classification() -> Classification:
|
||||||
|
return Classification.liquidity_burn
|
||||||
|
|
||||||
|
|
||||||
UNISWAP_V3_CONTRACT_SPECS = [
|
UNISWAP_V3_CONTRACT_SPECS = [
|
||||||
ClassifierSpec(
|
ClassifierSpec(
|
||||||
abi_name="UniswapV3Factory",
|
abi_name="UniswapV3Factory",
|
||||||
@ -106,6 +118,8 @@ UNISWAP_V3_GENERAL_SPECS = [
|
|||||||
protocol=Protocol.uniswap_v3,
|
protocol=Protocol.uniswap_v3,
|
||||||
classifiers={
|
classifiers={
|
||||||
"swap(address,bool,int256,uint160,bytes)": UniswapV3SwapClassifier,
|
"swap(address,bool,int256,uint160,bytes)": UniswapV3SwapClassifier,
|
||||||
|
"mint(address,int24,int24,uint128,bytes)": LiquidityMintClassifier,
|
||||||
|
"burn(int24,int24,uint128)": LiquidityBurnClassifier,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ClassifierSpec(
|
ClassifierSpec(
|
||||||
|
75
mev_inspect/crud/jit_liquidity.py
Normal file
75
mev_inspect/crud/jit_liquidity.py
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
from typing import List
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
|
from mev_inspect.models.jit_liquidity import JITLiquidityModel
|
||||||
|
from mev_inspect.schemas.jit_liquidity import JITLiquidity
|
||||||
|
|
||||||
|
from .shared import delete_by_block_range
|
||||||
|
|
||||||
|
|
||||||
|
def delete_jit_liquidity_for_blocks(
|
||||||
|
db_session,
|
||||||
|
after_block_number: int,
|
||||||
|
before_block_number: int,
|
||||||
|
) -> None:
|
||||||
|
delete_by_block_range(
|
||||||
|
db_session,
|
||||||
|
JITLiquidityModel,
|
||||||
|
after_block_number,
|
||||||
|
before_block_number,
|
||||||
|
)
|
||||||
|
db_session.commit()
|
||||||
|
|
||||||
|
|
||||||
|
def write_jit_liquidity(
|
||||||
|
db_session,
|
||||||
|
jit_liquidity_instances: List[JITLiquidity],
|
||||||
|
) -> None:
|
||||||
|
jit_liquidity_models = []
|
||||||
|
swap_jit_liquidity_ids = []
|
||||||
|
|
||||||
|
for jit_liquidity in jit_liquidity_instances:
|
||||||
|
jit_liquidity_id = str(uuid4())
|
||||||
|
jit_liquidity_models.append(
|
||||||
|
JITLiquidityModel(
|
||||||
|
id=jit_liquidity_id,
|
||||||
|
block_number=jit_liquidity.block_number,
|
||||||
|
bot_address=jit_liquidity.bot_address,
|
||||||
|
pool_address=jit_liquidity.pool_address,
|
||||||
|
token0_address=jit_liquidity.token0_address,
|
||||||
|
token1_address=jit_liquidity.token1_address,
|
||||||
|
mint_transaction_hash=jit_liquidity.mint_transaction_hash,
|
||||||
|
mint_transaction_trace=jit_liquidity.mint_trace,
|
||||||
|
burn_transaction_hash=jit_liquidity.burn_transaction_hash,
|
||||||
|
burn_transaction_trace=jit_liquidity.burn_trace,
|
||||||
|
mint_token0_amount=jit_liquidity.mint_token0_amount,
|
||||||
|
mint_token1_amount=jit_liquidity.mint_token1_amount,
|
||||||
|
burn_token0_amount=jit_liquidity.burn_token0_amount,
|
||||||
|
burn_token1_amount=jit_liquidity.burn_token1_amount,
|
||||||
|
token0_swap_volume=jit_liquidity.token0_swap_volume,
|
||||||
|
token1_swap_volume=jit_liquidity.token1_swap_volume,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
for swap in jit_liquidity.swaps:
|
||||||
|
swap_jit_liquidity_ids.append(
|
||||||
|
{
|
||||||
|
"jit_liquidity_id": jit_liquidity_id,
|
||||||
|
"swap_transaction_hash": swap.transaction_hash,
|
||||||
|
"swap_trace_address": swap.trace_address,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if len(jit_liquidity_models) > 0:
|
||||||
|
db_session.bulk_save_objects(jit_liquidity_models)
|
||||||
|
db_session.execute(
|
||||||
|
"""
|
||||||
|
INSERT INTO jit_liquidity_swaps
|
||||||
|
(jit_liquidity_id, swap_transaction_hash, swap_trace_address)
|
||||||
|
VALUES
|
||||||
|
(:jit_liquidity_id, :swap_transaction_hash, :swap_trace_address)
|
||||||
|
""",
|
||||||
|
params=swap_jit_liquidity_ids,
|
||||||
|
)
|
||||||
|
|
||||||
|
db_session.commit()
|
@ -9,6 +9,10 @@ from mev_inspect.block import create_from_block_number
|
|||||||
from mev_inspect.classifiers.trace import TraceClassifier
|
from mev_inspect.classifiers.trace import TraceClassifier
|
||||||
from mev_inspect.crud.arbitrages import delete_arbitrages_for_blocks, write_arbitrages
|
from mev_inspect.crud.arbitrages import delete_arbitrages_for_blocks, write_arbitrages
|
||||||
from mev_inspect.crud.blocks import delete_blocks, write_blocks
|
from mev_inspect.crud.blocks import delete_blocks, write_blocks
|
||||||
|
from mev_inspect.crud.jit_liquidity import (
|
||||||
|
delete_jit_liquidity_for_blocks,
|
||||||
|
write_jit_liquidity,
|
||||||
|
)
|
||||||
from mev_inspect.crud.liquidations import (
|
from mev_inspect.crud.liquidations import (
|
||||||
delete_liquidations_for_blocks,
|
delete_liquidations_for_blocks,
|
||||||
write_liquidations,
|
write_liquidations,
|
||||||
@ -34,6 +38,7 @@ from mev_inspect.crud.traces import (
|
|||||||
write_classified_traces,
|
write_classified_traces,
|
||||||
)
|
)
|
||||||
from mev_inspect.crud.transfers import delete_transfers_for_blocks, write_transfers
|
from mev_inspect.crud.transfers import delete_transfers_for_blocks, write_transfers
|
||||||
|
from mev_inspect.jit_liquidity import get_jit_liquidity
|
||||||
from mev_inspect.liquidations import get_liquidations
|
from mev_inspect.liquidations import get_liquidations
|
||||||
from mev_inspect.miner_payments import get_miner_payments
|
from mev_inspect.miner_payments import get_miner_payments
|
||||||
from mev_inspect.nft_trades import get_nft_trades
|
from mev_inspect.nft_trades import get_nft_trades
|
||||||
@ -41,6 +46,7 @@ from mev_inspect.punks import get_punk_bid_acceptances, get_punk_bids, get_punk_
|
|||||||
from mev_inspect.sandwiches import get_sandwiches
|
from mev_inspect.sandwiches import get_sandwiches
|
||||||
from mev_inspect.schemas.arbitrages import Arbitrage
|
from mev_inspect.schemas.arbitrages import Arbitrage
|
||||||
from mev_inspect.schemas.blocks import Block
|
from mev_inspect.schemas.blocks import Block
|
||||||
|
from mev_inspect.schemas.jit_liquidity import JITLiquidity
|
||||||
from mev_inspect.schemas.liquidations import Liquidation
|
from mev_inspect.schemas.liquidations import Liquidation
|
||||||
from mev_inspect.schemas.miner_payments import MinerPayment
|
from mev_inspect.schemas.miner_payments import MinerPayment
|
||||||
from mev_inspect.schemas.nft_trades import NftTrade
|
from mev_inspect.schemas.nft_trades import NftTrade
|
||||||
@ -100,6 +106,7 @@ async def inspect_many_blocks(
|
|||||||
all_miner_payments: List[MinerPayment] = []
|
all_miner_payments: List[MinerPayment] = []
|
||||||
|
|
||||||
all_nft_trades: List[NftTrade] = []
|
all_nft_trades: List[NftTrade] = []
|
||||||
|
all_jit_liquidity: List[JITLiquidity] = []
|
||||||
|
|
||||||
for block_number in range(after_block_number, before_block_number):
|
for block_number in range(after_block_number, before_block_number):
|
||||||
block = await create_from_block_number(
|
block = await create_from_block_number(
|
||||||
@ -149,6 +156,11 @@ async def inspect_many_blocks(
|
|||||||
nft_trades = get_nft_trades(classified_traces)
|
nft_trades = get_nft_trades(classified_traces)
|
||||||
logger.info(f"Block: {block_number} -- Found {len(nft_trades)} nft trades")
|
logger.info(f"Block: {block_number} -- Found {len(nft_trades)} nft trades")
|
||||||
|
|
||||||
|
jit_liquidity = get_jit_liquidity(classified_traces, swaps)
|
||||||
|
logger.info(
|
||||||
|
f"Block: {block_number} -- Found {len(jit_liquidity)} jit liquidity instances"
|
||||||
|
)
|
||||||
|
|
||||||
miner_payments = get_miner_payments(
|
miner_payments = get_miner_payments(
|
||||||
block.miner, block.base_fee_per_gas, classified_traces, block.receipts
|
block.miner, block.base_fee_per_gas, classified_traces, block.receipts
|
||||||
)
|
)
|
||||||
@ -167,6 +179,8 @@ async def inspect_many_blocks(
|
|||||||
|
|
||||||
all_nft_trades.extend(nft_trades)
|
all_nft_trades.extend(nft_trades)
|
||||||
|
|
||||||
|
all_jit_liquidity.extend(jit_liquidity)
|
||||||
|
|
||||||
all_miner_payments.extend(miner_payments)
|
all_miner_payments.extend(miner_payments)
|
||||||
|
|
||||||
logger.info("Writing data")
|
logger.info("Writing data")
|
||||||
@ -222,6 +236,11 @@ async def inspect_many_blocks(
|
|||||||
)
|
)
|
||||||
write_nft_trades(inspect_db_session, all_nft_trades)
|
write_nft_trades(inspect_db_session, all_nft_trades)
|
||||||
|
|
||||||
|
delete_jit_liquidity_for_blocks(
|
||||||
|
inspect_db_session, after_block_number, before_block_number
|
||||||
|
)
|
||||||
|
write_jit_liquidity(inspect_db_session, all_jit_liquidity)
|
||||||
|
|
||||||
delete_miner_payments_for_blocks(
|
delete_miner_payments_for_blocks(
|
||||||
inspect_db_session, after_block_number, before_block_number
|
inspect_db_session, after_block_number, before_block_number
|
||||||
)
|
)
|
||||||
|
276
mev_inspect/jit_liquidity.py
Normal file
276
mev_inspect/jit_liquidity.py
Normal file
@ -0,0 +1,276 @@
|
|||||||
|
from typing import List, Tuple
|
||||||
|
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from mev_inspect.schemas.jit_liquidity import JITLiquidity
|
||||||
|
from mev_inspect.schemas.swaps import Swap
|
||||||
|
from mev_inspect.schemas.traces import (
|
||||||
|
Classification,
|
||||||
|
ClassifiedTrace,
|
||||||
|
DecodedCallTrace,
|
||||||
|
Protocol,
|
||||||
|
)
|
||||||
|
from mev_inspect.schemas.transfers import Transfer
|
||||||
|
from mev_inspect.traces import get_traces_by_transaction_hash, is_child_trace_address
|
||||||
|
from mev_inspect.transfers import get_net_transfers
|
||||||
|
|
||||||
|
LIQUIDITY_MINT_ROUTERS = [
|
||||||
|
"0xC36442b4a4522E871399CD717aBDD847Ab11FE88".lower(), # Uniswap V3 NFT Position Manager
|
||||||
|
]
|
||||||
|
|
||||||
|
ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"
|
||||||
|
|
||||||
|
|
||||||
|
class JITTransferInfo(BaseModel):
|
||||||
|
token0_address: str
|
||||||
|
token1_address: str
|
||||||
|
mint_token0: int
|
||||||
|
mint_token1: int
|
||||||
|
burn_token0: int
|
||||||
|
burn_token1: int
|
||||||
|
error: bool
|
||||||
|
|
||||||
|
|
||||||
|
def get_jit_liquidity(
|
||||||
|
classified_traces: List[ClassifiedTrace], swaps: List[Swap]
|
||||||
|
) -> List[JITLiquidity]:
|
||||||
|
jit_liquidity_instances: List[JITLiquidity] = []
|
||||||
|
|
||||||
|
for index, trace in enumerate(classified_traces):
|
||||||
|
|
||||||
|
if not isinstance(trace, DecodedCallTrace):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if (
|
||||||
|
trace.classification == Classification.liquidity_mint
|
||||||
|
and trace.protocol == Protocol.uniswap_v3
|
||||||
|
):
|
||||||
|
for search_trace in classified_traces[index:]:
|
||||||
|
if (
|
||||||
|
search_trace.classification == Classification.liquidity_burn
|
||||||
|
and search_trace.to_address == trace.to_address
|
||||||
|
and search_trace.transaction_hash != trace.transaction_hash
|
||||||
|
):
|
||||||
|
if (search_trace.error is not None) or (trace.error is not None):
|
||||||
|
continue
|
||||||
|
|
||||||
|
bot_address = _get_bot_address(trace, classified_traces)
|
||||||
|
transfer_info: JITTransferInfo = _get_transfer_info(
|
||||||
|
classified_traces,
|
||||||
|
trace,
|
||||||
|
search_trace,
|
||||||
|
)
|
||||||
|
jit_swaps, token0_volume, token1_volume = _get_swap_info(
|
||||||
|
swaps, trace, search_trace, transfer_info.token0_address
|
||||||
|
)
|
||||||
|
|
||||||
|
# -- Error Checking Section --
|
||||||
|
if transfer_info.error or len(jit_swaps) == 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
jit_liquidity_instances.append(
|
||||||
|
JITLiquidity(
|
||||||
|
block_number=trace.block_number,
|
||||||
|
bot_address=bot_address,
|
||||||
|
pool_address=trace.to_address,
|
||||||
|
mint_transaction_hash=trace.transaction_hash,
|
||||||
|
mint_trace=trace.trace_address,
|
||||||
|
burn_transaction_hash=search_trace.transaction_hash,
|
||||||
|
burn_trace=search_trace.trace_address,
|
||||||
|
swaps=jit_swaps,
|
||||||
|
token0_address=transfer_info.token0_address,
|
||||||
|
token1_address=transfer_info.token1_address,
|
||||||
|
mint_token0_amount=transfer_info.mint_token0,
|
||||||
|
mint_token1_amount=transfer_info.mint_token1,
|
||||||
|
burn_token0_amount=transfer_info.burn_token0,
|
||||||
|
burn_token1_amount=transfer_info.burn_token1,
|
||||||
|
token0_swap_volume=token0_volume,
|
||||||
|
token1_swap_volume=token1_volume,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return jit_liquidity_instances
|
||||||
|
|
||||||
|
|
||||||
|
def _get_token_order(token_a: str, token_b: str) -> Tuple[str, str]:
|
||||||
|
token_order = True if int(token_a, 16) < int(token_b, 16) else False
|
||||||
|
return (token_a, token_b) if token_order else (token_b, token_a)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_swap_info(
|
||||||
|
swaps: List[Swap],
|
||||||
|
mint_trace: ClassifiedTrace,
|
||||||
|
burn_trace: ClassifiedTrace,
|
||||||
|
token0_address: str,
|
||||||
|
) -> Tuple[List[Swap], int, int]:
|
||||||
|
jit_swaps: List[Swap] = []
|
||||||
|
token0_swap_volume, token1_swap_volume = 0, 0
|
||||||
|
|
||||||
|
ordered_swaps = sorted(
|
||||||
|
swaps, key=lambda s: (s.transaction_position, s.trace_address)
|
||||||
|
)
|
||||||
|
|
||||||
|
for swap in ordered_swaps:
|
||||||
|
if swap.transaction_position <= mint_trace.transaction_position:
|
||||||
|
continue
|
||||||
|
if swap.transaction_position >= burn_trace.transaction_position:
|
||||||
|
break
|
||||||
|
if swap.contract_address == mint_trace.to_address:
|
||||||
|
jit_swaps.append(swap)
|
||||||
|
token0_swap_volume += (
|
||||||
|
swap.token_in_amount if swap.token_in_address == token0_address else 0
|
||||||
|
)
|
||||||
|
token1_swap_volume += (
|
||||||
|
0 if swap.token_in_address == token0_address else swap.token_in_amount
|
||||||
|
)
|
||||||
|
|
||||||
|
return jit_swaps, token0_swap_volume, token1_swap_volume
|
||||||
|
|
||||||
|
|
||||||
|
def _get_transfer_info(
|
||||||
|
classified_traces: List[ClassifiedTrace],
|
||||||
|
mint_trace: ClassifiedTrace,
|
||||||
|
burn_trace: ClassifiedTrace,
|
||||||
|
) -> JITTransferInfo:
|
||||||
|
|
||||||
|
grouped_traces = get_traces_by_transaction_hash(classified_traces)
|
||||||
|
mint_net_transfers, burn_net_transfers = [], []
|
||||||
|
pool_address = mint_trace.to_address
|
||||||
|
|
||||||
|
for transfer in get_net_transfers(grouped_traces[mint_trace.transaction_hash]):
|
||||||
|
if transfer.to_address == pool_address:
|
||||||
|
mint_net_transfers.append(transfer)
|
||||||
|
|
||||||
|
for transfer in get_net_transfers(grouped_traces[burn_trace.transaction_hash]):
|
||||||
|
if transfer.from_address == pool_address:
|
||||||
|
burn_net_transfers.append(transfer)
|
||||||
|
|
||||||
|
mint_len, burn_len = len(mint_net_transfers), len(burn_net_transfers)
|
||||||
|
|
||||||
|
if mint_len == 2 and burn_len == 2:
|
||||||
|
return _parse_standard_liquidity(mint_net_transfers, burn_net_transfers)
|
||||||
|
|
||||||
|
elif (mint_len == 2 and burn_len == 1) or (mint_len == 1 and burn_len == 2):
|
||||||
|
return _parse_liquidity_limit_order(mint_net_transfers, burn_net_transfers)
|
||||||
|
|
||||||
|
else:
|
||||||
|
return JITTransferInfo(
|
||||||
|
token0_address=ZERO_ADDRESS,
|
||||||
|
token1_address=ZERO_ADDRESS,
|
||||||
|
mint_token0=0,
|
||||||
|
mint_token1=0,
|
||||||
|
burn_token0=0,
|
||||||
|
burn_token1=0,
|
||||||
|
error=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_bot_address(
|
||||||
|
mint_trace: ClassifiedTrace,
|
||||||
|
classified_traces: List[ClassifiedTrace],
|
||||||
|
) -> str:
|
||||||
|
if "from_address" in mint_trace.dict().keys():
|
||||||
|
|
||||||
|
if mint_trace.from_address in LIQUIDITY_MINT_ROUTERS:
|
||||||
|
bot_trace = list(
|
||||||
|
filter(
|
||||||
|
lambda t: t.to_address == mint_trace.from_address
|
||||||
|
and t.transaction_hash == mint_trace.transaction_hash,
|
||||||
|
classified_traces,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if len(bot_trace) == 1 or is_child_trace_address(
|
||||||
|
bot_trace[1].trace_address, bot_trace[0].trace_address
|
||||||
|
):
|
||||||
|
return _get_bot_address(bot_trace[0], classified_traces)
|
||||||
|
else:
|
||||||
|
return ZERO_ADDRESS
|
||||||
|
|
||||||
|
elif type(mint_trace.from_address) == str:
|
||||||
|
return mint_trace.from_address
|
||||||
|
|
||||||
|
return ZERO_ADDRESS
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_standard_liquidity(
|
||||||
|
mint_net_transfers: List[Transfer],
|
||||||
|
burn_net_transfers: List[Transfer],
|
||||||
|
) -> JITTransferInfo:
|
||||||
|
token0_address, token1_address = _get_token_order(
|
||||||
|
mint_net_transfers[0].token_address, mint_net_transfers[1].token_address
|
||||||
|
)
|
||||||
|
|
||||||
|
mint_token0, mint_token1 = _parse_token_amounts(token0_address, mint_net_transfers)
|
||||||
|
|
||||||
|
burn_token0, burn_token1 = _parse_token_amounts(token0_address, burn_net_transfers)
|
||||||
|
return JITTransferInfo(
|
||||||
|
token0_address=token0_address,
|
||||||
|
token1_address=token1_address,
|
||||||
|
mint_token0=mint_token0,
|
||||||
|
mint_token1=mint_token1,
|
||||||
|
burn_token0=burn_token0,
|
||||||
|
burn_token1=burn_token1,
|
||||||
|
error=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_liquidity_limit_order(
|
||||||
|
mint_net_transfers: List[Transfer],
|
||||||
|
burn_net_transfers: List[Transfer],
|
||||||
|
) -> JITTransferInfo:
|
||||||
|
try:
|
||||||
|
token0_address, token1_address = _get_token_order(
|
||||||
|
burn_net_transfers[0].token_address, burn_net_transfers[1].token_address
|
||||||
|
)
|
||||||
|
except IndexError:
|
||||||
|
token0_address, token1_address = _get_token_order(
|
||||||
|
mint_net_transfers[0].token_address, mint_net_transfers[1].token_address
|
||||||
|
)
|
||||||
|
|
||||||
|
if len(mint_net_transfers) < 2:
|
||||||
|
if token0_address == mint_net_transfers[0].token_address:
|
||||||
|
mint_token0 = mint_net_transfers[0].amount
|
||||||
|
mint_token1 = 0
|
||||||
|
else:
|
||||||
|
mint_token0 = 0
|
||||||
|
mint_token1 = mint_net_transfers[0].amount
|
||||||
|
|
||||||
|
burn_token0, burn_token1 = _parse_token_amounts(
|
||||||
|
token0_address, burn_net_transfers
|
||||||
|
)
|
||||||
|
|
||||||
|
else:
|
||||||
|
if token0_address == burn_net_transfers[0].token_address:
|
||||||
|
burn_token0 = burn_net_transfers[0].amount
|
||||||
|
burn_token1 = 0
|
||||||
|
else:
|
||||||
|
burn_token0 = 0
|
||||||
|
burn_token1 = burn_net_transfers[0].amount
|
||||||
|
|
||||||
|
mint_token0, mint_token1 = _parse_token_amounts(
|
||||||
|
token0_address, mint_net_transfers
|
||||||
|
)
|
||||||
|
|
||||||
|
return JITTransferInfo(
|
||||||
|
token0_address=token0_address,
|
||||||
|
token1_address=token1_address,
|
||||||
|
mint_token0=mint_token0,
|
||||||
|
mint_token1=mint_token1,
|
||||||
|
burn_token0=burn_token0,
|
||||||
|
burn_token1=burn_token1,
|
||||||
|
error=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_token_amounts(
|
||||||
|
token0_address: str, net_transfers: List[Transfer]
|
||||||
|
) -> Tuple[int, int]:
|
||||||
|
if token0_address == net_transfers[0].token_address:
|
||||||
|
token0_amount = net_transfers[0].amount
|
||||||
|
token1_amount = net_transfers[1].amount
|
||||||
|
|
||||||
|
else:
|
||||||
|
token0_amount = net_transfers[1].amount
|
||||||
|
token1_amount = net_transfers[0].amount
|
||||||
|
|
||||||
|
return token0_amount, token1_amount
|
24
mev_inspect/models/jit_liquidity.py
Normal file
24
mev_inspect/models/jit_liquidity.py
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
from sqlalchemy import ARRAY, Column, Integer, Numeric, String
|
||||||
|
|
||||||
|
from .base import Base
|
||||||
|
|
||||||
|
|
||||||
|
class JITLiquidityModel(Base):
|
||||||
|
__tablename__ = "jit_liquidity"
|
||||||
|
|
||||||
|
id = Column(String, primary_key=True)
|
||||||
|
block_number = Column(Numeric(), nullable=False)
|
||||||
|
bot_address = Column(String(42), nullable=True)
|
||||||
|
pool_address = Column(String(42), nullable=False)
|
||||||
|
token0_address = Column(String(42), nullable=True)
|
||||||
|
token1_address = Column(String(42), nullable=True)
|
||||||
|
mint_transaction_hash = Column(String(66), nullable=False)
|
||||||
|
mint_transaction_trace = Column(ARRAY(Integer), nullable=False)
|
||||||
|
burn_transaction_hash = Column(String(66), nullable=False)
|
||||||
|
burn_transaction_trace = Column(ARRAY(Integer), nullable=False)
|
||||||
|
mint_token0_amount = Column(Numeric, nullable=False)
|
||||||
|
mint_token1_amount = Column(Numeric, nullable=False)
|
||||||
|
burn_token0_amount = Column(Numeric, nullable=False)
|
||||||
|
burn_token1_amount = Column(Numeric, nullable=False)
|
||||||
|
token0_swap_volume = Column(Numeric, nullable=True)
|
||||||
|
token1_swap_volume = Column(Numeric, nullable=True)
|
24
mev_inspect/schemas/jit_liquidity.py
Normal file
24
mev_inspect/schemas/jit_liquidity.py
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
from typing import List, Union
|
||||||
|
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from .swaps import Swap
|
||||||
|
|
||||||
|
|
||||||
|
class JITLiquidity(BaseModel):
|
||||||
|
block_number: int
|
||||||
|
bot_address: Union[str, None]
|
||||||
|
pool_address: str
|
||||||
|
mint_transaction_hash: str
|
||||||
|
mint_trace: List[int]
|
||||||
|
burn_transaction_hash: str
|
||||||
|
burn_trace: List[int]
|
||||||
|
swaps: List[Swap]
|
||||||
|
token0_address: str
|
||||||
|
token1_address: str
|
||||||
|
mint_token0_amount: int
|
||||||
|
mint_token1_amount: int
|
||||||
|
burn_token0_amount: int
|
||||||
|
burn_token1_amount: int
|
||||||
|
token0_swap_volume: int
|
||||||
|
token1_swap_volume: int
|
@ -34,6 +34,8 @@ class Classification(Enum):
|
|||||||
punk_bid = "punk_bid"
|
punk_bid = "punk_bid"
|
||||||
punk_accept_bid = "punk_accept_bid"
|
punk_accept_bid = "punk_accept_bid"
|
||||||
nft_trade = "nft_trade"
|
nft_trade = "nft_trade"
|
||||||
|
liquidity_mint = "liquidity_mint"
|
||||||
|
liquidity_burn = "liquidity_burn"
|
||||||
|
|
||||||
|
|
||||||
class Protocol(Enum):
|
class Protocol(Enum):
|
||||||
|
@ -3,7 +3,7 @@ from typing import Dict, List, Optional, Sequence
|
|||||||
from mev_inspect.classifiers.specs import get_classifier
|
from mev_inspect.classifiers.specs import get_classifier
|
||||||
from mev_inspect.schemas.classifiers import TransferClassifier
|
from mev_inspect.schemas.classifiers import TransferClassifier
|
||||||
from mev_inspect.schemas.prices import ETH_TOKEN_ADDRESS
|
from mev_inspect.schemas.prices import ETH_TOKEN_ADDRESS
|
||||||
from mev_inspect.schemas.traces import ClassifiedTrace, DecodedCallTrace
|
from mev_inspect.schemas.traces import Classification, ClassifiedTrace, DecodedCallTrace
|
||||||
from mev_inspect.schemas.transfers import Transfer
|
from mev_inspect.schemas.transfers import Transfer
|
||||||
from mev_inspect.traces import get_child_traces, is_child_trace_address
|
from mev_inspect.traces import get_child_traces, is_child_trace_address
|
||||||
|
|
||||||
@ -126,3 +126,100 @@ def remove_child_transfers_of_transfers(
|
|||||||
] = existing_addresses + [transfer.trace_address]
|
] = existing_addresses + [transfer.trace_address]
|
||||||
|
|
||||||
return updated_transfers
|
return updated_transfers
|
||||||
|
|
||||||
|
|
||||||
|
def get_net_transfers(
|
||||||
|
classified_traces: List[ClassifiedTrace],
|
||||||
|
) -> List[Transfer]:
|
||||||
|
"""
|
||||||
|
Returns the net transfers per transaction from a list of Classified Traces.
|
||||||
|
If A transfers 200WETH to B ,and later in the transaction, B transfers 50WETH to A,
|
||||||
|
the following transfer would be returned (from_address=A, to_address=B, amount=150)
|
||||||
|
|
||||||
|
If B transferred 300WETH to A, the following would be returned
|
||||||
|
(from_address=contract, to_address=bot, amount=100)
|
||||||
|
|
||||||
|
If B transferred 200WETH to A, no transfer would be returned
|
||||||
|
@param classified_traces:
|
||||||
|
@return: List of Transfer objects representing the net movement of tokens
|
||||||
|
"""
|
||||||
|
found_transfers: List[list] = []
|
||||||
|
return_transfers: List[Transfer] = []
|
||||||
|
for trace in classified_traces:
|
||||||
|
if not isinstance(trace, DecodedCallTrace):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if trace.classification == Classification.transfer:
|
||||||
|
if trace.from_address in [
|
||||||
|
t.token_address for t in return_transfers
|
||||||
|
]: # Proxy Case
|
||||||
|
continue
|
||||||
|
|
||||||
|
if trace.function_signature == "transfer(address,uint256)":
|
||||||
|
net_search_info = {
|
||||||
|
"to_address": trace.inputs["recipient"],
|
||||||
|
"token_address": trace.to_address,
|
||||||
|
"from_address": trace.from_address,
|
||||||
|
}
|
||||||
|
|
||||||
|
elif trace.function_signature == "transferFrom(address,address,uint256)":
|
||||||
|
net_search_info = {
|
||||||
|
"to_address": trace.inputs["recipient"],
|
||||||
|
"token_address": trace.to_address,
|
||||||
|
"from_address": trace.inputs["sender"],
|
||||||
|
}
|
||||||
|
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if sorted(list(net_search_info.values())) in found_transfers:
|
||||||
|
for index, transfer in enumerate(return_transfers):
|
||||||
|
if (
|
||||||
|
transfer.token_address != net_search_info["token_address"]
|
||||||
|
or transfer.transaction_hash != trace.transaction_hash
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if (
|
||||||
|
transfer.from_address == net_search_info["from_address"]
|
||||||
|
and transfer.to_address == net_search_info["to_address"]
|
||||||
|
):
|
||||||
|
return_transfers[index].amount += trace.inputs["amount"]
|
||||||
|
return_transfers[index].trace_address = [-1]
|
||||||
|
if (
|
||||||
|
transfer.from_address == net_search_info["to_address"]
|
||||||
|
and transfer.to_address == net_search_info["from_address"]
|
||||||
|
):
|
||||||
|
return_transfers[index].amount -= trace.inputs["amount"]
|
||||||
|
return_transfers[index].trace_address = [-1]
|
||||||
|
|
||||||
|
else:
|
||||||
|
return_transfers.append(
|
||||||
|
Transfer(
|
||||||
|
block_number=trace.block_number,
|
||||||
|
transaction_hash=trace.transaction_hash,
|
||||||
|
trace_address=trace.trace_address,
|
||||||
|
from_address=net_search_info["from_address"],
|
||||||
|
to_address=net_search_info["to_address"],
|
||||||
|
amount=trace.inputs["amount"],
|
||||||
|
token_address=net_search_info["token_address"],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
found_transfers.append(sorted(list(net_search_info.values())))
|
||||||
|
|
||||||
|
process_index = -1
|
||||||
|
while True:
|
||||||
|
process_index += 1
|
||||||
|
try:
|
||||||
|
transfer = return_transfers[process_index]
|
||||||
|
except IndexError:
|
||||||
|
break
|
||||||
|
if transfer.amount < 0:
|
||||||
|
return_transfers[process_index].from_address = transfer.to_address
|
||||||
|
return_transfers[process_index].to_address = transfer.from_address
|
||||||
|
return_transfers[process_index].amount = transfer.amount * -1
|
||||||
|
if transfer.amount == 0:
|
||||||
|
return_transfers.pop(process_index)
|
||||||
|
process_index -= 1
|
||||||
|
|
||||||
|
return return_transfers
|
||||||
|
1
tests/blocks/13601096.json
Normal file
1
tests/blocks/13601096.json
Normal file
File diff suppressed because one or more lines are too long
1
tests/blocks/14621812.json
Normal file
1
tests/blocks/14621812.json
Normal file
File diff suppressed because one or more lines are too long
1
tests/blocks/14643923.json
Normal file
1
tests/blocks/14643923.json
Normal file
File diff suppressed because one or more lines are too long
1
tests/blocks/14685550.json
Normal file
1
tests/blocks/14685550.json
Normal file
File diff suppressed because one or more lines are too long
188
tests/test_jit_liquidity.py
Normal file
188
tests/test_jit_liquidity.py
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
from mev_inspect.classifiers.trace import TraceClassifier
|
||||||
|
from mev_inspect.jit_liquidity import get_jit_liquidity
|
||||||
|
from mev_inspect.schemas.jit_liquidity import JITLiquidity
|
||||||
|
from mev_inspect.schemas.swaps import Swap
|
||||||
|
from mev_inspect.schemas.traces import Protocol
|
||||||
|
from mev_inspect.swaps import get_swaps
|
||||||
|
|
||||||
|
from .utils import load_test_block
|
||||||
|
|
||||||
|
|
||||||
|
def test_single_sandwich_jit_liquidity_WETH_USDC(trace_classifier: TraceClassifier):
|
||||||
|
test_block = load_test_block(13601096)
|
||||||
|
classified_traces = trace_classifier.classify(test_block.traces)
|
||||||
|
swaps = get_swaps(classified_traces)
|
||||||
|
jit_liquidity_instances = get_jit_liquidity(classified_traces, swaps)
|
||||||
|
|
||||||
|
jit_swap = Swap( # Double check these values
|
||||||
|
abi_name="UniswapV3Pool",
|
||||||
|
transaction_hash="0x943131400defa5db902b1df4ab5108b58527e525da3d507bd6e6465d88fa079c".lower(),
|
||||||
|
transaction_position=1,
|
||||||
|
block_number=13601096,
|
||||||
|
trace_address=[7, 0, 12, 1, 0],
|
||||||
|
contract_address="0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8".lower(),
|
||||||
|
from_address="0xAa6E8127831c9DE45ae56bB1b0d4D4Da6e5665BD".lower(),
|
||||||
|
to_address="0xAa6E8127831c9DE45ae56bB1b0d4D4Da6e5665BD".lower(),
|
||||||
|
token_in_address="0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48".lower(), # USDC Contract
|
||||||
|
token_in_amount=1896817745609,
|
||||||
|
token_out_address="0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2".lower(),
|
||||||
|
token_out_amount=408818202022592862626,
|
||||||
|
protocol=Protocol.uniswap_v3,
|
||||||
|
)
|
||||||
|
expected_jit_liquidity = [
|
||||||
|
JITLiquidity(
|
||||||
|
block_number=13601096,
|
||||||
|
bot_address="0xa57Bd00134B2850B2a1c55860c9e9ea100fDd6CF".lower(),
|
||||||
|
pool_address="0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8".lower(),
|
||||||
|
mint_transaction_hash="0x80e4abcb0b701e9d2c0d0fd216ef22eca5fc13904e8c7b3967bcad997480d638".lower(),
|
||||||
|
mint_trace=[0, 9, 1],
|
||||||
|
burn_transaction_hash="0x12b3d1f0e29d9093d8f3c7cce2da95edbef01aaab3794237f263da85c37c7d27".lower(),
|
||||||
|
burn_trace=[0, 1, 0],
|
||||||
|
swaps=[jit_swap],
|
||||||
|
token0_address="0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48".lower(),
|
||||||
|
token1_address="0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2".lower(),
|
||||||
|
mint_token0_amount=10864608891029,
|
||||||
|
mint_token1_amount=8281712219747858010668,
|
||||||
|
burn_token0_amount=12634177387879,
|
||||||
|
burn_token1_amount=7900319851971188832064,
|
||||||
|
token0_swap_volume=1896817745609,
|
||||||
|
token1_swap_volume=0,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
assert expected_jit_liquidity == jit_liquidity_instances
|
||||||
|
|
||||||
|
|
||||||
|
def test_single_sandwich_jit_liquidity_CRV_WETH(trace_classifier: TraceClassifier):
|
||||||
|
test_block = load_test_block(14621812)
|
||||||
|
classified_traces = trace_classifier.classify(test_block.traces)
|
||||||
|
swaps = get_swaps(classified_traces)
|
||||||
|
jit_liquidity_instances = get_jit_liquidity(classified_traces, swaps)
|
||||||
|
|
||||||
|
jit_swap = Swap( # Double check these values
|
||||||
|
abi_name="UniswapV3Pool",
|
||||||
|
transaction_hash="0x37e84f64698fe1a4852370b4d043491d5f96078d7c69e52f973932bc15ce8617".lower(),
|
||||||
|
transaction_position=5,
|
||||||
|
block_number=14621812,
|
||||||
|
trace_address=[0, 1],
|
||||||
|
contract_address="0x4c83A7f819A5c37D64B4c5A2f8238Ea082fA1f4e".lower(),
|
||||||
|
from_address="0x68b3465833fb72a70ecdf485e0e4c7bd8665fc45".lower(),
|
||||||
|
to_address="0x1d9d04bf507b86fea6c13a412f3bff40eeb64e96".lower(),
|
||||||
|
token_in_address="0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2".lower(), # USDC Contract
|
||||||
|
token_in_amount=6206673612383009024,
|
||||||
|
token_out_address="0xD533a949740bb3306d119CC777fa900bA034cd52".lower(),
|
||||||
|
token_out_amount=8111771836975942396605,
|
||||||
|
protocol=Protocol.uniswap_v3,
|
||||||
|
)
|
||||||
|
expected_jit_liquidity = [
|
||||||
|
JITLiquidity(
|
||||||
|
block_number=14621812,
|
||||||
|
bot_address="0xa57Bd00134B2850B2a1c55860c9e9ea100fDd6CF".lower(),
|
||||||
|
pool_address="0x4c83A7f819A5c37D64B4c5A2f8238Ea082fA1f4e".lower(),
|
||||||
|
mint_transaction_hash="0xdcb5eac97a6bcade485ee3dc8be0f7d8722e6ebacb3910fb31dea30ff40e694e".lower(),
|
||||||
|
mint_trace=[0, 9, 1],
|
||||||
|
burn_transaction_hash="0x499021c4f87facfffc08d143539019d8638c924a4786de15be99567ab6026b98".lower(),
|
||||||
|
burn_trace=[0, 1, 0],
|
||||||
|
swaps=[jit_swap],
|
||||||
|
token0_address="0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2".lower(),
|
||||||
|
token1_address="0xD533a949740bb3306d119CC777fa900bA034cd52".lower(),
|
||||||
|
mint_token0_amount=324305525132652136497,
|
||||||
|
mint_token1_amount=182991368595201974557004,
|
||||||
|
burn_token0_amount=330104892856183548121,
|
||||||
|
burn_token1_amount=175411922548908668697796,
|
||||||
|
token0_swap_volume=6206673612383009024,
|
||||||
|
token1_swap_volume=0,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
assert jit_liquidity_instances == expected_jit_liquidity
|
||||||
|
|
||||||
|
|
||||||
|
def test_single_mint_token_WETH_APE(trace_classifier):
|
||||||
|
test_block = load_test_block(14643923)
|
||||||
|
classified_traces = trace_classifier.classify(test_block.traces)
|
||||||
|
swaps = get_swaps(classified_traces)
|
||||||
|
jit_liquidity_instances = get_jit_liquidity(classified_traces, swaps)
|
||||||
|
|
||||||
|
jit_swap = Swap( # Double check these values
|
||||||
|
abi_name="UniswapV3Pool",
|
||||||
|
transaction_hash="0x43f9656e051a8e3b37f66668851922c6e8e4749d5a7aad605f21119cde541e49".lower(),
|
||||||
|
transaction_position=4,
|
||||||
|
block_number=14643923,
|
||||||
|
trace_address=[1, 0, 1, 0, 1, 0, 0],
|
||||||
|
contract_address="0xac4b3dacb91461209ae9d41ec517c2b9cb1b7daf".lower(),
|
||||||
|
from_address="0x74de5d4fcbf63e00296fd95d33236b9794016631".lower(),
|
||||||
|
to_address="0xdef1c0ded9bec7f1a1670819833240f027b25eff".lower(),
|
||||||
|
token_in_address="0x4d224452801aced8b2f0aebe155379bb5d594381".lower(), # USDC Contract
|
||||||
|
token_in_amount=6522531010660457256888,
|
||||||
|
token_out_address="0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2".lower(),
|
||||||
|
token_out_amount=36485453136086109896,
|
||||||
|
protocol=Protocol.uniswap_v3,
|
||||||
|
)
|
||||||
|
expected_jit_liquidity = [
|
||||||
|
JITLiquidity(
|
||||||
|
block_number=14643923,
|
||||||
|
bot_address="0xa57Bd00134B2850B2a1c55860c9e9ea100fDd6CF".lower(),
|
||||||
|
pool_address="0xac4b3dacb91461209ae9d41ec517c2b9cb1b7daf".lower(),
|
||||||
|
mint_transaction_hash="0x003e36cb5d78924c5beaeef15db00cad94009856fe483a031d52ae975557ef53".lower(),
|
||||||
|
mint_trace=[0, 7, 1],
|
||||||
|
burn_transaction_hash="0xec9b2988f6c88968250c3904f6d2d6573f7284cb422b8022a14b7f0dac546348".lower(),
|
||||||
|
burn_trace=[0, 1, 0],
|
||||||
|
swaps=[jit_swap],
|
||||||
|
token0_address="0x4d224452801aced8b2f0aebe155379bb5d594381".lower(),
|
||||||
|
token1_address="0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2".lower(),
|
||||||
|
mint_token0_amount=0,
|
||||||
|
mint_token1_amount=9073930631365320229693,
|
||||||
|
burn_token0_amount=2424427669988518000798,
|
||||||
|
burn_token1_amount=9060377725722224517671,
|
||||||
|
token0_swap_volume=6522531010660457256888,
|
||||||
|
token1_swap_volume=0,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
assert jit_liquidity_instances == expected_jit_liquidity
|
||||||
|
|
||||||
|
|
||||||
|
def test_single_mint_token_jit_ENS_WETH(trace_classifier):
|
||||||
|
test_block = load_test_block(14685550)
|
||||||
|
classified_traces = trace_classifier.classify(test_block.traces)
|
||||||
|
swaps = get_swaps(classified_traces)
|
||||||
|
jit_liquidity_instances = get_jit_liquidity(classified_traces, swaps)
|
||||||
|
|
||||||
|
jit_swap = Swap( # Double check these values
|
||||||
|
abi_name="UniswapV3Pool",
|
||||||
|
transaction_hash="0xeb9dad13e389ee87d656e9d2ca127061a430b9ccb2dd903a840737c979459aa3".lower(),
|
||||||
|
transaction_position=2,
|
||||||
|
block_number=14685550,
|
||||||
|
trace_address=[17],
|
||||||
|
contract_address="0x92560c178ce069cc014138ed3c2f5221ba71f58a".lower(),
|
||||||
|
from_address="0x36e9b6e7fadc7b8ee289c8a24ad96573cda3d7d9".lower(),
|
||||||
|
to_address="0x36e9b6e7fadc7b8ee289c8a24ad96573cda3d7d9".lower(),
|
||||||
|
token_in_address="0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2".lower(), # USDC Contract
|
||||||
|
token_in_amount=25467887766287027275,
|
||||||
|
token_out_address="0xc18360217d8f7ab5e7c516566761ea12ce7f9d72".lower(),
|
||||||
|
token_out_amount=3577729807778124677068,
|
||||||
|
protocol=Protocol.uniswap_v3,
|
||||||
|
)
|
||||||
|
expected_jit_liquidity = [
|
||||||
|
JITLiquidity(
|
||||||
|
block_number=14685550,
|
||||||
|
bot_address="0xa57Bd00134B2850B2a1c55860c9e9ea100fDd6CF".lower(),
|
||||||
|
pool_address="0x92560c178ce069cc014138ed3c2f5221ba71f58a".lower(),
|
||||||
|
mint_transaction_hash="0x1af86b40349a9fdaab5b1290d8fba532c2eefdd13d0ed22e825a45e437a000a4".lower(),
|
||||||
|
mint_trace=[0, 7, 1],
|
||||||
|
burn_transaction_hash="0x3265ce7a2d2c6ca796a87c4904f67324715a9381d6d2200690bfa30c55f238fb".lower(),
|
||||||
|
burn_trace=[0, 1, 0],
|
||||||
|
swaps=[jit_swap],
|
||||||
|
token0_address="0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2".lower(),
|
||||||
|
token1_address="0xc18360217d8f7ab5e7c516566761ea12ce7f9d72".lower(),
|
||||||
|
mint_token0_amount=0,
|
||||||
|
mint_token1_amount=2928204597556117752715,
|
||||||
|
burn_token0_amount=17321179792304275130,
|
||||||
|
burn_token1_amount=496888833716052284320,
|
||||||
|
token0_swap_volume=25467887766287027275,
|
||||||
|
token1_swap_volume=0,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
assert jit_liquidity_instances == expected_jit_liquidity
|
@ -1,5 +1,6 @@
|
|||||||
|
from mev_inspect.schemas.traces import Classification, DecodedCallTrace, TraceType
|
||||||
from mev_inspect.schemas.transfers import Transfer
|
from mev_inspect.schemas.transfers import Transfer
|
||||||
from mev_inspect.transfers import remove_child_transfers_of_transfers
|
from mev_inspect.transfers import get_net_transfers, remove_child_transfers_of_transfers
|
||||||
|
|
||||||
|
|
||||||
def test_remove_child_transfers_of_transfers(get_transaction_hashes, get_addresses):
|
def test_remove_child_transfers_of_transfers(get_transaction_hashes, get_addresses):
|
||||||
@ -67,6 +68,59 @@ def test_remove_child_transfers_of_transfers(get_transaction_hashes, get_address
|
|||||||
assert _equal_ignoring_order(removed_transfers, expected_transfers)
|
assert _equal_ignoring_order(removed_transfers, expected_transfers)
|
||||||
|
|
||||||
|
|
||||||
|
def test_net_transfers_same_token(get_addresses):
|
||||||
|
|
||||||
|
alice_address, bob_address, token_address = get_addresses(3)
|
||||||
|
transfer_alice_to_bob = DecodedCallTrace(
|
||||||
|
block_number=123,
|
||||||
|
transaction_hash="net_transfer_tx_hash",
|
||||||
|
block_hash="block_hash",
|
||||||
|
transaction_position=123,
|
||||||
|
type=TraceType.call,
|
||||||
|
action={},
|
||||||
|
functionName="transfer",
|
||||||
|
abiName="UniswapV3Pool",
|
||||||
|
subtraces=123,
|
||||||
|
trace_address=[0],
|
||||||
|
classification=Classification.transfer,
|
||||||
|
function_signature="transfer(address,uint256)",
|
||||||
|
from_address=alice_address,
|
||||||
|
to_address=token_address,
|
||||||
|
inputs={"recipient": bob_address, "amount": 700},
|
||||||
|
)
|
||||||
|
transfer_bob_to_alice = DecodedCallTrace(
|
||||||
|
block_number=123,
|
||||||
|
transaction_hash="net_transfer_tx_hash",
|
||||||
|
block_hash="block_hash",
|
||||||
|
transaction_position=123,
|
||||||
|
type=TraceType.call,
|
||||||
|
action={},
|
||||||
|
functionName="transfer",
|
||||||
|
abiName="UniswapV3Pool",
|
||||||
|
subtraces=123,
|
||||||
|
trace_address=[3],
|
||||||
|
classification=Classification.transfer,
|
||||||
|
function_signature="transfer(address,uint256)",
|
||||||
|
from_address=bob_address,
|
||||||
|
to_address=token_address,
|
||||||
|
inputs={"recipient": alice_address, "amount": 200},
|
||||||
|
)
|
||||||
|
|
||||||
|
expected_transfer = Transfer(
|
||||||
|
block_number=123,
|
||||||
|
transaction_hash="net_transfer_tx_hash",
|
||||||
|
trace_address=[-1],
|
||||||
|
from_address=alice_address,
|
||||||
|
to_address=bob_address,
|
||||||
|
amount=500,
|
||||||
|
token_address=token_address,
|
||||||
|
)
|
||||||
|
|
||||||
|
net_transfer = get_net_transfers([transfer_alice_to_bob, transfer_bob_to_alice])
|
||||||
|
|
||||||
|
assert expected_transfer == net_transfer[0]
|
||||||
|
|
||||||
|
|
||||||
def _equal_ignoring_order(first_list, second_list) -> bool:
|
def _equal_ignoring_order(first_list, second_list) -> bool:
|
||||||
return all(first in second_list for first in first_list) and all(
|
return all(first in second_list for first in first_list) and all(
|
||||||
second in first_list for second in second_list
|
second in first_list for second in second_list
|
||||||
|
Loading…
x
Reference in New Issue
Block a user