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_DEFAULT_DYNAMIC, 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 let new_size = calculate_memory_size(offset, WORD_SIZE_IN_BYTES_USIZE)?;
86 vm.current_call_frame
87 .increase_consumed_gas(gas_cost::mload(
88 new_size,
89 vm.current_call_frame.memory.len(),
90 )?)?;
91
92 let word = vm
93 .current_call_frame
94 .memory
95 .load_word_prerounded(offset, new_size)?;
96 #[expect(unsafe_code, reason = "top validated above; MLOAD is stack-neutral")]
99 unsafe {
100 vm.current_call_frame.stack.set_top_unchecked(word);
101 }
102
103 Ok(OpcodeResult::Continue)
104 }
105}
106
107pub struct OpMStoreHandler;
109impl OpcodeHandler for OpMStoreHandler {
110 #[inline(always)]
111 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
112 let [offset, value] = *vm.current_call_frame.stack.pop()?;
113
114 if vm.debug_mode.enabled && vm.debug_mode.handle_debug(offset, value)? {
116 return Ok(OpcodeResult::Continue);
117 }
118
119 let offset = u256_to_usize(offset)?;
120 let new_size = calculate_memory_size(offset, WORD_SIZE_IN_BYTES_USIZE)?;
123 vm.current_call_frame
124 .increase_consumed_gas(gas_cost::mstore(
125 new_size,
126 vm.current_call_frame.memory.len(),
127 )?)?;
128
129 vm.current_call_frame
130 .memory
131 .store_word_prerounded(offset, value, new_size)?;
132
133 Ok(OpcodeResult::Continue)
134 }
135}
136
137pub struct OpMStore8Handler;
139impl OpcodeHandler for OpMStore8Handler {
140 #[inline(always)]
141 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
142 let [offset, value] = *vm.current_call_frame.stack.pop()?;
143 let offset = u256_to_usize(offset)?;
144 let value = value.byte(0);
145
146 vm.current_call_frame
147 .increase_consumed_gas(gas_cost::mstore8(
148 calculate_memory_size(offset, size_of::<u8>())?,
149 vm.current_call_frame.memory.len(),
150 )?)?;
151
152 vm.current_call_frame
153 .memory
154 .store_data(offset, slice::from_ref(&value))?;
155
156 Ok(OpcodeResult::Continue)
157 }
158}
159
160pub struct OpMCopyHandler;
162impl OpcodeHandler for OpMCopyHandler {
163 #[inline(always)]
164 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
165 let [dst_offset, src_offset, len] = *vm.current_call_frame.stack.pop()?;
166 let (len, dst_offset) = size_offset_to_usize(len, dst_offset)?;
167 let src_offset = u256_to_usize(src_offset).unwrap_or(usize::MAX);
168
169 vm.current_call_frame
170 .increase_consumed_gas(gas_cost::mcopy(
171 calculate_memory_size(src_offset.max(dst_offset), len)?,
172 vm.current_call_frame.memory.len(),
173 len,
174 )?)?;
175
176 vm.current_call_frame
177 .memory
178 .copy_within(src_offset, dst_offset, len)?;
179
180 Ok(OpcodeResult::Continue)
181 }
182}
183
184pub struct OpMSizeHandler;
186impl OpcodeHandler for OpMSizeHandler {
187 #[inline(always)]
188 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
189 vm.current_call_frame
190 .increase_consumed_gas(gas_cost::MSIZE)?;
191
192 vm.current_call_frame
193 .stack
194 .push(vm.current_call_frame.memory.len().into())?;
195
196 Ok(OpcodeResult::Continue)
197 }
198}
199
200pub struct OpTLoadHandler;
202impl OpcodeHandler for OpTLoadHandler {
203 #[inline(always)]
204 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
205 vm.current_call_frame
206 .increase_consumed_gas(gas_cost::TLOAD)?;
207
208 let key = vm.current_call_frame.stack.pop1()?;
209 vm.current_call_frame
210 .stack
211 .push(vm.substate.get_transient(&vm.current_call_frame.to, &key))?;
212
213 Ok(OpcodeResult::Continue)
214 }
215}
216
217pub struct OpTStoreHandler;
219impl OpcodeHandler for OpTStoreHandler {
220 #[inline(always)]
221 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
222 if vm.current_call_frame.is_static {
223 return Err(ExceptionalHalt::OpcodeNotAllowedInStaticContext.into());
224 }
225
226 vm.current_call_frame
227 .increase_consumed_gas(gas_cost::TSTORE)?;
228
229 let [key, value] = *vm.current_call_frame.stack.pop()?;
230 vm.substate
231 .set_transient(&vm.current_call_frame.to, &key, value);
232
233 Ok(OpcodeResult::Continue)
234 }
235}
236
237pub struct OpSLoadHandler;
239impl OpcodeHandler for OpSLoadHandler {
240 #[inline(always)]
241 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
242 let storage_slot_key = vm.current_call_frame.stack.pop1()?;
243 let address = vm.current_call_frame.to;
244 let key = {
245 #[expect(unsafe_code)]
246 unsafe {
247 let mut hash = mem::transmute::<U256, H256>(storage_slot_key);
248 hash.0.reverse();
249 hash
250 }
251 };
252
253 vm.current_call_frame
254 .increase_consumed_gas(gas_cost::sload(
255 vm.substate.add_accessed_slot(address, key),
256 vm.env.config.fork,
257 )?)?;
258
259 vm.record_storage_slot_to_bal(address, storage_slot_key);
261
262 if vm.validation_observer.active {
264 vm.validation_check_sload(address, key);
265 }
266
267 let value = vm.get_storage_value(address, key)?;
268 vm.current_call_frame.stack.push(value)?;
269
270 Ok(OpcodeResult::Continue)
271 }
272}
273
274pub struct OpSStoreHandler;
276impl OpcodeHandler for OpSStoreHandler {
277 #[inline(always)]
278 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
279 if vm.current_call_frame.is_static {
280 return Err(ExceptionalHalt::OpcodeNotAllowedInStaticContext.into());
281 }
282
283 let fork = vm.env.config.fork;
284
285 if fork < Fork::Amsterdam && vm.current_call_frame.gas_remaining <= SSTORE_STIPEND {
288 return Err(ExceptionalHalt::OutOfGas.into());
289 }
290
291 let [storage_slot_key, value] = *vm.current_call_frame.stack.pop()?;
292 let to = vm.current_call_frame.to;
293 #[expect(unsafe_code)]
294 let key = unsafe {
295 let mut hash = mem::transmute::<U256, H256>(storage_slot_key);
296 hash.0.reverse();
297 hash
298 };
299
300 if fork >= Fork::Amsterdam {
301 let is_cold = !vm.substate.is_slot_accessed(&to, &key);
309 let access_cost = if is_cold {
310 gas_cost::cold_storage_access_cost(fork)
311 } else {
312 SSTORE_DEFAULT_DYNAMIC
313 };
314 let sstore_stipend_floor = u64::try_from(SSTORE_STIPEND)
315 .map_err(|_| InternalError::TypeConversion)?
316 .checked_add(1)
317 .ok_or(InternalError::Overflow)?;
318 vm.current_call_frame
319 .check_gas(access_cost.max(sstore_stipend_floor))?;
320 }
321
322 let (current_value, original_value, storage_slot_was_cold) =
323 vm.access_storage_slot_for_sstore(to, key)?;
324
325 vm.record_storage_slot_to_bal(to, storage_slot_key);
330
331 if vm.validation_observer.active {
334 vm.validation_check_sstore(to, key);
335 }
336
337 let needs_state_gas = fork >= Fork::Amsterdam
341 && value != current_value
342 && current_value == original_value
343 && original_value.is_zero()
344 && !value.is_zero();
345
346 vm.current_call_frame
347 .increase_consumed_gas(gas_cost::sstore(
348 original_value,
349 current_value,
350 value,
351 storage_slot_was_cold,
352 fork,
353 )?)?;
354
355 if needs_state_gas {
356 vm.increase_state_gas(vm.state_gas_storage_set)?;
357 }
358 let is_zero_to_n_to_zero_amsterdam = fork >= Fork::Amsterdam
362 && value != current_value
363 && current_value != original_value
364 && value == original_value
365 && original_value.is_zero();
366
367 if value != current_value {
368 let (remove_slot_cost, restore_empty_slot_cost, restore_slot_cost): (i64, i64, i64) =
374 if fork >= Fork::Amsterdam {
375 (STORAGE_CLEAR_REFUND_AMSTERDAM, 10000, 10000)
377 } else {
378 (4800, 19900, 2800)
380 };
381
382 let mut delta = 0i64;
384 #[expect(
385 clippy::arithmetic_side_effects,
386 reason = "delta additions are bounded by known constants"
387 )]
388 if current_value == original_value {
389 if !original_value.is_zero() && value.is_zero() {
390 delta += remove_slot_cost;
391 }
392 } else {
393 if !original_value.is_zero() {
394 if current_value.is_zero() {
395 delta -= remove_slot_cost;
396 } else if value.is_zero() {
397 delta += remove_slot_cost;
398 }
399 }
400
401 if value == original_value {
402 if original_value.is_zero() {
403 delta += restore_empty_slot_cost;
404 } else {
405 delta += restore_slot_cost;
406 }
407 }
408 }
409
410 match vm.substate.refunded_gas.checked_add_signed(delta) {
412 Some(refunded_gas) => vm.substate.refunded_gas = refunded_gas,
413 None if delta < 0 => return Err(InternalError::Underflow.into()),
414 None => return Err(InternalError::Overflow.into()),
415 }
416 }
417
418 if is_zero_to_n_to_zero_amsterdam {
420 vm.credit_state_gas_refund(vm.state_gas_storage_set)?;
421 }
422
423 if value != current_value {
424 vm.update_account_storage(to, key, storage_slot_key, value, current_value)?;
425 }
426
427 Ok(OpcodeResult::Continue)
428 }
429}
430
431pub struct OpJumpDestHandler;
433impl OpcodeHandler for OpJumpDestHandler {
434 #[inline(always)]
435 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
436 vm.current_call_frame
437 .increase_consumed_gas(gas_cost::JUMPDEST)?;
438
439 Ok(OpcodeResult::Continue)
440 }
441}
442
443pub struct OpJumpHandler;
445impl OpcodeHandler for OpJumpHandler {
446 #[inline(always)]
447 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
448 vm.current_call_frame
449 .increase_consumed_gas(gas_cost::JUMP)?;
450
451 let target = vm.current_call_frame.stack.pop1()?;
452 jump(vm, target.try_into().unwrap_or(usize::MAX), gas_cost::JUMP)?;
453
454 Ok(OpcodeResult::Continue)
455 }
456}
457
458pub struct OpJumpIHandler;
460impl OpcodeHandler for OpJumpIHandler {
461 #[inline(always)]
462 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
463 vm.current_call_frame
464 .increase_consumed_gas(gas_cost::JUMPI)?;
465
466 let [target, condition] = *vm.current_call_frame.stack.pop()?;
467 if !condition.is_zero() {
468 jump(vm, target.try_into().unwrap_or(usize::MAX), gas_cost::JUMPI)?;
469 }
470
471 Ok(OpcodeResult::Continue)
472 }
473}
474
475fn jump(vm: &mut VM<'_>, target: usize, parent_gas_cost: u64) -> Result<(), VMError> {
486 if vm.current_call_frame.bytecode.is_valid_jumpdest(target) {
490 if vm.opcode_tracer.active {
491 vm.opcode_tracer.last_opcode_gas_cost = Some(parent_gas_cost);
494
495 let synth = build_jumpdest_step(vm, target);
497
498 vm.current_call_frame.pc = target.wrapping_add(1);
500 if let Err(err) = vm
501 .current_call_frame
502 .increase_consumed_gas(gas_cost::JUMPDEST)
503 {
504 let refund = vm.substate.refunded_gas;
512 vm.opcode_tracer
513 .finalize_step(parent_gas_cost, refund, None);
514 if vm.opcode_tracer.synthesize_faulting_step(synth) {
515 vm.opcode_tracer.last_opcode_gas_cost = Some(gas_cost::JUMPDEST);
516 }
517 return Err(err.into());
518 }
519
520 vm.opcode_tracer.synthesize_step(synth);
521 } else {
522 vm.current_call_frame.pc = target.wrapping_add(1);
524 vm.current_call_frame
525 .increase_consumed_gas(gas_cost::JUMPDEST)?;
526 }
527 Ok(())
528 } else {
529 Err(ExceptionalHalt::InvalidJump.into())
531 }
532}
533
534#[expect(
540 clippy::as_conversions,
541 reason = "pc/depth/mem_size bounded; fit in target types"
542)]
543fn build_jumpdest_step(vm: &VM<'_>, target: usize) -> ethrex_common::tracing::OpcodeStep {
544 use crate::opcode_tracer::build_step;
545 use bytes::Bytes;
546
547 let cfg = &vm.opcode_tracer.cfg;
548 let gas = vm.current_call_frame.gas_remaining.max(0) as u64;
549 let depth = (vm.call_frames.len() as u32).saturating_add(1);
550 let refund = vm.substate.refunded_gas;
551 let mem_size = vm.current_call_frame.memory.len() as u64;
552
553 let stack_view = if cfg.disable_stack {
554 Vec::new()
555 } else {
556 vm.collect_stack_for_trace()
557 };
558 let mem_view = if cfg.enable_memory {
559 vm.collect_memory_for_trace()
560 } else {
561 Vec::new()
562 };
563 let return_data = if cfg.enable_return_data {
564 vm.current_call_frame.sub_return_data.clone()
565 } else {
566 Bytes::new()
567 };
568
569 build_step(
570 cfg,
571 target as u64,
572 Opcode::JUMPDEST as u8,
573 gas,
574 gas_cost::JUMPDEST,
575 depth,
576 refund,
577 &stack_view,
578 &mem_view,
579 mem_size,
580 &return_data,
581 None,
582 )
583}