use std::{sync::Arc, time::Instant};
use log::*;
use tari_engine_types::{
commit_result::{ExecuteResult, FinalizeResult},
component::{Component, derive_component_address_from_public_key},
entity_id_provider::EntityIdProvider,
indexed_value::{IndexedValue, IndexedWellKnownTypes},
instruction_result::InstructionResult,
limits,
lock::LockFlag,
published_template::TemplateBlob,
virtual_substate::VirtualSubstates,
};
use tari_ootle_common_types::{optional::Optional, services::template_provider::TemplateProvider};
use tari_ootle_template_metadata::MetadataHash;
use tari_ootle_transaction::{
AllocatableAddressType,
ComponentReference,
Instruction,
MigrateFunction,
ResourceAddressRef,
args::{InstructionArg, WorkspaceId, WorkspaceOffsetId},
call_arg,
call_args,
};
use tari_template_abi::{FunctionDef, Type};
use tari_template_builtin::ACCOUNT_TEMPLATE_ADDRESS;
use tari_template_lib::{
args::{AllocateAddressResult, BucketAction, BucketGetAmountArg, BucketRef, WorkspaceAction},
invoke_args,
models::{Bucket, BucketId},
types::{
Amount,
ComponentAddress,
NonFungibleAddress,
OwnerRule,
TemplateAddress,
access_rules::ComponentAccessRules,
constants::STEALTH_TARI_RESOURCE_ADDRESS,
crypto::RistrettoPublicKeyBytes,
stealth::StealthTransferStatement,
},
};
use crate::{
executables::{Executable, WeightedExecutable},
runtime::{
AuthParams,
AuthorizationScope,
NativeAction,
PayFee,
Runtime,
RuntimeError,
RuntimeInterface,
RuntimeInterfaceImpl,
RuntimeModule,
StateTracker,
scope::{CallScope, PushCallFrame},
},
state_store::StateReader,
template::LoadedTemplate,
traits::{ClaimProofVerifier, Invokable},
transaction::{TransactionError, error::TransactionErrorKind},
wasm::{WasmModule, WasmProcess},
};
const LOG_TARGET: &str = "tari::ootle::engine::instruction_processor";
const ACCOUNT_CONSTRUCTOR_FUNCTION: &str = "create";
const ACCOUNT_DEPOSIT_METHOD: &str = "deposit";
pub type ModulesCollection<TStore> = Arc<[Box<dyn RuntimeModule<TStore>>]>;
pub struct TransactionProcessor<TStore, TTemplateProvider> {
template_provider: Arc<TTemplateProvider>,
state_db: TStore,
auth_params: AuthParams,
virtual_substates: VirtualSubstates,
modules: ModulesCollection<TStore>,
claim_burn_proof_verifier: Arc<dyn ClaimProofVerifier + Send + Sync + 'static>,
}
impl<TStore, TTemplateProvider> TransactionProcessor<TStore, TTemplateProvider>
where
TStore: StateReader + Clone + 'static,
TTemplateProvider: TemplateProvider<Template = LoadedTemplate>,
{
pub fn new(
template_provider: Arc<TTemplateProvider>,
state_db: TStore,
auth_params: AuthParams,
virtual_substates: VirtualSubstates,
modules: ModulesCollection<TStore>,
claim_burn_proof_verifier: Arc<dyn ClaimProofVerifier + Send + Sync + 'static>,
) -> Self {
Self {
template_provider,
state_db,
auth_params,
virtual_substates,
modules,
claim_burn_proof_verifier,
}
}
#[expect(clippy::too_many_lines)]
pub fn execute<E: Executable + WeightedExecutable>(self, executable: E) -> Result<ExecuteResult, TransactionError> {
let id = executable.to_id();
let timer = Instant::now();
let entity_id_provider = EntityIdProvider::new(id.as_hash(), 1000);
let Self {
template_provider,
state_db,
auth_params,
virtual_substates,
modules,
claim_burn_proof_verifier,
} = self;
let execute_epoch = virtual_substates.current_epoch();
let initial_auth_scope = AuthorizationScope::new(auth_params.initial_ownership_proofs);
let mut initial_call_scope = CallScope::new();
initial_call_scope.set_auth_scope(initial_auth_scope);
initial_call_scope.add_substate_to_owned(STEALTH_TARI_RESOURCE_ADDRESS.into());
for input_substate_id in executable.all_inputs_iter() {
debug!(
target: LOG_TARGET,
"Adding substate to initial call scope: {}",
input_substate_id
);
initial_call_scope.add_substate_to_owned(input_substate_id);
}
let transaction_weight = executable.calculate_weight();
let tracker = StateTracker::new(
state_db,
virtual_substates,
initial_call_scope,
id.as_hash(),
transaction_weight,
);
let transaction_signer_public_key =
executable
.main_signer()
.ok_or_else(|| TransactionErrorKind::InvariantError {
details: "Transaction must have at least one authorized signature".to_string(),
})?;
let instructions = executable.into_instructions();
let blobs = std::sync::Arc::new(instructions.blobs);
let mut runtime_interface = Box::new(RuntimeInterfaceImpl::initialize(
tracker,
template_provider.clone(),
transaction_signer_public_key,
entity_id_provider,
modules,
claim_burn_proof_verifier,
std::sync::Arc::clone(&blobs),
)?) as Box<dyn RuntimeInterface>;
let runtime = Runtime::from_mut(&mut runtime_interface);
runtime_interface.set_runtime_pointer(runtime.as_pointer());
let transaction_hash = id.as_hash();
let (mut runtime, fee_exec_results) =
Self::process_instructions(&template_provider, runtime, instructions.fee, &blobs);
let fee_exec_result = match fee_exec_results {
Ok(execution_results) => {
if let Err(err) = runtime.interface_mut().checkpoint_fee_intent() {
let mut finalize = FinalizeResult::new_rejected(transaction_hash, err.to_reject_reason(None));
finalize.execution_results = execution_results;
return Ok(ExecuteResult {
finalize,
execution_time: timer.elapsed(),
execute_epoch: execute_epoch.map(Into::into),
wasm_execution_points: runtime.interface().wasm_points_consumed(),
});
}
execution_results
},
Err(err) => {
warn!(
target: LOG_TARGET,
"Fee payment failed for transaction {}: {}",
transaction_hash,
err
);
return Ok(ExecuteResult {
finalize: FinalizeResult::new_rejected(transaction_hash, err.to_reject_reason()),
execution_time: timer.elapsed(),
execute_epoch: execute_epoch.map(Into::into),
wasm_execution_points: runtime.interface().wasm_points_consumed(),
});
},
};
runtime
.interface_mut()
.workspace_invoke(WorkspaceAction::DropAll, invoke_args![].into())?;
let (mut runtime, instruction_result) =
Self::process_instructions(&template_provider, runtime, instructions.main, &blobs);
let wasm_execution_points = runtime.interface().wasm_points_consumed();
match instruction_result {
Ok(execution_results) => {
let mut finalize = runtime.interface_mut().finalize()?;
finalize.execution_results = execution_results;
Ok(ExecuteResult {
finalize,
execution_time: timer.elapsed(),
execute_epoch: execute_epoch.map(Into::into),
wasm_execution_points,
})
},
Err(err) => {
let mut finalize = runtime.interface_mut().finalize_failure(err.to_reject_reason())?;
finalize.execution_results = fee_exec_result;
Ok(ExecuteResult {
finalize,
execution_time: timer.elapsed(),
execute_epoch: execute_epoch.map(Into::into),
wasm_execution_points,
})
},
}
}
fn process_instructions(
template_provider: &TTemplateProvider,
mut runtime: Runtime,
instructions: Vec<Instruction>,
blobs: &tari_ootle_transaction::Blobs,
) -> (Runtime, Result<Vec<InstructionResult>, TransactionError>) {
let result: Result<_, _> = instructions
.into_iter()
.enumerate()
.map(|(idx, instruction)| {
Self::process_instruction(template_provider, &mut runtime, instruction, blobs)
.map_err(|e| TransactionError::new(idx + 1, e))
})
.collect();
let result = result.and_then(|result| {
runtime.interface().validate_finalized()?;
Ok::<_, TransactionError>(result)
});
(runtime, result)
}
#[allow(clippy::too_many_lines)]
fn process_instruction(
template_provider: &TTemplateProvider,
runtime: &mut Runtime,
instruction: Instruction,
blobs: &tari_ootle_transaction::Blobs,
) -> Result<InstructionResult, TransactionErrorKind> {
debug!(target: LOG_TARGET, "instruction = {:?}", instruction);
match instruction {
Instruction::CreateAccount {
owner_public_key: public_key_address,
owner_rule,
access_rules,
bucket_workspace_id: workspace_id,
} => Self::create_account(
template_provider,
runtime,
&public_key_address,
owner_rule,
access_rules,
workspace_id,
),
Instruction::CallFunction {
address: template_address,
function,
args,
} => Self::call_function(template_provider, runtime, &template_address, &function, args),
Instruction::CallMethod { call, method, args } => {
Self::call_method(template_provider, runtime, call, &method, args)
},
Instruction::PutLastInstructionOutputOnWorkspace { key } => {
Self::put_output_on_workspace_with_id(runtime, key)?;
Ok(InstructionResult::empty())
},
Instruction::DropAllProofsInWorkspace => {
Self::drop_all_proofs_in_workspace(runtime)?;
Ok(InstructionResult::empty())
},
Instruction::EmitLog { level, message } => {
runtime.interface_mut().emit_log(level, message.into_string())?;
Ok(InstructionResult::empty())
},
Instruction::ClaimBurn { claim, output_data } => {
runtime.interface_mut().claim_burn(*claim, output_data)?;
Ok(InstructionResult::empty())
},
Instruction::ClaimValidatorFees { address } => {
runtime.interface_mut().claim_validator_fees(address)?;
Ok(InstructionResult::empty())
},
Instruction::Assert { key, assertion } => {
runtime
.interface_mut()
.workspace_invoke(WorkspaceAction::Assert, invoke_args![key, assertion].into())?;
Ok(InstructionResult::empty())
},
Instruction::TakeFromBucket {
input_bucket,
amount,
output_bucket,
} => {
let runtime_mut = runtime.interface_mut();
let item = runtime_mut.workspace_invoke(WorkspaceAction::Get, invoke_args![input_bucket].into())?;
let bucket_ref = BucketRef::Ref(item.decode()?);
let bucket = runtime_mut.bucket_invoke(bucket_ref, BucketAction::Take, invoke_args![amount].into())?;
let prev_bucket_val = runtime_mut
.bucket_invoke(
bucket_ref,
BucketAction::GetAmount,
invoke_args![BucketGetAmountArg::Everything].into(),
)?
.decode::<Amount>()?;
if prev_bucket_val.is_zero() {
runtime_mut.bucket_invoke(bucket_ref, BucketAction::DropEmpty, invoke_args![].into())?;
}
runtime_mut.put_on_workspace(output_bucket, IndexedValue::from_value(bucket.into_value()?)?)?;
Ok(InstructionResult::empty())
},
Instruction::PutIntoBucket { src, dest } => {
let runtime_mut = runtime.interface_mut();
let src_item = runtime_mut.workspace_invoke(WorkspaceAction::Get, invoke_args![src].into())?;
let src_bucket_id: BucketId = src_item.decode()?;
let dest_item = runtime_mut.workspace_invoke(WorkspaceAction::Get, invoke_args![dest].into())?;
let dest_bucket_ref = BucketRef::Ref(dest_item.decode()?);
runtime_mut.bucket_invoke(dest_bucket_ref, BucketAction::Join, invoke_args![src_bucket_id].into())?;
Ok(InstructionResult::empty())
},
Instruction::PublishTemplate { binary, metadata_hash } => {
let bytes = blobs.get(binary).ok_or(TransactionErrorKind::BlobIndexOutOfBounds {
index: binary,
count: blobs.len(),
})?;
Self::publish_template(runtime, bytes, metadata_hash)
},
Instruction::AllocateAddress {
allocatable_type: substate_type,
workspace_id,
} => Self::allocate_address(runtime, substate_type, workspace_id),
Instruction::StealthTransfer {
resource_address_ref: resource_address,
statement,
revealed_input_bucket,
} => Self::stealth_transfer(runtime, resource_address, statement, revealed_input_bucket),
Instruction::PayFeeFromBucket { bucket } => Self::pay_fee_from_bucket(runtime, bucket),
Instruction::UpdateComponentTemplate {
component,
migrate,
new_template,
} => Self::update_component_template(template_provider, runtime, component, new_template, migrate),
}
}
fn update_component_template(
template_provider: &TTemplateProvider,
runtime: &mut Runtime,
component: ComponentReference,
new_template: TemplateAddress,
migrate: Option<MigrateFunction>,
) -> Result<InstructionResult, TransactionErrorKind> {
let (component_address, component) = runtime.interface_mut().load_component(component)?;
let template = template_provider
.get_template(&new_template)
.map_err(|e| TransactionErrorKind::FailedToLoadTemplate {
address: new_template,
details: e.to_string(),
})?
.ok_or(TransactionErrorKind::TemplateNotFound { address: new_template })?;
runtime
.interface_mut()
.track_template_loaded(&new_template, template.code_size())?;
let component_lock = runtime
.interface_mut()
.lock_component(component_address, LockFlag::Write)?;
let migration_function = migrate
.as_ref()
.map(|f| {
template
.template_def()
.get_function(&f.name)
.cloned()
.ok_or_else(|| TransactionErrorKind::FunctionNotFound {
name: f.name.to_string(),
})
.and_then(|f| {
if f.is_migration {
Ok(f)
} else {
Err(TransactionErrorKind::NotAMigrationFunction { name: f.name })
}
})
})
.transpose()?;
let component_scope = IndexedWellKnownTypes::from_value(component.state())?;
let (call_frame, final_args) = if migration_function.is_some() {
let resolved_args = migrate
.as_ref()
.map(|f| {
let component_arg = InstructionArg::from_type(&component_address).map_err(|e| {
RuntimeError::InvariantError {
function: "update_component_template",
details: format!("Failed to create component arg: {}", e),
}
})?;
runtime.interface().resolve_args(Some(component_arg), &f.args)
})
.transpose()?
.unwrap_or_default();
let arg_scope = resolved_args
.iter()
.map(IndexedWellKnownTypes::from_value)
.collect::<Result<_, _>>()?;
let frame = PushCallFrame::MigrationContext {
template_address: new_template,
module_name: template.template_name().to_string(),
component_scope,
component_lock,
arg_scope: Box::new(arg_scope),
entity_id: *component.entity_id(),
};
(frame, resolved_args)
} else {
let frame = PushCallFrame::MigrationContext {
template_address: new_template,
module_name: template.template_name().to_string(),
component_scope,
component_lock,
arg_scope: Box::new(IndexedWellKnownTypes::new()),
entity_id: *component.entity_id(),
};
(frame, vec![])
};
runtime.interface_mut().push_call_frame(call_frame)?;
runtime
.interface_mut()
.check_component_ownership(NativeAction::UpdateComponentTemplate.into())?;
runtime.interface_mut().update_component_template(new_template)?;
if let Some(function_def) = migration_function {
let result = Self::invoke_template(template, runtime.clone(), &function_def, &final_args)?;
runtime.interface_mut().validate_return_value(&result.indexed)?;
}
runtime.interface_mut().pop_call_frame()?;
Ok(InstructionResult::empty())
}
fn pay_fee_from_bucket(
runtime: &mut Runtime,
bucket: WorkspaceOffsetId,
) -> Result<InstructionResult, TransactionErrorKind> {
runtime.interface_mut().pay_fee(PayFee::FromBucket { bucket })?;
Ok(InstructionResult::empty())
}
fn stealth_transfer(
runtime: &mut Runtime,
resource_address: ResourceAddressRef,
statement: StealthTransferStatement,
revealed_funds_bucket: Option<WorkspaceOffsetId>,
) -> Result<InstructionResult, TransactionErrorKind> {
let revealed_funds_bucket = revealed_funds_bucket
.map(|id| {
runtime.interface().resolve_workspace_id(&id).and_then(|r| {
tari_bor::from_value(&r).map_err(|e| RuntimeError::InvalidArgument {
argument: "revealed_funds_bucket",
reason: format!("Expected workspace id {id} to be a BucketId: {e}"),
})
})
})
.transpose()?;
let maybe_bucket =
runtime
.interface_mut()
.stealth_transfer(resource_address, statement, revealed_funds_bucket)?;
runtime
.interface_mut()
.set_last_instruction_output(IndexedValue::from_type(&maybe_bucket.map(Bucket::from_id))?)?;
Ok(InstructionResult::empty())
}
fn put_output_on_workspace_with_id(runtime: &mut Runtime, key: WorkspaceId) -> Result<(), TransactionErrorKind> {
runtime
.interface_mut()
.workspace_invoke(WorkspaceAction::PutLastInstructionOutput, invoke_args![key].into())?;
Ok(())
}
fn drop_all_proofs_in_workspace(runtime: &mut Runtime) -> Result<(), TransactionErrorKind> {
runtime
.interface_mut()
.workspace_invoke(WorkspaceAction::DropAllProofs, invoke_args![].into())?;
Ok(())
}
fn allocate_address(
runtime: &mut Runtime,
substate_type: AllocatableAddressType,
workspace_id: WorkspaceId,
) -> Result<InstructionResult, TransactionErrorKind> {
let entity_id = runtime.interface().next_entity_id()?;
let result = runtime
.interface_mut()
.allocate_address(substate_type, entity_id, workspace_id)?;
match result {
AllocateAddressResult::ComponentAddress(alloc) => Ok(InstructionResult {
indexed: IndexedValue::from_type(&alloc)?,
return_type: Type::Other {
name: "ComponentAddressAllocation".to_string(),
},
}),
AllocateAddressResult::ResourceAddress(alloc) => Ok(InstructionResult {
indexed: IndexedValue::from_type(&alloc)?,
return_type: Type::Other {
name: "ResourceAddressAllocation".to_string(),
},
}),
}
}
fn publish_template(
runtime: &mut Runtime,
binary: &[u8],
metadata_hash: Option<MetadataHash>,
) -> Result<InstructionResult, TransactionErrorKind> {
if binary.len() > limits::ENGINE_LIMITS.max_template_binary_size_bytes {
return Err(TransactionErrorKind::WasmBinaryTooBig {
size: binary.len(),
max: limits::ENGINE_LIMITS.max_template_binary_size_bytes,
});
}
let template_def = WasmModule::validate_code(binary)?;
let blob = TemplateBlob::new_checked(binary).expect("template binary size verified above");
runtime
.interface_mut()
.publish_template(blob, metadata_hash, template_def)?;
Ok(InstructionResult::empty())
}
fn create_account(
template_provider: &TTemplateProvider,
runtime: &mut Runtime,
public_key_address: &RistrettoPublicKeyBytes,
owner_rule: Option<OwnerRule>,
access_rules: Option<ComponentAccessRules>,
workspace_id: Option<WorkspaceOffsetId>,
) -> Result<InstructionResult, TransactionErrorKind> {
let template = template_provider
.get_template(&ACCOUNT_TEMPLATE_ADDRESS)
.map_err(|e| TransactionErrorKind::FailedToLoadTemplate {
address: ACCOUNT_TEMPLATE_ADDRESS,
details: e.to_string(),
})?
.ok_or(TransactionErrorKind::TemplateNotFound {
address: ACCOUNT_TEMPLATE_ADDRESS,
})?;
let account_address = derive_component_address_from_public_key(&ACCOUNT_TEMPLATE_ADDRESS, public_key_address);
let maybe_existing_account = runtime
.interface_mut()
.load_component(ComponentReference::Address(account_address))
.optional()?;
match maybe_existing_account {
Some((_, component)) => {
if *component.template_address() != ACCOUNT_TEMPLATE_ADDRESS {
return Err(TransactionErrorKind::InvalidCreateAccount {
component_address: account_address,
details: format!(
"A component already exists at the derived account address {}, but it is not an Account \
(template address: {})",
account_address,
component.template_address()
),
});
}
if owner_rule.is_some() || access_rules.is_some() {
return Err(TransactionErrorKind::InvalidCreateAccount {
component_address: account_address,
details: "Cannot specify owner_rule or access_rules when an account already exists at the \
derived address"
.to_string(),
});
}
if let Some(workspace_id) = workspace_id {
let args = call_args![WorkspaceOffset(workspace_id)];
Self::invoke_component(
template,
runtime,
account_address,
&component,
ACCOUNT_DEPOSIT_METHOD,
args,
)?;
}
runtime
.interface_mut()
.set_last_instruction_output(IndexedValue::from_type(&account_address)?)?;
Ok(InstructionResult::empty())
},
None => {
let function_def = template
.template_def()
.get_function(ACCOUNT_CONSTRUCTOR_FUNCTION)
.cloned()
.ok_or_else(|| TransactionErrorKind::FunctionNotFound {
name: ACCOUNT_CONSTRUCTOR_FUNCTION.to_string(),
})?;
let mut args = call_args![
NonFungibleAddress::from_public_key(*public_key_address),
owner_rule,
access_rules
];
if let Some(workspace_id) = workspace_id {
args.push(call_arg![WorkspaceOffset(workspace_id)]);
} else {
let none: Option<()> = None;
args.push(call_arg![Literal(none)]);
}
let resolved_args = runtime.interface().resolve_args(None, &args)?;
let arg_scope = resolved_args
.iter()
.map(IndexedWellKnownTypes::from_value)
.collect::<Result<_, _>>()?;
runtime.interface_mut().push_call_frame(PushCallFrame::Static {
template_address: ACCOUNT_TEMPLATE_ADDRESS,
module_name: template.template_name().to_string(),
arg_scope,
entity_id: account_address.entity_id(),
})?;
let result = Self::invoke_template(template, runtime.clone(), &function_def, &resolved_args)?;
runtime.interface_mut().validate_return_value(&result.indexed)?;
runtime.interface_mut().pop_call_frame()?;
runtime
.interface_mut()
.set_last_instruction_output(IndexedValue::from_type(&account_address)?)?;
Ok(InstructionResult::empty())
},
}
}
pub(crate) fn call_function(
template_provider: &TTemplateProvider,
runtime: &mut Runtime,
template_address: &TemplateAddress,
function: &str,
args: Vec<InstructionArg>,
) -> Result<InstructionResult, TransactionErrorKind> {
let template = template_provider
.get_template(template_address)
.map_err(|e| TransactionErrorKind::FailedToLoadTemplate {
address: *template_address,
details: e.to_string(),
})?
.ok_or_else(|| TransactionErrorKind::TemplateNotFound {
address: *template_address,
})?;
runtime
.interface_mut()
.track_template_loaded(template_address, template.code_size())?;
let function_def = template.template_def().get_function(function).cloned().ok_or_else(|| {
TransactionErrorKind::FunctionNotFound {
name: function.to_string(),
}
})?;
if function_def.is_migration {
return Err(TransactionErrorKind::CannotCallMigrationFunctionDirectly {
name: function_def.name,
});
}
let resolved_args = runtime.interface().resolve_args(None, &args)?;
let arg_scope = resolved_args
.iter()
.map(IndexedWellKnownTypes::from_value)
.collect::<Result<_, _>>()?;
let frame = PushCallFrame::Static {
template_address: *template_address,
module_name: template.template_name().to_string(),
arg_scope,
entity_id: runtime.interface().next_entity_id()?,
};
runtime.interface_mut().push_call_frame(frame)?;
let result = Self::invoke_template(template, runtime.clone(), &function_def, &resolved_args)?;
runtime.interface_mut().validate_return_value(&result.indexed)?;
runtime.interface_mut().pop_call_frame()?;
Ok(result)
}
pub(crate) fn call_method(
template_provider: &TTemplateProvider,
runtime: &mut Runtime,
call: ComponentReference,
method: &str,
args: Vec<InstructionArg>,
) -> Result<InstructionResult, TransactionErrorKind> {
let (component_address, component) = runtime.interface_mut().load_component(call)?;
let template_address = *component.template_address();
let template = template_provider
.get_template(&template_address)
.map_err(|e| TransactionErrorKind::FailedToLoadTemplate {
address: template_address,
details: e.to_string(),
})?
.ok_or(TransactionErrorKind::TemplateNotFound {
address: template_address,
})?;
runtime
.interface_mut()
.track_template_loaded(&template_address, template.code_size())?;
Self::invoke_component(template, runtime, component_address, &component, method, args)
}
fn invoke_component(
template: LoadedTemplate,
runtime: &mut Runtime,
component_address: ComponentAddress,
component: &Component,
method: &str,
args: Vec<InstructionArg>,
) -> Result<InstructionResult, TransactionErrorKind> {
let function_def = template.template_def().get_function(method).cloned().ok_or_else(|| {
TransactionErrorKind::FunctionNotFound {
name: method.to_string(),
}
})?;
if function_def.is_migration {
return Err(TransactionErrorKind::CannotCallMigrationFunctionDirectly {
name: function_def.name,
});
}
let lock_flag = if function_def.is_mut {
LockFlag::Write
} else {
LockFlag::Read
};
let component_lock = runtime.interface_mut().lock_component(component_address, lock_flag)?;
let resolved_args = runtime
.interface()
.resolve_args(Some(InstructionArg::from_type(&component_address)?), &args)?;
let arg_scope = resolved_args
.iter()
.skip(1)
.map(IndexedWellKnownTypes::from_value)
.collect::<Result<_, _>>()?;
let component_scope = IndexedWellKnownTypes::from_value(component.state())?;
runtime.interface_mut().push_call_frame(PushCallFrame::ForComponent {
template_address: component.header.template_address,
module_name: template.template_name().to_string(),
component_scope,
component_lock,
arg_scope: Box::new(arg_scope),
entity_id: component.header.entity_id,
})?;
runtime.interface_mut().check_component_access_rules(method)?;
let result = Self::invoke_template(template, runtime.clone(), &function_def, &resolved_args)?;
runtime.interface_mut().validate_return_value(&result.indexed)?;
runtime.interface_mut().pop_call_frame()?;
Ok(result)
}
fn invoke_template(
module: LoadedTemplate,
runtime: Runtime,
function_def: &FunctionDef,
args: &[tari_bor::Value],
) -> Result<InstructionResult, TransactionErrorKind> {
let result = match module {
LoadedTemplate::Wasm(loaded) => {
let mut store = loaded.create_store();
let mut process = WasmProcess::init(&mut store, loaded, runtime)?;
process.invoke(&mut store, function_def, args)?
},
};
Ok(result)
}
}