1use crate::{
2 account::LevmAccount,
3 constants::STACK_LIMIT,
4 errors::{ExceptionalHalt, InternalError, VMError},
5 memory::{CallData, Memory},
6 utils::restore_cache_state,
7 vm::VM,
8};
9use bytes::Bytes;
10use ethrex_common::seeded_hash::SeededHashMap;
11use ethrex_common::types::block_access_list::BlockAccessListCheckpoint;
12use ethrex_common::{Address, H256, U256, types::Code};
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 #[expect(unsafe_code, reason = "top validated by caller; depth unchanged")]
97 #[inline]
98 pub unsafe fn set_top_unchecked(&mut self, value: U256) {
99 unsafe {
100 *self.values.get_unchecked_mut(self.offset) = value;
101 }
102 }
103
104 #[inline]
109 pub fn pop1_and_top_mut(&mut self) -> Result<(U256, &mut U256), ExceptionalHalt> {
110 let a = self.pop1()?;
111 Ok((a, self.top_mut()?))
112 }
113
114 #[inline]
116 pub fn push(&mut self, value: U256) -> Result<(), ExceptionalHalt> {
117 let next_offset = self
120 .offset
121 .checked_sub(1)
122 .ok_or(ExceptionalHalt::StackOverflow)?;
123
124 #[expect(unsafe_code, reason = "next_offset == self.offset - 1 >= 0")]
131 unsafe {
132 let slot = self.values.get_unchecked_mut(next_offset);
133 slot.0[0] = value.0[0];
134 slot.0[1] = value.0[1];
135 slot.0[2] = value.0[2];
136 slot.0[3] = value.0[3];
137 }
138 self.offset = next_offset;
139
140 Ok(())
141 }
142
143 #[inline]
144 pub fn push_zero(&mut self) -> Result<(), ExceptionalHalt> {
145 let next_offset = self
148 .offset
149 .checked_sub(1)
150 .ok_or(ExceptionalHalt::StackOverflow)?;
151
152 #[expect(unsafe_code, reason = "next_offset == self.offset - 1 >= 0")]
155 unsafe {
156 *self
157 .values
158 .get_unchecked_mut(next_offset)
159 .0
160 .as_mut_ptr()
161 .cast() = [0u64; U64_PER_U256];
162 }
163 self.offset = next_offset;
164
165 Ok(())
166 }
167
168 pub fn len(&self) -> usize {
169 #[expect(clippy::arithmetic_side_effects)]
172 {
173 self.values.len() - self.offset
174 }
175 }
176
177 pub fn is_empty(&self) -> bool {
178 self.offset == self.values.len()
179 }
180
181 #[inline(always)]
182 pub fn swap<const N: usize>(&mut self) -> Result<(), ExceptionalHalt> {
183 const {
186 assert!(STACK_LIMIT.checked_add(N).is_some());
187 }
188 #[expect(clippy::arithmetic_side_effects)]
189 let index = self.offset + N;
190
191 if index >= self.values.len() {
192 return Err(ExceptionalHalt::StackUnderflow);
193 }
194
195 #[expect(unsafe_code, reason = "self.offset always < STACK_LIMIT")]
196 unsafe {
197 assert_unchecked(self.offset < STACK_LIMIT)
198 };
199
200 self.values.swap(self.offset, index);
201 Ok(())
202 }
203
204 pub fn clear(&mut self) {
205 self.offset = STACK_LIMIT;
206 }
207
208 #[inline]
210 pub fn dup<const N: usize>(&mut self) -> Result<(), ExceptionalHalt> {
211 const {
214 assert!(STACK_LIMIT.checked_add(N).is_some());
215 }
216 #[expect(clippy::arithmetic_side_effects)]
217 let index = self.offset + N;
218 if index >= self.values.len() {
219 return Err(ExceptionalHalt::StackUnderflow);
220 }
221
222 self.offset = self
223 .offset
224 .checked_sub(1)
225 .ok_or(ExceptionalHalt::StackOverflow)?;
226
227 #[expect(unsafe_code, reason = "index < size, offset-1 >= 0")]
228 unsafe {
229 std::ptr::copy_nonoverlapping(
230 self.values.get_unchecked_mut(index).0.as_mut_ptr(),
231 self.values.get_unchecked_mut(self.offset).0.as_mut_ptr(),
232 U64_PER_U256,
233 );
234 }
235 Ok(())
236 }
237}
238
239impl Default for Stack {
240 fn default() -> Self {
241 Self {
242 values: Box::new([U256::zero(); STACK_LIMIT]),
243 offset: STACK_LIMIT,
244 }
245 }
246}
247
248impl fmt::Debug for Stack {
249 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
250 struct StackValues<'a>(&'a [U256]);
251
252 impl fmt::Debug for StackValues<'_> {
253 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
254 f.debug_list().entries(self.0.iter().rev()).finish()
255 }
256 }
257
258 #[expect(clippy::indexing_slicing)]
259 f.debug_tuple("Stack")
260 .field(&StackValues(&self.values[self.offset..]))
261 .finish()
262 }
263}
264
265impl Hash for Stack {
266 #[expect(
267 clippy::indexing_slicing,
268 reason = "offset is always within bounds of values"
269 )]
270 fn hash<H: Hasher>(&self, state: &mut H) {
271 self.values[self.offset..].hash(state);
272 }
273}
274
275#[derive(Debug)]
276pub struct CallFrame {
281 pub gas_limit: u64,
283 pub gas_remaining: i64,
289 pub pc: usize,
291 pub msg_sender: Address,
293 pub to: Address,
295 pub code_address: Address,
297 pub bytecode: Code,
301 pub msg_value: U256,
303 pub stack: Stack,
304 pub memory: Memory,
305 pub calldata: CallData,
307 pub output: Bytes,
309 pub sub_return_data: Bytes,
311 pub is_static: bool,
313 pub depth: usize,
315 pub is_create: bool,
317 pub call_frame_backup: CallFrameBackup,
319 pub ret_offset: usize,
321 pub ret_size: usize,
323 pub should_transfer_value: bool,
325 pub state_gas_used_at_entry: i64,
328 pub frame_state_gas_spilled: u64,
331 pub target_alive: bool,
336 pub new_account_state_gas_charged: bool,
340}
341
342#[derive(Debug, Clone, Eq, PartialEq, Default)]
343pub struct CallFrameBackup {
344 pub original_accounts_info: SeededHashMap<Address, LevmAccount>,
347 pub original_account_storage_slots: SeededHashMap<Address, SeededHashMap<H256, U256>>,
348 pub bal_checkpoint: Option<BlockAccessListCheckpoint>,
351 pub inserted_code_hashes: Vec<H256>,
357}
358
359impl CallFrameBackup {
360 pub fn backup_account_info(
361 &mut self,
362 address: Address,
363 account: &LevmAccount,
364 ) -> Result<(), InternalError> {
365 self.original_accounts_info
366 .entry(address)
367 .or_insert_with(|| LevmAccount {
368 info: account.info.clone(),
369 storage: Default::default(),
370 status: account.status.clone(),
371 has_storage: account.has_storage,
372 exists: account.exists,
373 });
374
375 Ok(())
376 }
377
378 pub fn clear(&mut self) {
379 self.original_accounts_info.clear();
380 self.original_account_storage_slots.clear();
381 self.bal_checkpoint = None;
382 self.inserted_code_hashes.clear();
383 }
384
385 pub fn extend(&mut self, other: CallFrameBackup) {
390 for (address, other_storage) in other.original_account_storage_slots {
393 self.original_account_storage_slots
394 .entry(address)
395 .or_default()
396 .extend(other_storage);
397 }
398 self.original_accounts_info
399 .extend(other.original_accounts_info);
400 self.inserted_code_hashes.extend(other.inserted_code_hashes);
401 }
403
404 pub fn absorb(&mut self, other: &CallFrameBackup) {
413 for (address, account) in other.original_accounts_info.iter() {
414 self.original_accounts_info
415 .entry(*address)
416 .or_insert_with(|| account.clone());
417 }
418 for (address, storage) in other.original_account_storage_slots.iter() {
419 let slot = self
420 .original_account_storage_slots
421 .entry(*address)
422 .or_default();
423 for (key, value) in storage.iter() {
424 slot.entry(*key).or_insert(*value);
425 }
426 }
427 }
428}
429
430impl CallFrame {
431 #[expect(
432 clippy::too_many_arguments,
433 reason = "inlined constructor, many args needed for performance"
434 )]
435 #[inline(always)]
438 pub fn new(
439 msg_sender: Address,
440 to: Address,
441 code_address: Address,
442 bytecode: Code,
443 msg_value: U256,
444 calldata: CallData,
445 is_static: bool,
446 gas_limit: u64,
447 depth: usize,
448 should_transfer_value: bool,
449 is_create: bool,
450 ret_offset: usize,
451 ret_size: usize,
452 stack: Stack,
453 memory: Memory,
454 ) -> Self {
455 #[expect(clippy::as_conversions, reason = "remaining gas conversion")]
458 Self {
459 gas_limit,
460 gas_remaining: gas_limit as i64,
461 msg_sender,
462 to,
463 code_address,
464 bytecode,
465 msg_value,
466 calldata,
467 is_static,
468 depth,
469 should_transfer_value,
470 is_create,
471 ret_offset,
472 ret_size,
473 stack,
474 memory,
475 call_frame_backup: CallFrameBackup::default(),
476 output: Bytes::default(),
477 pc: 0,
478 sub_return_data: Bytes::default(),
479 state_gas_used_at_entry: 0,
480 frame_state_gas_spilled: 0,
481 target_alive: false,
482 new_account_state_gas_charged: false,
483 }
484 }
485
486 #[inline(always)]
487 pub fn next_opcode(&self) -> u8 {
488 #[expect(unsafe_code, reason = "pc bounded by padded bytecode len")]
493 unsafe {
494 *self.bytecode.dispatch_buf().get_unchecked(self.pc)
495 }
496 }
497
498 pub fn pc(&self) -> usize {
499 self.pc
500 }
501
502 #[inline(always)]
504 #[expect(clippy::as_conversions, reason = "remaining gas conversion")]
505 #[expect(clippy::arithmetic_side_effects, reason = "arithmethic checked")]
506 pub fn increase_consumed_gas(&mut self, gas: u64) -> Result<(), ExceptionalHalt> {
507 self.gas_remaining -= gas as i64;
508
509 if self.gas_remaining < 0 {
510 return Err(ExceptionalHalt::OutOfGas);
511 }
512
513 Ok(())
514 }
515
516 #[inline(always)]
518 #[expect(clippy::as_conversions, reason = "remaining gas conversion")]
519 pub fn check_gas(&self, gas: u64) -> Result<(), ExceptionalHalt> {
520 if self.gas_remaining < 0 || (self.gas_remaining as u64) < gas {
521 return Err(ExceptionalHalt::OutOfGas);
522 }
523 Ok(())
524 }
525
526 pub fn set_code(&mut self, code: Code) -> Result<(), VMError> {
527 self.bytecode = code;
528 Ok(())
529 }
530}
531
532impl<'a> VM<'a> {
533 #[inline(always)]
535 pub fn add_callframe(&mut self, new_call_frame: CallFrame) {
536 if self.call_frames.is_empty() {
540 self.call_frames.reserve(8);
541 }
542 self.call_frames.push(new_call_frame);
543 #[allow(unsafe_code, reason = "just pushed, so the vec is not empty")]
544 unsafe {
545 std::mem::swap(
546 &mut self.current_call_frame,
547 self.call_frames.last_mut().unwrap_unchecked(),
548 );
549 }
550 }
551
552 #[inline(always)]
553 pub fn pop_call_frame(&mut self) -> Result<CallFrame, InternalError> {
554 let mut new = self.call_frames.pop().ok_or(InternalError::CallFrame)?;
555
556 std::mem::swap(&mut new, &mut self.current_call_frame);
557
558 Ok(new)
559 }
560
561 pub fn is_initial_call_frame(&self) -> bool {
562 self.call_frames.is_empty()
563 }
564
565 pub fn restore_cache_state(&mut self) -> Result<(), VMError> {
567 let callframe_backup = self.current_call_frame.call_frame_backup.clone();
568 restore_cache_state(self.db, callframe_backup)
569 }
570
571 pub fn restore_cache_state_consuming(&mut self) -> Result<(), VMError> {
580 let callframe_backup = std::mem::take(&mut self.current_call_frame.call_frame_backup);
581 restore_cache_state(self.db, callframe_backup)
582 }
583
584 pub fn merge_call_frame_backup_with_parent(
589 &mut self,
590 child_call_frame_backup: &CallFrameBackup,
591 ) -> Result<(), VMError> {
592 let parent_backup_accounts = &mut self
593 .current_call_frame
594 .call_frame_backup
595 .original_accounts_info;
596 for (address, account) in child_call_frame_backup.original_accounts_info.iter() {
597 if parent_backup_accounts.get(address).is_none() {
598 parent_backup_accounts.insert(*address, account.clone());
599 }
600 }
601
602 let parent_backup_storage = &mut self
603 .current_call_frame
604 .call_frame_backup
605 .original_account_storage_slots;
606 for (address, storage) in child_call_frame_backup
607 .original_account_storage_slots
608 .iter()
609 {
610 let parent_storage = parent_backup_storage.entry(*address).or_default();
611 for (key, value) in storage {
612 if parent_storage.get(key).is_none() {
613 parent_storage.insert(*key, *value);
614 }
615 }
616 }
617
618 self.current_call_frame
621 .call_frame_backup
622 .inserted_code_hashes
623 .extend(child_call_frame_backup.inserted_code_hashes.iter().copied());
624
625 Ok(())
626 }
627
628 #[inline(always)]
629 #[expect(
630 clippy::arithmetic_side_effects,
631 reason = "pc bounded by padded bytecode len"
632 )]
633 pub fn advance_pc(&mut self) {
634 self.current_call_frame.pc += 1;
635 }
636}
637
638#[cfg(test)]
639mod tests {
640 use super::*;
641 use ethrex_common::types::AccountInfo;
642
643 #[test]
644 fn absorb_keeps_earliest_original_value() {
645 let addr = Address::from_low_u64_be(1);
646 let mut acc = CallFrameBackup::default();
647
648 let mut first = CallFrameBackup::default();
649 first
650 .original_account_storage_slots
651 .entry(addr)
652 .or_default()
653 .insert(H256::zero(), U256::from(10));
654
655 let mut second = CallFrameBackup::default();
656 second
657 .original_account_storage_slots
658 .entry(addr)
659 .or_default()
660 .insert(H256::zero(), U256::from(20));
661
662 acc.absorb(&first);
663 acc.absorb(&second);
664
665 let value = acc
667 .original_account_storage_slots
668 .get(&addr)
669 .and_then(|slots| slots.get(&H256::zero()))
670 .copied();
671 assert_eq!(value, Some(U256::from(10)));
672 }
673
674 #[test]
675 fn absorb_keeps_earliest_account_info() {
676 let addr = Address::from_low_u64_be(2);
677 let mut acc = CallFrameBackup::default();
678
679 let first_acc = LevmAccount {
680 info: AccountInfo {
681 nonce: 1,
682 ..Default::default()
683 },
684 ..Default::default()
685 };
686 let mut first = CallFrameBackup::default();
687 first.original_accounts_info.insert(addr, first_acc);
688
689 let second_acc = LevmAccount {
690 info: AccountInfo {
691 nonce: 2,
692 ..Default::default()
693 },
694 ..Default::default()
695 };
696 let mut second = CallFrameBackup::default();
697 second.original_accounts_info.insert(addr, second_acc);
698
699 acc.absorb(&first);
700 acc.absorb(&second);
701
702 let nonce = acc.original_accounts_info.get(&addr).map(|a| a.info.nonce);
703 assert_eq!(nonce, Some(1));
704 }
705}