1use crate::{
2 account::LevmAccount,
3 constants::STACK_LIMIT,
4 errors::{ExceptionalHalt, InternalError, VMError},
5 memory::Memory,
6 utils::restore_cache_state,
7 vm::VM,
8};
9use bytes::Bytes;
10use ethrex_common::types::block_access_list::BlockAccessListCheckpoint;
11use ethrex_common::{Address, H256, U256, types::Code};
12use rustc_hash::FxHashMap;
13use std::{
14 fmt,
15 hash::{Hash, Hasher},
16 hint::assert_unchecked,
17};
18
19const U64_PER_U256: usize = U256::MAX.0.len();
21
22#[derive(Clone, PartialEq, Eq)]
23pub struct Stack {
34 pub values: Box<[U256; STACK_LIMIT]>,
35 pub offset: usize,
36}
37
38impl Stack {
39 #[inline]
40 pub fn pop<const N: usize>(&mut self) -> Result<&[U256; N], ExceptionalHalt> {
41 const {
43 assert!(N <= STACK_LIMIT);
44 }
45
46 let next_offset = self.offset.wrapping_add(N);
49
50 #[expect(unsafe_code)]
54 let values = unsafe {
55 self.values
56 .get_unchecked(self.offset..)
57 .first_chunk::<N>()
58 .ok_or(ExceptionalHalt::StackUnderflow)?
59 };
60 self.offset = next_offset;
62
63 Ok(values)
64 }
65
66 #[inline]
67 pub fn pop1(&mut self) -> Result<U256, ExceptionalHalt> {
68 let value = *self
69 .values
70 .get(self.offset)
71 .ok_or(ExceptionalHalt::StackUnderflow)?;
72 self.offset = self.offset.wrapping_add(1);
75
76 Ok(value)
77 }
78
79 #[inline]
84 pub fn top_mut(&mut self) -> Result<&mut U256, ExceptionalHalt> {
85 self.values
86 .get_mut(self.offset)
87 .ok_or(ExceptionalHalt::StackUnderflow)
88 }
89
90 #[inline]
95 pub fn pop1_and_top_mut(&mut self) -> Result<(U256, &mut U256), ExceptionalHalt> {
96 let a = self.pop1()?;
97 Ok((a, self.top_mut()?))
98 }
99
100 #[inline]
102 pub fn push(&mut self, value: U256) -> Result<(), ExceptionalHalt> {
103 let next_offset = self
106 .offset
107 .checked_sub(1)
108 .ok_or(ExceptionalHalt::StackOverflow)?;
109
110 #[expect(unsafe_code, reason = "next_offset == self.offset - 1 >= 0")]
117 unsafe {
118 let slot = self.values.get_unchecked_mut(next_offset);
119 slot.0[0] = value.0[0];
120 slot.0[1] = value.0[1];
121 slot.0[2] = value.0[2];
122 slot.0[3] = value.0[3];
123 }
124 self.offset = next_offset;
125
126 Ok(())
127 }
128
129 #[inline]
130 pub fn push_zero(&mut self) -> Result<(), ExceptionalHalt> {
131 let next_offset = self
134 .offset
135 .checked_sub(1)
136 .ok_or(ExceptionalHalt::StackOverflow)?;
137
138 #[expect(unsafe_code, reason = "next_offset == self.offset - 1 >= 0")]
141 unsafe {
142 *self
143 .values
144 .get_unchecked_mut(next_offset)
145 .0
146 .as_mut_ptr()
147 .cast() = [0u64; U64_PER_U256];
148 }
149 self.offset = next_offset;
150
151 Ok(())
152 }
153
154 pub fn len(&self) -> usize {
155 #[expect(clippy::arithmetic_side_effects)]
158 {
159 self.values.len() - self.offset
160 }
161 }
162
163 pub fn is_empty(&self) -> bool {
164 self.offset == self.values.len()
165 }
166
167 #[inline(always)]
168 pub fn swap<const N: usize>(&mut self) -> Result<(), ExceptionalHalt> {
169 const {
172 assert!(STACK_LIMIT.checked_add(N).is_some());
173 }
174 #[expect(clippy::arithmetic_side_effects)]
175 let index = self.offset + N;
176
177 if index >= self.values.len() {
178 return Err(ExceptionalHalt::StackUnderflow);
179 }
180
181 #[expect(unsafe_code, reason = "self.offset always < STACK_LIMIT")]
182 unsafe {
183 assert_unchecked(self.offset < STACK_LIMIT)
184 };
185
186 self.values.swap(self.offset, index);
187 Ok(())
188 }
189
190 pub fn clear(&mut self) {
191 self.offset = STACK_LIMIT;
192 }
193
194 #[inline]
196 pub fn dup<const N: usize>(&mut self) -> Result<(), ExceptionalHalt> {
197 const {
200 assert!(STACK_LIMIT.checked_add(N).is_some());
201 }
202 #[expect(clippy::arithmetic_side_effects)]
203 let index = self.offset + N;
204 if index >= self.values.len() {
205 return Err(ExceptionalHalt::StackUnderflow);
206 }
207
208 self.offset = self
209 .offset
210 .checked_sub(1)
211 .ok_or(ExceptionalHalt::StackOverflow)?;
212
213 #[expect(unsafe_code, reason = "index < size, offset-1 >= 0")]
214 unsafe {
215 std::ptr::copy_nonoverlapping(
216 self.values.get_unchecked_mut(index).0.as_mut_ptr(),
217 self.values.get_unchecked_mut(self.offset).0.as_mut_ptr(),
218 U64_PER_U256,
219 );
220 }
221 Ok(())
222 }
223}
224
225impl Default for Stack {
226 fn default() -> Self {
227 Self {
228 values: Box::new([U256::zero(); STACK_LIMIT]),
229 offset: STACK_LIMIT,
230 }
231 }
232}
233
234impl fmt::Debug for Stack {
235 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
236 struct StackValues<'a>(&'a [U256]);
237
238 impl fmt::Debug for StackValues<'_> {
239 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
240 f.debug_list().entries(self.0.iter().rev()).finish()
241 }
242 }
243
244 #[expect(clippy::indexing_slicing)]
245 f.debug_tuple("Stack")
246 .field(&StackValues(&self.values[self.offset..]))
247 .finish()
248 }
249}
250
251impl Hash for Stack {
252 #[expect(
253 clippy::indexing_slicing,
254 reason = "offset is always within bounds of values"
255 )]
256 fn hash<H: Hasher>(&self, state: &mut H) {
257 self.values[self.offset..].hash(state);
258 }
259}
260
261#[derive(Debug)]
262pub struct CallFrame {
267 pub gas_limit: u64,
269 pub gas_remaining: i64,
275 pub pc: usize,
277 pub msg_sender: Address,
279 pub to: Address,
281 pub code_address: Address,
283 pub bytecode: Code,
287 pub msg_value: U256,
289 pub stack: Stack,
290 pub memory: Memory,
291 pub calldata: Bytes,
293 pub output: Bytes,
295 pub sub_return_data: Bytes,
297 pub is_static: bool,
299 pub depth: usize,
301 pub is_create: bool,
303 pub call_frame_backup: CallFrameBackup,
305 pub ret_offset: usize,
307 pub ret_size: usize,
309 pub should_transfer_value: bool,
311 pub state_gas_used_at_entry: i64,
314 pub frame_state_gas_spilled: u64,
317 pub target_alive: bool,
322 pub new_account_state_gas_charged: bool,
326}
327
328#[derive(Debug, Clone, Eq, PartialEq, Default)]
329pub struct CallFrameBackup {
330 pub original_accounts_info: FxHashMap<Address, LevmAccount>,
331 pub original_account_storage_slots: FxHashMap<Address, FxHashMap<H256, U256>>,
332 pub bal_checkpoint: Option<BlockAccessListCheckpoint>,
335 pub inserted_code_hashes: Vec<H256>,
341}
342
343impl CallFrameBackup {
344 pub fn backup_account_info(
345 &mut self,
346 address: Address,
347 account: &LevmAccount,
348 ) -> Result<(), InternalError> {
349 self.original_accounts_info
350 .entry(address)
351 .or_insert_with(|| LevmAccount {
352 info: account.info.clone(),
353 storage: Default::default(),
354 status: account.status.clone(),
355 has_storage: account.has_storage,
356 exists: account.exists,
357 });
358
359 Ok(())
360 }
361
362 pub fn clear(&mut self) {
363 self.original_accounts_info.clear();
364 self.original_account_storage_slots.clear();
365 self.bal_checkpoint = None;
366 self.inserted_code_hashes.clear();
367 }
368
369 pub fn extend(&mut self, other: CallFrameBackup) {
374 for (address, other_storage) in other.original_account_storage_slots {
377 self.original_account_storage_slots
378 .entry(address)
379 .or_default()
380 .extend(other_storage);
381 }
382 self.original_accounts_info
383 .extend(other.original_accounts_info);
384 self.inserted_code_hashes.extend(other.inserted_code_hashes);
385 }
387
388 pub fn absorb(&mut self, other: &CallFrameBackup) {
397 for (address, account) in other.original_accounts_info.iter() {
398 self.original_accounts_info
399 .entry(*address)
400 .or_insert_with(|| account.clone());
401 }
402 for (address, storage) in other.original_account_storage_slots.iter() {
403 let slot = self
404 .original_account_storage_slots
405 .entry(*address)
406 .or_default();
407 for (key, value) in storage.iter() {
408 slot.entry(*key).or_insert(*value);
409 }
410 }
411 }
412}
413
414impl CallFrame {
415 #[expect(
416 clippy::too_many_arguments,
417 reason = "inlined constructor, many args needed for performance"
418 )]
419 #[inline(always)]
422 pub fn new(
423 msg_sender: Address,
424 to: Address,
425 code_address: Address,
426 bytecode: Code,
427 msg_value: U256,
428 calldata: Bytes,
429 is_static: bool,
430 gas_limit: u64,
431 depth: usize,
432 should_transfer_value: bool,
433 is_create: bool,
434 ret_offset: usize,
435 ret_size: usize,
436 stack: Stack,
437 memory: Memory,
438 ) -> Self {
439 #[expect(clippy::as_conversions, reason = "remaining gas conversion")]
442 Self {
443 gas_limit,
444 gas_remaining: gas_limit as i64,
445 msg_sender,
446 to,
447 code_address,
448 bytecode,
449 msg_value,
450 calldata,
451 is_static,
452 depth,
453 should_transfer_value,
454 is_create,
455 ret_offset,
456 ret_size,
457 stack,
458 memory,
459 call_frame_backup: CallFrameBackup::default(),
460 output: Bytes::default(),
461 pc: 0,
462 sub_return_data: Bytes::default(),
463 state_gas_used_at_entry: 0,
464 frame_state_gas_spilled: 0,
465 target_alive: false,
466 new_account_state_gas_charged: false,
467 }
468 }
469
470 #[inline(always)]
471 pub fn next_opcode(&self) -> u8 {
472 #[expect(unsafe_code, reason = "pc bounded by padded bytecode len")]
477 unsafe {
478 *self.bytecode.dispatch_buf().get_unchecked(self.pc)
479 }
480 }
481
482 pub fn pc(&self) -> usize {
483 self.pc
484 }
485
486 #[inline(always)]
488 #[expect(clippy::as_conversions, reason = "remaining gas conversion")]
489 #[expect(clippy::arithmetic_side_effects, reason = "arithmethic checked")]
490 pub fn increase_consumed_gas(&mut self, gas: u64) -> Result<(), ExceptionalHalt> {
491 self.gas_remaining -= gas as i64;
492
493 if self.gas_remaining < 0 {
494 return Err(ExceptionalHalt::OutOfGas);
495 }
496
497 Ok(())
498 }
499
500 #[inline(always)]
502 #[expect(clippy::as_conversions, reason = "remaining gas conversion")]
503 pub fn check_gas(&self, gas: u64) -> Result<(), ExceptionalHalt> {
504 if self.gas_remaining < 0 || (self.gas_remaining as u64) < gas {
505 return Err(ExceptionalHalt::OutOfGas);
506 }
507 Ok(())
508 }
509
510 pub fn set_code(&mut self, code: Code) -> Result<(), VMError> {
511 self.bytecode = code;
512 Ok(())
513 }
514}
515
516impl<'a> VM<'a> {
517 #[inline(always)]
519 pub fn add_callframe(&mut self, new_call_frame: CallFrame) {
520 if self.call_frames.is_empty() {
524 self.call_frames.reserve(8);
525 }
526 self.call_frames.push(new_call_frame);
527 #[allow(unsafe_code, reason = "just pushed, so the vec is not empty")]
528 unsafe {
529 std::mem::swap(
530 &mut self.current_call_frame,
531 self.call_frames.last_mut().unwrap_unchecked(),
532 );
533 }
534 }
535
536 #[inline(always)]
537 pub fn pop_call_frame(&mut self) -> Result<CallFrame, InternalError> {
538 let mut new = self.call_frames.pop().ok_or(InternalError::CallFrame)?;
539
540 std::mem::swap(&mut new, &mut self.current_call_frame);
541
542 Ok(new)
543 }
544
545 pub fn is_initial_call_frame(&self) -> bool {
546 self.call_frames.is_empty()
547 }
548
549 pub fn restore_cache_state(&mut self) -> Result<(), VMError> {
551 let callframe_backup = self.current_call_frame.call_frame_backup.clone();
552 restore_cache_state(self.db, callframe_backup)
553 }
554
555 pub fn restore_cache_state_consuming(&mut self) -> Result<(), VMError> {
564 let callframe_backup = std::mem::take(&mut self.current_call_frame.call_frame_backup);
565 restore_cache_state(self.db, callframe_backup)
566 }
567
568 pub fn merge_call_frame_backup_with_parent(
573 &mut self,
574 child_call_frame_backup: &CallFrameBackup,
575 ) -> Result<(), VMError> {
576 let parent_backup_accounts = &mut self
577 .current_call_frame
578 .call_frame_backup
579 .original_accounts_info;
580 for (address, account) in child_call_frame_backup.original_accounts_info.iter() {
581 if parent_backup_accounts.get(address).is_none() {
582 parent_backup_accounts.insert(*address, account.clone());
583 }
584 }
585
586 let parent_backup_storage = &mut self
587 .current_call_frame
588 .call_frame_backup
589 .original_account_storage_slots;
590 for (address, storage) in child_call_frame_backup
591 .original_account_storage_slots
592 .iter()
593 {
594 let parent_storage = parent_backup_storage.entry(*address).or_default();
595 for (key, value) in storage {
596 if parent_storage.get(key).is_none() {
597 parent_storage.insert(*key, *value);
598 }
599 }
600 }
601
602 self.current_call_frame
605 .call_frame_backup
606 .inserted_code_hashes
607 .extend(child_call_frame_backup.inserted_code_hashes.iter().copied());
608
609 Ok(())
610 }
611
612 #[inline(always)]
613 #[expect(
614 clippy::arithmetic_side_effects,
615 reason = "pc bounded by padded bytecode len"
616 )]
617 pub fn advance_pc(&mut self) {
618 self.current_call_frame.pc += 1;
619 }
620}
621
622#[cfg(test)]
623mod tests {
624 use super::*;
625 use ethrex_common::types::AccountInfo;
626
627 #[test]
628 fn absorb_keeps_earliest_original_value() {
629 let addr = Address::from_low_u64_be(1);
630 let mut acc = CallFrameBackup::default();
631
632 let mut first = CallFrameBackup::default();
633 first
634 .original_account_storage_slots
635 .entry(addr)
636 .or_default()
637 .insert(H256::zero(), U256::from(10));
638
639 let mut second = CallFrameBackup::default();
640 second
641 .original_account_storage_slots
642 .entry(addr)
643 .or_default()
644 .insert(H256::zero(), U256::from(20));
645
646 acc.absorb(&first);
647 acc.absorb(&second);
648
649 let value = acc
651 .original_account_storage_slots
652 .get(&addr)
653 .and_then(|slots| slots.get(&H256::zero()))
654 .copied();
655 assert_eq!(value, Some(U256::from(10)));
656 }
657
658 #[test]
659 fn absorb_keeps_earliest_account_info() {
660 let addr = Address::from_low_u64_be(2);
661 let mut acc = CallFrameBackup::default();
662
663 let first_acc = LevmAccount {
664 info: AccountInfo {
665 nonce: 1,
666 ..Default::default()
667 },
668 ..Default::default()
669 };
670 let mut first = CallFrameBackup::default();
671 first.original_accounts_info.insert(addr, first_acc);
672
673 let second_acc = LevmAccount {
674 info: AccountInfo {
675 nonce: 2,
676 ..Default::default()
677 },
678 ..Default::default()
679 };
680 let mut second = CallFrameBackup::default();
681 second.original_accounts_info.insert(addr, second_acc);
682
683 acc.absorb(&first);
684 acc.absorb(&second);
685
686 let nonce = acc.original_accounts_info.get(&addr).map(|a| a.info.nonce);
687 assert_eq!(nonce, Some(1));
688 }
689}