Skip to content

Commit 293eaef

Browse files
authoredApr 3, 2025··
feature: utility inspectors (#98)
* feat: a couple utility inspectors * feat: inspector stack * lint: clippy * feat: stack vs layered * fix: allow instantiating layered * fix: trace syscalls and change fail values * lint: clippy * chore: better docs
1 parent 9a79e24 commit 293eaef

File tree

8 files changed

+845
-7
lines changed

8 files changed

+845
-7
lines changed
 

‎src/inspectors/layer.rs

+145
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
use revm::{
2+
interpreter::{
3+
CallInputs, CallOutcome, CreateInputs, CreateOutcome, EOFCreateInputs, Interpreter,
4+
InterpreterTypes,
5+
},
6+
primitives::{Address, Log, U256},
7+
Inspector,
8+
};
9+
10+
/// A layer in a stack of inspectors. Contains its own inspector and an
11+
/// inner inspector. This is used to create a stack of inspectors that can
12+
/// be used to inspect the execution of a contract.
13+
///
14+
/// Use `Layered` when you need to retain type information about the inner
15+
/// inspectors.
16+
///
17+
/// The current inspector will be invoked first, then the inner inspector.
18+
/// For functions that may return values (e.g. [`Inspector::call`]), if the
19+
/// current inspector returns a value, the inner inspector will not be invoked.
20+
#[derive(Clone, Debug)]
21+
pub struct Layered<Outer, Inner> {
22+
outer: Outer,
23+
inner: Inner,
24+
}
25+
26+
impl<Outer, Inner> Layered<Outer, Inner> {
27+
/// Create a new [`Layered`] inspector with the given current and inner
28+
/// inspectors.
29+
pub const fn new(outer: Outer, inner: Inner) -> Self {
30+
Self { outer, inner }
31+
}
32+
33+
/// Wrap this inspector in another, creating a new [`Layered`] inspector.
34+
/// with this as the inner inspector.
35+
pub const fn wrap_in<Other>(self, outer: Other) -> Layered<Other, Self> {
36+
Layered { outer, inner: self }
37+
}
38+
39+
/// Wrap this inspector around another, creating a new [`Layered`] inspector
40+
/// with this as the outer inspector.
41+
pub const fn wrap_around<Other>(self, inner: Other) -> Layered<Self, Other> {
42+
Layered { outer: self, inner }
43+
}
44+
45+
/// Get a reference to the current inspector.
46+
pub const fn outer(&self) -> &Outer {
47+
&self.outer
48+
}
49+
50+
/// Get a mutable reference to the current inspector.
51+
pub fn outer_mut(&mut self) -> &mut Outer {
52+
&mut self.outer
53+
}
54+
55+
/// Get a reference to the inner inspector.
56+
pub const fn inner(&self) -> &Inner {
57+
&self.inner
58+
}
59+
60+
/// Get a mutable reference to the inner inspector.
61+
pub fn inner_mut(&mut self) -> &mut Inner {
62+
&mut self.inner
63+
}
64+
}
65+
66+
impl<Ctx, Int: InterpreterTypes, Outer, Inner> Inspector<Ctx, Int> for Layered<Outer, Inner>
67+
where
68+
Outer: Inspector<Ctx, Int>,
69+
Inner: Inspector<Ctx, Int>,
70+
{
71+
fn initialize_interp(&mut self, interp: &mut Interpreter<Int>, context: &mut Ctx) {
72+
self.outer.initialize_interp(interp, context);
73+
self.inner.initialize_interp(interp, context);
74+
}
75+
76+
fn step(&mut self, interp: &mut Interpreter<Int>, context: &mut Ctx) {
77+
self.outer.step(interp, context);
78+
self.inner.step(interp, context);
79+
}
80+
81+
fn step_end(&mut self, interp: &mut Interpreter<Int>, context: &mut Ctx) {
82+
self.outer.step_end(interp, context);
83+
self.inner.step_end(interp, context);
84+
}
85+
86+
fn log(&mut self, interp: &mut Interpreter<Int>, context: &mut Ctx, log: Log) {
87+
self.outer.log(interp, context, log.clone());
88+
self.inner.log(interp, context, log);
89+
}
90+
91+
fn call(&mut self, context: &mut Ctx, inputs: &mut CallInputs) -> Option<CallOutcome> {
92+
if let Some(outcome) = self.outer.call(context, inputs) {
93+
return Some(outcome);
94+
}
95+
self.inner.call(context, inputs)
96+
}
97+
98+
fn call_end(&mut self, context: &mut Ctx, inputs: &CallInputs, outcome: &mut CallOutcome) {
99+
self.outer.call_end(context, inputs, outcome);
100+
self.inner.call_end(context, inputs, outcome);
101+
}
102+
103+
fn create(&mut self, context: &mut Ctx, inputs: &mut CreateInputs) -> Option<CreateOutcome> {
104+
if let Some(outcome) = self.outer.create(context, inputs) {
105+
return Some(outcome);
106+
}
107+
self.inner.create(context, inputs)
108+
}
109+
110+
fn create_end(
111+
&mut self,
112+
context: &mut Ctx,
113+
inputs: &CreateInputs,
114+
outcome: &mut CreateOutcome,
115+
) {
116+
self.outer.create_end(context, inputs, outcome);
117+
self.inner.create_end(context, inputs, outcome);
118+
}
119+
120+
fn eofcreate(
121+
&mut self,
122+
context: &mut Ctx,
123+
inputs: &mut EOFCreateInputs,
124+
) -> Option<CreateOutcome> {
125+
if let Some(outcome) = self.outer.eofcreate(context, inputs) {
126+
return Some(outcome);
127+
}
128+
self.inner.eofcreate(context, inputs)
129+
}
130+
131+
fn eofcreate_end(
132+
&mut self,
133+
context: &mut Ctx,
134+
inputs: &EOFCreateInputs,
135+
outcome: &mut CreateOutcome,
136+
) {
137+
self.outer.eofcreate_end(context, inputs, outcome);
138+
self.inner.eofcreate_end(context, inputs, outcome);
139+
}
140+
141+
fn selfdestruct(&mut self, contract: Address, target: Address, value: U256) {
142+
self.outer.selfdestruct(contract, target, value);
143+
self.inner.selfdestruct(contract, target, value);
144+
}
145+
}

‎src/inspectors/mod.rs

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
mod timeout;
2+
pub use timeout::TimeLimit;
3+
4+
mod set;
5+
pub use set::InspectorSet;
6+
7+
mod spanning;
8+
pub use spanning::SpanningInspector;
9+
10+
mod layer;
11+
pub use layer::Layered;
12+
13+
#[cfg(test)]
14+
mod test {
15+
use super::*;
16+
use crate::{test_utils::TestInspector, NoopBlock, NoopCfg};
17+
use revm::{database::InMemoryDB, inspector::InspectorEvmTr, primitives::B256};
18+
use std::time::Duration;
19+
20+
#[test]
21+
fn test() {
22+
let inspector =
23+
Layered::new(TimeLimit::new(Duration::from_micros(10)), SpanningInspector::at_info())
24+
.wrap_around(TestInspector::default());
25+
26+
let mut trevm = crate::TrevmBuilder::new()
27+
.with_db(InMemoryDB::default())
28+
.with_insp(inspector)
29+
.build_trevm()
30+
.unwrap()
31+
.fill_cfg(&NoopCfg)
32+
.fill_block(&NoopBlock);
33+
34+
trevm.apply_eip4788(B256::repeat_byte(0xaa)).unwrap();
35+
36+
assert!(trevm.inner_mut_unchecked().inspector().outer().outer().has_elapsed());
37+
}
38+
}

‎src/inspectors/set.rs

+154
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
use std::collections::VecDeque;
2+
3+
use revm::{
4+
interpreter::{
5+
CallInputs, CallOutcome, CreateInputs, CreateOutcome, EOFCreateInputs, Interpreter,
6+
InterpreterTypes,
7+
},
8+
primitives::{Address, Log, U256},
9+
Inspector,
10+
};
11+
12+
/// A stack of [`Inspector`]s.
13+
///
14+
/// This is a thin wrapper around a [`VecDeque`] of inspectors.
15+
#[derive(Default)]
16+
pub struct InspectorSet<Ctx, Int> {
17+
inspectors: VecDeque<Box<dyn Inspector<Ctx, Int>>>,
18+
}
19+
20+
impl<Ctx, Int> core::ops::Deref for InspectorSet<Ctx, Int> {
21+
type Target = VecDeque<Box<dyn Inspector<Ctx, Int>>>;
22+
23+
fn deref(&self) -> &Self::Target {
24+
&self.inspectors
25+
}
26+
}
27+
28+
impl<Ctx, Int> core::ops::DerefMut for InspectorSet<Ctx, Int> {
29+
fn deref_mut(&mut self) -> &mut Self::Target {
30+
&mut self.inspectors
31+
}
32+
}
33+
34+
impl<Ctx, Int> core::fmt::Debug for InspectorSet<Ctx, Int> {
35+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36+
f.debug_struct("InspectorStack").field("inspectors", &self.inspectors.len()).finish()
37+
}
38+
}
39+
40+
impl<Ctx, Int> InspectorSet<Ctx, Int>
41+
where
42+
Int: InterpreterTypes,
43+
{
44+
/// Instantiate a new empty inspector stack.
45+
pub fn new() -> Self {
46+
Self { inspectors: Default::default() }
47+
}
48+
49+
/// Instantiate a new empty stack with pre-allocated capacity.
50+
pub fn with_capacity(cap: usize) -> Self {
51+
Self { inspectors: VecDeque::with_capacity(cap) }
52+
}
53+
54+
/// Push an inspector to the back of the stack.
55+
pub fn push_back<I: Inspector<Ctx, Int> + 'static>(&mut self, inspector: I) {
56+
self.inspectors.push_back(Box::new(inspector));
57+
}
58+
59+
/// Push an inspector to the front of the stack.
60+
pub fn push_front<I: Inspector<Ctx, Int> + 'static>(&mut self, inspector: I) {
61+
self.inspectors.push_front(Box::new(inspector));
62+
}
63+
64+
/// Pop an inspector from the back of the stack.
65+
pub fn pop_back(&mut self) -> Option<Box<dyn Inspector<Ctx, Int>>> {
66+
self.inspectors.pop_back()
67+
}
68+
69+
/// Pop an inspector from the front of the stack.
70+
pub fn pop_front(&mut self) -> Option<Box<dyn Inspector<Ctx, Int>>> {
71+
self.inspectors.pop_front()
72+
}
73+
}
74+
75+
impl<Ctx, Int> Inspector<Ctx, Int> for InspectorSet<Ctx, Int>
76+
where
77+
Int: InterpreterTypes,
78+
{
79+
fn initialize_interp(&mut self, interp: &mut Interpreter<Int>, context: &mut Ctx) {
80+
self.inspectors.iter_mut().for_each(|i| i.initialize_interp(interp, context));
81+
}
82+
83+
fn step(&mut self, interp: &mut Interpreter<Int>, context: &mut Ctx) {
84+
self.inspectors.iter_mut().for_each(|i| i.step(interp, context));
85+
}
86+
87+
fn step_end(&mut self, interp: &mut Interpreter<Int>, context: &mut Ctx) {
88+
self.inspectors.iter_mut().for_each(|i| i.step_end(interp, context));
89+
}
90+
91+
fn log(&mut self, interp: &mut Interpreter<Int>, context: &mut Ctx, log: Log) {
92+
self.inspectors.iter_mut().for_each(|i| i.log(interp, context, log.clone()));
93+
}
94+
95+
fn call(&mut self, context: &mut Ctx, inputs: &mut CallInputs) -> Option<CallOutcome> {
96+
for inspector in self.inspectors.iter_mut() {
97+
let outcome = inspector.call(context, inputs);
98+
if outcome.is_some() {
99+
return outcome;
100+
}
101+
}
102+
None
103+
}
104+
105+
fn call_end(&mut self, context: &mut Ctx, inputs: &CallInputs, outcome: &mut CallOutcome) {
106+
self.inspectors.iter_mut().for_each(|i| i.call_end(context, inputs, outcome))
107+
}
108+
109+
fn create(&mut self, context: &mut Ctx, inputs: &mut CreateInputs) -> Option<CreateOutcome> {
110+
for inspector in self.inspectors.iter_mut() {
111+
let outcome = inspector.create(context, inputs);
112+
if outcome.is_some() {
113+
return outcome;
114+
}
115+
}
116+
None
117+
}
118+
119+
fn create_end(
120+
&mut self,
121+
context: &mut Ctx,
122+
inputs: &CreateInputs,
123+
outcome: &mut CreateOutcome,
124+
) {
125+
self.inspectors.iter_mut().for_each(|i| i.create_end(context, inputs, outcome))
126+
}
127+
128+
fn eofcreate(
129+
&mut self,
130+
context: &mut Ctx,
131+
inputs: &mut EOFCreateInputs,
132+
) -> Option<CreateOutcome> {
133+
for inspector in self.inspectors.iter_mut() {
134+
let outcome = inspector.eofcreate(context, inputs);
135+
if outcome.is_some() {
136+
return outcome;
137+
}
138+
}
139+
None
140+
}
141+
142+
fn eofcreate_end(
143+
&mut self,
144+
context: &mut Ctx,
145+
inputs: &EOFCreateInputs,
146+
outcome: &mut CreateOutcome,
147+
) {
148+
self.inspectors.iter_mut().for_each(|i| i.eofcreate_end(context, inputs, outcome))
149+
}
150+
151+
fn selfdestruct(&mut self, contract: Address, target: Address, value: U256) {
152+
self.inspectors.iter_mut().for_each(|i| i.selfdestruct(contract, target, value))
153+
}
154+
}

‎src/inspectors/spanning.rs

+220
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
use alloy::hex;
2+
use revm::{
3+
interpreter::{
4+
CallInputs, CallOutcome, CreateInputs, CreateOutcome, EOFCreateInputs, Interpreter,
5+
InterpreterTypes,
6+
},
7+
Inspector,
8+
};
9+
use tracing::{
10+
debug_span, error_span, info_span, span::EnteredSpan, trace_span, warn_span, Level, Span,
11+
};
12+
13+
macro_rules! runtime_level_span {
14+
($level:expr, $($args:tt)*) => {{
15+
match $level {
16+
Level::TRACE => trace_span!($($args)*),
17+
Level::DEBUG => debug_span!($($args)*),
18+
Level::INFO => info_span!($($args)*),
19+
Level::WARN => warn_span!($($args)*),
20+
Level::ERROR => error_span!($($args)*),
21+
}
22+
}};
23+
}
24+
25+
/// Inspector that creates spans for each call and create operation.
26+
///
27+
/// This inspector is useful for tracing the execution of the EVM and
28+
/// contextualizing information from other tracing inspectors. It uses
29+
/// [`tracing`] to create spans for each call frame, at a specfied [`Level`],
30+
/// and adds interpreter information to the span.
31+
///
32+
/// Spans are created at the beginning of each call and create operation,
33+
/// and closed at the end of the operation. The spans are named
34+
/// according to the operation type (call or create) and include
35+
/// the call depth, gas remaining, and other relevant information.
36+
///
37+
/// # Note on functionality
38+
///
39+
/// We assume that the EVM execution is synchronous, so [`EnteredSpan`]s will
40+
/// not be held across await points. This means we can simply keep a
41+
/// `Vec<EnteredSpan>` to which push and from which we pop. The first span in
42+
/// the vec will always be our root span, and 1 span will be held for each
43+
/// active callframe.
44+
///
45+
pub struct SpanningInspector {
46+
active: Vec<EnteredSpan>,
47+
level: Level,
48+
}
49+
50+
impl core::fmt::Debug for SpanningInspector {
51+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52+
f.debug_struct("SpanningInspector")
53+
.field("active", &self.active.len())
54+
.field("level", &self.level)
55+
.finish()
56+
}
57+
}
58+
59+
impl SpanningInspector {
60+
/// Create a new `SpanningInspector` with the given tracing level.
61+
/// Spans will be created at this level.
62+
pub const fn new(level: Level) -> Self {
63+
Self { active: Vec::new(), level }
64+
}
65+
66+
/// Create a new `SpanningInspector` producing spans at [`Level::TRACE`].
67+
pub const fn at_trace() -> Self {
68+
Self::new(Level::TRACE)
69+
}
70+
71+
/// Create a new `SpanningInspector` producing spans at [`Level::DEBUG`].
72+
pub const fn at_debug() -> Self {
73+
Self::new(Level::DEBUG)
74+
}
75+
76+
/// Create a new `SpanningInspector` producing spans at [`Level::WARN`].
77+
pub const fn at_info() -> Self {
78+
Self::new(Level::INFO)
79+
}
80+
81+
/// Create a root span
82+
fn root_span<Ctx, Int>(&mut self, _interp: &mut Interpreter<Int>, _context: &mut Ctx) -> Span
83+
where
84+
Int: InterpreterTypes,
85+
{
86+
runtime_level_span!(
87+
self.level,
88+
parent: None, // this is the root span :)
89+
"evm_execution",
90+
)
91+
}
92+
93+
/// Init the inspector by setting the root span. This should be called only in [`Inspector::initialize_interp`].
94+
fn init<Ctx, Int>(&mut self, interp: &mut Interpreter<Int>, context: &mut Ctx)
95+
where
96+
Int: InterpreterTypes,
97+
{
98+
// just in case
99+
self.active.clear();
100+
let span = self.root_span(interp, context).entered();
101+
self.active.push(span);
102+
}
103+
104+
/// Exit the active span.
105+
fn exit_span(&mut self) {
106+
self.active.pop();
107+
// If there's only 1 left, it's the root span for the trace, and all
108+
// callframes are closed. We are now done with execution.
109+
if self.active.len() == 1 {
110+
self.active.pop();
111+
}
112+
}
113+
114+
/// Create a span for a `CALL`-family opcode.
115+
fn span_call<Ctx>(&self, _context: &Ctx, inputs: &CallInputs) -> Span {
116+
let mut selector = inputs.input.clone();
117+
selector.truncate(4);
118+
runtime_level_span!(
119+
self.level,
120+
"call",
121+
input_len = inputs.input.len(),
122+
selector = hex::encode(&selector),
123+
gas_limit = inputs.gas_limit,
124+
bytecode_address = %inputs.bytecode_address,
125+
target_addrses = %inputs.target_address,
126+
caller = %inputs.caller,
127+
value = %inputs.value.get(),
128+
scheme = ?inputs.scheme,
129+
)
130+
}
131+
132+
/// Create, enter, and store a span for a `CALL`-family opcode.
133+
fn enter_call<Ctx>(&mut self, context: &Ctx, inputs: &CallInputs) {
134+
self.active.push(self.span_call(context, inputs).entered())
135+
}
136+
137+
/// Create a span for a `CREATE`-family opcode.
138+
fn span_create<Ctx>(&self, _context: &Ctx, inputs: &CreateInputs) -> Span {
139+
runtime_level_span!(
140+
self.level,
141+
"create",
142+
caller = %inputs.caller,
143+
value = %inputs.value,
144+
gas_limit = inputs.gas_limit,
145+
scheme = ?inputs.scheme,
146+
)
147+
}
148+
149+
/// Create, enter, and store a span for a `CREATE`-family opcode.
150+
fn enter_create<Ctx>(&mut self, context: &Ctx, inputs: &CreateInputs) {
151+
self.active.push(self.span_create(context, inputs).entered())
152+
}
153+
154+
/// Create a span for an EOF `CREATE`-family opcode.
155+
fn span_eof_create<Ctx>(&self, _context: &Ctx, inputs: &EOFCreateInputs) -> Span {
156+
runtime_level_span!(
157+
self.level,
158+
"eof_create",
159+
caller = %inputs.caller,
160+
value = %inputs.value,
161+
gas_limit = inputs.gas_limit,
162+
kind = ?inputs.kind,
163+
)
164+
}
165+
166+
/// Create, enter, and store a span for an EOF `CREATE`-family opcode.
167+
fn enter_eof_create<Ctx>(&mut self, context: &Ctx, inputs: &EOFCreateInputs) {
168+
self.active.push(self.span_eof_create(context, inputs).entered())
169+
}
170+
}
171+
172+
impl<Ctx, Int> Inspector<Ctx, Int> for SpanningInspector
173+
where
174+
Int: InterpreterTypes,
175+
{
176+
fn initialize_interp(&mut self, interp: &mut Interpreter<Int>, context: &mut Ctx) {
177+
self.init(interp, context);
178+
}
179+
180+
fn call(&mut self, context: &mut Ctx, inputs: &mut CallInputs) -> Option<CallOutcome> {
181+
self.enter_call(context, inputs);
182+
None
183+
}
184+
185+
fn call_end(&mut self, _context: &mut Ctx, _inputs: &CallInputs, _outcome: &mut CallOutcome) {
186+
self.exit_span();
187+
}
188+
189+
fn create(&mut self, context: &mut Ctx, inputs: &mut CreateInputs) -> Option<CreateOutcome> {
190+
self.enter_create(context, inputs);
191+
None
192+
}
193+
194+
fn create_end(
195+
&mut self,
196+
_context: &mut Ctx,
197+
_inputs: &CreateInputs,
198+
_outcome: &mut CreateOutcome,
199+
) {
200+
self.exit_span();
201+
}
202+
203+
fn eofcreate(
204+
&mut self,
205+
context: &mut Ctx,
206+
inputs: &mut EOFCreateInputs,
207+
) -> Option<CreateOutcome> {
208+
self.enter_eof_create(context, inputs);
209+
None
210+
}
211+
212+
fn eofcreate_end(
213+
&mut self,
214+
_context: &mut Ctx,
215+
_inputs: &EOFCreateInputs,
216+
_outcome: &mut CreateOutcome,
217+
) {
218+
self.exit_span();
219+
}
220+
}

‎src/inspectors/timeout.rs

+155
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
use revm::{
2+
interpreter::{
3+
CallInputs, CallOutcome, CreateInputs, CreateOutcome, EOFCreateInputs, Gas,
4+
InstructionResult, Interpreter, InterpreterResult, InterpreterTypes,
5+
},
6+
primitives::Bytes,
7+
Inspector,
8+
};
9+
use std::time::{Duration, Instant};
10+
11+
const CALL_TIMEOUT: CallOutcome = CallOutcome {
12+
result: InterpreterResult {
13+
result: InstructionResult::CallTooDeep,
14+
output: Bytes::new(),
15+
gas: Gas::new_spent(0),
16+
},
17+
memory_offset: 0..0,
18+
};
19+
20+
const CREATE_TIMEOUT: CreateOutcome = CreateOutcome {
21+
result: InterpreterResult {
22+
result: InstructionResult::CallTooDeep,
23+
output: Bytes::new(),
24+
gas: Gas::new_spent(0),
25+
},
26+
address: None,
27+
};
28+
29+
/// A revm [`Inspector`] that limits wallclock time spent on execution.
30+
///
31+
/// This inspector will stop execution at the beginning and end of each
32+
/// callframe after the timeout has been reached. Specifically, it will stop
33+
/// when:
34+
/// - any callframe is aborted (e.g. due to execeeding its gas limit)
35+
/// - any of the following instructions are executed:
36+
/// - `CALL`
37+
/// - `CREATE`
38+
/// - `RETURN`
39+
/// - `STOP`
40+
/// - `REVERT`
41+
/// - any invalid opcode
42+
///
43+
/// When execution is terminated by the timer, it will result in a
44+
/// [`InstructionResult::CallTooDeep`]. This is somewhat unintutive. `revm`
45+
/// uses the [`InstructionResult`] enum to represent possible outcomes of a
46+
/// opcode. It requires that the inspector's outcome is a valid
47+
/// [`InstructionResult`], but does not provide a way to represent a custom
48+
/// outcome. This means that the inspector must overload an existing outcome.
49+
/// `CallTooDeep` is used here because it is effectively unreachable in normal
50+
/// `evm` execution due to [EIP-150] call gas forwarding rules, and therefore
51+
/// overloading it is unlikely to cause issues.
52+
///
53+
/// ## Usage Note
54+
///
55+
/// When the timeout is triggered, the inspector will overwrite the
56+
/// [`CallOutcome`] or [`CreateOutcome`]. This means that if other inspectors
57+
/// have already run, they may have inspected data that is no longer valid.
58+
///
59+
/// To avoid this, run this inspector FIRST on any multi-inspector setup. I.e.
60+
/// in a stack, this should be the OUTERMOST inspector. This ensures that
61+
/// invalid data is not inspected, and that other inspectors do not consume
62+
/// memory or compute resources inspecting data that is guaranteed to be
63+
/// discarded.
64+
///
65+
/// [EIP-150]: https://eips.ethereum.org/EIPS/eip-150
66+
#[derive(Debug, Clone, Copy)]
67+
pub struct TimeLimit {
68+
duration: Duration,
69+
execution_start: Instant,
70+
}
71+
72+
impl TimeLimit {
73+
/// Create a new [`TimeLimit`] inspector.
74+
///
75+
/// The inspector will stop execution after the given duration has passed.
76+
pub fn new(duration: Duration) -> Self {
77+
Self { duration, execution_start: Instant::now() }
78+
}
79+
80+
/// Check if the timeout has been reached.
81+
pub fn has_elapsed(&self) -> bool {
82+
self.execution_start.elapsed() >= self.duration
83+
}
84+
85+
/// Set the execution start time to [`Instant::now`]. This is invoked during [`Inspector::initialize_interp`].
86+
pub fn reset(&mut self) {
87+
self.execution_start = Instant::now();
88+
}
89+
90+
/// Get the amount of time that has elapsed since execution start.
91+
pub fn elapsed(&self) -> Duration {
92+
self.execution_start.elapsed()
93+
}
94+
}
95+
96+
impl<Ctx, Int: InterpreterTypes> Inspector<Ctx, Int> for TimeLimit {
97+
fn initialize_interp(&mut self, _interp: &mut Interpreter<Int>, _context: &mut Ctx) {
98+
self.reset();
99+
}
100+
101+
fn call(&mut self, _context: &mut Ctx, _inputs: &mut CallInputs) -> Option<CallOutcome> {
102+
if self.has_elapsed() {
103+
return Some(CALL_TIMEOUT);
104+
}
105+
106+
None
107+
}
108+
109+
fn call_end(&mut self, _context: &mut Ctx, _inputs: &CallInputs, outcome: &mut CallOutcome) {
110+
if self.has_elapsed() {
111+
*outcome = CALL_TIMEOUT;
112+
}
113+
}
114+
115+
fn create(&mut self, _context: &mut Ctx, _inputs: &mut CreateInputs) -> Option<CreateOutcome> {
116+
if self.has_elapsed() {
117+
return Some(CREATE_TIMEOUT);
118+
}
119+
None
120+
}
121+
122+
fn create_end(
123+
&mut self,
124+
_context: &mut Ctx,
125+
_inputs: &CreateInputs,
126+
outcome: &mut CreateOutcome,
127+
) {
128+
if self.has_elapsed() {
129+
*outcome = CREATE_TIMEOUT;
130+
}
131+
}
132+
133+
fn eofcreate(
134+
&mut self,
135+
_context: &mut Ctx,
136+
_inputs: &mut EOFCreateInputs,
137+
) -> Option<CreateOutcome> {
138+
if self.has_elapsed() {
139+
return Some(CREATE_TIMEOUT);
140+
}
141+
142+
None
143+
}
144+
145+
fn eofcreate_end(
146+
&mut self,
147+
_context: &mut Ctx,
148+
_inputs: &EOFCreateInputs,
149+
outcome: &mut CreateOutcome,
150+
) {
151+
if self.has_elapsed() {
152+
*outcome = CREATE_TIMEOUT;
153+
}
154+
}
155+
}

‎src/lib.rs

+5-2
Original file line numberDiff line numberDiff line change
@@ -115,10 +115,10 @@
115115
//! statistics or indices that are only available after the block is closed.
116116
//!
117117
//! ```
118-
//! # use revm::{database::in_memory_db::InMemoryDB};
118+
//! # use revm::{database::in_memory_db::InMemoryDB, inspector::NoOpInspector};
119119
//! # use trevm::{TrevmBuilder, EvmErrored, Cfg, BlockDriver};
120120
//! # use alloy::primitives::B256;
121-
//! # fn t<C: Cfg, D: BlockDriver<()>>(cfg: &C, mut driver: D)
121+
//! # fn t<C: Cfg, D: BlockDriver<NoOpInspector>>(cfg: &C, mut driver: D)
122122
//! # -> Result<(), Box<dyn std::error::Error>> {
123123
//! let trevm = TrevmBuilder::new()
124124
//! .with_db(InMemoryDB::default())
@@ -400,6 +400,9 @@ pub use fill::{fillers, Block, Cfg, NoopBlock, NoopCfg, Tx};
400400
/// Type aliases for constraining revm context.
401401
pub mod helpers;
402402

403+
/// Utility inspectors.
404+
pub mod inspectors;
405+
403406
pub mod journal;
404407

405408
mod lifecycle;

‎src/system/mod.rs

+8-4
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,10 @@ pub const MAX_BLOB_GAS_PER_BLOCK_CANCUN: u64 = 786_432;
5959
/// The maximum blob gas limit for a block in Prague.
6060
pub const MAX_BLOB_GAS_PER_BLOCK_PRAGUE: u64 = 1_179_648;
6161

62-
use crate::{helpers::Evm, EvmExtUnchecked, Tx};
62+
use crate::{
63+
helpers::{Ctx, Evm},
64+
EvmExtUnchecked, Tx,
65+
};
6366
use alloy::primitives::{Address, Bytes};
6467
use revm::{
6568
bytecode::Bytecode,
@@ -68,7 +71,7 @@ use revm::{
6871
Block, ContextTr, Transaction,
6972
},
7073
primitives::KECCAK_EMPTY,
71-
Database, DatabaseCommit, ExecuteEvm,
74+
Database, DatabaseCommit, InspectEvm, Inspector,
7275
};
7376

7477
fn checked_insert_code<Db, Insp>(
@@ -127,18 +130,19 @@ pub(crate) fn execute_system_tx<Db, Insp>(
127130
) -> Result<ExecutionResult, EVMError<Db::Error>>
128131
where
129132
Db: Database + DatabaseCommit,
133+
Insp: Inspector<Ctx<Db>>,
130134
{
131135
syscall.fill_tx(evm);
132136

133137
let limit = evm.tx().gas_limit();
134138

135139
let block = &mut evm.data.ctx.block;
140+
136141
let old_gas_limit = core::mem::replace(&mut block.gas_limit, limit);
137142
let old_base_fee = core::mem::take(&mut block.basefee);
138-
139143
let previous_nonce_check = std::mem::replace(&mut evm.data.ctx.cfg.disable_nonce_check, true);
140144

141-
let mut result = evm.replay()?;
145+
let mut result = evm.inspect_replay()?;
142146

143147
// Cleanup the syscall.
144148
cleanup_syscall(evm, &mut result, syscall, old_gas_limit, old_base_fee, previous_nonce_check);

‎src/test_utils.rs

+120-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@ use revm::{
44
bytecode::Bytecode,
55
database::{CacheDB, EmptyDB, InMemoryDB, State},
66
inspector::{inspectors::TracerEip3155, NoOpInspector},
7-
primitives::hardfork::SpecId,
7+
interpreter::{
8+
CallInputs, CallOutcome, CreateInputs, CreateOutcome, EOFCreateInputs, Interpreter,
9+
InterpreterTypes,
10+
},
11+
primitives::{hardfork::SpecId, Log},
812
state::AccountInfo,
913
Context, Inspector, MainBuilder,
1014
};
@@ -115,3 +119,118 @@ pub fn test_trevm_tracing() -> EvmNeedsCfg<InMemoryDB, TracerEip3155> {
115119
.build_mainnet_with_inspector(TracerEip3155::new(Box::new(std::io::stdout()))),
116120
)
117121
}
122+
123+
/// Inspector that tracks whether the various inspector methods were
124+
/// called. This is useful for testing the inspector stack.
125+
///
126+
/// It is not a real inspector, and does not do anything with the
127+
/// information it collects.
128+
#[derive(Default, Debug, Clone, Copy)]
129+
pub struct TestInspector {
130+
/// Whether initialize_interp was called.
131+
pub init_interp: bool,
132+
/// Whether step was called.
133+
pub step: bool,
134+
/// Whether step_end was called.
135+
pub step_end: bool,
136+
/// Whether log was called.
137+
pub log: bool,
138+
/// Whether call was called.
139+
pub call: bool,
140+
/// Whether call_end was called.
141+
pub call_end: bool,
142+
/// Whether create was called.
143+
pub create: bool,
144+
/// Whether create_end was called.
145+
pub create_end: bool,
146+
/// Whether eofcreate was called.
147+
pub eofcreate: bool,
148+
/// Whether eofcreate_end was called.
149+
pub eofcreate_end: bool,
150+
/// Whether selfdestruct was called.
151+
pub selfdestruct: bool,
152+
}
153+
154+
impl TestInspector {
155+
/// Reset the inspector to its default state.
156+
pub fn reset(&mut self) {
157+
*self = Self::default();
158+
}
159+
}
160+
161+
impl<Ctx, Int> Inspector<Ctx, Int> for TestInspector
162+
where
163+
Int: InterpreterTypes,
164+
{
165+
fn initialize_interp(&mut self, _interp: &mut Interpreter<Int>, _context: &mut Ctx) {
166+
tracing::info!("initialize_interp");
167+
self.init_interp = true;
168+
}
169+
170+
fn step(&mut self, _interp: &mut Interpreter<Int>, _context: &mut Ctx) {
171+
tracing::info!("step");
172+
self.step = true;
173+
}
174+
175+
fn step_end(&mut self, _interp: &mut Interpreter<Int>, _context: &mut Ctx) {
176+
tracing::info!("step_end");
177+
self.step_end = true;
178+
}
179+
180+
fn log(&mut self, _interp: &mut Interpreter<Int>, _context: &mut Ctx, log: Log) {
181+
tracing::info!(?log, "log");
182+
self.log = true;
183+
}
184+
185+
fn call(&mut self, _context: &mut Ctx, inputs: &mut CallInputs) -> Option<CallOutcome> {
186+
tracing::info!(?inputs, "call");
187+
self.call = true;
188+
None
189+
}
190+
191+
fn call_end(&mut self, _context: &mut Ctx, inputs: &CallInputs, outcome: &mut CallOutcome) {
192+
tracing::info!(?inputs, ?outcome, "call_end");
193+
self.call_end = true;
194+
}
195+
196+
fn create(&mut self, _context: &mut Ctx, inputs: &mut CreateInputs) -> Option<CreateOutcome> {
197+
tracing::info!(?inputs, "create");
198+
self.create = true;
199+
None
200+
}
201+
202+
fn create_end(
203+
&mut self,
204+
_context: &mut Ctx,
205+
_inputs: &CreateInputs,
206+
_outcome: &mut CreateOutcome,
207+
) {
208+
tracing::info!("create_end");
209+
self.create_end = true;
210+
}
211+
212+
fn eofcreate(
213+
&mut self,
214+
_context: &mut Ctx,
215+
_inputs: &mut EOFCreateInputs,
216+
) -> Option<CreateOutcome> {
217+
tracing::info!("eofcreate");
218+
self.eofcreate = true;
219+
None
220+
}
221+
222+
fn eofcreate_end(
223+
&mut self,
224+
_context: &mut Ctx,
225+
_inputs: &EOFCreateInputs,
226+
_outcome: &mut CreateOutcome,
227+
) {
228+
tracing::info!("eofcreate_end");
229+
self.eofcreate_end = true;
230+
}
231+
232+
fn selfdestruct(&mut self, contract: Address, target: Address, value: U256) {
233+
tracing::info!(?contract, ?target, ?value, "selfdestruct");
234+
self.selfdestruct = true;
235+
}
236+
}

0 commit comments

Comments
 (0)
Please sign in to comment.