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
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
//! Value/behavioral tests that close surviving mutants in `crates/mega-evm/src/block/executor.rs`
//! and `crates/mega-evm/src/external/`.
//!
//! The `src/**` modules carry inline `#[cfg(test)] mod tests`, but those unit tests are not
//! exercised by the mutation run (which builds the integration test targets). These integration
//! tests re-assert the same invariants through the crate's public / `test-utils`-gated API.
//!
//! Covered survivors:
//! * `block/executor.rs:70` — `Debug::fmt` replaced with `Ok(Default::default())`. The real impl
//! writes the struct name; the mutant writes nothing. We pin the formatted output.
//! * `block/executor.rs:297` — `post_execution_changes -> Ok(vec![])`. The real body *always*
//! pushes exactly one `PostBlock(BalanceIncrements)` outcome (because
//! `eips::transact_balance_increments` unconditionally returns `Some(state)`), so a fresh
//! executor's `post_execution_changes()` returns a length-1 vec. The mutant returns length 0.
//! * `block/executor.rs:496` — `get_accessed_bucket_ids -> vec![]` and `->
//! vec![Default::default()]` (i.e. `vec![0]`). After a transaction that performs an `SSTORE`
//! (zero→non-zero), the dynamic storage gas tracker records the slot's bucket id. With
//! `TestExternalEnvs`'s default `SimpleBucketHasher`, that id lies in `[NUM_META_BUCKETS,
//! NUM_BUCKETS)` (≥ `65_536`), so the returned set is non-empty and never `{0}` — killing both
//! variants.
//! * `external/hasher/fallback.rs:{109,112,120}` — value-changing mutations in
//! `DeterministicHasher::write`. `AHashBucketHasher` is the public, production-compatible hasher
//! whose contract is to reproduce SPECIFIC bucket ids matching the `salt` crate. We pin known
//! input→bucket-id pairs, one per affected `write` branch, against the documented expected values
//! (the same constants cross-checked against `salt` in `src/external/hasher/mod.rs`). The `125:47
//! |`→`&` mutant is also killed by `ahash_bucket_id_small_input_is_pinned` (the small-input
//! combine), but the `125:47 |`→`^` mutant is provably equivalent and is recorded in
//! `mutants/suppressions.toml`: at line 125 the `|` ORs `value[0] as u128` (occupies only bits
//! 0..63) with `(value[1] as u128) << 64` (occupies only bits 64..127); the operands have
//! disjoint set bits for every input, so `| == ^` identically and no input distinguishes them.
//! * `external/hasher/fallback.rs:{108,112}` — the `>`→`>=` length-routing boundary mutants in
//! `write`. The full run hid these because no pinned input had a length sitting exactly on a
//! boundary; we add `len == {8,32}` inputs whose bucket id flips when the comparison does
//! (`ahash_bucket_id_len{8,32}_*`). The sibling line-109 `> 16`→`>= 16` is provably equivalent (a
//! len-16 input hashes identically either way — see the suppression note below) and is recorded
//! in `mutants/suppressions.toml`.
//!
//! The remaining `fallback.rs` survivors are recorded in `mutants/suppressions.toml` as
//! dead/unreachable code rather than killed here: the typed `write_u8/u16/u32/u64/u128 -> ()`
//! stubs and the two `^`→`|`/`&` mutants inside `update`. `AHashBucketHasher::bucket_id` drives
//! only the slice `write` (→ `large_update`); it never invokes the typed `write_uN` methods nor
//! `update`. Those are reachable solely via `core::hash::Hasher` trait dispatch on the
//! `pub(crate)`, non-re-exported `DeterministicHasher`, which no integration test (separate crate)
//! can construct — and no non-test caller anywhere in `mega-evm` invokes them. So no public-API
//! input distinguishes the mutants.
//!
//! `external/mod.rs:94` (`EmptyExternalEnv::external_envs` →
//! `ExternalEnvs::from(Default::default())`) is provably equivalent and is recorded in
//! `mutants/suppressions.toml` instead of being killed here: `EmptyExternalEnv` is a unit struct,
//! so both the real body (`ExternalEnvs { salt_env: EmptyExternalEnv, oracle_env: EmptyExternalEnv
//! }`) and the mutant (`From<ExternalEnvs<EmptyExternalEnv>>` of the `Default` impl, which yields
//! the identical fields) construct the same value; no observable behavior distinguishes them.
use Infallible;
use ;
use ;
use ForkCondition;
use OpAlloyReceiptBuilder;
use ;
use ;
use ;
const CALLER: Address = address!;
const STORAGE_CONTRACT: Address = address!;
/// Lower bound of the valid SALT bucket range (`NUM_BUCKETS / MIN_BUCKET_SIZE`, i.e. 256^3 / 256).
/// Both `SimpleBucketHasher` and `AHashBucketHasher` map keys into `[NUM_META_BUCKETS,
/// NUM_BUCKETS)`.
const NUM_META_BUCKETS: u32 = / 256; // 65_536
/// Wires a `MegaBlockExecutor` over `state` at the `MiniRex` spec, with a `TestExternalEnvs` salt
/// environment (default `SimpleBucketHasher`). Mirrors
/// `tests/block_executor/accessed_block_hashes`.
// ============================================================================
// block/executor.rs:70 — Debug::fmt
// ============================================================================
/// `Debug` for `MegaBlockExecutor` must write the struct name. The mutant returns `Ok(())`
/// without writing anything, producing an empty string.
// ============================================================================
// block/executor.rs:297 — post_execution_changes
// ============================================================================
/// `post_execution_changes` always produces exactly one `PostBlock(BalanceIncrements)` outcome,
/// because `eips::transact_balance_increments` unconditionally returns `Some(state)`. The mutant
/// drops it to an empty vec.
// ============================================================================
// block/executor.rs:496 — get_accessed_bucket_ids
// ============================================================================
/// A contract that writes a non-zero value to a previously-zero storage slot, then stops.
/// The `SSTORE` triggers the dynamic SALT gas hook, recording the slot's bucket id.
/// After a transaction performs an `SSTORE`, `get_accessed_bucket_ids` returns the non-empty set
/// of buckets touched. With the default `SimpleBucketHasher` every recorded id is in
/// `[NUM_META_BUCKETS, NUM_BUCKETS)` (≥ `65_536`), so the result is neither empty (kills `vec![]`)
/// nor `{0}` (kills `vec![Default::default()]`).
// ============================================================================
// external/hasher/fallback.rs:{109,112,120,125} — DeterministicHasher::write branches
// ============================================================================
//
// `AHashBucketHasher::bucket_id(key)` = `hash(key) % NUM_KV_BUCKETS + NUM_META_BUCKETS`, where
// `hash` is the deterministic AHash fallback. Its `write` routes by input length:
//
// * len <= 8 -> small branch; line 125 `|` combine
// * 9 <= len <= 16 -> mid branch; line 120 `|` combine, and the line-109 `> 16` outer guard
// (false here) selects this branch
// * len >= 17 -> large branch; the line-109 `> 16` outer guard (true) and the line-112
// `while data.len() > 16` loop guard both gate the 16-byte block reads
//
// The four mutations (`>`→`==` on 109/112, `|`→`&` on 120/125) all change the produced hash for an
// input that hits their branch, hence change the bucket id. We pin one input per branch against the
// expected unmutated value. Expected hashes are the documented cross-checked values from
// `src/external/hasher/mod.rs` (`hash("hello") = 1027176506268606463`, etc.); expected bucket ids
// for the >=17-byte inputs are the documented `bucket_id` cross-check values from that same module.
/// `NUM_KV_BUCKETS = NUM_BUCKETS - NUM_META_BUCKETS = 16_777_216 - 65_536`.
const NUM_KV_BUCKETS: u64 = - ;
const NUM_META_BUCKETS_U64: u64 = / 256;
/// Maps a documented `hash(key)` value to the expected bucket id.
const
/// `len <= 8` (small branch, line 125): `"hello"` is 5 bytes.
/// `hash("hello") = 1_027_176_506_268_606_463` (documented in `external/hasher/mod.rs`).
/// `9 <= len <= 16` (mid branch, lines 120 and 109): `"hash test"` is 9 bytes.
/// `hash("hash test") = 2_116_618_212_096_523_432` (documented in `external/hasher/mod.rs`).
/// A `> 16`→`== 16` mutant on line 109 would route a 9-byte input identically, but a `>`→`==`
/// at line 109 also misroutes the boundary len 16 and all len >= 17 — covered by the large test —
/// while the `|`->`&` mutant on line 120 directly changes this 9-byte result.
/// `len >= 17` (large branch, lines 109 + 112).
///
/// `[0u8; 20]` -> bucket id `12_666_336`; the 52-byte address+slot key -> `9_450_146`; the 20-byte
/// `deadbeef` address -> `12_196_828`. All three are documented cross-check values from
/// `external/hasher/mod.rs`. At these lengths the loop `while data.len() > 16` runs at least once
/// (and three times for the 52-byte key); the `> 16`→`== 16` mutants on lines 109/112 skip or
/// misroute those reads, changing the hash.
// ============================================================================
// external/hasher/fallback.rs:{108,112} — length-routing boundary mutants
// ============================================================================
//
// `write` routes by input length with three `>` comparisons. The full run hid the
// `>`→`>=` boundary variants because no pinned input had a length sitting exactly on a
// boundary. Each test below uses an input whose length lands on the relevant boundary so
// the `>`→`>=` flip changes which branch (or how many loop iterations) executes, and thus
// the produced hash and bucket id:
//
// * line 108 `data.len() > 8` : at len == 8 the original takes the small branch (read_small);
// `>= 8` reroutes len-8 input into the mid branch (read_u64 front + read_last_u64 back), a
// different combine.
// * line 112 `while data.len() > 16` : with a len that is a multiple of 16 (== 32), the loop hits
// `data.len() == 16` mid-iteration. `>` stops there (1 pass); `>= 16` runs a second read_u128
// pass, changing the hash.
//
// The line-109 `> 16`→`>= 16` mutant is NOT here: it is provably equivalent (a len-16 input
// produces the identical single `large_update(le_u128(bytes[0..16]))` under both branches —
// the mid branch's `front | (back << 64)` reassembles the same little-endian u128 that the
// large branch's `read_last_u128` reads), so it is recorded in `mutants/suppressions.toml`.
//
// Expected bucket ids are the unmutated `AHashBucketHasher::bucket_id` outputs for these
// exact inputs (the production hasher's contract is to reproduce specific salt-compatible
// bucket ids), pinned the same way as the documented large-input cross-check values above.
/// `len == 8` boundary (line 108 `> 8`). The small branch (`read_small`) is taken; a `>= 8`
/// mutant misroutes this input into the mid branch.
/// `len == 32` boundary (line 112 `while data.len() > 16`). The loop reaches `data.len()
/// == 16` after one 16-byte read; `>` stops (one pass) while a `>= 16` mutant runs a second
/// `read_u128` pass, changing the hash.