├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── question.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── ci.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── abi ├── aavecore.json ├── aavepool.json ├── bpool.json ├── bproxy.json ├── cether.json ├── comptroller.json ├── ctoken.json ├── curvepool.json ├── curveregistry.json ├── unipair.json └── unirouterv2.json ├── res ├── 11017338.csv ├── 11017338.trace.json ├── balancer_trade.json ├── balancer_trade2.json ├── bot_selfdestruct.json ├── bot_trade.json ├── compound_liquidation.json ├── curve_arb.json ├── dydx_loan.json ├── exact_tokens_for_eth.json ├── exchange_proxy.json ├── liquidation_1.json ├── reverted_arb.json ├── simple_curve_arb.json ├── simple_liquidation.json ├── sushipairs.csv ├── triangular_arb.json ├── v1pairs.csv ├── v2pairs.csv └── zapper1.json ├── scripts └── addrs.py └── src ├── addresses.rs ├── cached_provider.rs ├── inspectors ├── aave.rs ├── balancer.rs ├── batch.rs ├── compound.rs ├── curve.rs ├── erc20.rs ├── mod.rs ├── uniswap.rs └── zeroex.rs ├── lib.rs ├── main.rs ├── mevdb.rs ├── prices.rs ├── reducers ├── arbitrage.rs ├── liquidation.rs ├── mod.rs └── trade.rs ├── test_helpers └── mod.rs ├── traits.rs └── types ├── actions.rs ├── classification.rs ├── evaluation.rs ├── inspection.rs └── mod.rs /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Version** 11 | List the versions of all `mev-inspect-rs` crates you are using. The easiest way to get 12 | this information is using `cargo-tree`. 13 | 14 | `cargo install cargo-tree` 15 | (see install here: https://github.com/sfackler/cargo-tree) 16 | 17 | Then: 18 | 19 | `cargo tree | grep mev-inspect` 20 | 21 | **Platform** 22 | The output of `uname -a` (UNIX), or version and 32 or 64-bit (Windows) 23 | 24 | **Description** 25 | Enter your issue details here. 26 | One way to structure the description: 27 | 28 | [short summary of the bug] 29 | 30 | I tried this code: 31 | 32 | [code sample that causes the bug] 33 | 34 | I expected to see this happen: [explanation] 35 | 36 | Instead, this happened: [explanation] 37 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: feature-request 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: Please use the Discord group for questions 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | Please post your question as a discussion in the Flashbots Discord 11 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 8 | 9 | ## Motivation 10 | 11 | 16 | 17 | ## Solution 18 | 19 | 23 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: pull_request 2 | 3 | name: Tests 4 | 5 | jobs: 6 | tests: 7 | name: Check 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout sources 11 | uses: actions/checkout@v2 12 | 13 | - name: Install stable toolchain 14 | uses: actions-rs/toolchain@v1 15 | with: 16 | profile: minimal 17 | toolchain: stable 18 | override: true 19 | components: rustfmt, clippy 20 | 21 | - name: cargo test 22 | run: cargo test 23 | 24 | - name: cargo fmt 25 | run: cargo fmt --all -- --check 26 | 27 | - name: cargo clippy 28 | run: cargo clippy -- -D warnings 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mev-inspect" 3 | version = "0.1.0" 4 | authors = ["Georgios Konstantopoulos "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | ethers = { version = "0.2.1", features = ["abigen"] } 9 | serde_json = "1.0.61" 10 | once_cell = "1.5.2" 11 | itertools = "0.10.0" 12 | anyhow = "1.0.37" 13 | 14 | # cached provider 15 | thiserror = "1.0.23" 16 | async-trait = "0.1.42" 17 | serde = "1.0.118" 18 | 19 | # postgres connection 20 | tokio = { version = "1.0.2", features = ["macros", "rt-multi-thread"] } 21 | tokio-postgres = "0.7.0" 22 | rust_decimal = { version = "1.10.0", features = ["db-postgres", "db-tokio-postgres"] } 23 | gumdrop = "0.8.0" 24 | futures = "0.3.8" 25 | hex = "0.4.2" 26 | log = "0.4.14" 27 | pretty_env_logger = "0.4.0" 28 | 29 | [features] 30 | postgres-tests = [] 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Flashbots 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #

MEV Inspect

2 | 3 | **Ethereum MEV Inspector in Rust** 4 |
Further documentation on mev-inspect-rs available [here](https://docs.flashbots.net/flashbots-data/mev-inspect-rs/inspect-quick-start) 5 | 6 | ## Inspectors 7 | 8 | - Curve 9 | - Balancer 10 | - Uniswap (& clones) 11 | - Aave 12 | - Compound 13 | - 0x 14 | - DyDx 15 | 16 | ## Installing 17 | 18 | `cargo build --release` 19 | 20 | ## Running the CLI 21 | 22 | ``` 23 | Usage: ./target/release/mev-inspect [OPTIONS] 24 | 25 | Optional arguments: 26 | -h, --help 27 | -r, --reset clear and re-build the database 28 | -o, --overwrite do not skip blocks which already exist 29 | -u, --url URL The tracing / archival node's URL (default: http://localhost:8545) 30 | -c, --cache CACHE Path to where traces will be cached 31 | -d, --db-cfg DB-CFG Database config 32 | -D, --db-table DB-TABLE the table of the database (default: mev_inspections) 33 | 34 | Available commands: 35 | tx inspect a transaction 36 | blocks inspect a range of blocks 37 | ``` 38 | 39 | ## Running the tests 40 | 41 | **Tests require `postgres` installed.** 42 | 43 | `cargo test` 44 | -------------------------------------------------------------------------------- /abi/bproxy.json: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"address","name":"_weth","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"constant":false,"inputs":[{"components":[{"internalType":"address","name":"pool","type":"address"},{"internalType":"address","name":"tokenIn","type":"address"},{"internalType":"address","name":"tokenOut","type":"address"},{"internalType":"uint256","name":"swapAmount","type":"uint256"},{"internalType":"uint256","name":"limitReturnAmount","type":"uint256"},{"internalType":"uint256","name":"maxPrice","type":"uint256"}],"internalType":"struct ExchangeProxy.Swap[]","name":"swaps","type":"tuple[]"},{"internalType":"contract TokenInterface","name":"tokenIn","type":"address"},{"internalType":"contract TokenInterface","name":"tokenOut","type":"address"},{"internalType":"uint256","name":"totalAmountIn","type":"uint256"},{"internalType":"uint256","name":"minTotalAmountOut","type":"uint256"}],"name":"batchSwapExactIn","outputs":[{"internalType":"uint256","name":"totalAmountOut","type":"uint256"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"components":[{"internalType":"address","name":"pool","type":"address"},{"internalType":"address","name":"tokenIn","type":"address"},{"internalType":"address","name":"tokenOut","type":"address"},{"internalType":"uint256","name":"swapAmount","type":"uint256"},{"internalType":"uint256","name":"limitReturnAmount","type":"uint256"},{"internalType":"uint256","name":"maxPrice","type":"uint256"}],"internalType":"struct ExchangeProxy.Swap[]","name":"swaps","type":"tuple[]"},{"internalType":"contract TokenInterface","name":"tokenIn","type":"address"},{"internalType":"contract TokenInterface","name":"tokenOut","type":"address"},{"internalType":"uint256","name":"maxTotalAmountIn","type":"uint256"}],"name":"batchSwapExactOut","outputs":[{"internalType":"uint256","name":"totalAmountIn","type":"uint256"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[],"name":"isOwner","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"components":[{"internalType":"address","name":"pool","type":"address"},{"internalType":"address","name":"tokenIn","type":"address"},{"internalType":"address","name":"tokenOut","type":"address"},{"internalType":"uint256","name":"swapAmount","type":"uint256"},{"internalType":"uint256","name":"limitReturnAmount","type":"uint256"},{"internalType":"uint256","name":"maxPrice","type":"uint256"}],"internalType":"struct ExchangeProxy.Swap[][]","name":"swapSequences","type":"tuple[][]"},{"internalType":"contract TokenInterface","name":"tokenIn","type":"address"},{"internalType":"contract TokenInterface","name":"tokenOut","type":"address"},{"internalType":"uint256","name":"totalAmountIn","type":"uint256"},{"internalType":"uint256","name":"minTotalAmountOut","type":"uint256"}],"name":"multihopBatchSwapExactIn","outputs":[{"internalType":"uint256","name":"totalAmountOut","type":"uint256"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"components":[{"internalType":"address","name":"pool","type":"address"},{"internalType":"address","name":"tokenIn","type":"address"},{"internalType":"address","name":"tokenOut","type":"address"},{"internalType":"uint256","name":"swapAmount","type":"uint256"},{"internalType":"uint256","name":"limitReturnAmount","type":"uint256"},{"internalType":"uint256","name":"maxPrice","type":"uint256"}],"internalType":"struct ExchangeProxy.Swap[][]","name":"swapSequences","type":"tuple[][]"},{"internalType":"contract TokenInterface","name":"tokenIn","type":"address"},{"internalType":"contract TokenInterface","name":"tokenOut","type":"address"},{"internalType":"uint256","name":"maxTotalAmountIn","type":"uint256"}],"name":"multihopBatchSwapExactOut","outputs":[{"internalType":"uint256","name":"totalAmountIn","type":"uint256"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"renounceOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"_registry","type":"address"}],"name":"setRegistry","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"contract TokenInterface","name":"tokenIn","type":"address"},{"internalType":"contract TokenInterface","name":"tokenOut","type":"address"},{"internalType":"uint256","name":"totalAmountIn","type":"uint256"},{"internalType":"uint256","name":"minTotalAmountOut","type":"uint256"},{"internalType":"uint256","name":"nPools","type":"uint256"}],"name":"smartSwapExactIn","outputs":[{"internalType":"uint256","name":"totalAmountOut","type":"uint256"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"internalType":"contract TokenInterface","name":"tokenIn","type":"address"},{"internalType":"contract TokenInterface","name":"tokenOut","type":"address"},{"internalType":"uint256","name":"totalAmountOut","type":"uint256"},{"internalType":"uint256","name":"maxTotalAmountIn","type":"uint256"},{"internalType":"uint256","name":"nPools","type":"uint256"}],"name":"smartSwapExactOut","outputs":[{"internalType":"uint256","name":"totalAmountIn","type":"uint256"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"tokenIn","type":"address"},{"internalType":"address","name":"tokenOut","type":"address"},{"internalType":"uint256","name":"swapAmount","type":"uint256"},{"internalType":"uint256","name":"nPools","type":"uint256"}],"name":"viewSplitExactIn","outputs":[{"components":[{"internalType":"address","name":"pool","type":"address"},{"internalType":"address","name":"tokenIn","type":"address"},{"internalType":"address","name":"tokenOut","type":"address"},{"internalType":"uint256","name":"swapAmount","type":"uint256"},{"internalType":"uint256","name":"limitReturnAmount","type":"uint256"},{"internalType":"uint256","name":"maxPrice","type":"uint256"}],"internalType":"struct ExchangeProxy.Swap[]","name":"swaps","type":"tuple[]"},{"internalType":"uint256","name":"totalOutput","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"tokenIn","type":"address"},{"internalType":"address","name":"tokenOut","type":"address"},{"internalType":"uint256","name":"swapAmount","type":"uint256"},{"internalType":"uint256","name":"nPools","type":"uint256"}],"name":"viewSplitExactOut","outputs":[{"components":[{"internalType":"address","name":"pool","type":"address"},{"internalType":"address","name":"tokenIn","type":"address"},{"internalType":"address","name":"tokenOut","type":"address"},{"internalType":"uint256","name":"swapAmount","type":"uint256"},{"internalType":"uint256","name":"limitReturnAmount","type":"uint256"},{"internalType":"uint256","name":"maxPrice","type":"uint256"}],"internalType":"struct ExchangeProxy.Swap[]","name":"swaps","type":"tuple[]"},{"internalType":"uint256","name":"totalOutput","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"}] 2 | -------------------------------------------------------------------------------- /abi/cether.json: -------------------------------------------------------------------------------- 1 | [{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"spender","type":"address"},{"name":"amount","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"mint","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[],"name":"reserveFactorMantissa","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"account","type":"address"}],"name":"borrowBalanceCurrent","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"exchangeRateStored","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"src","type":"address"},{"name":"dst","type":"address"},{"name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"pendingAdmin","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"owner","type":"address"}],"name":"balanceOfUnderlying","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getCash","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"newComptroller","type":"address"}],"name":"_setComptroller","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalBorrows","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"repayBorrow","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[],"name":"comptroller","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"reduceAmount","type":"uint256"}],"name":"_reduceReserves","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"initialExchangeRateMantissa","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"accrualBlockNumber","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"totalBorrowsCurrent","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"redeemAmount","type":"uint256"}],"name":"redeemUnderlying","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalReserves","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"account","type":"address"}],"name":"borrowBalanceStored","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"accrueInterest","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"dst","type":"address"},{"name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"borrowIndex","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"borrower","type":"address"},{"name":"cTokenCollateral","type":"address"}],"name":"liquidateBorrow","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[],"name":"supplyRatePerBlock","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"liquidator","type":"address"},{"name":"borrower","type":"address"},{"name":"seizeTokens","type":"uint256"}],"name":"seize","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"newPendingAdmin","type":"address"}],"name":"_setPendingAdmin","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"exchangeRateCurrent","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"account","type":"address"}],"name":"getAccountSnapshot","outputs":[{"name":"","type":"uint256"},{"name":"","type":"uint256"},{"name":"","type":"uint256"},{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"borrowAmount","type":"uint256"}],"name":"borrow","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"redeemTokens","type":"uint256"}],"name":"redeem","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"owner","type":"address"},{"name":"spender","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"borrower","type":"address"}],"name":"repayBorrowBehalf","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[],"name":"_acceptAdmin","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"newInterestRateModel","type":"address"}],"name":"_setInterestRateModel","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"interestRateModel","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"admin","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"borrowRatePerBlock","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"newReserveFactorMantissa","type":"uint256"}],"name":"_setReserveFactor","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"isCToken","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[{"name":"comptroller_","type":"address"},{"name":"interestRateModel_","type":"address"},{"name":"initialExchangeRateMantissa_","type":"uint256"},{"name":"name_","type":"string"},{"name":"symbol_","type":"string"},{"name":"decimals_","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":false,"name":"interestAccumulated","type":"uint256"},{"indexed":false,"name":"borrowIndex","type":"uint256"},{"indexed":false,"name":"totalBorrows","type":"uint256"}],"name":"AccrueInterest","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"minter","type":"address"},{"indexed":false,"name":"mintAmount","type":"uint256"},{"indexed":false,"name":"mintTokens","type":"uint256"}],"name":"Mint","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"redeemer","type":"address"},{"indexed":false,"name":"redeemAmount","type":"uint256"},{"indexed":false,"name":"redeemTokens","type":"uint256"}],"name":"Redeem","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"borrower","type":"address"},{"indexed":false,"name":"borrowAmount","type":"uint256"},{"indexed":false,"name":"accountBorrows","type":"uint256"},{"indexed":false,"name":"totalBorrows","type":"uint256"}],"name":"Borrow","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"payer","type":"address"},{"indexed":false,"name":"borrower","type":"address"},{"indexed":false,"name":"repayAmount","type":"uint256"},{"indexed":false,"name":"accountBorrows","type":"uint256"},{"indexed":false,"name":"totalBorrows","type":"uint256"}],"name":"RepayBorrow","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"liquidator","type":"address"},{"indexed":false,"name":"borrower","type":"address"},{"indexed":false,"name":"repayAmount","type":"uint256"},{"indexed":false,"name":"cTokenCollateral","type":"address"},{"indexed":false,"name":"seizeTokens","type":"uint256"}],"name":"LiquidateBorrow","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"oldPendingAdmin","type":"address"},{"indexed":false,"name":"newPendingAdmin","type":"address"}],"name":"NewPendingAdmin","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"oldAdmin","type":"address"},{"indexed":false,"name":"newAdmin","type":"address"}],"name":"NewAdmin","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"oldComptroller","type":"address"},{"indexed":false,"name":"newComptroller","type":"address"}],"name":"NewComptroller","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"oldInterestRateModel","type":"address"},{"indexed":false,"name":"newInterestRateModel","type":"address"}],"name":"NewMarketInterestRateModel","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"oldReserveFactorMantissa","type":"uint256"},{"indexed":false,"name":"newReserveFactorMantissa","type":"uint256"}],"name":"NewReserveFactor","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"admin","type":"address"},{"indexed":false,"name":"reduceAmount","type":"uint256"},{"indexed":false,"name":"newTotalReserves","type":"uint256"}],"name":"ReservesReduced","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"error","type":"uint256"},{"indexed":false,"name":"info","type":"uint256"},{"indexed":false,"name":"detail","type":"uint256"}],"name":"Failure","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"Approval","type":"event"}] 2 | -------------------------------------------------------------------------------- /abi/ctoken.json: -------------------------------------------------------------------------------- 1 | [{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"spender","type":"address"},{"name":"amount","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"repayAmount","type":"uint256"}],"name":"repayBorrow","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"reserveFactorMantissa","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"account","type":"address"}],"name":"borrowBalanceCurrent","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"exchangeRateStored","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"src","type":"address"},{"name":"dst","type":"address"},{"name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"borrower","type":"address"},{"name":"repayAmount","type":"uint256"}],"name":"repayBorrowBehalf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"pendingAdmin","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"owner","type":"address"}],"name":"balanceOfUnderlying","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getCash","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"newComptroller","type":"address"}],"name":"_setComptroller","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalBorrows","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"comptroller","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"reduceAmount","type":"uint256"}],"name":"_reduceReserves","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"initialExchangeRateMantissa","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"accrualBlockNumber","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"underlying","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"totalBorrowsCurrent","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"redeemAmount","type":"uint256"}],"name":"redeemUnderlying","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalReserves","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"account","type":"address"}],"name":"borrowBalanceStored","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"mintAmount","type":"uint256"}],"name":"mint","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"accrueInterest","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"dst","type":"address"},{"name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"borrowIndex","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"supplyRatePerBlock","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"liquidator","type":"address"},{"name":"borrower","type":"address"},{"name":"seizeTokens","type":"uint256"}],"name":"seize","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"newPendingAdmin","type":"address"}],"name":"_setPendingAdmin","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"exchangeRateCurrent","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"account","type":"address"}],"name":"getAccountSnapshot","outputs":[{"name":"","type":"uint256"},{"name":"","type":"uint256"},{"name":"","type":"uint256"},{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"borrowAmount","type":"uint256"}],"name":"borrow","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"redeemTokens","type":"uint256"}],"name":"redeem","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"owner","type":"address"},{"name":"spender","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"_acceptAdmin","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"newInterestRateModel","type":"address"}],"name":"_setInterestRateModel","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"interestRateModel","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"borrower","type":"address"},{"name":"repayAmount","type":"uint256"},{"name":"cTokenCollateral","type":"address"}],"name":"liquidateBorrow","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"admin","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"borrowRatePerBlock","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"newReserveFactorMantissa","type":"uint256"}],"name":"_setReserveFactor","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"isCToken","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[{"name":"underlying_","type":"address"},{"name":"comptroller_","type":"address"},{"name":"interestRateModel_","type":"address"},{"name":"initialExchangeRateMantissa_","type":"uint256"},{"name":"name_","type":"string"},{"name":"symbol_","type":"string"},{"name":"decimals_","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"interestAccumulated","type":"uint256"},{"indexed":false,"name":"borrowIndex","type":"uint256"},{"indexed":false,"name":"totalBorrows","type":"uint256"}],"name":"AccrueInterest","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"minter","type":"address"},{"indexed":false,"name":"mintAmount","type":"uint256"},{"indexed":false,"name":"mintTokens","type":"uint256"}],"name":"Mint","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"redeemer","type":"address"},{"indexed":false,"name":"redeemAmount","type":"uint256"},{"indexed":false,"name":"redeemTokens","type":"uint256"}],"name":"Redeem","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"borrower","type":"address"},{"indexed":false,"name":"borrowAmount","type":"uint256"},{"indexed":false,"name":"accountBorrows","type":"uint256"},{"indexed":false,"name":"totalBorrows","type":"uint256"}],"name":"Borrow","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"payer","type":"address"},{"indexed":false,"name":"borrower","type":"address"},{"indexed":false,"name":"repayAmount","type":"uint256"},{"indexed":false,"name":"accountBorrows","type":"uint256"},{"indexed":false,"name":"totalBorrows","type":"uint256"}],"name":"RepayBorrow","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"liquidator","type":"address"},{"indexed":false,"name":"borrower","type":"address"},{"indexed":false,"name":"repayAmount","type":"uint256"},{"indexed":false,"name":"cTokenCollateral","type":"address"},{"indexed":false,"name":"seizeTokens","type":"uint256"}],"name":"LiquidateBorrow","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"oldPendingAdmin","type":"address"},{"indexed":false,"name":"newPendingAdmin","type":"address"}],"name":"NewPendingAdmin","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"oldAdmin","type":"address"},{"indexed":false,"name":"newAdmin","type":"address"}],"name":"NewAdmin","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"oldComptroller","type":"address"},{"indexed":false,"name":"newComptroller","type":"address"}],"name":"NewComptroller","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"oldInterestRateModel","type":"address"},{"indexed":false,"name":"newInterestRateModel","type":"address"}],"name":"NewMarketInterestRateModel","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"oldReserveFactorMantissa","type":"uint256"},{"indexed":false,"name":"newReserveFactorMantissa","type":"uint256"}],"name":"NewReserveFactor","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"admin","type":"address"},{"indexed":false,"name":"reduceAmount","type":"uint256"},{"indexed":false,"name":"newTotalReserves","type":"uint256"}],"name":"ReservesReduced","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"error","type":"uint256"},{"indexed":false,"name":"info","type":"uint256"},{"indexed":false,"name":"detail","type":"uint256"}],"name":"Failure","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"Approval","type":"event"}] 2 | -------------------------------------------------------------------------------- /abi/curvepool.json: -------------------------------------------------------------------------------- 1 | [{"inputs": [{"indexed": true, "type": "address", "name": "buyer"}, {"indexed": false, "type": "int128", "name": "sold_id"}, {"indexed": false, "type": "uint256", "name": "tokens_sold"}, {"indexed": false, "type": "int128", "name": "bought_id"}, {"indexed": false, "type": "uint256", "name": "tokens_bought"}], "type": "event", "name": "TokenExchange", "anonymous": false}, {"inputs": [{"indexed": true, "type": "address", "name": "buyer"}, {"indexed": false, "type": "int128", "name": "sold_id"}, {"indexed": false, "type": "uint256", "name": "tokens_sold"}, {"indexed": false, "type": "int128", "name": "bought_id"}, {"indexed": false, "type": "uint256", "name": "tokens_bought"}], "type": "event", "name": "TokenExchangeUnderlying", "anonymous": false}, {"inputs": [{"indexed": true, "type": "address", "name": "provider"}, {"indexed": false, "type": "uint256[2]", "name": "token_amounts"}, {"indexed": false, "type": "uint256[2]", "name": "fees"}, {"indexed": false, "type": "uint256", "name": "invariant"}, {"indexed": false, "type": "uint256", "name": "token_supply"}], "type": "event", "name": "AddLiquidity", "anonymous": false}, {"inputs": [{"indexed": true, "type": "address", "name": "provider"}, {"indexed": false, "type": "uint256[2]", "name": "token_amounts"}, {"indexed": false, "type": "uint256[2]", "name": "fees"}, {"indexed": false, "type": "uint256", "name": "token_supply"}], "type": "event", "name": "RemoveLiquidity", "anonymous": false}, {"inputs": [{"indexed": true, "type": "address", "name": "provider"}, {"indexed": false, "type": "uint256[2]", "name": "token_amounts"}, {"indexed": false, "type": "uint256[2]", "name": "fees"}, {"indexed": false, "type": "uint256", "name": "invariant"}, {"indexed": false, "type": "uint256", "name": "token_supply"}], "type": "event", "name": "RemoveLiquidityImbalance", "anonymous": false}, {"inputs": [{"indexed": true, "type": "uint256", "name": "deadline", "unit": "sec"}, {"indexed": true, "type": "address", "name": "admin"}], "type": "event", "name": "CommitNewAdmin", "anonymous": false}, {"inputs": [{"indexed": true, "type": "address", "name": "admin"}], "type": "event", "name": "NewAdmin", "anonymous": false}, {"inputs": [{"indexed": true, "type": "uint256", "name": "deadline", "unit": "sec"}, {"indexed": false, "type": "uint256", "name": "A"}, {"indexed": false, "type": "uint256", "name": "fee"}, {"indexed": false, "type": "uint256", "name": "admin_fee"}], "type": "event", "name": "CommitNewParameters", "anonymous": false}, {"inputs": [{"indexed": false, "type": "uint256", "name": "A"}, {"indexed": false, "type": "uint256", "name": "fee"}, {"indexed": false, "type": "uint256", "name": "admin_fee"}], "type": "event", "name": "NewParameters", "anonymous": false}, {"inputs": [{"type": "address[2]", "name": "_coins"}, {"type": "address[2]", "name": "_underlying_coins"}, {"type": "address", "name": "_pool_token"}, {"type": "uint256", "name": "_A"}, {"type": "uint256", "name": "_fee"}], "constant": false, "outputs": [], "stateMutability": "view", "payable": false, "type": "constructor"}, {"inputs": [], "constant": true, "name": "get_virtual_price", "outputs": [{"type": "uint256", "name": "out"}], "stateMutability": "nonpayable", "payable": false, "type": "function"}, {"inputs": [{"type": "uint256[2]", "name": "amounts"}, {"type": "bool", "name": "deposit"}], "constant": true, "name": "calc_token_amount", "outputs": [{"type": "uint256", "name": "out"}], "stateMutability": "nonpayable", "payable": false, "type": "function"}, {"inputs": [{"type": "uint256[2]", "name": "amounts"}, {"type": "uint256", "name": "min_mint_amount"}], "constant": false, "name": "add_liquidity", "outputs": [], "stateMutability": "view", "payable": false, "type": "function"}, {"inputs": [{"type": "int128", "name": "i"}, {"type": "int128", "name": "j"}, {"type": "uint256", "name": "dx"}], "constant": true, "name": "get_dy", "outputs": [{"type": "uint256", "name": "out"}], "stateMutability": "nonpayable", "payable": false, "type": "function"}, {"inputs": [{"type": "int128", "name": "i"}, {"type": "int128", "name": "j"}, {"type": "uint256", "name": "dy"}], "constant": true, "name": "get_dx", "outputs": [{"type": "uint256", "name": "out"}], "stateMutability": "nonpayable", "payable": false, "type": "function"}, {"inputs": [{"type": "int128", "name": "i"}, {"type": "int128", "name": "j"}, {"type": "uint256", "name": "dx"}], "constant": true, "name": "get_dy_underlying", "outputs": [{"type": "uint256", "name": "out"}], "stateMutability": "nonpayable", "payable": false, "type": "function"}, {"inputs": [{"type": "int128", "name": "i"}, {"type": "int128", "name": "j"}, {"type": "uint256", "name": "dy"}], "constant": true, "name": "get_dx_underlying", "outputs": [{"type": "uint256", "name": "out"}], "stateMutability": "nonpayable", "payable": false, "type": "function"}, {"inputs": [{"type": "int128", "name": "i"}, {"type": "int128", "name": "j"}, {"type": "uint256", "name": "dx"}, {"type": "uint256", "name": "min_dy"}], "constant": false, "name": "exchange", "outputs": [], "stateMutability": "view", "payable": false, "type": "function"}, {"inputs": [{"type": "int128", "name": "i"}, {"type": "int128", "name": "j"}, {"type": "uint256", "name": "dx"}, {"type": "uint256", "name": "min_dy"}], "constant": false, "name": "exchange_underlying", "outputs": [], "stateMutability": "view", "payable": false, "type": "function"}, {"inputs": [{"type": "uint256", "name": "_amount"}, {"type": "uint256[2]", "name": "min_amounts"}], "constant": false, "name": "remove_liquidity", "outputs": [], "stateMutability": "view", "payable": false, "type": "function"}, {"inputs": [{"type": "uint256[2]", "name": "amounts"}, {"type": "uint256", "name": "max_burn_amount"}], "constant": false, "name": "remove_liquidity_imbalance", "outputs": [], "stateMutability": "view", "payable": false, "type": "function"}, {"inputs": [{"type": "uint256", "name": "amplification"}, {"type": "uint256", "name": "new_fee"}, {"type": "uint256", "name": "new_admin_fee"}], "constant": false, "name": "commit_new_parameters", "outputs": [], "stateMutability": "view", "payable": false, "type": "function"}, {"inputs": [], "constant": false, "name": "apply_new_parameters", "outputs": [], "stateMutability": "view", "payable": false, "type": "function"}, {"inputs": [], "constant": false, "name": "revert_new_parameters", "outputs": [], "stateMutability": "view", "payable": false, "type": "function"}, {"inputs": [{"type": "address", "name": "_owner"}], "constant": false, "name": "commit_transfer_ownership", "outputs": [], "stateMutability": "view", "payable": false, "type": "function"}, {"inputs": [], "constant": false, "name": "apply_transfer_ownership", "outputs": [], "stateMutability": "view", "payable": false, "type": "function"}, {"inputs": [], "constant": false, "name": "revert_transfer_ownership", "outputs": [], "stateMutability": "view", "payable": false, "type": "function"}, {"inputs": [], "constant": false, "name": "withdraw_admin_fees", "outputs": [], "stateMutability": "view", "payable": false, "type": "function"}, {"inputs": [], "constant": false, "name": "kill_me", "outputs": [], "stateMutability": "view", "payable": false, "type": "function"}, {"inputs": [], "constant": false, "name": "unkill_me", "outputs": [], "stateMutability": "view", "payable": false, "type": "function"}, {"inputs": [{"type": "int128", "name": "arg0"}], "constant": true, "name": "coins", "outputs": [{"type": "address", "name": "out"}], "stateMutability": "nonpayable", "payable": false, "type": "function"}, {"inputs": [{"type": "int128", "name": "arg0"}], "constant": true, "name": "underlying_coins", "outputs": [{"type": "address", "name": "out"}], "stateMutability": "nonpayable", "payable": false, "type": "function"}, {"inputs": [{"type": "int128", "name": "arg0"}], "constant": true, "name": "balances", "outputs": [{"type": "uint256", "name": "out"}], "stateMutability": "nonpayable", "payable": false, "type": "function"}, {"inputs": [], "constant": true, "name": "A", "outputs": [{"type": "uint256", "name": "out"}], "stateMutability": "nonpayable", "payable": false, "type": "function"}, {"inputs": [], "constant": true, "name": "fee", "outputs": [{"type": "uint256", "name": "out"}], "stateMutability": "nonpayable", "payable": false, "type": "function"}, {"inputs": [], "constant": true, "name": "admin_fee", "outputs": [{"type": "uint256", "name": "out"}], "stateMutability": "nonpayable", "payable": false, "type": "function"}, {"inputs": [], "constant": true, "name": "owner", "outputs": [{"type": "address", "name": "out"}], "stateMutability": "nonpayable", "payable": false, "type": "function"}, {"inputs": [], "constant": true, "name": "admin_actions_deadline", "outputs": [{"type": "uint256", "name": "out", "unit": "sec"}], "stateMutability": "nonpayable", "payable": false, "type": "function"}, {"inputs": [], "constant": true, "name": "transfer_ownership_deadline", "outputs": [{"type": "uint256", "name": "out", "unit": "sec"}], "stateMutability": "nonpayable", "payable": false, "type": "function"}, {"inputs": [], "constant": true, "name": "future_A", "outputs": [{"type": "uint256", "name": "out"}], "stateMutability": "nonpayable", "payable": false, "type": "function"}, {"inputs": [], "constant": true, "name": "future_fee", "outputs": [{"type": "uint256", "name": "out"}], "stateMutability": "nonpayable", "payable": false, "type": "function"}, {"inputs": [], "constant": true, "name": "future_admin_fee", "outputs": [{"type": "uint256", "name": "out"}], "stateMutability": "nonpayable", "payable": false, "type": "function"}, {"inputs": [], "constant": true, "name": "future_owner", "outputs": [{"type": "address", "name": "out"}], "stateMutability": "nonpayable", "payable": false, "type": "function"}] -------------------------------------------------------------------------------- /abi/curveregistry.json: -------------------------------------------------------------------------------- 1 | [{"name":"PoolAdded","inputs":[{"type":"address","name":"pool","indexed":true},{"type":"bytes","name":"rate_method_id","indexed":false}],"anonymous":false,"type":"event"},{"name":"PoolRemoved","inputs":[{"type":"address","name":"pool","indexed":true}],"anonymous":false,"type":"event"},{"outputs":[],"inputs":[{"type":"address","name":"_address_provider"},{"type":"address","name":"_gauge_controller"}],"stateMutability":"nonpayable","type":"constructor"},{"name":"find_pool_for_coins","outputs":[{"type":"address","name":""}],"inputs":[{"type":"address","name":"_from"},{"type":"address","name":"_to"}],"stateMutability":"view","type":"function"},{"name":"find_pool_for_coins","outputs":[{"type":"address","name":""}],"inputs":[{"type":"address","name":"_from"},{"type":"address","name":"_to"},{"type":"uint256","name":"i"}],"stateMutability":"view","type":"function"},{"name":"get_n_coins","outputs":[{"type":"uint256[2]","name":""}],"inputs":[{"type":"address","name":"_pool"}],"stateMutability":"view","type":"function","gas":1704},{"name":"get_coins","outputs":[{"type":"address[8]","name":""}],"inputs":[{"type":"address","name":"_pool"}],"stateMutability":"view","type":"function","gas":12285},{"name":"get_underlying_coins","outputs":[{"type":"address[8]","name":""}],"inputs":[{"type":"address","name":"_pool"}],"stateMutability":"view","type":"function","gas":12347},{"name":"get_decimals","outputs":[{"type":"uint256[8]","name":""}],"inputs":[{"type":"address","name":"_pool"}],"stateMutability":"view","type":"function","gas":8199},{"name":"get_underlying_decimals","outputs":[{"type":"uint256[8]","name":""}],"inputs":[{"type":"address","name":"_pool"}],"stateMutability":"view","type":"function","gas":8261},{"name":"get_rates","outputs":[{"type":"uint256[8]","name":""}],"inputs":[{"type":"address","name":"_pool"}],"stateMutability":"view","type":"function","gas":34780},{"name":"get_gauges","outputs":[{"type":"address[10]","name":""},{"type":"int128[10]","name":""}],"inputs":[{"type":"address","name":"_pool"}],"stateMutability":"view","type":"function","gas":20310},{"name":"get_balances","outputs":[{"type":"uint256[8]","name":""}],"inputs":[{"type":"address","name":"_pool"}],"stateMutability":"view","type":"function","gas":16818},{"name":"get_underlying_balances","outputs":[{"type":"uint256[8]","name":""}],"inputs":[{"type":"address","name":"_pool"}],"stateMutability":"view","type":"function","gas":158953},{"name":"get_virtual_price_from_lp_token","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"address","name":"_token"}],"stateMutability":"view","type":"function","gas":2080},{"name":"get_A","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"address","name":"_pool"}],"stateMutability":"view","type":"function","gas":1198},{"name":"get_parameters","outputs":[{"type":"uint256","name":"A"},{"type":"uint256","name":"future_A"},{"type":"uint256","name":"fee"},{"type":"uint256","name":"admin_fee"},{"type":"uint256","name":"future_fee"},{"type":"uint256","name":"future_admin_fee"},{"type":"address","name":"future_owner"},{"type":"uint256","name":"initial_A"},{"type":"uint256","name":"initial_A_time"},{"type":"uint256","name":"future_A_time"}],"inputs":[{"type":"address","name":"_pool"}],"stateMutability":"view","type":"function","gas":6458},{"name":"get_fees","outputs":[{"type":"uint256[2]","name":""}],"inputs":[{"type":"address","name":"_pool"}],"stateMutability":"view","type":"function","gas":1603},{"name":"get_admin_balances","outputs":[{"type":"uint256[8]","name":""}],"inputs":[{"type":"address","name":"_pool"}],"stateMutability":"view","type":"function","gas":36719},{"name":"get_coin_indices","outputs":[{"type":"int128","name":""},{"type":"int128","name":""},{"type":"bool","name":""}],"inputs":[{"type":"address","name":"_pool"},{"type":"address","name":"_from"},{"type":"address","name":"_to"}],"stateMutability":"view","type":"function","gas":27456},{"name":"estimate_gas_used","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"address","name":"_pool"},{"type":"address","name":"_from"},{"type":"address","name":"_to"}],"stateMutability":"view","type":"function","gas":32329},{"name":"add_pool","outputs":[],"inputs":[{"type":"address","name":"_pool"},{"type":"uint256","name":"_n_coins"},{"type":"address","name":"_lp_token"},{"type":"bytes32","name":"_rate_method_id"},{"type":"uint256","name":"_decimals"},{"type":"uint256","name":"_underlying_decimals"},{"type":"bool","name":"_has_initial_A"},{"type":"bool","name":"_is_v1"}],"stateMutability":"nonpayable","type":"function","gas":10196577},{"name":"add_pool_without_underlying","outputs":[],"inputs":[{"type":"address","name":"_pool"},{"type":"uint256","name":"_n_coins"},{"type":"address","name":"_lp_token"},{"type":"bytes32","name":"_rate_method_id"},{"type":"uint256","name":"_decimals"},{"type":"uint256","name":"_use_rates"},{"type":"bool","name":"_has_initial_A"},{"type":"bool","name":"_is_v1"}],"stateMutability":"nonpayable","type":"function","gas":5590664},{"name":"add_metapool","outputs":[],"inputs":[{"type":"address","name":"_pool"},{"type":"uint256","name":"_n_coins"},{"type":"address","name":"_lp_token"},{"type":"uint256","name":"_decimals"}],"stateMutability":"nonpayable","type":"function","gas":10226976},{"name":"remove_pool","outputs":[],"inputs":[{"type":"address","name":"_pool"}],"stateMutability":"nonpayable","type":"function","gas":779646579509},{"name":"set_pool_gas_estimates","outputs":[],"inputs":[{"type":"address[5]","name":"_addr"},{"type":"uint256[2][5]","name":"_amount"}],"stateMutability":"nonpayable","type":"function","gas":355578},{"name":"set_coin_gas_estimates","outputs":[],"inputs":[{"type":"address[10]","name":"_addr"},{"type":"uint256[10]","name":"_amount"}],"stateMutability":"nonpayable","type":"function","gas":357165},{"name":"set_gas_estimate_contract","outputs":[],"inputs":[{"type":"address","name":"_pool"},{"type":"address","name":"_estimator"}],"stateMutability":"nonpayable","type":"function","gas":37747},{"name":"set_liquidity_gauges","outputs":[],"inputs":[{"type":"address","name":"_pool"},{"type":"address[10]","name":"_liquidity_gauges"}],"stateMutability":"nonpayable","type":"function","gas":365793},{"name":"address_provider","outputs":[{"type":"address","name":""}],"inputs":[],"stateMutability":"view","type":"function","gas":2111},{"name":"gauge_controller","outputs":[{"type":"address","name":""}],"inputs":[],"stateMutability":"view","type":"function","gas":2141},{"name":"pool_list","outputs":[{"type":"address","name":""}],"inputs":[{"type":"uint256","name":"arg0"}],"stateMutability":"view","type":"function","gas":2280},{"name":"pool_count","outputs":[{"type":"uint256","name":""}],"inputs":[],"stateMutability":"view","type":"function","gas":2201},{"name":"get_pool_from_lp_token","outputs":[{"type":"address","name":""}],"inputs":[{"type":"address","name":"arg0"}],"stateMutability":"view","type":"function","gas":2446},{"name":"get_lp_token","outputs":[{"type":"address","name":""}],"inputs":[{"type":"address","name":"arg0"}],"stateMutability":"view","type":"function","gas":2476}] 2 | -------------------------------------------------------------------------------- /abi/unipair.json: -------------------------------------------------------------------------------- 1 | [{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount0","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount1","type":"uint256"},{"indexed":true,"internalType":"address","name":"to","type":"address"}],"name":"Burn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount0","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount1","type":"uint256"}],"name":"Mint","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount0In","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount1In","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount0Out","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount1Out","type":"uint256"},{"indexed":true,"internalType":"address","name":"to","type":"address"}],"name":"Swap","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint112","name":"reserve0","type":"uint112"},{"indexed":false,"internalType":"uint112","name":"reserve1","type":"uint112"}],"name":"Sync","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"constant":true,"inputs":[],"name":"DOMAIN_SEPARATOR","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"MINIMUM_LIQUIDITY","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"PERMIT_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"to","type":"address"}],"name":"burn","outputs":[{"internalType":"uint256","name":"amount0","type":"uint256"},{"internalType":"uint256","name":"amount1","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"factory","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getReserves","outputs":[{"internalType":"uint112","name":"_reserve0","type":"uint112"},{"internalType":"uint112","name":"_reserve1","type":"uint112"},{"internalType":"uint32","name":"_blockTimestampLast","type":"uint32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"_token0","type":"address"},{"internalType":"address","name":"_token1","type":"address"}],"name":"initialize","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"kLast","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"to","type":"address"}],"name":"mint","outputs":[{"internalType":"uint256","name":"liquidity","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"nonces","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"permit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"price0CumulativeLast","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"price1CumulativeLast","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"to","type":"address"}],"name":"skim","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"amount0Out","type":"uint256"},{"internalType":"uint256","name":"amount1Out","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"swap","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"sync","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"token0","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"token1","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"}] 2 | 3 | -------------------------------------------------------------------------------- /abi/unirouterv2.json: -------------------------------------------------------------------------------- 1 | [{"inputs": [{"internalType": "address", "name": "_factory", "type": "address"}, {"internalType": "address", "name": "_WETH", "type": "address"}], "stateMutability": "nonpayable", "type": "constructor"}, {"inputs": [], "name": "WETH", "outputs": [{"internalType": "address", "name": "", "type": "address"}], "stateMutability": "view", "type": "function"}, {"inputs": [{"internalType": "address", "name": "tokenA", "type": "address"}, {"internalType": "address", "name": "tokenB", "type": "address"}, {"internalType": "uint256", "name": "amountADesired", "type": "uint256"}, {"internalType": "uint256", "name": "amountBDesired", "type": "uint256"}, {"internalType": "uint256", "name": "amountAMin", "type": "uint256"}, {"internalType": "uint256", "name": "amountBMin", "type": "uint256"}, {"internalType": "address", "name": "to", "type": "address"}, {"internalType": "uint256", "name": "deadline", "type": "uint256"}], "name": "addLiquidity", "outputs": [{"internalType": "uint256", "name": "amountA", "type": "uint256"}, {"internalType": "uint256", "name": "amountB", "type": "uint256"}, {"internalType": "uint256", "name": "liquidity", "type": "uint256"}], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [{"internalType": "address", "name": "token", "type": "address"}, {"internalType": "uint256", "name": "amountTokenDesired", "type": "uint256"}, {"internalType": "uint256", "name": "amountTokenMin", "type": "uint256"}, {"internalType": "uint256", "name": "amountETHMin", "type": "uint256"}, {"internalType": "address", "name": "to", "type": "address"}, {"internalType": "uint256", "name": "deadline", "type": "uint256"}], "name": "addLiquidityETH", "outputs": [{"internalType": "uint256", "name": "amountToken", "type": "uint256"}, {"internalType": "uint256", "name": "amountETH", "type": "uint256"}, {"internalType": "uint256", "name": "liquidity", "type": "uint256"}], "stateMutability": "payable", "type": "function"}, {"inputs": [], "name": "factory", "outputs": [{"internalType": "address", "name": "", "type": "address"}], "stateMutability": "view", "type": "function"}, {"inputs": [{"internalType": "uint256", "name": "amountOut", "type": "uint256"}, {"internalType": "uint256", "name": "reserveIn", "type": "uint256"}, {"internalType": "uint256", "name": "reserveOut", "type": "uint256"}], "name": "getAmountIn", "outputs": [{"internalType": "uint256", "name": "amountIn", "type": "uint256"}], "stateMutability": "pure", "type": "function"}, {"inputs": [{"internalType": "uint256", "name": "amountIn", "type": "uint256"}, {"internalType": "uint256", "name": "reserveIn", "type": "uint256"}, {"internalType": "uint256", "name": "reserveOut", "type": "uint256"}], "name": "getAmountOut", "outputs": [{"internalType": "uint256", "name": "amountOut", "type": "uint256"}], "stateMutability": "pure", "type": "function"}, {"inputs": [{"internalType": "uint256", "name": "amountOut", "type": "uint256"}, {"internalType": "address[]", "name": "path", "type": "address[]"}], "name": "getAmountsIn", "outputs": [{"internalType": "uint256[]", "name": "amounts", "type": "uint256[]"}], "stateMutability": "view", "type": "function"}, {"inputs": [{"internalType": "uint256", "name": "amountIn", "type": "uint256"}, {"internalType": "address[]", "name": "path", "type": "address[]"}], "name": "getAmountsOut", "outputs": [{"internalType": "uint256[]", "name": "amounts", "type": "uint256[]"}], "stateMutability": "view", "type": "function"}, {"inputs": [{"internalType": "uint256", "name": "amountA", "type": "uint256"}, {"internalType": "uint256", "name": "reserveA", "type": "uint256"}, {"internalType": "uint256", "name": "reserveB", "type": "uint256"}], "name": "quote", "outputs": [{"internalType": "uint256", "name": "amountB", "type": "uint256"}], "stateMutability": "pure", "type": "function"}, {"inputs": [{"internalType": "address", "name": "tokenA", "type": "address"}, {"internalType": "address", "name": "tokenB", "type": "address"}, {"internalType": "uint256", "name": "liquidity", "type": "uint256"}, {"internalType": "uint256", "name": "amountAMin", "type": "uint256"}, {"internalType": "uint256", "name": "amountBMin", "type": "uint256"}, {"internalType": "address", "name": "to", "type": "address"}, {"internalType": "uint256", "name": "deadline", "type": "uint256"}], "name": "removeLiquidity", "outputs": [{"internalType": "uint256", "name": "amountA", "type": "uint256"}, {"internalType": "uint256", "name": "amountB", "type": "uint256"}], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [{"internalType": "address", "name": "token", "type": "address"}, {"internalType": "uint256", "name": "liquidity", "type": "uint256"}, {"internalType": "uint256", "name": "amountTokenMin", "type": "uint256"}, {"internalType": "uint256", "name": "amountETHMin", "type": "uint256"}, {"internalType": "address", "name": "to", "type": "address"}, {"internalType": "uint256", "name": "deadline", "type": "uint256"}], "name": "removeLiquidityETH", "outputs": [{"internalType": "uint256", "name": "amountToken", "type": "uint256"}, {"internalType": "uint256", "name": "amountETH", "type": "uint256"}], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [{"internalType": "address", "name": "token", "type": "address"}, {"internalType": "uint256", "name": "liquidity", "type": "uint256"}, {"internalType": "uint256", "name": "amountTokenMin", "type": "uint256"}, {"internalType": "uint256", "name": "amountETHMin", "type": "uint256"}, {"internalType": "address", "name": "to", "type": "address"}, {"internalType": "uint256", "name": "deadline", "type": "uint256"}], "name": "removeLiquidityETHSupportingFeeOnTransferTokens", "outputs": [{"internalType": "uint256", "name": "amountETH", "type": "uint256"}], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [{"internalType": "address", "name": "token", "type": "address"}, {"internalType": "uint256", "name": "liquidity", "type": "uint256"}, {"internalType": "uint256", "name": "amountTokenMin", "type": "uint256"}, {"internalType": "uint256", "name": "amountETHMin", "type": "uint256"}, {"internalType": "address", "name": "to", "type": "address"}, {"internalType": "uint256", "name": "deadline", "type": "uint256"}, {"internalType": "bool", "name": "approveMax", "type": "bool"}, {"internalType": "uint8", "name": "v", "type": "uint8"}, {"internalType": "bytes32", "name": "r", "type": "bytes32"}, {"internalType": "bytes32", "name": "s", "type": "bytes32"}], "name": "removeLiquidityETHWithPermit", "outputs": [{"internalType": "uint256", "name": "amountToken", "type": "uint256"}, {"internalType": "uint256", "name": "amountETH", "type": "uint256"}], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [{"internalType": "address", "name": "token", "type": "address"}, {"internalType": "uint256", "name": "liquidity", "type": "uint256"}, {"internalType": "uint256", "name": "amountTokenMin", "type": "uint256"}, {"internalType": "uint256", "name": "amountETHMin", "type": "uint256"}, {"internalType": "address", "name": "to", "type": "address"}, {"internalType": "uint256", "name": "deadline", "type": "uint256"}, {"internalType": "bool", "name": "approveMax", "type": "bool"}, {"internalType": "uint8", "name": "v", "type": "uint8"}, {"internalType": "bytes32", "name": "r", "type": "bytes32"}, {"internalType": "bytes32", "name": "s", "type": "bytes32"}], "name": "removeLiquidityETHWithPermitSupportingFeeOnTransferTokens", "outputs": [{"internalType": "uint256", "name": "amountETH", "type": "uint256"}], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [{"internalType": "address", "name": "tokenA", "type": "address"}, {"internalType": "address", "name": "tokenB", "type": "address"}, {"internalType": "uint256", "name": "liquidity", "type": "uint256"}, {"internalType": "uint256", "name": "amountAMin", "type": "uint256"}, {"internalType": "uint256", "name": "amountBMin", "type": "uint256"}, {"internalType": "address", "name": "to", "type": "address"}, {"internalType": "uint256", "name": "deadline", "type": "uint256"}, {"internalType": "bool", "name": "approveMax", "type": "bool"}, {"internalType": "uint8", "name": "v", "type": "uint8"}, {"internalType": "bytes32", "name": "r", "type": "bytes32"}, {"internalType": "bytes32", "name": "s", "type": "bytes32"}], "name": "removeLiquidityWithPermit", "outputs": [{"internalType": "uint256", "name": "amountA", "type": "uint256"}, {"internalType": "uint256", "name": "amountB", "type": "uint256"}], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [{"internalType": "uint256", "name": "amountOut", "type": "uint256"}, {"internalType": "address[]", "name": "path", "type": "address[]"}, {"internalType": "address", "name": "to", "type": "address"}, {"internalType": "uint256", "name": "deadline", "type": "uint256"}], "name": "swapETHForExactTokens", "outputs": [{"internalType": "uint256[]", "name": "amounts", "type": "uint256[]"}], "stateMutability": "payable", "type": "function"}, {"inputs": [{"internalType": "uint256", "name": "amountOutMin", "type": "uint256"}, {"internalType": "address[]", "name": "path", "type": "address[]"}, {"internalType": "address", "name": "to", "type": "address"}, {"internalType": "uint256", "name": "deadline", "type": "uint256"}], "name": "swapExactETHForTokens", "outputs": [{"internalType": "uint256[]", "name": "amounts", "type": "uint256[]"}], "stateMutability": "payable", "type": "function"}, {"inputs": [{"internalType": "uint256", "name": "amountOutMin", "type": "uint256"}, {"internalType": "address[]", "name": "path", "type": "address[]"}, {"internalType": "address", "name": "to", "type": "address"}, {"internalType": "uint256", "name": "deadline", "type": "uint256"}], "name": "swapExactETHForTokensSupportingFeeOnTransferTokens", "outputs": [], "stateMutability": "payable", "type": "function"}, {"inputs": [{"internalType": "uint256", "name": "amountIn", "type": "uint256"}, {"internalType": "uint256", "name": "amountOutMin", "type": "uint256"}, {"internalType": "address[]", "name": "path", "type": "address[]"}, {"internalType": "address", "name": "to", "type": "address"}, {"internalType": "uint256", "name": "deadline", "type": "uint256"}], "name": "swapExactTokensForETH", "outputs": [{"internalType": "uint256[]", "name": "amounts", "type": "uint256[]"}], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [{"internalType": "uint256", "name": "amountIn", "type": "uint256"}, {"internalType": "uint256", "name": "amountOutMin", "type": "uint256"}, {"internalType": "address[]", "name": "path", "type": "address[]"}, {"internalType": "address", "name": "to", "type": "address"}, {"internalType": "uint256", "name": "deadline", "type": "uint256"}], "name": "swapExactTokensForETHSupportingFeeOnTransferTokens", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [{"internalType": "uint256", "name": "amountIn", "type": "uint256"}, {"internalType": "uint256", "name": "amountOutMin", "type": "uint256"}, {"internalType": "address[]", "name": "path", "type": "address[]"}, {"internalType": "address", "name": "to", "type": "address"}, {"internalType": "uint256", "name": "deadline", "type": "uint256"}], "name": "swapExactTokensForTokens", "outputs": [{"internalType": "uint256[]", "name": "amounts", "type": "uint256[]"}], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [{"internalType": "uint256", "name": "amountIn", "type": "uint256"}, {"internalType": "uint256", "name": "amountOutMin", "type": "uint256"}, {"internalType": "address[]", "name": "path", "type": "address[]"}, {"internalType": "address", "name": "to", "type": "address"}, {"internalType": "uint256", "name": "deadline", "type": "uint256"}], "name": "swapExactTokensForTokensSupportingFeeOnTransferTokens", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [{"internalType": "uint256", "name": "amountOut", "type": "uint256"}, {"internalType": "uint256", "name": "amountInMax", "type": "uint256"}, {"internalType": "address[]", "name": "path", "type": "address[]"}, {"internalType": "address", "name": "to", "type": "address"}, {"internalType": "uint256", "name": "deadline", "type": "uint256"}], "name": "swapTokensForExactETH", "outputs": [{"internalType": "uint256[]", "name": "amounts", "type": "uint256[]"}], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [{"internalType": "uint256", "name": "amountOut", "type": "uint256"}, {"internalType": "uint256", "name": "amountInMax", "type": "uint256"}, {"internalType": "address[]", "name": "path", "type": "address[]"}, {"internalType": "address", "name": "to", "type": "address"}, {"internalType": "uint256", "name": "deadline", "type": "uint256"}], "name": "swapTokensForExactTokens", "outputs": [{"internalType": "uint256[]", "name": "amounts", "type": "uint256[]"}], "stateMutability": "nonpayable", "type": "function"}, {"stateMutability": "payable", "type": "receive"}] 2 | 3 | -------------------------------------------------------------------------------- /res/11017338.csv: -------------------------------------------------------------------------------- 1 | hash,gasPrice,gasUsed,calls,unknownCalls,classifiedCalls,classifiedActions,compactedProviders,type,status,profit 2 | 3 | 0x8305be032131db3aebf16c12be7745b121e1c12d772aa84962a4ba63282fdcd8,1,21,1,1,0,0,,,,0 4 | 0x3a322b82c42ea9fb7ab2b5fd336cff2bcc0cab9a8710a347dc57071295eb1139,1085.7551,63,13,0,13,3,"UNISWAP(2),KNOWN_BOT",UNCLASSIFIED_BOT,SUCCESS,-1.9754 5 | 0xe17e7170675bad8987a0f888a5c082ec7f0de1685ca95be664549fc22ab892c6,1039.7485,21,1,1,0,0,,,,0 6 | 0xec423a81daaf9436d359f967dc56bf17e7f539270b489470eaa140b7d379b52a,588.8,177,15,15,0,0,,,,0 7 | 0x9b08b7c8efe5cfd40c012b956a6031f60c076bc07d5946888a0d55e5ed78b38a,497.9978,152,18,16,2,2,UNISWAP(2),ARBITRAGE,CHECKED,0 8 | 0x52311e6ec870f530e84f79bbb08dce05c95d80af5a3cb29ab85d128a15dbea8d,256,22,4,0,4,2,"UNISWAP,KNOWN_BOT",UNCLASSIFIED_BOT,CHECKED,0 9 | 0x300e336026374c9f72fe416f47fdee3677cd337d7dd23a6fb5b8093b1b33ebea,232,103,7,0,7,1,SUSHISWAP,,,0 10 | 0xac74348dc319b6bca470d7eba4900ad4449d595b33553b5b0e1a3c79e2ebd743,184.3753,32,3,2,1,1,UNISWAP,,,0 11 | 0x026e704e8f1b2f3d9dc69300c4d6f5311a15708b92fa87011fbfad1be27aa480,170,21,1,1,0,0,,,,0 12 | 0x8aeaa1bb023b01cc7eed4a94dc55fa13fbb17c93b251fcd49b521ed1bcfb8b07,158.3009,27,2,0,2,2,"UNISWAP,KNOWN_BOT",UNCLASSIFIED_BOT,CHECKED,0 13 | 0x46909832db6ca33317c43436c76eef4b654d7f9cbc5e64cf47079aa7ea8be845,156,119,9,0,9,1,UNISWAP,,,0 14 | 0xf309cc82b495128de5c69167e111230079c7a29ced40f26e4be9c64acd0b3691,151,171,12,0,12,1,UNISWAP,,,0 15 | 0xf3d84d31b319f80ba92e34e67dba207df2f49e01d987d2dd468b5853e0e1e8c7,150,125,8,0,8,1,UNISWAP,,,0 16 | 0xebcfe3ff2c22b7edb0007d54434d08946d4a764c64c3691fc4af0f3d9d42bf27,145.14,103,8,0,8,3,"UNISWAP(2),KNOWN_BOT",UNCLASSIFIED_BOT,SUCCESS,-0.1424 17 | 0x1f39888ed3924dfa7f4911a83fdec103658494911ab7a6d32d2947fba84a1c3d,129.15,27,2,0,2,2,"UNISWAP,KNOWN_BOT",UNCLASSIFIED_BOT,CHECKED,0 18 | 0xfaa8e803ba22f6ef7efdb2eb9307ae8f105d55f15b3e7fb4e9321555be6b4e3f,125,28,2,0,2,1,UNISWAP,,,0 19 | 0x8d8be7f01fdb15d87ae7d37af0049d5b488b6dd2c10032425e6ba181493cf7a7,123,162,12,0,12,1,UNISWAP,,,0 20 | 0x843e4e606a8324ae2b9b10edce6d3ae65cc632f97363fc62304f8beb754382a8,123,94,9,0,9,3,"UNISWAP(2),KNOWN_BOT",UNCLASSIFIED_BOT,SUCCESS,0.1377 21 | 0x2defd141d1935212c5fe97cca2829ee451355419bac65fcb63a8c977885edd3d,123,26,2,0,2,1,KNOWN_BOT,UNCLASSIFIED_BOT,,0 22 | 0xc12bc9df3c58a1096337901bcea9fd1dd81f78dc2878d4ff5797e2179e018487,123,26,2,0,2,1,KNOWN_BOT,UNCLASSIFIED_BOT,,0 23 | 0xc07e37b197138acc576bac6d4faf4ff61b6fd9be22175b32b7aa430db0269758,122.9999,26,2,0,2,1,KNOWN_BOT,UNCLASSIFIED_BOT,,0 24 | 0x123d03cef9ccd4230d111d01cf1785aed4242eb2e1e542bd792d025eb7e3cc84,120,28,2,0,2,1,UNISWAP,,,0 25 | 0x1672be3a04011d1591d292b374408607f91175e940aff377216cc139e7190246,119,40,1,1,0,0,,,,0 26 | 0x93690c02fc4d58734225d898ea4091df104040450c0f204b6bf6f6850ac4602f,118,2806,391,214,177,8,"AAVE,UNISWAP(7)",LIQUIDATION,SUCCESS,0 27 | 0xd9306dc8c1230cc0faef22a8442d0994b8fc9a8f4c9faeab94a9a7eac8e59710,118,191,21,0,21,5,"UNISWAP(4),KNOWN_BOT",ARBITRAGE,SUCCESS,0.6266 28 | 0x69d1c3d1e38d63155c613d7f37b1ce31f8d0b55ca274b7c05c118771acb5c576,118,29,2,0,2,1,KNOWN_BOT,UNCLASSIFIED_BOT,,0 29 | 0x4cf1a912197c2542208f7c1b5624fa5ea75508fa45f41c28f7e6aaa443d14db2,118,73,6,0,6,3,"UNISWAP(2),KNOWN_BOT",ARBITRAGE,CHECKED,0 30 | 0x716f6aac1d1e3b2ed9c441f1b2155a1f3e62220e6061d443fa1ce72626d8587d,118,29,2,0,2,1,KNOWN_BOT,UNCLASSIFIED_BOT,,0 31 | 0x243b4b5bf96d345f690f6b17e75031dc634d0e97c47d73cbecf2327250077591,118,172,22,20,2,2,UNISWAP(2),ARBITRAGE,CHECKED,0 32 | 0x622519e27d56ea892c6e5e479b68e1eb6278e222ed34d0dc4f8f0fd254723def,118,83,7,0,7,1,UNISWAP,,,0 33 | 0x37c13f67588ea55489a30d930e58eefe51d5ef10870261cfa6310a022a6aafba,118,395,77,77,0,0,,,,0 34 | 0x03d469e1a158ad50db4a40ad9de04aac86bff629dc88daef2fbda7dc915291c8,118,29,2,0,2,1,KNOWN_BOT,UNCLASSIFIED_BOT,,0 35 | 0xf5f0b7e1c1761eff33956965f90b6d291fa2ff3c9907b450d483a58932c54598,118,43,6,0,6,3,"UNISWAP(2),KNOWN_BOT",ARBITRAGE,CHECKED,0 36 | 0xc0d2b860cd2c9d8c54041e0b732d043287a130e207722e869172374672c5eee1,118,29,2,0,2,1,KNOWN_BOT,UNCLASSIFIED_BOT,,0 37 | 0xba487aa8718493ac2b23569b0330e97b5d0b99da00584ca05910a93506e12deb,118,62,10,0,10,2,"UNISWAP,KNOWN_BOT",UNCLASSIFIED_BOT,CHECKED,0 38 | 0x46f4a4d409b44d85e64b1722b8b0f70e9713eb16d2c89da13cffd91486442627,118,252,28,0,28,4,"UNISWAP(2),BALANCER,KNOWN_BOT",ARBITRAGE,SUCCESS,0.0411 39 | 0xfd24e512dc90bd1ca8a4f7987be6122c1fa3221b261e8728212f2f4d980ee4cd,118,39,5,0,5,3,"UNISWAP(2),KNOWN_BOT",ARBITRAGE,CHECKED,0 40 | 0x9a9316f89986cb54a290f3098f394ad046d80531954c994478799a9dfdba356a,118,33,3,0,3,1,UNISWAP,,,0 41 | 0x87bdb70f303b688c6dd87ebe7d5d98efa404f2e08df5d1587cdf54ea57603884,117.25,114,13,8,5,2,UNISWAP(2),,,0 42 | 0x9bfddd15e67bb75f287ae62574124b54c49bdc902fe8ae473c837881cd7a0e71,116.25,36,1,1,0,0,,,,0 43 | 0x7fa6abb4c9ba3ca02fc3263a1632eb45cc398e954fdd56b72482c7c717ee2f73,101,34,3,0,3,1,UNISWAP,,,0 44 | 0x72493a035de37b73d3fcda2aa20852f4196165f3ce593244e51fa8e7c80bc13f,100,114,8,0,8,1,UNISWAP,,,0 45 | 0x65342cf2ddeaa15c5578984d56ebb5e1a0f0db8808ec9a095bce70b35c3f5e23,100,90,8,0,8,3,"UNISWAP(2),KNOWN_BOT",UNCLASSIFIED_BOT,SUCCESS,0.3081 46 | 0x9ee6e8aba5fef11d30ec5a1fe09e334a378d2964df30f804bbed8fdd5e46509e,100,27,2,0,2,1,KNOWN_BOT,UNCLASSIFIED_BOT,,0 47 | 0x00f64cd20b51f1503a8ac8a8f014494d22bfeed22bb32d58f5ec362d3465513d,100,27,2,0,2,1,KNOWN_BOT,UNCLASSIFIED_BOT,,0 48 | 0x923db12cb9ff2eba672278315d8d9251b7acda8d3aa5c6832b1b0d757fb06d4e,100,21,1,1,0,0,,,,0 49 | 0x934b921dd4166b1f139aa6eb66bd7d8c46be8bd8be36a65e263becb9176e9cb9,99.9999,49,8,6,2,1,UNISWAP,,,0 50 | 0xfd352d75691b9eae5cc941061446befaa5177ac29573c25644d19010d3174399,99.9999,27,2,0,2,1,KNOWN_BOT,UNCLASSIFIED_BOT,,0 51 | 0x020dc85097e2216ce544b5319ab8dabb47ecf93b2ad2a175c7aa033d5c55475c,99.9999,32,4,4,0,0,,,,0 52 | 0xb6bb58f21ac75781252f3434c2d532fab6e2566b4c7f71fbf8446cde716a69b2,96,40,1,1,0,0,,,,0 53 | 0xdf5dd9e267b0d94246edf381e8e682ef9963c8ee2414275d25de6b7efc30e37a,89,29,2,0,2,1,SUSHISWAP,,,0 54 | 0xe3133c34621bb4377d92e101cfc2795ec4b44264fb9277a1780007cb9124e828,88.5,21,1,1,0,0,,,,0 55 | 0x5cea32cd4c5b205fdd4a7708f85f7044127392c2e4352c9f344c40a9c52c3ed4,88.5,21,1,1,0,0,,,,0 56 | 0x68bdee6333d3e4179775d5b7277e9cc8c084bd95584fdbc84b262f2c516140d1,84,21,1,1,0,0,,,,0 57 | 0x47f7aed0a8ffd6e919c2eaf4caaf0bc83cf462519325cace903ee164b242435c,80,140,8,8,0,0,,,,0 58 | 0x402008bab788527ea102580dd8d2ab38046a2e8a9619d95e7b498b088df55fa9,79.5038,119,8,0,8,3,"UNISWAP(2),KNOWN_BOT",UNCLASSIFIED_BOT,SUCCESS,-0.0276 59 | 0xea427ab320396157f431bd254e0149d62fe219caeaece8332975801458572d03,78.3972,27,2,0,2,2,"UNISWAP,KNOWN_BOT",UNCLASSIFIED_BOT,CHECKED,0 60 | 0x058a0b829692a745666d6f52f7b04cbacea0f2505aeec07f6e7040f5e271e77d,78,56,1,1,0,0,,,,0 61 | 0xd1c3a822beeb81b24be67ad0bfaad99acce3e9cb49ef036249e4c80c12641ccd,78,56,1,1,0,0,,,,0 62 | 0x08220b432060b424a5a1b4c38112476d52af08a06bab3bdcb1fec425cda8eed0,77.5,21,1,1,0,0,,,,0 63 | 0xfd7cd4b0b48408e879eea008e8c13ccc5cb27eb6064d510c35c03b968c2c4ddb,76,104,9,0,9,1,UNISWAP,,,0 64 | 0x561d9b93d4bbcb7113708442f4a058f91e7e3d93b79785dd36392c4b3158ffe8,76,216,1,1,0,0,,,,0 65 | 0xe15f2b304803321421add4d17c92d3845fc0ae9f567ba6a41c3e5b52547da362,76,117,2,2,0,0,,,,0 66 | 0x04287e39a0b647a32d381e0aa3be525b9fc8feae8b87506306ba8449335cb588,76,100,10,0,10,1,UNISWAP,,,0 67 | 0x7df3db096bf4b9c43bc4d183c803798ce92f65052d035599b9e3a5d75c577dea,76,221,26,0,26,1,UNISWAP,,,0 68 | 0x46d198e6562f3a2c13ae397f0b5782de5cd99e0bf1236527ec69620c9336408e,76,216,1,1,0,0,,,,0 69 | 0x374569ef8175c96bca7c9a748d01b6cb80e802d9a7edda05421a75a4524a0036,75.9,21,1,1,0,0,,,,0 70 | 0xad0e11977e98ce41d73b815e1e15d852532f949663022bf0bf964781b4514e6c,75,161,12,12,0,0,,,,0 71 | 0x0565af5977a2ef68b84a64b39d3ea84c83f567677d138257f9b4978c72f16a92,75,121,10,0,10,1,UNISWAP,,,0 72 | 0xc2bfb1a47f011ff0667258f02ab12441cb87b2a1c37bfb22f66a8184d1019cc3,70.8,21,1,1,0,0,,,,0 73 | 0xba1ec60cbc4b9a7701079fe59eff3b1e0160f867aa5e46656d16d6ae044c7f95,70.1504,21,1,1,0,0,,,,0 74 | 0x0553fdb7d5b692e7edf69a1c31260a33d0094e50a79be9da3b89e01517e3fd45,70,28,2,0,2,1,UNISWAP,,,0 75 | 0x501aee4f3ef1e080fd70d0f1d883684a2c3459f926362488c5ac1568854e7ab9,70,25,1,1,0,0,,,,0 76 | 0xdfeae07360e2d7695a498e57e2054c658d1d78bbcd3c763fc8888b5433b6c6d5,67.6105,265,34,0,34,10,"UNISWAP(9),KNOWN_BOT",ARBITRAGE,SUCCESS,0.0239 77 | 0x4fb13d99baf0c242a6bc93eff8f5c5efbb140565c80bcc60f1e3f26239cd68c0,67.6104,62,11,0,11,1,KNOWN_BOT,UNCLASSIFIED_BOT,,0 78 | 0x6aecc9c9070519e0ef80d6e7cb89f1ab521853160bd7e5582648b7b71a321782,67.2,26,1,1,0,0,,,,0 79 | 0x1d9a2c8bfcd9f6e133c490d892fe3869bada484160a81966e645616cfc21652a,67,346,33,0,33,6,"UNISWAP(4),BALANCER,KNOWN_BOT",ARBITRAGE,SUCCESS,0.0475 80 | 0xd9df5ae2e9e18099913559f71473866758df3fd25919be605c71c300e64165fd,67,37,5,0,5,3,"UNISWAP(2),KNOWN_BOT",ARBITRAGE,CHECKED,0 81 | 0x3482c0b5eba76211d86e03b7f43bc37357fda9b2dc0b5d2e2e036cb3c578ea9a,67,113,8,0,8,1,UNISWAP,,,0 82 | 0x2f85ce5bb5f7833e052897fa4a070615a4e21a247e1ccc2347a3882f0e73943d,67,39,5,0,5,3,"UNISWAP(2),KNOWN_BOT",ARBITRAGE,CHECKED,0 83 | 0xe43734199366c665e341675e0f6ea280745d7d801924815b2c642dc83c8756d6,67,41,5,0,5,3,"UNISWAP(2),KNOWN_BOT",ARBITRAGE,CHECKED,0 84 | 0x84d521006182440b0000c24cf3fbede8dbcf3a5b58495c9e02b26e78d3ae1202,67,30,1,1,0,0,,,,0 85 | 0xe3bae9e2a9414c7092caeee499bf428103981f9868e6ddeb10f7bc2f343a936f,66,104,8,3,5,2,UNISWAP(2),,,0 86 | 0x828341ec205074147645a52e89e4e0431f96c606bf856883cb06d21912518443,66,28,2,0,2,1,UNISWAP,,,0 87 | 0xaa15b58f7d108f4dbc4b1f9c7598ec03d34b28c1129b81d634c687e6a6d97c5c,66,21,1,1,0,0,,,,0 88 | 0x8c8e44a0707dbad5eb8d108479f0b662e9ec0939bca8957d7c6284ca843852ab,66,45,1,1,0,0,,,,0 89 | 0x2085581881aed511eaa0e46de73e121fa6e0dd1866162900dd970715d827eb26,66,164,8,0,8,1,UNISWAP,,,0 90 | 0x77403a6eb1e9ebf7bf4110aa25d471551aa7580ad65d93c335c6abfebe7e84fa,66,207,22,0,22,1,UNISWAP,,,0 91 | 0x0ae84b973cd56e00ffcb8a101a547ccd65d79ed3617248f18605dca5ae3b4fcd,66,21,1,1,0,0,,,,0 92 | 0x0ab927f9e8e15eaac4a43e4c1302c8e89f4ffa68055a214e73d7e547382db083,66,34,3,0,3,1,UNISWAP,,,0 93 | 0x9cb610b65802be9bcd8f10f8c9ba85a02c0c796b8f0c11ccb4c309478c120f29,66,28,2,0,2,1,UNISWAP,,,0 94 | 0x41959e1954b5681eedfc2203e405eebfa597e59d4a6fe6d01b183e7d109086db,66,21,1,1,0,0,,,,0 95 | 0xcdaf941f3a26eed85dc12bf1ba96cffe8106a4e665e3ca7257a66104b659cba9,66,133,10,9,1,1,UNISWAP,,,0 96 | 0xb05e364c3b539b038f6b94b66256c29825bfa1227ea8507710ceb27266223647,66,34,3,0,3,1,UNISWAP,,,0 97 | 0x6da4c285965b21789fd8d16d35c34c0fb267456bba6898cdcb82873290a9f431,66,40,1,1,0,0,,,,0 98 | 0xb82a928ddd942e26b8c7d1a70a9c78e258f0f72dddb180dffd724e746d9205fd,66,45,1,1,0,0,,,,0 99 | 0xb56e0a391e7952f96ca2b6799551a296626012ed6a5f324e69e72336dbbe5c35,65.9999,29,2,0,2,1,KNOWN_BOT,UNCLASSIFIED_BOT,,0 100 | 0x119708a208d8980aca073fe0cc1555c52432ec58225b1bf1326ab2fefbb2e872,65.9999,95,10,5,5,2,UNISWAP(2),,,0 101 | 0xb72946a1da900d53a1d962a985ce1883c598916e4dc1f5843da77300479a401b,65.6755,35,5,5,0,0,,,,0 102 | 0x75e78a9db46a990ef015da3cc978d02c58adf4d66e376a63dd89afdecfea4c8d,65,44,1,1,0,0,,,,0 103 | 0xedf1e014985709d2f922b65f8db1efcf4d8de6acaeebe2f6286e41f1698a4a86,65,44,1,1,0,0,,,,0 104 | 0x7e518e98ad971abe10b30e4904e7de19b2550060cca05dfbfb78298613150ebf,65,21,1,1,0,0,,,,0 105 | 0x06510240fcc8a2350ae544d47f8cd8720b45bdf6cedc7635c63a593a78eaab99,65,21,1,1,0,0,,,,0 106 | 0xc64b50209af2a47a04bf12d3a0a30e804b612d380c51942efdad775d60a78b41,65,21,1,1,0,0,,,,0 107 | 0x375b5a232aa2ddcf04b95d9c38cacf305c1d1eaacfc39ae35aa7d349dc02fb95,64.9,189,14,0,14,1,UNISWAP,,,0 108 | 0x367684ca6ac638182ded517cbd3e8fbdf641b69948d9859ca364a2f033a02168,64,56,1,1,0,0,,,,0 109 | 0x589fcd48c6da1d90794d369a4c5246b238ed558717547fd92038f300391ac223,64,117,9,0,9,1,UNISWAP,,,0 110 | 0x68e70557983453a618c61df97bc63c6c487fe28f38718712b5cd7fe2a70bcf9d,62,39,1,1,0,0,,,,0 111 | 0x1b874a656e6fcd6b7b03bc00cc53bfbab3eba813a768df5e1c0281bc0c18ed8a,62,106,1,1,0,0,,,,0 112 | 0xbcdc993b7224aa89f370b5fb83fdd3d247aef2b7ceabe1957a48672ff445bec8,62,102,1,1,0,0,,,,0 113 | 0xf7fb3b8b1bc6cc868818e5b60a23a5bd590410c115f681797aa595b42bc01c44,62,107,1,1,0,0,,,,0 114 | 0xa4196c40463b531f4026c7095e89b9682a2ff75577fb8355a7da149ad09407ee,62,82,1,1,0,0,,,,0 115 | 0xe205edfd7d73002dad37681198bbab26ae78c4f7f6e74ec6942aaf7925d82419,62,21,1,1,0,0,,,,0 116 | 0x21f330b3d5bbbb01a21b3d0a509c6060cd03704af0e3b621638496f6003cc939,62,71,1,1,0,0,,,,0 117 | 0x077402bd0bbd0309ec8489fd55707472394f5808707a4e7b65cfb8f5f6d19f85,62,107,9,0,9,1,UNISWAP,,,0 118 | 0xa95761e74f25c6e35f00d8dcfe97d72151347f19c537a233f8909819499df3f6,62,40,1,1,0,0,,,,0 119 | 0x3c8139e5d414ba7f708853b3646d2b7032e5a665621a7fd0aa21c316015342cf,62,21,1,1,0,0,,,,0 120 | 0xdac54c7a805bc8a92cd0527e57facfd54f45f2b7a898da782151c91aacfe3b18,62,37,1,1,0,0,,,,0 121 | 0x00df604a0735bb000ba48c809e4b583e1204d7a99ecc00cca58e568b8431467f,62,21,1,1,0,0,,,,0 122 | 0x0616397ec16937d80b942d82bb7d05f68ff24971c14bfd55a862520126ad1062,61.6,21,1,1,0,0,,,,0 123 | 0x911b15ea0cfe053a6aad9c8c79100682063ae5b136a239cf374aa2c94ae7190e,61,40,2,2,0,0,,,,0 124 | 0x857df0b5f750c23661c92661a1171f0f1ee1ed39db68ef05aacf84198e8610f9,61,41,1,1,0,0,,,,0 125 | 0x6c26847d8ed45b3f075bb5a74baabea69baa8dacc95e82e3b43756202e354ca9,61,21,1,1,0,0,,,,0 126 | 0xd4403a9871759bd74c5342a90cf7ade39afa022dd0e71868e46fc5f918d2732a,61,21,1,1,0,0,,,,0 127 | -------------------------------------------------------------------------------- /res/exact_tokens_for_eth.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "action": { 4 | "callType": "call", 5 | "from": "0xe94cf39bbe5613071d8bf16263c94ada65a70fc8", 6 | "gas": "0x24515", 7 | "input": "0x18cbafe5000000000000000000000000000000000000000000000000f0d791cd4a8440000000000000000000000000000000000000000000000000000eb8aebbaa0cc0c100000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000e94cf39bbe5613071d8bf16263c94ada65a70fc8000000000000000000000000000000000000000000000000000000005fa55be40000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c259bd68fe764cfa3fd5c04a3bd24363c7e112ec000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", 8 | "to": "0x7a250d5630b4cf539739df2c5dacb4c659f2488d", 9 | "value": "0x0" 10 | }, 11 | "blockHash": "0x88df6369b36b1788bcf2000401ee2c2c89336b9e4d6856514f8b0ff5d53d6ecf", 12 | "blockNumber": 11204208, 13 | "result": { 14 | "gasUsed": "0x1f831", 15 | "output": "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000f0d791cd4a8440000000000000000000000000000000000000000000000000000ecde8042c298ba4" 16 | }, 17 | "subtraces": 5, 18 | "traceAddress": [], 19 | "transactionHash": "0xeef0edcc4ce9aa85db5bc6a788b5a770dcc0d13eb7df4e7c008c1ac6666cd989", 20 | "transactionPosition": 248, 21 | "type": "call" 22 | }, 23 | { 24 | "action": { 25 | "callType": "staticcall", 26 | "from": "0x7a250d5630b4cf539739df2c5dacb4c659f2488d", 27 | "gas": "0x22e46", 28 | "input": "0x0902f1ac", 29 | "to": "0xe7240d0da75b3ad88ff12b38756c50405ff39fe3", 30 | "value": "0x0" 31 | }, 32 | "blockHash": "0x88df6369b36b1788bcf2000401ee2c2c89336b9e4d6856514f8b0ff5d53d6ecf", 33 | "blockNumber": 11204208, 34 | "result": { 35 | "gasUsed": "0x4b4", 36 | "output": "0x00000000000000000000000000000000000000000000000aaed2b263e281a2810000000000000000000000000000000000000000000000ac557fe0ec93b5998d000000000000000000000000000000000000000000000000000000005fa55725" 37 | }, 38 | "subtraces": 0, 39 | "traceAddress": [ 40 | 0 41 | ], 42 | "transactionHash": "0xeef0edcc4ce9aa85db5bc6a788b5a770dcc0d13eb7df4e7c008c1ac6666cd989", 43 | "transactionPosition": 248, 44 | "type": "call" 45 | }, 46 | { 47 | "action": { 48 | "callType": "call", 49 | "from": "0x7a250d5630b4cf539739df2c5dacb4c659f2488d", 50 | "gas": "0x21c77", 51 | "input": "0x23b872dd000000000000000000000000e94cf39bbe5613071d8bf16263c94ada65a70fc8000000000000000000000000e7240d0da75b3ad88ff12b38756c50405ff39fe3000000000000000000000000000000000000000000000000f0d791cd4a844000", 52 | "to": "0xc259bd68fe764cfa3fd5c04a3bd24363c7e112ec", 53 | "value": "0x0" 54 | }, 55 | "blockHash": "0x88df6369b36b1788bcf2000401ee2c2c89336b9e4d6856514f8b0ff5d53d6ecf", 56 | "blockNumber": 11204208, 57 | "result": { 58 | "gasUsed": "0x5d80", 59 | "output": "0x0000000000000000000000000000000000000000000000000000000000000001" 60 | }, 61 | "subtraces": 0, 62 | "traceAddress": [ 63 | 1 64 | ], 65 | "transactionHash": "0xeef0edcc4ce9aa85db5bc6a788b5a770dcc0d13eb7df4e7c008c1ac6666cd989", 66 | "transactionPosition": 248, 67 | "type": "call" 68 | }, 69 | { 70 | "action": { 71 | "callType": "call", 72 | "from": "0x7a250d5630b4cf539739df2c5dacb4c659f2488d", 73 | "gas": "0x1b321", 74 | "input": "0x022c0d9f0000000000000000000000000000000000000000000000000ecde8042c298ba400000000000000000000000000000000000000000000000000000000000000000000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488d00000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000", 75 | "to": "0xe7240d0da75b3ad88ff12b38756c50405ff39fe3", 76 | "value": "0x0" 77 | }, 78 | "blockHash": "0x88df6369b36b1788bcf2000401ee2c2c89336b9e4d6856514f8b0ff5d53d6ecf", 79 | "blockNumber": 11204208, 80 | "result": { 81 | "gasUsed": "0x117bf", 82 | "output": "0x" 83 | }, 84 | "subtraces": 3, 85 | "traceAddress": [ 86 | 2 87 | ], 88 | "transactionHash": "0xeef0edcc4ce9aa85db5bc6a788b5a770dcc0d13eb7df4e7c008c1ac6666cd989", 89 | "transactionPosition": 248, 90 | "type": "call" 91 | }, 92 | { 93 | "action": { 94 | "callType": "call", 95 | "from": "0xe7240d0da75b3ad88ff12b38756c50405ff39fe3", 96 | "gas": "0x18470", 97 | "input": "0xa9059cbb0000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488d0000000000000000000000000000000000000000000000000ecde8042c298ba4", 98 | "to": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", 99 | "value": "0x0" 100 | }, 101 | "blockHash": "0x88df6369b36b1788bcf2000401ee2c2c89336b9e4d6856514f8b0ff5d53d6ecf", 102 | "blockNumber": 11204208, 103 | "result": { 104 | "gasUsed": "0x75d2", 105 | "output": "0x0000000000000000000000000000000000000000000000000000000000000001" 106 | }, 107 | "subtraces": 0, 108 | "traceAddress": [ 109 | 2, 110 | 0 111 | ], 112 | "transactionHash": "0xeef0edcc4ce9aa85db5bc6a788b5a770dcc0d13eb7df4e7c008c1ac6666cd989", 113 | "transactionPosition": 248, 114 | "type": "call" 115 | }, 116 | { 117 | "action": { 118 | "callType": "staticcall", 119 | "from": "0xe7240d0da75b3ad88ff12b38756c50405ff39fe3", 120 | "gas": "0x1096b", 121 | "input": "0x70a08231000000000000000000000000e7240d0da75b3ad88ff12b38756c50405ff39fe3", 122 | "to": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", 123 | "value": "0x0" 124 | }, 125 | "blockHash": "0x88df6369b36b1788bcf2000401ee2c2c89336b9e4d6856514f8b0ff5d53d6ecf", 126 | "blockNumber": 11204208, 127 | "result": { 128 | "gasUsed": "0x4d2", 129 | "output": "0x00000000000000000000000000000000000000000000000aa004ca5fb65816dd" 130 | }, 131 | "subtraces": 0, 132 | "traceAddress": [ 133 | 2, 134 | 1 135 | ], 136 | "transactionHash": "0xeef0edcc4ce9aa85db5bc6a788b5a770dcc0d13eb7df4e7c008c1ac6666cd989", 137 | "transactionPosition": 248, 138 | "type": "call" 139 | }, 140 | { 141 | "action": { 142 | "callType": "staticcall", 143 | "from": "0xe7240d0da75b3ad88ff12b38756c50405ff39fe3", 144 | "gas": "0xfe79", 145 | "input": "0x70a08231000000000000000000000000e7240d0da75b3ad88ff12b38756c50405ff39fe3", 146 | "to": "0xc259bd68fe764cfa3fd5c04a3bd24363c7e112ec", 147 | "value": "0x0" 148 | }, 149 | "blockHash": "0x88df6369b36b1788bcf2000401ee2c2c89336b9e4d6856514f8b0ff5d53d6ecf", 150 | "blockNumber": 11204208, 151 | "result": { 152 | "gasUsed": "0x4ac", 153 | "output": "0x0000000000000000000000000000000000000000000000ad465772b9de39d98d" 154 | }, 155 | "subtraces": 0, 156 | "traceAddress": [ 157 | 2, 158 | 2 159 | ], 160 | "transactionHash": "0xeef0edcc4ce9aa85db5bc6a788b5a770dcc0d13eb7df4e7c008c1ac6666cd989", 161 | "transactionPosition": 248, 162 | "type": "call" 163 | }, 164 | { 165 | "action": { 166 | "callType": "call", 167 | "from": "0x7a250d5630b4cf539739df2c5dacb4c659f2488d", 168 | "gas": "0x9929", 169 | "input": "0x2e1a7d4d0000000000000000000000000000000000000000000000000ecde8042c298ba4", 170 | "to": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", 171 | "value": "0x0" 172 | }, 173 | "blockHash": "0x88df6369b36b1788bcf2000401ee2c2c89336b9e4d6856514f8b0ff5d53d6ecf", 174 | "blockNumber": 11204208, 175 | "result": { 176 | "gasUsed": "0x2e93", 177 | "output": "0x" 178 | }, 179 | "subtraces": 1, 180 | "traceAddress": [ 181 | 3 182 | ], 183 | "transactionHash": "0xeef0edcc4ce9aa85db5bc6a788b5a770dcc0d13eb7df4e7c008c1ac6666cd989", 184 | "transactionPosition": 248, 185 | "type": "call" 186 | }, 187 | { 188 | "action": { 189 | "callType": "call", 190 | "from": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", 191 | "gas": "0x8fc", 192 | "input": "0x", 193 | "to": "0x7a250d5630b4cf539739df2c5dacb4c659f2488d", 194 | "value": "0xecde8042c298ba4" 195 | }, 196 | "blockHash": "0x88df6369b36b1788bcf2000401ee2c2c89336b9e4d6856514f8b0ff5d53d6ecf", 197 | "blockNumber": 11204208, 198 | "result": { 199 | "gasUsed": "0x53", 200 | "output": "0x" 201 | }, 202 | "subtraces": 0, 203 | "traceAddress": [ 204 | 3, 205 | 0 206 | ], 207 | "transactionHash": "0xeef0edcc4ce9aa85db5bc6a788b5a770dcc0d13eb7df4e7c008c1ac6666cd989", 208 | "transactionPosition": 248, 209 | "type": "call" 210 | }, 211 | { 212 | "action": { 213 | "callType": "call", 214 | "from": "0x7a250d5630b4cf539739df2c5dacb4c659f2488d", 215 | "gas": "0x4d70", 216 | "input": "0x", 217 | "to": "0xe94cf39bbe5613071d8bf16263c94ada65a70fc8", 218 | "value": "0xecde8042c298ba4" 219 | }, 220 | "blockHash": "0x88df6369b36b1788bcf2000401ee2c2c89336b9e4d6856514f8b0ff5d53d6ecf", 221 | "blockNumber": 11204208, 222 | "result": { 223 | "gasUsed": "0x0", 224 | "output": "0x" 225 | }, 226 | "subtraces": 0, 227 | "traceAddress": [ 228 | 4 229 | ], 230 | "transactionHash": "0xeef0edcc4ce9aa85db5bc6a788b5a770dcc0d13eb7df4e7c008c1ac6666cd989", 231 | "transactionPosition": 248, 232 | "type": "call" 233 | } 234 | ] 235 | -------------------------------------------------------------------------------- /scripts/addrs.py: -------------------------------------------------------------------------------- 1 | # Script to get all Uni V1, V2 and Sushiswap pair addresses. 2 | # Requires web3.py installed (`pip install web3`) 3 | # URL=http://localhost:8545 python addrs.py 4 | from web3 import Web3, HTTPProvider 5 | import os 6 | 7 | # abis 8 | v1abi = [{"name": "NewExchange", "inputs": [{"type": "address", "name": "token", "indexed": True}, {"type": "address", "name": "exchange", "indexed": True}], "anonymous": False, "type": "event"}] 9 | v2abi = [{"inputs":[{"internalType":"address","name":"_feeToSetter","type":"address"}],"payable":False,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":False,"inputs":[{"indexed":True,"internalType":"address","name":"token0","type":"address"},{"indexed":True,"internalType":"address","name":"token1","type":"address"},{"indexed":False,"internalType":"address","name":"pair","type":"address"},{"indexed":False,"internalType":"uint256","name":"","type":"uint256"}],"name":"PairCreated","type":"event"},{"constant":True,"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"allPairs","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":False,"stateMutability":"view","type":"function"},{"constant":True,"inputs":[],"name":"allPairsLength","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":False,"stateMutability":"view","type":"function"},{"constant":False,"inputs":[{"internalType":"address","name":"tokenA","type":"address"},{"internalType":"address","name":"tokenB","type":"address"}],"name":"createPair","outputs":[{"internalType":"address","name":"pair","type":"address"}],"payable":False,"stateMutability":"nonpayable","type":"function"},{"constant":True,"inputs":[],"name":"feeTo","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":False,"stateMutability":"view","type":"function"},{"constant":True,"inputs":[],"name":"feeToSetter","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":False,"stateMutability":"view","type":"function"},{"constant":True,"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"getPair","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":False,"stateMutability":"view","type":"function"},{"constant":False,"inputs":[{"internalType":"address","name":"_feeTo","type":"address"}],"name":"setFeeTo","outputs":[],"payable":False,"stateMutability":"nonpayable","type":"function"},{"constant":False,"inputs":[{"internalType":"address","name":"_feeToSetter","type":"address"}],"name":"setFeeToSetter","outputs":[],"payable":False,"stateMutability":"nonpayable","type":"function"}] 10 | 11 | # addrs 12 | V1FACTORY = "0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95" 13 | V2FACTORY = "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f" 14 | SUSHIFACTORY = "0xC0AEe478e3658e2610c5F7A4A2E1777cE9e4f2Ac" 15 | 16 | # deployed at blocks 17 | V1DEPLOY = 0x65224d 18 | V2DEPLOY = 0x9899c3 19 | SUSHIDEPLOY = 0xa4b4f5 20 | 21 | URL = os.environ["URL"] 22 | w3 = Web3(HTTPProvider(URL)) 23 | 24 | def dump(fname, data): 25 | with open(fname, 'w') as f: 26 | for item in data: 27 | f.write("%s\n" % item) 28 | 29 | # if you get RPC errors, run each section separately 30 | contract = w3.eth.contract(address = V1FACTORY, abi=v1abi) 31 | events = contract.events.NewExchange.createFilter(fromBlock=V1DEPLOY).get_all_entries() 32 | pairs = [e.args.exchange for e in events] 33 | dump("./res/v1pairs.csv", pairs) 34 | 35 | contract = w3.eth.contract(address = V2FACTORY, abi=v2abi) 36 | events = contract.events.PairCreated.createFilter(fromBlock=V2DEPLOY).get_all_entries() 37 | pairs = [e.args.pair for e in events] 38 | dump("./res/v2pairs.csv", pairs) 39 | 40 | contract = w3.eth.contract(address = SUSHIFACTORY, abi=v2abi) 41 | events = contract.events.PairCreated.createFilter(fromBlock=SUSHIDEPLOY).get_all_entries() 42 | pairs = [e.args.pair for e in events] 43 | dump("./res/sushipairs.csv", pairs) 44 | -------------------------------------------------------------------------------- /src/addresses.rs: -------------------------------------------------------------------------------- 1 | use super::types::Protocol; 2 | 3 | use ethers::types::Address; 4 | 5 | use once_cell::sync::Lazy; 6 | use std::collections::{HashMap, HashSet}; 7 | use std::fs::File; 8 | use std::io::{self, BufRead}; 9 | use std::path::Path; 10 | 11 | pub fn lookup(address: Address) -> String { 12 | ADDRESSBOOK 13 | .get(&address) 14 | .unwrap_or(&format!("{:?}", &address)) 15 | .clone() 16 | } 17 | 18 | fn insert_many( 19 | mut map: HashMap, 20 | addrs: &[&str], 21 | value: T, 22 | ) -> HashMap { 23 | for addr in addrs { 24 | map.insert(parse_address(addr), value.clone()); 25 | } 26 | map 27 | } 28 | 29 | // reads line-separated addresses from a file path and returns them as a vector 30 | fn read_addrs

(path: P) -> Vec

31 | where 32 | P: AsRef, 33 | { 34 | let file = File::open(path).unwrap(); 35 | let lines = io::BufReader::new(file).lines(); 36 | lines.map(|line| parse_address(&line.unwrap())).collect() 37 | } 38 | 39 | // Protocol Addrs 40 | pub static PROTOCOLS: Lazy> = Lazy::new(|| { 41 | let map = HashMap::new(); 42 | let map = insert_many( 43 | map, 44 | &[ 45 | "0x9346c20186d1794101b8517177a1b15c49c9ff9b", 46 | "0x2b095969ae40BcE8BaAF515B16614A636C22a6Db", 47 | "0x2fdbadf3c4d5a8666bc06645b8358ab803996e28", 48 | "0x7a250d5630b4cf539739df2c5dacb4c659f2488d", 49 | "0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc", 50 | "0xDcD6011f4C6B80e470D9487f5871a0Cba7C93f48", // 0x: UniswapV2Bridge 51 | ], 52 | Protocol::Uniswap, 53 | ); 54 | 55 | let mut map = insert_many( 56 | map, 57 | &[ 58 | // sUSD - WETH 59 | "0xf1f85b2c54a2bd284b1cf4141d64fd171bd85539", 60 | // Sushi YFI 61 | "0x088ee5007c98a9677165d78dd2109ae4a3d04d0c", 62 | // Sushi router 63 | "d9e1cE17f2641f24aE83637ab66a2cca9C378B9F", 64 | ], 65 | Protocol::Sushiswap, 66 | ); 67 | 68 | for addr in read_addrs("./res/v1pairs.csv") { 69 | map.insert(addr, Protocol::UniswapV1); 70 | } 71 | 72 | for addr in read_addrs("./res/v2pairs.csv") { 73 | map.insert(addr, Protocol::Uniswap); 74 | } 75 | 76 | for addr in read_addrs("./res/sushipairs.csv") { 77 | map.insert(addr, Protocol::Sushiswap); 78 | } 79 | 80 | // uni router 02 81 | map.insert( 82 | parse_address("7a250d5630B4cF539739dF2C5dAcb4c659F2488D"), 83 | Protocol::Uniswap, 84 | ); 85 | 86 | // uni router 01 87 | map.insert( 88 | parse_address("f164fC0Ec4E93095b804a4795bBe1e041497b92a"), 89 | Protocol::Uniswap, 90 | ); 91 | 92 | // sushi router 93 | map.insert( 94 | "d9e1cE17f2641f24aE83637ab66a2cca9C378B9F".parse().unwrap(), 95 | Protocol::Sushiswap, 96 | ); 97 | 98 | insert_many( 99 | map, 100 | &["0xfe01821Ca163844203220cd08E4f2B2FB43aE4E4"], // 0x: BalancerBridge 101 | Protocol::Balancer, 102 | ) 103 | }); 104 | 105 | // Addresses which should be ignored when used as the target of a transaction 106 | pub static FILTER: Lazy> = Lazy::new(|| { 107 | let mut set = HashSet::new(); 108 | // 1inch 109 | set.insert(parse_address("0x11111254369792b2ca5d084ab5eea397ca8fa48b")); 110 | // 1inch v2 111 | set.insert(parse_address("0x111111125434b319222cdbf8c261674adb56f3ae")); 112 | // 1inch v3 router 113 | set.insert(parse_address("0x11111112542d85b3ef69ae05771c2dccff4faa26")); 114 | // paraswap 115 | set.insert(parse_address("0x9509665d015bfe3c77aa5ad6ca20c8afa1d98989")); 116 | // paraswap v2 117 | set.insert(parse_address("0x86969d29F5fd327E1009bA66072BE22DB6017cC6")); 118 | // Paraswap v3 119 | set.insert(parse_address("0xf90e98f3d8dce44632e5020abf2e122e0f99dfab")); 120 | // furucombo 121 | set.insert(parse_address("0x57805e5a227937bac2b0fdacaa30413ddac6b8e1")); 122 | // furucombo proxy v1 123 | set.insert(parse_address("0x17e8ca1b4798b97602895f63206afcd1fc90ca5f")); 124 | // yearn recycler 125 | set.insert(parse_address("0x5F07257145fDd889c6E318F99828E68A449A5c7A")); 126 | // drc, weird deflationary token 127 | set.insert(parse_address("0xc66d62a2f9ff853d9721ec94fa17d469b40dde8d")); 128 | // Rootkit finance deployer 129 | set.insert(parse_address("0x804cc8d469483d202c69752ce0304f71ae14abdf")); 130 | // Metamask Swap 131 | set.insert(parse_address("0x881d40237659c251811cec9c364ef91dc08d300c")); 132 | // DEX.ag 133 | set.insert(parse_address("0x745daa146934b27e3f0b6bff1a6e36b9b90fb131")); 134 | // Cream Finance deployer 135 | set.insert(parse_address("0x197939c1ca20c2b506d6811d8b6cdb3394471074")); 136 | // Zerion SDK 137 | set.insert(parse_address("0xb2be281e8b11b47fec825973fc8bb95332022a54")); 138 | // KeeperDAO 139 | set.insert(parse_address("0x3d71d79c224998e608d03c5ec9b405e7a38505f0")); 140 | // ParaSwap P4 141 | set.insert(parse_address("0x1bd435f3c054b6e901b7b108a0ab7617c808677b")); 142 | set 143 | }); 144 | 145 | pub static ZEROX: Lazy
= 146 | Lazy::new(|| parse_address("0x61935cbdd02287b511119ddb11aeb42f1593b7ef")); 147 | 148 | pub static DYDX: Lazy
= 149 | Lazy::new(|| parse_address("0x1e0447b19bb6ecfdae1e4ae1694b0c3659614e4e")); 150 | 151 | pub static BALANCER_PROXY: Lazy
= 152 | Lazy::new(|| parse_address("0x3E66B66Fd1d0b02fDa6C811Da9E0547970DB2f21")); 153 | 154 | pub static CURVE_REGISTRY: Lazy
= 155 | Lazy::new(|| parse_address("0x7D86446dDb609eD0F5f8684AcF30380a356b2B4c")); 156 | 157 | pub static CETH: Lazy
= 158 | Lazy::new(|| parse_address("4Ddc2D193948926D02f9B1fE9e1daa0718270ED5")); 159 | 160 | pub static COMPTROLLER: Lazy
= 161 | Lazy::new(|| parse_address("3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B")); 162 | 163 | pub static COMP_ORACLE: Lazy
= 164 | Lazy::new(|| parse_address("922018674c12a7F0D394ebEEf9B58F186CdE13c1")); 165 | 166 | pub static AAVE_LENDING_POOL: Lazy
= 167 | Lazy::new(|| parse_address("398eC7346DcD622eDc5ae82352F02bE94C62d119")); 168 | 169 | pub static AAVE_LENDING_POOL_CORE: Lazy
= 170 | Lazy::new(|| parse_address("3dfd23a6c5e8bbcfc9581d2e864a68feb6a076d3")); 171 | 172 | pub static WETH: Lazy
= 173 | Lazy::new(|| parse_address("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2")); 174 | 175 | pub static ETH: Lazy
= 176 | Lazy::new(|| parse_address("0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee")); 177 | 178 | pub static ADDRESSBOOK: Lazy> = Lazy::new(|| { 179 | // TODO: Read these from a CSV? 180 | let map: HashMap = [ 181 | // 0x Exchange Proxies 182 | ( 183 | "0xdef1c0ded9bec7f1a1670819833240f027b25eff", 184 | "0x: ExchangeProxy", 185 | ), 186 | ( 187 | "0xfe01821Ca163844203220cd08E4f2B2FB43aE4E4", 188 | "0x: BalancerBridge", 189 | ), 190 | ( 191 | "0xDcD6011f4C6B80e470D9487f5871a0Cba7C93f48", 192 | "0x: UniswapV2Bridge", 193 | ), 194 | ( 195 | "0x761C446DFC9f7826374ABDeCc79F992e7F17330b", 196 | "0x: TranformERC20", 197 | ), 198 | // Contracts 199 | ( 200 | "0x2fdbadf3c4d5a8666bc06645b8358ab803996e28", 201 | "UniswapPair YFI 8", 202 | ), 203 | ( 204 | "0x3dA1313aE46132A397D90d95B1424A9A7e3e0fCE", 205 | "UniswapPair CRV 8", 206 | ), 207 | ( 208 | "0x7a250d5630b4cf539739df2c5dacb4c659f2488d", 209 | "Uniswap Router V2", 210 | ), 211 | ( 212 | "0x088ee5007C98a9677165D78dD2109AE4a3D04d0C", 213 | "Sushiswap: YFI", 214 | ), 215 | ( 216 | "0x7c66550c9c730b6fdd4c03bc2e73c5462c5f7acc", 217 | "Kyber: Contract 2", 218 | ), 219 | ( 220 | "0x10908c875d865c66f271f5d3949848971c9595c9", 221 | "Kyber: Reserve Uniswap V2", 222 | ), 223 | ( 224 | "0x3dfd23a6c5e8bbcfc9581d2e864a68feb6a076d3", 225 | "AAVE: Lending Pool Core", 226 | ), 227 | ( 228 | "0xb6ad5fd2698a68917e39216304d4845625da2f57", 229 | "Balancer: YFI/yyDAI+yUSDC+yUSDT+yTUSD 50/50", 230 | ), 231 | ( 232 | "0xd44082f25f8002c5d03165c5d74b520fbc6d342d", 233 | "Balancer: Pool 293 (YFI / LEND / MKR / WETH / LINK)", 234 | ), 235 | // Tokens 236 | ("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", "USDC"), 237 | ("0x0000000000000000000000000000000000000000", "ETH"), 238 | ("0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", "ETH"), 239 | ("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", "WETH"), 240 | ("0x0bc529c00c6401aef6d220be8c6ea1667f6ad93e", "YFI"), 241 | ("0xe41d2489571d322189246dafa5ebde1f4699f498", "ZRX"), 242 | ("0x0d8775f648430679a709e98d2b0cb6250d2887ef", "BAT"), 243 | ("0xd533a949740bb3306d119cc777fa900ba034cd52", "CRV"), 244 | ("0x80fb784b7ed66730e8b1dbd9820afd29931aab03", "LEND"), 245 | ("0x6B175474E89094C44DA98B954EEDEAC495271D0F", "DAI"), 246 | ("0xc00e94cb662c3520282e6f5717214004a7f26888", "COMP"), 247 | ("0x5d3a536e4d6dbd6114cc1ead35777bab948e3643", "cDAI"), 248 | ("0x514910771af9ca656af840dff83e8264ecf986ca", "LINK"), 249 | ("0x2260fac5e5542a773aa44fbcfedf7c193bc2c599", "WBTC"), 250 | ( 251 | "0x5dbcf33d8c2e976c6b560249878e6f1491bca25c", 252 | "yyDAI+yUSDC+yUSDT+yTUSD", 253 | ), 254 | ("0x0000000000b3f879cb30fe243b4dfee438691c04", "GST2"), 255 | ] 256 | .iter() 257 | .map(|(addr, token)| (parse_address(addr), token.to_string())) 258 | .collect(); 259 | 260 | // https://github.com/flashbots/mev-inspect/blob/master/src/InspectorKnownBot.ts#L17 261 | insert_many( 262 | map, 263 | &[ 264 | "0x9799b475dec92bd99bbdd943013325c36157f383", 265 | "0xad572bba83cd36902b508e89488b0a038986a9f3", 266 | "0x00000000553a85582988aa8ad43fb7dda2466bc7", 267 | "0xa619651c323923ecd5a8e5311771d57ac7e64d87", 268 | "0x0000000071e801062eb0544403f66176bba42dc0", 269 | "0x5f3e759d09e1059e4c46d6984f07cbb36a73bdf1", 270 | "0x000000000000084e91743124a982076c59f10084", 271 | "0x00000000002bde777710c370e08fc83d61b2b8e1", 272 | "0x42d0ba0223700dea8bca7983cc4bf0e000dee772", 273 | "0xfd52a4bd2289aeccf8521f535ec194b7e21cdc96", 274 | "0xfe7f0897239ce9cc6645d9323e6fe428591b821c", 275 | "0x7ee8ab2a8d890c000acc87bf6e22e2ad383e23ce", 276 | "0x860bd2dba9cd475a61e6d1b45e16c365f6d78f66", 277 | "0x78a55b9b3bbeffb36a43d9905f654d2769dc55e8", 278 | "0x2204b8bd8c62c632df16af1475554d07e75769f0", 279 | "0xe33c8e3a0d14a81f0dd7e174830089e82f65fc85", 280 | "0xb958a8f59ac6145851729f73c7a6968311d8b633", 281 | "0x3144d9885e57e6931cf51a2cac6a70dad6b805b2", 282 | "0x000000000000006f6502b7f2bbac8c30a3f67e9a", 283 | "0x42a65ebdcce01d41a6e9f94b7367120fa78d26fe", 284 | "0x6780846518290724038e86c98a1e903888338875", 285 | "0xa21a415b78767166ee222c92bf4b47b6c2f916e0", 286 | "0xf9bf440b8b8423b472c646c3e51aa5e3d04a66f4", 287 | "0xd1c300000000b961df238700ef00600097000049", 288 | "0xd39169726d64d18add3dbbcb3cef12f36db0c70a", 289 | "0x00000000000017c75025d397b91d284bbe8fc7f2", 290 | "0x000000000025d4386f7fb58984cbe110aee3a4c4", 291 | "0x72b94a9e3473fdd9ecf3da7dd6cc6bb218ae79e3", 292 | "0x6cdc900324c935a2807ecc308f8ead1fcd62fe35", 293 | "0x435c90cdbbe09fa5a862a291b79c1623adbe16d0", 294 | "0xb00ba6778cf84100da676101e011b3d229458270", 295 | "0xb00ba6e641a3129b8c515bb14a4c1bba32d2e8df", 296 | "0x8a3960472b3d63894b68df3f10f58f11828d6fd9", 297 | "0xb8db34f834e9df42f2002ceb7b829dad89d08e14", 298 | "0x7e2deaa00273d0b4ef1ceef712e7d9f812df3e8a", 299 | "0x3d71d79c224998e608d03c5ec9b405e7a38505f0", 300 | "0xff73257d2bee2cce718010205cb2c1bb7755db24", 301 | "0x245b47669f44fc23b6e841953b7cc0a7bbdba9ef", 302 | "0x0000000000007f150bd6f54c40a34d7c3d5e9f56", 303 | "0x7c651d7084b4ba899391d2d4d5d3d47fff823351", 304 | "0x661c650c8bfcde6d842f465b3d69ed008638d614", 305 | "0x175789024955c56b06a618806fc13df71d08a377", 306 | // "0x00000000000080c886232e9b7ebbfb942b5987aa", 307 | "0x8be4db5926232bc5b02b841dbede8161924495c4", // sandwich bot 308 | // Old ones, for back-fill 309 | "0x0000000000009480cded7b47d438e73edf0f67e5", 310 | "0x18d81d985d585405688ef7c62806152cf797ae37", 311 | "0x000000000000a32dc5dd625c107898a1c72ad34a", 312 | "0x1b1e08043553cad2a3b82bfc2df40f7dcc0d58aa", 313 | "0x18f60c7bd9fb6619b807d8d81334f1760c69fb59", 314 | "0xb87c7d5a5ff0092cf427855c1ea9b7708d717292", 315 | // Aave Kyber Uni liquidation 316 | "0x80119949f52cb9bf18ecf259e3c3b59f0e5e5a5b", 317 | ], 318 | "KNOWN BOT".to_string(), 319 | ) 320 | }); 321 | 322 | pub fn parse_address(addr: &str) -> Address { 323 | let addr = addr.strip_prefix("0x").unwrap_or(addr); 324 | addr.parse().unwrap() 325 | } 326 | -------------------------------------------------------------------------------- /src/cached_provider.rs: -------------------------------------------------------------------------------- 1 | use async_trait::async_trait; 2 | use ethers::{ 3 | providers::{FromErr, Middleware}, 4 | types::{BlockNumber, Trace}, 5 | }; 6 | use serde::{de::DeserializeOwned, Serialize}; 7 | use std::path::{Path, PathBuf}; 8 | 9 | #[derive(Clone, Debug)] 10 | pub struct CachedProvider { 11 | inner: M, 12 | cache: PathBuf, 13 | } 14 | 15 | use thiserror::Error; 16 | 17 | impl CachedProvider { 18 | /// Creates a new provider with the cache located at the provided path 19 | pub fn new>(inner: M, cache: P) -> Self { 20 | Self { 21 | inner, 22 | cache: cache.into(), 23 | } 24 | } 25 | 26 | fn read>( 27 | &self, 28 | fname: K, 29 | ) -> Result> { 30 | let path = self.cache.join(fname); 31 | let json = std::fs::read_to_string(path)?; 32 | Ok(serde_json::from_str::(&json)?) 33 | } 34 | 35 | fn write>( 36 | &self, 37 | fname: K, 38 | data: T, 39 | ) -> Result<(), CachedProviderError> { 40 | let path = self.cache.join(fname); 41 | let writer = std::fs::File::create(path)?; 42 | Ok(serde_json::to_writer(writer, &data)?) 43 | } 44 | } 45 | 46 | #[async_trait] 47 | impl Middleware for CachedProvider { 48 | type Error = CachedProviderError; 49 | type Provider = M::Provider; 50 | type Inner = M; 51 | 52 | fn inner(&self) -> &Self::Inner { 53 | &self.inner 54 | } 55 | 56 | async fn trace_block(&self, block: BlockNumber) -> Result, Self::Error> { 57 | // check if it exists, else get from the provider 58 | let mut traces = None; 59 | if let BlockNumber::Number(block_number) = block { 60 | traces = self 61 | .read(format!("{}.trace.json", block_number.as_u64())) 62 | .ok(); 63 | }; 64 | 65 | if let Some(traces) = traces { 66 | Ok(traces) 67 | } else { 68 | let traces: Vec = self 69 | .inner() 70 | .trace_block(block) 71 | .await 72 | .map_err(CachedProviderError::MiddlewareError)?; 73 | 74 | let block_number = if let BlockNumber::Number(block_number) = block { 75 | block_number.as_u64() 76 | } else { 77 | self.get_block_number().await?.as_u64() 78 | }; 79 | 80 | self.write(format!("{}.trace.json", block_number), &traces)?; 81 | 82 | Ok(traces) 83 | } 84 | } 85 | } 86 | 87 | #[derive(Error, Debug)] 88 | pub enum CachedProviderError { 89 | /// Thrown when the internal middleware errors 90 | #[error("{0}")] 91 | MiddlewareError(M::Error), 92 | #[error(transparent)] 93 | IoError(#[from] std::io::Error), 94 | #[error(transparent)] 95 | DeserializationError(#[from] serde_json::Error), 96 | } 97 | 98 | impl FromErr for CachedProviderError { 99 | fn from(src: M::Error) -> Self { 100 | CachedProviderError::MiddlewareError(src) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/inspectors/aave.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | addresses::AAVE_LENDING_POOL, 3 | types::{actions::Liquidation, Classification, Inspection, Protocol}, 4 | Inspector, 5 | }; 6 | use ethers::{ 7 | abi::Abi, 8 | contract::BaseContract, 9 | types::{Address, U256}, 10 | }; 11 | 12 | type LiquidationCall = (Address, Address, Address, U256, bool); 13 | 14 | #[derive(Clone, Debug)] 15 | pub struct Aave { 16 | pub pool: BaseContract, 17 | } 18 | 19 | impl Aave { 20 | pub fn new() -> Self { 21 | Aave { 22 | pool: BaseContract::from({ 23 | serde_json::from_str::(include_str!("../../abi/aavepool.json")) 24 | .expect("could not parse aave abi") 25 | }), 26 | } 27 | } 28 | } 29 | 30 | impl Inspector for Aave { 31 | fn inspect(&self, inspection: &mut Inspection) { 32 | for action in inspection.actions.iter_mut() { 33 | match action { 34 | Classification::Unknown(ref mut calltrace) => { 35 | let call = calltrace.as_ref(); 36 | if call.to == *AAVE_LENDING_POOL { 37 | inspection.protocols.insert(Protocol::Aave); 38 | 39 | // https://github.com/aave/aave-protocol/blob/master/contracts/lendingpool/LendingPool.sol#L805 40 | if let Ok((collateral, reserve, user, purchase_amount, _)) = 41 | self.pool 42 | .decode::("liquidationCall", &call.input) 43 | { 44 | // Set the amount to 0. We'll set it at the reducer 45 | *action = Classification::new( 46 | Liquidation { 47 | sent_token: reserve, 48 | sent_amount: purchase_amount, 49 | 50 | received_token: collateral, 51 | received_amount: U256::zero(), 52 | from: call.from, 53 | liquidated_user: user, 54 | }, 55 | calltrace.trace_address.clone(), 56 | ); 57 | } 58 | } 59 | } 60 | Classification::Known(_) | Classification::Prune => {} 61 | } 62 | } 63 | } 64 | } 65 | 66 | #[cfg(test)] 67 | mod tests { 68 | use super::*; 69 | use crate::{ 70 | inspectors::ERC20, reducers::LiquidationReducer, test_helpers::read_trace, Reducer, 71 | }; 72 | 73 | struct MyInspector { 74 | aave: Aave, 75 | erc20: ERC20, 76 | reducer: LiquidationReducer, 77 | } 78 | 79 | impl MyInspector { 80 | fn inspect(&self, inspection: &mut Inspection) { 81 | self.aave.inspect(inspection); 82 | self.erc20.inspect(inspection); 83 | self.reducer.reduce(inspection); 84 | inspection.prune(); 85 | } 86 | 87 | fn new() -> Self { 88 | Self { 89 | aave: Aave::new(), 90 | erc20: ERC20::new(), 91 | reducer: LiquidationReducer::new(), 92 | } 93 | } 94 | } 95 | 96 | #[tokio::test] 97 | async fn simple_liquidation() { 98 | let mut inspection = read_trace("simple_liquidation.json"); 99 | let aave = MyInspector::new(); 100 | aave.inspect(&mut inspection); 101 | 102 | let liquidation = inspection 103 | .known() 104 | .iter() 105 | .find_map(|x| x.as_ref().liquidation()) 106 | .cloned() 107 | .unwrap(); 108 | 109 | assert_eq!( 110 | liquidation.sent_amount.to_string(), 111 | "11558317402311470764075" 112 | ); 113 | assert_eq!( 114 | liquidation.received_amount.to_string(), 115 | "1100830609991235507621" 116 | ); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/inspectors/balancer.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | addresses::BALANCER_PROXY, 3 | inspectors::find_matching, 4 | traits::Inspector, 5 | types::{actions::Trade, Classification, Inspection, Protocol}, 6 | }; 7 | 8 | use ethers::{ 9 | abi::Abi, 10 | contract::BaseContract, 11 | types::{Address, Call as TraceCall, U256}, 12 | }; 13 | 14 | #[derive(Debug, Clone)] 15 | /// An inspector for Uniswap 16 | pub struct Balancer { 17 | bpool: BaseContract, 18 | bproxy: BaseContract, 19 | } 20 | 21 | type Swap = (Address, U256, Address, U256, U256); 22 | 23 | impl Inspector for Balancer { 24 | fn inspect(&self, inspection: &mut Inspection) { 25 | let actions = inspection.actions.to_vec(); 26 | let mut prune = Vec::new(); 27 | for i in 0..inspection.actions.len() { 28 | let action = &mut inspection.actions[i]; 29 | 30 | if let Some(calltrace) = action.as_call() { 31 | let call = calltrace.as_ref(); 32 | let (token_in, _, token_out, _, _) = if let Ok(inner) = self 33 | .bpool 34 | .decode::("swapExactAmountIn", &call.input) 35 | { 36 | inner 37 | } else if let Ok(inner) = self 38 | .bpool 39 | .decode::("swapExactAmountOut", &call.input) 40 | { 41 | inner 42 | } else { 43 | if self.check(calltrace.as_ref()) { 44 | inspection.protocols.insert(Protocol::Balancer); 45 | } 46 | continue; 47 | }; 48 | 49 | // In Balancer, the 2 subtraces of the `swap*` call are the transfers 50 | // In both cases, the in asset is being transferred _to_ the pair, 51 | // and the out asset is transferred _from_ the pair 52 | let t1 = find_matching( 53 | actions.iter().enumerate().skip(i + 1), 54 | |t| t.transfer(), 55 | |t| t.token == token_in, 56 | true, 57 | ); 58 | 59 | let t2 = find_matching( 60 | actions.iter().enumerate().skip(i + 1), 61 | |t| t.transfer(), 62 | |t| t.token == token_out, 63 | true, 64 | ); 65 | 66 | match (t1, t2) { 67 | (Some((j, t1)), Some((k, t2))) => { 68 | if t1.from != t2.to || t2.from != t1.to { 69 | continue; 70 | } 71 | 72 | *action = 73 | Classification::new(Trade::new(t1.clone(), t2.clone()), Vec::new()); 74 | prune.push(j); 75 | prune.push(k); 76 | 77 | inspection.protocols.insert(Protocol::Balancer); 78 | } 79 | _ => {} 80 | }; 81 | } 82 | } 83 | 84 | prune 85 | .iter() 86 | .for_each(|p| inspection.actions[*p] = Classification::Prune); 87 | // TODO: Add checked calls 88 | } 89 | } 90 | 91 | impl Balancer { 92 | fn check(&self, call: &TraceCall) -> bool { 93 | // TODO: Adjust for exchange proxy calls 94 | call.to == *BALANCER_PROXY 95 | } 96 | 97 | /// Constructor 98 | pub fn new() -> Self { 99 | Self { 100 | bpool: BaseContract::from({ 101 | serde_json::from_str::(include_str!("../../abi/bpool.json")) 102 | .expect("could not parse uniswap abi") 103 | }), 104 | bproxy: BaseContract::from({ 105 | serde_json::from_str::(include_str!("../../abi/bproxy.json")) 106 | .expect("could not parse uniswap abi") 107 | }), 108 | } 109 | } 110 | } 111 | 112 | #[cfg(test)] 113 | mod tests { 114 | use super::*; 115 | use crate::test_helpers::*; 116 | use crate::{ 117 | addresses::ADDRESSBOOK, 118 | inspectors::ERC20, 119 | reducers::{ArbitrageReducer, TradeReducer}, 120 | types::Inspection, 121 | Inspector, Reducer, 122 | }; 123 | 124 | struct MyInspector { 125 | erc20: ERC20, 126 | balancer: Balancer, 127 | trade: TradeReducer, 128 | arb: ArbitrageReducer, 129 | } 130 | 131 | impl MyInspector { 132 | fn inspect(&self, inspection: &mut Inspection) { 133 | self.erc20.inspect(inspection); 134 | self.balancer.inspect(inspection); 135 | self.trade.reduce(inspection); 136 | self.arb.reduce(inspection); 137 | inspection.prune(); 138 | } 139 | 140 | fn new() -> Self { 141 | Self { 142 | erc20: ERC20::new(), 143 | balancer: Balancer::new(), 144 | trade: TradeReducer::new(), 145 | arb: ArbitrageReducer::new(), 146 | } 147 | } 148 | } 149 | 150 | #[test] 151 | fn bot_trade() { 152 | let mut inspection = read_trace("balancer_trade.json"); 153 | let bal = MyInspector::new(); 154 | bal.inspect(&mut inspection); 155 | 156 | let known = inspection.known(); 157 | 158 | assert_eq!(known.len(), 4); 159 | let t1 = known[0].as_ref().transfer().unwrap(); 160 | assert_eq!( 161 | t1.amount, 162 | U256::from_dec_str("134194492674651541324").unwrap() 163 | ); 164 | let trade = known[1].as_ref().trade().unwrap(); 165 | assert_eq!( 166 | trade.t1.amount, 167 | U256::from_dec_str("7459963749616500736").unwrap() 168 | ); 169 | let _t2 = known[2].as_ref().transfer().unwrap(); 170 | let _t3 = known[3].as_ref().transfer().unwrap(); 171 | } 172 | 173 | #[test] 174 | fn comp_collect_trade() { 175 | let mut inspection = read_trace("balancer_trade2.json"); 176 | let bal = MyInspector::new(); 177 | bal.inspect(&mut inspection); 178 | 179 | let known = inspection.known(); 180 | 181 | assert_eq!(known.len(), 3); 182 | let trade = known[0].as_ref().trade().unwrap(); 183 | assert_eq!( 184 | trade.t1.amount, 185 | U256::from_dec_str("1882725882636").unwrap() 186 | ); 187 | assert_eq!(ADDRESSBOOK.get(&trade.t1.token).unwrap(), "cDAI",); 188 | assert_eq!( 189 | trade.t2.amount, 190 | U256::from_dec_str("2048034448010009909").unwrap() 191 | ); 192 | assert_eq!(ADDRESSBOOK.get(&trade.t2.token).unwrap(), "COMP",); 193 | 194 | // 2 comp payouts 195 | let t1 = known[1].as_ref().transfer().unwrap(); 196 | assert_eq!(ADDRESSBOOK.get(&t1.token).unwrap(), "COMP",); 197 | let t2 = known[2].as_ref().transfer().unwrap(); 198 | assert_eq!(ADDRESSBOOK.get(&t2.token).unwrap(), "COMP",); 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /src/inspectors/compound.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | actions_after, 3 | addresses::{CETH, COMPTROLLER, COMP_ORACLE, WETH}, 4 | traits::Inspector, 5 | types::{ 6 | actions::{Liquidation, SpecificAction}, 7 | Classification, Inspection, Protocol, Status, 8 | }, 9 | }; 10 | use ethers::{ 11 | abi::{Abi, FunctionExt}, 12 | contract::{abigen, BaseContract, ContractError}, 13 | providers::Middleware, 14 | types::{Address, Call, CallType, U256}, 15 | }; 16 | 17 | use std::collections::HashMap; 18 | 19 | type LiquidateBorrow = (Address, U256, Address); 20 | type LiquidateBorrowEth = (Address, Address); 21 | type SeizeInternal = (Address, Address, Address, U256); 22 | 23 | abigen!( 24 | Comptroller, 25 | "abi/comptroller.json", 26 | methods { 27 | // TODO: Fix bug in ethers-rs so that we can rename them properly 28 | borrowGuardianPaused(address) as borrow_guardian_paused2; 29 | mintGuardianPaused(address) as mint_guardian_paused2; 30 | actionGuardianPaused(address) as action_paused2; 31 | }, 32 | ); 33 | 34 | abigen!(CToken, "abi/ctoken.json",); 35 | 36 | #[derive(Debug, Clone)] 37 | /// An inspector for Compound liquidations 38 | pub struct Compound { 39 | ctoken: BaseContract, 40 | cether: BaseContract, 41 | comptroller: BaseContract, 42 | ctoken_to_token: HashMap, 43 | } 44 | 45 | impl Inspector for Compound { 46 | fn inspect(&self, inspection: &mut Inspection) { 47 | let mut found = false; 48 | for i in 0..inspection.actions.len() { 49 | // split in two so that we can iterate mutably without cloning 50 | let (action, subtraces) = actions_after(&mut inspection.actions, i); 51 | 52 | // if the provided action is a liquidation, start parsing all the subtraces 53 | if let Some((mut liquidation, trace)) = self.try_as_liquidation(&action) { 54 | inspection.protocols.insert(Protocol::Compound); 55 | 56 | // omit the double-counted Dcall 57 | if let Some(ref call_type) = action.as_call().map(|call| &call.call.call_type) { 58 | if matches!(call_type, CallType::DelegateCall) { 59 | continue; 60 | } 61 | } 62 | 63 | // once we find the `seize` call, parse it 64 | if let Some(seized) = subtraces.iter().find_map(|seize| self.try_as_seize(seize)) { 65 | liquidation.received_amount = seized.2; 66 | 67 | *action = Classification::new(liquidation, trace); 68 | if inspection.status != Status::Reverted { 69 | inspection.status = Status::Success; 70 | } 71 | found = true; 72 | } 73 | } else if self.is_preflight(&action) && !found { 74 | // insert an empty liquidation for the actions upstream 75 | *action = Classification::new(SpecificAction::LiquidationCheck, Vec::new()); 76 | // a pre-flight is only marked as "Checked" if a successful 77 | // liquidation was not already found before it 78 | inspection.status = Status::Checked; 79 | } 80 | } 81 | } 82 | } 83 | 84 | impl Compound { 85 | /// Constructor 86 | pub fn new>(ctoken_to_token: T) -> Self { 87 | Self { 88 | ctoken: BaseContract::from({ 89 | serde_json::from_str::(include_str!("../../abi/ctoken.json")) 90 | .expect("could not parse ctoken abi") 91 | }), 92 | cether: BaseContract::from({ 93 | serde_json::from_str::(include_str!("../../abi/cether.json")) 94 | .expect("could not parse ctoken abi") 95 | }), 96 | comptroller: BaseContract::from({ 97 | serde_json::from_str::(include_str!("../../abi/comptroller.json")) 98 | .expect("could not parse ctoken abi") 99 | }), 100 | ctoken_to_token: ctoken_to_token.into_iter().collect(), 101 | } 102 | } 103 | 104 | /// Instantiates Compound with all live markets 105 | /// 106 | /// # Panics 107 | /// 108 | /// - If the `Ctoken.underlying` call fails 109 | pub async fn create( 110 | provider: std::sync::Arc, 111 | ) -> Result> { 112 | let comptroller = Comptroller::new(*COMPTROLLER, provider.clone()); 113 | 114 | let markets = comptroller.get_all_markets().call().await?; 115 | let futs = markets 116 | .into_iter() 117 | .map(|market| { 118 | let provider = provider.clone(); 119 | async move { 120 | if market != *CETH { 121 | ( 122 | market, 123 | CToken::new(market, provider) 124 | .underlying() 125 | .call() 126 | .await 127 | .expect("could not get underlying"), 128 | ) 129 | } else { 130 | (market, *WETH) 131 | } 132 | } 133 | }) 134 | .collect::>(); 135 | let res = futures::future::join_all(futs).await; 136 | 137 | Ok(Compound::new(res)) 138 | } 139 | 140 | /// Find the liquidation action 141 | fn try_as_liquidation(&self, action: &Classification) -> Option<(Liquidation, Vec)> { 142 | match action { 143 | Classification::Unknown(ref calltrace) => { 144 | let call = calltrace.as_ref(); 145 | if let Ok((liquidated_user, repaid_amount, ctoken_collateral)) = 146 | self.ctoken 147 | .decode::("liquidateBorrow", &call.input) 148 | { 149 | Some(( 150 | Liquidation { 151 | sent_token: *self.underlying(&call.to), 152 | sent_amount: repaid_amount, 153 | 154 | received_token: ctoken_collateral, 155 | received_amount: 0.into(), 156 | 157 | from: call.from, 158 | liquidated_user, 159 | }, 160 | calltrace.trace_address.clone(), 161 | )) 162 | } else if let Ok((liquidated_user, ctoken_collateral)) = 163 | self.cether 164 | .decode::("liquidateBorrow", &call.input) 165 | { 166 | Some(( 167 | Liquidation { 168 | sent_token: *self.underlying(&call.to), 169 | sent_amount: call.value, 170 | 171 | received_token: ctoken_collateral, 172 | received_amount: 0.into(), 173 | 174 | from: call.from, 175 | liquidated_user, 176 | }, 177 | calltrace.trace_address.clone(), 178 | )) 179 | } else { 180 | None 181 | } 182 | } 183 | _ => None, 184 | } 185 | } 186 | 187 | // Parses a subtrace 188 | fn try_as_seize(&self, call: &Call) -> Option<(Address, Address, U256)> { 189 | if let Ok((_seizertoken, liquidator, borrower, seizetokens)) = self 190 | .ctoken 191 | .decode::("seizeInternal", &call.input) 192 | { 193 | Some((borrower, liquidator, seizetokens)) 194 | } else if let Ok((liquidator, borrower, seizetokens)) = self 195 | .ctoken 196 | .decode::<(Address, Address, U256), _>("seize", &call.input) 197 | { 198 | Some((borrower, liquidator, seizetokens)) 199 | } else { 200 | None 201 | } 202 | } 203 | 204 | fn is_preflight(&self, action: &Classification) -> bool { 205 | match action { 206 | Classification::Unknown(ref calltrace) => { 207 | let call = calltrace.as_ref(); 208 | // checks if liquidation is allowed 209 | call.to == *COMPTROLLER && call.input.as_ref().starts_with(&self.comptroller.as_ref().function("liquidateBorrowAllowed").unwrap().selector()) || 210 | // checks oracle price 211 | call.to == *COMP_ORACLE && call.input.as_ref().starts_with(ðers::utils::id("getUnderlyingPrice(address)")) 212 | } 213 | _ => false, 214 | } 215 | } 216 | 217 | // helper for converting cToken to Token address. 218 | // TODO: Should this also include decimals? Or should we assume that 219 | // cTokens always use 8 decimals 220 | fn underlying<'a>(&'a self, address: &'a Address) -> &'a Address { 221 | if let Some(inner) = self.ctoken_to_token.get(address) { 222 | inner 223 | } else { 224 | &address 225 | } 226 | } 227 | } 228 | 229 | #[cfg(test)] 230 | mod tests { 231 | use super::*; 232 | use crate::{ 233 | addresses::{parse_address, ADDRESSBOOK}, 234 | test_helpers::*, 235 | types::Status, 236 | Inspector, 237 | }; 238 | use ethers::providers::Provider; 239 | use std::convert::TryFrom; 240 | 241 | #[test] 242 | // https://etherscan.io/tx/0xb7ba825294f757f8b8b6303b2aef542bcaebc9cc0217ddfaf822200a00594ed9 243 | fn liquidate() { 244 | let mut inspection = read_trace("compound_liquidation.json"); 245 | let ctoken_to_token = vec![( 246 | parse_address("0xb3319f5d18bc0d84dd1b4825dcde5d5f7266d407"), 247 | parse_address("0xe41d2489571d322189246dafa5ebde1f4699f498"), 248 | )]; 249 | let compound = Compound::new(ctoken_to_token); 250 | compound.inspect(&mut inspection); 251 | 252 | let liquidation = inspection 253 | .known() 254 | .iter() 255 | .find_map(|x| x.as_ref().liquidation()) 256 | .cloned() 257 | .unwrap(); 258 | 259 | assert_eq!(ADDRESSBOOK.get(&liquidation.sent_token).unwrap(), "ZRX"); 260 | // cETH has 8 decimals 261 | assert_eq!(liquidation.received_amount, 5250648.into()); 262 | // ZRX has 18 decimals 263 | assert_eq!(liquidation.sent_amount, 653800000000000000u64.into()); 264 | 265 | assert_eq!(inspection.protocols, crate::set![Protocol::Compound]); 266 | assert_eq!(inspection.status, Status::Success); 267 | } 268 | 269 | #[tokio::test] 270 | async fn instantiate() { 271 | let provider = 272 | Provider::try_from("https://mainnet.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27") 273 | .unwrap(); 274 | let compound = Compound::create(std::sync::Arc::new(provider)) 275 | .await 276 | .unwrap(); 277 | 278 | // cZRX -> ZRX 279 | assert_eq!( 280 | *compound.underlying(&parse_address("0xb3319f5d18bc0d84dd1b4825dcde5d5f7266d407")), 281 | parse_address("0xe41d2489571d322189246dafa5ebde1f4699f498"), 282 | ); 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /src/inspectors/curve.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::too_many_arguments)] 2 | use crate::{ 3 | addresses::CURVE_REGISTRY, 4 | traits::Inspector, 5 | types::{actions::AddLiquidity, Classification, Inspection, Protocol}, 6 | }; 7 | 8 | use ethers::{ 9 | abi::parse_abi, 10 | contract::decode_function_data, 11 | contract::{abigen, ContractError}, 12 | providers::Middleware, 13 | types::{Address, Bytes, Call as TraceCall, U256}, 14 | }; 15 | use ethers::{abi::Abi, contract::BaseContract}; 16 | use std::collections::HashMap; 17 | 18 | // Type aliases for Curve 19 | type Exchange = (u128, u128, U256, U256); 20 | 21 | #[derive(Debug, Clone)] 22 | /// An inspector for Curve 23 | pub struct Curve { 24 | pool: BaseContract, 25 | pool4: BaseContract, 26 | pools: HashMap>, 27 | } 28 | 29 | abigen!( 30 | CurveRegistry, 31 | "abi/curveregistry.json", 32 | methods { 33 | find_pool_for_coins(address,address,uint256) as find_pool_for_coins2; 34 | } 35 | ); 36 | 37 | impl Inspector for Curve { 38 | fn inspect(&self, inspection: &mut Inspection) { 39 | let mut prune = Vec::new(); 40 | for i in 0..inspection.actions.len() { 41 | let action = &mut inspection.actions[i]; 42 | 43 | if let Some(calltrace) = action.as_call() { 44 | let call = calltrace.as_ref(); 45 | if self.check(call) { 46 | inspection.protocols.insert(Protocol::Curve); 47 | } 48 | 49 | if let Some(liquidity) = self.as_add_liquidity(&call.to, &call.input) { 50 | *action = Classification::new(liquidity, calltrace.trace_address.clone()); 51 | prune.push(i); 52 | } 53 | } 54 | } 55 | 56 | let actions = inspection.actions.to_vec(); 57 | prune 58 | .into_iter() 59 | .for_each(|idx| actions[idx].prune_subcalls(&mut inspection.actions)); 60 | // TODO: Add checked calls 61 | } 62 | } 63 | 64 | impl Curve { 65 | /// Constructor 66 | pub fn new)>>(pools: T) -> Self { 67 | Self { 68 | pool: serde_json::from_str::(include_str!("../../abi/curvepool.json")) 69 | .expect("could not parse Curve 2-pool abi") 70 | .into(), 71 | pool4: parse_abi(&[ 72 | "function add_liquidity(uint256[4] calldata amounts, uint256 deadline) external", 73 | ]) 74 | .expect("could not parse curve 4-pool abi") 75 | .into(), 76 | pools: pools.into_iter().collect(), 77 | } 78 | } 79 | 80 | fn as_add_liquidity(&self, to: &Address, data: &Bytes) -> Option { 81 | let tokens = self.pools.get(to)?; 82 | // adapter for Curve's pool-specific abi decoding 83 | // TODO: Do we need to add the tripool? 84 | let amounts = match tokens.len() { 85 | 2 => self 86 | .pool 87 | .decode::<([U256; 2], U256), _>("add_liquidity", data) 88 | .map(|x| x.0.to_vec()), 89 | 4 => self 90 | .pool4 91 | .decode::<([U256; 4], U256), _>("add_liquidity", data) 92 | .map(|x| x.0.to_vec()), 93 | _ => return None, 94 | }; 95 | let amounts = match amounts { 96 | Ok(tokens) => tokens, 97 | Err(_) => return None, 98 | }; 99 | 100 | Some(AddLiquidity { 101 | tokens: tokens.clone(), 102 | amounts, 103 | }) 104 | } 105 | 106 | pub async fn create( 107 | provider: std::sync::Arc, 108 | ) -> Result> { 109 | let mut this = Self::new(vec![]); 110 | let registry = CurveRegistry::new(*CURVE_REGISTRY, provider); 111 | 112 | let pool_count = registry.pool_count().call().await?; 113 | // TODO: Cache these locally. 114 | for i in 0..pool_count.as_u64() { 115 | let pool = registry.pool_list(i.into()).call().await?; 116 | let tokens = registry.get_underlying_coins(pool).call().await?; 117 | this.pools.insert(pool, tokens.to_vec()); 118 | } 119 | 120 | Ok(this) 121 | } 122 | 123 | fn check(&self, call: &TraceCall) -> bool { 124 | if !self.pools.is_empty() && self.pools.get(&call.to).is_none() { 125 | return false; 126 | } 127 | for function in self.pool.as_ref().functions() { 128 | // exchange & exchange_underlying 129 | if function.name.starts_with("exchange") 130 | && decode_function_data::(function, &call.input, true).is_ok() 131 | { 132 | return true; 133 | } 134 | } 135 | false 136 | } 137 | } 138 | 139 | #[cfg(test)] 140 | mod tests { 141 | use super::*; 142 | use crate::{ 143 | inspectors::ERC20, 144 | reducers::{ArbitrageReducer, TradeReducer}, 145 | test_helpers::read_trace, 146 | Reducer, 147 | }; 148 | use ethers::providers::Provider; 149 | use std::convert::TryFrom; 150 | 151 | #[tokio::test] 152 | async fn instantiate() { 153 | let provider = 154 | Provider::try_from("https://mainnet.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27") 155 | .unwrap(); 156 | let curve = Curve::create(std::sync::Arc::new(provider)).await.unwrap(); 157 | 158 | assert!(!curve.pools.is_empty()); 159 | } 160 | 161 | struct MyInspector { 162 | inspector: Curve, 163 | erc20: ERC20, 164 | reducer1: TradeReducer, 165 | reducer2: ArbitrageReducer, 166 | } 167 | 168 | impl MyInspector { 169 | fn inspect(&self, inspection: &mut Inspection) { 170 | self.inspector.inspect(inspection); 171 | self.erc20.inspect(inspection); 172 | self.reducer1.reduce(inspection); 173 | self.reducer2.reduce(inspection); 174 | inspection.prune(); 175 | } 176 | 177 | fn new() -> Self { 178 | Self { 179 | inspector: Curve::new(vec![]), 180 | erc20: ERC20::new(), 181 | reducer1: TradeReducer::new(), 182 | reducer2: ArbitrageReducer::new(), 183 | } 184 | } 185 | } 186 | 187 | #[tokio::test] 188 | async fn simple_arb() { 189 | let mut inspection = read_trace("simple_curve_arb.json"); 190 | let inspector = MyInspector::new(); 191 | inspector.inspect(&mut inspection); 192 | 193 | let arb = inspection 194 | .known() 195 | .iter() 196 | .find_map(|x| x.as_ref().arbitrage()) 197 | .cloned() 198 | .unwrap(); 199 | assert_eq!(arb.profit.to_string(), "45259140804"); 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /src/inspectors/erc20.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | addresses::{ETH, WETH}, 3 | types::{ 4 | actions::{Deposit, SpecificAction, Transfer, Withdrawal}, 5 | Classification, Inspection, 6 | }, 7 | Inspector, 8 | }; 9 | use ethers::{ 10 | abi::parse_abi, 11 | contract::BaseContract, 12 | types::{Address, Call as TraceCall, CallType, U256}, 13 | }; 14 | 15 | #[derive(Debug, Clone)] 16 | /// Decodes ERC20 calls 17 | pub struct ERC20(BaseContract); 18 | 19 | impl Inspector for ERC20 { 20 | fn inspect(&self, inspection: &mut Inspection) { 21 | inspection.actions.iter_mut().for_each(|classification| { 22 | if let Some(calltrace) = classification.as_call() { 23 | if let Some(transfer) = self.try_parse(calltrace.as_ref()) { 24 | *classification = Classification::new(transfer, calltrace.trace_address.clone()) 25 | } 26 | } 27 | }) 28 | } 29 | } 30 | 31 | impl ERC20 { 32 | pub fn new() -> Self { 33 | Self(BaseContract::from( 34 | parse_abi(&[ 35 | "function transferFrom(address, address, uint256)", 36 | "function transfer(address, uint256)", 37 | "function deposit()", 38 | "function withdraw(uint256)", 39 | "function mint(address, uint256)", 40 | "function burnFrom(address, uint256)", 41 | ]) 42 | .expect("could not parse erc20 abi"), 43 | )) 44 | } 45 | 46 | /// Parse a Call trace to discover a token action 47 | pub fn try_parse(&self, trace_call: &TraceCall) -> Option { 48 | if trace_call.gas == 2300.into() { 49 | return None; 50 | } 51 | 52 | // do not parse delegatecall's 53 | if trace_call.call_type != CallType::Call { 54 | return None; 55 | } 56 | 57 | let token = trace_call.to; 58 | if let Ok((from, to, amount)) = self 59 | .0 60 | .decode::<(Address, Address, U256), _>("transferFrom", &trace_call.input) 61 | { 62 | Some(SpecificAction::Transfer(Transfer { 63 | from, 64 | to, 65 | amount, 66 | token, 67 | })) 68 | } else if let Ok((from, amount)) = self 69 | .0 70 | .decode::<(Address, U256), _>("burnFrom", &trace_call.input) 71 | { 72 | Some(SpecificAction::Transfer(Transfer { 73 | from, 74 | // Burns send to `0x0` 75 | to: Address::zero(), 76 | amount, 77 | token, 78 | })) 79 | } else if let Ok((to, amount)) = self 80 | .0 81 | .decode::<(Address, U256), _>("mint", &trace_call.input) 82 | { 83 | Some(SpecificAction::Transfer(Transfer { 84 | // Mints create from `0x0` 85 | from: Address::zero(), 86 | to, 87 | amount, 88 | token, 89 | })) 90 | } else if let Ok((to, amount)) = self 91 | .0 92 | .decode::<(Address, U256), _>("transfer", &trace_call.input) 93 | { 94 | Some(SpecificAction::Transfer(Transfer { 95 | from: trace_call.from, 96 | to, 97 | amount, 98 | token, 99 | })) 100 | } else if let Ok(amount) = self.0.decode::("withdraw", &trace_call.input) { 101 | Some(SpecificAction::WethWithdrawal(Withdrawal { 102 | to: trace_call.from, 103 | amount, 104 | })) 105 | } else if trace_call 106 | .input 107 | .as_ref() 108 | .starts_with(ðers::utils::id("deposit()")) 109 | { 110 | Some(SpecificAction::WethDeposit(Deposit { 111 | from: trace_call.from, 112 | amount: trace_call.value, 113 | })) 114 | } else if trace_call.value > 0.into() && trace_call.from != *WETH { 115 | // ETH transfer 116 | Some(SpecificAction::Transfer(Transfer { 117 | from: trace_call.from, 118 | to: trace_call.to, 119 | amount: trace_call.value, 120 | token: *ETH, 121 | })) 122 | } else { 123 | None 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/inspectors/mod.rs: -------------------------------------------------------------------------------- 1 | //! # Inspectors 2 | //! 3 | //! All inspectors go here. An inspector is an implementer of the `Inspector` 4 | //! trait and is responsible for decoding a `Trace` in isolation. No sub-trace 5 | //! specific logic needs to be written. 6 | 7 | mod uniswap; 8 | /// A Uniswap inspector 9 | pub use uniswap::Uniswap; 10 | 11 | mod curve; 12 | /// A Curve inspector 13 | pub use curve::Curve; 14 | 15 | mod balancer; 16 | /// A Balancer inspector 17 | pub use balancer::Balancer; 18 | 19 | mod aave; 20 | /// An Aave inspector 21 | pub use aave::Aave; 22 | 23 | mod erc20; 24 | /// ERC20 Inspector, to be used for parsing subtraces involving transfer/transferFrom 25 | pub use erc20::ERC20; 26 | 27 | mod batch; 28 | /// Takes multiple inspectors 29 | pub use batch::{BatchEvaluationError, BatchInspector}; 30 | 31 | mod compound; 32 | pub use compound::Compound; 33 | 34 | mod zeroex; 35 | pub use zeroex::ZeroEx; 36 | 37 | use crate::types::{actions::SpecificAction, Classification}; 38 | 39 | /// Given an iterator over index,Classification tuples, it will try to cast 40 | /// each classification to the given specific action (depending on the function given 41 | /// to `cast`), and then it will check if it satisfies a condition. If yes, it returns 42 | /// that classification and its index. If `check_all` is set to false, it will return 43 | /// None if the action we're looking for is not the first known action, e.g. 44 | /// given a [Deposit, Transfer, Trade], if we're looking for a Trade, `check_all` must 45 | /// be set to true, otherwise once the Transfer is hit, it will return None 46 | pub(crate) fn find_matching<'a, I, T, F1, F2>( 47 | mut actions: I, 48 | cast: F1, 49 | check_fn: F2, 50 | check_all: bool, 51 | ) -> Option<(usize, &'a T)> 52 | where 53 | I: Iterator, 54 | F1: Fn(&SpecificAction) -> Option<&T>, 55 | F2: Fn(&T) -> bool, 56 | { 57 | let mut found_known = false; 58 | actions.find_map(|(j, a)| { 59 | if check_all || !found_known { 60 | if let Some(action) = a.as_action() { 61 | if let Some(t) = cast(&action) { 62 | if check_fn(t) { 63 | return Some((j, t)); 64 | } 65 | } 66 | found_known = true; 67 | } 68 | } 69 | None 70 | }) 71 | } 72 | -------------------------------------------------------------------------------- /src/inspectors/zeroex.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | addresses::PROTOCOLS, 3 | traits::Inspector, 4 | types::{actions::Transfer, Classification, Inspection, Protocol}, 5 | }; 6 | 7 | use ethers::{ 8 | abi::parse_abi, 9 | contract::BaseContract, 10 | types::{Address, Bytes, U256}, 11 | }; 12 | 13 | #[derive(Debug, Clone)] 14 | /// An inspector for ZeroEx Exchange Proxy transfers 15 | pub struct ZeroEx { 16 | bridge: BaseContract, 17 | } 18 | 19 | type BridgeTransfer = (Address, Address, Address, U256, Bytes); 20 | 21 | impl ZeroEx { 22 | pub fn new() -> Self { 23 | let bridge = BaseContract::from( 24 | parse_abi(&[ 25 | "function bridgeTransferFrom(address tokenAddress, address from, address to, uint256 amount, bytes calldata bridgeData)" 26 | ]).expect("could not parse bridge abi")); 27 | 28 | Self { bridge } 29 | } 30 | } 31 | 32 | impl Inspector for ZeroEx { 33 | fn inspect(&self, inspection: &mut Inspection) { 34 | let actions = inspection.actions.to_vec(); 35 | let mut prune = Vec::new(); 36 | for i in 0..inspection.actions.len() { 37 | let action = &mut inspection.actions[i]; 38 | 39 | if let Some(calltrace) = action.as_call() { 40 | let call = calltrace.as_ref(); 41 | 42 | if let Ok(transfer) = self 43 | .bridge 44 | .decode::("bridgeTransferFrom", &call.input) 45 | { 46 | // we found a 0x transaction 47 | inspection.protocols.insert(Protocol::ZeroEx); 48 | 49 | // the bridge call will tell us which sub-protocol was used 50 | if let Some(protocol) = PROTOCOLS.get(&transfer.1) { 51 | inspection.protocols.insert(*protocol); 52 | } 53 | 54 | // change this to a transfer 55 | *action = Classification::new( 56 | Transfer { 57 | token: transfer.0, 58 | from: transfer.1, 59 | to: transfer.2, 60 | amount: transfer.3, 61 | }, 62 | calltrace.trace_address.clone(), 63 | ); 64 | 65 | // keep the index to prune all the subcalls 66 | prune.push(i); 67 | } 68 | } 69 | } 70 | 71 | // remove the subcalls from any of the classified calls 72 | prune 73 | .into_iter() 74 | .for_each(|idx| actions[idx].prune_subcalls(&mut inspection.actions)); 75 | } 76 | } 77 | 78 | #[cfg(test)] 79 | mod tests { 80 | use super::*; 81 | use crate::{ 82 | inspectors::ERC20, 83 | reducers::{ArbitrageReducer, TradeReducer}, 84 | test_helpers::*, 85 | types::Status, 86 | Reducer, 87 | }; 88 | 89 | struct MyInspector { 90 | zeroex: ZeroEx, 91 | erc20: ERC20, 92 | trade: TradeReducer, 93 | arbitrage: ArbitrageReducer, 94 | } 95 | 96 | impl MyInspector { 97 | fn inspect(&self, inspection: &mut Inspection) { 98 | self.zeroex.inspect(inspection); 99 | self.erc20.inspect(inspection); 100 | self.trade.reduce(inspection); 101 | self.arbitrage.reduce(inspection); 102 | inspection.prune(); 103 | } 104 | 105 | fn new() -> Self { 106 | Self { 107 | zeroex: ZeroEx::new(), 108 | erc20: ERC20::new(), 109 | trade: TradeReducer::new(), 110 | arbitrage: ArbitrageReducer::new(), 111 | } 112 | } 113 | } 114 | 115 | #[test] 116 | // Split trade between balancer and uniswap via the 0x exchange proxy 117 | fn balancer_uni_zeroex() { 118 | let mut inspection = read_trace("exchange_proxy.json"); 119 | let zeroex = MyInspector::new(); 120 | zeroex.inspect(&mut inspection); 121 | assert_eq!( 122 | inspection.protocols, 123 | crate::set![Protocol::ZeroEx, Protocol::Balancer, Protocol::Uniswap] 124 | ); 125 | assert_eq!(inspection.status, Status::Reverted); 126 | let known = inspection.known(); 127 | 128 | assert_eq!(known.len(), 3); 129 | // transfer in 130 | let t1 = known[0].as_ref().transfer().unwrap(); 131 | assert_eq!( 132 | t1.amount, 133 | U256::from_dec_str("1000000000000000000000").unwrap() 134 | ); 135 | 136 | // balancer trade out 137 | let balancer = known[1].as_ref().trade().unwrap(); 138 | assert_eq!( 139 | balancer.t1.amount, 140 | U256::from_dec_str("384007192433857968681").unwrap() 141 | ); 142 | 143 | let uniswap = known[2].as_ref().trade().unwrap(); 144 | assert_eq!( 145 | uniswap.t1.amount, 146 | U256::from_dec_str("622513125832506272941").unwrap() 147 | ); 148 | 149 | // the trade required more than we put in (TODO: is this correct?) 150 | assert_ne!(t1.amount, balancer.t1.amount + uniswap.t1.amount); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::clippy::new_without_default)] 2 | #![allow(clippy::clippy::clippy::single_match)] 3 | //! MEV-INSPECT 4 | //! 5 | //! Utility for MEV Inspection 6 | //! 7 | //! - Inspectors 8 | //! - UniswapV2 (and clones) 9 | //! - Processor 10 | //! - Database 11 | //! - PostGres (maybe Influx) + Grafana? 12 | 13 | /// MEV Inspectors 14 | pub mod inspectors; 15 | 16 | /// Reducers 17 | pub mod reducers; 18 | 19 | /// Batch Inspector which tries to decode traces using 20 | /// multiple inspectors 21 | pub use inspectors::BatchInspector; 22 | 23 | /// Types for MEV-INSPECT 24 | pub mod types; 25 | 26 | /// Various addresses which are found among protocols 27 | pub mod addresses; 28 | 29 | mod cached_provider; 30 | pub use cached_provider::CachedProvider; 31 | 32 | #[cfg(test)] 33 | mod test_helpers; 34 | 35 | mod traits; 36 | pub use traits::*; 37 | 38 | /// PostGres trait implementations 39 | mod mevdb; 40 | pub use mevdb::{BatchInserts, MevDB}; 41 | 42 | mod prices; 43 | pub use prices::HistoricalPrice; 44 | 45 | /// Checks if `a2` is a subtrace of `a1` 46 | pub(crate) fn is_subtrace(a1: &[usize], a2: &[usize]) -> bool { 47 | if a1.is_empty() { 48 | return false; 49 | } 50 | 51 | a1 == &a2[..std::cmp::min(a1.len(), a2.len())] 52 | } 53 | 54 | use crate::types::Classification; 55 | use ethers::types::Call; 56 | pub(crate) fn actions_after( 57 | actions: &mut [Classification], 58 | i: usize, 59 | ) -> (&mut Classification, Vec<&Call>) { 60 | let (actions, rest) = actions.split_at_mut(i + 1); 61 | let action = &mut actions[actions.len() - 1]; 62 | 63 | let subtraces = rest 64 | .iter() 65 | .filter_map(|t| t.as_call().map(|x| &x.call)) 66 | .collect(); 67 | (action, subtraces) 68 | } 69 | 70 | #[cfg(test)] 71 | mod tests { 72 | use super::is_subtrace; 73 | 74 | #[test] 75 | fn check() { 76 | let test_cases = vec![ 77 | (vec![0], vec![0, 1], true), 78 | (vec![0], vec![0, 0], true), 79 | (vec![0, 1], vec![0, 1, 0], true), 80 | (vec![0, 1], vec![0, 1, 1], true), 81 | (vec![0, 1], vec![0, 2], false), 82 | (vec![0, 1], vec![0], false), 83 | (vec![], vec![0, 1], false), 84 | (vec![15], vec![15, 0, 3, 22, 0, 0], true), 85 | ]; 86 | 87 | for (a1, a2, expected) in test_cases { 88 | assert_eq!(is_subtrace(&a1, &a2), expected); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use mev_inspect::{ 2 | inspectors::{Aave, Balancer, Compound, Curve, Uniswap, ZeroEx, ERC20}, 3 | reducers::{ArbitrageReducer, LiquidationReducer, TradeReducer}, 4 | types::Evaluation, 5 | BatchInserts, BatchInspector, CachedProvider, HistoricalPrice, Inspector, MevDB, Reducer, 6 | }; 7 | 8 | use ethers::{ 9 | providers::{Middleware, Provider, StreamExt}, 10 | types::{BlockNumber, TxHash, U256}, 11 | }; 12 | 13 | use futures::SinkExt; 14 | use gumdrop::Options; 15 | use std::io::Write; 16 | use std::{collections::HashMap, convert::TryFrom, path::PathBuf, sync::Arc}; 17 | 18 | #[derive(Debug, Options, Clone)] 19 | struct Opts { 20 | help: bool, 21 | 22 | #[options(help = "clear and re-build the database")] 23 | reset: bool, 24 | 25 | #[options(help = "do not skip blocks which already exist")] 26 | overwrite: bool, 27 | 28 | #[options( 29 | default = "http://localhost:8545", 30 | help = "The tracing / archival node's URL" 31 | )] 32 | url: String, 33 | 34 | #[options(help = "Path to where traces will be cached")] 35 | cache: Option, 36 | 37 | #[options(help = "Database config")] 38 | db_cfg: tokio_postgres::Config, 39 | #[options(default = "mev_inspections", help = "the table of the database")] 40 | db_table: String, 41 | 42 | // Single tx or many blocks 43 | #[options(command)] 44 | cmd: Option, 45 | } 46 | 47 | #[derive(Debug, Options, Clone)] 48 | enum Command { 49 | #[options(help = "inspect a transaction")] 50 | Tx(TxOpts), 51 | #[options(help = "inspect a range of blocks")] 52 | Blocks(BlockOpts), 53 | } 54 | 55 | #[derive(Debug, Options, Clone)] 56 | struct TxOpts { 57 | help: bool, 58 | #[options(free, help = "the transaction's hash")] 59 | tx: TxHash, 60 | } 61 | #[derive(Debug, Options, Clone)] 62 | struct BlockOpts { 63 | help: bool, 64 | #[options(help = "the block to start tracing from")] 65 | from: u64, 66 | #[options(help = "the block to finish tracing at")] 67 | to: u64, 68 | #[options(default = "4", help = "How many separate tasks to use")] 69 | tasks: u64, 70 | #[options( 71 | default = "10", 72 | help = "Maximum of requests each task is allowed to execute concurrently" 73 | )] 74 | max_requests: usize, 75 | } 76 | 77 | #[tokio::main] 78 | async fn main() -> anyhow::Result<()> { 79 | pretty_env_logger::init(); 80 | let opts = Opts::parse_args_default_or_exit(); 81 | 82 | // Instantiate the provider and read from the cached files if needed 83 | if let Some(ref cache) = opts.cache { 84 | let provider = CachedProvider::new(Provider::try_from(opts.url.as_str())?, cache); 85 | run(provider, opts).await 86 | } else { 87 | let provider = Provider::try_from(opts.url.as_str())?; 88 | run(provider, opts).await 89 | } 90 | } 91 | 92 | async fn run(provider: M, opts: Opts) -> anyhow::Result<()> { 93 | let provider = Arc::new(provider); 94 | // Instantiate the thing which will query historical prices 95 | let prices = HistoricalPrice::new(provider.clone()); 96 | 97 | let compound = Compound::create(provider.clone()).await?; 98 | let curve = Curve::create(provider.clone()).await?; 99 | let inspectors: Vec> = vec![ 100 | // Classify Transfers 101 | Box::new(ZeroEx::new()), 102 | Box::new(ERC20::new()), 103 | // Classify AMMs 104 | Box::new(Balancer::new()), 105 | Box::new(Uniswap::new()), 106 | Box::new(curve), 107 | // Classify Liquidations 108 | Box::new(Aave::new()), 109 | Box::new(compound), 110 | ]; 111 | 112 | let reducers: Vec> = vec![ 113 | Box::new(LiquidationReducer::new()), 114 | Box::new(TradeReducer::new()), 115 | Box::new(ArbitrageReducer::new()), 116 | ]; 117 | let processor = BatchInspector::new(inspectors, reducers); 118 | 119 | // TODO: Pass overwrite parameter 120 | let mut db = MevDB::connect(opts.db_cfg, &opts.db_table).await?; 121 | db.create().await?; 122 | if opts.reset { 123 | db.clear().await?; 124 | db.create().await?; 125 | } 126 | log::debug!("created mevdb table"); 127 | 128 | if let Some(cmd) = opts.cmd { 129 | match cmd { 130 | Command::Tx(opts) => { 131 | let traces = provider.trace_transaction(opts.tx).await?; 132 | if let Some(inspection) = processor.inspect_one(traces) { 133 | let gas_used = provider 134 | .get_transaction_receipt(inspection.hash) 135 | .await? 136 | .expect("tx not found") 137 | .gas_used 138 | .unwrap_or_default(); 139 | 140 | let gas_price = provider 141 | .get_transaction(inspection.hash) 142 | .await? 143 | .expect("tx not found") 144 | .gas_price; 145 | 146 | let evaluation = 147 | Evaluation::new(inspection, &prices, gas_used, gas_price).await?; 148 | println!("Found: {:?}", evaluation.as_ref().hash); 149 | println!("Revenue: {:?} WEI", evaluation.profit); 150 | println!("Cost: {:?} WEI", evaluation.gas_used * evaluation.gas_price); 151 | println!("Actions: {:?}", evaluation.actions); 152 | println!("Protocols: {:?}", evaluation.inspection.protocols); 153 | println!("Status: {:?}", evaluation.inspection.status); 154 | db.insert(&evaluation).await?; 155 | } else { 156 | eprintln!("No actions found for tx {:?}", opts.tx); 157 | } 158 | } 159 | Command::Blocks(inner) => { 160 | log::debug!("command blocks {:?}", inner); 161 | let provider = Arc::new(provider); 162 | let processor = Arc::new(processor); 163 | let prices = Arc::new(prices); 164 | 165 | let (tx, rx) = futures::channel::mpsc::unbounded(); 166 | 167 | // divide the bloccs to process equally onto all the tasks 168 | assert!(inner.from < inner.to); 169 | let mut num_tasks = inner.tasks as usize; 170 | let block_num = inner.to - inner.from; 171 | let blocks_per_task = block_num.max(inner.tasks) / inner.tasks; 172 | let rem = block_num % inner.tasks; 173 | 174 | if rem > 0 { 175 | let rem_start = inner.to - rem - blocks_per_task; 176 | let processor = Arc::clone(&processor); 177 | let eval_stream = processor.evaluate_blocks( 178 | Arc::clone(&provider), 179 | Arc::clone(&prices), 180 | rem_start..inner.to, 181 | inner.max_requests, 182 | ); 183 | let mut tx = tx.clone(); 184 | log::debug!("spawning batch for blocks: [{}..{})", rem_start, inner.to); 185 | tokio::task::spawn(async move { 186 | // wrap in an ok because send_all only sends Result::Ok 187 | let mut iter = eval_stream.map(Ok); 188 | let _ = tx.send_all(&mut iter).await; 189 | }); 190 | 191 | num_tasks -= 1; 192 | }; 193 | 194 | for from in (inner.from..inner.to) 195 | .into_iter() 196 | .step_by(blocks_per_task as usize) 197 | .take(num_tasks as usize) 198 | { 199 | let processor = Arc::clone(&processor); 200 | let eval_stream = processor.evaluate_blocks( 201 | Arc::clone(&provider), 202 | Arc::clone(&prices), 203 | from..from + blocks_per_task, 204 | inner.max_requests, 205 | ); 206 | let mut tx = tx.clone(); 207 | log::debug!( 208 | "spawning batch for blocks: [{}..{})", 209 | from, 210 | from + blocks_per_task 211 | ); 212 | tokio::task::spawn(async move { 213 | // wrap in an ok because send_all only sends Result::Ok 214 | let mut iter = eval_stream.map(Ok); 215 | let _ = tx.send_all(&mut iter).await; 216 | }); 217 | } 218 | // drop the sender so that the channel gets closed 219 | drop(tx); 220 | 221 | // all the evaluations arrive at the receiver and are inserted into the DB 222 | let mut inserts = BatchInserts::new(db, rx); 223 | let mut insert_ctn = 0usize; 224 | let mut error_ctn = 0usize; 225 | while let Some(res) = inserts.next().await { 226 | match res { 227 | Ok(eval) => { 228 | insert_ctn += 1; 229 | log::info!( 230 | "Inserted tx 0x{} in block {}", 231 | eval.inspection.hash, 232 | eval.inspection.block_number, 233 | ); 234 | } 235 | Err(err) => { 236 | error_ctn += 1; 237 | log::error!("failed to insert: {:?}", err) 238 | } 239 | } 240 | } 241 | println!( 242 | "inserted evaluations: {}, errors: {}, block range [{}..{}) using {} tasks", 243 | insert_ctn, error_ctn, inner.from, inner.to, inner.tasks 244 | ); 245 | } 246 | }; 247 | } else { 248 | let mut watcher = provider.watch_blocks().await?; 249 | while watcher.next().await.is_some() { 250 | let block = provider.get_block_number().await?; 251 | let stdout = std::io::stdout(); 252 | let mut lock = stdout.lock(); 253 | writeln!(lock, "Got block: {}", block.as_u64())?; 254 | process_block( 255 | &mut lock, 256 | block.as_u64(), 257 | &provider, 258 | &processor, 259 | &mut db, 260 | &prices, 261 | ) 262 | .await?; 263 | } 264 | } 265 | 266 | Ok(()) 267 | } 268 | 269 | async fn process_block( 270 | lock: &mut std::io::StdoutLock<'_>, 271 | block_number: u64, 272 | provider: &M, 273 | processor: &BatchInspector, 274 | db: &mut MevDB, 275 | prices: &HistoricalPrice, 276 | ) -> anyhow::Result<()> { 277 | let block_number = block_number.into(); 278 | 279 | // get all the traces 280 | let traces = provider 281 | .trace_block(BlockNumber::Number(block_number)) 282 | .await?; 283 | // get all the block txs 284 | let block = provider 285 | .get_block_with_txs(block_number) 286 | .await? 287 | .expect("block should exist"); 288 | let gas_price_txs = block 289 | .transactions 290 | .iter() 291 | .map(|tx| (tx.hash, tx.gas_price)) 292 | .collect::>(); 293 | 294 | // get all the receipts 295 | let receipts = provider.parity_block_receipts(block_number).await?; 296 | let gas_used_txs = receipts 297 | .into_iter() 298 | .map(|receipt| { 299 | ( 300 | receipt.transaction_hash, 301 | receipt.gas_used.unwrap_or_default(), 302 | ) 303 | }) 304 | .collect::>(); 305 | 306 | let inspections = processor.inspect_many(traces); 307 | 308 | let t1 = std::time::Instant::now(); 309 | 310 | let eval_futs = inspections.into_iter().map(|inspection| { 311 | let gas_used = gas_used_txs 312 | .get(&inspection.hash) 313 | .cloned() 314 | .unwrap_or_default(); 315 | let gas_price = gas_price_txs 316 | .get(&inspection.hash) 317 | .cloned() 318 | .unwrap_or_default(); 319 | Evaluation::new(inspection, &prices, gas_used, gas_price) 320 | }); 321 | for evaluation in futures::future::join_all(eval_futs).await { 322 | if let Ok(evaluation) = evaluation { 323 | db.insert(&evaluation).await?; 324 | } 325 | } 326 | 327 | writeln!( 328 | lock, 329 | "Processed {:?} in {:?}", 330 | block_number, 331 | std::time::Instant::now().duration_since(t1) 332 | )?; 333 | Ok(()) 334 | } 335 | -------------------------------------------------------------------------------- /src/prices.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::clippy::too_many_arguments)] 2 | use crate::addresses::{parse_address, ETH, WETH}; 3 | use ethers::{ 4 | contract::{abigen, ContractError}, 5 | providers::Middleware, 6 | types::{Address, BlockNumber, U256}, 7 | utils::WEI_IN_ETHER, 8 | }; 9 | use once_cell::sync::Lazy; 10 | use std::{collections::HashMap, sync::Arc}; 11 | 12 | // Generate type-safe bindings to Uniswap's router 13 | abigen!(Uniswap, "abi/unirouterv2.json"); 14 | 15 | /// Gets historical prices in ETH for any token via Uniswap. 16 | /// **Requires an archive node to work** 17 | pub struct HistoricalPrice { 18 | uniswap: Uniswap, 19 | } 20 | 21 | static DECIMALS: Lazy> = Lazy::new(|| { 22 | [ 23 | ("a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", 6), 24 | ("0xdac17f958d2ee523a2206206994597c13d831ec7", 6), 25 | ] 26 | .iter() 27 | .map(|(addr, decimals)| (parse_address(addr), *decimals)) 28 | .collect::>() 29 | }); 30 | 31 | impl HistoricalPrice { 32 | /// Instantiates a Unirouter 33 | pub fn new>>(provider: T) -> Self { 34 | let unirouter: Address = "7a250d5630b4cf539739df2c5dacb4c659f2488d" 35 | .parse() 36 | .expect("cannot parse unirouter"); 37 | Self { 38 | uniswap: Uniswap::new(unirouter, provider.into()), 39 | } 40 | } 41 | 42 | /// Converts any token amount to ETH by querying historical Uniswap prices 43 | /// at a specific block 44 | pub async fn quote, A: Into>( 45 | &self, 46 | token: Address, 47 | amount: A, 48 | block: T, 49 | ) -> Result> { 50 | let amount = amount.into(); 51 | 52 | // assume price parity of WETH / ETH 53 | if token == *ETH || token == *WETH { 54 | return Ok(amount); 55 | } 56 | 57 | // get a marginal price for a 1 ETH buy order 58 | let one = DECIMALS 59 | .get(&token) 60 | .map(|decimals| U256::from(10u64.pow(*decimals as u32))) 61 | .unwrap_or(WEI_IN_ETHER); 62 | 63 | // ask uniswap how much we'd get from the TOKEN -> WETH path 64 | let amounts = self 65 | .uniswap 66 | .get_amounts_out(one, vec![token, *WETH]) 67 | .block(block) 68 | .call() 69 | .await?; 70 | 71 | debug_assert_eq!(one, amounts[0]); 72 | debug_assert_eq!(amounts.len(), 2); 73 | Ok(amounts[1] / one * amount) 74 | } 75 | } 76 | 77 | #[cfg(test)] 78 | mod tests { 79 | use super::*; 80 | use ethers::{ 81 | providers::{Http, Provider}, 82 | utils::WEI_IN_ETHER as WEI, 83 | }; 84 | use std::convert::TryFrom; 85 | 86 | fn to_eth(amt: U256) -> U256 { 87 | ethers::utils::WEI_IN_ETHER / amt 88 | } 89 | 90 | static PROVIDER: Lazy> = Lazy::new(|| { 91 | let url: String = std::env::var("ARCHIVE").expect("Archive node URL should be set"); 92 | let provider = Provider::::try_from(url).unwrap(); 93 | provider 94 | }); 95 | 96 | #[tokio::test] 97 | #[ignore] // This test can only run against an archive node 98 | async fn check_historical_price() { 99 | let prices = HistoricalPrice::new(PROVIDER.clone()); 100 | let one = U256::from(1e6 as u64); 101 | 102 | for (token, amount, block, expected) in [ 103 | ( 104 | "a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", 105 | one, 106 | 11248959u64, 107 | 465u64, 108 | ), 109 | ( 110 | "a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", 111 | one, 112 | 10532013, 113 | 302, 114 | ), 115 | ( 116 | "e41d2489571d322189246dafa5ebde1f4699f498", 117 | WEI, 118 | 11248959, 119 | 1277, 120 | ), 121 | ] 122 | .iter() 123 | { 124 | let amount = prices 125 | .quote(parse_address(token), *amount, *block) 126 | .await 127 | .unwrap(); 128 | assert_eq!(to_eth(amount), (*expected).into()); 129 | } 130 | } 131 | 132 | #[tokio::test] 133 | #[ignore] // This test can only run against an archive node 134 | async fn old_block_fail() { 135 | let prices = HistoricalPrice::new(PROVIDER.clone()); 136 | prices 137 | .quote( 138 | parse_address("e41d2489571d322189246dafa5ebde1f4699f498"), 139 | WEI, 140 | 9082920, 141 | ) 142 | .await 143 | .unwrap_err(); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/reducers/arbitrage.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | inspectors::find_matching, 3 | types::{ 4 | actions::{Arbitrage, SpecificAction}, 5 | Classification, Inspection, 6 | }, 7 | Reducer, 8 | }; 9 | 10 | #[derive(Clone, Debug)] 11 | pub struct ArbitrageReducer; 12 | 13 | impl ArbitrageReducer { 14 | pub fn new() -> Self { 15 | Self 16 | } 17 | } 18 | 19 | impl Reducer for ArbitrageReducer { 20 | fn reduce(&self, inspection: &mut Inspection) { 21 | let actions = inspection.actions.to_vec(); 22 | let mut prune = Vec::new(); 23 | inspection 24 | .actions 25 | .iter_mut() 26 | .enumerate() 27 | .for_each(|(i, action)| { 28 | // check if we got a trade 29 | let trade = if let Some(trade) = action.as_action().map(|x| x.trade()).flatten() { 30 | trade 31 | } else { 32 | return; 33 | }; 34 | 35 | let res = find_matching( 36 | actions.iter().enumerate().skip(i + 1), 37 | |t| t.trade(), 38 | |t| t.t2.token == trade.t1.token, 39 | true, 40 | ); 41 | if let Some((j, trade2)) = res { 42 | if trade2.t2.amount > trade.t1.amount { 43 | *action = Classification::new( 44 | Arbitrage { 45 | profit: trade2.t2.amount.saturating_sub(trade.t1.amount), 46 | token: trade2.t2.token, 47 | to: trade2.t2.to, 48 | }, 49 | // TODO! 50 | Vec::new(), 51 | ); 52 | // prune everything in that range 53 | prune.push((i + 1, j + 1)); 54 | } 55 | } 56 | }); 57 | 58 | for range in prune { 59 | inspection.actions[range.0..range.1] 60 | .iter_mut() 61 | .for_each(|a| match a { 62 | // Of the known actions, prune only the trades/transfers 63 | Classification::Known(c) => match c.action { 64 | SpecificAction::Arbitrage(_) 65 | | SpecificAction::Trade(_) 66 | | SpecificAction::Transfer(_) => { 67 | *a = Classification::Prune; 68 | } 69 | _ => {} 70 | }, 71 | _ => *a = Classification::Prune, 72 | }) 73 | } 74 | } 75 | } 76 | 77 | #[cfg(test)] 78 | mod tests { 79 | use super::*; 80 | use crate::test_helpers::*; 81 | use crate::types::actions::{Arbitrage, Trade, Transfer}; 82 | 83 | fn test_trade_to_arbitrage(input: Vec, expected: Vec) { 84 | let uniswap = ArbitrageReducer::new(); 85 | let mut inspection = mk_inspection(input); 86 | uniswap.reduce(&mut inspection); 87 | assert_eq!(inspection.actions, expected); 88 | } 89 | 90 | #[test] 91 | fn simple_arb() { 92 | let addrs = addrs(); 93 | let token1 = addrs[0]; 94 | let token2 = addrs[1]; 95 | 96 | let usr = addrs[4]; 97 | let uni1 = addrs[5]; 98 | let uni2 = addrs[6]; 99 | 100 | let t1 = Trade::new( 101 | Transfer { 102 | from: usr, 103 | to: uni1, 104 | amount: 100.into(), 105 | token: token1, 106 | }, 107 | Transfer { 108 | from: uni1, 109 | to: usr, 110 | amount: 200.into(), 111 | token: token2, 112 | }, 113 | ); 114 | 115 | let t2 = Trade::new( 116 | Transfer { 117 | from: usr, 118 | to: uni2, 119 | amount: 200.into(), 120 | token: token2, 121 | }, 122 | Transfer { 123 | from: uni2, 124 | to: usr, 125 | amount: 110.into(), 126 | token: token1, 127 | }, 128 | ); 129 | 130 | // the 2 trades get condensed down to 1 arb 131 | let input = vec![ 132 | Classification::new(t1.clone(), Vec::new()), 133 | Classification::new(t2.clone(), Vec::new()), 134 | ]; 135 | let expected = vec![ 136 | Classification::new( 137 | Arbitrage { 138 | profit: 10.into(), 139 | token: token1, 140 | to: usr, 141 | }, 142 | Vec::new(), 143 | ), 144 | Classification::Prune, 145 | ]; 146 | 147 | test_trade_to_arbitrage(input, expected); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/reducers/liquidation.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | addresses::{ETH, WETH}, 3 | inspectors::find_matching, 4 | types::{ 5 | actions::{ProfitableLiquidation, Transfer}, 6 | Classification, Inspection, 7 | }, 8 | Reducer, 9 | }; 10 | 11 | pub struct LiquidationReducer; 12 | 13 | impl LiquidationReducer { 14 | pub fn new() -> Self { 15 | Self 16 | } 17 | } 18 | 19 | impl Reducer for LiquidationReducer { 20 | fn reduce(&self, inspection: &mut Inspection) { 21 | let actions = inspection.actions.clone(); 22 | let mut prune = Vec::new(); 23 | 24 | // 1. find all the liquidations and populate their received amount with 25 | // the transfer that was their subtrace 26 | // 2. find the tx right before the liquidation which has a matching token 27 | // as the received token and use that to calculate the profit 28 | inspection 29 | .actions 30 | .iter_mut() 31 | .enumerate() 32 | .for_each(|(i, ref mut action)| { 33 | let opt = action 34 | .as_action_mut() 35 | .map(|x| x.liquidation_mut()) 36 | .flatten(); 37 | let liquidation = if let Some(liquidation) = opt { 38 | liquidation 39 | } else { 40 | return; 41 | }; 42 | 43 | // find the transfer which corresponds to this liquidation 44 | let mut liq = liquidation.clone(); 45 | let check_fn = |t: &Transfer| { 46 | t.to == liq.from && (t.token == liq.received_token || t.token == *ETH) 47 | }; 48 | 49 | // found the transfer after, which is the one that pays us 50 | let res = find_matching( 51 | actions.iter().enumerate().skip(i + 1), 52 | |t| t.transfer(), 53 | check_fn, 54 | true, 55 | ); 56 | if let Some((idx, received)) = res { 57 | // prune the repayment subcall 58 | prune.push(idx); 59 | 60 | // there may be a DEX trade before the liquidation, allowing 61 | // us to instantly determine if it's a profitable liquidation 62 | // or not 63 | let res = find_matching( 64 | actions.iter().enumerate(), 65 | |t| t.trade(), 66 | |t| t.t2.token == liq.sent_token, 67 | true, 68 | ); 69 | 70 | if let Some((_, paid)) = res { 71 | // prune.push(idx2); 72 | let tokens_match = (received.token == paid.t1.token) 73 | || ((received.token == *ETH && paid.t1.token == *WETH) 74 | || (received.token == *WETH && paid.t1.token == *ETH)); 75 | if received.amount > paid.t1.amount && tokens_match { 76 | liq.received_amount = received.amount; 77 | let profitable_liq = ProfitableLiquidation { 78 | token: paid.t1.token, 79 | liquidation: liq.clone(), 80 | profit: received.amount - paid.t1.amount, 81 | }; 82 | **action = Classification::new(profitable_liq, Vec::new()); 83 | return; 84 | } 85 | } 86 | 87 | liquidation.received_amount = received.amount; 88 | } 89 | }); 90 | for i in prune { 91 | inspection.actions[i] = Classification::Prune; 92 | } 93 | } 94 | } 95 | 96 | #[cfg(test)] 97 | mod tests { 98 | use super::*; 99 | use crate::test_helpers::*; 100 | use crate::types::actions::{Liquidation, Trade, Transfer}; 101 | 102 | fn test_profitable_liquidation(input: Vec, expected: Vec) { 103 | let aave = LiquidationReducer::new(); 104 | let mut inspection = mk_inspection(input); 105 | aave.reduce(&mut inspection); 106 | assert_eq!(inspection.actions, expected); 107 | } 108 | 109 | #[test] 110 | fn to_profitable_liquidation() { 111 | let addrs = addrs(); 112 | 113 | let token = addrs[0]; 114 | let token1 = addrs[1]; 115 | let token2 = addrs[6]; 116 | let usr = addrs[2]; 117 | let liquidated = addrs[3]; 118 | let vault = addrs[4]; 119 | let dex = addrs[5]; 120 | 121 | // trade USDC for ETH 122 | let trade1 = Trade { 123 | t1: Transfer { 124 | token, 125 | from: usr, 126 | amount: 100.into(), 127 | to: dex, 128 | }, 129 | t2: Transfer { 130 | token: token1, 131 | from: dex, 132 | amount: 1.into(), 133 | to: dex, 134 | }, 135 | }; 136 | 137 | // trade ETH for YFI 138 | let trade2 = Trade { 139 | t1: Transfer { 140 | token: token1, 141 | from: dex, 142 | amount: 1.into(), 143 | to: dex, 144 | }, 145 | t2: Transfer { 146 | token: token2, 147 | from: dex, 148 | amount: 5.into(), 149 | to: dex, 150 | }, 151 | }; 152 | 153 | // sends YFI 154 | let repayment = Transfer { 155 | token: token2, 156 | from: dex, 157 | amount: 5.into(), 158 | to: vault, // goes to the user's underwater vault 159 | }; 160 | 161 | // repays YFI, gets ETH collateral 162 | let mut liquidation = Liquidation { 163 | sent_token: token2, 164 | sent_amount: 5.into(), 165 | 166 | received_token: token1, 167 | // This is 0 and will get populated via the transfer subtrace 168 | received_amount: 0.into(), 169 | 170 | from: usr, 171 | liquidated_user: liquidated, 172 | }; 173 | 174 | // gets paid out in ETH 175 | let payout = Transfer { 176 | token: token1, 177 | to: usr, 178 | from: vault, 179 | amount: 3.into(), 180 | }; 181 | 182 | liquidation.received_amount = payout.amount; 183 | let res = ProfitableLiquidation { 184 | liquidation: liquidation.clone(), 185 | profit: 2.into(), 186 | token: token1, 187 | }; 188 | 189 | // we expect that we are left with a ProfitableLiquidation. 190 | // the transfer _after_ is culled 191 | let input = vec![ 192 | Classification::new(trade1.clone(), Vec::new()), 193 | Classification::new(trade2.clone(), Vec::new()), 194 | Classification::new(repayment.clone(), Vec::new()), 195 | // the payout tx is a subtrace of the liquidation tx 196 | Classification::new(liquidation, vec![0, 5]), 197 | Classification::new(payout, vec![0, 5, 3]), 198 | ]; 199 | let expected = vec![ 200 | Classification::new(trade1, Vec::new()), 201 | Classification::new(trade2, Vec::new()), 202 | Classification::new(repayment, Vec::new()), 203 | Classification::new(res, Vec::new()), 204 | Classification::Prune, 205 | ]; 206 | 207 | test_profitable_liquidation(input, expected); 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /src/reducers/mod.rs: -------------------------------------------------------------------------------- 1 | mod trade; 2 | pub use trade::TradeReducer; 3 | 4 | mod arbitrage; 5 | pub use arbitrage::ArbitrageReducer; 6 | 7 | mod liquidation; 8 | pub use liquidation::LiquidationReducer; 9 | -------------------------------------------------------------------------------- /src/reducers/trade.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | inspectors::find_matching, 3 | types::{actions::Trade, Classification, Inspection}, 4 | Reducer, 5 | }; 6 | 7 | pub struct TradeReducer; 8 | 9 | impl TradeReducer { 10 | /// Instantiates the reducer 11 | pub fn new() -> Self { 12 | Self 13 | } 14 | } 15 | 16 | impl Reducer for TradeReducer { 17 | fn reduce(&self, inspection: &mut Inspection) { 18 | let actions = inspection.actions.to_vec(); 19 | let mut prune = Vec::new(); 20 | inspection 21 | .actions 22 | .iter_mut() 23 | .enumerate() 24 | .for_each(|(i, action)| { 25 | // check if we got a transfer 26 | let transfer = 27 | if let Some(transfer) = action.as_action().map(|x| x.transfer()).flatten() { 28 | transfer 29 | } else { 30 | return; 31 | }; 32 | 33 | // find the first transfer after it 34 | let res = find_matching( 35 | actions.iter().enumerate().skip(i + 1), 36 | |t| t.transfer(), 37 | |t| t.to == transfer.from && t.from == transfer.to && t.token != transfer.token, 38 | true, 39 | ); 40 | 41 | if let Some((j, transfer2)) = res { 42 | // only match transfers which were on the same rank of the trace 43 | // trades across multiple trace levels are handled by their individual 44 | // inspectors 45 | if actions[i].trace_address().len() != actions[j].trace_address().len() { 46 | return; 47 | } 48 | 49 | *action = Classification::new( 50 | Trade { 51 | t1: transfer.clone(), 52 | t2: transfer2.clone(), 53 | }, 54 | actions[i].trace_address(), 55 | ); 56 | 57 | // If there is no follow-up transfer that uses `transfer2`, prune it: 58 | let res = find_matching( 59 | actions.iter().enumerate().skip(j + 1), 60 | |t| t.transfer(), 61 | |t| t.to == transfer2.from && t.from == transfer2.to, 62 | false, 63 | ); 64 | if res.is_none() { 65 | prune.push(j); 66 | } 67 | } 68 | }); 69 | 70 | prune 71 | .iter() 72 | .for_each(|p| inspection.actions[*p] = Classification::Prune); 73 | } 74 | } 75 | 76 | #[cfg(test)] 77 | mod tests { 78 | use super::*; 79 | use crate::test_helpers::*; 80 | use crate::types::actions::Transfer; 81 | 82 | fn test_transfer_to_trade(input: Vec, expected: Vec) { 83 | let uniswap = TradeReducer::new(); 84 | let mut inspection = mk_inspection(input); 85 | uniswap.reduce(&mut inspection); 86 | assert_eq!(inspection.actions, expected); 87 | } 88 | 89 | #[test] 90 | fn two_continuous_transfers_ok() { 91 | let addrs = addrs(); 92 | let token1 = addrs[0]; 93 | let token2 = addrs[1]; 94 | let addr1 = addrs[2]; 95 | let addr2 = addrs[3]; 96 | 97 | let t1 = Transfer { 98 | from: addr1, 99 | to: addr2, 100 | amount: 1.into(), 101 | token: token1, 102 | }; 103 | 104 | let t2 = Transfer { 105 | from: addr2, 106 | to: addr1, 107 | amount: 5.into(), 108 | token: token2, 109 | }; 110 | 111 | let input = vec![ 112 | Classification::new(t1.clone(), Vec::new()), 113 | Classification::new(t2.clone(), Vec::new()), 114 | ]; 115 | let expected = vec![ 116 | Classification::new(Trade { t1, t2 }, Vec::new()), 117 | Classification::Prune, 118 | ]; 119 | 120 | test_transfer_to_trade(input, expected); 121 | } 122 | 123 | #[test] 124 | fn non_continuous_transfers_ok() { 125 | let addrs = addrs(); 126 | let token1 = addrs[0]; 127 | let token2 = addrs[1]; 128 | let addr1 = addrs[2]; 129 | let addr2 = addrs[3]; 130 | 131 | let t1 = Transfer { 132 | from: addr1, 133 | to: addr2, 134 | amount: 1.into(), 135 | token: token1, 136 | }; 137 | 138 | let t2 = Transfer { 139 | from: addr2, 140 | to: addr1, 141 | amount: 5.into(), 142 | token: token2, 143 | }; 144 | 145 | // There's some junk between our two transfers 146 | let input = vec![ 147 | Classification::new(t1.clone(), Vec::new()), 148 | Classification::Prune, 149 | Classification::new(t2.clone(), Vec::new()), 150 | ]; 151 | // but it still understand that it's a trade 152 | let expected = vec![ 153 | Classification::new(Trade { t1, t2 }, Vec::new()), 154 | Classification::Prune, 155 | Classification::Prune, 156 | ]; 157 | 158 | test_transfer_to_trade(input, expected); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/test_helpers/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::types::{inspection::TraceWrapper, Classification, Inspection, Status}; 2 | use ethers::types::{Address, Trace, TxHash}; 3 | use once_cell::sync::Lazy; 4 | use std::{collections::HashSet, convert::TryInto}; 5 | 6 | pub const TRACE: &str = include_str!("../../res/11017338.trace.json"); 7 | pub static TRACES: Lazy> = Lazy::new(|| serde_json::from_str(TRACE).unwrap()); 8 | 9 | pub fn addrs() -> Vec
{ 10 | use ethers::core::rand::thread_rng; 11 | (0..10) 12 | .into_iter() 13 | .map(|_| ethers::signers::LocalWallet::new(&mut thread_rng()).address()) 14 | .collect() 15 | } 16 | 17 | pub fn mk_inspection(actions: Vec) -> Inspection { 18 | Inspection { 19 | status: Status::Success, 20 | actions, 21 | protocols: HashSet::new(), 22 | from: Address::zero(), 23 | contract: Address::zero(), 24 | proxy_impl: None, 25 | hash: TxHash::zero(), 26 | block_number: 0, 27 | } 28 | } 29 | 30 | pub fn read_trace(path: &str) -> Inspection { 31 | let input = std::fs::read_to_string(format!("res/{}", path)).unwrap(); 32 | let traces: Vec = serde_json::from_str(&input).unwrap(); 33 | TraceWrapper(traces).try_into().unwrap() 34 | } 35 | 36 | pub fn get_trace(hash: &str) -> Inspection { 37 | let hash = if hash.starts_with("0x") { 38 | &hash[2..] 39 | } else { 40 | hash 41 | }; 42 | 43 | TraceWrapper( 44 | TRACES 45 | .iter() 46 | .filter(|t| t.transaction_hash == Some(hash.parse::().unwrap())) 47 | .cloned() 48 | .collect::>(), 49 | ) 50 | .try_into() 51 | .unwrap() 52 | } 53 | 54 | #[macro_export] 55 | macro_rules! set { 56 | ( $( $x:expr ),* ) => { // Match zero or more comma delimited items 57 | { 58 | let mut temp_set = std::collections::HashSet::new(); // Create a mutable HashSet 59 | $( 60 | temp_set.insert($x); // Insert each item matched into the HashSet 61 | )* 62 | temp_set // Return the populated HashSet 63 | } 64 | }; 65 | } 66 | -------------------------------------------------------------------------------- /src/traits.rs: -------------------------------------------------------------------------------- 1 | use crate::types::Inspection; 2 | 3 | pub trait Reducer { 4 | /// By default the reducer is empty. A consumer may optionally 5 | /// implement this method to perform additional actions on the classified & 6 | /// filtered results. 7 | fn reduce(&self, _: &mut Inspection); 8 | } 9 | 10 | /// Trait for defining an inspector for a specific DeFi protocol 11 | pub trait Inspector: core::fmt::Debug { 12 | /// Classifies an inspection's actions 13 | fn inspect(&self, inspection: &mut Inspection); 14 | } 15 | -------------------------------------------------------------------------------- /src/types/actions.rs: -------------------------------------------------------------------------------- 1 | use crate::addresses::lookup; 2 | 3 | use ethers::types::{Address, Bytes, U256}; 4 | 5 | use std::fmt; 6 | 7 | // https://github.com/flashbots/mev-inspect/blob/master/src/types.ts#L65-L87 8 | #[derive(Debug, Clone, PartialOrd, PartialEq)] 9 | /// The types of actions 10 | pub enum SpecificAction { 11 | WethDeposit(Deposit), 12 | WethWithdrawal(Withdrawal), 13 | 14 | Transfer(Transfer), 15 | Trade(Trade), 16 | Liquidation(Liquidation), 17 | 18 | AddLiquidity(AddLiquidity), 19 | 20 | Arbitrage(Arbitrage), 21 | ProfitableLiquidation(ProfitableLiquidation), 22 | 23 | Unclassified(Bytes), 24 | 25 | LiquidationCheck, 26 | } 27 | 28 | #[derive(Debug, Clone, PartialOrd, PartialEq)] 29 | pub struct AddLiquidity { 30 | pub tokens: Vec
, 31 | pub amounts: Vec, 32 | } 33 | 34 | impl From for SpecificAction { 35 | fn from(src: AddLiquidity) -> Self { 36 | SpecificAction::AddLiquidity(src) 37 | } 38 | } 39 | 40 | impl SpecificAction { 41 | pub fn deposit(&self) -> Option<&Deposit> { 42 | match self { 43 | SpecificAction::WethDeposit(inner) => Some(inner), 44 | _ => None, 45 | } 46 | } 47 | 48 | pub fn withdrawal(&self) -> Option<&Withdrawal> { 49 | match self { 50 | SpecificAction::WethWithdrawal(inner) => Some(inner), 51 | _ => None, 52 | } 53 | } 54 | 55 | pub fn transfer(&self) -> Option<&Transfer> { 56 | match self { 57 | SpecificAction::Transfer(inner) => Some(inner), 58 | _ => None, 59 | } 60 | } 61 | 62 | pub fn trade(&self) -> Option<&Trade> { 63 | match self { 64 | SpecificAction::Trade(inner) => Some(inner), 65 | _ => None, 66 | } 67 | } 68 | 69 | pub fn arbitrage(&self) -> Option<&Arbitrage> { 70 | match self { 71 | SpecificAction::Arbitrage(inner) => Some(inner), 72 | _ => None, 73 | } 74 | } 75 | 76 | pub fn liquidation(&self) -> Option<&Liquidation> { 77 | match self { 78 | SpecificAction::Liquidation(inner) => Some(inner), 79 | _ => None, 80 | } 81 | } 82 | 83 | // TODO: Can we convert these to AsRef / AsMut Options somehow? 84 | pub fn liquidation_mut(&mut self) -> Option<&mut Liquidation> { 85 | match self { 86 | SpecificAction::Liquidation(inner) => Some(inner), 87 | _ => None, 88 | } 89 | } 90 | 91 | pub fn profitable_liquidation(&self) -> Option<&ProfitableLiquidation> { 92 | match self { 93 | SpecificAction::ProfitableLiquidation(inner) => Some(inner), 94 | _ => None, 95 | } 96 | } 97 | 98 | pub fn add_liquidity(&self) -> Option<&AddLiquidity> { 99 | match self { 100 | SpecificAction::AddLiquidity(inner) => Some(inner), 101 | _ => None, 102 | } 103 | } 104 | } 105 | 106 | #[derive(Clone, PartialOrd, PartialEq)] 107 | /// A token transfer 108 | pub struct Transfer { 109 | pub from: Address, 110 | pub to: Address, 111 | pub amount: U256, 112 | pub token: Address, 113 | } 114 | 115 | impl From for SpecificAction { 116 | fn from(src: Transfer) -> Self { 117 | SpecificAction::Transfer(src) 118 | } 119 | } 120 | 121 | // Manually implemented Debug (and Display?) for datatypes so that we 122 | // can get their token names instead of using addresses. TODO: Could we 123 | // also normalize the decimals? What about tokens with non-18 decimals e.g. Tether? 124 | impl fmt::Debug for Transfer { 125 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 126 | f.debug_struct("Transfer") 127 | .field("from", &lookup(self.from)) 128 | .field("to", &lookup(self.to)) 129 | .field("amount", &self.amount) 130 | .field("token", &lookup(self.token)) 131 | .finish() 132 | } 133 | } 134 | 135 | #[derive(Clone, PartialOrd, PartialEq)] 136 | pub struct Deposit { 137 | pub from: Address, 138 | pub amount: U256, 139 | } 140 | 141 | impl From for SpecificAction { 142 | fn from(src: Deposit) -> Self { 143 | SpecificAction::WethDeposit(src) 144 | } 145 | } 146 | 147 | impl fmt::Debug for Deposit { 148 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 149 | f.debug_struct("Deposit") 150 | .field("from", &lookup(self.from)) 151 | .field("amount", &self.amount) 152 | .finish() 153 | } 154 | } 155 | 156 | #[derive(Clone, PartialOrd, PartialEq)] 157 | pub struct Withdrawal { 158 | pub to: Address, 159 | pub amount: U256, 160 | } 161 | 162 | impl From for SpecificAction { 163 | fn from(src: Withdrawal) -> Self { 164 | SpecificAction::WethWithdrawal(src) 165 | } 166 | } 167 | 168 | impl fmt::Debug for Withdrawal { 169 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 170 | f.debug_struct("Withdrawal") 171 | .field("to", &lookup(self.to)) 172 | .field("amount", &self.amount) 173 | .finish() 174 | } 175 | } 176 | 177 | #[derive(Debug, Clone, PartialOrd, PartialEq)] 178 | pub struct Trade { 179 | pub t1: Transfer, 180 | pub t2: Transfer, 181 | } 182 | 183 | impl From for SpecificAction { 184 | fn from(src: Trade) -> Self { 185 | SpecificAction::Trade(src) 186 | } 187 | } 188 | 189 | impl Trade { 190 | /// Creates a new trade made up of 2 matching transfers 191 | pub fn new(t1: Transfer, t2: Transfer) -> Self { 192 | assert!( 193 | t1.from == t2.to && t2.from == t1.to, 194 | "Found mismatched trade" 195 | ); 196 | Self { t1, t2 } 197 | } 198 | } 199 | 200 | #[derive(Clone, PartialOrd, PartialEq)] 201 | pub struct Arbitrage { 202 | pub profit: U256, 203 | pub token: Address, 204 | pub to: Address, 205 | } 206 | 207 | impl From for SpecificAction { 208 | fn from(src: Arbitrage) -> Self { 209 | SpecificAction::Arbitrage(src) 210 | } 211 | } 212 | 213 | impl fmt::Debug for Arbitrage { 214 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 215 | f.debug_struct("Arbitrage") 216 | .field("profit", &self.profit) 217 | .field("to", &lookup(self.to)) 218 | .field("token", &lookup(self.token)) 219 | .finish() 220 | } 221 | } 222 | 223 | #[derive(Default, Clone, PartialOrd, PartialEq)] 224 | pub struct Liquidation { 225 | pub sent_token: Address, 226 | pub sent_amount: U256, 227 | 228 | pub received_token: Address, 229 | pub received_amount: U256, 230 | 231 | pub from: Address, 232 | pub liquidated_user: Address, 233 | } 234 | 235 | impl From for SpecificAction { 236 | fn from(src: Liquidation) -> Self { 237 | SpecificAction::Liquidation(src) 238 | } 239 | } 240 | 241 | impl fmt::Debug for Liquidation { 242 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 243 | f.debug_struct("Liquidation") 244 | .field("sent_token", &lookup(self.sent_token)) 245 | .field("sent_amount", &self.sent_amount) 246 | .field("received_token", &lookup(self.received_token)) 247 | .field("received_amount", &self.received_amount) 248 | .field("liquidated_user", &lookup(self.liquidated_user)) 249 | .field("from", &lookup(self.from)) 250 | .finish() 251 | } 252 | } 253 | 254 | #[derive(Clone, PartialOrd, PartialEq)] 255 | pub struct ProfitableLiquidation { 256 | pub liquidation: Liquidation, 257 | pub profit: U256, 258 | pub token: Address, 259 | } 260 | 261 | impl AsRef for ProfitableLiquidation { 262 | fn as_ref(&self) -> &Liquidation { 263 | &self.liquidation 264 | } 265 | } 266 | 267 | impl From for SpecificAction { 268 | fn from(src: ProfitableLiquidation) -> Self { 269 | SpecificAction::ProfitableLiquidation(src) 270 | } 271 | } 272 | 273 | impl fmt::Debug for ProfitableLiquidation { 274 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 275 | f.debug_struct("ProfitableLiquidation") 276 | .field("liquidation", &self.liquidation) 277 | .field("profit", &self.profit) 278 | .field("token", &lookup(self.token)) 279 | .finish() 280 | } 281 | } 282 | -------------------------------------------------------------------------------- /src/types/classification.rs: -------------------------------------------------------------------------------- 1 | use crate::{addresses::lookup, is_subtrace, types::actions::SpecificAction}; 2 | use ethers::types::Call; 3 | use std::fmt; 4 | 5 | #[derive(Clone, PartialEq)] 6 | pub enum Classification { 7 | Known(ActionTrace), 8 | Unknown(CallTrace), 9 | Prune, 10 | } 11 | 12 | #[derive(Debug, Clone, PartialOrd, PartialEq)] 13 | pub struct ActionTrace { 14 | pub action: SpecificAction, 15 | pub trace_address: Vec, 16 | } 17 | 18 | impl AsRef for ActionTrace { 19 | fn as_ref(&self) -> &SpecificAction { 20 | &self.action 21 | } 22 | } 23 | 24 | impl From for Classification { 25 | fn from(action: ActionTrace) -> Self { 26 | Classification::Known(action) 27 | } 28 | } 29 | 30 | #[derive(Debug, Clone, PartialEq)] 31 | pub struct CallTrace { 32 | pub call: Call, 33 | pub trace_address: Vec, 34 | } 35 | 36 | impl AsRef for CallTrace { 37 | fn as_ref(&self) -> &Call { 38 | &self.call 39 | } 40 | } 41 | 42 | impl From for Classification { 43 | fn from(call: CallTrace) -> Self { 44 | Classification::Unknown(call) 45 | } 46 | } 47 | 48 | impl Classification { 49 | pub fn new>(action: T, trace_address: Vec) -> Self { 50 | Classification::Known(ActionTrace { 51 | action: action.into(), 52 | trace_address, 53 | }) 54 | } 55 | 56 | /// Gets the trace address in this call (Empty if Prune) 57 | pub fn trace_address(&self) -> Vec { 58 | match &self { 59 | Classification::Known(inner) => inner.trace_address.clone(), 60 | Classification::Unknown(inner) => inner.trace_address.clone(), 61 | Classification::Prune => vec![], 62 | } 63 | } 64 | 65 | pub fn prune_subcalls(&self, classifications: &mut [Classification]) { 66 | let t1 = self.trace_address(); 67 | 68 | for c in classifications.iter_mut() { 69 | let t2 = c.trace_address(); 70 | if t2 == t1 { 71 | continue; 72 | } 73 | 74 | if is_subtrace(&t1, &t2) { 75 | *c = Classification::Prune; 76 | } 77 | } 78 | } 79 | 80 | pub fn subcalls(&self, classifications: &[Classification]) -> Vec { 81 | let t1 = self.trace_address(); 82 | 83 | let mut v = Vec::new(); 84 | for c in classifications.iter() { 85 | let t2 = c.trace_address(); 86 | 87 | if is_subtrace(&t1, &t2) { 88 | v.push(c.clone()); 89 | } 90 | } 91 | v 92 | } 93 | 94 | pub fn as_action(&self) -> Option<&SpecificAction> { 95 | match self { 96 | Classification::Known(ref inner) => Some(&inner.action), 97 | _ => None, 98 | } 99 | } 100 | 101 | pub fn as_action_mut(&mut self) -> Option<&mut SpecificAction> { 102 | match self { 103 | Classification::Known(ref mut inner) => Some(&mut inner.action), 104 | _ => None, 105 | } 106 | } 107 | 108 | pub fn as_call(&self) -> Option<&CallTrace> { 109 | match self { 110 | Classification::Unknown(ref inner) => Some(&inner), 111 | _ => None, 112 | } 113 | } 114 | } 115 | 116 | impl fmt::Debug for Classification { 117 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 118 | match &self { 119 | Classification::Known(action) => write!(f, "{:#?}", action), 120 | Classification::Unknown(CallTrace { 121 | call, 122 | trace_address, 123 | }) => f 124 | .debug_struct("TraceCall") 125 | .field("from", &lookup(call.from)) 126 | .field("to", &lookup(call.to)) 127 | .field("value", &call.value) 128 | .field("gas", &call.gas) 129 | .field("input", &hex::encode(&call.input)) 130 | .field("call_type", &call.call_type) 131 | .field("trace", trace_address) 132 | .finish(), 133 | Classification::Prune => f.debug_tuple("Pruned").finish(), 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/types/evaluation.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | types::{actions::SpecificAction, Inspection, Status}, 3 | HistoricalPrice, 4 | }; 5 | 6 | use ethers::{ 7 | contract::ContractError, 8 | providers::Middleware, 9 | types::{TxHash, U256}, 10 | }; 11 | use std::collections::HashSet; 12 | 13 | use thiserror::Error; 14 | 15 | #[derive(Debug, Clone, PartialOrd, PartialEq, Eq, Hash)] 16 | pub enum ActionType { 17 | Liquidation, 18 | Arbitrage, 19 | Trade, 20 | } 21 | 22 | #[derive(Clone, Debug)] 23 | pub struct Evaluation { 24 | /// The internal inspection which produced this evaluation 25 | pub inspection: Inspection, 26 | /// The gas used in total by this transaction 27 | pub gas_used: U256, 28 | /// The gas price used in this transaction 29 | pub gas_price: U256, 30 | /// The actions involved 31 | pub actions: HashSet, 32 | /// The money made by this transfer 33 | pub profit: U256, 34 | } 35 | 36 | impl AsRef for Evaluation { 37 | fn as_ref(&self) -> &Inspection { 38 | &self.inspection 39 | } 40 | } 41 | 42 | impl Evaluation { 43 | /// Takes an inspection and reduces it to the data format which will be pushed 44 | /// to the database. 45 | pub async fn new( 46 | inspection: Inspection, 47 | prices: &HistoricalPrice, 48 | gas_used: U256, 49 | gas_price: U256, 50 | ) -> Result> 51 | where 52 | T: 'static, 53 | { 54 | // TODO: Figure out how to sum up liquidations & arbs while pruning 55 | // aggressively 56 | // TODO: If an Inspection is CHECKED and contains >1 trading protocol, 57 | // then probably this is an Arbitrage? 58 | let mut actions = HashSet::new(); 59 | let mut profit = U256::zero(); 60 | for action in &inspection.actions { 61 | // only get the known actions 62 | let action = if let Some(action) = action.as_action() { 63 | action 64 | } else { 65 | continue; 66 | }; 67 | 68 | // set their action type 69 | use SpecificAction::*; 70 | match action { 71 | Arbitrage(_) => { 72 | actions.insert(ActionType::Arbitrage); 73 | } 74 | Liquidation(_) | ProfitableLiquidation(_) | LiquidationCheck => { 75 | actions.insert(ActionType::Liquidation); 76 | } 77 | Trade(_) => { 78 | actions.insert(ActionType::Trade); 79 | } 80 | _ => {} 81 | }; 82 | 83 | // dont try to calculate & normalize profits for unsuccessful txs 84 | if inspection.status != Status::Success { 85 | continue; 86 | } 87 | 88 | match action { 89 | SpecificAction::Arbitrage(arb) => { 90 | if arb.profit > 0.into() { 91 | profit += prices 92 | .quote(arb.token, arb.profit, inspection.block_number) 93 | .await 94 | .map_err(EvalError::Contract)?; 95 | } 96 | } 97 | SpecificAction::Liquidation(liq) => { 98 | if liq.sent_amount == U256::MAX { 99 | eprintln!( 100 | "U256::max detected in {}, skipping profit calculation", 101 | inspection.hash 102 | ); 103 | continue; 104 | } 105 | let res = futures::future::join( 106 | prices.quote(liq.sent_token, liq.sent_amount, inspection.block_number), 107 | prices.quote( 108 | liq.received_token, 109 | liq.received_amount, 110 | inspection.block_number, 111 | ), 112 | ) 113 | .await; 114 | 115 | match res { 116 | (Ok(amount_in), Ok(amount_out)) => { 117 | profit += amount_out.saturating_sub(amount_in); 118 | } 119 | _ => println!("Could not fetch prices from Uniswap"), 120 | }; 121 | 122 | if res.0.is_err() { 123 | println!("Sent: {} of token {:?}", liq.sent_amount, liq.sent_token); 124 | } 125 | 126 | if res.1.is_err() { 127 | println!( 128 | "Received: {} of token {:?}", 129 | liq.received_amount, liq.received_token 130 | ); 131 | } 132 | } 133 | SpecificAction::ProfitableLiquidation(liq) => { 134 | profit += prices 135 | .quote(liq.token, liq.profit, inspection.block_number) 136 | .await 137 | .map_err(EvalError::Contract)?; 138 | } 139 | _ => (), 140 | }; 141 | } 142 | 143 | Ok(Evaluation { 144 | inspection, 145 | gas_used, 146 | gas_price, 147 | actions, 148 | profit, 149 | }) 150 | } 151 | } 152 | 153 | // TODO: Can we do something about the generic static type bounds? 154 | #[derive(Debug, Error)] 155 | pub enum EvalError 156 | where 157 | M: 'static, 158 | { 159 | #[error(transparent)] 160 | Provider(M::Error), 161 | #[error("Transaction was not found {0}")] 162 | TxNotFound(TxHash), 163 | #[error(transparent)] 164 | Contract(ContractError), 165 | } 166 | -------------------------------------------------------------------------------- /src/types/inspection.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | addresses::{DYDX, FILTER, ZEROX}, 3 | types::{ 4 | classification::{ActionTrace, CallTrace}, 5 | Classification, Protocol, Status, 6 | }, 7 | }; 8 | use ethers::types::{Action, Address, CallType, Trace, TxHash}; 9 | use std::{collections::HashSet, convert::TryFrom}; 10 | 11 | #[derive(Debug, Clone)] 12 | /// The result of an inspection of a trace along with its inspected subtraces 13 | pub struct Inspection { 14 | /// Success / failure 15 | pub status: Status, 16 | 17 | ////// What 18 | /// All the classified / unclassified actions that happened 19 | pub actions: Vec, 20 | 21 | ///// Where 22 | /// All the involved protocols 23 | pub protocols: HashSet, 24 | 25 | // Who 26 | /// The sender of the transaction 27 | pub from: Address, 28 | /// The first receiver of this tx, the contract being interacted with. In case 29 | /// of sophisticated bots, this will be the bot's contract logic. 30 | pub contract: Address, 31 | /// If this is set, then the `contract` was a proxy and the actual logic is 32 | /// in this address 33 | pub proxy_impl: Option
, 34 | 35 | ////// When 36 | /// The trace's tx hash 37 | pub hash: TxHash, 38 | 39 | /// The block number of this tx 40 | pub block_number: u64, 41 | } 42 | 43 | impl Inspection { 44 | // TODO: Is there a better way to do this without re-allocating? 45 | // Maybe this? https://doc.rust-lang.org/std/vec/struct.DrainFilter.html 46 | pub fn prune(&mut self) { 47 | self.actions = self 48 | .actions 49 | .iter() 50 | .filter(|action| match action { 51 | // Remove any of the pruned calls 52 | Classification::Prune => false, 53 | // Remove calls with 2300 gas as they are probably due to 54 | // the gas stipend for low level calls, which we've already 55 | // taken into account. 56 | Classification::Unknown(call) => call.as_ref().gas != 2300.into(), 57 | Classification::Known(_) => true, 58 | }) 59 | .cloned() 60 | .collect(); 61 | } 62 | 63 | /// Returns: types of protocols, types of actions (arb, liq), bot addresses and profit 64 | /// Bots that perform liq/arbs maybe for a profit that are not in the addressbook should be 65 | /// added 66 | pub fn summary(&self) {} 67 | 68 | /// Returns all the successfully classified calls in this Inspection 69 | pub fn known(&self) -> Vec { 70 | self.actions 71 | .iter() 72 | .filter_map(|classification| match classification { 73 | Classification::Known(inner) => Some(inner), 74 | Classification::Unknown(_) | Classification::Prune => None, 75 | }) 76 | .cloned() 77 | .collect() 78 | } 79 | 80 | /// Returns all the unsuccessfully classified calls in this Inspection 81 | pub fn unknown(&self) -> Vec { 82 | self.actions 83 | .iter() 84 | .filter_map(|classification| match classification { 85 | Classification::Unknown(inner) => Some(inner), 86 | Classification::Known(_) | Classification::Prune => None, 87 | }) 88 | .cloned() 89 | .collect() 90 | } 91 | } 92 | 93 | /// Helper type to bypass https://github.com/rust-lang/rust/issues/50133#issuecomment-646908391 94 | pub(crate) struct TraceWrapper(pub(crate) T); 95 | impl> TryFrom> for Inspection { 96 | type Error = (); 97 | 98 | fn try_from(traces: TraceWrapper) -> Result { 99 | let mut traces = traces.0.into_iter().peekable(); 100 | 101 | // get the first trace 102 | let trace = match traces.peek() { 103 | Some(inner) => inner, 104 | None => return Err(()), 105 | }; 106 | let call = match trace.action { 107 | Action::Call(ref call) => call, 108 | // the first action we care about must be a call. everything else 109 | // is junk 110 | _ => return Err(()), 111 | }; 112 | 113 | // Filter out unwanted calls 114 | if FILTER.get(&call.to).is_some() { 115 | return Err(()); 116 | } 117 | 118 | let mut inspection = Inspection { 119 | status: Status::Success, 120 | // all unclassified calls 121 | actions: Vec::new(), 122 | // start off with empty protocols since everything is unclassified 123 | protocols: HashSet::new(), 124 | from: call.from, 125 | contract: call.to, 126 | proxy_impl: None, 127 | hash: trace.transaction_hash.unwrap_or_else(TxHash::zero), 128 | block_number: trace.block_number, 129 | }; 130 | 131 | inspection.actions = traces 132 | .into_iter() 133 | .filter_map(|trace| { 134 | // Revert if all subtraces revert? There are counterexamples 135 | // e.g. when a low-level trace's revert is handled 136 | if trace.error.is_some() { 137 | inspection.status = Status::Reverted; 138 | } 139 | 140 | match trace.action { 141 | Action::Call(call) => { 142 | if inspection.proxy_impl.is_none() 143 | && call.call_type == CallType::DelegateCall 144 | && call.from == inspection.contract 145 | { 146 | inspection.proxy_impl = Some(call.to); 147 | } 148 | 149 | if call.to == *DYDX { 150 | inspection.protocols.insert(Protocol::DyDx); 151 | } 152 | 153 | if call.to == *ZEROX { 154 | inspection.protocols.insert(Protocol::ZeroEx); 155 | } 156 | 157 | Some( 158 | CallTrace { 159 | call, 160 | trace_address: trace.trace_address, 161 | } 162 | .into(), 163 | ) 164 | } 165 | _ => None, 166 | } 167 | }) 168 | .collect(); 169 | 170 | Ok(inspection) 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/types/mod.rs: -------------------------------------------------------------------------------- 1 | //! All the datatypes associated with MEV-Inspect 2 | pub mod actions; 3 | 4 | pub mod evaluation; 5 | pub use evaluation::{EvalError, Evaluation}; 6 | 7 | pub(crate) mod classification; 8 | pub use classification::Classification; 9 | 10 | pub(crate) mod inspection; 11 | pub use inspection::Inspection; 12 | 13 | #[derive(Debug, Clone, PartialOrd, PartialEq)] 14 | pub enum Status { 15 | /// When a transaction reverts without touching any DeFi protocol 16 | Reverted, 17 | /// When a transaction reverts early but it had touched a DeFi protocol 18 | Checked, 19 | /// When a transaction succeeds 20 | Success, 21 | } 22 | 23 | #[derive(Debug, Clone, Copy, PartialOrd, PartialEq, Eq, Ord, Hash)] 24 | /// The supported protocols 25 | pub enum Protocol { 26 | // Uniswap & Forks 27 | UniswapV1, 28 | Uniswap, 29 | Uniswappy, 30 | Sushiswap, 31 | SakeSwap, 32 | 33 | // Other AMMs 34 | Curve, 35 | Balancer, 36 | 37 | // Lending / Liquidations 38 | Aave, 39 | Compound, 40 | 41 | // Aggregators 42 | ZeroEx, 43 | 44 | // Misc. 45 | Flashloan, 46 | DyDx, 47 | } 48 | --------------------------------------------------------------------------------