├── .env ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── Cargo.toml ├── README.md ├── crates └── poirot-core │ ├── Cargo.toml │ ├── logs.txt │ └── src │ ├── abi.rs │ ├── abi │ └── uniswap.json │ ├── action.rs │ ├── lib.rs │ ├── main.rs │ └── parser.rs └── rustfmt.toml /.env: -------------------------------------------------------------------------------- 1 | export DB_PATH="/home/data/reth/db" 2 | export RETH_PORT="8489" 3 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | pull_request: 6 | 7 | name: Rust 8 | 9 | jobs: 10 | tests-stable: 11 | name: Tests (Stable) 12 | runs-on: ubuntu-latest 13 | env: 14 | ETH_RPC_URL: https://eth-mainnet.alchemyapi.io/v2/Lc7oIGYeL_QvInzI0Wiu_pOZZDEKBrdf 15 | steps: 16 | - name: Checkout sources 17 | uses: actions/checkout@v2 18 | 19 | - name: Install toolchain 20 | uses: actions-rs/toolchain@v1 21 | with: 22 | toolchain: stable 23 | profile: minimal 24 | override: true 25 | 26 | - uses: Swatinem/rust-cache@v1 27 | with: 28 | cache-on-failure: true 29 | 30 | - name: cargo test 31 | run: cargo test --all --all-features 32 | 33 | lint: 34 | runs-on: ubuntu-latest 35 | steps: 36 | - name: Checkout sources 37 | uses: actions/checkout@v2 38 | 39 | - name: Install toolchain 40 | uses: actions-rs/toolchain@v1 41 | with: 42 | toolchain: nightly 43 | profile: minimal 44 | components: rustfmt, clippy 45 | override: true 46 | 47 | - uses: Swatinem/rust-cache@v1 48 | with: 49 | cache-on-failure: true 50 | 51 | - name: cargo fmt 52 | run: cargo +nightly fmt --all -- --check 53 | 54 | - name: cargo clippy 55 | run: cargo +nightly clippy --all --all-features -- -D warnings -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb 15 | 16 | .env 17 | .DS_Store -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "crates/poirot-core" 4 | ] 5 | 6 | 7 | [patch.crates-io] 8 | # patched for quantity U256 responses 9 | ruint = { git = "https://github.com/paradigmxyz/uint" } 10 | revm-primitives = { git = "https://github.com/bluealloy/revm/", branch = "release/v25" } 11 | revm = { git = "https://github.com/bluealloy/revm/", branch = "release/v25" } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Poirot-rs 2 | 3 | A searchers' searcher 4 | 5 | Deprecated. 6 | 7 | We've been cooking, watch out for [Brontes](https://github.com/SorellaLabs/brontes) 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /crates/poirot-core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "poirot-core" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | 9 | [dependencies] 10 | reth-tasks = { git = "https://github.com/paradigmxyz/reth", package = "reth-tasks"} 11 | reth-rpc = { git = "https://github.com/paradigmxyz/reth", package = "reth-rpc"} 12 | reth-rpc-api = { git = "https://github.com/paradigmxyz/reth", package = "reth-rpc-api"} 13 | reth-rpc-builder = { git = "https://github.com/paradigmxyz/reth", package = "reth-rpc-builder"} 14 | reth-rpc-types = { git = "https://github.com/paradigmxyz/reth", package = "reth-rpc-types"} 15 | reth-provider = { git = "https://github.com/paradigmxyz/reth", package = "reth-provider"} 16 | reth-transaction-pool = { git = "https://github.com/paradigmxyz/reth", package = "reth-transaction-pool"} 17 | reth-network-api = { git = "https://github.com/paradigmxyz/reth", package = "reth-network-api"} 18 | reth-primitives = { git = "https://github.com/paradigmxyz/reth", package = "reth-primitives"} 19 | reth-db = { git = "https://github.com/paradigmxyz/reth", package = "reth-db"} 20 | reth-blockchain-tree = { git = "https://github.com/paradigmxyz/reth", package = "reth-blockchain-tree"} 21 | reth-beacon-consensus = { git = "https://github.com/paradigmxyz/reth", package = "reth-beacon-consensus"} 22 | reth-revm = { git = "https://github.com/paradigmxyz/reth", package = "reth-revm"} 23 | reth-interfaces = { git = "https://github.com/paradigmxyz/reth", package = "reth-interfaces"} 24 | 25 | alloy-json-abi = {git = "https://github.com/alloy-rs/core", package = "alloy-json-abi"} 26 | serde_json = { version = "1.0", default-features = false, features = ["alloc"] } 27 | alloy-sol-types = {git = "https://github.com/alloy-rs/core", package = "alloy-sol-types", features = ["json"]} 28 | alloy-primitives = {git = "https://github.com/alloy-rs/core", package = "alloy-primitives"} 29 | alloy-dyn-abi = {git = "https://github.com/alloy-rs/core", package = "alloy-dyn-abi"} 30 | tokio = { version = "1.28.2", features = ["full"] } 31 | hex = "0.4.3" 32 | eyre = "0.6.8" 33 | ruint2 = "1.9.0" 34 | tracing = "0.1.0" 35 | tracing-subscriber = { version = "0.3", default-features = false, features = ["env-filter", "fmt"] } 36 | 37 | # evm 38 | ethers = { version = "2.0.7", features = ["ipc", "ws"] } 39 | rustc-hex = "2.1.0" 40 | revm-primitives = "1.1.2" -------------------------------------------------------------------------------- /crates/poirot-core/src/abi.rs: -------------------------------------------------------------------------------- 1 | use alloy_dyn_abi::DynSolType; 2 | use alloy_json_abi::{Function, JsonAbi}; 3 | use alloy_sol_types::decode; 4 | use reth_rpc_types::trace::parity::{Action as RethAction, LocalizedTransactionTrace}; 5 | use revm_primitives::bits::B160; 6 | use serde_json::Value; 7 | use std::{collections::HashMap, path::PathBuf}; 8 | 9 | pub struct ContractAbiStorage<'a> { 10 | mapping: HashMap<&'a B160, PathBuf>, 11 | } 12 | 13 | impl<'a> ContractAbiStorage<'a> { 14 | pub fn new() -> Self { 15 | Self { mapping: HashMap::new() } 16 | } 17 | 18 | pub fn add_abi(&mut self, contract_address: &'a B160, abi_path: PathBuf) { 19 | self.mapping.insert(contract_address, abi_path); 20 | } 21 | 22 | pub fn get_abi(&self, contract_address: &'a B160) -> Option<&PathBuf> { 23 | self.mapping.get(contract_address) 24 | } 25 | } 26 | 27 | pub fn sleuth<'a>( 28 | storage: &'a ContractAbiStorage, 29 | trace: LocalizedTransactionTrace, 30 | ) -> Result> { 31 | let action = trace.trace.action; 32 | 33 | let (contract_address, input) = match action { 34 | RethAction::Call(call_action) => (call_action.to, call_action.input.to_vec()), 35 | _ => return Err(From::from("The action in the transaction trace is not Call(CallAction)")), 36 | }; 37 | 38 | let abi_path = storage.get_abi(&contract_address).ok_or("No ABI found for this contract")?; 39 | 40 | let file = std::fs::File::open(abi_path)?; 41 | let reader = std::io::BufReader::new(file); 42 | 43 | let json_abi: JsonAbi = serde_json::from_reader(reader)?; 44 | 45 | let function_selector = &input[..4]; 46 | 47 | //todo: 48 | if let Some(functions) = Some(json_abi.functions.values().flatten()) { 49 | for function in functions { 50 | if function.selector() == function_selector { 51 | let input_types: Vec = 52 | function.inputs.iter().map(|input| input.to_string()).collect(); 53 | 54 | let mut decoded_inputs = Vec::new(); 55 | for (index, input_type_str) in input_types.iter().enumerate() { 56 | //TODO: just fix this, where you properly provide the expected decoding from the abi 57 | let input_data = &input[4 + index..]; // Skip the function selector and previous inputs 58 | let dyn_sol_type: DynSolType = input_type_str.parse().unwrap(); 59 | let dyn_sol_value = dyn_sol_type.decode_params(input_data)?; 60 | decoded_inputs.push(format!("{:?}", dyn_sol_value)); 61 | } 62 | 63 | let printout = format!("Function: {}\nInputs: {:?}", function.name, decoded_inputs); 64 | return Ok(printout) 65 | } 66 | } 67 | } 68 | 69 | Err(From::from("No matching function found in the ABI")) 70 | } 71 | -------------------------------------------------------------------------------- /crates/poirot-core/src/abi/uniswap.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "anonymous": false, 4 | "inputs": [ 5 | { 6 | "indexed": true, 7 | "internalType": "address", 8 | "name": "owner", 9 | "type": "address" 10 | }, 11 | { 12 | "indexed": true, 13 | "internalType": "int24", 14 | "name": "tickLower", 15 | "type": "int24" 16 | }, 17 | { 18 | "indexed": true, 19 | "internalType": "int24", 20 | "name": "tickUpper", 21 | "type": "int24" 22 | }, 23 | { 24 | "indexed": false, 25 | "internalType": "uint128", 26 | "name": "amount", 27 | "type": "uint128" 28 | }, 29 | { 30 | "indexed": false, 31 | "internalType": "uint256", 32 | "name": "amount0", 33 | "type": "uint256" 34 | }, 35 | { 36 | "indexed": false, 37 | "internalType": "uint256", 38 | "name": "amount1", 39 | "type": "uint256" 40 | } 41 | ], 42 | "name": "Burn", 43 | "type": "event" 44 | }, 45 | { 46 | "anonymous": false, 47 | "inputs": [ 48 | { 49 | "indexed": true, 50 | "internalType": "address", 51 | "name": "owner", 52 | "type": "address" 53 | }, 54 | { 55 | "indexed": false, 56 | "internalType": "address", 57 | "name": "recipient", 58 | "type": "address" 59 | }, 60 | { 61 | "indexed": true, 62 | "internalType": "int24", 63 | "name": "tickLower", 64 | "type": "int24" 65 | }, 66 | { 67 | "indexed": true, 68 | "internalType": "int24", 69 | "name": "tickUpper", 70 | "type": "int24" 71 | }, 72 | { 73 | "indexed": false, 74 | "internalType": "uint128", 75 | "name": "amount0", 76 | "type": "uint128" 77 | }, 78 | { 79 | "indexed": false, 80 | "internalType": "uint128", 81 | "name": "amount1", 82 | "type": "uint128" 83 | } 84 | ], 85 | "name": "Collect", 86 | "type": "event" 87 | }, 88 | { 89 | "anonymous": false, 90 | "inputs": [ 91 | { 92 | "indexed": true, 93 | "internalType": "address", 94 | "name": "sender", 95 | "type": "address" 96 | }, 97 | { 98 | "indexed": true, 99 | "internalType": "address", 100 | "name": "recipient", 101 | "type": "address" 102 | }, 103 | { 104 | "indexed": false, 105 | "internalType": "uint128", 106 | "name": "amount0", 107 | "type": "uint128" 108 | }, 109 | { 110 | "indexed": false, 111 | "internalType": "uint128", 112 | "name": "amount1", 113 | "type": "uint128" 114 | } 115 | ], 116 | "name": "CollectProtocol", 117 | "type": "event" 118 | }, 119 | { 120 | "anonymous": false, 121 | "inputs": [ 122 | { 123 | "indexed": true, 124 | "internalType": "address", 125 | "name": "sender", 126 | "type": "address" 127 | }, 128 | { 129 | "indexed": true, 130 | "internalType": "address", 131 | "name": "recipient", 132 | "type": "address" 133 | }, 134 | { 135 | "indexed": false, 136 | "internalType": "uint256", 137 | "name": "amount0", 138 | "type": "uint256" 139 | }, 140 | { 141 | "indexed": false, 142 | "internalType": "uint256", 143 | "name": "amount1", 144 | "type": "uint256" 145 | }, 146 | { 147 | "indexed": false, 148 | "internalType": "uint256", 149 | "name": "paid0", 150 | "type": "uint256" 151 | }, 152 | { 153 | "indexed": false, 154 | "internalType": "uint256", 155 | "name": "paid1", 156 | "type": "uint256" 157 | } 158 | ], 159 | "name": "Flash", 160 | "type": "event" 161 | }, 162 | { 163 | "anonymous": false, 164 | "inputs": [ 165 | { 166 | "indexed": false, 167 | "internalType": "uint16", 168 | "name": "observationCardinalityNextOld", 169 | "type": "uint16" 170 | }, 171 | { 172 | "indexed": false, 173 | "internalType": "uint16", 174 | "name": "observationCardinalityNextNew", 175 | "type": "uint16" 176 | } 177 | ], 178 | "name": "IncreaseObservationCardinalityNext", 179 | "type": "event" 180 | }, 181 | { 182 | "anonymous": false, 183 | "inputs": [ 184 | { 185 | "indexed": false, 186 | "internalType": "uint160", 187 | "name": "sqrtPriceX96", 188 | "type": "uint160" 189 | }, 190 | { 191 | "indexed": false, 192 | "internalType": "int24", 193 | "name": "tick", 194 | "type": "int24" 195 | } 196 | ], 197 | "name": "Initialize", 198 | "type": "event" 199 | }, 200 | { 201 | "anonymous": false, 202 | "inputs": [ 203 | { 204 | "indexed": false, 205 | "internalType": "address", 206 | "name": "sender", 207 | "type": "address" 208 | }, 209 | { 210 | "indexed": true, 211 | "internalType": "address", 212 | "name": "owner", 213 | "type": "address" 214 | }, 215 | { 216 | "indexed": true, 217 | "internalType": "int24", 218 | "name": "tickLower", 219 | "type": "int24" 220 | }, 221 | { 222 | "indexed": true, 223 | "internalType": "int24", 224 | "name": "tickUpper", 225 | "type": "int24" 226 | }, 227 | { 228 | "indexed": false, 229 | "internalType": "uint128", 230 | "name": "amount", 231 | "type": "uint128" 232 | }, 233 | { 234 | "indexed": false, 235 | "internalType": "uint256", 236 | "name": "amount0", 237 | "type": "uint256" 238 | }, 239 | { 240 | "indexed": false, 241 | "internalType": "uint256", 242 | "name": "amount1", 243 | "type": "uint256" 244 | } 245 | ], 246 | "name": "Mint", 247 | "type": "event" 248 | }, 249 | { 250 | "anonymous": false, 251 | "inputs": [ 252 | { 253 | "indexed": false, 254 | "internalType": "uint8", 255 | "name": "feeProtocol0Old", 256 | "type": "uint8" 257 | }, 258 | { 259 | "indexed": false, 260 | "internalType": "uint8", 261 | "name": "feeProtocol1Old", 262 | "type": "uint8" 263 | }, 264 | { 265 | "indexed": false, 266 | "internalType": "uint8", 267 | "name": "feeProtocol0New", 268 | "type": "uint8" 269 | }, 270 | { 271 | "indexed": false, 272 | "internalType": "uint8", 273 | "name": "feeProtocol1New", 274 | "type": "uint8" 275 | } 276 | ], 277 | "name": "SetFeeProtocol", 278 | "type": "event" 279 | }, 280 | { 281 | "anonymous": false, 282 | "inputs": [ 283 | { 284 | "indexed": true, 285 | "internalType": "address", 286 | "name": "sender", 287 | "type": "address" 288 | }, 289 | { 290 | "indexed": true, 291 | "internalType": "address", 292 | "name": "recipient", 293 | "type": "address" 294 | }, 295 | { 296 | "indexed": false, 297 | "internalType": "int256", 298 | "name": "amount0", 299 | "type": "int256" 300 | }, 301 | { 302 | "indexed": false, 303 | "internalType": "int256", 304 | "name": "amount1", 305 | "type": "int256" 306 | }, 307 | { 308 | "indexed": false, 309 | "internalType": "uint160", 310 | "name": "sqrtPriceX96", 311 | "type": "uint160" 312 | }, 313 | { 314 | "indexed": false, 315 | "internalType": "uint128", 316 | "name": "liquidity", 317 | "type": "uint128" 318 | }, 319 | { 320 | "indexed": false, 321 | "internalType": "int24", 322 | "name": "tick", 323 | "type": "int24" 324 | } 325 | ], 326 | "name": "Swap", 327 | "type": "event" 328 | }, 329 | { 330 | "inputs": [ 331 | { "internalType": "int24", "name": "tickLower", "type": "int24" }, 332 | { "internalType": "int24", "name": "tickUpper", "type": "int24" }, 333 | { "internalType": "uint128", "name": "amount", "type": "uint128" } 334 | ], 335 | "name": "burn", 336 | "outputs": [ 337 | { "internalType": "uint256", "name": "amount0", "type": "uint256" }, 338 | { "internalType": "uint256", "name": "amount1", "type": "uint256" } 339 | ], 340 | "stateMutability": "nonpayable", 341 | "type": "function" 342 | }, 343 | { 344 | "inputs": [ 345 | { "internalType": "address", "name": "recipient", "type": "address" }, 346 | { "internalType": "int24", "name": "tickLower", "type": "int24" }, 347 | { "internalType": "int24", "name": "tickUpper", "type": "int24" }, 348 | { 349 | "internalType": "uint128", 350 | "name": "amount0Requested", 351 | "type": "uint128" 352 | }, 353 | { 354 | "internalType": "uint128", 355 | "name": "amount1Requested", 356 | "type": "uint128" 357 | } 358 | ], 359 | "name": "collect", 360 | "outputs": [ 361 | { "internalType": "uint128", "name": "amount0", "type": "uint128" }, 362 | { "internalType": "uint128", "name": "amount1", "type": "uint128" } 363 | ], 364 | "stateMutability": "nonpayable", 365 | "type": "function" 366 | }, 367 | { 368 | "inputs": [ 369 | { "internalType": "address", "name": "recipient", "type": "address" }, 370 | { 371 | "internalType": "uint128", 372 | "name": "amount0Requested", 373 | "type": "uint128" 374 | }, 375 | { 376 | "internalType": "uint128", 377 | "name": "amount1Requested", 378 | "type": "uint128" 379 | } 380 | ], 381 | "name": "collectProtocol", 382 | "outputs": [ 383 | { "internalType": "uint128", "name": "amount0", "type": "uint128" }, 384 | { "internalType": "uint128", "name": "amount1", "type": "uint128" } 385 | ], 386 | "stateMutability": "nonpayable", 387 | "type": "function" 388 | }, 389 | { 390 | "inputs": [], 391 | "name": "factory", 392 | "outputs": [{ "internalType": "address", "name": "", "type": "address" }], 393 | "stateMutability": "view", 394 | "type": "function" 395 | }, 396 | { 397 | "inputs": [], 398 | "name": "fee", 399 | "outputs": [{ "internalType": "uint24", "name": "", "type": "uint24" }], 400 | "stateMutability": "view", 401 | "type": "function" 402 | }, 403 | { 404 | "inputs": [], 405 | "name": "feeGrowthGlobal0X128", 406 | "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], 407 | "stateMutability": "view", 408 | "type": "function" 409 | }, 410 | { 411 | "inputs": [], 412 | "name": "feeGrowthGlobal1X128", 413 | "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], 414 | "stateMutability": "view", 415 | "type": "function" 416 | }, 417 | { 418 | "inputs": [ 419 | { "internalType": "address", "name": "recipient", "type": "address" }, 420 | { "internalType": "uint256", "name": "amount0", "type": "uint256" }, 421 | { "internalType": "uint256", "name": "amount1", "type": "uint256" }, 422 | { "internalType": "bytes", "name": "data", "type": "bytes" } 423 | ], 424 | "name": "flash", 425 | "outputs": [], 426 | "stateMutability": "nonpayable", 427 | "type": "function" 428 | }, 429 | { 430 | "inputs": [ 431 | { 432 | "internalType": "uint16", 433 | "name": "observationCardinalityNext", 434 | "type": "uint16" 435 | } 436 | ], 437 | "name": "increaseObservationCardinalityNext", 438 | "outputs": [], 439 | "stateMutability": "nonpayable", 440 | "type": "function" 441 | }, 442 | { 443 | "inputs": [ 444 | { "internalType": "uint160", "name": "sqrtPriceX96", "type": "uint160" } 445 | ], 446 | "name": "initialize", 447 | "outputs": [], 448 | "stateMutability": "nonpayable", 449 | "type": "function" 450 | }, 451 | { 452 | "inputs": [], 453 | "name": "liquidity", 454 | "outputs": [{ "internalType": "uint128", "name": "", "type": "uint128" }], 455 | "stateMutability": "view", 456 | "type": "function" 457 | }, 458 | { 459 | "inputs": [], 460 | "name": "maxLiquidityPerTick", 461 | "outputs": [{ "internalType": "uint128", "name": "", "type": "uint128" }], 462 | "stateMutability": "view", 463 | "type": "function" 464 | }, 465 | { 466 | "inputs": [ 467 | { "internalType": "address", "name": "recipient", "type": "address" }, 468 | { "internalType": "int24", "name": "tickLower", "type": "int24" }, 469 | { "internalType": "int24", "name": "tickUpper", "type": "int24" }, 470 | { "internalType": "uint128", "name": "amount", "type": "uint128" }, 471 | { "internalType": "bytes", "name": "data", "type": "bytes" } 472 | ], 473 | "name": "mint", 474 | "outputs": [ 475 | { "internalType": "uint256", "name": "amount0", "type": "uint256" }, 476 | { "internalType": "uint256", "name": "amount1", "type": "uint256" } 477 | ], 478 | "stateMutability": "nonpayable", 479 | "type": "function" 480 | }, 481 | { 482 | "inputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], 483 | "name": "observations", 484 | "outputs": [ 485 | { "internalType": "uint32", "name": "blockTimestamp", "type": "uint32" }, 486 | { "internalType": "int56", "name": "tickCumulative", "type": "int56" }, 487 | { 488 | "internalType": "uint160", 489 | "name": "secondsPerLiquidityCumulativeX128", 490 | "type": "uint160" 491 | }, 492 | { "internalType": "bool", "name": "initialized", "type": "bool" } 493 | ], 494 | "stateMutability": "view", 495 | "type": "function" 496 | }, 497 | { 498 | "inputs": [ 499 | { "internalType": "uint32[]", "name": "secondsAgos", "type": "uint32[]" } 500 | ], 501 | "name": "observe", 502 | "outputs": [ 503 | { 504 | "internalType": "int56[]", 505 | "name": "tickCumulatives", 506 | "type": "int56[]" 507 | }, 508 | { 509 | "internalType": "uint160[]", 510 | "name": "secondsPerLiquidityCumulativeX128s", 511 | "type": "uint160[]" 512 | } 513 | ], 514 | "stateMutability": "view", 515 | "type": "function" 516 | }, 517 | { 518 | "inputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], 519 | "name": "positions", 520 | "outputs": [ 521 | { "internalType": "uint128", "name": "liquidity", "type": "uint128" }, 522 | { 523 | "internalType": "uint256", 524 | "name": "feeGrowthInside0LastX128", 525 | "type": "uint256" 526 | }, 527 | { 528 | "internalType": "uint256", 529 | "name": "feeGrowthInside1LastX128", 530 | "type": "uint256" 531 | }, 532 | { "internalType": "uint128", "name": "tokensOwed0", "type": "uint128" }, 533 | { "internalType": "uint128", "name": "tokensOwed1", "type": "uint128" } 534 | ], 535 | "stateMutability": "view", 536 | "type": "function" 537 | }, 538 | { 539 | "inputs": [], 540 | "name": "protocolFees", 541 | "outputs": [ 542 | { "internalType": "uint128", "name": "token0", "type": "uint128" }, 543 | { "internalType": "uint128", "name": "token1", "type": "uint128" } 544 | ], 545 | "stateMutability": "view", 546 | "type": "function" 547 | }, 548 | { 549 | "inputs": [ 550 | { "internalType": "uint8", "name": "feeProtocol0", "type": "uint8" }, 551 | { "internalType": "uint8", "name": "feeProtocol1", "type": "uint8" } 552 | ], 553 | "name": "setFeeProtocol", 554 | "outputs": [], 555 | "stateMutability": "nonpayable", 556 | "type": "function" 557 | }, 558 | { 559 | "inputs": [], 560 | "name": "slot0", 561 | "outputs": [ 562 | { "internalType": "uint160", "name": "sqrtPriceX96", "type": "uint160" }, 563 | { "internalType": "int24", "name": "tick", "type": "int24" }, 564 | { 565 | "internalType": "uint16", 566 | "name": "observationIndex", 567 | "type": "uint16" 568 | }, 569 | { 570 | "internalType": "uint16", 571 | "name": "observationCardinality", 572 | "type": "uint16" 573 | }, 574 | { 575 | "internalType": "uint16", 576 | "name": "observationCardinalityNext", 577 | "type": "uint16" 578 | }, 579 | { "internalType": "uint8", "name": "feeProtocol", "type": "uint8" }, 580 | { "internalType": "bool", "name": "unlocked", "type": "bool" } 581 | ], 582 | "stateMutability": "view", 583 | "type": "function" 584 | }, 585 | { 586 | "inputs": [ 587 | { "internalType": "int24", "name": "tickLower", "type": "int24" }, 588 | { "internalType": "int24", "name": "tickUpper", "type": "int24" } 589 | ], 590 | "name": "snapshotCumulativesInside", 591 | "outputs": [ 592 | { 593 | "internalType": "int56", 594 | "name": "tickCumulativeInside", 595 | "type": "int56" 596 | }, 597 | { 598 | "internalType": "uint160", 599 | "name": "secondsPerLiquidityInsideX128", 600 | "type": "uint160" 601 | }, 602 | { "internalType": "uint32", "name": "secondsInside", "type": "uint32" } 603 | ], 604 | "stateMutability": "view", 605 | "type": "function" 606 | }, 607 | { 608 | "inputs": [ 609 | { "internalType": "address", "name": "recipient", "type": "address" }, 610 | { "internalType": "bool", "name": "zeroForOne", "type": "bool" }, 611 | { "internalType": "int256", "name": "amountSpecified", "type": "int256" }, 612 | { 613 | "internalType": "uint160", 614 | "name": "sqrtPriceLimitX96", 615 | "type": "uint160" 616 | }, 617 | { "internalType": "bytes", "name": "data", "type": "bytes" } 618 | ], 619 | "name": "swap", 620 | "outputs": [ 621 | { "internalType": "int256", "name": "amount0", "type": "int256" }, 622 | { "internalType": "int256", "name": "amount1", "type": "int256" } 623 | ], 624 | "stateMutability": "nonpayable", 625 | "type": "function" 626 | }, 627 | { 628 | "inputs": [{ "internalType": "int16", "name": "", "type": "int16" }], 629 | "name": "tickBitmap", 630 | "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], 631 | "stateMutability": "view", 632 | "type": "function" 633 | }, 634 | { 635 | "inputs": [], 636 | "name": "tickSpacing", 637 | "outputs": [{ "internalType": "int24", "name": "", "type": "int24" }], 638 | "stateMutability": "view", 639 | "type": "function" 640 | }, 641 | { 642 | "inputs": [{ "internalType": "int24", "name": "", "type": "int24" }], 643 | "name": "ticks", 644 | "outputs": [ 645 | { 646 | "internalType": "uint128", 647 | "name": "liquidityGross", 648 | "type": "uint128" 649 | }, 650 | { "internalType": "int128", "name": "liquidityNet", "type": "int128" }, 651 | { 652 | "internalType": "uint256", 653 | "name": "feeGrowthOutside0X128", 654 | "type": "uint256" 655 | }, 656 | { 657 | "internalType": "uint256", 658 | "name": "feeGrowthOutside1X128", 659 | "type": "uint256" 660 | }, 661 | { 662 | "internalType": "int56", 663 | "name": "tickCumulativeOutside", 664 | "type": "int56" 665 | }, 666 | { 667 | "internalType": "uint160", 668 | "name": "secondsPerLiquidityOutsideX128", 669 | "type": "uint160" 670 | }, 671 | { "internalType": "uint32", "name": "secondsOutside", "type": "uint32" }, 672 | { "internalType": "bool", "name": "initialized", "type": "bool" } 673 | ], 674 | "stateMutability": "view", 675 | "type": "function" 676 | }, 677 | { 678 | "inputs": [], 679 | "name": "token0", 680 | "outputs": [{ "internalType": "address", "name": "", "type": "address" }], 681 | "stateMutability": "view", 682 | "type": "function" 683 | }, 684 | { 685 | "inputs": [], 686 | "name": "token1", 687 | "outputs": [{ "internalType": "address", "name": "", "type": "address" }], 688 | "stateMutability": "view", 689 | "type": "function" 690 | } 691 | ] 692 | -------------------------------------------------------------------------------- /crates/poirot-core/src/action.rs: -------------------------------------------------------------------------------- 1 | use alloy_primitives::{Address, U256}; 2 | use reth_primitives::{H160, H256}; 3 | use reth_revm::precompile::primitives::ruint::Uint; 4 | use reth_rpc_types::trace::parity::LocalizedTransactionTrace; 5 | 6 | #[derive(Debug, Clone)] 7 | pub struct Action { 8 | pub ty: ActionType, 9 | pub hash: H256, 10 | pub block: u64, 11 | } 12 | 13 | #[derive(Debug, Clone)] 14 | pub enum ActionType { 15 | Transfer(Transfer), 16 | PoolCreation(PoolCreation), 17 | Swap(Swap), 18 | WethDeposit(Deposit), 19 | WethWithdraw(Withdrawal), 20 | Unclassified(LocalizedTransactionTrace), 21 | } 22 | 23 | #[derive(Debug, Clone)] 24 | pub enum Protocol { 25 | UniswapV2, 26 | Sushiswap, 27 | Balancer, 28 | Curve, 29 | UniswapV3, 30 | SushiswapV3, 31 | Bancor, 32 | Kyber, 33 | Mooniswap, 34 | Dodo, 35 | DodoV2, 36 | DodoV3, 37 | } 38 | 39 | #[derive(Debug, Clone)] 40 | pub struct Withdrawal { 41 | pub to: H160, 42 | pub amount: alloy_primitives::Uint<256, 4>, 43 | } 44 | 45 | #[derive(Debug, Clone)] 46 | pub struct Deposit { 47 | pub from: H160, 48 | pub amount: Uint<256, 4>, 49 | } 50 | 51 | #[derive(Debug, Clone)] 52 | pub struct Transfer { 53 | pub to: Address, 54 | pub amount: U256, 55 | pub token: H160, 56 | } 57 | 58 | #[derive(Debug, Clone)] 59 | pub struct PoolCreation { 60 | pub token_0: Address, 61 | pub token_1: Address, 62 | pub fee: u32, 63 | } 64 | 65 | #[derive(Debug, Clone)] 66 | pub struct Swap { 67 | pub recipient: Address, 68 | pub direction: bool, 69 | pub amount_specified: alloy_primitives::Signed<256, 4>, 70 | pub price_limit: alloy_primitives::Uint<256, 4>, 71 | pub data: Vec, 72 | } 73 | 74 | impl Transfer { 75 | /// Public constructor function to instantiate a [`Transfer`]. 76 | pub fn new(to: Address, amount: ruint2::Uint<256, 4>, token: H160) -> Self { 77 | Self { to, amount, token } 78 | } 79 | } 80 | 81 | impl PoolCreation { 82 | /// Public constructor function to instantiate a [`PoolCreation`]. 83 | pub fn new(token_0: Address, token_1: Address, fee: u32) -> Self { 84 | Self { token_0, token_1, fee } 85 | } 86 | } 87 | 88 | impl Deposit { 89 | /// Public constructor function to instantiate a [`Deposit`]. 90 | pub fn new(from: H160, amount: Uint<256, 4>) -> Self { 91 | Self { from, amount } 92 | } 93 | } 94 | 95 | impl Withdrawal { 96 | /// Public constructor function to instantiate a [`Withdrawal`]. 97 | pub fn new(to: H160, amount: alloy_primitives::Uint<256, 4>) -> Self { 98 | Self { to, amount } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /crates/poirot-core/src/lib.rs: -------------------------------------------------------------------------------- 1 | use eyre::Context; 2 | use reth_beacon_consensus::BeaconConsensus; 3 | use reth_blockchain_tree::{ 4 | externals::TreeExternals, BlockchainTree, BlockchainTreeConfig, ShareableBlockchainTree, 5 | }; 6 | 7 | // Reth 8 | use reth_db::{ 9 | database::{Database, DatabaseGAT}, 10 | mdbx::{Env, WriteMap}, 11 | tables, 12 | transaction::DbTx, 13 | DatabaseError, 14 | }; 15 | use reth_network_api::noop::NoopNetwork; 16 | use reth_primitives::MAINNET; 17 | use reth_provider::{providers::BlockchainProvider, ProviderFactory}; 18 | use reth_revm::Factory; 19 | use reth_rpc::{ 20 | eth::{ 21 | cache::{EthStateCache, EthStateCacheConfig}, 22 | gas_oracle::{GasPriceOracle, GasPriceOracleConfig}, 23 | }, 24 | DebugApi, EthApi, EthFilter, TraceApi, TracingCallGuard, 25 | }; 26 | use reth_tasks::TaskManager; 27 | use reth_transaction_pool::{EthTransactionValidator, GasCostOrdering, Pool, PooledTransaction}; 28 | // Std 29 | use std::{fmt::Debug, path::Path, sync::Arc}; 30 | use tokio::runtime::Handle; 31 | 32 | pub mod abi; 33 | pub mod action; 34 | pub mod parser; 35 | 36 | pub type Provider = BlockchainProvider< 37 | Arc>, 38 | ShareableBlockchainTree>, Arc, Factory>, 39 | >; 40 | 41 | pub type RethTxPool = 42 | Pool, GasCostOrdering>; 43 | 44 | pub type RethApi = EthApi; 45 | 46 | pub struct TracingClient { 47 | pub reth_api: EthApi, 48 | pub reth_trace: TraceApi, 49 | pub reth_filter: EthFilter, 50 | pub reth_debug: DebugApi, 51 | } 52 | 53 | impl TracingClient { 54 | pub fn new(db_path: &Path, handle: Handle) -> Self { 55 | let task_manager = TaskManager::new(handle); 56 | let task_executor = task_manager.executor(); 57 | 58 | tokio::task::spawn(task_manager); 59 | 60 | let chain = MAINNET.clone(); 61 | let db = Arc::new(init_db(db_path).unwrap()); 62 | 63 | let tree_externals = TreeExternals::new( 64 | db.clone(), 65 | Arc::new(BeaconConsensus::new(Arc::clone(&chain))), 66 | Factory::new(chain.clone()), 67 | Arc::clone(&chain), 68 | ); 69 | 70 | let tree_config = BlockchainTreeConfig::default(); 71 | 72 | let (canon_state_notification_sender, _receiver) = 73 | tokio::sync::broadcast::channel(tree_config.max_reorg_depth() as usize * 2); 74 | 75 | let blockchain_tree = ShareableBlockchainTree::new( 76 | BlockchainTree::new(tree_externals, canon_state_notification_sender, tree_config) 77 | .unwrap(), 78 | ); 79 | 80 | let provider = BlockchainProvider::new( 81 | ProviderFactory::new(Arc::clone(&db), Arc::clone(&chain)), 82 | blockchain_tree, 83 | ) 84 | .unwrap(); 85 | 86 | let state_cache = EthStateCache::spawn(provider.clone(), EthStateCacheConfig::default()); 87 | 88 | let tx_pool = reth_transaction_pool::Pool::eth_pool( 89 | EthTransactionValidator::new(provider.clone(), chain, task_executor.clone()), 90 | Default::default(), 91 | ); 92 | 93 | let reth_api = EthApi::new( 94 | provider.clone(), 95 | tx_pool.clone(), 96 | NoopNetwork::default(), 97 | state_cache.clone(), 98 | GasPriceOracle::new( 99 | provider.clone(), 100 | GasPriceOracleConfig::default(), 101 | state_cache.clone(), 102 | ), 103 | ); 104 | 105 | let tracing_call_guard = TracingCallGuard::new(10); 106 | 107 | let reth_trace = TraceApi::new( 108 | provider.clone(), 109 | reth_api.clone(), 110 | state_cache.clone(), 111 | Box::new(task_executor.clone()), 112 | tracing_call_guard.clone(), 113 | ); 114 | 115 | let reth_debug = DebugApi::new( 116 | provider.clone(), 117 | reth_api.clone(), 118 | Box::new(task_executor.clone()), 119 | tracing_call_guard, 120 | ); 121 | 122 | let reth_filter = 123 | EthFilter::new(provider, tx_pool, state_cache, 1000, Box::new(task_executor)); 124 | 125 | Self { reth_api, reth_filter, reth_trace, reth_debug } 126 | } 127 | } 128 | 129 | /// re-implementation of 'view()' 130 | /// allows for a function to be passed in through a RO libmdbx transaction 131 | /// /reth/crates/storage/db/src/abstraction/database.rs 132 | pub fn view(db: &Env, f: F) -> Result 133 | where 134 | F: FnOnce(& as DatabaseGAT<'_>>::TX) -> T, 135 | { 136 | let tx = db.tx()?; 137 | let res = f(&tx); 138 | tx.commit()?; 139 | 140 | Ok(res) 141 | } 142 | 143 | /// Opens up an existing database at the specified path. 144 | pub fn init_db + Debug>(path: P) -> eyre::Result> { 145 | let _ = std::fs::create_dir_all(path.as_ref()); 146 | let db = reth_db::mdbx::Env::::open( 147 | path.as_ref(), 148 | reth_db::mdbx::EnvKind::RO, 149 | None, 150 | )?; 151 | 152 | view(&db, |tx| { 153 | for table in tables::Tables::ALL.iter().map(|table| table.name()) { 154 | tx.inner.open_db(Some(table)).wrap_err("Could not open db.").unwrap(); 155 | } 156 | })?; 157 | 158 | Ok(db) 159 | } 160 | -------------------------------------------------------------------------------- /crates/poirot-core/src/main.rs: -------------------------------------------------------------------------------- 1 | use ethers::prelude::k256::elliptic_curve::rand_core::block; 2 | use poirot_core::{parser::Parser, TracingClient}; 3 | 4 | use poirot_core::action::ActionType; 5 | use std::{env, error::Error, path::Path}; 6 | 7 | // reth types 8 | use reth_rpc_types::trace::parity::TraceType; 9 | use std::collections::HashSet; 10 | 11 | use reth_primitives::{BlockId, BlockNumberOrTag}; 12 | use tracing::Subscriber; 13 | use tracing_subscriber::{ 14 | filter::Directive, prelude::*, registry::LookupSpan, EnvFilter, Layer, Registry, 15 | }; 16 | // reth types 17 | use reth_primitives::{BlockNumHash, H256}; 18 | use reth_rpc_types::trace::geth::GethDebugTracingOptions; 19 | // alloy 20 | use alloy_json_abi::*; 21 | use std::str::FromStr; 22 | 23 | fn main() { 24 | let _ = tracing_subscriber::fmt() 25 | .with_env_filter(EnvFilter::from_default_env()) 26 | .with_writer(std::io::stderr) 27 | .try_init(); 28 | 29 | // Create the runtime 30 | let runtime = tokio_runtime().expect("Failed to create runtime"); 31 | 32 | let current_dir = env::current_dir().expect("Failed to get current directory"); 33 | let abi_dir = current_dir.parent().expect("Failed to get parent directory").join("abi"); 34 | 35 | /*match load_all_jsonabis("abi") { 36 | Ok(abis) => { 37 | for abi in abis { 38 | println!("Successfully loaded ABI"); 39 | } 40 | } 41 | Err(e) => eprintln!("Failed to load ABIs: {}", e) 42 | } */ 43 | 44 | // Use the runtime to execute the async function 45 | match runtime.block_on(run(runtime.handle().clone())) { 46 | Ok(()) => println!("Success!"), 47 | Err(e) => { 48 | eprintln!("Error: {:?}", e); 49 | 50 | let mut source: Option<&dyn Error> = e.source(); 51 | while let Some(err) = source { 52 | eprintln!("Caused by: {:?}", err); 53 | source = err.source(); 54 | } 55 | } 56 | } 57 | } 58 | 59 | pub fn tokio_runtime() -> Result { 60 | tokio::runtime::Builder::new_multi_thread() 61 | .enable_all() 62 | // increase stack size, mostly for RPC calls that use the evm: and 63 | .thread_stack_size(8 * 1024 * 1024) 64 | .build() 65 | } 66 | 67 | async fn run(handle: tokio::runtime::Handle) -> Result<(), Box> { 68 | // Read environment variables 69 | let db_path = match env::var("DB_PATH") { 70 | Ok(path) => path, 71 | Err(_) => return Err(Box::new(std::env::VarError::NotPresent)), 72 | }; 73 | 74 | let db_path = Path::new(&db_path); 75 | 76 | // Initialize TracingClient 77 | let tracer = TracingClient::new(db_path, handle); 78 | 79 | // Test 80 | test(&tracer).await?; 81 | 82 | /* 83 | 84 | let parity_trace = 85 | tracer.reth_trace.trace_block(BlockId::Number(BlockNumberOrTag::Latest)).await?; 86 | 87 | let parser = Parser::new(parity_trace.unwrap()); 88 | 89 | for i in parser.parse() { 90 | match i.ty { 91 | // ActionType::Transfer(_) => println!("{i:#?}"), 92 | ActionType::Swap(_) => println!("{i:#?}"), 93 | _ => continue, 94 | } 95 | } 96 | 97 | // Print traces 98 | */ 99 | 100 | Ok(()) 101 | } 102 | 103 | async fn test(tracer: &TracingClient) -> Result<(), Box> { 104 | let tx_hash_str = "0x6f4c57c271b9054dbe31833d20138b15838e1482384c0cd6b1be44db54805bce"; 105 | let tx_hash = H256::from_str(tx_hash_str)?; 106 | 107 | let trace_types: HashSet = 108 | vec![TraceType::Trace, TraceType::VmTrace, TraceType::StateDiff].into_iter().collect(); 109 | 110 | let trace_results = tracer.reth_trace.replay_transaction(tx_hash, trace_types).await?; 111 | 112 | // Print traces 113 | trace_results 114 | .trace 115 | .as_ref() 116 | .map(|trace| { 117 | trace.iter().for_each(|t| println!("{:#?}", t)); 118 | }) 119 | .unwrap_or_else(|| println!("No regular trace found for transaction.")); 120 | 121 | trace_results 122 | .vm_trace 123 | .as_ref() 124 | .map(|vm_trace| { 125 | println!("{:#?}", vm_trace); 126 | }) 127 | .unwrap_or_else(|| println!("No VM trace found for transaction.")); 128 | 129 | trace_results 130 | .state_diff 131 | .as_ref() 132 | .map(|state_diff| { 133 | println!("{:#?}", state_diff); 134 | }) 135 | .unwrap_or_else(|| println!("No state diff found for transaction.")); 136 | 137 | Ok(()) 138 | } 139 | -------------------------------------------------------------------------------- /crates/poirot-core/src/parser.rs: -------------------------------------------------------------------------------- 1 | use crate::action::{Action, ActionType, Deposit, PoolCreation, Swap, Transfer, Withdrawal}; 2 | 3 | use reth_rpc_types::trace::parity::{Action as RethAction, LocalizedTransactionTrace}; 4 | 5 | use alloy_sol_types::{sol, SolCall, SolInterface}; 6 | 7 | sol! { 8 | #[derive(Debug, PartialEq)] 9 | interface IERC20 { 10 | function transfer(address to, uint256 amount) external returns (bool); 11 | function transferFrom(address from, address to, uint256 amount) external returns (bool); 12 | } 13 | } 14 | 15 | sol! { 16 | #[derive(Debug, PartialEq)] 17 | interface IUniswapV3Factory { 18 | function createPool(address tokenA, address tokenB, uint24 fee) external returns (address); 19 | function getPool( 20 | address tokenA, 21 | address tokenB, 22 | uint24 fee 23 | ) external view returns (address pool); 24 | function feeAmountTickSpacing(uint24 fee) external view returns (int24); 25 | function setOwner(address _owner) external; 26 | function enableFeeAmount(uint24 fee, int24 tickSpacing) external; 27 | 28 | } 29 | } 30 | 31 | sol! { 32 | interface IUniswapV3FlashCallback { 33 | function uniswapV3FlashCallback( 34 | uint256 fee0, 35 | uint256 fee1, 36 | bytes calldata data 37 | ) external; 38 | } 39 | } 40 | 41 | sol! { 42 | interface IUniswapV3MintCallback { 43 | function uniswapV3MintCallback( 44 | uint256 amount0Owed, 45 | uint256 amount1Owed, 46 | bytes calldata data 47 | ) external; 48 | } 49 | } 50 | 51 | sol! { 52 | interface IUniswapV3SwapCallback { 53 | function uniswapV3SwapCallback( 54 | int256 amount0Delta, 55 | int256 amount1Delta, 56 | bytes calldata data 57 | ) external; 58 | } 59 | } 60 | 61 | sol! { 62 | #[derive(Debug, PartialEq)] 63 | interface IUniswapV3PoolDeployer { 64 | function parameters() 65 | external 66 | view 67 | returns ( 68 | address factory, 69 | address token0, 70 | address token1, 71 | uint24 fee, 72 | int24 tickSpacing 73 | ); 74 | } 75 | } 76 | 77 | sol! { 78 | #[derive(Debug, PartialEq)] 79 | interface WETH9 { 80 | function deposit() public payable; 81 | function withdraw(uint wad) public; 82 | } 83 | } 84 | 85 | sol! { 86 | #[derive(Debug, PartialEq)] 87 | interface IUniswapV3Pool { 88 | function swap(address recipient, bool zeroForOne, int256 amountSpecified, uint160 sqrtPriceLimitX96, bytes data) external override returns (int256, int256); 89 | function mint( 90 | address recipient, 91 | int24 tickLower, 92 | int24 tickUpper, 93 | uint128 amount, 94 | bytes calldata data 95 | ) external returns (uint256 amount0, uint256 amount1); 96 | function collect( 97 | address recipient, 98 | int24 tickLower, 99 | int24 tickUpper, 100 | uint128 amount0Requested, 101 | uint128 amount1Requested 102 | ) external returns (uint128 amount0, uint128 amount1); 103 | function burn( 104 | int24 tickLower, 105 | int24 tickUpper, 106 | uint128 amount 107 | ) external returns (uint256 amount0, uint256 amount1); 108 | function flash( 109 | address recipient, 110 | uint256 amount0, 111 | uint256 amount1, 112 | bytes calldata data 113 | ) external; 114 | } 115 | } 116 | 117 | pub struct Parser { 118 | block_trace: Vec, 119 | } 120 | //TODO: Instead of directly going from trace to action we should have an intermediatary filter step 121 | //TODO: This step could be used to filter known contract interactions & directly match on the 122 | // appropriate decoder instead of naively looping thorugh all of them 123 | 124 | impl Parser { 125 | pub fn new(block_trace: Vec) -> Self { 126 | Self { block_trace } 127 | } 128 | 129 | pub fn parse(&self) -> Vec { 130 | let mut actions = vec![]; 131 | 132 | for i in self.block_trace.clone() { 133 | let parsed = self.parse_trace(&i); 134 | 135 | if parsed.is_some() { 136 | actions.push(parsed.unwrap()); 137 | } else { 138 | actions.push(Action { 139 | ty: ActionType::Unclassified(i.clone()), 140 | hash: i.clone().transaction_hash.unwrap(), 141 | block: i.clone().block_number.unwrap(), 142 | }); 143 | } 144 | } 145 | 146 | actions 147 | } 148 | 149 | //TODO: Note, because a transaction can be a swap -> transfer -> transfer we would have to 150 | // avoid double counting the transfer & essentially create a higher TODO: level swap action 151 | // that contains its subsequent transfers 152 | /// Parse a single transaction trace. 153 | pub fn parse_trace(&self, curr: &LocalizedTransactionTrace) -> Option { 154 | self.parse_transfer(curr) 155 | .or_else(|| self.parse_pool_creation(curr)) 156 | .or_else(|| self.parse_weth(curr)) 157 | .or_else(|| self.parse_swap(curr)) 158 | } 159 | // TODO: So here we kind of have to create a type for each contract abi, and we can automate 160 | // that by using the alloy json abi, so we can just decode them for any function, & then 161 | // after that we sort for actions of interest, so even if we don't care about it for mev we 162 | // still know wtf is going on so we can eaisly see what we are missing that could 163 | // potentially be interesting because we have our db that is going to have all contract 164 | // address so we want to do something where we are automating new integrations 165 | // & can do something like this collect contract addr classified by (type, so like dex etc..) -> 166 | // abi -> function -> action type -> mev filtering if of interest So you are basically 167 | // filtering by contract address, then match to a pool of function selectors, then match to 168 | // decode which has corresponding action type From this we then inspect for mev by filtering 169 | // through all the actions of interest 170 | 171 | pub fn parse_swap(&self, curr: &LocalizedTransactionTrace) -> Option { 172 | match &curr.trace.action { 173 | RethAction::Call(call) => { 174 | let mut decoded = match IUniswapV3Pool::swapCall::decode(&call.input.to_vec(), true) 175 | { 176 | Ok(decoded) => decoded, 177 | Err(_) => return None, 178 | }; 179 | 180 | return Some(Action { 181 | ty: ActionType::Swap(Swap { 182 | recipient: decoded.recipient, 183 | direction: decoded.zeroForOne, 184 | amount_specified: decoded.amountSpecified, 185 | price_limit: decoded.sqrtPriceLimitX96, 186 | data: decoded.data, 187 | }), 188 | hash: curr.transaction_hash.unwrap(), 189 | block: curr.block_number.unwrap(), 190 | }) 191 | } 192 | _ => None, 193 | } 194 | } 195 | 196 | pub fn parse_weth(&self, curr: &LocalizedTransactionTrace) -> Option { 197 | match &curr.trace.action { 198 | RethAction::Call(call) => { 199 | let mut decoded = match WETH9::WETH9Calls::decode(&call.input.to_vec(), true) { 200 | Ok(decoded) => decoded, 201 | Err(_) => return None, 202 | }; 203 | 204 | match decoded { 205 | WETH9::WETH9Calls::deposit(deposit_call) => { 206 | return Some(Action { 207 | ty: ActionType::WethDeposit(Deposit::new(call.from, call.value)), 208 | hash: curr.transaction_hash.unwrap(), 209 | block: curr.block_number.unwrap(), 210 | }) 211 | } 212 | WETH9::WETH9Calls::withdraw(withdraw_call) => { 213 | return Some(Action { 214 | ty: ActionType::WethWithdraw(Withdrawal::new( 215 | call.from, 216 | withdraw_call.wad, 217 | )), 218 | hash: curr.transaction_hash.unwrap(), 219 | block: curr.block_number.unwrap(), 220 | }) 221 | } 222 | _ => return None, 223 | } 224 | } 225 | _ => None, 226 | } 227 | } 228 | 229 | pub fn parse_transfer(&self, curr: &LocalizedTransactionTrace) -> Option { 230 | match &curr.trace.action { 231 | RethAction::Call(call) => { 232 | let mut decoded = match IERC20::IERC20Calls::decode(&call.input.to_vec(), true) { 233 | Ok(decoded) => decoded, 234 | Err(_) => return None, 235 | }; 236 | 237 | match decoded { 238 | IERC20::IERC20Calls::transfer(transfer_call) => { 239 | return Some(Action { 240 | ty: ActionType::Transfer(Transfer::new( 241 | transfer_call.to, 242 | transfer_call.amount.into(), 243 | call.to, 244 | )), 245 | hash: curr.transaction_hash.unwrap(), 246 | block: curr.block_number.unwrap(), 247 | }) 248 | } 249 | IERC20::IERC20Calls::transferFrom(transfer_from_call) => { 250 | return Some(Action { 251 | ty: ActionType::Transfer(Transfer::new( 252 | transfer_from_call.to, 253 | transfer_from_call.amount.into(), 254 | call.to, 255 | )), 256 | hash: curr.transaction_hash.unwrap(), 257 | block: curr.block_number.unwrap(), 258 | }) 259 | } 260 | _ => return None, 261 | } 262 | } 263 | _ => None, 264 | } 265 | } 266 | 267 | pub fn parse_pool_creation(&self, curr: &LocalizedTransactionTrace) -> Option { 268 | match &curr.trace.action { 269 | RethAction::Call(call) => { 270 | let mut decoded = 271 | match IUniswapV3Factory::createPoolCall::decode(&call.input.to_vec(), true) { 272 | Ok(decoded) => decoded, 273 | Err(_) => return None, 274 | }; 275 | 276 | return Some(Action { 277 | ty: ActionType::PoolCreation(PoolCreation::new( 278 | decoded.tokenA, 279 | decoded.tokenB, 280 | decoded.fee, 281 | )), 282 | hash: curr.transaction_hash.unwrap(), 283 | block: curr.block_number.unwrap(), 284 | }) 285 | } 286 | _ => None, 287 | } 288 | } 289 | } 290 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | reorder_imports = true 2 | imports_granularity = "Crate" 3 | use_small_heuristics = "Max" 4 | comment_width = 100 5 | wrap_comments = true 6 | binop_separator = "Back" 7 | trailing_comma = "Vertical" 8 | trailing_semicolon = false 9 | use_field_init_shorthand = true --------------------------------------------------------------------------------