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 let value = vm.get_storage_value(address, key)?;
249 vm.current_call_frame.stack.push(value)?;
250
251 Ok(OpcodeResult::Continue)
252 }
253}
254
255pub struct OpSStoreHandler;
257impl OpcodeHandler for OpSStoreHandler {
258 #[inline(always)]
259 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
260 if vm.current_call_frame.is_static {
261 return Err(ExceptionalHalt::OpcodeNotAllowedInStaticContext.into());
262 }
263
264 if vm.current_call_frame.gas_remaining <= SSTORE_STIPEND {
266 return Err(ExceptionalHalt::OutOfGas.into());
267 }
268
269 let [storage_slot_key, value] = *vm.current_call_frame.stack.pop()?;
270 let to = vm.current_call_frame.to;
271 #[expect(unsafe_code)]
272 let key = unsafe {
273 let mut hash = mem::transmute::<U256, H256>(storage_slot_key);
274 hash.0.reverse();
275 hash
276 };
277
278 let (current_value, original_value, storage_slot_was_cold) =
279 vm.access_storage_slot_for_sstore(to, key)?;
280
281 vm.record_storage_slot_to_bal(to, storage_slot_key);
285
286 let fork = vm.env.config.fork;
287
288 let needs_state_gas = fork >= Fork::Amsterdam
292 && value != current_value
293 && current_value == original_value
294 && original_value.is_zero()
295 && !value.is_zero();
296
297 vm.current_call_frame
298 .increase_consumed_gas(gas_cost::sstore(
299 original_value,
300 current_value,
301 value,
302 storage_slot_was_cold,
303 fork,
304 )?)?;
305
306 if needs_state_gas {
307 vm.increase_state_gas(vm.state_gas_storage_set)?;
308 }
309 let is_zero_to_n_to_zero_amsterdam = fork >= Fork::Amsterdam
313 && value != current_value
314 && current_value != original_value
315 && value == original_value
316 && original_value.is_zero();
317
318 if value != current_value {
319 let (remove_slot_cost, restore_empty_slot_cost, restore_slot_cost): (i64, i64, i64) =
325 if fork >= Fork::Amsterdam {
326 (STORAGE_CLEAR_REFUND_AMSTERDAM, 10000, 10000)
328 } else {
329 (4800, 19900, 2800)
331 };
332
333 let mut delta = 0i64;
335 #[expect(
336 clippy::arithmetic_side_effects,
337 reason = "delta additions are bounded by known constants"
338 )]
339 if current_value == original_value {
340 if !original_value.is_zero() && value.is_zero() {
341 delta += remove_slot_cost;
342 }
343 } else {
344 if !original_value.is_zero() {
345 if current_value.is_zero() {
346 delta -= remove_slot_cost;
347 } else if value.is_zero() {
348 delta += remove_slot_cost;
349 }
350 }
351
352 if value == original_value {
353 if original_value.is_zero() {
354 delta += restore_empty_slot_cost;
355 } else {
356 delta += restore_slot_cost;
357 }
358 }
359 }
360
361 match vm.substate.refunded_gas.checked_add_signed(delta) {
363 Some(refunded_gas) => vm.substate.refunded_gas = refunded_gas,
364 None if delta < 0 => return Err(InternalError::Underflow.into()),
365 None => return Err(InternalError::Overflow.into()),
366 }
367 }
368
369 if is_zero_to_n_to_zero_amsterdam {
371 vm.credit_state_gas_refund(vm.state_gas_storage_set)?;
372 }
373
374 if value != current_value {
375 vm.update_account_storage(to, key, storage_slot_key, value, current_value)?;
376 }
377
378 Ok(OpcodeResult::Continue)
379 }
380}
381
382pub struct OpJumpDestHandler;
384impl OpcodeHandler for OpJumpDestHandler {
385 #[inline(always)]
386 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
387 vm.current_call_frame
388 .increase_consumed_gas(gas_cost::JUMPDEST)?;
389
390 Ok(OpcodeResult::Continue)
391 }
392}
393
394pub struct OpJumpHandler;
396impl OpcodeHandler for OpJumpHandler {
397 #[inline(always)]
398 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
399 vm.current_call_frame
400 .increase_consumed_gas(gas_cost::JUMP)?;
401
402 let target = vm.current_call_frame.stack.pop1()?;
403 jump(vm, target.try_into().unwrap_or(usize::MAX), gas_cost::JUMP)?;
404
405 Ok(OpcodeResult::Continue)
406 }
407}
408
409pub struct OpJumpIHandler;
411impl OpcodeHandler for OpJumpIHandler {
412 #[inline(always)]
413 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
414 vm.current_call_frame
415 .increase_consumed_gas(gas_cost::JUMPI)?;
416
417 let [target, condition] = *vm.current_call_frame.stack.pop()?;
418 if !condition.is_zero() {
419 jump(vm, target.try_into().unwrap_or(usize::MAX), gas_cost::JUMPI)?;
420 }
421
422 Ok(OpcodeResult::Continue)
423 }
424}
425
426fn jump(vm: &mut VM<'_>, target: usize, parent_gas_cost: u64) -> Result<(), VMError> {
435 #[expect(clippy::as_conversions, reason = "safe")]
439 if vm
440 .current_call_frame
441 .bytecode
442 .dispatch_buf()
443 .get(target)
444 .is_some_and(|&value| {
445 value == Opcode::JUMPDEST as u8
446 && vm
447 .current_call_frame
448 .bytecode
449 .jump_targets
450 .binary_search(&(target as u32))
451 .is_ok()
452 })
453 {
454 if vm.opcode_tracer.active {
455 vm.opcode_tracer.last_opcode_gas_cost = Some(parent_gas_cost);
458
459 let synth = build_jumpdest_step(vm, target);
461
462 vm.current_call_frame.pc = target.wrapping_add(1);
464 vm.current_call_frame
465 .increase_consumed_gas(gas_cost::JUMPDEST)?;
466
467 vm.opcode_tracer.synthesize_step(synth);
468 } else {
469 vm.current_call_frame.pc = target.wrapping_add(1);
471 vm.current_call_frame
472 .increase_consumed_gas(gas_cost::JUMPDEST)?;
473 }
474 Ok(())
475 } else {
476 Err(ExceptionalHalt::InvalidJump.into())
478 }
479}
480
481#[expect(
487 clippy::as_conversions,
488 reason = "pc/depth/mem_size bounded; fit in target types"
489)]
490fn build_jumpdest_step(vm: &VM<'_>, target: usize) -> ethrex_common::tracing::OpcodeStep {
491 use crate::opcode_tracer::build_step;
492 use bytes::Bytes;
493
494 let cfg = &vm.opcode_tracer.cfg;
495 let gas = vm.current_call_frame.gas_remaining.max(0) as u64;
496 let depth = (vm.call_frames.len() as u32).saturating_add(1);
497 let refund = vm.substate.refunded_gas;
498 let mem_size = vm.current_call_frame.memory.len() as u64;
499
500 let stack_view = if cfg.disable_stack {
501 Vec::new()
502 } else {
503 vm.collect_stack_for_trace()
504 };
505 let mem_view = if cfg.enable_memory {
506 vm.collect_memory_for_trace()
507 } else {
508 Vec::new()
509 };
510 let return_data = if cfg.enable_return_data {
511 vm.current_call_frame.sub_return_data.clone()
512 } else {
513 Bytes::new()
514 };
515
516 build_step(
517 cfg,
518 target as u64,
519 Opcode::JUMPDEST as u8,
520 gas,
521 gas_cost::JUMPDEST,
522 depth,
523 refund,
524 &stack_view,
525 &mem_view,
526 mem_size,
527 &return_data,
528 None,
529 )
530}