From 56b88a0caad9cc0de60833f067f8b6354e6fe974 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Tue, 28 Apr 2026 13:52:09 +0200 Subject: [PATCH 1/2] refactor(interpreter): unify call handlers --- crates/interpreter/src/instructions.rs | 12 +- .../interpreter/src/instructions/contract.rs | 177 +++++------------- 2 files changed, 56 insertions(+), 133 deletions(-) diff --git a/crates/interpreter/src/instructions.rs b/crates/interpreter/src/instructions.rs index 42c63446dc..c52cdc6821 100644 --- a/crates/interpreter/src/instructions.rs +++ b/crates/interpreter/src/instructions.rs @@ -287,14 +287,14 @@ const fn instruction_table_impl() -> Instructio table[LOG3 as usize] = Instruction::new(host::log::<3, _>); table[LOG4 as usize] = Instruction::new(host::log::<4, _>); - table[CREATE as usize] = Instruction::new(contract::create::<_, false, _>); - table[CALL as usize] = Instruction::new(contract::call); - table[CALLCODE as usize] = Instruction::new(contract::call_code); + table[CREATE as usize] = Instruction::new(contract::create::); + table[CALL as usize] = Instruction::new(contract::call::); + table[CALLCODE as usize] = Instruction::new(contract::call::); table[RETURN as usize] = Instruction::new(control::ret); - table[DELEGATECALL as usize] = Instruction::new(contract::delegate_call); - table[CREATE2 as usize] = Instruction::new(contract::create::<_, true, _>); + table[DELEGATECALL as usize] = Instruction::new(contract::call::); + table[CREATE2 as usize] = Instruction::new(contract::create::); - table[STATICCALL as usize] = Instruction::new(contract::static_call); + table[STATICCALL as usize] = Instruction::new(contract::call::); table[REVERT as usize] = Instruction::new(control::revert); table[INVALID as usize] = Instruction::new(control::invalid); table[SELFDESTRUCT as usize] = Instruction::new(host::selfdestruct); diff --git a/crates/interpreter/src/instructions/contract.rs b/crates/interpreter/src/instructions/contract.rs index e885671936..106e84664c 100644 --- a/crates/interpreter/src/instructions/contract.rs +++ b/crates/interpreter/src/instructions/contract.rs @@ -15,7 +15,7 @@ use crate::{ InstructionExecResult as Result, InstructionResult, InterpreterAction, }; use context_interface::CreateScheme; -use primitives::{hardfork::SpecId, Address, Bytes, B256, U256}; +use primitives::{hardfork::SpecId, Bytes, U256}; use std::boxed::Box; use crate::InstructionContext as Ictx; @@ -23,7 +23,7 @@ use crate::InstructionContext as Ictx; /// Implements the CREATE/CREATE2 instruction. /// /// Creates a new contract with provided bytecode. -pub fn create( +pub fn create( context: Ictx<'_, H, IT>, ) -> Result { // Static call check is before gas charging (unlike execution-specs where it's @@ -126,139 +126,62 @@ pub fn create( Err(InstructionResult::Suspend) } -/// Implements the CALL instruction. -/// -/// Message call with value transfer to another account. -pub fn call(mut context: Ictx<'_, H, IT>) -> Result { - popn!([local_gas_limit, to, value], context.interpreter); - let to = to.into_address(); - // Max gas limit is not possible in real ethereum situation. - let local_gas_limit = u64::try_from(local_gas_limit).unwrap_or(u64::MAX); - let has_transfer = !value.is_zero(); +/// Implements the CALL, CALLCODE, DELEGATECALL, and STATICCALL instructions. +pub fn call(mut context: Ictx<'_, H, IT>) -> Result { + use bytecode::opcode::{CALL, CALLCODE, DELEGATECALL, STATICCALL}; - if context.interpreter.runtime_flag.is_static() && has_transfer { - return Err(InstructionResult::CallNotAllowedInsideStatic); + if KIND == DELEGATECALL { + check!(context.interpreter, HOMESTEAD); + } else if KIND == STATICCALL { + check!(context.interpreter, BYZANTIUM); } - let (input, return_memory_offset) = - get_memory_input_and_out_ranges(context.interpreter, context.host.gas_params())?; - - let (gas_limit, bytecode, bytecode_hash) = - load_acc_and_calc_gas(&mut context, to, has_transfer, true, local_gas_limit)?; - - // Call host to interact with target contract - context - .interpreter - .bytecode - .set_action(InterpreterAction::NewFrame(FrameInput::Call(Box::new( - CallInputs { - input: CallInput::SharedBuffer(input), - gas_limit, - target_address: to, - caller: context.interpreter.input.target_address(), - bytecode_address: to, - known_bytecode: (bytecode_hash, bytecode), - value: CallValue::Transfer(value), - scheme: CallScheme::Call, - is_static: context.interpreter.runtime_flag.is_static(), - return_memory_offset, - reservoir: context.interpreter.gas.reservoir(), - }, - )))); - Err(InstructionResult::Suspend) -} - -/// Implements the CALLCODE instruction. -/// -/// Message call with alternative account's code. -pub fn call_code(mut context: Ictx<'_, H, IT>) -> Result { - popn!([local_gas_limit, to, value], context.interpreter); - let to = Address::from_word(B256::from(to)); + let (local_gas_limit, to, value) = if matches!(KIND, CALL | CALLCODE) { + popn!([local_gas_limit, to, value], context.interpreter); + (local_gas_limit, to, value) + } else { + popn!([local_gas_limit, to], context.interpreter); + (local_gas_limit, to, U256::ZERO) + }; + let to = to.into_address(); // Max gas limit is not possible in real ethereum situation. let local_gas_limit = u64::try_from(local_gas_limit).unwrap_or(u64::MAX); let has_transfer = !value.is_zero(); - let (input, return_memory_offset) = - get_memory_input_and_out_ranges(context.interpreter, context.host.gas_params())?; - - let (gas_limit, bytecode, bytecode_hash) = - load_acc_and_calc_gas(&mut context, to, has_transfer, false, local_gas_limit)?; - - // Call host to interact with target contract - context - .interpreter - .bytecode - .set_action(InterpreterAction::NewFrame(FrameInput::Call(Box::new( - CallInputs { - input: CallInput::SharedBuffer(input), - gas_limit, - target_address: context.interpreter.input.target_address(), - caller: context.interpreter.input.target_address(), - bytecode_address: to, - known_bytecode: (bytecode_hash, bytecode), - value: CallValue::Transfer(value), - scheme: CallScheme::CallCode, - is_static: context.interpreter.runtime_flag.is_static(), - return_memory_offset, - reservoir: context.interpreter.gas.reservoir(), - }, - )))); - Err(InstructionResult::Suspend) -} - -/// Implements the DELEGATECALL instruction. -/// -/// Message call with alternative account's code but same sender and value. -pub fn delegate_call(mut context: Ictx<'_, H, IT>) -> Result { - check!(context.interpreter, HOMESTEAD); - popn!([local_gas_limit, to], context.interpreter); - let to = Address::from_word(B256::from(to)); - // Max gas limit is not possible in real ethereum situation. - let local_gas_limit = u64::try_from(local_gas_limit).unwrap_or(u64::MAX); + if KIND == CALL && context.interpreter.runtime_flag.is_static() && has_transfer { + return Err(InstructionResult::CallNotAllowedInsideStatic); + } let (input, return_memory_offset) = get_memory_input_and_out_ranges(context.interpreter, context.host.gas_params())?; + let is_call = KIND == CALL; let (gas_limit, bytecode, bytecode_hash) = - load_acc_and_calc_gas(&mut context, to, false, false, local_gas_limit)?; + load_acc_and_calc_gas(&mut context, to, has_transfer, is_call, local_gas_limit)?; - // Call host to interact with target contract - context - .interpreter - .bytecode - .set_action(InterpreterAction::NewFrame(FrameInput::Call(Box::new( - CallInputs { - input: CallInput::SharedBuffer(input), - gas_limit, - target_address: context.interpreter.input.target_address(), - caller: context.interpreter.input.caller_address(), - bytecode_address: to, - known_bytecode: (bytecode_hash, bytecode), - value: CallValue::Apparent(context.interpreter.input.call_value()), - scheme: CallScheme::DelegateCall, - is_static: context.interpreter.runtime_flag.is_static(), - return_memory_offset, - reservoir: context.interpreter.gas.reservoir(), - }, - )))); - Err(InstructionResult::Suspend) -} - -/// Implements the STATICCALL instruction. -/// -/// Static message call (cannot modify state). -pub fn static_call(mut context: Ictx<'_, H, IT>) -> Result { - check!(context.interpreter, BYZANTIUM); - popn!([local_gas_limit, to], context.interpreter); - let to = Address::from_word(B256::from(to)); - // Max gas limit is not possible in real ethereum situation. - let local_gas_limit = u64::try_from(local_gas_limit).unwrap_or(u64::MAX); - - let (input, return_memory_offset) = - get_memory_input_and_out_ranges(context.interpreter, context.host.gas_params())?; - - let (gas_limit, bytecode, bytecode_hash) = - load_acc_and_calc_gas(&mut context, to, false, false, local_gas_limit)?; + let target_address = if matches!(KIND, CALLCODE | DELEGATECALL) { + context.interpreter.input.target_address() + } else { + to + }; + let caller = if KIND == DELEGATECALL { + context.interpreter.input.caller_address() + } else { + context.interpreter.input.target_address() + }; + let value = if KIND == DELEGATECALL { + CallValue::Apparent(context.interpreter.input.call_value()) + } else { + CallValue::Transfer(value) + }; + let scheme = match KIND { + CALL => CallScheme::Call, + CALLCODE => CallScheme::CallCode, + DELEGATECALL => CallScheme::DelegateCall, + STATICCALL => CallScheme::StaticCall, + _ => unreachable!(), + }; + let is_static = context.interpreter.runtime_flag.is_static() || KIND == STATICCALL; // Call host to interact with target contract context @@ -268,13 +191,13 @@ pub fn static_call(mut context: Ictx<'_, H, IT>) -> R CallInputs { input: CallInput::SharedBuffer(input), gas_limit, - target_address: to, - caller: context.interpreter.input.target_address(), + target_address, + caller, bytecode_address: to, known_bytecode: (bytecode_hash, bytecode), - value: CallValue::Transfer(U256::ZERO), - scheme: CallScheme::StaticCall, - is_static: true, + value, + scheme, + is_static, return_memory_offset, reservoir: context.interpreter.gas.reservoir(), }, From e1e61a24f83b828447da6aa3d47c8635510ef8c4 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Tue, 28 Apr 2026 13:52:51 +0200 Subject: [PATCH 2/2] refactor(interpreter): validate call kind --- crates/interpreter/src/instructions/contract.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/interpreter/src/instructions/contract.rs b/crates/interpreter/src/instructions/contract.rs index 106e84664c..c6d79a6cea 100644 --- a/crates/interpreter/src/instructions/contract.rs +++ b/crates/interpreter/src/instructions/contract.rs @@ -130,6 +130,10 @@ pub fn create( pub fn call(mut context: Ictx<'_, H, IT>) -> Result { use bytecode::opcode::{CALL, CALLCODE, DELEGATECALL, STATICCALL}; + if !matches!(KIND, CALL | CALLCODE | DELEGATECALL | STATICCALL) { + unreachable!("invalid call kind") + } + if KIND == DELEGATECALL { check!(context.interpreter, HOMESTEAD); } else if KIND == STATICCALL {