use crate::{
errors::{ExceptionalHalt, OpcodeResult, VMError},
gas_cost,
opcode_handlers::OpcodeHandler,
vm::VM,
};
pub struct OpDupHandler<const N: usize>;
impl<const N: usize> OpcodeHandler for OpDupHandler<N> {
#[inline(always)]
fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
vm.current_call_frame
.increase_consumed_gas(gas_cost::DUPN)?;
vm.current_call_frame.stack.dup::<N>()?;
Ok(OpcodeResult::Continue)
}
}
pub struct OpDupNHandler;
impl OpcodeHandler for OpDupNHandler {
#[inline(always)]
fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
vm.current_call_frame
.increase_consumed_gas(gas_cost::DUPN)?;
let relative_offset = vm
.current_call_frame
.bytecode
.dispatch_buf()
.get(vm.current_call_frame.pc)
.copied()
.unwrap_or_default();
if (0x5B..0x80).contains(&relative_offset) {
return Err(ExceptionalHalt::InvalidOpcode.into());
}
let relative_offset = relative_offset.wrapping_add(145);
let absolute_offset = vm
.current_call_frame
.stack
.offset
.checked_add(usize::from(relative_offset).wrapping_sub(1))
.ok_or(ExceptionalHalt::StackUnderflow)?;
if absolute_offset >= vm.current_call_frame.stack.values.len() {
return Err(ExceptionalHalt::StackUnderflow.into());
}
#[expect(unsafe_code, reason = "bound already checked")]
vm.current_call_frame.stack.push(unsafe {
*vm.current_call_frame
.stack
.values
.get_unchecked(absolute_offset)
})?;
vm.current_call_frame.pc = vm.current_call_frame.pc.wrapping_add(1);
Ok(OpcodeResult::Continue)
}
}