Compare commits

...

49 Commits

Author SHA1 Message Date
Taarush Vemulapalli
a464a0cccc has_liquidations helper 2021-10-16 13:11:27 -04:00
Taarush Vemulapalli
34d0afdd9c fixes for WETH/underlying_markets 2021-10-16 12:10:40 -04:00
Taarush Vemulapalli
9ec8b2ab2f Support for Cream markets + test 2021-10-14 15:12:06 -07:00
Luke Van Seters
24951891ca Merge pull request #101 from flashbots/mev-backfill
Add `mev` command for easy inspect use
2021-10-13 17:24:07 -04:00
Luke Van Seters
2e921f2685 Add db command. Update Tiltfile / app to store DB host in secrets 2021-10-13 14:55:53 -04:00
Luke Van Seters
0ddb0104af Add some echos. Add backfill 2021-10-13 14:21:30 -04:00
Luke Van Seters
7d66bce9ee Add mev script 2021-10-13 14:14:10 -04:00
Luke Van Seters
561f8c3450 new script who dis 2021-10-13 14:07:50 -04:00
Luke Van Seters
e785dd0b25 Merge pull request #96 from flashbots/eth-transfers-eeee
Remove ETH / ERC20 transfer distinction
2021-10-13 10:24:36 -04:00
Taarush Vemulapalli
ed83b49091 Compound
* compound v2 + tests
2021-10-13 07:19:52 -07:00
Luke Van Seters
2a852746fe Only get eth transfer if only transfering eth 2021-10-12 20:17:50 -04:00
Luke Van Seters
3950a9c809 Handle ETH transfers in swaps 2021-10-12 20:13:59 -04:00
Luke Van Seters
6de8f494c4 get => build 2021-10-12 20:13:59 -04:00
Luke Van Seters
9df6dfdf5b Build => get 2021-10-12 20:13:59 -04:00
Luke Van Seters
378f5b248e Remove ETH / ERC20 transfer distinction 2021-10-12 20:13:59 -04:00
Robert Miller
f7fbd97a50 Merge pull request #99 from flashbots/curve-swaps-2
Add support for Curve swaps
2021-10-12 13:26:19 -04:00
Luke Van Seters
e3b360ec39 Fix swap tests 2021-10-12 12:23:47 -04:00
Luke Van Seters
547b51df92 Add swap support for curve 2021-10-12 12:19:24 -04:00
Luke Van Seters
0c4f605229 Write protocol for swaps 2021-10-12 12:19:09 -04:00
Luke Van Seters
1c1b80721c Merge pull request #94 from elopio/issue/tuple
decode: collapse tuples
2021-10-12 08:19:34 -07:00
Luke Van Seters
ed463ad979 Merge pull request #98 from flashbots/lag-block-listener
Lag the block listener 5 blocks
2021-10-12 08:18:59 -07:00
Luke Van Seters
d76bb52016 Lag the block listener 5 blocks 2021-10-11 16:00:58 -07:00
Luke Van Seters
b5f625112e Merge pull request #97 from flashbots/pre-commit-pr-fix
Add --all-files to pre-commit GH action
2021-10-11 15:53:10 -07:00
Luke Van Seters
b8ff6f0e8b run --all-files 2021-10-11 15:48:36 -07:00
Luke Van Seters
2377222750 Add --all-files to pre-commit GH aciton 2021-10-11 15:45:55 -07:00
Leo Arias
ba73f58396 Run precommit 2021-10-11 17:51:38 +00:00
Leo Arias
a67769cea3 Run precommit 2021-10-11 17:31:23 +00:00
Leo Arias
4e5ad64929 decode: collapse tuples 2021-10-11 01:49:37 +00:00
Luke Van Seters
b6fc27b3f6 rename get_transfers => get_erc20_transfers 2021-10-08 12:24:43 -04:00
Luke Van Seters
afcff7c845 Merge pull request #92 from flashbots/swaps-classifier
Use SwapClassifier for decoding Swap objects
2021-10-08 11:47:27 -04:00
Luke Van Seters
a1fd035de8 Update tests 2021-10-08 11:37:12 -04:00
Luke Van Seters
3039f3eed2 Use SwapClassifier for Swap objects 2021-10-08 11:37:12 -04:00
Luke Van Seters
8c6d7ab889 Merge pull request #90 from flashbots/specs-v2
Group classifying a trace as a `transfer` with the logic to decode a `Transfer` object
2021-10-08 11:36:39 -04:00
Luke Van Seters
e3eb858ed9 Fail at runtime if not implemented 2021-10-06 16:43:04 -04:00
Luke Van Seters
058cbeed94 Fix tests for decoded call trace 2021-10-06 16:00:17 -04:00
Luke Van Seters
f1379cc0a0 Switch to class instead of instance 2021-10-06 15:56:28 -04:00
Luke Van Seters
02c9c1cddc Add transfer parsing to transfer classifiers 2021-10-06 15:28:50 -04:00
Luke Van Seters
86ee26dd1a Make Classifier a union 2021-10-06 15:14:24 -04:00
Luke Van Seters
d57a2d021d Add specific classifiers for each type 2021-10-06 15:12:44 -04:00
Luke Van Seters
621a2798c8 No burn 2021-10-06 14:55:00 -04:00
Luke Van Seters
d2c397f212 Change classifications => classifiers 2021-10-06 14:53:38 -04:00
Luke Van Seters
8a94eeaf39 Add .envrc to gitignore 2021-10-05 12:43:43 -04:00
Luke Van Seters
3c761d85f8 Merge pull request #88 from elopio/typos/readme
Fix typos in README
2021-10-05 10:22:49 -04:00
Leo Arias
e75a2919cd Fix typos in README 2021-10-05 04:38:48 +00:00
Gui Heise
7dbbd9f545 Merge pull request #86 from flashbots/liquidation-models
Add liquidation models
2021-10-01 18:06:13 -04:00
Luke Van Seters
f9c3431854 Merge pull request #84 from flashbots/aave-db
Add database migration for liquidations
2021-10-01 13:01:09 -04:00
Robert Miller
4834d068f6 Merge pull request #74 from flashbots/aave-v0
Add AAVE liquidations to inspect_block
2021-09-30 13:58:37 -04:00
Luke Van Seters
e6f5ece46f Update README.md 2021-09-29 12:40:49 -04:00
Gui Heise
7dbf4a9e0e Database migration for liquidations 2021-09-29 10:24:46 -04:00
53 changed files with 1141 additions and 266 deletions

View File

@@ -51,7 +51,7 @@ jobs:
- name: Run precommit
run: |
poetry run pre-commit
poetry run pre-commit run --all-files
- name: Test with pytest
shell: bash

3
.gitignore vendored
View File

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

View File

@@ -37,9 +37,9 @@ Example:
export RPC_URL="http://111.111.111.111:8546"
```
**Note: mev-inspect-py currently requires and RPC with support for parity traces**
**Note: mev-inspect-py currently requires an RPC with support for OpenEthereum / Erigon traces (not geth 😔)**
Next, start all servcies with:
Next, start all services with:
```
tilt up
```
@@ -127,7 +127,7 @@ Postgres tip: Enter `\x` to enter "Explanded display" mode which looks nicer for
### Pre-commit
We use pre-commit to maintain a consistent style, prevent errors, and ensure test coverage.
We use pre-commit to maintain a consistent style, prevent errors, and ensure test coverage.
To set up, install dependencies through poetry
```

View File

@@ -16,6 +16,7 @@ k8s_yaml(configmap_from_dict("mev-inspect-rpc", inputs = {
k8s_yaml(secret_from_dict("mev-inspect-db-credentials", inputs = {
"username" : "postgres",
"password": "password",
"host": "postgresql",
}))
docker_build_with_restart("mev-inspect-py", ".",

View File

@@ -19,6 +19,11 @@ spec:
image: mev-inspect-py
command: [ "/app/entrypoint.sh" ]
env:
- name: POSTGRES_HOST
valueFrom:
secretKeyRef:
name: mev-inspect-db-credentials
key: host
- name: POSTGRES_USER
valueFrom:
secretKeyRef:
@@ -29,8 +34,6 @@ spec:
secretKeyRef:
name: mev-inspect-db-credentials
key: password
- name: POSTGRES_HOST
value: postgresql
- name: RPC_URL
valueFrom:
configMapKeyRef:

View File

@@ -19,6 +19,10 @@ logging.basicConfig(filename="listener.log", level=logging.INFO)
logger = logging.getLogger(__name__)
# lag to make sure the blocks we see are settled
BLOCK_NUMBER_LAG = 5
def run():
rpc = os.getenv("RPC_URL")
if rpc is None:
@@ -39,7 +43,9 @@ def run():
logger.info(f"Latest block: {latest_block_number}")
logger.info(f"Last written block: {last_written_block}")
if last_written_block is None or last_written_block < latest_block_number:
if (last_written_block is None) or (
last_written_block < (latest_block_number - BLOCK_NUMBER_LAG)
):
block_number = (
latest_block_number
if last_written_block is None

41
mev Executable file
View File

@@ -0,0 +1,41 @@
#!/bin/sh
set -e
DB_NAME=mev_inspect
function get_kube_db_secret(){
kubectl get secrets mev-inspect-db-credentials -o jsonpath="{.data.$1}" | base64 --decode
}
function db(){
host=$(get_kube_db_secret "host")
username=$(get_kube_db_secret "username")
password=$(get_kube_db_secret "password")
kubectl run -i --rm --tty postgres-client \
--env="PGPASSWORD=$password" \
--image=jbergknoff/postgresql-client \
-- $DB_NAME --host=$host --user=$username
}
case "$1" in
db)
echo "Connecting to $DB_NAME"
db
;;
inspect)
block_number=$2
echo "Inspecting block $block_number"
kubectl exec -ti deploy/mev-inspect-deployment -- poetry run inspect-block $block_number
;;
test)
echo "Running tests"
kubectl exec -ti deploy/mev-inspect-deployment -- poetry run pytest tests
;;
*)
echo "Usage: "$1" {inspect|test}"
exit 1
esac
exit 0

View File

@@ -11,8 +11,8 @@ from mev_inspect.schemas.classified_traces import (
Protocol,
)
from mev_inspect.schemas.transfers import ERC20Transfer
from mev_inspect.schemas.liquidations import Liquidation
from mev_inspect.transfers import get_transfer
AAVE_CONTRACT_ADDRESSES: List[str] = [
# AAVE Proxy
@@ -42,6 +42,7 @@ def get_aave_liquidations(
trace.classification == Classification.liquidate
and isinstance(trace, DecodedCallTrace)
and not is_child_of_any_address(trace, parent_liquidations)
and trace.protocol == Protocol.aave
):
parent_liquidations.append(trace.trace_address)
@@ -76,10 +77,12 @@ def _get_liquidator_payback(
for child in child_traces:
if child.classification == Classification.transfer:
child_transfer = ERC20Transfer.from_trace(child)
child_transfer = get_transfer(child)
if (child_transfer.to_address == liquidator) and (
child.from_address in AAVE_CONTRACT_ADDRESSES
if (
child_transfer is not None
and child_transfer.to_address == liquidator
and child.from_address in AAVE_CONTRACT_ADDRESSES
):
return child_transfer.amount

View File

@@ -12,15 +12,32 @@ THIS_FILE_DIRECTORY = Path(__file__).parents[0]
ABI_DIRECTORY_PATH = THIS_FILE_DIRECTORY / "abis"
def get_abi(abi_name: str, protocol: Optional[Protocol]) -> Optional[ABI]:
def get_abi_path(abi_name: str, protocol: Optional[Protocol]) -> Optional[Path]:
abi_filename = f"{abi_name}.json"
abi_path = (
ABI_DIRECTORY_PATH / abi_filename
if protocol is None
else ABI_DIRECTORY_PATH / protocol.value / abi_filename
)
if abi_path.is_file():
return abi_path
return None
# raw abi, for instantiating contract for queries (as opposed to classification, see below)
def get_raw_abi(abi_name: str, protocol: Optional[Protocol]) -> Optional[str]:
abi_path = get_abi_path(abi_name, protocol)
if abi_path is not None:
with abi_path.open() as abi_file:
return abi_file.read()
return None
def get_abi(abi_name: str, protocol: Optional[Protocol]) -> Optional[ABI]:
abi_path = get_abi_path(abi_name, protocol)
if abi_path is not None:
with abi_path.open() as abi_file:
abi_json = json.load(abi_file)
return parse_obj_as(ABI, abi_json)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -82,4 +82,4 @@ def cache_block(cache_path: Path, block: Block):
def _get_cache_path(block_number: int) -> Path:
cache_directory_path = Path(cache_directory)
return cache_directory_path / f"{block_number}-new.json"
return cache_directory_path / f"{block_number}.json"

View File

@@ -1,11 +1,16 @@
from typing import Dict, Optional, Tuple, Type
from mev_inspect.schemas.classified_traces import DecodedCallTrace, Protocol
from mev_inspect.schemas.classifiers import ClassifierSpec, Classifier
from .aave import AAVE_CLASSIFIER_SPECS
from .curve import CURVE_CLASSIFIER_SPECS
from .erc20 import ERC20_CLASSIFIER_SPECS
from .uniswap import UNISWAP_CLASSIFIER_SPECS
from .weth import WETH_CLASSIFIER_SPECS
from .weth import WETH_CLASSIFIER_SPECS, WETH_ADDRESS
from .zero_ex import ZEROX_CLASSIFIER_SPECS
from .balancer import BALANCER_CLASSIFIER_SPECS
from .compound import COMPOUND_CLASSIFIER_SPECS
ALL_CLASSIFIER_SPECS = (
ERC20_CLASSIFIER_SPECS
@@ -15,4 +20,21 @@ ALL_CLASSIFIER_SPECS = (
+ AAVE_CLASSIFIER_SPECS
+ ZEROX_CLASSIFIER_SPECS
+ BALANCER_CLASSIFIER_SPECS
+ COMPOUND_CLASSIFIER_SPECS
)
_SPECS_BY_ABI_NAME_AND_PROTOCOL: Dict[
Tuple[str, Optional[Protocol]], ClassifierSpec
] = {(spec.abi_name, spec.protocol): spec for spec in ALL_CLASSIFIER_SPECS}
def get_classifier(
trace: DecodedCallTrace,
) -> Optional[Type[Classifier]]:
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:
return spec.classifiers.get(trace.function_signature)
return None

View File

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

View File

@@ -1,16 +1,29 @@
from mev_inspect.schemas.classified_traces import (
Classification,
ClassifierSpec,
DecodedCallTrace,
Protocol,
)
from mev_inspect.schemas.classifiers import (
ClassifierSpec,
SwapClassifier,
)
BALANCER_V1_POOL_ABI_NAME = "BPool"
class BalancerSwapClassifier(SwapClassifier):
@staticmethod
def get_swap_recipient(trace: DecodedCallTrace) -> str:
return trace.from_address
BALANCER_V1_SPECS = [
ClassifierSpec(
abi_name="BPool",
abi_name=BALANCER_V1_POOL_ABI_NAME,
protocol=Protocol.balancer_v1,
classifications={
"swapExactAmountIn(address,uint256,address,uint256,uint256)": Classification.swap,
"swapExactAmountOut(address,uint256,address,uint256,uint256)": Classification.swap,
classifiers={
"swapExactAmountIn(address,uint256,address,uint256,uint256)": BalancerSwapClassifier,
"swapExactAmountOut(address,uint256,address,uint256,uint256)": BalancerSwapClassifier,
},
),
]

View File

@@ -0,0 +1,165 @@
from mev_inspect.schemas.classified_traces import (
Protocol,
)
from mev_inspect.schemas.classifiers import (
ClassifierSpec,
LiquidationClassifier,
SeizeClassifier,
)
COMPOUND_V2_CETH_SPEC = ClassifierSpec(
abi_name="CEther",
protocol=Protocol.compound_v2,
valid_contract_addresses=["0x4ddc2d193948926d02f9b1fe9e1daa0718270ed5"],
classifiers={
"liquidateBorrow(address,address)": LiquidationClassifier,
"seize(address,address,uint256)": SeizeClassifier,
},
)
CREAM_CETH_SPEC = ClassifierSpec(
abi_name="CEther",
protocol=Protocol.cream,
valid_contract_addresses=["0xD06527D5e56A3495252A528C4987003b712860eE"],
classifiers={
"liquidateBorrow(address,address)": LiquidationClassifier,
"seize(address,address,uint256)": SeizeClassifier,
},
)
COMPOUND_V2_CTOKEN_SPEC = ClassifierSpec(
abi_name="CToken",
protocol=Protocol.compound_v2,
valid_contract_addresses=[
"0x6c8c6b02e7b2be14d4fa6022dfd6d75921d90e4e",
"0x5d3a536e4d6dbd6114cc1ead35777bab948e3643",
"0x158079ee67fce2f58472a96584a73c7ab9ac95c1",
"0x39aa39c021dfbae8fac545936693ac917d5e7563",
"0xf650c3d88d12db855b8bf7d11be6c55a4e07dcc9",
"0xc11b1268c1a384e55c48c2391d8d480264a3a7f4",
"0xb3319f5d18bc0d84dd1b4825dcde5d5f7266d407",
"0xf5dce57282a584d2746faf1593d3121fcac444dc",
"0x35a18000230da775cac24873d00ff85bccded550",
"0x70e36f6bf80a52b3b46b3af8e106cc0ed743e8e4",
"0xccf4429db6322d5c611ee964527d42e5d685dd6a",
"0x12392f67bdf24fae0af363c24ac620a2f67dad86",
"0xface851a4921ce59e912d19329929ce6da6eb0c7",
"0x95b4ef2869ebd94beb4eee400a99824bf5dc325b",
"0x4b0181102a0112a2ef11abee5563bb4a3176c9d7",
"0xe65cdb6479bac1e22340e4e755fae7e509ecd06c",
"0x80a2ae356fc9ef4305676f7a3e2ed04e12c33946",
],
classifiers={
"liquidateBorrow(address,uint256,address)": LiquidationClassifier,
"seize(address,address,uint256)": SeizeClassifier,
},
)
CREAM_CTOKEN_SPEC = ClassifierSpec(
abi_name="CToken",
protocol=Protocol.cream,
valid_contract_addresses=[
"0xd06527d5e56a3495252a528c4987003b712860ee",
"0x51f48b638f82e8765f7a26373a2cb4ccb10c07af",
"0x44fbebd2f576670a6c33f6fc0b00aa8c5753b322",
"0xcbae0a83f4f9926997c8339545fb8ee32edc6b76",
"0xce4fe9b4b8ff61949dcfeb7e03bc9faca59d2eb3",
"0x19d1666f543d42ef17f66e376944a22aea1a8e46",
"0x9baf8a5236d44ac410c0186fe39178d5aad0bb87",
"0x797aab1ce7c01eb727ab980762ba88e7133d2157",
"0x892b14321a4fcba80669ae30bd0cd99a7ecf6ac0",
"0x697256caa3ccafd62bb6d3aa1c7c5671786a5fd9",
"0x8b86e0598616a8d4f1fdae8b59e55fb5bc33d0d6",
"0xc7fd8dcee4697ceef5a2fd4608a7bd6a94c77480",
"0x17107f40d70f4470d20cb3f138a052cae8ebd4be",
"0x1ff8cdb51219a8838b52e9cac09b71e591bc998e",
"0x3623387773010d9214b10c551d6e7fc375d31f58",
"0x4ee15f44c6f0d8d1136c83efd2e8e4ac768954c6",
"0x338286c0bc081891a4bda39c7667ae150bf5d206",
"0x10fdbd1e48ee2fd9336a482d746138ae19e649db",
"0x01da76dea59703578040012357b81ffe62015c2d",
"0xef58b2d5a1b8d3cde67b8ab054dc5c831e9bc025",
"0xe89a6d0509faf730bd707bf868d9a2a744a363c7",
"0xeff039c3c1d668f408d09dd7b63008622a77532c",
"0x22b243b96495c547598d9042b6f94b01c22b2e9e",
"0x8b3ff1ed4f36c2c2be675afb13cc3aa5d73685a5",
"0x2a537fa9ffaea8c1a41d3c2b68a9cb791529366d",
"0x7ea9c63e216d5565c3940a2b3d150e59c2907db3",
"0x3225e3c669b39c7c8b3e204a8614bb218c5e31bc",
"0xf55bbe0255f7f4e70f63837ff72a577fbddbe924",
"0x903560b1cce601794c584f58898da8a8b789fc5d",
"0x054b7ed3f45714d3091e82aad64a1588dc4096ed",
"0xd5103afcd0b3fa865997ef2984c66742c51b2a8b",
"0xfd609a03b393f1a1cfcacedabf068cad09a924e2",
"0xd692ac3245bb82319a31068d6b8412796ee85d2c",
"0x92b767185fb3b04f881e3ac8e5b0662a027a1d9f",
"0x10a3da2bb0fae4d591476fd97d6636fd172923a8",
"0x3c6c553a95910f9fc81c98784736bd628636d296",
"0x21011bc93d9e515b9511a817a1ed1d6d468f49fc",
"0x85759961b116f1d36fd697855c57a6ae40793d9b",
"0x7c3297cfb4c4bbd5f44b450c0872e0ada5203112",
"0x7aaa323d7e398be4128c7042d197a2545f0f1fea",
"0x011a014d5e8eb4771e575bb1000318d509230afa",
"0xe6c3120f38f56deb38b69b65cc7dcaf916373963",
"0x4fe11bc316b6d7a345493127fbe298b95adaad85",
"0xcd22c4110c12ac41acefa0091c432ef44efaafa0",
"0x228619cca194fbe3ebeb2f835ec1ea5080dafbb2",
"0x73f6cba38922960b7092175c0add22ab8d0e81fc",
"0x38f27c03d6609a86ff7716ad03038881320be4ad",
"0x5ecad8a75216cea7dff978525b2d523a251eea92",
"0x5c291bc83d15f71fb37805878161718ea4b6aee9",
"0x6ba0c66c48641e220cf78177c144323b3838d375",
"0xd532944df6dfd5dd629e8772f03d4fc861873abf",
"0x197070723ce0d3810a0e47f06e935c30a480d4fc",
"0xc25eae724f189ba9030b2556a1533e7c8a732e14",
"0x25555933a8246ab67cbf907ce3d1949884e82b55",
"0xc68251421edda00a10815e273fa4b1191fac651b",
"0x65883978ada0e707c3b2be2a6825b1c4bdf76a90",
"0x8b950f43fcac4931d408f1fcda55c6cb6cbf3096",
"0x59089279987dd76fc65bf94cb40e186b96e03cb3",
"0x2db6c82ce72c8d7d770ba1b5f5ed0b6e075066d6",
"0xb092b4601850e23903a42eacbc9d8a0eec26a4d5",
"0x081fe64df6dc6fc70043aedf3713a3ce6f190a21",
"0x1d0986fb43985c88ffa9ad959cc24e6a087c7e35",
"0xc36080892c64821fa8e396bc1bd8678fa3b82b17",
"0x8379baa817c5c5ab929b03ee8e3c48e45018ae41",
"0x299e254a8a165bbeb76d9d69305013329eea3a3b",
"0xf8445c529d363ce114148662387eba5e62016e20",
"0x28526bb33d7230e65e735db64296413731c5402e",
"0x45406ba53bb84cd32a58e7098a2d4d1b11b107f6",
"0x6d1b9e01af17dd08d6dec08e210dfd5984ff1c20",
"0x1f9b4756b008106c806c7e64322d7ed3b72cb284",
"0xab10586c918612ba440482db77549d26b7abf8f7",
"0xdfff11dfe6436e42a17b86e7f419ac8292990393",
"0xdbb5e3081def4b6cdd8864ac2aeda4cbf778fecf",
"0x71cefcd324b732d4e058afacba040d908c441847",
"0x1a122348b73b58ea39f822a89e6ec67950c2bbd0",
"0x523effc8bfefc2948211a05a905f761cba5e8e9e",
"0x4202d97e00b9189936edf37f8d01cff88bdd81d4",
"0x4baa77013ccd6705ab0522853cb0e9d453579dd4",
"0x98e329eb5aae2125af273102f3440de19094b77c",
"0x8c3b7a4320ba70f8239f83770c4015b5bc4e6f91",
"0xe585c76573d7593abf21537b607091f76c996e73",
"0x81e346729723c4d15d0fb1c5679b9f2926ff13c6",
"0x766175eac1a99c969ddd1ebdbe7e270d508d8fff",
"0xd7394428536f63d5659cc869ef69d10f9e66314b",
"0x1241b10e7ea55b22f5b2d007e8fecdf73dcff999",
"0x2a867fd776b83e1bd4e13c6611afd2f6af07ea6d",
"0x250fb308199fe8c5220509c1bf83d21d60b7f74a",
"0x4112a717edd051f77d834a6703a1ef5e3d73387f",
"0xf04ce2e71d32d789a259428ddcd02d3c9f97fb4e",
"0x89e42987c39f72e2ead95a8a5bc92114323d5828",
"0x58da9c9fc3eb30abbcbbab5ddabb1e6e2ef3d2ef",
],
classifiers={
"liquidateBorrow(address,uint256,address)": LiquidationClassifier,
"seize(address,address,uint256)": SeizeClassifier,
},
)
COMPOUND_CLASSIFIER_SPECS = [
COMPOUND_V2_CETH_SPEC,
COMPOUND_V2_CTOKEN_SPEC,
CREAM_CETH_SPEC,
CREAM_CTOKEN_SPEC,
]

View File

@@ -1,29 +1,20 @@
from mev_inspect.schemas.classified_traces import (
ClassifierSpec,
Protocol,
)
"""
Deployment addresses found here
https://curve.readthedocs.io/ref-addresses.html
from mev_inspect.schemas.classifiers import (
ClassifierSpec,
DecodedCallTrace,
SwapClassifier,
)
class CurveSwapClassifier(SwapClassifier):
@staticmethod
def get_swap_recipient(trace: DecodedCallTrace) -> str:
return trace.from_address
organized into 3 groups
1. Base Pools: 2 or more tokens implementing stable swap
- StableSwap<pool>
- Deposit<pool>
- CurveContract<version>
- CurveTokenV1/V2
2. Meta Pools: 1 token trading with an LP from above
- StableSwap<pool>
- Deposit<pool>
- CurveTokenV1/V2
3. Liquidity Gauges: stake LP get curve governance token?
- LiquidityGauge
- LiquidityGaugeV1/V2
- LiquidityGaugeReward
4. DAO stuff
5..? Other stuff, haven't decided if important
"""
CURVE_BASE_POOLS = [
ClassifierSpec(
abi_name="CurveTokenV1",
@@ -72,101 +63,171 @@ CURVE_BASE_POOLS = [
abi_name="StableSwap3Pool",
protocol=Protocol.curve,
valid_contract_addresses=["0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7"],
classifiers={
"exchange(int128,int128,uint256,uint256)": CurveSwapClassifier,
},
),
ClassifierSpec(
abi_name="StableSwapAAVE",
protocol=Protocol.curve,
valid_contract_addresses=["0xDeBF20617708857ebe4F679508E7b7863a8A8EeE"],
classifiers={
"exchange(int128,int128,uint256,uint256)": CurveSwapClassifier,
"exchange_underlying(int128,int128,uint256,uint256)": CurveSwapClassifier,
},
),
ClassifierSpec(
abi_name="StableSwapAETH",
protocol=Protocol.curve,
valid_contract_addresses=["0xA96A65c051bF88B4095Ee1f2451C2A9d43F53Ae2"],
classifiers={
"exchange(int128,int128,uint256,uint256)": CurveSwapClassifier,
},
),
ClassifierSpec(
abi_name="StableSwapBUSD",
protocol=Protocol.curve,
valid_contract_addresses=["0x79a8C46DeA5aDa233ABaFFD40F3A0A2B1e5A4F27"],
classifiers={
"exchange(int128,int128,uint256,uint256)": CurveSwapClassifier,
"exchange_underlying(int128,int128,uint256,uint256)": CurveSwapClassifier,
},
),
ClassifierSpec(
abi_name="StableSwapCompound",
protocol=Protocol.curve,
valid_contract_addresses=["0xA2B47E3D5c44877cca798226B7B8118F9BFb7A56"],
classifiers={
"exchange(int128,int128,uint256,uint256)": CurveSwapClassifier,
"exchange_underlying(int128,int128,uint256,uint256)": CurveSwapClassifier,
},
),
ClassifierSpec(
abi_name="StableSwapEURS",
protocol=Protocol.curve,
valid_contract_addresses=["0x0Ce6a5fF5217e38315f87032CF90686C96627CAA"],
classifiers={
"exchange(int128,int128,uint256,uint256)": CurveSwapClassifier,
},
),
ClassifierSpec(
abi_name="StableSwaphBTC",
protocol=Protocol.curve,
valid_contract_addresses=["0x4CA9b3063Ec5866A4B82E437059D2C43d1be596F"],
classifiers={
"exchange(int128,int128,uint256,uint256)": CurveSwapClassifier,
},
),
ClassifierSpec(
abi_name="StableSwapIronBank",
protocol=Protocol.curve,
valid_contract_addresses=["0x2dded6Da1BF5DBdF597C45fcFaa3194e53EcfeAF"],
classifiers={
"exchange(int128,int128,uint256,uint256)": CurveSwapClassifier,
"exchange_underlying(int128,int128,uint256,uint256)": CurveSwapClassifier,
},
),
ClassifierSpec(
abi_name="StableSwapLink",
protocol=Protocol.curve,
valid_contract_addresses=["0xf178c0b5bb7e7abf4e12a4838c7b7c5ba2c623c0"],
classifiers={
"exchange(int128,int128,uint256,uint256)": CurveSwapClassifier,
},
),
ClassifierSpec(
abi_name="StableSwapPAX",
protocol=Protocol.curve,
valid_contract_addresses=["0x06364f10B501e868329afBc005b3492902d6C763"],
classifiers={
"exchange(int128,int128,uint256,uint256)": CurveSwapClassifier,
"exchange_underlying(int128,int128,uint256,uint256)": CurveSwapClassifier,
},
),
ClassifierSpec(
abi_name="StableSwaprenBTC",
protocol=Protocol.curve,
valid_contract_addresses=["0x93054188d876f558f4a66B2EF1d97d16eDf0895B"],
classifiers={
"exchange(int128,int128,uint256,uint256)": CurveSwapClassifier,
},
),
ClassifierSpec(
abi_name="StableSwaprETH",
protocol=Protocol.curve,
valid_contract_addresses=["0xF9440930043eb3997fc70e1339dBb11F341de7A8"],
classifiers={
"exchange(int128,int128,uint256,uint256)": CurveSwapClassifier,
},
),
ClassifierSpec(
abi_name="StableSwapsAAVE",
protocol=Protocol.curve,
valid_contract_addresses=["0xEB16Ae0052ed37f479f7fe63849198Df1765a733"],
classifiers={
"exchange(int128,int128,uint256,uint256)": CurveSwapClassifier,
"exchange_underlying(int128,int128,uint256,uint256)": CurveSwapClassifier,
},
),
ClassifierSpec(
abi_name="StableSwapsBTC",
protocol=Protocol.curve,
valid_contract_addresses=["0x7fC77b5c7614E1533320Ea6DDc2Eb61fa00A9714"],
classifiers={
"exchange(int128,int128,uint256,uint256)": CurveSwapClassifier,
},
),
ClassifierSpec(
abi_name="StableSwapsETH",
protocol=Protocol.curve,
valid_contract_addresses=["0xc5424B857f758E906013F3555Dad202e4bdB4567"],
classifiers={
"exchange(int128,int128,uint256,uint256)": CurveSwapClassifier,
},
),
ClassifierSpec(
abi_name="StableSwapstETH",
protocol=Protocol.curve,
valid_contract_addresses=["0xDC24316b9AE028F1497c275EB9192a3Ea0f67022"],
classifiers={
"exchange(int128,int128,uint256,uint256)": CurveSwapClassifier,
},
),
ClassifierSpec(
abi_name="StableSwapsUSD",
protocol=Protocol.curve,
valid_contract_addresses=["0xA5407eAE9Ba41422680e2e00537571bcC53efBfD"],
classifiers={
"exchange(int128,int128,uint256,uint256)": CurveSwapClassifier,
"exchange_underlying(int128,int128,uint256,uint256)": CurveSwapClassifier,
},
),
ClassifierSpec(
abi_name="StableSwapUSDT",
protocol=Protocol.curve,
valid_contract_addresses=["0x52EA46506B9CC5Ef470C5bf89f17Dc28bB35D85C"],
classifiers={
"exchange(int128,int128,uint256,uint256)": CurveSwapClassifier,
"exchange_underlying(int128,int128,uint256,uint256)": CurveSwapClassifier,
},
),
ClassifierSpec(
abi_name="StableSwapY",
protocol=Protocol.curve,
valid_contract_addresses=["0x45F783CCE6B7FF23B2ab2D70e416cdb7D6055f51"],
classifiers={
"exchange(int128,int128,uint256,uint256)": CurveSwapClassifier,
"exchange_underlying(int128,int128,uint256,uint256)": CurveSwapClassifier,
},
),
ClassifierSpec(
abi_name="StableSwapYv2",
protocol=Protocol.curve,
valid_contract_addresses=["0x8925D9d9B4569D737a48499DeF3f67BaA5a144b9"],
classifiers={
"exchange(int128,int128,uint256,uint256)": CurveSwapClassifier,
"exchange_underlying(int128,int128,uint256,uint256)": CurveSwapClassifier,
},
),
ClassifierSpec(
abi_name="DepositBUSD",
@@ -300,51 +361,91 @@ CURVE_META_POOLS = [
abi_name="StableSwapbBTC",
protocol=Protocol.curve,
valid_contract_addresses=["0x071c661B4DeefB59E2a3DdB20Db036821eeE8F4b"],
classifiers={
"exchange(int128,int128,uint256,uint256)": CurveSwapClassifier,
"exchange_underlying(int128,int128,uint256,uint256)": CurveSwapClassifier,
},
),
ClassifierSpec(
abi_name="StableSwapDUSD",
protocol=Protocol.curve,
valid_contract_addresses=["0x8038C01A0390a8c547446a0b2c18fc9aEFEcc10c"],
classifiers={
"exchange(int128,int128,uint256,uint256)": CurveSwapClassifier,
"exchange_underlying(int128,int128,uint256,uint256)": CurveSwapClassifier,
},
),
ClassifierSpec(
abi_name="StableSwapGUSD",
protocol=Protocol.curve,
valid_contract_addresses=["0x4f062658EaAF2C1ccf8C8e36D6824CDf41167956"],
classifiers={
"exchange(int128,int128,uint256,uint256)": CurveSwapClassifier,
"exchange_underlying(int128,int128,uint256,uint256)": CurveSwapClassifier,
},
),
ClassifierSpec(
abi_name="StableSwapHUSD",
protocol=Protocol.curve,
valid_contract_addresses=["0x3eF6A01A0f81D6046290f3e2A8c5b843e738E604"],
classifiers={
"exchange(int128,int128,uint256,uint256)": CurveSwapClassifier,
"exchange_underlying(int128,int128,uint256,uint256)": CurveSwapClassifier,
},
),
ClassifierSpec(
abi_name="StableSwapLinkUSD",
protocol=Protocol.curve,
valid_contract_addresses=["0xE7a24EF0C5e95Ffb0f6684b813A78F2a3AD7D171"],
classifiers={
"exchange(int128,int128,uint256,uint256)": CurveSwapClassifier,
"exchange_underlying(int128,int128,uint256,uint256)": CurveSwapClassifier,
},
),
ClassifierSpec(
abi_name="StableSwapMUSD",
protocol=Protocol.curve,
valid_contract_addresses=["0x8474DdbE98F5aA3179B3B3F5942D724aFcdec9f6"],
classifiers={
"exchange(int128,int128,uint256,uint256)": CurveSwapClassifier,
"exchange_underlying(int128,int128,uint256,uint256)": CurveSwapClassifier,
},
),
ClassifierSpec(
abi_name="StableSwapoBTC",
protocol=Protocol.curve,
valid_contract_addresses=["0xd81dA8D904b52208541Bade1bD6595D8a251F8dd"],
classifiers={
"exchange(int128,int128,uint256,uint256)": CurveSwapClassifier,
"exchange_underlying(int128,int128,uint256,uint256)": CurveSwapClassifier,
},
),
ClassifierSpec(
abi_name="StableSwappBTC",
protocol=Protocol.curve,
valid_contract_addresses=["0x7F55DDe206dbAD629C080068923b36fe9D6bDBeF"],
classifiers={
"exchange(int128,int128,uint256,uint256)": CurveSwapClassifier,
"exchange_underlying(int128,int128,uint256,uint256)": CurveSwapClassifier,
},
),
ClassifierSpec(
abi_name="StableSwapRSV",
protocol=Protocol.curve,
valid_contract_addresses=["0xC18cC39da8b11dA8c3541C598eE022258F9744da"],
classifiers={
"exchange(int128,int128,uint256,uint256)": CurveSwapClassifier,
"exchange_underlying(int128,int128,uint256,uint256)": CurveSwapClassifier,
},
),
ClassifierSpec(
abi_name="StableSwaptBTC",
protocol=Protocol.curve,
valid_contract_addresses=["0xC25099792E9349C7DD09759744ea681C7de2cb66"],
classifiers={
"exchange(int128,int128,uint256,uint256)": CurveSwapClassifier,
"exchange_underlying(int128,int128,uint256,uint256)": CurveSwapClassifier,
},
),
ClassifierSpec(
abi_name="StableSwapUSD",
@@ -353,82 +454,29 @@ CURVE_META_POOLS = [
"0x3E01dD8a5E1fb3481F0F589056b428Fc308AF0Fb",
"0x0f9cb53Ebe405d49A0bbdBD291A65Ff571bC83e1",
],
classifiers={
"exchange(int128,int128,uint256,uint256)": CurveSwapClassifier,
"exchange_underlying(int128,int128,uint256,uint256)": CurveSwapClassifier,
},
),
ClassifierSpec(
abi_name="StableSwapUSDP",
protocol=Protocol.curve,
valid_contract_addresses=["0x42d7025938bEc20B69cBae5A77421082407f053A"],
classifiers={
"exchange(int128,int128,uint256,uint256)": CurveSwapClassifier,
"exchange_underlying(int128,int128,uint256,uint256)": CurveSwapClassifier,
},
),
ClassifierSpec(
abi_name="StableSwapUST",
protocol=Protocol.curve,
valid_contract_addresses=["0x890f4e345B1dAED0367A877a1612f86A1f86985f"],
classifiers={
"exchange(int128,int128,uint256,uint256)": CurveSwapClassifier,
"exchange_underlying(int128,int128,uint256,uint256)": CurveSwapClassifier,
},
),
]
"""
CURVE_LIQUIDITY_GAUGES = [
ClassifierSpec(
abi_name="LiquidityGauge",
protocol=Protocol.curve,
valid_contract_addresses=[
"0xbFcF63294aD7105dEa65aA58F8AE5BE2D9d0952A", # 3Pool
"0x69Fb7c45726cfE2baDeE8317005d3F94bE838840", # BUSD
"0x7ca5b0a2910B33e9759DC7dDB0413949071D7575", # Compound
"0xC5cfaDA84E902aD92DD40194f0883ad49639b023", # GUSD
"0x4c18E409Dc8619bFb6a1cB56D114C3f592E0aE79", # hBTC
"0x2db0E83599a91b508Ac268a6197b8B14F5e72840", # HUSD
"0x64E3C23bfc40722d3B649844055F1D51c1ac041d", # PAX
"0xB1F2cdeC61db658F091671F5f199635aEF202CAC", # renBTC
"0xC2b1DF84112619D190193E48148000e3990Bf627", # USDK
"0xF98450B5602fa59CC66e1379DFfB6FDDc724CfC4", # USDN
"0xBC89cd85491d81C6AD2954E6d0362Ee29fCa8F53", # USDT
"0xFA712EE4788C042e2B7BB55E6cb8ec569C4530c1", # Y
],
),
ClassifierSpec(
abi_name="LiquidityGaugeV2",
protocol=Protocol.curve,
valid_contract_addresses=[
"0xd662908ADA2Ea1916B3318327A97eB18aD588b5d", # AAVE
"0x6d10ed2cF043E6fcf51A0e7b4C2Af3Fa06695707", # ankrETH
"0xdFc7AdFa664b08767b735dE28f9E84cd30492aeE", # bBTC
"0x90Bb609649E0451E5aD952683D64BD2d1f245840", # EURS
"0x72e158d38dbd50a483501c24f792bdaaa3e7d55c", # FRAX
"0x11137B10C210b579405c21A07489e28F3c040AB1", # oBTC
"0xF5194c3325202F456c95c1Cf0cA36f8475C1949F", # IronBank
"0xFD4D8a17df4C27c1dD245d153ccf4499e806C87D", # Link
"0xd7d147c6Bb90A718c3De8C0568F9B560C79fa416", # pBTC
"0x462253b8F74B72304c145DB0e4Eebd326B22ca39", # sAAVE
"0x3C0FFFF15EA30C35d7A85B85c0782D6c94e1d238", # sETH
"0x182B723a58739a9c974cFDB385ceaDb237453c28", # stETH
"0x055be5DDB7A925BfEF3417FC157f53CA77cA7222", # USDP
"0x3B7020743Bc2A4ca9EaF9D0722d42E20d6935855", # UST
"0x8101E6760130be2C8Ace79643AB73500571b7162", # Yv2
],
),
ClassifierSpec(
abi_name="LiquidityGaugeV3",
protocol=Protocol.curve,
valid_contract_addresses=[
"0x9582C4ADACB3BCE56Fea3e590F05c3ca2fb9C477", # alUSD
"0x824F13f1a2F29cFEEa81154b46C0fc820677A637", # rETH
"0x6955a55416a06839309018A8B0cB72c4DDC11f15", # TriCrypto
],
),
ClassifierSpec(
abi_name="LiquidityGaugeReward",
protocol=Protocol.curve,
valid_contract_addresses=[
"0xAEA6c312f4b3E04D752946d329693F7293bC2e6D", # DUSD
"0x5f626c30EC1215f4EdCc9982265E8b1F411D1352", # MUSD
"0x4dC4A289a8E33600D8bD4cf5F6313E43a37adec7", # RSV
"0x705350c4BcD35c9441419DdD5d2f097d7a55410F", # sBTC
"0xA90996896660DEcC6E997655E065b23788857849", # sUSDv2
"0x6828bcF74279eE32f2723eC536c22c51Eed383C6", # tBTC
],
),
]
"""
CURVE_CLASSIFIER_SPECS = [*CURVE_BASE_POOLS, *CURVE_META_POOLS]

View File

@@ -1,15 +1,30 @@
from mev_inspect.schemas.classified_traces import (
Classification,
from mev_inspect.schemas.classified_traces import DecodedCallTrace
from mev_inspect.schemas.classifiers import (
ClassifierSpec,
TransferClassifier,
)
from mev_inspect.schemas.transfers import Transfer
class ERC20TransferClassifier(TransferClassifier):
@staticmethod
def get_transfer(trace: DecodedCallTrace) -> Transfer:
return Transfer(
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(
abi_name="ERC20",
classifications={
"transferFrom(address,address,uint256)": Classification.transfer,
"transfer(address,uint256)": Classification.transfer,
"burn(address)": Classification.burn,
classifiers={
"transferFrom(address,address,uint256)": ERC20TransferClassifier,
"transfer(address,uint256)": ERC20TransferClassifier,
},
)

View File

@@ -1,8 +1,33 @@
from mev_inspect.schemas.classified_traces import (
Classification,
ClassifierSpec,
DecodedCallTrace,
Protocol,
)
from mev_inspect.schemas.classifiers import (
ClassifierSpec,
SwapClassifier,
)
UNISWAP_V2_PAIR_ABI_NAME = "UniswapV2Pair"
UNISWAP_V3_POOL_ABI_NAME = "UniswapV3Pool"
class UniswapV3SwapClassifier(SwapClassifier):
@staticmethod
def get_swap_recipient(trace: DecodedCallTrace) -> str:
if trace.inputs is not None and "recipient" in trace.inputs:
return trace.inputs["recipient"]
else:
return trace.from_address
class UniswapV2SwapClassifier(SwapClassifier):
@staticmethod
def get_swap_recipient(trace: DecodedCallTrace) -> str:
if trace.inputs is not None and "to" in trace.inputs:
return trace.inputs["to"]
else:
return trace.from_address
UNISWAP_V3_CONTRACT_SPECS = [
@@ -65,9 +90,9 @@ UNISWAP_V3_CONTRACT_SPECS = [
UNISWAP_V3_GENERAL_SPECS = [
ClassifierSpec(
abi_name="UniswapV3Pool",
classifications={
"swap(address,bool,int256,uint160,bytes)": Classification.swap,
abi_name=UNISWAP_V3_POOL_ABI_NAME,
classifiers={
"swap(address,bool,int256,uint160,bytes)": UniswapV3SwapClassifier,
},
),
ClassifierSpec(
@@ -96,9 +121,9 @@ UNISWAPPY_V2_CONTRACT_SPECS = [
]
UNISWAPPY_V2_PAIR_SPEC = ClassifierSpec(
abi_name="UniswapV2Pair",
classifications={
"swap(uint256,uint256,address,bytes)": Classification.swap,
abi_name=UNISWAP_V2_PAIR_ABI_NAME,
classifiers={
"swap(uint256,uint256,address,bytes)": UniswapV2SwapClassifier,
},
)

View File

@@ -1,16 +1,37 @@
from mev_inspect.schemas.classified_traces import (
Classification,
ClassifierSpec,
Protocol,
)
from mev_inspect.schemas.classifiers import (
ClassifierSpec,
DecodedCallTrace,
TransferClassifier,
)
from mev_inspect.schemas.transfers import Transfer
class WethTransferClassifier(TransferClassifier):
@staticmethod
def get_transfer(trace: DecodedCallTrace) -> Transfer:
return Transfer(
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_ADDRESS = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
WETH_SPEC = ClassifierSpec(
abi_name="WETH9",
protocol=Protocol.weth,
valid_contract_addresses=["0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"],
classifications={
"transferFrom(address,address,uint256)": Classification.transfer,
"transfer(address,uint256)": Classification.transfer,
valid_contract_addresses=[WETH_ADDRESS],
classifiers={
"transferFrom(address,address,uint256)": WethTransferClassifier,
"transfer(address,uint256)": WethTransferClassifier,
},
)

View File

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

View File

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

View File

@@ -0,0 +1,125 @@
from typing import Dict, List, Optional
from web3 import Web3
from mev_inspect.traces import get_child_traces
from mev_inspect.schemas.classified_traces import (
ClassifiedTrace,
Classification,
Protocol,
)
from mev_inspect.schemas.liquidations import Liquidation
from mev_inspect.abi import get_raw_abi
from mev_inspect.transfers import ETH_TOKEN_ADDRESS
V2_COMPTROLLER_ADDRESS = "0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B"
V2_C_ETHER = "0x4Ddc2D193948926D02f9B1fE9e1daa0718270ED5"
CREAM_COMPTROLLER_ADDRESS = "0x3d5BC3c8d13dcB8bF317092d84783c2697AE9258"
CREAM_CR_ETHER = "0xD06527D5e56A3495252A528C4987003b712860eE"
# helper, only queried once in the beginning (inspect_block)
def fetch_all_underlying_markets(w3: Web3, protocol: Protocol) -> Dict[str, str]:
if protocol == Protocol.compound_v2:
c_ether = V2_C_ETHER
address = V2_COMPTROLLER_ADDRESS
elif protocol == Protocol.cream:
c_ether = CREAM_CR_ETHER
address = CREAM_COMPTROLLER_ADDRESS
else:
raise ValueError(f"No Comptroller found for {protocol}")
token_mapping = {}
comptroller_abi = get_raw_abi("Comptroller", Protocol.compound_v2)
comptroller_instance = w3.eth.contract(address=address, abi=comptroller_abi)
markets = comptroller_instance.functions.getAllMarkets().call()
token_abi = get_raw_abi("CToken", Protocol.compound_v2)
for token in markets:
# make an exception for cETH (as it has no .underlying())
if token != c_ether:
token_instance = w3.eth.contract(address=token, abi=token_abi)
underlying_token = token_instance.functions.underlying().call()
token_mapping[
token.lower()
] = underlying_token.lower() # make k:v lowercase for consistancy
return token_mapping
def get_compound_liquidations(
traces: List[ClassifiedTrace],
collateral_by_c_token_address: Dict[str, str],
collateral_by_cr_token_address: Dict[str, str],
) -> List[Liquidation]:
"""Inspect list of classified traces and identify liquidation"""
liquidations: List[Liquidation] = []
for trace in traces:
if (
trace.classification == Classification.liquidate
and (
trace.protocol == Protocol.compound_v2
or trace.protocol == Protocol.cream
)
and trace.inputs is not None
and trace.to_address is not None
):
# First, we look for cEther liquidations (position paid back via tx.value)
child_traces = get_child_traces(
trace.transaction_hash, trace.trace_address, traces
)
seize_trace = _get_seize_call(child_traces)
underlying_markets = {}
if trace.protocol == Protocol.compound_v2:
underlying_markets = collateral_by_c_token_address
elif trace.protocol == Protocol.cream:
underlying_markets = collateral_by_cr_token_address
if (
seize_trace is not None
and seize_trace.inputs is not None
and len(underlying_markets) != 0
):
c_token_collateral = trace.inputs["cTokenCollateral"]
if trace.abi_name == "CEther":
liquidations.append(
Liquidation(
liquidated_user=trace.inputs["borrower"],
collateral_token_address=ETH_TOKEN_ADDRESS, # WETH since all cEther liquidations provide Ether
debt_token_address=c_token_collateral,
liquidator_user=seize_trace.inputs["liquidator"],
debt_purchase_amount=trace.value,
protocol=trace.protocol,
received_amount=seize_trace.inputs["seizeTokens"],
transaction_hash=trace.transaction_hash,
trace_address=trace.trace_address,
block_number=trace.block_number,
)
)
elif (
trace.abi_name == "CToken"
): # cToken liquidations where liquidator pays back via token transfer
c_token_address = trace.to_address
liquidations.append(
Liquidation(
liquidated_user=trace.inputs["borrower"],
collateral_token_address=underlying_markets[
c_token_address
],
debt_token_address=c_token_collateral,
liquidator_user=seize_trace.inputs["liquidator"],
debt_purchase_amount=trace.inputs["repayAmount"],
protocol=trace.protocol,
received_amount=seize_trace.inputs["seizeTokens"],
transaction_hash=trace.transaction_hash,
trace_address=trace.trace_address,
block_number=trace.block_number,
)
)
return liquidations
def _get_seize_call(traces: List[ClassifiedTrace]) -> Optional[ClassifiedTrace]:
"""Find the call to `seize` in the child traces (successful liquidation)"""
for trace in traces:
if trace.classification == Classification.seize:
return trace
return None

View File

@@ -2,7 +2,7 @@ import json
from typing import List
from mev_inspect.models.transfers import TransferModel
from mev_inspect.schemas.transfers import ERC20Transfer
from mev_inspect.schemas.transfers import Transfer
def delete_transfers_for_block(
@@ -20,7 +20,7 @@ def delete_transfers_for_block(
def write_transfers(
db_session,
transfers: List[ERC20Transfer],
transfers: List[Transfer],
) -> None:
models = [TransferModel(**json.loads(transfer.json())) for transfer in transfers]

View File

@@ -1,5 +1,7 @@
from typing import Dict, Optional
import eth_utils.abi
from hexbytes import HexBytes
from eth_abi import decode_abi
from eth_abi.exceptions import InsufficientDataBytes, NonEmptyPaddingBytes
@@ -26,7 +28,12 @@ class ABIDecoder:
return None
names = [input.name for input in func.inputs]
types = [input.type for input in func.inputs]
types = [
input.type
if input.type != "tuple"
else eth_utils.abi.collapse_if_tuple(input.dict())
for input in func.inputs
]
try:
decoded = decode_abi(types, params)

View File

@@ -27,8 +27,7 @@ from mev_inspect.crud.liquidations import (
from mev_inspect.miner_payments import get_miner_payments
from mev_inspect.swaps import get_swaps
from mev_inspect.transfers import get_transfers
from mev_inspect.aave_liquidations import get_aave_liquidations
from mev_inspect.liquidations import get_liquidations
logger = logging.getLogger(__name__)
@@ -82,7 +81,7 @@ def inspect_block(
delete_arbitrages_for_block(db_session, block_number)
write_arbitrages(db_session, arbitrages)
liquidations = get_aave_liquidations(classified_traces)
liquidations = get_liquidations(classified_traces, w3)
logger.info(f"Found {len(liquidations)} liquidations")
if should_write_liquidations:

View File

@@ -0,0 +1,40 @@
from typing import List
from web3 import Web3
from mev_inspect.aave_liquidations import get_aave_liquidations
from mev_inspect.compound_liquidations import (
get_compound_liquidations,
fetch_all_underlying_markets,
)
from mev_inspect.schemas.classified_traces import (
ClassifiedTrace,
Classification,
Protocol,
)
from mev_inspect.schemas.liquidations import Liquidation
def has_liquidations(classified_traces: List[ClassifiedTrace]) -> bool:
liquidations_exist = False
for classified_trace in classified_traces:
if classified_trace.classification == Classification.liquidate:
liquidations_exist = True
return liquidations_exist
def get_liquidations(
classified_traces: List[ClassifiedTrace], w3: Web3
) -> List[Liquidation]:
# to avoid contract calls to fetch comp/cream markets
# unless there is a liquidation
if has_liquidations(classified_traces):
aave_liquidations = get_aave_liquidations(classified_traces)
comp_markets = fetch_all_underlying_markets(w3, Protocol.compound_v2)
cream_markets = fetch_all_underlying_markets(w3, Protocol.cream)
compound_liquidations = get_compound_liquidations(
classified_traces, comp_markets, cream_markets
)
return aave_liquidations + compound_liquidations
return []

View File

@@ -1,7 +1,8 @@
from enum import Enum
from typing import List, Union
from typing import List, Optional, Union
from typing_extensions import Literal
import eth_utils.abi
from hexbytes import HexBytes
from pydantic import BaseModel
from web3 import Web3
@@ -26,6 +27,10 @@ NON_FUNCTION_DESCRIPTION_TYPES = Union[
class ABIDescriptionInput(BaseModel):
name: str
type: str
components: Optional[List["ABIDescriptionInput"]]
ABIDescriptionInput.update_forward_refs()
class ABIGenericDescription(BaseModel):
@@ -42,7 +47,12 @@ class ABIFunctionDescription(BaseModel):
return Web3.sha3(text=signature)[0:4]
def get_signature(self) -> str:
joined_input_types = ",".join(input.type for input in self.inputs)
joined_input_types = ",".join(
input.type
if input.type != "tuple"
else eth_utils.abi.collapse_if_tuple(input.dict())
for input in self.inputs
)
return f"{self.name}({joined_input_types})"

View File

@@ -1,17 +1,15 @@
from enum import Enum
from typing import Any, Dict, List, Optional
from pydantic import BaseModel
from .blocks import Trace
class Classification(Enum):
unknown = "unknown"
swap = "swap"
burn = "burn"
transfer = "transfer"
liquidate = "liquidate"
seize = "seize"
class Protocol(Enum):
@@ -23,6 +21,8 @@ class Protocol(Enum):
curve = "curve"
zero_ex = "0x"
balancer_v1 = "balancer_v1"
compound_v2 = "compound_v2"
cream = "cream"
class ClassifiedTrace(Trace):
@@ -62,12 +62,5 @@ class DecodedCallTrace(CallTrace):
protocol: Optional[Protocol]
gas: Optional[int]
gas_used: Optional[int]
function_name: Optional[str]
function_signature: Optional[str]
class ClassifierSpec(BaseModel):
abi_name: str
protocol: Optional[Protocol] = None
valid_contract_addresses: Optional[List[str]] = None
classifications: Dict[str, Classification] = {}
function_name: str
function_signature: str

View File

@@ -0,0 +1,55 @@
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 Transfer
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) -> Transfer:
raise NotImplementedError()
class SwapClassifier(Classifier):
@staticmethod
def get_classification() -> Classification:
return Classification.swap
@staticmethod
@abstractmethod
def get_swap_recipient(trace: DecodedCallTrace) -> str:
raise NotImplementedError()
class LiquidationClassifier(Classifier):
@staticmethod
def get_classification() -> Classification:
return Classification.liquidate
class SeizeClassifier(Classifier):
@staticmethod
def get_classification() -> Classification:
return Classification.seize
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,8 +1,9 @@
from typing import List, TypeVar
from typing import List
from pydantic import BaseModel
from .classified_traces import Classification, ClassifiedTrace, Protocol
ETH_TOKEN_ADDRESS = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"
class Transfer(BaseModel):
@@ -12,50 +13,4 @@ class Transfer(BaseModel):
from_address: str
to_address: str
amount: int
# To preserve the specific Transfer type
TransferGeneric = TypeVar("TransferGeneric", bound="Transfer")
class EthTransfer(Transfer):
@classmethod
def from_trace(cls, trace: ClassifiedTrace) -> "EthTransfer":
return cls(
block_number=trace.block_number,
transaction_hash=trace.transaction_hash,
trace_address=trace.trace_address,
amount=trace.value,
to_address=trace.to_address,
from_address=trace.from_address,
)
class ERC20Transfer(Transfer):
token_address: str
@classmethod
def from_trace(cls, trace: ClassifiedTrace) -> "ERC20Transfer":
if trace.classification != Classification.transfer or trace.inputs is None:
raise ValueError("Invalid transfer")
if trace.protocol == Protocol.weth:
return cls(
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,
)
else:
return cls(
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,
)

View File

@@ -1,8 +1,8 @@
import json
from hexbytes import HexBytes
from pydantic import BaseModel
from web3.datastructures import AttributeDict
from pydantic import BaseModel
def to_camel(string: str) -> str:

View File

@@ -1,24 +1,24 @@
from typing import List, Optional
from mev_inspect.classifiers.specs import get_classifier
from mev_inspect.schemas.classified_traces import (
ClassifiedTrace,
Classification,
DecodedCallTrace,
)
from mev_inspect.schemas.classifiers import SwapClassifier
from mev_inspect.schemas.swaps import Swap
from mev_inspect.schemas.transfers import ERC20Transfer
from mev_inspect.schemas.transfers import Transfer
from mev_inspect.traces import get_traces_by_transaction_hash
from mev_inspect.transfers import (
build_eth_transfer,
get_child_transfers,
get_transfer,
filter_transfers,
remove_child_transfers_of_transfers,
)
UNISWAP_V2_PAIR_ABI_NAME = "UniswapV2Pair"
UNISWAP_V3_POOL_ABI_NAME = "UniswapV3Pool"
BALANCER_V1_POOL_ABI_NAME = "BPool"
def get_swaps(traces: List[ClassifiedTrace]) -> List[Swap]:
swaps = []
@@ -32,11 +32,16 @@ def _get_swaps_for_transaction(traces: List[ClassifiedTrace]) -> List[Swap]:
ordered_traces = list(sorted(traces, key=lambda t: t.trace_address))
swaps: List[Swap] = []
prior_transfers: List[ERC20Transfer] = []
prior_transfers: List[Transfer] = []
for trace in ordered_traces:
if trace.classification == Classification.transfer:
prior_transfers.append(ERC20Transfer.from_trace(trace))
if not isinstance(trace, DecodedCallTrace):
continue
elif trace.classification == Classification.transfer:
transfer = get_transfer(trace)
if transfer is not None:
prior_transfers.append(transfer)
elif trace.classification == Classification.swap:
child_transfers = get_child_transfers(
@@ -58,9 +63,9 @@ def _get_swaps_for_transaction(traces: List[ClassifiedTrace]) -> List[Swap]:
def _parse_swap(
trace: ClassifiedTrace,
prior_transfers: List[ERC20Transfer],
child_transfers: List[ERC20Transfer],
trace: DecodedCallTrace,
prior_transfers: List[Transfer],
child_transfers: List[Transfer],
) -> Optional[Swap]:
pool_address = trace.to_address
recipient_address = _get_recipient_address(trace)
@@ -68,7 +73,13 @@ def _parse_swap(
if recipient_address is None:
return None
transfers_to_pool = filter_transfers(prior_transfers, to_address=pool_address)
transfers_to_pool = []
if trace.value is not None and trace.value > 0:
transfers_to_pool = [build_eth_transfer(trace)]
if len(transfers_to_pool) == 0:
transfers_to_pool = filter_transfers(prior_transfers, to_address=pool_address)
if len(transfers_to_pool) == 0:
transfers_to_pool = filter_transfers(child_transfers, to_address=pool_address)
@@ -92,6 +103,7 @@ def _parse_swap(
block_number=trace.block_number,
trace_address=trace.trace_address,
pool_address=pool_address,
protocol=trace.protocol,
from_address=transfer_in.from_address,
to_address=transfer_out.to_address,
token_in_address=transfer_in.token_address,
@@ -102,20 +114,9 @@ def _parse_swap(
)
def _get_recipient_address(trace: ClassifiedTrace) -> Optional[str]:
if trace.abi_name == UNISWAP_V3_POOL_ABI_NAME:
return (
trace.inputs["recipient"]
if trace.inputs is not None and "recipient" in trace.inputs
else trace.from_address
)
elif trace.abi_name == UNISWAP_V2_PAIR_ABI_NAME:
return (
trace.inputs["to"]
if trace.inputs is not None and "to" in trace.inputs
else trace.from_address
)
elif trace.abi_name == BALANCER_V1_POOL_ABI_NAME:
return trace.from_address
else:
return None
def _get_recipient_address(trace: DecodedCallTrace) -> Optional[str]:
classifier = get_classifier(trace)
if classifier is not None and issubclass(classifier, SwapClassifier):
return classifier.get_swap_recipient(trace)
return None

View File

@@ -1,49 +1,95 @@
from typing import Dict, List, Optional, Sequence
from mev_inspect.schemas.classified_traces import Classification, ClassifiedTrace
from mev_inspect.schemas.transfers import ERC20Transfer, EthTransfer, TransferGeneric
from mev_inspect.classifiers.specs import get_classifier
from mev_inspect.schemas.classifiers import TransferClassifier
from mev_inspect.schemas.classified_traces import (
ClassifiedTrace,
DecodedCallTrace,
)
from mev_inspect.schemas.transfers import ETH_TOKEN_ADDRESS, Transfer
from mev_inspect.traces import is_child_trace_address, get_child_traces
def get_eth_transfers(traces: List[ClassifiedTrace]) -> List[EthTransfer]:
def get_transfers(traces: List[ClassifiedTrace]) -> List[Transfer]:
transfers = []
for trace in traces:
if trace.value is not None and trace.value > 0:
transfers.append(EthTransfer.from_trace(trace))
transfer = get_transfer(trace)
if transfer is not None:
transfers.append(transfer)
return transfers
def get_transfers(traces: List[ClassifiedTrace]) -> List[ERC20Transfer]:
transfers = []
def get_eth_transfers(traces: List[ClassifiedTrace]) -> List[Transfer]:
transfers = get_transfers(traces)
for trace in traces:
if trace.classification == Classification.transfer:
transfers.append(ERC20Transfer.from_trace(trace))
return [
transfer
for transfer in transfers
if transfer.token_address == ETH_TOKEN_ADDRESS
]
return transfers
def get_transfer(trace: ClassifiedTrace) -> Optional[Transfer]:
if _is_simple_eth_transfer(trace):
return build_eth_transfer(trace)
if isinstance(trace, DecodedCallTrace):
return _build_erc20_transfer(trace)
return None
def _is_simple_eth_transfer(trace: ClassifiedTrace) -> bool:
return (
trace.value is not None
and trace.value > 0
and "input" in trace.action
and trace.action["input"] == "0x"
)
def build_eth_transfer(trace: ClassifiedTrace) -> Transfer:
return Transfer(
block_number=trace.block_number,
transaction_hash=trace.transaction_hash,
trace_address=trace.trace_address,
amount=trace.value,
to_address=trace.to_address,
from_address=trace.from_address,
token_address=ETH_TOKEN_ADDRESS,
)
def _build_erc20_transfer(trace: DecodedCallTrace) -> Optional[Transfer]:
classifier = get_classifier(trace)
if classifier is not None and issubclass(classifier, TransferClassifier):
return classifier.get_transfer(trace)
return None
def get_child_transfers(
transaction_hash: str,
parent_trace_address: List[int],
traces: List[ClassifiedTrace],
) -> List[ERC20Transfer]:
) -> List[Transfer]:
child_transfers = []
for child_trace in get_child_traces(transaction_hash, parent_trace_address, traces):
if child_trace.classification == Classification.transfer:
child_transfers.append(ERC20Transfer.from_trace(child_trace))
transfer = get_transfer(child_trace)
if transfer is not None:
child_transfers.append(transfer)
return child_transfers
def filter_transfers(
transfers: Sequence[TransferGeneric],
transfers: Sequence[Transfer],
to_address: Optional[str] = None,
from_address: Optional[str] = None,
) -> List[TransferGeneric]:
) -> List[Transfer]:
filtered_transfers = []
for transfer in transfers:
@@ -59,8 +105,8 @@ def filter_transfers(
def remove_child_transfers_of_transfers(
transfers: List[ERC20Transfer],
) -> List[ERC20Transfer]:
transfers: List[Transfer],
) -> List[Transfer]:
updated_transfers = []
transfer_addresses_by_transaction: Dict[str, List[List[int]]] = {}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
tests/comp_markets.json Normal file
View File

@@ -0,0 +1 @@
{"0x6c8c6b02e7b2be14d4fa6022dfd6d75921d90e4e": "0x0d8775f648430679a709e98d2b0cb6250d2887ef", "0x5d3a536e4d6dbd6114cc1ead35777bab948e3643": "0x6b175474e89094c44da98b954eedeac495271d0f", "0x158079ee67fce2f58472a96584a73c7ab9ac95c1": "0x1985365e9f78359a9b6ad760e32412f4a445e862", "0x39aa39c021dfbae8fac545936693ac917d5e7563": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", "0xf650c3d88d12db855b8bf7d11be6c55a4e07dcc9": "0xdac17f958d2ee523a2206206994597c13d831ec7", "0xc11b1268c1a384e55c48c2391d8d480264a3a7f4": "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599", "0xb3319f5d18bc0d84dd1b4825dcde5d5f7266d407": "0xe41d2489571d322189246dafa5ebde1f4699f498", "0xf5dce57282a584d2746faf1593d3121fcac444dc": "0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359", "0x35a18000230da775cac24873d00ff85bccded550": "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984", "0x70e36f6bf80a52b3b46b3af8e106cc0ed743e8e4": "0xc00e94cb662c3520282e6f5717214004a7f26888", "0xccf4429db6322d5c611ee964527d42e5d685dd6a": "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599", "0x12392f67bdf24fae0af363c24ac620a2f67dad86": "0x0000000000085d4780b73119b644ae5ecd22b376", "0xface851a4921ce59e912d19329929ce6da6eb0c7": "0x514910771af9ca656af840dff83e8264ecf986ca", "0x95b4ef2869ebd94beb4eee400a99824bf5dc325b": "0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2", "0x4b0181102a0112a2ef11abee5563bb4a3176c9d7": "0x6b3595068778dd592e39a122f4f5a5cf09c90fe2", "0xe65cdb6479bac1e22340e4e755fae7e509ecd06c": "0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9", "0x80a2ae356fc9ef4305676f7a3e2ed04e12c33946": "0x0bc529c00c6401aef6d220be8c6ea1667f6ad93e"}

1
tests/cream_markets.json Normal file

File diff suppressed because one or more lines are too long

View File

@@ -1,11 +1,11 @@
from typing import List
from typing import List, Optional
from mev_inspect.schemas.blocks import TraceType
from mev_inspect.schemas.classified_traces import (
Classification,
ClassifiedTrace,
CallTrace,
DecodedCallTrace,
Protocol,
)
@@ -18,7 +18,7 @@ def make_transfer_trace(
token_address: str,
amount: int,
):
return CallTrace(
return DecodedCallTrace(
transaction_hash=transaction_hash,
block_number=block_number,
type=TraceType.call,
@@ -26,6 +26,9 @@ def make_transfer_trace(
classification=Classification.transfer,
from_address=from_address,
to_address=token_address,
abi_name="ERC20",
function_name="transfer",
function_signature="transfer(address,uint256)",
inputs={
"recipient": to_address,
"amount": amount,
@@ -43,6 +46,8 @@ def make_swap_trace(
from_address: str,
pool_address: str,
abi_name: str,
function_signature: str,
protocol: Optional[Protocol],
recipient_address: str,
recipient_input_key: str,
):
@@ -56,8 +61,11 @@ def make_swap_trace(
classification=Classification.swap,
from_address=from_address,
to_address=pool_address,
function_name="swap",
function_signature=function_signature,
inputs={recipient_input_key: recipient_address},
abi_name=abi_name,
protocol=protocol,
block_hash=str(block_number),
)

View File

@@ -1,9 +1,9 @@
from mev_inspect.arbitrages import get_arbitrages
from mev_inspect.schemas.swaps import Swap
from mev_inspect.swaps import (
from mev_inspect.classifiers.specs.uniswap import (
UNISWAP_V2_PAIR_ABI_NAME,
UNISWAP_V3_POOL_ABI_NAME,
)
from mev_inspect.schemas.swaps import Swap
def test_two_pool_arbitrage(get_transaction_hashes, get_addresses):

140
tests/test_compound.py Normal file
View File

@@ -0,0 +1,140 @@
from mev_inspect.compound_liquidations import get_compound_liquidations
from mev_inspect.schemas.liquidations import Liquidation
from mev_inspect.schemas.classified_traces import Protocol
from mev_inspect.classifiers.trace import TraceClassifier
from tests.utils import load_test_block, load_comp_markets, load_cream_markets
comp_markets = load_comp_markets()
cream_markets = load_cream_markets()
def test_c_ether_liquidations():
block_number = 13234998
transaction_hash = (
"0x78f7e67391c2bacde45e5057241f8b9e21a59330bce4332eecfff8fac279d090"
)
liquidations = [
Liquidation(
liquidated_user="0xb5535a3681cf8d5431b8acfd779e2f79677ecce9",
liquidator_user="0xe0090ec6895c087a393f0e45f1f85098a6c33bef",
collateral_token_address="0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
debt_token_address="0x39aa39c021dfbae8fac545936693ac917d5e7563",
debt_purchase_amount=268066492249420078,
received_amount=4747650169097,
protocol=Protocol.compound_v2,
transaction_hash=transaction_hash,
trace_address=[1],
block_number=block_number,
)
]
block = load_test_block(block_number)
trace_classifier = TraceClassifier()
classified_traces = trace_classifier.classify(block.traces)
result = get_compound_liquidations(classified_traces, comp_markets, cream_markets)
assert result == liquidations
block_number = 13207907
transaction_hash = (
"0x42a575e3f41d24f3bb00ae96f220a8bd1e24e6a6282c2e0059bb7820c61e91b1"
)
liquidations = [
Liquidation(
liquidated_user="0x45df6f00166c3fb77dc16b9e47ff57bc6694e898",
liquidator_user="0xe0090ec6895c087a393f0e45f1f85098a6c33bef",
collateral_token_address="0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
debt_token_address="0x35a18000230da775cac24873d00ff85bccded550",
debt_purchase_amount=414547860568297082,
received_amount=321973320649,
protocol=Protocol.compound_v2,
transaction_hash=transaction_hash,
trace_address=[1],
block_number=block_number,
)
]
block = load_test_block(block_number)
trace_classifier = TraceClassifier()
classified_traces = trace_classifier.classify(block.traces)
result = get_compound_liquidations(classified_traces, comp_markets, cream_markets)
assert result == liquidations
block_number = 13298725
transaction_hash = (
"0x22a98b27a1d2c4f3cba9d65257d18ee961d6c98f21c7eade37da0543847eb654"
)
liquidations = [
Liquidation(
liquidated_user="0xacbcf5d2970eef25f02a27e9d9cd31027b058b9b",
liquidator_user="0xe0090ec6895c087a393f0e45f1f85098a6c33bef",
collateral_token_address="0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
debt_token_address="0x35a18000230da775cac24873d00ff85bccded550",
debt_purchase_amount=1106497772527562662,
received_amount=910895850496,
protocol=Protocol.compound_v2,
transaction_hash=transaction_hash,
trace_address=[1],
block_number=block_number,
)
]
block = load_test_block(block_number)
trace_classifier = TraceClassifier()
classified_traces = trace_classifier.classify(block.traces)
result = get_compound_liquidations(classified_traces, comp_markets, cream_markets)
assert result == liquidations
def test_c_token_liquidation():
block_number = 13326607
transaction_hash = (
"0x012215bedd00147c58e1f59807664914b2abbfc13c260190dc9cfc490be3e343"
)
liquidations = [
Liquidation(
liquidated_user="0xacdd5528c1c92b57045041b5278efa06cdade4d8",
liquidator_user="0xe0090ec6895c087a393f0e45f1f85098a6c33bef",
collateral_token_address="0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
debt_token_address="0x70e36f6bf80a52b3b46b3af8e106cc0ed743e8e4",
debt_purchase_amount=1207055531,
received_amount=21459623305,
protocol=Protocol.compound_v2,
transaction_hash=transaction_hash,
trace_address=[1],
block_number=block_number,
)
]
block = load_test_block(block_number)
trace_classifier = TraceClassifier()
classified_traces = trace_classifier.classify(block.traces)
result = get_compound_liquidations(classified_traces, comp_markets, cream_markets)
assert result == liquidations
def test_cream_token_liquidation():
block_number = 12674514
transaction_hash = (
"0x0809bdbbddcf566e5392682a9bd9d0006a92a4dc441163c791b1136f982994b1"
)
liquidations = [
Liquidation(
liquidated_user="0x46bf9479dc569bc796b7050344845f6564d45fba",
liquidator_user="0xa2863cad9c318669660eb4eca8b3154b90fb4357",
collateral_token_address="0x514910771af9ca656af840dff83e8264ecf986ca",
debt_token_address="0x44fbebd2f576670a6c33f6fc0b00aa8c5753b322",
debt_purchase_amount=14857434973806369550,
received_amount=1547215810826,
protocol=Protocol.cream,
transaction_hash=transaction_hash,
trace_address=[],
block_number=block_number,
)
]
block = load_test_block(block_number)
trace_classifier = TraceClassifier()
classified_traces = trace_classifier.classify(block.traces)
result = get_compound_liquidations(classified_traces, comp_markets, cream_markets)
assert result == liquidations

69
tests/test_decode.py Normal file
View File

@@ -0,0 +1,69 @@
import pydantic
from mev_inspect import decode
from mev_inspect.schemas import abi
def test_decode_function_with_simple_argument():
test_function_name = "testFunction"
test_parameter_name = "testParameter"
test_abi = pydantic.parse_obj_as(
abi.ABI,
[
{
"name": test_function_name,
"type": "function",
"inputs": [{"name": test_parameter_name, "type": "uint256"}],
}
],
)
# 4byte signature of the test function.
# https://www.4byte.directory/signatures/?bytes4_signature=0x350c530b
test_function_selector = "350c530b"
test_function_argument = (
"0000000000000000000000000000000000000000000000000000000000000001"
)
abi_decoder = decode.ABIDecoder(test_abi)
call_data = abi_decoder.decode(
"0x" + test_function_selector + test_function_argument
)
assert call_data.function_name == test_function_name
assert call_data.function_signature == "testFunction(uint256)"
assert call_data.inputs == {test_parameter_name: 1}
def test_decode_function_with_tuple_argument():
test_function_name = "testFunction"
test_tuple_name = "testTuple"
test_parameter_name = "testParameter"
test_abi = pydantic.parse_obj_as(
abi.ABI,
[
{
"name": test_function_name,
"type": "function",
"inputs": [
{
"name": test_tuple_name,
"type": "tuple",
"components": [
{"name": test_parameter_name, "type": "uint256"}
],
}
],
}
],
)
# 4byte signature of the test function.
# https://www.4byte.directory/signatures/?bytes4_signature=0x98568079
test_function_selector = "98568079"
test_function_argument = (
"0000000000000000000000000000000000000000000000000000000000000001"
)
abi_decoder = decode.ABIDecoder(test_abi)
call_data = abi_decoder.decode(
"0x" + test_function_selector + test_function_argument
)
assert call_data.function_name == test_function_name
assert call_data.function_signature == "testFunction((uint256))"
assert call_data.inputs == {test_tuple_name: (1,)}

View File

@@ -1,9 +1,10 @@
from mev_inspect.swaps import (
get_swaps,
from mev_inspect.swaps import get_swaps
from mev_inspect.classifiers.specs.balancer import BALANCER_V1_POOL_ABI_NAME
from mev_inspect.classifiers.specs.uniswap import (
UNISWAP_V2_PAIR_ABI_NAME,
UNISWAP_V3_POOL_ABI_NAME,
BALANCER_V1_POOL_ABI_NAME,
)
from mev_inspect.schemas.classified_traces import Protocol
from .helpers import (
make_unknown_trace,
@@ -64,6 +65,8 @@ def test_swaps(
from_address=alice_address,
pool_address=first_pool_address,
abi_name=UNISWAP_V2_PAIR_ABI_NAME,
protocol=None,
function_signature="swap(uint256,uint256,address,bytes)",
recipient_address=bob_address,
recipient_input_key="to",
),
@@ -83,6 +86,8 @@ def test_swaps(
from_address=bob_address,
pool_address=second_pool_address,
abi_name=UNISWAP_V3_POOL_ABI_NAME,
protocol=None,
function_signature="swap(address,bool,int256,uint160,bytes)",
recipient_address=carl_address,
recipient_input_key="recipient",
),
@@ -129,6 +134,8 @@ def test_swaps(
from_address=bob_address,
pool_address=third_pool_address,
abi_name=BALANCER_V1_POOL_ABI_NAME,
protocol=Protocol.balancer_v1,
function_signature="swapExactAmountIn(address,uint256,address,uint256,uint256)",
recipient_address=bob_address,
recipient_input_key="recipient",
),
@@ -178,7 +185,7 @@ def test_swaps(
assert bal_v1_swap.transaction_hash == third_transaction_hash
assert bal_v1_swap.block_number == block_number
assert bal_v1_swap.trace_address == [6]
assert bal_v1_swap.protocol is None
assert bal_v1_swap.protocol == Protocol.balancer_v1
assert bal_v1_swap.pool_address == third_pool_address
assert bal_v1_swap.from_address == bob_address
assert bal_v1_swap.to_address == bob_address

View File

@@ -1,4 +1,4 @@
from mev_inspect.schemas.transfers import ERC20Transfer
from mev_inspect.schemas.transfers import Transfer
from mev_inspect.transfers import remove_child_transfers_of_transfers
@@ -13,7 +13,7 @@ def test_remove_child_transfers_of_transfers(get_transaction_hashes, get_address
third_token_address,
] = get_addresses(5)
outer_transfer = ERC20Transfer(
outer_transfer = Transfer(
block_number=123,
transaction_hash=transaction_hash,
trace_address=[0],
@@ -23,7 +23,7 @@ def test_remove_child_transfers_of_transfers(get_transaction_hashes, get_address
token_address=first_token_address,
)
inner_transfer = ERC20Transfer(
inner_transfer = Transfer(
**{
**outer_transfer.dict(),
**dict(
@@ -33,7 +33,7 @@ def test_remove_child_transfers_of_transfers(get_transaction_hashes, get_address
}
)
other_transfer = ERC20Transfer(
other_transfer = Transfer(
block_number=123,
transaction_hash=transaction_hash,
trace_address=[1],
@@ -43,7 +43,7 @@ def test_remove_child_transfers_of_transfers(get_transaction_hashes, get_address
token_address=third_token_address,
)
separate_transaction_transfer = ERC20Transfer(
separate_transaction_transfer = Transfer(
**{
**inner_transfer.dict(),
**dict(transaction_hash=other_transaction_hash),

View File

@@ -1,5 +1,6 @@
import json
import os
from typing import Dict
from mev_inspect.schemas.blocks import Block
@@ -14,3 +15,17 @@ def load_test_block(block_number: int) -> Block:
with open(block_path, "r") as block_file:
block_json = json.load(block_file)
return Block(**block_json)
def load_comp_markets() -> Dict[str, str]:
comp_markets_path = f"{THIS_FILE_DIRECTORY}/comp_markets.json"
with open(comp_markets_path, "r") as markets_file:
markets = json.load(markets_file)
return markets
def load_cream_markets() -> Dict[str, str]:
cream_markets_path = f"{THIS_FILE_DIRECTORY}/cream_markets.json"
with open(cream_markets_path, "r") as markets_file:
markets = json.load(markets_file)
return markets