use crate::{
constants::WORD_SIZE_IN_BYTES_USIZE,
errors::{ExceptionalHalt, InternalError, OpcodeResult, VMError},
gas_cost::{self, SSTORE_STIPEND},
memory::calculate_memory_size,
opcode_handlers::OpcodeHandler,
opcodes::Opcode,
utils::{size_offset_to_usize, u256_to_usize},
vm::VM,
};
use ethrex_common::{H256, U256, types::Fork};
use std::{mem, slice};
pub struct OpPopHandler;
impl OpcodeHandler for OpPopHandler {
#[inline(always)]
fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
vm.current_call_frame.increase_consumed_gas(gas_cost::POP)?;
vm.current_call_frame.stack.pop1()?;
Ok(OpcodeResult::Continue)
}
}
pub struct OpGasHandler;
impl OpcodeHandler for OpGasHandler {
#[inline(always)]
fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
vm.current_call_frame.increase_consumed_gas(gas_cost::GAS)?;
vm.current_call_frame
.stack
.push(vm.current_call_frame.gas_remaining.into())?;
Ok(OpcodeResult::Continue)
}
}
pub struct OpPcHandler;
impl OpcodeHandler for OpPcHandler {
#[inline(always)]
fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
vm.current_call_frame.increase_consumed_gas(gas_cost::PC)?;
vm.current_call_frame
.stack
.push(vm.current_call_frame.pc.wrapping_sub(1).into())?;
Ok(OpcodeResult::Continue)
}
}
pub struct OpMLoadHandler;
impl OpcodeHandler for OpMLoadHandler {
#[inline(always)]
fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
let offset = u256_to_usize(*vm.current_call_frame.stack.top_mut()?)?;
vm.current_call_frame
.increase_consumed_gas(gas_cost::mload(
calculate_memory_size(offset, WORD_SIZE_IN_BYTES_USIZE)?,
vm.current_call_frame.memory.len(),
)?)?;
let word = vm.current_call_frame.memory.load_word(offset)?;
*vm.current_call_frame.stack.top_mut()? = word;
Ok(OpcodeResult::Continue)
}
}
pub struct OpMStoreHandler;
impl OpcodeHandler for OpMStoreHandler {
#[inline(always)]
fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
let [offset, value] = *vm.current_call_frame.stack.pop()?;
if vm.debug_mode.enabled && vm.debug_mode.handle_debug(offset, value)? {
return Ok(OpcodeResult::Continue);
}
let offset = u256_to_usize(offset)?;
vm.current_call_frame
.increase_consumed_gas(gas_cost::mstore(
calculate_memory_size(offset, WORD_SIZE_IN_BYTES_USIZE)?,
vm.current_call_frame.memory.len(),
)?)?;
vm.current_call_frame.memory.store_word(offset, value)?;
Ok(OpcodeResult::Continue)
}
}
pub struct OpMStore8Handler;
impl OpcodeHandler for OpMStore8Handler {
#[inline(always)]
fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
let [offset, value] = *vm.current_call_frame.stack.pop()?;
let offset = u256_to_usize(offset)?;
let value = value.byte(0);
vm.current_call_frame
.increase_consumed_gas(gas_cost::mstore8(
calculate_memory_size(offset, size_of::<u8>())?,
vm.current_call_frame.memory.len(),
)?)?;
vm.current_call_frame
.memory
.store_data(offset, slice::from_ref(&value))?;
Ok(OpcodeResult::Continue)
}
}
pub struct OpMCopyHandler;
impl OpcodeHandler for OpMCopyHandler {
#[inline(always)]
fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
let [dst_offset, src_offset, len] = *vm.current_call_frame.stack.pop()?;
let (len, dst_offset) = size_offset_to_usize(len, dst_offset)?;
let src_offset = u256_to_usize(src_offset).unwrap_or(usize::MAX);
vm.current_call_frame
.increase_consumed_gas(gas_cost::mcopy(
calculate_memory_size(src_offset.max(dst_offset), len)?,
vm.current_call_frame.memory.len(),
len,
)?)?;
vm.current_call_frame
.memory
.copy_within(src_offset, dst_offset, len)?;
Ok(OpcodeResult::Continue)
}
}
pub struct OpMSizeHandler;
impl OpcodeHandler for OpMSizeHandler {
#[inline(always)]
fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
vm.current_call_frame
.increase_consumed_gas(gas_cost::MSIZE)?;
vm.current_call_frame
.stack
.push(vm.current_call_frame.memory.len().into())?;
Ok(OpcodeResult::Continue)
}
}
pub struct OpTLoadHandler;
impl OpcodeHandler for OpTLoadHandler {
#[inline(always)]
fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
vm.current_call_frame
.increase_consumed_gas(gas_cost::TLOAD)?;
let key = vm.current_call_frame.stack.pop1()?;
vm.current_call_frame
.stack
.push(vm.substate.get_transient(&vm.current_call_frame.to, &key))?;
Ok(OpcodeResult::Continue)
}
}
pub struct OpTStoreHandler;
impl OpcodeHandler for OpTStoreHandler {
#[inline(always)]
fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
if vm.current_call_frame.is_static {
return Err(ExceptionalHalt::OpcodeNotAllowedInStaticContext.into());
}
vm.current_call_frame
.increase_consumed_gas(gas_cost::TSTORE)?;
let [key, value] = *vm.current_call_frame.stack.pop()?;
vm.substate
.set_transient(&vm.current_call_frame.to, &key, value);
Ok(OpcodeResult::Continue)
}
}
pub struct OpSLoadHandler;
impl OpcodeHandler for OpSLoadHandler {
#[inline(always)]
fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
let storage_slot_key = vm.current_call_frame.stack.pop1()?;
let address = vm.current_call_frame.to;
let key = {
#[expect(unsafe_code)]
unsafe {
let mut hash = mem::transmute::<U256, H256>(storage_slot_key);
hash.0.reverse();
hash
}
};
vm.current_call_frame
.increase_consumed_gas(gas_cost::sload(
vm.substate.add_accessed_slot(address, key),
)?)?;
vm.record_storage_slot_to_bal(address, storage_slot_key);
let value = vm.get_storage_value(address, key)?;
vm.current_call_frame.stack.push(value)?;
Ok(OpcodeResult::Continue)
}
}
pub struct OpSStoreHandler;
impl OpcodeHandler for OpSStoreHandler {
#[inline(always)]
fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
if vm.current_call_frame.is_static {
return Err(ExceptionalHalt::OpcodeNotAllowedInStaticContext.into());
}
if vm.current_call_frame.gas_remaining <= SSTORE_STIPEND {
return Err(ExceptionalHalt::OutOfGas.into());
}
let [storage_slot_key, value] = *vm.current_call_frame.stack.pop()?;
let to = vm.current_call_frame.to;
#[expect(unsafe_code)]
let key = unsafe {
let mut hash = mem::transmute::<U256, H256>(storage_slot_key);
hash.0.reverse();
hash
};
let (current_value, original_value, storage_slot_was_cold) =
vm.access_storage_slot_for_sstore(to, key)?;
vm.record_storage_slot_to_bal(to, storage_slot_key);
let fork = vm.env.config.fork;
let needs_state_gas = fork >= Fork::Amsterdam
&& value != current_value
&& current_value == original_value
&& original_value.is_zero()
&& !value.is_zero();
vm.current_call_frame
.increase_consumed_gas(gas_cost::sstore(
original_value,
current_value,
value,
storage_slot_was_cold,
fork,
)?)?;
if needs_state_gas {
vm.increase_state_gas(vm.state_gas_storage_set)?;
}
let is_zero_to_n_to_zero_amsterdam = fork >= Fork::Amsterdam
&& value != current_value
&& current_value != original_value
&& value == original_value
&& original_value.is_zero();
if value != current_value {
const REMOVE_SLOT_COST: i64 = 4800;
const RESTORE_EMPTY_SLOT_COST: i64 = 19900;
const RESTORE_SLOT_COST: i64 = 2800;
let mut delta = 0i64;
#[expect(
clippy::arithmetic_side_effects,
reason = "delta additions are bounded by known constants"
)]
if current_value == original_value {
if !original_value.is_zero() && value.is_zero() {
delta += REMOVE_SLOT_COST;
}
} else {
if !original_value.is_zero() {
if current_value.is_zero() {
delta -= REMOVE_SLOT_COST;
} else if value.is_zero() {
delta += REMOVE_SLOT_COST;
}
}
if value == original_value {
if original_value.is_zero() {
if fork >= Fork::Amsterdam {
delta += RESTORE_SLOT_COST; } else {
delta += RESTORE_EMPTY_SLOT_COST;
}
} else {
delta += RESTORE_SLOT_COST;
}
}
}
match vm.substate.refunded_gas.checked_add_signed(delta) {
Some(refunded_gas) => vm.substate.refunded_gas = refunded_gas,
None if delta < 0 => return Err(InternalError::Underflow.into()),
None => return Err(InternalError::Overflow.into()),
}
}
if is_zero_to_n_to_zero_amsterdam {
vm.credit_state_gas_refund(vm.state_gas_storage_set)?;
}
if value != current_value {
vm.update_account_storage(to, key, storage_slot_key, value, current_value)?;
}
Ok(OpcodeResult::Continue)
}
}
pub struct OpJumpDestHandler;
impl OpcodeHandler for OpJumpDestHandler {
#[inline(always)]
fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
vm.current_call_frame
.increase_consumed_gas(gas_cost::JUMPDEST)?;
Ok(OpcodeResult::Continue)
}
}
pub struct OpJumpHandler;
impl OpcodeHandler for OpJumpHandler {
#[inline(always)]
fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
vm.current_call_frame
.increase_consumed_gas(gas_cost::JUMP)?;
let target = vm.current_call_frame.stack.pop1()?;
jump(vm, target.try_into().unwrap_or(usize::MAX), gas_cost::JUMP)?;
Ok(OpcodeResult::Continue)
}
}
pub struct OpJumpIHandler;
impl OpcodeHandler for OpJumpIHandler {
#[inline(always)]
fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
vm.current_call_frame
.increase_consumed_gas(gas_cost::JUMPI)?;
let [target, condition] = *vm.current_call_frame.stack.pop()?;
if !condition.is_zero() {
jump(vm, target.try_into().unwrap_or(usize::MAX), gas_cost::JUMPI)?;
}
Ok(OpcodeResult::Continue)
}
}
fn jump(vm: &mut VM<'_>, target: usize, parent_gas_cost: u64) -> Result<(), VMError> {
#[expect(clippy::as_conversions, reason = "safe")]
if vm
.current_call_frame
.bytecode
.dispatch_buf()
.get(target)
.is_some_and(|&value| {
value == Opcode::JUMPDEST as u8
&& vm
.current_call_frame
.bytecode
.jump_targets
.binary_search(&(target as u32))
.is_ok()
})
{
if vm.opcode_tracer.active {
vm.opcode_tracer.last_opcode_gas_cost = Some(parent_gas_cost);
let synth = build_jumpdest_step(vm, target);
vm.current_call_frame.pc = target.wrapping_add(1);
vm.current_call_frame
.increase_consumed_gas(gas_cost::JUMPDEST)?;
vm.opcode_tracer.synthesize_step(synth);
} else {
vm.current_call_frame.pc = target.wrapping_add(1);
vm.current_call_frame
.increase_consumed_gas(gas_cost::JUMPDEST)?;
}
Ok(())
} else {
Err(ExceptionalHalt::InvalidJump.into())
}
}
#[expect(
clippy::as_conversions,
reason = "pc/depth/mem_size bounded; fit in target types"
)]
fn build_jumpdest_step(vm: &VM<'_>, target: usize) -> ethrex_common::tracing::OpcodeStep {
use crate::opcode_tracer::build_step;
use bytes::Bytes;
let cfg = &vm.opcode_tracer.cfg;
let gas = vm.current_call_frame.gas_remaining.max(0) as u64;
let depth = (vm.call_frames.len() as u32).saturating_add(1);
let refund = vm.substate.refunded_gas;
let mem_size = vm.current_call_frame.memory.len() as u64;
let stack_view = if cfg.disable_stack {
Vec::new()
} else {
vm.collect_stack_for_trace()
};
let mem_view = if cfg.enable_memory {
vm.collect_memory_for_trace()
} else {
Vec::new()
};
let return_data = if cfg.enable_return_data {
vm.current_call_frame.sub_return_data.clone()
} else {
Bytes::new()
};
build_step(
cfg,
target as u64,
Opcode::JUMPDEST as u8,
gas,
gas_cost::JUMPDEST,
depth,
refund,
&stack_view,
&mem_view,
mem_size,
&return_data,
None,
)
}