use std::{cell::RefCell, collections::BTreeSet, rc::Rc};
use parity_wasm::elements::Module;
use tracing::warn;
use wasmi::ModuleRef;
use casper_types::{
account::AccountHash,
bytesrepr::FromBytes,
contracts::NamedKeys,
system::{auction, handle_payment, mint},
BlockTime, CLTyped, CLValue, ContractPackage, DeployHash, EntryPoint, EntryPointType, Key,
Phase, ProtocolVersion, RuntimeArgs,
};
use crate::{
core::{
engine_state::{
execution_effect::ExecutionEffect, execution_result::ExecutionResult,
system_contract_cache::SystemContractCache, EngineConfig,
},
execution::{address_generator::AddressGenerator, Error},
runtime::{extract_access_rights_from_keys, instance_and_memory, Runtime},
runtime_context::{self, RuntimeContext},
tracking_copy::TrackingCopy,
},
shared::{account::Account, gas::Gas, newtypes::CorrelationId, stored_value::StoredValue},
storage::{global_state::StateReader, protocol_data::ProtocolData},
};
macro_rules! on_fail_charge {
($fn:expr) => {
match $fn {
Ok(res) => res,
Err(e) => {
let exec_err: Error = e.into();
warn!("Execution failed: {:?}", exec_err);
return ExecutionResult::precondition_failure(exec_err.into());
}
}
};
($fn:expr, $cost:expr, $transfers:expr) => {
match $fn {
Ok(res) => res,
Err(e) => {
let exec_err: Error = e.into();
warn!("Execution failed: {:?}", exec_err);
return ExecutionResult::Failure {
error: exec_err.into(),
effect: Default::default(),
transfers: $transfers,
cost: $cost,
};
}
}
};
($fn:expr, $cost:expr, $effect:expr, $transfers:expr) => {
match $fn {
Ok(res) => res,
Err(e) => {
let exec_err: Error = e.into();
warn!("Execution failed: {:?}", exec_err);
return ExecutionResult::Failure {
error: exec_err.into(),
effect: $effect,
transfers: $transfers,
cost: $cost,
};
}
}
};
}
pub struct Executor {
config: EngineConfig,
}
#[allow(clippy::too_many_arguments)]
impl Executor {
pub fn new(config: EngineConfig) -> Self {
Executor { config }
}
pub fn config(&self) -> EngineConfig {
self.config
}
pub fn exec<R>(
&self,
module: Module,
entry_point: EntryPoint,
args: RuntimeArgs,
base_key: Key,
account: &Account,
named_keys: &mut NamedKeys,
authorization_keys: BTreeSet<AccountHash>,
blocktime: BlockTime,
deploy_hash: DeployHash,
gas_limit: Gas,
protocol_version: ProtocolVersion,
correlation_id: CorrelationId,
tracking_copy: Rc<RefCell<TrackingCopy<R>>>,
phase: Phase,
protocol_data: ProtocolData,
system_contract_cache: SystemContractCache,
contract_package: &ContractPackage,
) -> ExecutionResult
where
R: StateReader<Key, StoredValue>,
R::Error: Into<Error>,
{
let entry_point_name = entry_point.name();
let entry_point_type = entry_point.entry_point_type();
let entry_point_access = entry_point.access();
let (instance, memory) = on_fail_charge!(instance_and_memory(
module.clone(),
protocol_version,
protocol_data.wasm_config()
));
let access_rights = {
let keys: Vec<Key> = named_keys.values().cloned().collect();
extract_access_rights_from_keys(keys)
};
let hash_address_generator = {
let generator = AddressGenerator::new(deploy_hash.as_bytes(), phase);
Rc::new(RefCell::new(generator))
};
let uref_address_generator = {
let generator = AddressGenerator::new(deploy_hash.as_bytes(), phase);
Rc::new(RefCell::new(generator))
};
let target_address_generator = {
let generator = AddressGenerator::new(deploy_hash.as_bytes(), phase);
Rc::new(RefCell::new(generator))
};
let gas_counter: Gas = Gas::default();
let transfers = Vec::default();
let effects_snapshot = tracking_copy.borrow().effect();
let context = RuntimeContext::new(
tracking_copy,
entry_point_type,
named_keys,
access_rights,
args.clone(),
authorization_keys,
&account,
base_key,
blocktime,
deploy_hash,
gas_limit,
gas_counter,
hash_address_generator,
uref_address_generator,
target_address_generator,
protocol_version,
correlation_id,
phase,
protocol_data,
transfers,
);
let mut runtime = Runtime::new(self.config, system_contract_cache, memory, module, context);
let accounts_access_rights = {
let keys: Vec<Key> = account.named_keys().values().cloned().collect();
extract_access_rights_from_keys(keys)
};
on_fail_charge!(runtime_context::validate_entry_point_access_with(
&contract_package,
entry_point_access,
|uref| runtime_context::uref_has_access_rights(uref, &accounts_access_rights)
));
if runtime.is_mint(base_key) {
match runtime.call_host_mint(
protocol_version,
entry_point.name(),
&mut runtime.context().named_keys().to_owned(),
&args,
Default::default(),
) {
Ok(_value) => {
return ExecutionResult::Success {
effect: runtime.context().effect(),
transfers: runtime.context().transfers().to_owned(),
cost: runtime.context().gas_counter(),
};
}
Err(error) => {
return ExecutionResult::Failure {
error: error.into(),
effect: effects_snapshot,
transfers: runtime.context().transfers().to_owned(),
cost: runtime.context().gas_counter(),
};
}
}
} else if runtime.is_handle_payment(base_key) {
match runtime.call_host_handle_payment(
protocol_version,
entry_point.name(),
&mut runtime.context().named_keys().to_owned(),
&args,
Default::default(),
) {
Ok(_value) => {
return ExecutionResult::Success {
effect: runtime.context().effect(),
transfers: runtime.context().transfers().to_owned(),
cost: runtime.context().gas_counter(),
};
}
Err(error) => {
return ExecutionResult::Failure {
error: error.into(),
effect: effects_snapshot,
transfers: runtime.context().transfers().to_owned(),
cost: runtime.context().gas_counter(),
};
}
}
} else if runtime.is_auction(base_key) {
match runtime.call_host_auction(
protocol_version,
entry_point.name(),
&mut runtime.context().named_keys().to_owned(),
&args,
Default::default(),
) {
Ok(_value) => {
return ExecutionResult::Success {
effect: runtime.context().effect(),
transfers: runtime.context().transfers().to_owned(),
cost: runtime.context().gas_counter(),
}
}
Err(error) => {
return ExecutionResult::Failure {
error: error.into(),
effect: effects_snapshot,
transfers: runtime.context().transfers().to_owned(),
cost: runtime.context().gas_counter(),
}
}
}
}
on_fail_charge!(
instance.invoke_export(entry_point_name, &[], &mut runtime),
runtime.context().gas_counter(),
effects_snapshot,
runtime.context().transfers().to_owned()
);
ExecutionResult::Success {
effect: runtime.context().effect(),
transfers: runtime.context().transfers().to_owned(),
cost: runtime.context().gas_counter(),
}
}
pub fn exec_standard_payment<R>(
&self,
system_module: Module,
payment_args: RuntimeArgs,
payment_base_key: Key,
account: &Account,
payment_named_keys: &mut NamedKeys,
authorization_keys: BTreeSet<AccountHash>,
blocktime: BlockTime,
deploy_hash: DeployHash,
payment_gas_limit: Gas,
protocol_version: ProtocolVersion,
correlation_id: CorrelationId,
tracking_copy: Rc<RefCell<TrackingCopy<R>>>,
phase: Phase,
protocol_data: ProtocolData,
system_contract_cache: SystemContractCache,
) -> ExecutionResult
where
R: StateReader<Key, StoredValue>,
R::Error: Into<Error>,
{
let hash_address_generator = {
let generator = AddressGenerator::new(deploy_hash.as_bytes(), phase);
Rc::new(RefCell::new(generator))
};
let uref_address_generator = {
let generator = AddressGenerator::new(deploy_hash.as_bytes(), phase);
Rc::new(RefCell::new(generator))
};
let transfer_address_generator = {
let generator = AddressGenerator::new(deploy_hash.as_bytes(), phase);
Rc::new(RefCell::new(generator))
};
let mut runtime = match self.create_runtime(
system_module,
EntryPointType::Session,
payment_args,
payment_named_keys,
Default::default(),
payment_base_key,
&account,
authorization_keys,
blocktime,
deploy_hash,
payment_gas_limit,
hash_address_generator,
uref_address_generator,
transfer_address_generator,
protocol_version,
correlation_id,
Rc::clone(&tracking_copy),
phase,
protocol_data,
system_contract_cache,
) {
Ok((_instance, runtime)) => runtime,
Err(error) => {
return ExecutionResult::Failure {
error: error.into(),
effect: Default::default(),
transfers: Vec::default(),
cost: Gas::default(),
};
}
};
let effects_snapshot = tracking_copy.borrow().effect();
match runtime.call_host_standard_payment() {
Ok(()) => ExecutionResult::Success {
effect: runtime.context().effect(),
transfers: runtime.context().transfers().to_owned(),
cost: runtime.context().gas_counter(),
},
Err(error) => ExecutionResult::Failure {
error: error.into(),
effect: effects_snapshot,
transfers: runtime.context().transfers().to_owned(),
cost: runtime.context().gas_counter(),
},
}
}
pub fn exec_system_contract<R, T>(
&self,
direct_system_contract_call: DirectSystemContractCall,
module: Module,
runtime_args: RuntimeArgs,
named_keys: &mut NamedKeys,
extra_keys: &[Key],
base_key: Key,
account: &Account,
authorization_keys: BTreeSet<AccountHash>,
blocktime: BlockTime,
deploy_hash: DeployHash,
gas_limit: Gas,
protocol_version: ProtocolVersion,
correlation_id: CorrelationId,
tracking_copy: Rc<RefCell<TrackingCopy<R>>>,
phase: Phase,
protocol_data: ProtocolData,
system_contract_cache: SystemContractCache,
) -> (Option<T>, ExecutionResult)
where
R: StateReader<Key, StoredValue>,
R::Error: Into<Error>,
T: FromBytes + CLTyped,
{
match direct_system_contract_call {
DirectSystemContractCall::Slash
| DirectSystemContractCall::RunAuction
| DirectSystemContractCall::DistributeRewards => {
if Some(protocol_data.auction().value()) != base_key.into_hash() {
panic!(
"{} should only be called with the auction contract",
direct_system_contract_call.entry_point_name()
);
}
}
DirectSystemContractCall::FinalizePayment
| DirectSystemContractCall::GetPaymentPurse => {
if Some(protocol_data.handle_payment().value()) != base_key.into_hash() {
panic!(
"{} should only be called with the handle payment contract",
direct_system_contract_call.entry_point_name()
);
}
}
DirectSystemContractCall::CreatePurse | DirectSystemContractCall::Transfer => {
if Some(protocol_data.mint().value()) != base_key.into_hash() {
panic!(
"{} should only be called with the mint contract",
direct_system_contract_call.entry_point_name()
);
}
}
DirectSystemContractCall::GetEraValidators => {
if Some(protocol_data.auction().value()) != base_key.into_hash() {
panic!(
"{} should only be called with the auction contract",
direct_system_contract_call.entry_point_name()
);
}
}
}
let hash_address_generator = {
let generator = AddressGenerator::new(deploy_hash.as_bytes(), phase);
Rc::new(RefCell::new(generator))
};
let uref_address_generator = {
let generator = AddressGenerator::new(deploy_hash.as_bytes(), phase);
Rc::new(RefCell::new(generator))
};
let transfer_address_generator = {
let generator = AddressGenerator::new(deploy_hash.as_bytes(), phase);
Rc::new(RefCell::new(generator))
};
let gas_counter = Gas::default();
let effect_snapshot = tracking_copy.borrow().effect();
let transfers = Vec::default();
let (_, runtime) = match self.create_runtime(
module,
EntryPointType::Contract,
runtime_args.clone(),
named_keys,
extra_keys,
base_key,
account,
authorization_keys,
blocktime,
deploy_hash,
gas_limit,
hash_address_generator,
uref_address_generator,
transfer_address_generator,
protocol_version,
correlation_id,
tracking_copy,
phase,
protocol_data,
system_contract_cache,
) {
Ok((instance, runtime)) => (instance, runtime),
Err(error) => {
return ExecutionResult::Failure {
effect: effect_snapshot,
transfers,
cost: gas_counter,
error: error.into(),
}
.take_without_ret()
}
};
let mut inner_named_keys = runtime.context().named_keys().clone();
let ret = direct_system_contract_call.host_exec(
runtime,
protocol_version,
&mut inner_named_keys,
&runtime_args,
extra_keys,
effect_snapshot,
);
*named_keys = inner_named_keys;
ret
}
pub fn exec_wasm_direct<R, T>(
&self,
module: Module,
entry_point_name: &str,
args: RuntimeArgs,
account: &mut Account,
authorization_keys: BTreeSet<AccountHash>,
blocktime: BlockTime,
deploy_hash: DeployHash,
gas_limit: Gas,
hash_address_generator: Rc<RefCell<AddressGenerator>>,
uref_address_generator: Rc<RefCell<AddressGenerator>>,
transfer_address_generator: Rc<RefCell<AddressGenerator>>,
protocol_version: ProtocolVersion,
correlation_id: CorrelationId,
tracking_copy: Rc<RefCell<TrackingCopy<R>>>,
phase: Phase,
protocol_data: ProtocolData,
system_contract_cache: SystemContractCache,
) -> Result<T, Error>
where
R: StateReader<Key, StoredValue>,
R::Error: Into<Error>,
T: FromBytes + CLTyped,
{
let mut named_keys: NamedKeys = account.named_keys().clone();
let base_key = account.account_hash().into();
let (instance, mut runtime) = self.create_runtime(
module,
EntryPointType::Session,
args,
&mut named_keys,
Default::default(),
base_key,
account,
authorization_keys,
blocktime,
deploy_hash,
gas_limit,
hash_address_generator,
uref_address_generator,
transfer_address_generator,
protocol_version,
correlation_id,
tracking_copy,
phase,
protocol_data,
system_contract_cache,
)?;
let error: wasmi::Error = match instance.invoke_export(entry_point_name, &[], &mut runtime)
{
Err(error) => error,
Ok(_) => {
let result = runtime.take_host_buffer().unwrap_or(CLValue::from_t(())?);
let ret = result.into_t()?;
*account.named_keys_mut() = named_keys;
return Ok(ret);
}
};
let return_value: CLValue = match error
.as_host_error()
.and_then(|host_error| host_error.downcast_ref::<Error>())
{
Some(Error::Ret(_)) => runtime
.take_host_buffer()
.ok_or(Error::ExpectedReturnValue)?,
Some(Error::Revert(code)) => return Err(Error::Revert(*code)),
Some(error) => return Err(error.clone()),
_ => return Err(Error::Interpreter(error.into())),
};
let ret = return_value.into_t()?;
*account.named_keys_mut() = named_keys;
Ok(ret)
}
pub fn create_runtime<'a, R>(
&self,
module: Module,
entry_point_type: EntryPointType,
runtime_args: RuntimeArgs,
named_keys: &'a mut NamedKeys,
extra_keys: &[Key],
base_key: Key,
account: &'a Account,
authorization_keys: BTreeSet<AccountHash>,
blocktime: BlockTime,
deploy_hash: DeployHash,
gas_limit: Gas,
hash_address_generator: Rc<RefCell<AddressGenerator>>,
uref_address_generator: Rc<RefCell<AddressGenerator>>,
transfer_address_generator: Rc<RefCell<AddressGenerator>>,
protocol_version: ProtocolVersion,
correlation_id: CorrelationId,
tracking_copy: Rc<RefCell<TrackingCopy<R>>>,
phase: Phase,
protocol_data: ProtocolData,
system_contract_cache: SystemContractCache,
) -> Result<(ModuleRef, Runtime<'a, R>), Error>
where
R: StateReader<Key, StoredValue>,
R::Error: Into<Error>,
{
let access_rights = {
let mut keys: Vec<Key> = named_keys.values().cloned().collect();
keys.extend(extra_keys);
extract_access_rights_from_keys(keys)
};
let gas_counter = Gas::default();
let transfers = Vec::default();
let runtime_context = RuntimeContext::new(
tracking_copy,
entry_point_type,
named_keys,
access_rights,
runtime_args,
authorization_keys,
account,
base_key,
blocktime,
deploy_hash,
gas_limit,
gas_counter,
hash_address_generator,
uref_address_generator,
transfer_address_generator,
protocol_version,
correlation_id,
phase,
protocol_data,
transfers,
);
let (instance, memory) = instance_and_memory(
module.clone(),
protocol_version,
protocol_data.wasm_config(),
)?;
let runtime = Runtime::new(
self.config,
system_contract_cache,
memory,
module,
runtime_context,
);
Ok((instance, runtime))
}
}
pub enum DirectSystemContractCall {
Slash,
RunAuction,
DistributeRewards,
FinalizePayment,
CreatePurse,
Transfer,
GetEraValidators,
GetPaymentPurse,
}
impl DirectSystemContractCall {
fn entry_point_name(&self) -> &str {
match self {
DirectSystemContractCall::Slash => auction::METHOD_SLASH,
DirectSystemContractCall::RunAuction => auction::METHOD_RUN_AUCTION,
DirectSystemContractCall::DistributeRewards => auction::METHOD_DISTRIBUTE,
DirectSystemContractCall::FinalizePayment => handle_payment::METHOD_FINALIZE_PAYMENT,
DirectSystemContractCall::CreatePurse => mint::METHOD_CREATE,
DirectSystemContractCall::Transfer => mint::METHOD_TRANSFER,
DirectSystemContractCall::GetEraValidators => auction::METHOD_GET_ERA_VALIDATORS,
DirectSystemContractCall::GetPaymentPurse => handle_payment::METHOD_GET_PAYMENT_PURSE,
}
}
fn host_exec<R, T>(
&self,
mut runtime: Runtime<R>,
protocol_version: ProtocolVersion,
named_keys: &mut NamedKeys,
runtime_args: &RuntimeArgs,
extra_keys: &[Key],
execution_effect: ExecutionEffect,
) -> (Option<T>, ExecutionResult)
where
R: StateReader<Key, StoredValue>,
R::Error: Into<Error>,
T: FromBytes + CLTyped,
{
let entry_point_name = self.entry_point_name();
let result = match self {
DirectSystemContractCall::Slash
| DirectSystemContractCall::RunAuction
| DirectSystemContractCall::DistributeRewards => runtime.call_host_auction(
protocol_version,
entry_point_name,
named_keys,
runtime_args,
extra_keys,
),
DirectSystemContractCall::FinalizePayment => runtime.call_host_handle_payment(
protocol_version,
entry_point_name,
named_keys,
runtime_args,
extra_keys,
),
DirectSystemContractCall::CreatePurse | DirectSystemContractCall::Transfer => runtime
.call_host_mint(
protocol_version,
entry_point_name,
named_keys,
runtime_args,
extra_keys,
),
DirectSystemContractCall::GetEraValidators => runtime.call_host_auction(
protocol_version,
entry_point_name,
named_keys,
runtime_args,
extra_keys,
),
DirectSystemContractCall::GetPaymentPurse => runtime.call_host_handle_payment(
protocol_version,
entry_point_name,
named_keys,
runtime_args,
extra_keys,
),
};
match result {
Ok(value) => match value.into_t() {
Ok(ret) => ExecutionResult::Success {
effect: runtime.context().effect(),
transfers: runtime.context().transfers().to_owned(),
cost: runtime.context().gas_counter(),
}
.take_with_ret(ret),
Err(error) => ExecutionResult::Failure {
error: Error::CLValue(error).into(),
effect: execution_effect,
transfers: runtime.context().transfers().to_owned(),
cost: runtime.context().gas_counter(),
}
.take_without_ret(),
},
Err(error) => ExecutionResult::Failure {
error: error.into(),
effect: execution_effect,
transfers: runtime.context().transfers().to_owned(),
cost: runtime.context().gas_counter(),
}
.take_without_ret(),
}
}
}