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, STORAGE_CLEAR_REFUND_AMSTERDAM},
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 vm.env.config.fork,
243 )?)?;
244
245 vm.record_storage_slot_to_bal(address, storage_slot_key);
247
248 if vm.validation_observer.active {
250 vm.validation_check_sload(address, key);
251 }
252
253 let value = vm.get_storage_value(address, key)?;
254 vm.current_call_frame.stack.push(value)?;
255
256 Ok(OpcodeResult::Continue)
257 }
258}
259
260pub struct OpSStoreHandler;
262impl OpcodeHandler for OpSStoreHandler {
263 #[inline(always)]
264 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
265 if vm.current_call_frame.is_static {
266 return Err(ExceptionalHalt::OpcodeNotAllowedInStaticContext.into());
267 }
268
269 if vm.current_call_frame.gas_remaining <= SSTORE_STIPEND {
271 return Err(ExceptionalHalt::OutOfGas.into());
272 }
273
274 let [storage_slot_key, value] = *vm.current_call_frame.stack.pop()?;
275 let to = vm.current_call_frame.to;
276 #[expect(unsafe_code)]
277 let key = unsafe {
278 let mut hash = mem::transmute::<U256, H256>(storage_slot_key);
279 hash.0.reverse();
280 hash
281 };
282
283 let (current_value, original_value, storage_slot_was_cold) =
284 vm.access_storage_slot_for_sstore(to, key)?;
285
286 vm.record_storage_slot_to_bal(to, storage_slot_key);
290
291 if vm.validation_observer.active {
294 vm.validation_check_sstore(to, key);
295 }
296
297 let fork = vm.env.config.fork;
298
299 let needs_state_gas = fork >= Fork::Amsterdam
303 && value != current_value
304 && current_value == original_value
305 && original_value.is_zero()
306 && !value.is_zero();
307
308 vm.current_call_frame
309 .increase_consumed_gas(gas_cost::sstore(
310 original_value,
311 current_value,
312 value,
313 storage_slot_was_cold,
314 fork,
315 )?)?;
316
317 if needs_state_gas {
318 vm.increase_state_gas(vm.state_gas_storage_set)?;
319 }
320 let is_zero_to_n_to_zero_amsterdam = fork >= Fork::Amsterdam
324 && value != current_value
325 && current_value != original_value
326 && value == original_value
327 && original_value.is_zero();
328
329 if value != current_value {
330 let (remove_slot_cost, restore_empty_slot_cost, restore_slot_cost): (i64, i64, i64) =
336 if fork >= Fork::Amsterdam {
337 (STORAGE_CLEAR_REFUND_AMSTERDAM, 10000, 10000)
339 } else {
340 (4800, 19900, 2800)
342 };
343
344 let mut delta = 0i64;
346 #[expect(
347 clippy::arithmetic_side_effects,
348 reason = "delta additions are bounded by known constants"
349 )]
350 if current_value == original_value {
351 if !original_value.is_zero() && value.is_zero() {
352 delta += remove_slot_cost;
353 }
354 } else {
355 if !original_value.is_zero() {
356 if current_value.is_zero() {
357 delta -= remove_slot_cost;
358 } else if value.is_zero() {
359 delta += remove_slot_cost;
360 }
361 }
362
363 if value == original_value {
364 if original_value.is_zero() {
365 delta += restore_empty_slot_cost;
366 } else {
367 delta += restore_slot_cost;
368 }
369 }
370 }
371
372 match vm.substate.refunded_gas.checked_add_signed(delta) {
374 Some(refunded_gas) => vm.substate.refunded_gas = refunded_gas,
375 None if delta < 0 => return Err(InternalError::Underflow.into()),
376 None => return Err(InternalError::Overflow.into()),
377 }
378 }
379
380 if is_zero_to_n_to_zero_amsterdam {
382 vm.credit_state_gas_refund(vm.state_gas_storage_set)?;
383 }
384
385 if value != current_value {
386 vm.update_account_storage(to, key, storage_slot_key, value, current_value)?;
387 }
388
389 Ok(OpcodeResult::Continue)
390 }
391}
392
393pub struct OpJumpDestHandler;
395impl OpcodeHandler for OpJumpDestHandler {
396 #[inline(always)]
397 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
398 vm.current_call_frame
399 .increase_consumed_gas(gas_cost::JUMPDEST)?;
400
401 Ok(OpcodeResult::Continue)
402 }
403}
404
405pub struct OpJumpHandler;
407impl OpcodeHandler for OpJumpHandler {
408 #[inline(always)]
409 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
410 vm.current_call_frame
411 .increase_consumed_gas(gas_cost::JUMP)?;
412
413 let target = vm.current_call_frame.stack.pop1()?;
414 jump(vm, target.try_into().unwrap_or(usize::MAX), gas_cost::JUMP)?;
415
416 Ok(OpcodeResult::Continue)
417 }
418}
419
420pub struct OpJumpIHandler;
422impl OpcodeHandler for OpJumpIHandler {
423 #[inline(always)]
424 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
425 vm.current_call_frame
426 .increase_consumed_gas(gas_cost::JUMPI)?;
427
428 let [target, condition] = *vm.current_call_frame.stack.pop()?;
429 if !condition.is_zero() {
430 jump(vm, target.try_into().unwrap_or(usize::MAX), gas_cost::JUMPI)?;
431 }
432
433 Ok(OpcodeResult::Continue)
434 }
435}
436
437fn jump(vm: &mut VM<'_>, target: usize, parent_gas_cost: u64) -> Result<(), VMError> {
446 #[expect(clippy::as_conversions, reason = "safe")]
450 if vm
451 .current_call_frame
452 .bytecode
453 .dispatch_buf()
454 .get(target)
455 .is_some_and(|&value| {
456 value == Opcode::JUMPDEST as u8
457 && vm
458 .current_call_frame
459 .bytecode
460 .jump_targets
461 .binary_search(&(target as u32))
462 .is_ok()
463 })
464 {
465 if vm.opcode_tracer.active {
466 vm.opcode_tracer.last_opcode_gas_cost = Some(parent_gas_cost);
469
470 let synth = build_jumpdest_step(vm, target);
472
473 vm.current_call_frame.pc = target.wrapping_add(1);
475 vm.current_call_frame
476 .increase_consumed_gas(gas_cost::JUMPDEST)?;
477
478 vm.opcode_tracer.synthesize_step(synth);
479 } else {
480 vm.current_call_frame.pc = target.wrapping_add(1);
482 vm.current_call_frame
483 .increase_consumed_gas(gas_cost::JUMPDEST)?;
484 }
485 Ok(())
486 } else {
487 Err(ExceptionalHalt::InvalidJump.into())
489 }
490}
491
492#[expect(
498 clippy::as_conversions,
499 reason = "pc/depth/mem_size bounded; fit in target types"
500)]
501fn build_jumpdest_step(vm: &VM<'_>, target: usize) -> ethrex_common::tracing::OpcodeStep {
502 use crate::opcode_tracer::build_step;
503 use bytes::Bytes;
504
505 let cfg = &vm.opcode_tracer.cfg;
506 let gas = vm.current_call_frame.gas_remaining.max(0) as u64;
507 let depth = (vm.call_frames.len() as u32).saturating_add(1);
508 let refund = vm.substate.refunded_gas;
509 let mem_size = vm.current_call_frame.memory.len() as u64;
510
511 let stack_view = if cfg.disable_stack {
512 Vec::new()
513 } else {
514 vm.collect_stack_for_trace()
515 };
516 let mem_view = if cfg.enable_memory {
517 vm.collect_memory_for_trace()
518 } else {
519 Vec::new()
520 };
521 let return_data = if cfg.enable_return_data {
522 vm.current_call_frame.sub_return_data.clone()
523 } else {
524 Bytes::new()
525 };
526
527 build_step(
528 cfg,
529 target as u64,
530 Opcode::JUMPDEST as u8,
531 gas,
532 gas_cost::JUMPDEST,
533 depth,
534 refund,
535 &stack_view,
536 &mem_view,
537 mem_size,
538 &return_data,
539 None,
540 )
541}