Skip to main content

ethrex_levm/opcode_handlers/
dup.rs

1//! # Stack duplication operations
2//!
3//! Includes the following opcodes:
4//!   - `DUP1` to `DUP16`
5
6use crate::{
7    errors::{ExceptionalHalt, OpcodeResult, VMError},
8    gas_cost,
9    opcode_handlers::OpcodeHandler,
10    vm::VM,
11};
12
13/// Implementation for the `DUPn` opcodes.
14pub struct OpDupHandler<const N: usize>;
15impl<const N: usize> OpcodeHandler for OpDupHandler<N> {
16    #[inline(always)]
17    fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
18        vm.current_call_frame
19            .increase_consumed_gas(gas_cost::DUPN)?;
20
21        vm.current_call_frame.stack.dup::<N>()?;
22
23        Ok(OpcodeResult::Continue)
24    }
25}
26
27/// Implementation for the `DUPN` opcode.
28pub struct OpDupNHandler;
29impl OpcodeHandler for OpDupNHandler {
30    #[inline(always)]
31    fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
32        vm.current_call_frame
33            .increase_consumed_gas(gas_cost::DUPN)?;
34
35        let relative_offset = vm
36            .current_call_frame
37            .bytecode
38            .dispatch_buf()
39            .get(vm.current_call_frame.pc)
40            .copied()
41            .unwrap_or_default();
42
43        // Remove offsets that break backwards compatibility, which are
44        //   - 0x5B, which corresponds to a JUMPDEST opcode.
45        //   - 0x5F to 0x7F, which corresponds to PUSHx opcodes.
46        //   - The extra 3 values (0x5C, 0x5D and 0x5E) are probably included to simplify decoding.
47        if (0x5B..0x80).contains(&relative_offset) {
48            return Err(ExceptionalHalt::InvalidOpcode.into());
49        }
50        let relative_offset = relative_offset.wrapping_add(145);
51
52        // Stack grows downwards, so we add the offset to get deeper elements
53        // relative_offset is 1-indexed stack depth (17-235), convert to 0-indexed for array access
54        // The n-th element (1-indexed) is at array index offset + (n-1)
55        let absolute_offset = vm
56            .current_call_frame
57            .stack
58            .offset
59            .checked_add(usize::from(relative_offset).wrapping_sub(1))
60            .ok_or(ExceptionalHalt::StackUnderflow)?;
61
62        // Verify the offset is within stack bounds
63        if absolute_offset >= vm.current_call_frame.stack.values.len() {
64            return Err(ExceptionalHalt::StackUnderflow.into());
65        }
66
67        #[expect(unsafe_code, reason = "bound already checked")]
68        vm.current_call_frame.stack.push(unsafe {
69            *vm.current_call_frame
70                .stack
71                .values
72                .get_unchecked(absolute_offset)
73        })?;
74
75        vm.current_call_frame.pc = vm.current_call_frame.pc.wrapping_add(1);
76        Ok(OpcodeResult::Continue)
77    }
78}