1use alloc::string::ToString;
2use alloy_primitives::map::HashMap;
3use alloy_rpc_types_trace::opcode::OpcodeGas;
4use revm::{
5 bytecode::opcode::{self, OpCode},
6 context::{ContextTr, JournalTr},
7 interpreter::{
8 interpreter_types::{Immediates, Jumps},
9 CallInputs, CallOutcome, CallScheme, CreateInputs, CreateOutcome, CreateScheme,
10 Interpreter,
11 },
12 Inspector,
13};
14
15#[derive(Clone, Debug, Default)]
17pub struct OpcodeGasInspector {
18 opcode_counts: HashMap<OpCode, u64>,
20 opcode_gas: HashMap<OpCode, u64>,
22 last_opcode_gas_remaining: Option<(OpCode, u64)>,
24}
25
26impl OpcodeGasInspector {
27 pub fn new() -> Self {
29 Self::default()
30 }
31
32 pub const fn opcode_counts(&self) -> &HashMap<OpCode, u64> {
34 &self.opcode_counts
35 }
36
37 pub const fn opcode_gas(&self) -> &HashMap<OpCode, u64> {
39 &self.opcode_gas
40 }
41
42 pub fn opcode_iter(&self) -> impl Iterator<Item = (OpCode, (u64, u64))> + '_ {
46 self.opcode_counts.iter().map(move |(&opcode, &count)| {
47 let gas = self.opcode_gas.get(&opcode).copied().unwrap_or_default();
48 (opcode, (count, gas))
49 })
50 }
51
52 pub fn opcode_gas_iter(&self) -> impl Iterator<Item = OpcodeGas> + '_ {
56 self.opcode_iter().map(|(opcode, (count, gas_used))| OpcodeGas {
57 opcode: opcode.to_string(),
58 count,
59 gas_used,
60 })
61 }
62
63 fn subtract_gas_limit(&mut self, opcode_value: u8, gas_limit: u64) {
66 if let Some(opcode) = OpCode::new(opcode_value) {
67 let opcode_gas = self.opcode_gas.entry(opcode).or_default();
68 *opcode_gas = opcode_gas.saturating_sub(gas_limit);
69 }
70 }
71}
72
73impl<CTX> Inspector<CTX> for OpcodeGasInspector
74where
75 CTX: ContextTr,
76{
77 fn step(&mut self, interp: &mut Interpreter, _context: &mut CTX) {
78 let opcode_value = interp.bytecode.opcode();
79 if let Some(opcode) = OpCode::new(opcode_value) {
80 *self.opcode_counts.entry(opcode).or_default() += 1;
82
83 self.last_opcode_gas_remaining = Some((opcode, interp.gas.remaining()));
85 }
86 }
87
88 fn step_end(&mut self, interp: &mut Interpreter, _context: &mut CTX) {
89 if let Some((opcode, gas_remaining)) = self.last_opcode_gas_remaining.take() {
91 let gas_cost = gas_remaining.saturating_sub(interp.gas.remaining());
92 *self.opcode_gas.entry(opcode).or_default() += gas_cost;
93 }
94 }
95
96 fn call(&mut self, context: &mut CTX, inputs: &mut CallInputs) -> Option<CallOutcome> {
97 if context.journal_ref().depth() == 0 {
98 return None;
100 }
101
102 let opcode = match inputs.scheme {
106 CallScheme::Call => opcode::CALL,
107 CallScheme::CallCode => opcode::CALLCODE,
108 CallScheme::DelegateCall => opcode::DELEGATECALL,
109 CallScheme::StaticCall => opcode::STATICCALL,
110 };
111
112 self.subtract_gas_limit(opcode, inputs.gas_limit);
113
114 None
115 }
116
117 fn create(&mut self, context: &mut CTX, inputs: &mut CreateInputs) -> Option<CreateOutcome> {
118 if context.journal_ref().depth() == 0 {
119 return None;
121 }
122
123 let opcode = match inputs.scheme() {
127 CreateScheme::Create => opcode::CREATE,
128 CreateScheme::Create2 { .. } => opcode::CREATE2,
129 CreateScheme::Custom { .. } => return None,
130 };
131
132 self.subtract_gas_limit(opcode, inputs.gas_limit());
133
134 None
135 }
136}
137
138pub fn immediate_size(bytecode: &impl Immediates) -> u8 {
143 let opcode = bytecode.read_u8();
144 let Some(opcode) = OpCode::new(opcode) else { return 0 };
145 opcode.info().immediate_size()
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151 use revm::{
152 bytecode::Bytecode,
153 database::CacheDB,
154 database_interface::EmptyDB,
155 interpreter::{interpreter::ExtBytecode, InputsImpl, SharedMemory},
156 primitives::{hardfork::SpecId, Bytes},
157 Context, MainContext,
158 };
159
160 #[test]
161 fn test_opcode_counter_inspector() {
162 let mut opcode_counter = OpcodeGasInspector::new();
163
164 let opcodes = [opcode::ADD, opcode::ADD, opcode::ADD, opcode::BYTE];
165
166 let bytecode = Bytecode::new_raw(Bytes::from(opcodes));
167 let mut interpreter = Interpreter::new(
168 SharedMemory::new(),
169 ExtBytecode::new(bytecode),
170 InputsImpl::default(),
171 false,
172 SpecId::default(),
173 u64::MAX,
174 );
175 let db = CacheDB::new(EmptyDB::default());
176
177 let mut context = Context::mainnet().with_db(db);
178 for _ in &opcodes {
179 opcode_counter.step(&mut interpreter, &mut context);
180 }
181 }
182
183 #[test]
184 fn test_with_variety_of_opcodes() {
185 let mut opcode_counter = OpcodeGasInspector::new();
186
187 let opcodes = [
188 opcode::PUSH1,
189 opcode::PUSH1,
190 opcode::ADD,
191 opcode::PUSH1,
192 opcode::SSTORE,
193 opcode::STOP,
194 ];
195
196 let bytecode = Bytecode::new_raw(Bytes::from(opcodes));
197 let mut interpreter = Interpreter::new(
198 SharedMemory::new(),
199 ExtBytecode::new(bytecode),
200 InputsImpl::default(),
201 false,
202 SpecId::default(),
203 u64::MAX,
204 );
205 let db = CacheDB::new(EmptyDB::default());
206
207 let mut context = Context::mainnet().with_db(db);
208 for _ in opcodes.iter() {
209 opcode_counter.step(&mut interpreter, &mut context);
210 }
211 }
212}