evm 1.1.1

Ethereum Virtual Machine
Documentation
//! Internal routines that can be reused.

use alloc::vec::Vec;
use evm_interpreter::uint::H160;
use evm_interpreter::{
	ExitError, ExitException, Interpreter, Opcode,
	runtime::{
		RuntimeBackend, RuntimeEnvironment, RuntimeState, SetCodeOrigin, TouchKind, Transfer,
	},
	trap::{CallScheme, CallTrap, CreateTrap},
};

use crate::{
	backend::TransactionalBackend,
	invoker::{InvokerControl, InvokerExit},
	standard::{Config, InvokerState, Resolver, ResolverOrigin, SubstackInvoke},
};

/// Routine for making a call interpreter.
///
/// It will:
/// * Mark the current address as hot.
/// * Attempt transfer.
/// * Use [Resolver] to resolve the call into a new interpreter in the sub call stack.
#[allow(clippy::too_many_arguments)]
pub fn make_enter_call_machine<H, R>(
	origin: ResolverOrigin,
	resolver: &R,
	scheme: CallScheme,
	code_address: H160,
	input: Vec<u8>,
	transfer: Option<Transfer>,
	state: <R::Interpreter as Interpreter<H>>::State,
	handler: &mut H,
) -> Result<InvokerControl<R::Interpreter, R::State>, ExitError>
where
	R::State: AsRef<RuntimeState>,
	H: RuntimeEnvironment + RuntimeBackend + TransactionalBackend,
	R: Resolver<H>,
{
	handler.mark_hot(state.as_ref().context.address, TouchKind::StateChange);

	if let Some(transfer) = transfer {
		match handler.transfer(transfer) {
			Ok(()) => (),
			Err(err) => {
				return Ok(InvokerControl::DirectExit(InvokerExit {
					result: Err(err),
					substate: Some(state),
					retval: Vec::new(),
				}));
			}
		}
	}

	resolver.resolve_call(origin, scheme, code_address, input, state, handler)
}

/// Routine for making a create interpreter.
///
/// It will:
/// * Mark the current address as hot.
/// * Check for init size limit.
/// * Attempt transfer.
/// * Check create collision.
/// * Increase nonce of the current address.
/// * Reset state of the current address.
/// * And finally, use [Resolver] to resolve the call into a new interpreter in the sub call stack.
#[allow(clippy::too_many_arguments)]
pub fn make_enter_create_machine<H, R>(
	origin: ResolverOrigin,
	resolver: &R,
	_caller: H160,
	init_code: Vec<u8>,
	transfer: Transfer,
	state: <R::Interpreter as Interpreter<H>>::State,
	handler: &mut H,
) -> Result<InvokerControl<R::Interpreter, R::State>, ExitError>
where
	R::State: AsRef<RuntimeState> + AsRef<Config>,
	H: RuntimeEnvironment + RuntimeBackend + TransactionalBackend,
	R: Resolver<H>,
{
	handler.mark_hot(
		AsRef::<RuntimeState>::as_ref(&state).context.address,
		TouchKind::StateChange,
	);

	match handler.transfer(transfer) {
		Ok(()) => (),
		Err(err) => {
			return Ok(InvokerControl::DirectExit(InvokerExit {
				result: Err(err),
				substate: Some(state),
				retval: Vec::new(),
			}));
		}
	}

	if !handler.can_create(AsRef::<RuntimeState>::as_ref(&state).context.address) {
		return Ok(InvokerControl::DirectExit(InvokerExit {
			result: Err(ExitException::CreateCollision.into()),
			substate: Some(state),
			retval: Vec::new(),
		}));
	}

	if AsRef::<Config>::as_ref(&state).eip161_create_increase_nonce {
		match handler.inc_nonce(AsRef::<RuntimeState>::as_ref(&state).context.address) {
			Ok(()) => (),
			Err(err) => {
				return Ok(InvokerControl::DirectExit(InvokerExit {
					result: Err(err),
					substate: Some(state),
					retval: Vec::new(),
				}));
			}
		}
	}

	handler.reset_storage(AsRef::<RuntimeState>::as_ref(&state).context.address);
	handler.mark_create(AsRef::<RuntimeState>::as_ref(&state).context.address);

	resolver.resolve_create(origin, init_code, state, handler)
}

/// Enter a call substack, which internally calls [make_enter_call_machine].
#[allow(clippy::type_complexity, clippy::too_many_arguments)]
pub fn enter_call_substack<H, R>(
	resolver: &R,
	trap_data: CallTrap,
	code_address: H160,
	state: R::State,
	handler: &mut H,
) -> Result<(SubstackInvoke, InvokerControl<R::Interpreter, R::State>), ExitError>
where
	R::State: AsRef<RuntimeState>,
	H: RuntimeEnvironment + RuntimeBackend + TransactionalBackend,
	R: Resolver<H>,
{
	handler.push_substate();

	let work = || -> Result<_, ExitError> {
		let machine = make_enter_call_machine(
			ResolverOrigin::Substack,
			resolver,
			trap_data.scheme,
			code_address,
			trap_data.input.clone(),
			trap_data.transfer.clone(),
			state,
			handler,
		)?;

		Ok(machine)
	};

	let res = work();
	let invoke = SubstackInvoke::Call { trap: trap_data };

	match res {
		Ok(machine) => Ok((invoke, machine)),
		Err(err) => Ok((
			invoke,
			InvokerControl::DirectExit(InvokerExit {
				result: Err(err),
				substate: None,
				retval: Vec::new(),
			}),
		)),
	}
}

/// Enter a create substack, which internally calls [make_enter_create_machine].
#[allow(clippy::type_complexity, clippy::too_many_arguments)]
pub fn enter_create_substack<H, R>(
	resolver: &R,
	code: Vec<u8>,
	trap_data: CreateTrap,
	address: H160,
	state: R::State,
	handler: &mut H,
) -> Result<(SubstackInvoke, InvokerControl<R::Interpreter, R::State>), ExitError>
where
	R::State: AsRef<RuntimeState> + AsRef<Config>,
	H: RuntimeEnvironment + RuntimeBackend + TransactionalBackend,
	R: Resolver<H>,
{
	handler.push_substate();

	let scheme = trap_data.scheme;
	let value = trap_data.value;
	let caller = scheme.caller();

	let transfer = Transfer {
		source: caller,
		target: address,
		value,
	};

	let invoke = SubstackInvoke::Create {
		address,
		trap: trap_data,
	};
	let machine = make_enter_create_machine(
		ResolverOrigin::Substack,
		resolver,
		caller,
		code,
		transfer,
		state,
		handler,
	)?;

	Ok((invoke, machine))
}

fn check_first_byte(config: &Config, code: &[u8]) -> Result<(), ExitError> {
	if config.eip3541_disallow_executable_format && Some(&Opcode::EOFMAGIC.as_u8()) == code.first()
	{
		return Err(ExitException::InvalidOpcode(Opcode::EOFMAGIC).into());
	}
	Ok(())
}

/// Routine for deploying the create code.
pub fn deploy_create_code<S, H>(
	address: H160,
	retbuf: Vec<u8>,
	state: &mut S,
	handler: &mut H,
	origin: SetCodeOrigin,
) -> Result<(), ExitError>
where
	S: InvokerState + AsRef<Config>,
	H: RuntimeEnvironment + RuntimeBackend + TransactionalBackend,
{
	check_first_byte(state.as_ref(), &retbuf[..])?;

	if let Some(limit) = AsRef::<Config>::as_ref(state).create_contract_limit()
		&& retbuf.len() > limit
	{
		return Err(ExitException::CreateContractLimit.into());
	}

	state.record_codedeposit(retbuf.len())?;

	handler.set_code(address, retbuf, origin)?;

	Ok(())
}