1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
//! The in-process simulation engine — the per-block shared EVM handle.
//!
//! ADR-019 D4/D7 (decision R — Rust-canonical + strategy/engine separation):
//! this module owns the **engine** — the revm EVM handle (`BlockSimHandle`),
//! the layered DB stack (`BlockEvm` / `ProductionBlockDb`), the
//! `ArcDynProviderEthereum` provider newtype, and the block-env wiring + the
//! state-override application. The **strategy** (the 7-call pre/post-balance
//! bundle, `compute_priority_fee`, `decode_balance`, `SimResult`,
//! `dispatch_profitable_results`, `SimulateContext`, `SimulatePath`,
//! `FailBuckets`, the calldata builders) relocated to the
//! `degenbot-arbitrage` crate, which drives the borrowed `&mut evm`
//! the engine exposes via [`BlockSimHandle::evm_mut`].
//!
//! The engine stays generic + thin: it never names `SimulateContext` (a
//! strategy type) — [`BlockSimHandle::build`] takes the block-env primitives
//! (`provider`, `base_fee_next`, `current_block`, `block_timestamp`) + a
//! projected `&SimulationOverrideParams` directly. Multiple searcher
//! strategies (the settlement-arbitrage bundle today; sandwich/JIT-L/liquidation later) can
//! drive the same engine with their own `SimulateContext`-equivalent config.
//!
//! # Per-block shared-EVM handle (Tier 1, `V5HCR5`)
//!
//! Retires the per-path `simulate_in_process` (which rebuilt the full
//! `CacheDB`+EVM stack per call). The per-block handle is built ONCE per block
//! (by the strategy's `dispatch_profitable_results`) and shared (as `&mut`)
//! across the serial fan-out. The measured shape: the trigger path pays the
//! cold RPCs, the fan-out hits the warmed `CacheDB` at ~1 µs p50 (~50× faster
//! than the per-path fresh-`CacheDB` config A — benchmark
//! `examples/rpc_cache_fanout.rs`).
//!
//! # Correctness — shared `CacheDB` does NOT leak execute() SSTOREs
//!
//! revm 41 splits journalling into `transact_one` (accumulate to the journal) →
//! `finalize` (return the `State` + CLEAR the journal; does NOT commit to the
//! DB) → `commit` (write a `State` to the DB). The strategy's
//! `simulate_path_on_evm` calls `transact_one` + `finalize` only — it NEVER
//! calls `commit`. So execute()'s SSTOREs live in the per-path `State`
//! returned by `finalize` (then discarded), NOT in the shared `CacheDB`. The
//! `CacheDB` accumulates only READ caches (account info, bytecode, storage
//! slots read during balanceOf/execute), which is exactly the latency win +
//! never a stale-write hazard. (revm-handler-41/src/api.rs:44-110 is the
//! source of truth on this split.)
// Solidity/EVM identifiers (WETH9, PoolManager, ERC6909, cacheMD, databaseRef)
// are ubiquitous here — match the degenbot-simulation convention.
use BlockId;
use Ethereum;
use U256;
use ;
use SimAnchorState;
use RwLock;
use CacheDB;
use WrapDatabaseAsync;
use Arc;
use SimulationOverrideParams;
use ;
// ─────────────────────────────────────────────────────────────────────────
// The type-erased provider newtype (bridges `Arc<dyn Provider>` → `Provider`)
// ─────────────────────────────────────────────────────────────────────────
/// A `Provider<Ethereum>` newtype over `Arc<dyn Provider<Ethereum>>`, so the
/// type-erased provider from [`AlloyProvider::provider_arc`](degenbot_rpc::provider::AlloyProvider::provider_arc)
/// satisfies `P: Provider<Ethereum>` for [`revm::database::AlloyDB::new`].
///
/// Alloy's `Provider` trait carries `#[auto_impl::auto_impl(&, &mut, Rc, Arc,
/// Box)]`, but the generated impl is `impl<T: Provider + Sized> Provider for
/// Arc<T>` — it does **not** cover `?Sized` trait objects, so
/// `Arc<dyn Provider<Ethereum>>` itself does not satisfy `P: Provider`. This
/// newtype bridges the gap: it stores the `Arc<dyn Provider>` (always `Sized`)
/// and impls `Provider` by delegating `root()` — the one non-default `Provider`
/// method. Every other `Provider` method has a default impl routed through
/// `root()`.
;
// ─────────────────────────────────────────────────────────────────────────
// The per-block shared-EVM handle (Tier 1, task `V5HCR5`)
// ─────────────────────────────────────────────────────────────────────────
/// The production DB stack backing a per-block [`BlockEvm`] —
/// `CacheDB<WarmCodeCache<BotStateDb<WrapDatabaseAsync<AlloyDB<...>>>>>`.
/// The `WarmCodeCache` layer (cross-block bytecode + account-existence, per-
/// entry TTL'd) sits between the per-block `CacheDB` and the `BotStateDb`
/// storage-forwarding seam; the `AlloyDB` cold-miss fallback (RPC) sits at the
/// bottom (see [`super::warm_code_cache`]).
pub type ProductionBlockDb<'a> = ;
/// The concrete per-block EVM type held by [`BlockSimHandle`] — revm's
/// [`revm::MainnetEvm`] over the production [`ProductionBlockDb`] stack. The
/// inspector type parameter is [`super::inspectors::SimInspector`] (a nested
/// tuple `(AccessListCollector, (CallTraceInspector,
/// SwapEventCaptureInspector))` — ADR-019 D3 + ergo epic 63I7WJ) — baked in so
/// the strategy's `simulate_path_on_evm` can attach it to `execute()`'s
/// `inspect_one` run and drain the access list + call trace + swap events.
/// revm's blanket `Inspector` impl covers 2-tuples only, so the three-way
/// composition is nested (`AccessListCollector` is `L`, the
/// `CallTraceInspector`/`SwapEventCaptureInspector` pair is `R`).
pub type BlockEvm<'a> = MainnetEvm;
/// Owns a per-block shared EVM (one `CacheDB` + revm `Context`, state overrides
/// applied once) for the in-process sim fan-out (Tier 1, `V5HCR5`).
///
/// Built ONCE per block by [`BlockSimHandle::build`] (the strategy's
/// `dispatch_profitable_results` calls it), then each candidate path is
/// simulated SERIALLY by the strategy's `simulate_path_on_evm` over the shared
/// `&mut evm` exposed by [`BlockSimHandle::evm_mut`]. Per-path isolation is
/// revm's `finalize()` (clears the journal between paths — execute()'s
/// SSTOREs live in the per-path `State` returned by `finalize`, never
/// committed to the shared `CacheDB`; see the module-level note above). The
/// `CacheDB` + EVM drop naturally at end of block — no cross-block caching in
/// Tier 1, so memory is bounded by one block's working set.
///
/// Serial (not `buffer_unordered`) because a shared `&mut evm` can't be held
/// across `'static`+`Send` futures. The benchmark's config B (serial-warm,
/// ~1 µs p50) beats config A (parallel-cold, ~590 µs p50) by ~60× — losing
/// concurrency is a NET WIN because the per-path RPC cold-miss dwarfs the
/// per-path EVM execution.
///
/// The state overrides — owner ETH funding, executor code injection, warmup
/// slots, WETH balance — are all per-block-invariant, so applying them once at
/// [`BlockSimHandle::build`] is semantically identical to applying them
/// per-path.