Skip to content

Commit 9c1654c

Browse files
authored
Initialize README.md for evmdecode project
Add README for evmdecode project with features, usage, and architecture details.
1 parent eeeebd8 commit 9c1654c

1 file changed

Lines changed: 234 additions & 0 deletions

File tree

README.md

Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
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

Comments
 (0)