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 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 let fork = vm.env.config.fork;
270
271 if fork < Fork::Amsterdam && vm.current_call_frame.gas_remaining <= SSTORE_STIPEND {
274 return Err(ExceptionalHalt::OutOfGas.into());
275 }
276
277 let [storage_slot_key, value] = *vm.current_call_frame.stack.pop()?;
278 let to = vm.current_call_frame.to;
279 #[expect(unsafe_code)]
280 let key = unsafe {
281 let mut hash = mem::transmute::<U256, H256>(storage_slot_key);
282 hash.0.reverse();
283 hash
284 };
285
286 if fork >= Fork::Amsterdam {
287 let is_cold = !vm.substate.is_slot_accessed(&to, &key);
295 let access_cost = if is_cold {
296 gas_cost::cold_storage_access_cost(fork)
297 } else {
298 SSTORE_DEFAULT_DYNAMIC
299 };
300 let sstore_stipend_floor = u64::try_from(SSTORE_STIPEND)
301 .map_err(|_| InternalError::TypeConversion)?
302 .checked_add(1)
303 .ok_or(InternalError::Overflow)?;
304 vm.current_call_frame
305 .check_gas(access_cost.max(sstore_stipend_floor))?;
306 }
307
308 let (current_value, original_value, storage_slot_was_cold) =
309 vm.access_storage_slot_for_sstore(to, key)?;
310
311 vm.record_storage_slot_to_bal(to, storage_slot_key);
316
317 if vm.validation_observer.active {
320 vm.validation_check_sstore(to, key);
321 }
322
323 let needs_state_gas = fork >= Fork::Amsterdam
327 && value != current_value
328 && current_value == original_value
329 && original_value.is_zero()
330 && !value.is_zero();
331
332 vm.current_call_frame
333 .increase_consumed_gas(gas_cost::sstore(
334 original_value,
335 current_value,
336 value,
337 storage_slot_was_cold,
338 fork,
339 )?)?;
340
341 if needs_state_gas {
342 vm.increase_state_gas(vm.state_gas_storage_set)?;
343 }
344 let is_zero_to_n_to_zero_amsterdam = fork >= Fork::Amsterdam
348 && value != current_value
349 && current_value != original_value
350 && value == original_value
351 && original_value.is_zero();
352
353 if value != current_value {
354 let (remove_slot_cost, restore_empty_slot_cost, restore_slot_cost): (i64, i64, i64) =
360 if fork >= Fork::Amsterdam {
361 (STORAGE_CLEAR_REFUND_AMSTERDAM, 10000, 10000)
363 } else {
364 (4800, 19900, 2800)
366 };
367
368 let mut delta = 0i64;
370 #[expect(
371 clippy::arithmetic_side_effects,
372 reason = "delta additions are bounded by known constants"
373 )]
374 if current_value == original_value {
375 if !original_value.is_zero() && value.is_zero() {
376 delta += remove_slot_cost;
377 }
378 } else {
379 if !original_value.is_zero() {
380 if current_value.is_zero() {
381 delta -= remove_slot_cost;
382 } else if value.is_zero() {
383 delta += remove_slot_cost;
384 }
385 }
386
387 if value == original_value {
388 if original_value.is_zero() {
389 delta += restore_empty_slot_cost;
390 } else {
391 delta += restore_slot_cost;
392 }
393 }
394 }
395
396 match vm.substate.refunded_gas.checked_add_signed(delta) {
398 Some(refunded_gas) => vm.substate.refunded_gas = refunded_gas,
399 None if delta < 0 => return Err(InternalError::Underflow.into()),
400 None => return Err(InternalError::Overflow.into()),
401 }
402 }
403
404 if is_zero_to_n_to_zero_amsterdam {
406 vm.credit_state_gas_refund(vm.state_gas_storage_set)?;
407 }
408
409 if value != current_value {
410 vm.update_account_storage(to, key, storage_slot_key, value, current_value)?;
411 }
412
413 Ok(OpcodeResult::Continue)
414 }
415}
416
417pub struct OpJumpDestHandler;
419impl OpcodeHandler for OpJumpDestHandler {
420 #[inline(always)]
421 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
422 vm.current_call_frame
423 .increase_consumed_gas(gas_cost::JUMPDEST)?;
424
425 Ok(OpcodeResult::Continue)
426 }
427}
428
429pub struct OpJumpHandler;
431impl OpcodeHandler for OpJumpHandler {
432 #[inline(always)]
433 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
434 vm.current_call_frame
435 .increase_consumed_gas(gas_cost::JUMP)?;
436
437 let target = vm.current_call_frame.stack.pop1()?;
438 jump(vm, target.try_into().unwrap_or(usize::MAX), gas_cost::JUMP)?;
439
440 Ok(OpcodeResult::Continue)
441 }
442}
443
444pub struct OpJumpIHandler;
446impl OpcodeHandler for OpJumpIHandler {
447 #[inline(always)]
448 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
449 vm.current_call_frame
450 .increase_consumed_gas(gas_cost::JUMPI)?;
451
452 let [target, condition] = *vm.current_call_frame.stack.pop()?;
453 if !condition.is_zero() {
454 jump(vm, target.try_into().unwrap_or(usize::MAX), gas_cost::JUMPI)?;
455 }
456
457 Ok(OpcodeResult::Continue)
458 }
459}
460
461fn jump(vm: &mut VM<'_>, target: usize, parent_gas_cost: u64) -> Result<(), VMError> {
470 #[expect(clippy::as_conversions, reason = "safe")]
474 if vm
475 .current_call_frame
476 .bytecode
477 .dispatch_buf()
478 .get(target)
479 .is_some_and(|&value| {
480 value == Opcode::JUMPDEST as u8
481 && vm
482 .current_call_frame
483 .bytecode
484 .jump_targets
485 .binary_search(&(target as u32))
486 .is_ok()
487 })
488 {
489 if vm.opcode_tracer.active {
490 vm.opcode_tracer.last_opcode_gas_cost = Some(parent_gas_cost);
493
494 let synth = build_jumpdest_step(vm, target);
496
497 vm.current_call_frame.pc = target.wrapping_add(1);
499 vm.current_call_frame
500 .increase_consumed_gas(gas_cost::JUMPDEST)?;
501
502 vm.opcode_tracer.synthesize_step(synth);
503 } else {
504 vm.current_call_frame.pc = target.wrapping_add(1);
506 vm.current_call_frame
507 .increase_consumed_gas(gas_cost::JUMPDEST)?;
508 }
509 Ok(())
510 } else {
511 Err(ExceptionalHalt::InvalidJump.into())
513 }
514}
515
516#[expect(
522 clippy::as_conversions,
523 reason = "pc/depth/mem_size bounded; fit in target types"
524)]
525fn build_jumpdest_step(vm: &VM<'_>, target: usize) -> ethrex_common::tracing::OpcodeStep {
526 use crate::opcode_tracer::build_step;
527 use bytes::Bytes;
528
529 let cfg = &vm.opcode_tracer.cfg;
530 let gas = vm.current_call_frame.gas_remaining.max(0) as u64;
531 let depth = (vm.call_frames.len() as u32).saturating_add(1);
532 let refund = vm.substate.refunded_gas;
533 let mem_size = vm.current_call_frame.memory.len() as u64;
534
535 let stack_view = if cfg.disable_stack {
536 Vec::new()
537 } else {
538 vm.collect_stack_for_trace()
539 };
540 let mem_view = if cfg.enable_memory {
541 vm.collect_memory_for_trace()
542 } else {
543 Vec::new()
544 };
545 let return_data = if cfg.enable_return_data {
546 vm.current_call_frame.sub_return_data.clone()
547 } else {
548 Bytes::new()
549 };
550
551 build_step(
552 cfg,
553 target as u64,
554 Opcode::JUMPDEST as u8,
555 gas,
556 gas_cost::JUMPDEST,
557 depth,
558 refund,
559 &stack_view,
560 &mem_view,
561 mem_size,
562 &return_data,
563 None,
564 )
565}