ethrex_levm/opcode_handlers/
stack_memory_storage_flow.rs1use crate::{
21 constants::WORD_SIZE_IN_BYTES_USIZE,
22 errors::{ExceptionalHalt, InternalError, OpcodeResult, VMError},
23 gas_cost::{self, SSTORE_STIPEND},
24 memory::calculate_memory_size,
25 opcode_handlers::OpcodeHandler,
26 opcodes::Opcode,
27 utils::{size_offset_to_usize, u256_to_usize},
28 vm::VM,
29};
30use ethrex_common::{H256, U256, types::Fork};
31use std::{mem, slice};
32
33pub struct OpPopHandler;
35impl OpcodeHandler for OpPopHandler {
36 #[inline(always)]
37 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
38 vm.current_call_frame.increase_consumed_gas(gas_cost::POP)?;
39
40 vm.current_call_frame.stack.pop1()?;
41
42 Ok(OpcodeResult::Continue)
43 }
44}
45
46pub struct OpGasHandler;
48impl OpcodeHandler for OpGasHandler {
49 #[inline(always)]
50 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
51 vm.current_call_frame.increase_consumed_gas(gas_cost::GAS)?;
52
53 vm.current_call_frame
54 .stack
55 .push(vm.current_call_frame.gas_remaining.into())?;
56
57 Ok(OpcodeResult::Continue)
58 }
59}
60
61pub struct OpPcHandler;
63impl OpcodeHandler for OpPcHandler {
64 #[inline(always)]
65 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
66 vm.current_call_frame.increase_consumed_gas(gas_cost::PC)?;
67
68 vm.current_call_frame
71 .stack
72 .push(vm.current_call_frame.pc.wrapping_sub(1).into())?;
73
74 Ok(OpcodeResult::Continue)
75 }
76}
77
78pub struct OpMLoadHandler;
80impl OpcodeHandler for OpMLoadHandler {
81 #[inline(always)]
82 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
83 let offset = u256_to_usize(*vm.current_call_frame.stack.top_mut()?)?;
85 vm.current_call_frame
86 .increase_consumed_gas(gas_cost::mload(
87 calculate_memory_size(offset, WORD_SIZE_IN_BYTES_USIZE)?,
88 vm.current_call_frame.memory.len(),
89 )?)?;
90
91 let word = vm.current_call_frame.memory.load_word(offset)?;
92 *vm.current_call_frame.stack.top_mut()? = word;
93
94 Ok(OpcodeResult::Continue)
95 }
96}
97
98pub struct OpMStoreHandler;
100impl OpcodeHandler for OpMStoreHandler {
101 #[inline(always)]
102 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
103 let [offset, value] = *vm.current_call_frame.stack.pop()?;
104
105 if vm.debug_mode.enabled && vm.debug_mode.handle_debug(offset, value)? {
107 return Ok(OpcodeResult::Continue);
108 }
109
110 let offset = u256_to_usize(offset)?;
111 vm.current_call_frame
112 .increase_consumed_gas(gas_cost::mstore(
113 calculate_memory_size(offset, WORD_SIZE_IN_BYTES_USIZE)?,
114 vm.current_call_frame.memory.len(),
115 )?)?;
116
117 vm.current_call_frame.memory.store_word(offset, value)?;
118
119 Ok(OpcodeResult::Continue)
120 }
121}
122
123pub struct OpMStore8Handler;
125impl OpcodeHandler for OpMStore8Handler {
126 #[inline(always)]
127 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
128 let [offset, value] = *vm.current_call_frame.stack.pop()?;
129 let offset = u256_to_usize(offset)?;
130 let value = value.byte(0);
131
132 vm.current_call_frame
133 .increase_consumed_gas(gas_cost::mstore8(
134 calculate_memory_size(offset, size_of::<u8>())?,
135 vm.current_call_frame.memory.len(),
136 )?)?;
137
138 vm.current_call_frame
139 .memory
140 .store_data(offset, slice::from_ref(&value))?;
141
142 Ok(OpcodeResult::Continue)
143 }
144}
145
146pub struct OpMCopyHandler;
148impl OpcodeHandler for OpMCopyHandler {
149 #[inline(always)]
150 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
151 let [dst_offset, src_offset, len] = *vm.current_call_frame.stack.pop()?;
152 let (len, dst_offset) = size_offset_to_usize(len, dst_offset)?;
153 let src_offset = u256_to_usize(src_offset).unwrap_or(usize::MAX);
154
155 vm.current_call_frame
156 .increase_consumed_gas(gas_cost::mcopy(
157 calculate_memory_size(src_offset.max(dst_offset), len)?,
158 vm.current_call_frame.memory.len(),
159 len,
160 )?)?;
161
162 vm.current_call_frame
163 .memory
164 .copy_within(src_offset, dst_offset, len)?;
165
166 Ok(OpcodeResult::Continue)
167 }
168}
169
170pub struct OpMSizeHandler;
172impl OpcodeHandler for OpMSizeHandler {
173 #[inline(always)]
174 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
175 vm.current_call_frame
176 .increase_consumed_gas(gas_cost::MSIZE)?;
177
178 vm.current_call_frame
179 .stack
180 .push(vm.current_call_frame.memory.len().into())?;
181
182 Ok(OpcodeResult::Continue)
183 }
184}
185
186pub struct OpTLoadHandler;
188impl OpcodeHandler for OpTLoadHandler {
189 #[inline(always)]
190 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
191 vm.current_call_frame
192 .increase_consumed_gas(gas_cost::TLOAD)?;
193
194 let key = vm.current_call_frame.stack.pop1()?;
195 vm.current_call_frame
196 .stack
197 .push(vm.substate.get_transient(&vm.current_call_frame.to, &key))?;
198
199 Ok(OpcodeResult::Continue)
200 }
201}
202
203pub struct OpTStoreHandler;
205impl OpcodeHandler for OpTStoreHandler {
206 #[inline(always)]
207 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
208 if vm.current_call_frame.is_static {
209 return Err(ExceptionalHalt::OpcodeNotAllowedInStaticContext.into());
210 }
211
212 vm.current_call_frame
213 .increase_consumed_gas(gas_cost::TSTORE)?;
214
215 let [key, value] = *vm.current_call_frame.stack.pop()?;
216 vm.substate
217 .set_transient(&vm.current_call_frame.to, &key, value);
218
219 Ok(OpcodeResult::Continue)
220 }
221}
222
223pub struct OpSLoadHandler;
225impl OpcodeHandler for OpSLoadHandler {
226 #[inline(always)]
227 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
228 let storage_slot_key = vm.current_call_frame.stack.pop1()?;
229 let address = vm.current_call_frame.to;
230 let key = {
231 #[expect(unsafe_code)]
232 unsafe {
233 let mut hash = mem::transmute::<U256, H256>(storage_slot_key);
234 hash.0.reverse();
235 hash
236 }
237 };
238
239 vm.current_call_frame
240 .increase_consumed_gas(gas_cost::sload(
241 vm.substate.add_accessed_slot(address, key),
242 )?)?;
243
244 vm.record_storage_slot_to_bal(address, storage_slot_key);
246
247 let value = vm.get_storage_value(address, key)?;
248 vm.current_call_frame.stack.push(value)?;
249
250 Ok(OpcodeResult::Continue)
251 }
252}
253
254pub struct OpSStoreHandler;
256impl OpcodeHandler for OpSStoreHandler {
257 #[inline(always)]
258 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
259 if vm.current_call_frame.is_static {
260 return Err(ExceptionalHalt::OpcodeNotAllowedInStaticContext.into());
261 }
262
263 if vm.current_call_frame.gas_remaining <= SSTORE_STIPEND {
265 return Err(ExceptionalHalt::OutOfGas.into());
266 }
267
268 let [storage_slot_key, value] = *vm.current_call_frame.stack.pop()?;
269 let to = vm.current_call_frame.to;
270 #[expect(unsafe_code)]
271 let key = unsafe {
272 let mut hash = mem::transmute::<U256, H256>(storage_slot_key);
273 hash.0.reverse();
274 hash
275 };
276
277 let (current_value, original_value, storage_slot_was_cold) =
278 vm.access_storage_slot_for_sstore(to, key)?;
279
280 vm.record_storage_slot_to_bal(to, storage_slot_key);
284
285 let fork = vm.env.config.fork;
286
287 let needs_state_gas = fork >= Fork::Amsterdam
291 && value != current_value
292 && current_value == original_value
293 && original_value.is_zero()
294 && !value.is_zero();
295
296 vm.current_call_frame
297 .increase_consumed_gas(gas_cost::sstore(
298 original_value,
299 current_value,
300 value,
301 storage_slot_was_cold,
302 fork,
303 )?)?;
304
305 if needs_state_gas {
306 vm.increase_state_gas(vm.state_gas_storage_set)?;
307 }
308 let is_zero_to_n_to_zero_amsterdam = fork >= Fork::Amsterdam
312 && value != current_value
313 && current_value != original_value
314 && value == original_value
315 && original_value.is_zero();
316
317 if value != current_value {
318 const REMOVE_SLOT_COST: i64 = 4800;
320 const RESTORE_EMPTY_SLOT_COST: i64 = 19900;
321 const RESTORE_SLOT_COST: i64 = 2800;
322
323 let mut delta = 0i64;
325 #[expect(
326 clippy::arithmetic_side_effects,
327 reason = "delta additions are bounded by known constants"
328 )]
329 if current_value == original_value {
330 if !original_value.is_zero() && value.is_zero() {
331 delta += REMOVE_SLOT_COST;
332 }
333 } else {
334 if !original_value.is_zero() {
335 if current_value.is_zero() {
336 delta -= REMOVE_SLOT_COST;
337 } else if value.is_zero() {
338 delta += REMOVE_SLOT_COST;
339 }
340 }
341
342 if value == original_value {
343 if original_value.is_zero() {
344 if fork >= Fork::Amsterdam {
349 delta += RESTORE_SLOT_COST; } else {
351 delta += RESTORE_EMPTY_SLOT_COST;
352 }
353 } else {
354 delta += RESTORE_SLOT_COST;
355 }
356 }
357 }
358
359 match vm.substate.refunded_gas.checked_add_signed(delta) {
361 Some(refunded_gas) => vm.substate.refunded_gas = refunded_gas,
362 None if delta < 0 => return Err(InternalError::Underflow.into()),
363 None => return Err(InternalError::Overflow.into()),
364 }
365 }
366
367 if is_zero_to_n_to_zero_amsterdam {
369 vm.credit_state_gas_refund(vm.state_gas_storage_set)?;
370 }
371
372 if value != current_value {
373 vm.update_account_storage(to, key, storage_slot_key, value, current_value)?;
374 }
375
376 Ok(OpcodeResult::Continue)
377 }
378}
379
380pub struct OpJumpDestHandler;
382impl OpcodeHandler for OpJumpDestHandler {
383 #[inline(always)]
384 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
385 vm.current_call_frame
386 .increase_consumed_gas(gas_cost::JUMPDEST)?;
387
388 Ok(OpcodeResult::Continue)
389 }
390}
391
392pub struct OpJumpHandler;
394impl OpcodeHandler for OpJumpHandler {
395 #[inline(always)]
396 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
397 vm.current_call_frame
398 .increase_consumed_gas(gas_cost::JUMP)?;
399
400 let target = vm.current_call_frame.stack.pop1()?;
401 jump(vm, target.try_into().unwrap_or(usize::MAX), gas_cost::JUMP)?;
402
403 Ok(OpcodeResult::Continue)
404 }
405}
406
407pub struct OpJumpIHandler;
409impl OpcodeHandler for OpJumpIHandler {
410 #[inline(always)]
411 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
412 vm.current_call_frame
413 .increase_consumed_gas(gas_cost::JUMPI)?;
414
415 let [target, condition] = *vm.current_call_frame.stack.pop()?;
416 if !condition.is_zero() {
417 jump(vm, target.try_into().unwrap_or(usize::MAX), gas_cost::JUMPI)?;
418 }
419
420 Ok(OpcodeResult::Continue)
421 }
422}
423
424fn jump(vm: &mut VM<'_>, target: usize, parent_gas_cost: u64) -> Result<(), VMError> {
433 #[expect(clippy::as_conversions, reason = "safe")]
437 if vm
438 .current_call_frame
439 .bytecode
440 .dispatch_buf()
441 .get(target)
442 .is_some_and(|&value| {
443 value == Opcode::JUMPDEST as u8
444 && vm
445 .current_call_frame
446 .bytecode
447 .jump_targets
448 .binary_search(&(target as u32))
449 .is_ok()
450 })
451 {
452 if vm.opcode_tracer.active {
453 vm.opcode_tracer.last_opcode_gas_cost = Some(parent_gas_cost);
456
457 let synth = build_jumpdest_step(vm, target);
459
460 vm.current_call_frame.pc = target.wrapping_add(1);
462 vm.current_call_frame
463 .increase_consumed_gas(gas_cost::JUMPDEST)?;
464
465 vm.opcode_tracer.synthesize_step(synth);
466 } else {
467 vm.current_call_frame.pc = target.wrapping_add(1);
469 vm.current_call_frame
470 .increase_consumed_gas(gas_cost::JUMPDEST)?;
471 }
472 Ok(())
473 } else {
474 Err(ExceptionalHalt::InvalidJump.into())
476 }
477}
478
479#[expect(
485 clippy::as_conversions,
486 reason = "pc/depth/mem_size bounded; fit in target types"
487)]
488fn build_jumpdest_step(vm: &VM<'_>, target: usize) -> ethrex_common::tracing::OpcodeStep {
489 use crate::opcode_tracer::build_step;
490 use bytes::Bytes;
491
492 let cfg = &vm.opcode_tracer.cfg;
493 let gas = vm.current_call_frame.gas_remaining.max(0) as u64;
494 let depth = (vm.call_frames.len() as u32).saturating_add(1);
495 let refund = vm.substate.refunded_gas;
496 let mem_size = vm.current_call_frame.memory.len() as u64;
497
498 let stack_view = if cfg.disable_stack {
499 Vec::new()
500 } else {
501 vm.collect_stack_for_trace()
502 };
503 let mem_view = if cfg.enable_memory {
504 vm.collect_memory_for_trace()
505 } else {
506 Vec::new()
507 };
508 let return_data = if cfg.enable_return_data {
509 vm.current_call_frame.sub_return_data.clone()
510 } else {
511 Bytes::new()
512 };
513
514 build_step(
515 cfg,
516 target as u64,
517 Opcode::JUMPDEST as u8,
518 gas,
519 gas_cost::JUMPDEST,
520 depth,
521 refund,
522 &stack_view,
523 &mem_view,
524 mem_size,
525 &return_data,
526 None,
527 )
528}