use crate::{
constants::*,
errors::{ContextResult, ExceptionalHalt, InternalError, TxResult, VMError},
gas_cost::{CODE_DEPOSIT_COST, CODE_DEPOSIT_REGULAR_COST_PER_WORD},
utils::create_eth_transfer_log,
vm::VM,
};
use bytes::Bytes;
use ethrex_common::types::{Code, Fork};
impl<'a> VM<'a> {
pub fn handle_precompile_result(
precompile_result: Result<Bytes, VMError>,
gas_limit: u64,
gas_remaining: u64,
) -> Result<ContextResult, VMError> {
match precompile_result {
Ok(output) => {
let gas_used = gas_limit
.checked_sub(gas_remaining)
.ok_or(InternalError::Underflow)?;
Ok(ContextResult {
result: TxResult::Success,
gas_used,
gas_spent: gas_used, output,
})
}
Err(error) => {
if error.should_propagate() {
return Err(error);
}
Ok(ContextResult {
result: TxResult::Revert(error),
gas_used: gas_limit,
gas_spent: gas_limit, output: Bytes::new(),
})
}
}
}
#[cold] pub fn handle_opcode_result(&mut self) -> Result<ContextResult, VMError> {
if self.is_create()? {
let validate_create = self.validate_contract_creation();
if let Err(error) = validate_create {
if error.should_propagate() {
return Err(error);
}
let callframe = &mut self.current_call_frame;
callframe.gas_remaining = 0;
#[expect(clippy::as_conversions, reason = "remaining gas conversion")]
let gas_used = callframe
.gas_limit
.checked_sub(callframe.gas_remaining as u64)
.ok_or(InternalError::Underflow)?;
return Ok(ContextResult {
result: TxResult::Revert(error),
gas_used,
gas_spent: gas_used, output: Bytes::new(),
});
}
let contract_address = self.current_call_frame.to;
let code = self.current_call_frame.output.clone();
self.update_account_bytecode(contract_address, Code::from_bytecode(code, self.crypto))?;
}
#[expect(clippy::as_conversions, reason = "remaining gas conversion")]
let gas_used = {
let callframe = &mut self.current_call_frame;
callframe
.gas_limit
.checked_sub(callframe.gas_remaining as u64)
.ok_or(InternalError::Underflow)?
};
Ok(ContextResult {
result: TxResult::Success,
gas_used,
gas_spent: gas_used, output: std::mem::take(&mut self.current_call_frame.output),
})
}
#[cold] pub fn handle_opcode_error(&mut self, error: VMError) -> Result<ContextResult, VMError> {
if error.should_propagate() {
return Err(error);
}
let callframe = &mut self.current_call_frame;
if !error.is_revert_opcode() {
callframe.gas_remaining = 0;
}
#[expect(clippy::as_conversions, reason = "remaining gas conversion")]
let gas_used = callframe
.gas_limit
.checked_sub(callframe.gas_remaining as u64)
.ok_or(InternalError::Underflow)?;
Ok(ContextResult {
result: TxResult::Revert(error),
gas_used,
gas_spent: gas_used, output: std::mem::take(&mut callframe.output),
})
}
pub fn handle_create_transaction(&mut self) -> Result<Option<ContextResult>, VMError> {
let new_contract_address = self.current_call_frame.to;
if let Some(recorder) = self.db.bal_recorder.as_mut() {
recorder.record_touched_address(new_contract_address);
}
let new_account = self.get_account_mut(new_contract_address)?;
if new_account.create_would_collide() {
self.current_call_frame.gas_remaining = 0;
return Ok(Some(ContextResult {
result: TxResult::Revert(ExceptionalHalt::AddressAlreadyOccupied.into()),
gas_used: self.env.gas_limit,
gas_spent: self.env.gas_limit, output: Bytes::new(),
}));
}
let value = self.current_call_frame.msg_value;
self.increase_account_balance(new_contract_address, value)?;
if self.env.config.fork >= Fork::Amsterdam && !value.is_zero() {
let log = create_eth_transfer_log(self.env.origin, new_contract_address, value);
self.substate.add_log(log);
}
self.increment_account_nonce(new_contract_address)?;
Ok(None)
}
fn validate_contract_creation(&mut self) -> Result<(), VMError> {
let fork = self.env.config.fork;
let code = &self.current_call_frame.output;
let code_length: u64 = code
.len()
.try_into()
.map_err(|_| InternalError::TypeConversion)?;
if code.first().is_some_and(|v| v == &EOF_PREFIX) {
return Err(ExceptionalHalt::InvalidContractPrefix.into());
}
if fork >= Fork::Amsterdam {
if code_length > AMSTERDAM_MAX_CODE_SIZE {
return Err(ExceptionalHalt::ContractOutputTooBig.into());
}
let words = code_length.div_ceil(32);
let regular = words
.checked_mul(CODE_DEPOSIT_REGULAR_COST_PER_WORD)
.ok_or(InternalError::Overflow)?;
let state = code_length
.checked_mul(self.cost_per_state_byte)
.ok_or(InternalError::Overflow)?;
self.current_call_frame.increase_consumed_gas(regular)?;
if state > 0 {
self.increase_state_gas(state)?;
}
} else {
if code_length > MAX_CODE_SIZE {
return Err(ExceptionalHalt::ContractOutputTooBig.into());
}
let regular = code_length
.checked_mul(CODE_DEPOSIT_COST)
.ok_or(InternalError::Overflow)?;
self.current_call_frame.increase_consumed_gas(regular)?;
}
Ok(())
}
}