|
| 1 | +# evmdecode |
| 2 | + |
| 3 | +> EVM transaction decoder, simulator, disassembler, and MEV analyzer — built in Rust. |
| 4 | +
|
| 5 | +A CLI tool that takes any Ethereum transaction hash or contract address and gives you a full breakdown — decoded ABI, gas analysis, bytecode disassembly, and MEV opportunity detection. Built with `alloy-rs`, `tokio`, and a clean multi-crate workspace architecture. |
| 6 | + |
| 7 | +--- |
| 8 | + |
| 9 | +## Features |
| 10 | + |
| 11 | +| Command | What it does | |
| 12 | +|---------|-------------| |
| 13 | +| `decode` | Resolve function selector via 4byte.directory + ABI decode all calldata params | |
| 14 | +| `simulate` | Fetch receipt, show event logs with topics, compute gas breakdown | |
| 15 | +| `disasm` | Full EVM bytecode disassembly with opcode categories, jump table, and function selectors | |
| 16 | +| `mev` | Detect Uniswap V2/V3 swaps, sandwich attacks, and arbitrage with confidence scores | |
| 17 | + |
| 18 | +--- |
| 19 | + |
| 20 | +## Demo |
| 21 | + |
| 22 | +### Decode a USDC transfer |
| 23 | +```bash |
| 24 | +$ evmdecode decode --tx 0x412f5f3c... --rpc $RPC_URL |
| 25 | + |
| 26 | +tx : 0x412f5f3c2f50993e7736150699c747ad5f682e20a610a0d1d5fa9d2fae466a4c |
| 27 | +from : 0x946Aa581287709B59dB1e635DAF3c35408C20DEf |
| 28 | +to : 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 ← USDC contract |
| 29 | +selector: 0xa9059cbb |
| 30 | +signature: transfer(address,uint256) |
| 31 | + |
| 32 | +Decoded Call |
| 33 | + selector : a9059cbb |
| 34 | + function : transfer(address,uint256) |
| 35 | ++---------+-----------------------------------------------------+ |
| 36 | +| type | value | |
| 37 | ++---------+-----------------------------------------------------+ |
| 38 | +| address | Address(0x4e5ae324d39935169cf35721b1fb31ed65d69974) | |
| 39 | +| uint256 | Uint(187208650578, 256) | ← 187,208 USDC |
| 40 | ++---------+-----------------------------------------------------+ |
| 41 | +``` |
| 42 | + |
| 43 | +### Simulate + gas breakdown |
| 44 | +```bash |
| 45 | +$ evmdecode simulate --tx 0x412f5f3c... --rpc $RPC_URL |
| 46 | + |
| 47 | +Receipt |
| 48 | + status : success |
| 49 | + gas used : 40372 |
| 50 | + |
| 51 | +Gas Breakdown |
| 52 | + total : 40372 |
| 53 | + ├ intrinsic : 21620 (53.6%) [21000 base + calldata] |
| 54 | + ├ calldata : 620 (1.5%) [39 zero × 4 + 29 nonzero × 16] |
| 55 | + ├ logs/store : 2000 (5.0%) [estimated] |
| 56 | + └ execution : 16752 (41.5%) [opcodes, memory] |
| 57 | +``` |
| 58 | + |
| 59 | +### Disassemble a contract |
| 60 | +```bash |
| 61 | +$ evmdecode disasm --address 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 \ |
| 62 | + --rpc $RPC_URL --decode-metadata --functions |
| 63 | + |
| 64 | +Bytecode: 2186 bytes |
| 65 | + |
| 66 | +Metadata |
| 67 | + total instructions : 844 |
| 68 | + jump destinations : 69 |
| 69 | + storage ops : 2 SLOAD / 3 SSTORE |
| 70 | + |
| 71 | +Opcode Categories |
| 72 | + Push : 211 Control : 137 |
| 73 | + Dup : 121 Swap : 72 |
| 74 | + Storage : 5 System : 14 |
| 75 | + |
| 76 | +Function Selectors (PUSH4) |
| 77 | + 0x3659cfe6 → upgradeTo(address) |
| 78 | + 0x4f1ef286 → upgradeToAndCall(address,bytes) |
| 79 | + 0x5c60da1b → implementation() |
| 80 | + 0x8f283970 → changeAdmin(address) |
| 81 | + 0xf851a440 → admin() |
| 82 | +``` |
| 83 | + |
| 84 | +### MEV analysis |
| 85 | +```bash |
| 86 | +$ evmdecode mev --tx 0x42f750... --rpc $RPC_URL |
| 87 | + |
| 88 | +MEV Analysis |
| 89 | + |
| 90 | + Swap [98%] |
| 91 | + confidence : ███████████████████░ |
| 92 | + detail : Uniswap V2 swap across 1 pool(s) |
| 93 | +``` |
| 94 | + |
| 95 | +--- |
| 96 | + |
| 97 | +## Installation |
| 98 | + |
| 99 | +### Prerequisites |
| 100 | +- Rust 1.75+ (`rustup update stable`) |
| 101 | +- An Ethereum RPC endpoint ([Alchemy](https://www.alchemy.com), [Infura](https://infura.io), or local node) |
| 102 | + |
| 103 | +### Build from source |
| 104 | +```bash |
| 105 | +git clone https://github.com/YOUR_USERNAME/evmdecode |
| 106 | +cd evmdecode |
| 107 | +cargo build --release |
| 108 | +``` |
| 109 | + |
| 110 | +The binary will be at `target/release/evmdecode`. |
| 111 | + |
| 112 | +--- |
| 113 | + |
| 114 | +## Usage |
| 115 | + |
| 116 | +```bash |
| 117 | +export RPC_URL="https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY" |
| 118 | + |
| 119 | +# Decode calldata from a tx hash |
| 120 | +evmdecode decode --tx <TX_HASH> --rpc $RPC_URL |
| 121 | + |
| 122 | +# Simulate + event logs + gas breakdown |
| 123 | +evmdecode simulate --tx <TX_HASH> --rpc $RPC_URL |
| 124 | + |
| 125 | +# Disassemble a contract by address |
| 126 | +evmdecode disasm --address <CONTRACT_ADDRESS> --rpc $RPC_URL \ |
| 127 | + --decode-metadata \ # show opcode category breakdown |
| 128 | + --functions \ # resolve PUSH4 selectors via 4byte.directory |
| 129 | + --jumptable \ # list all JUMPDEST offsets |
| 130 | + --storage-slots # list all SLOAD/SSTORE positions |
| 131 | + |
| 132 | +# Disassemble from a tx hash (fetches the contract at tx.to) |
| 133 | +evmdecode disasm --tx <TX_HASH> --rpc $RPC_URL --functions |
| 134 | + |
| 135 | +# Disassemble raw hex bytecode (no RPC needed) |
| 136 | +evmdecode disasm --hex 0x6080604052... --decode-metadata |
| 137 | + |
| 138 | +# Filter disassembly to specific opcodes |
| 139 | +evmdecode disasm --address <ADDR> --rpc $RPC_URL --filter JUMP |
| 140 | + |
| 141 | +# MEV analysis |
| 142 | +evmdecode mev --tx <TX_HASH> --rpc $RPC_URL |
| 143 | +``` |
| 144 | + |
| 145 | +--- |
| 146 | + |
| 147 | +## Architecture |
| 148 | + |
| 149 | +Eight-crate Rust workspace — each crate has one responsibility: |
| 150 | + |
| 151 | +``` |
| 152 | +evmdecode/ |
| 153 | +├── crates/ |
| 154 | +│ ├── core/ # shared types: RawTx, DecodedCall, StateDiff, GasReport, MevAlert |
| 155 | +│ ├── rpc/ # async RPC provider, tx fetcher, bytecode fetcher, trace caller |
| 156 | +│ ├── decoder/ # 4byte selector lookup, ABI decode via alloy::dyn_abi, disassembler |
| 157 | +│ ├── simulator/ # state diff parsing (debug_traceTransaction), eth_call replay |
| 158 | +│ ├── gas/ # intrinsic gas (EIP-2028), calldata cost, execution breakdown |
| 159 | +│ ├── mev/ # swap/sandwich/arb detectors, Uniswap V2/V3 log parsing |
| 160 | +│ ├── render/ # colored terminal output, ASCII tables via tabled |
| 161 | +│ └── cli/ # clap subcommands: decode, simulate, disasm, mev |
| 162 | +``` |
| 163 | + |
| 164 | +Dependency flow (no circular deps): |
| 165 | + |
| 166 | +``` |
| 167 | +cli → [rpc, decoder, simulator, gas, mev, render] → core |
| 168 | +``` |
| 169 | + |
| 170 | +--- |
| 171 | + |
| 172 | +## MEV Detection |
| 173 | + |
| 174 | +Three detectors run in sequence on every `mev` call: |
| 175 | + |
| 176 | +**Swap detector** — matches calldata selector against known Uniswap V2/V3 function signatures and checks if `tx.to` is a known router address. Confirmed by Swap event logs in the receipt. Confidence 50–98%. |
| 177 | + |
| 178 | +**Arbitrage detector** — looks for ≥2 unique pools in the same tx's Swap logs with a cyclic token flow (amount sent to one pool, received from another). Confidence 72–90% depending on pool count. |
| 179 | + |
| 180 | +**Sandwich detector** — fetches all txs in the same block, searches a ±5 tx window around the target for two txs from the same address that touch the same token. No `debug_` namespace required — uses `eth_getBlockByNumber` + `eth_getTransactionReceipt`. Confidence 60–92% based on adjacency. |
| 181 | + |
| 182 | +Supported protocols: |
| 183 | + |
| 184 | +| Protocol | Detection method | |
| 185 | +|----------|-----------------| |
| 186 | +| Uniswap V2 | Selector + Swap event topic | |
| 187 | +| Uniswap V3 | Selector + Swap event topic | |
| 188 | +| Uniswap Universal Router | Address match | |
| 189 | +| 1inch V5/V6 | Address match | |
| 190 | +| Paraswap | Address match | |
| 191 | + |
| 192 | +--- |
| 193 | + |
| 194 | +## RPC Requirements |
| 195 | + |
| 196 | +| Command | Required RPC methods | |
| 197 | +|---------|---------------------| |
| 198 | +| `decode` | `eth_getTransactionByHash` | |
| 199 | +| `simulate` | `eth_getTransactionByHash`, `eth_getTransactionReceipt` | |
| 200 | +| `simulate` (full state diff) | `debug_traceTransaction` — needs Alchemy Growth or archive node | |
| 201 | +| `disasm` | `eth_getCode` | |
| 202 | +| `mev` | `eth_getTransactionByHash`, `eth_getTransactionReceipt`, `eth_getBlockByNumber` | |
| 203 | + |
| 204 | +Free-tier Alchemy keys work for all commands except full state diff. |
| 205 | + |
| 206 | +--- |
| 207 | + |
| 208 | +## Tech Stack |
| 209 | + |
| 210 | +| Crate | Purpose | |
| 211 | +|-------|---------| |
| 212 | +| [`alloy`](https://alloy.rs) | Ethereum provider, ABI encoding, primitive types | |
| 213 | +| [`tokio`](https://tokio.rs) | Async runtime | |
| 214 | +| [`clap`](https://docs.rs/clap) | CLI argument parsing | |
| 215 | +| [`reqwest`](https://docs.rs/reqwest) | HTTP client for RPC + 4byte.directory | |
| 216 | +| [`tabled`](https://docs.rs/tabled) | ASCII table rendering | |
| 217 | +| [`colored`](https://docs.rs/colored) | Terminal color output | |
| 218 | +| [`serde`](https://serde.rs) | JSON serialization | |
| 219 | + |
| 220 | +--- |
| 221 | + |
| 222 | +## What I Learned Building This |
| 223 | + |
| 224 | +- **alloy-rs vs ethers-rs** — alloy 0.3 uses concrete types (`RootProvider<Http<Client>>`) rather than trait objects for providers; `impl Provider` doesn't work as a return type due to type parameter ambiguity |
| 225 | +- **EVM gas model** — intrinsic gas is 21,000 base + 4 per zero calldata byte + 16 per non-zero byte (EIP-2028); storage writes (SSTORE) dominate execution cost |
| 226 | +- **4byte.directory** — selector collisions are common; the oldest registration (`ordering=id ASC`) is canonical; a hardcoded table of the top ~15 selectors prevents most false positives |
| 227 | +- **Proxy patterns** — the USDC contract is an EIP-1967 transparent proxy; the implementation address lives at storage slot `keccak256("eip1967.proxy.implementation") - 1` |
| 228 | +- **MEV mechanics** — sandwich detection doesn't require archive node access; `eth_getBlockByNumber` + transfer log comparison is sufficient to identify same-attacker front/back-run pairs |
| 229 | + |
| 230 | +--- |
| 231 | + |
| 232 | +## License |
| 233 | + |
| 234 | +MIT |
0 commit comments