mega-evm 1.6.0

The evm tailored for the MegaETH
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
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
#[cfg(not(feature = "std"))]
use alloc as std;
use alloy_primitives::{Address, U256};
use revm::{
    handler::{EthFrame, FrameResult},
    interpreter::{
        interpreter::EthInterpreter, interpreter_action::FrameInit, InterpreterAction, SStoreResult,
    },
};
use std::vec::Vec;

use crate::{constants, JournalInspectTr, MegaSpecId, MegaTransaction};

use super::{LimitCheck, LimitKind};

/// Per-frame metadata for trackers that need account update deduplication
/// (data size and KV update trackers).
///
/// All fields are private to this module: the
/// `push_call_frame` / `push_create_frame` / `pop_frame_unwind_parent` methods on
/// `FrameLimitTracker<CallFrameInfo>` own the invariant that wires `target_updated` and
/// `charged_parent_update` together. Callers should not touch these fields directly.
#[derive(Debug, Clone, Default)]
pub(crate) struct CallFrameInfo {
    /// The target address of the frame. `None` during CREATE until the address is known.
    target_address: Option<Address>,
    /// Whether this frame's target address has been marked as updated.
    target_updated: bool,
    /// True when this frame caused the parent's `target_updated` to be flipped to `true`
    /// (Rex5+ only). Used by `pop_frame_unwind_parent` to undo the parent flag on revert,
    /// so a subsequent successful call from the same parent still charges the parent account.
    charged_parent_update: bool,
}

#[derive(Debug, Clone)]
pub(crate) struct FrameLimitTracker<I> {
    /// Top-level (TX-scope) entry. Holds the TX limit and accumulates usage
    /// from pre-frame data and from the last frame pop.
    tx_entry: FrameLimitEntry<()>,
    /// Stack of child frame entries.
    frame_stack: Vec<FrameLimitEntry<I>>,
    /// Whether Rex4 (per-frame budgeting) is active. Used by the `CallFrameInfo`
    /// specialization to choose between `push_frame` (Rex4+) and `push_frame_with_limit`
    /// (`u64::MAX` for pre-Rex4) when constructing call/create/intercept frames.
    /// Other instantiations (`FrameLimitTracker<()>`) currently do not consult this flag.
    rex4_enabled: bool,
    /// Whether Rex5 (parent account update dedup) is active. Used by the `CallFrameInfo`
    /// specialization to gate the `target_updated` mutation in `push_call_frame` /
    /// `push_create_frame` and the matching unwind in `pop_frame_unwind_parent`.
    /// Other instantiations do not consult this flag.
    rex5_enabled: bool,

    /// Cached `Σ(persistent_usage + discardable_usage)` across `tx_entry` and every entry on
    /// `frame_stack`. Maintained incrementally so `net_usage()` is O(1) instead of O(depth)
    /// per opcode. See `net_usage()` for the invariant and `pop_frame` for the revert delta.
    cached_total_used: u64,
    /// Cached `Σ refund` across `tx_entry` and every entry on `frame_stack`. Maintained
    /// together with `cached_total_used` so `net_usage = saturating_sub(used, refund)` is
    /// a single subtraction.
    cached_total_refund: u64,
}

/// Per-frame budget entry on the frame stack.
#[derive(Debug, Clone)]
pub(crate) struct FrameLimitEntry<I> {
    /// Maximum usage allowed in this frame.
    pub(crate) limit: u64,

    // The three budget fields below MUST only be mutated through `FrameLimitTracker`'s
    // cache-aware helpers (`add_tx_persistent`, `add_frame_persistent`,
    // `add_frame_discardable`, `add_frame_refund`) or through `pop_frame`'s explicit
    // cache deltas. Writing them directly desyncs `cached_total_used` /
    // `cached_total_refund` and silently corrupts every subsequent `net_usage()` result.
    /// Persistent usage in this frame even if it is reverted.
    persistent_usage: u64,
    /// Discardable usage if this frame is reverted.
    discardable_usage: u64,
    /// Refund usage in this frame.
    refund: u64,

    /// Additional information about the frame.
    #[allow(dead_code)]
    pub(crate) info: I,
}

impl<I> FrameLimitEntry<I> {
    pub(crate) fn new(limit: u64, info: I) -> Self {
        Self { limit, persistent_usage: 0, discardable_usage: 0, refund: 0, info }
    }

    /// Returns the remaining budget for this frame.
    ///
    /// Computed as `limit - (used - refund)`, clamped to `[0, limit]`.
    /// The net usage (`used - refund`) is computed first to stay consistent with
    /// the exceed check in `exceeds_current_frame_limit`.
    #[inline]
    pub(crate) fn remaining(&self) -> u64 {
        self.limit.saturating_sub(self.used().saturating_sub(self.refund))
    }

    /// Returns usage for this frame.
    #[inline]
    pub(crate) fn used(&self) -> u64 {
        self.persistent_usage.checked_add(self.discardable_usage).expect("overflow")
    }
}

impl<I> FrameLimitTracker<I> {
    pub(crate) fn new(spec: MegaSpecId, tx_limit: u64) -> Self {
        Self {
            tx_entry: FrameLimitEntry::new(tx_limit, ()),
            frame_stack: Vec::new(),
            rex4_enabled: spec.is_enabled(MegaSpecId::REX4),
            rex5_enabled: spec.is_enabled(MegaSpecId::REX5),
            cached_total_used: 0,
            cached_total_refund: 0,
        }
    }

    /// Returns the TX-level limit.
    pub(crate) fn tx_limit(&self) -> u64 {
        self.tx_entry.limit
    }

    /// Resets the tracker for a new transaction.
    pub(crate) fn reset(&mut self) {
        self.tx_entry.persistent_usage = 0;
        self.tx_entry.discardable_usage = 0;
        self.tx_entry.refund = 0;
        self.frame_stack.clear();
        self.cached_total_used = 0;
        self.cached_total_refund = 0;
    }

    /// Returns the remaining budget of the current frame.
    ///
    /// If the frame stack is non-empty, returns the top frame's remaining budget.
    /// If the frame stack is empty (before the first frame is pushed), returns
    /// the TX-level remaining which accounts for pre-frame usage (e.g. intrinsic charges).
    pub(crate) fn current_frame_remaining(&self) -> u64 {
        match self.frame_stack.last() {
            Some(entry) => entry.remaining(),
            None => self.tx_entry.remaining(),
        }
    }

    /// Returns the maximum limit that can be forwarded to the next frame.
    ///
    /// - **Nested frame**: parent's remaining × 98/100.
    /// - **Top-level frame** (empty stack): `tx_entry.remaining()`, which accounts for pre-frame
    ///   intrinsic usage (e.g., calldata size, access lists) already charged into `tx_entry`.
    ///
    /// Individual trackers that need a different top-level budget should use
    /// `push_frame_with_limit()` instead of `push_frame()`.
    fn max_forward_limit(&self) -> u64 {
        match self.frame_stack.last() {
            Some(entry) => {
                let remaining = u128::from(entry.remaining());
                let numerator = u128::from(constants::rex4::FRAME_LIMIT_NUMERATOR);
                let denominator = u128::from(constants::rex4::FRAME_LIMIT_DENOMINATOR);
                ((remaining * numerator) / denominator) as u64
            }
            None => self.tx_entry.remaining(),
        }
    }

    pub(crate) fn push_frame(&mut self, info: I) {
        self.frame_stack.push(FrameLimitEntry::new(self.max_forward_limit(), info));
    }

    /// Pops the current frame from the stack and merges its usage into the parent.
    ///
    /// On success: `persistent_usage`, `discardable_usage`, and `refund` are all merged.
    /// On failure: only `persistent_usage` is merged; `discardable_usage` and `refund` are dropped.
    ///
    /// Cache invariant: every mutation to `persistent_usage` / `discardable_usage` / `refund`
    /// — whether via the public helpers or via this pop — keeps `cached_total_used` and
    /// `cached_total_refund` in sync. On revert the child's `discardable_usage` and `refund`
    /// are subtracted from the cache because they vanish (they are neither kept on the child
    /// nor merged into the parent).
    pub(crate) fn pop_frame(&mut self, success: bool) -> Option<FrameLimitEntry<I>> {
        let child = self.frame_stack.pop();
        if let Some(child) = &child {
            if let Some(parent) = self.frame_stack.last_mut() {
                // Persistent usage is always preserved: move from child slot to parent slot.
                // The cache already counts it once, so no cache update is needed here.
                parent.persistent_usage += child.persistent_usage;
                if success {
                    // Discardable & refund are preserved on success — same total, just relocated
                    // from child entry to parent entry. Cache stays the same.
                    parent.discardable_usage += child.discardable_usage;
                    parent.refund += child.refund;
                } else {
                    // Revert: child's discardable_usage and refund vanish entirely. Subtract them
                    // from the cache to maintain the `Σ(persistent + discardable) - Σ refund`
                    // invariant.
                    self.cached_total_used -= child.discardable_usage;
                    self.cached_total_refund -= child.refund;
                }
            } else {
                // Last frame popped — merge into tx_entry. Same cache reasoning as the parent
                // branch above.
                self.tx_entry.persistent_usage += child.persistent_usage;
                if success {
                    self.tx_entry.discardable_usage += child.discardable_usage;
                    self.tx_entry.refund += child.refund;
                } else {
                    self.cached_total_used -= child.discardable_usage;
                    self.cached_total_refund -= child.refund;
                }
            }
        }
        child
    }

    /// Returns whether the current frame has exceeded its frame-local limit.
    /// If exceeded, `frame_local` is always `true` since this checks per-frame budgets.
    pub(crate) fn exceeds_current_frame_limit(&self, kind: LimitKind) -> LimitCheck {
        match self.frame_stack.last() {
            Some(entry) if entry.used().saturating_sub(entry.refund) > entry.limit => {
                LimitCheck::ExceedsLimit {
                    kind,
                    limit: entry.limit,
                    used: entry.used(),
                    frame_local: true,
                }
            }
            _ => LimitCheck::WithinLimit,
        }
    }

    /// Returns a mutable reference to the current (top) frame entry.
    pub(crate) fn frame_mut(&mut self) -> Option<&mut FrameLimitEntry<I>> {
        self.frame_stack.last_mut()
    }

    /// Returns whether there is at least one active frame on the stack.
    pub(crate) fn has_active_frame(&self) -> bool {
        !self.frame_stack.is_empty()
    }

    /// Pushes a new frame with a custom limit, bypassing `max_forward_limit()`.
    ///
    /// Used by pre-Rex4 specs (`u64::MAX`, per-frame limits not enforced) and by any tracker that
    /// intentionally wants to bypass the default `max_forward_limit()` behavior.
    pub(crate) fn push_frame_with_limit(&mut self, limit: u64, info: I) {
        self.frame_stack.push(FrameLimitEntry::new(limit, info));
    }

    /// Returns the total net usage across `tx_entry` and all frames on the stack.
    /// Net usage = Σ(`persistent_usage` + `discardable_usage`) - Σ(refund), clamped to 0.
    ///
    /// O(1): served from `cached_total_used` / `cached_total_refund`, which are maintained
    /// incrementally by `add_tx_persistent`, `add_frame_persistent`, `add_frame_discardable`,
    /// `add_frame_refund`, and `pop_frame`.
    #[inline]
    pub(crate) fn net_usage(&self) -> u64 {
        let net_usage = self.cached_total_used.saturating_sub(self.cached_total_refund);
        #[cfg(debug_assertions)]
        assert_eq!(
            net_usage,
            self.net_usage_uncached(),
            "cached net_usage must match uncached reference"
        );
        net_usage
    }

    /// Reference implementation of `net_usage` that walks `tx_entry + frame_stack` directly.
    /// Active only in debug builds (gated by `cfg(debug_assertions)`), where it backs the
    /// per-call assertion inside `net_usage()` so the incremental cache is verified against
    /// the slow O(depth) walk on every opcode in tests and dev runs. Release builds
    /// (including `cargo bench` and production) compile this out entirely.
    #[cfg(any(debug_assertions, test))]
    fn net_usage_uncached(&self) -> u64 {
        let mut total_used: u64 = self.tx_entry.used();
        let mut total_refund: u64 = self.tx_entry.refund;
        for entry in &self.frame_stack {
            total_used += entry.used();
            total_refund += entry.refund;
        }
        total_used.saturating_sub(total_refund)
    }

    /// Adds `n` to `tx_entry.persistent_usage` and keeps the cache in sync.
    ///
    /// Used by trackers to record intrinsic / pre-frame usage (e.g. base TX size,
    /// EIP-7702 authority updates, caller account update). The current frame stack
    /// may be empty (pre-`frame_init`) or non-empty (`compute_gas`'s fallback when no
    /// frame exists).
    #[inline]
    pub(crate) fn add_tx_persistent(&mut self, n: u64) {
        self.tx_entry.persistent_usage += n;
        self.cached_total_used += n;
    }

    /// Adds `n` to the current top frame's `persistent_usage` and keeps the cache in sync.
    /// Returns `false` (and does nothing) when the frame stack is empty — callers that
    /// need a tx-level fallback must check the return value.
    #[inline]
    pub(crate) fn add_frame_persistent(&mut self, n: u64) -> bool {
        match self.frame_stack.last_mut() {
            Some(entry) => {
                entry.persistent_usage += n;
                self.cached_total_used += n;
                true
            }
            None => false,
        }
    }

    /// Adds `n` to the current top frame's `discardable_usage` and keeps the cache in sync.
    /// No-op when the frame stack is empty (discardable usage requires a frame to be
    /// discardable into; pre-frame usage is always persistent).
    #[inline]
    pub(crate) fn add_frame_discardable(&mut self, n: u64) {
        if let Some(entry) = self.frame_stack.last_mut() {
            entry.discardable_usage += n;
            self.cached_total_used += n;
        }
    }

    /// Adds `n` to the current top frame's `refund` and keeps the cache in sync.
    /// No-op when the frame stack is empty.
    #[inline]
    pub(crate) fn add_frame_refund(&mut self, n: u64) {
        if let Some(entry) = self.frame_stack.last_mut() {
            entry.refund += n;
            self.cached_total_refund += n;
        }
    }
}

impl FrameLimitTracker<CallFrameInfo> {
    /// Pushes a new `CallFrameInfo` frame using the per-spec budget choice:
    /// - **Rex4+**: `max_forward_limit()` (parent × 98/100, or `tx_entry.remaining()` at top
    ///   level).
    /// - **Pre-Rex4**: `u64::MAX` since per-frame limits are not enforced.
    fn push_call_frame_info(&mut self, info: CallFrameInfo) {
        if self.rex4_enabled {
            self.push_frame(info);
        } else {
            self.push_frame_with_limit(u64::MAX, info);
        }
    }

    /// Pushes a synthetic frame for inspector / access-control interception paths.
    ///
    /// No parent-flag mutation: an intercepted call is short-circuited before any
    /// state-modifying account update is recorded, so the parent does not need to be marked.
    /// The frame exists only to keep the per-tracker frame stack aligned with the EVM's call
    /// stack so that the matching `pop_frame_unwind_parent` finds an entry to pop.
    pub(crate) fn push_dummy_frame(&mut self) {
        self.push_call_frame_info(CallFrameInfo::default());
    }

    /// Pushes a CALL frame and updates the parent's dedup flag.
    ///
    /// Returns `true` when the parent's account info should be charged by the caller
    /// (i.e., the parent has not been marked updated yet *and* this call transfers value).
    /// Rex5+: the parent's `target_updated` flag is set so subsequent value-transferring
    /// calls from the same parent frame don't double-charge the caller account; the
    /// `charged_parent_update` flag is recorded on the new child so a revert can unwind it.
    /// Pre-Rex5: the parent flag is left untouched, preserving pre-Rex5 semantics.
    pub(crate) fn push_call_frame(&mut self, target: Address, has_transfer: bool) -> bool {
        let rex5_enabled = self.rex5_enabled;
        let parent_needs_update = has_transfer &&
            match self.frame_mut() {
                Some(entry) if !entry.info.target_updated => {
                    if rex5_enabled {
                        entry.info.target_updated = true;
                    }
                    true
                }
                _ => false,
            };
        let charged_parent_update = rex5_enabled && parent_needs_update;
        self.push_call_frame_info(CallFrameInfo {
            target_address: Some(target),
            target_updated: has_transfer,
            charged_parent_update,
        });
        parent_needs_update
    }

    /// Pushes a CREATE frame and updates the parent's dedup flag.
    ///
    /// Returns `true` when the parent's account info should be charged by the caller
    /// (i.e., the parent has not been marked updated yet — CREATE always increments the
    /// caller's nonce, so there is no `has_transfer` gate).
    /// The created address is unknown at push time; callers should fill it in via
    /// `set_created_address` once `frame_init` completes.
    /// Rex5+ deduplication semantics match `push_call_frame`.
    pub(crate) fn push_create_frame(&mut self) -> bool {
        let rex5_enabled = self.rex5_enabled;
        let parent_needs_update = match self.frame_mut() {
            Some(entry) if !entry.info.target_updated => {
                if rex5_enabled {
                    entry.info.target_updated = true;
                }
                true
            }
            _ => false,
        };
        let charged_parent_update = rex5_enabled && parent_needs_update;
        self.push_call_frame_info(CallFrameInfo {
            target_address: None,
            target_updated: true,
            charged_parent_update,
        });
        parent_needs_update
    }

    /// Records the created address on the current CREATE frame.
    ///
    /// Asserts that the current frame's `target_address` has not already been set,
    /// matching the previous inline behavior. Does nothing if the frame stack is empty.
    pub(crate) fn set_created_address(&mut self, addr: Address) {
        if let Some(entry) = self.frame_mut() {
            assert!(entry.info.target_address.is_none(), "created account already recorded");
            entry.info.target_address = Some(addr);
        }
    }

    /// Pops the current frame and, on revert, unwinds the parent's `target_updated` flag
    /// if this child set it.
    ///
    /// Rex5+ only: when the reverting child had `charged_parent_update` set, the parent's
    /// `target_updated` is reset so the next successful call from the same parent still
    /// charges the parent account (avoiding undercounting after a revert-then-retry pattern).
    /// Pre-Rex5 child frames have `charged_parent_update = false`, so this method behaves
    /// identically to `pop_frame` for older specs.
    pub(crate) fn pop_frame_unwind_parent(&mut self, success: bool) {
        let child = self.pop_frame(success);
        if !success {
            if let Some(child_entry) = child {
                if child_entry.info.charged_parent_update {
                    // charged_parent_update=true implies a parent frame exists
                    // (the flag is only set when frame_mut() returned Some).
                    self.frame_mut()
                        .expect("parent frame must exist when charged_parent_update is true")
                        .info
                        .target_updated = false;
                }
            }
        }
    }
}

pub(crate) trait TxRuntimeLimit {
    fn tx_limit(&self) -> u64;
    fn tx_usage(&self) -> u64;
    fn reset(&mut self);
    fn check_limit(&self) -> LimitCheck;

    #[inline]
    fn before_tx_start(&mut self, _tx: &MegaTransaction) {}
    #[inline]
    fn push_empty_frame(&mut self) {}
    #[inline]
    fn before_frame_init<JOURNAL: JournalInspectTr<DBError: core::fmt::Debug>>(
        &mut self,
        _frame_init: &FrameInit,
        _journal: &mut JOURNAL,
    ) -> Result<(), JOURNAL::DBError> {
        Ok(())
    }
    #[inline]
    fn after_frame_init_on_frame(&mut self, _frame: &EthFrame<EthInterpreter>) {}
    #[inline]
    fn before_frame_run(&mut self, _frame: &EthFrame<EthInterpreter>) {}
    #[inline]
    fn after_frame_run<'a>(
        &mut self,
        _frame: &'a EthFrame<EthInterpreter>,
        _action: &'a mut InterpreterAction,
    ) {
    }
    #[inline]
    fn before_frame_return_result<const LAST_FRAME: bool>(&mut self, _result: &FrameResult) {}
    #[inline]
    fn after_sstore(
        &mut self,
        _target_address: Address,
        _slot: U256,
        _store_result: &SStoreResult,
    ) {
    }
    fn after_log(&mut self, _num_topics: u64, _data_size: u64) {}
    #[inline]
    fn after_selfdestruct(&mut self, _refund: u64) {}
}

#[cfg(test)]
mod tests {
    use alloy_primitives::address;

    use super::*;

    const ADDR: Address = address!("0000000000000000000000000000000000001234");

    /// `set_created_address` on an empty frame stack must be a no-op.
    #[test]
    fn test_set_created_address_empty_stack_is_noop() {
        let mut t = FrameLimitTracker::<CallFrameInfo>::new(MegaSpecId::EQUIVALENCE, u64::MAX);
        // No frame on the stack — should not panic, just do nothing.
        t.set_created_address(ADDR);
    }

    /// `set_created_address` panics when called twice on the same CREATE frame
    /// (invariant: `target_address` must be `None` when first filled in).
    #[test]
    #[should_panic(expected = "created account already recorded")]
    fn test_set_created_address_duplicate_panics() {
        let mut t = FrameLimitTracker::<CallFrameInfo>::new(MegaSpecId::EQUIVALENCE, u64::MAX);
        t.push_create_frame();
        t.set_created_address(ADDR);
        t.set_created_address(ADDR); // second call must panic
    }

    /// Drives `FrameLimitTracker` through a representative sequence of pushes, mutations,
    /// and pops (both success and revert) and asserts that the cached `net_usage()` stays
    /// in sync with the uncached reference walk after every step. This is the load-bearing
    /// guarantee of the cache-based refactor: any future change that introduces a new
    /// mutation site without going through a helper will break this test.
    #[test]
    fn test_net_usage_cache_matches_uncached() {
        let mut t = FrameLimitTracker::<()>::new(MegaSpecId::EQUIVALENCE, u64::MAX);
        assert_eq!(t.net_usage(), t.net_usage_uncached());
        assert_eq!(t.net_usage(), 0);

        // Pre-frame intrinsic usage goes to tx_entry.
        t.add_tx_persistent(100);
        assert_eq!(t.net_usage(), t.net_usage_uncached());

        // add_frame_persistent on empty stack must be a no-op and return false.
        assert!(!t.add_frame_persistent(50));
        assert_eq!(t.net_usage(), t.net_usage_uncached());
        // add_frame_discardable / add_frame_refund are no-ops on empty stack.
        t.add_frame_discardable(50);
        t.add_frame_refund(50);
        assert_eq!(t.net_usage(), t.net_usage_uncached());
        assert_eq!(t.net_usage(), 100);

        // Push frame 1 and mix in persistent/discardable/refund.
        t.push_frame(());
        assert!(t.add_frame_persistent(20));
        t.add_frame_discardable(30);
        t.add_frame_refund(10);
        assert_eq!(t.net_usage(), t.net_usage_uncached());

        // Push frame 2 (nested) and mutate.
        t.push_frame(());
        assert!(t.add_frame_persistent(7));
        t.add_frame_discardable(15);
        t.add_frame_refund(3);
        assert_eq!(t.net_usage(), t.net_usage_uncached());

        // Push frame 3 (deeper) and revert it — discardable & refund must vanish from cache,
        // persistent must merge into the parent (frame 2).
        t.push_frame(());
        assert!(t.add_frame_persistent(5));
        t.add_frame_discardable(11);
        t.add_frame_refund(2);
        let before_revert = t.net_usage();
        let popped = t.pop_frame(false).expect("frame 3 popped");
        assert_eq!(popped.discardable_usage, 11);
        assert_eq!(popped.refund, 2);
        assert_eq!(t.net_usage(), t.net_usage_uncached());
        // After revert: cache should drop the child's discardable (11) and refund (2),
        // so net_usage changes by -(11) + 2 = -9 vs before.
        assert_eq!(t.net_usage(), before_revert - 9);

        // Push frame 3 again and pop it successfully — discardable/refund merge into parent,
        // cache is unchanged by the pop itself (totals are invariant under transfer).
        t.push_frame(());
        assert!(t.add_frame_persistent(4));
        t.add_frame_discardable(6);
        t.add_frame_refund(1);
        let before_success = t.net_usage();
        t.pop_frame(true);
        assert_eq!(t.net_usage(), t.net_usage_uncached());
        assert_eq!(t.net_usage(), before_success);

        // Pop frame 2 with success → merge into frame 1.
        t.pop_frame(true);
        assert_eq!(t.net_usage(), t.net_usage_uncached());

        // Pop the last frame (frame 1) with revert → discardable/refund vanish, persistent
        // merges into tx_entry.
        let before_last_revert = t.net_usage();
        let frame_1 = t.pop_frame(false).expect("frame 1 popped");
        assert_eq!(t.net_usage(), t.net_usage_uncached());
        // The popped frame's discardable/refund leave the cache.
        assert_eq!(t.net_usage(), before_last_revert - frame_1.discardable_usage + frame_1.refund);

        // Reset returns the cache to zero in sync with the entries.
        t.reset();
        assert_eq!(t.net_usage(), t.net_usage_uncached());
        assert_eq!(t.net_usage(), 0);
    }

    /// Verifies that a refund exceeding the cumulative usage clamps `net_usage()` to 0,
    /// matching the saturating semantics of the uncached reference. This guards against
    /// signed-style accounting bugs where refunds outrun used and an unsigned subtraction
    /// would otherwise wrap.
    #[test]
    fn test_net_usage_saturates_when_refund_exceeds_used() {
        let mut t = FrameLimitTracker::<()>::new(MegaSpecId::EQUIVALENCE, u64::MAX);
        t.push_frame(());
        t.add_frame_discardable(10);
        t.add_frame_refund(100);
        assert_eq!(t.net_usage(), 0);
        assert_eq!(t.net_usage(), t.net_usage_uncached());
    }
}