use std::fmt::Display;
use indexmap::IndexSet;
use tari_engine_types::{indexed_value::IndexedWellKnownTypes, lock::LockId, substate::SubstateId};
use tari_template_lib::{
models::{BucketId, ProofId},
types::{
EntityId,
TemplateAddress,
constants::{PUBLIC_IDENTITY_RESOURCE_ADDRESS, TARI_TOKEN},
},
};
use crate::runtime::{
AuthorizationScope,
RuntimeError,
locking::{LockError, LockedSubstate},
};
#[derive(Debug, Clone)]
pub struct CallScope {
orphans: IndexSet<SubstateId>,
owned: IndexSet<SubstateId>,
referenced: IndexSet<SubstateId>,
component_lock: Option<LockedSubstate>,
lock_scope: IndexSet<LockId>,
bucket_scope: IndexSet<BucketId>,
auth_scope: AuthorizationScope,
}
impl CallScope {
pub fn new() -> Self {
Self {
orphans: IndexSet::new(),
owned: IndexSet::new(),
referenced: IndexSet::new(),
component_lock: None,
lock_scope: IndexSet::new(),
bucket_scope: IndexSet::new(),
auth_scope: AuthorizationScope::empty(),
}
}
pub fn for_component(component_lock: LockedSubstate) -> Self {
let mut this = Self::new();
this.component_lock = Some(component_lock);
this
}
pub(crate) fn set_auth_scope(&mut self, scope: AuthorizationScope) {
self.auth_scope = scope;
}
pub fn is_lock_in_scope(&self, lock_id: LockId) -> bool {
self.lock_scope.contains(&lock_id)
}
pub fn lock_scope(&self) -> &IndexSet<LockId> {
&self.lock_scope
}
pub fn is_proof_in_scope(&self, proof_id: &ProofId) -> bool {
self.auth_scope.contains_proof(proof_id)
}
pub fn is_bucket_in_scope(&self, bucket_id: BucketId) -> bool {
self.bucket_scope.contains(&bucket_id)
}
pub fn is_substate_in_scope(&self, address: &SubstateId) -> bool {
if *address == TARI_TOKEN {
return true;
}
if address
.as_non_fungible_address()
.filter(|addr| *addr.resource_address() == PUBLIC_IDENTITY_RESOURCE_ADDRESS)
.is_some()
{
return true;
}
self.owned.contains(address) || self.referenced.contains(address) || self.orphans.contains(address)
}
pub fn add_lock_to_scope(&mut self, lock_id: LockId) {
self.lock_scope.insert(lock_id);
}
pub fn add_bucket_to_scope(&mut self, bucket_id: BucketId) {
self.bucket_scope.insert(bucket_id);
}
pub fn remove_bucket_from_scope(&mut self, bucket_id: BucketId) -> bool {
self.bucket_scope.swap_remove(&bucket_id)
}
pub fn add_proof_to_scope(&mut self, proof_id: ProofId) {
self.auth_scope_mut().add_proof(proof_id);
}
pub fn remove_lock_from_scope(&mut self, lock_id: LockId) -> Result<(), RuntimeError> {
if !self.lock_scope.swap_remove(&lock_id) {
return Err(RuntimeError::LockError(LockError::LockIdNotFound { lock_id }));
}
Ok(())
}
pub fn get_current_component_lock(&self) -> Option<&LockedSubstate> {
self.component_lock.as_ref()
}
pub fn take_current_component_lock(&mut self) -> Option<LockedSubstate> {
self.component_lock.take()
}
pub fn owned_nodes(&self) -> &IndexSet<SubstateId> {
&self.owned
}
pub fn orphans(&self) -> &IndexSet<SubstateId> {
&self.orphans
}
pub fn move_node_to_owned(&mut self, address: &SubstateId) -> Result<(), RuntimeError> {
if self.orphans.swap_remove(address) && !self.owned.insert(address.clone()) {
return Err(RuntimeError::DuplicateSubstate {
address: address.clone(),
});
}
Ok(())
}
pub fn auth_scope(&self) -> &AuthorizationScope {
&self.auth_scope
}
pub fn auth_scope_mut(&mut self) -> &mut AuthorizationScope {
&mut self.auth_scope
}
pub fn add_substate_to_scope(&mut self, address: SubstateId) -> Result<(), RuntimeError> {
if self.is_substate_in_scope(&address) {
return Err(RuntimeError::DuplicateSubstate { address });
}
self.add_substate_to_scope_unchecked(address);
Ok(())
}
fn add_substate_to_scope_unchecked(&mut self, address: SubstateId) {
if address.is_root() {
self.owned.insert(address);
} else {
self.orphans.insert(address);
}
}
pub fn add_substate_to_owned(&mut self, address: SubstateId) {
self.referenced.swap_remove(&address);
self.orphans.swap_remove(&address);
self.owned.insert(address);
}
pub fn add_substate_to_referenced(&mut self, address: SubstateId) {
if self.is_substate_in_scope(&address) {
return;
}
self.referenced.insert(address);
}
pub fn update_from_parent(&mut self, _parent: &CallScope) {
}
pub fn update_from_child_scope(&mut self, child: CallScope) {
self.owned.extend(child.owned.iter().cloned());
for owned in &child.owned {
self.orphans.swap_remove(owned);
}
self.bucket_scope.extend(child.bucket_scope);
self.auth_scope.update_from_child(child.auth_scope);
}
pub fn include_owned_in_scope(&mut self, values: &IndexedWellKnownTypes) {
for addr in values.referenced_substates() {
if addr.is_public_key_identity() || addr.is_transaction_receipt() || addr.is_template() {
continue;
}
self.add_substate_to_owned(addr);
}
}
pub fn include_refs_in_scope(&mut self, values: &IndexedWellKnownTypes) {
for addr in values.referenced_substates() {
if addr.is_public_key_identity() || addr.is_vault() || addr.is_read_only() {
continue;
}
self.add_substate_to_referenced(addr);
}
for bucket_id in values.bucket_ids() {
self.add_bucket_to_scope(*bucket_id);
}
for proof_id in values.proof_ids() {
self.add_proof_to_scope(*proof_id);
}
}
}
impl Default for CallScope {
fn default() -> Self {
Self::new()
}
}
impl Display for CallScope {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if !self.owned.is_empty() {
writeln!(f, "Owned:")?;
for owned in &self.owned {
writeln!(f, " {}", owned)?;
}
}
if !self.referenced.is_empty() {
writeln!(f, "Referenced:")?;
for referenced in &self.referenced {
writeln!(f, " {}", referenced)?;
}
}
if !self.orphans.is_empty() {
writeln!(f, "Orphans:")?;
for orphan in &self.orphans {
writeln!(f, " {}", orphan)?;
}
}
if !self.lock_scope.is_empty() {
writeln!(f, "Locks:")?;
for lock in &self.lock_scope {
writeln!(f, " {}", lock)?;
}
}
if !self.auth_scope.proofs().is_empty() {
writeln!(f, "Proofs:")?;
for proof in self.auth_scope.proofs() {
writeln!(f, " {}", proof)?;
}
}
if !self.bucket_scope.is_empty() {
writeln!(f, "Buckets:")?;
for bucket in &self.bucket_scope {
writeln!(f, " {}", bucket)?;
}
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct CallFrame {
scope: CallScope,
current_template: TemplateAddress,
current_module: String,
entity_id: EntityId,
allow_cross_template_calls: bool,
allow_migration_calls: bool,
}
impl CallFrame {
pub fn for_static(current_template: TemplateAddress, current_module: String, entity_id: EntityId) -> Self {
Self {
scope: CallScope::new(),
current_template,
current_module,
entity_id,
allow_cross_template_calls: true,
allow_migration_calls: false,
}
}
pub fn for_component(
current_template: TemplateAddress,
current_module: String,
component_lock: LockedSubstate,
entity_id: EntityId,
) -> Self {
Self {
scope: CallScope::for_component(component_lock),
current_template,
current_module,
entity_id,
allow_cross_template_calls: true,
allow_migration_calls: false,
}
}
pub fn migration_context(
current_template: TemplateAddress,
current_module: String,
component_lock: LockedSubstate,
entity_id: EntityId,
) -> Self {
Self {
scope: CallScope::for_component(component_lock),
current_template,
current_module,
entity_id,
allow_cross_template_calls: false,
allow_migration_calls: true,
}
}
pub fn scope(&self) -> &CallScope {
&self.scope
}
pub fn scope_mut(&mut self) -> &mut CallScope {
&mut self.scope
}
pub fn into_scope(self) -> CallScope {
self.scope
}
pub fn entity_id(&self) -> EntityId {
self.entity_id
}
pub fn set_entity_id(&mut self, entity_id: EntityId) {
self.entity_id = entity_id;
}
pub fn current_template(&self) -> (&TemplateAddress, &str) {
(&self.current_template, &self.current_module)
}
pub fn is_cross_template_calls_allowed(&self) -> bool {
self.allow_cross_template_calls
}
pub fn are_migration_calls_allowed(&self) -> bool {
self.allow_migration_calls
}
}
#[derive(Debug, Clone)]
pub enum PushCallFrame {
ForComponent {
template_address: TemplateAddress,
module_name: String,
component_scope: IndexedWellKnownTypes,
component_lock: LockedSubstate,
arg_scope: Box<IndexedWellKnownTypes>,
entity_id: EntityId,
},
MigrationContext {
template_address: TemplateAddress,
module_name: String,
component_scope: IndexedWellKnownTypes,
component_lock: LockedSubstate,
arg_scope: Box<IndexedWellKnownTypes>,
entity_id: EntityId,
},
Static {
template_address: TemplateAddress,
module_name: String,
arg_scope: IndexedWellKnownTypes,
entity_id: EntityId,
},
}
impl PushCallFrame {
pub fn component_lock(&self) -> Option<&LockedSubstate> {
match self {
Self::ForComponent { component_lock, .. } => Some(component_lock),
Self::MigrationContext { component_lock, .. } => Some(component_lock),
Self::Static { .. } => None,
}
}
pub fn arg_scope(&self) -> &IndexedWellKnownTypes {
match self {
Self::ForComponent { arg_scope, .. } => arg_scope,
Self::MigrationContext { arg_scope, .. } => arg_scope,
Self::Static { arg_scope, .. } => arg_scope,
}
}
pub(super) fn into_new_call_frame(self) -> CallFrame {
match self {
Self::ForComponent {
template_address,
module_name,
component_scope,
component_lock,
arg_scope,
entity_id,
} => {
let mut frame = CallFrame::for_component(template_address, module_name, component_lock, entity_id);
frame.scope_mut().include_owned_in_scope(&component_scope);
frame.scope_mut().include_refs_in_scope(&arg_scope);
frame
},
Self::MigrationContext {
template_address,
module_name,
component_scope,
component_lock,
arg_scope,
entity_id,
} => {
let mut frame = CallFrame::migration_context(template_address, module_name, component_lock, entity_id);
frame.scope_mut().include_owned_in_scope(&component_scope);
frame.scope_mut().include_refs_in_scope(&arg_scope);
frame
},
Self::Static {
template_address,
module_name,
arg_scope,
entity_id,
} => {
let mut frame = CallFrame::for_static(template_address, module_name, entity_id);
frame.scope_mut().include_refs_in_scope(&arg_scope);
frame
},
}
}
pub fn entity_id(&self) -> Option<EntityId> {
match self {
Self::ForComponent { entity_id, .. } => Some(*entity_id),
Self::MigrationContext { entity_id, .. } => Some(*entity_id),
Self::Static { .. } => None,
}
}
}