diff --git a/dan_layer/engine/src/runtime/error.rs b/dan_layer/engine/src/runtime/error.rs index 8a095fe62..e268bed22 100644 --- a/dan_layer/engine/src/runtime/error.rs +++ b/dan_layer/engine/src/runtime/error.rs @@ -220,6 +220,11 @@ pub enum RuntimeError { DuplicateBucket { bucket_id: BucketId }, #[error("Duplicate proof {proof_id}")] DuplicateProof { proof_id: ProofId }, + + #[error("Address allocation not found with id {id}")] + AddressAllocationNotFound { id: u32 }, + #[error("Address allocation type mismatch: {address}")] + AddressAllocationTypeMismatch { address: SubstateAddress }, } impl RuntimeError { @@ -251,6 +256,8 @@ pub enum TransactionCommitError { DanglingProofs { count: usize }, #[error("Locked value (amount: {locked_amount}) remaining in vault {vault_id}")] DanglingLockedValueInVault { vault_id: VaultId, locked_amount: Amount }, + #[error("{count} dangling address allocations remain after transaction execution")] + DanglingAddressAllocations { count: usize }, #[error("{} orphaned substate(s) detected: {}", .substates.len(), .substates.join(", "))] OrphanedSubstates { substates: Vec }, #[error("{count} dangling items in workspace after transaction execution")] diff --git a/dan_layer/engine/src/runtime/impl.rs b/dan_layer/engine/src/runtime/impl.rs index 7bb2e37b1..aad20a005 100644 --- a/dan_layer/engine/src/runtime/impl.rs +++ b/dan_layer/engine/src/runtime/impl.rs @@ -282,6 +282,12 @@ impl> RuntimeInte .map(|l| l.address().as_component_address().unwrap()); Ok(InvokeResult::encode(&maybe_address)?) }), + CallerContextAction::AllocateNewComponentAddress => self.tracker.write_with(|state| { + let (template, _) = state.current_template()?; + let address = self.tracker.id_provider().new_component_address(*template, None)?; + let allocation = state.new_address_allocation(address)?; + Ok(InvokeResult::encode(&allocation)?) + }), } } @@ -322,6 +328,7 @@ impl> RuntimeInte arg.owner_rule, arg.access_rules, arg.component_id, + arg.address_allocation, )?; Ok(InvokeResult::encode(&component_address)?) }, diff --git a/dan_layer/engine/src/runtime/tracker.rs b/dan_layer/engine/src/runtime/tracker.rs index c3cd1e105..bcf7f5986 100644 --- a/dan_layer/engine/src/runtime/tracker.rs +++ b/dan_layer/engine/src/runtime/tracker.rs @@ -43,7 +43,7 @@ use tari_engine_types::{ use tari_template_lib::{ auth::{ComponentAccessRules, OwnerRule}, crypto::RistrettoPublicKeyBytes, - models::{Amount, BucketId, ComponentAddress, Metadata, UnclaimedConfidentialOutputAddress}, + models::{AddressAllocation, Amount, BucketId, ComponentAddress, Metadata, UnclaimedConfidentialOutputAddress}, Hash, }; use tari_transaction::id_provider::IdProvider; @@ -161,14 +161,18 @@ impl StateTracker { owner_rule: OwnerRule, access_rules: ComponentAccessRules, component_id: Option, + address_allocation: Option>, ) -> Result { self.write_with(|state| { let (template_address, module_name) = state.current_template().map(|(addr, name)| (*addr, name.to_string()))?; - let component_address = self - .id_provider() - .new_component_address(template_address, component_id)?; + let component_address = match address_allocation { + Some(address_allocation) => state.take_allocated_address(address_allocation.id())?, + None => self + .id_provider() + .new_component_address(template_address, component_id)?, + }; let component = ComponentBody { state: component_state }; let component = ComponentHeader { diff --git a/dan_layer/engine/src/runtime/tracker_auth.rs b/dan_layer/engine/src/runtime/tracker_auth.rs index e86ece26b..6f8564047 100644 --- a/dan_layer/engine/src/runtime/tracker_auth.rs +++ b/dan_layer/engine/src/runtime/tracker_auth.rs @@ -7,8 +7,8 @@ use tari_template_lib::auth::{ Ownership, RequireRule, ResourceAuthAction, - ResourceOrNonFungibleAddress, RestrictedAccessRule, + RuleRequirement, }; use crate::runtime::{ @@ -154,19 +154,19 @@ fn check_require_rule( rule: &RequireRule, ) -> Result { match rule { - RequireRule::Require(resx_or_addr) => check_resource_or_non_fungible(state, scope, resx_or_addr), - RequireRule::AnyOf(resx_or_addrs) => { - for resx_or_addr in resx_or_addrs { - if check_resource_or_non_fungible(state, scope, resx_or_addr)? { + RequireRule::Require(requirement) => check_requirement(state, scope, requirement), + RequireRule::AnyOf(requirements) => { + for requirement in requirements { + if check_requirement(state, scope, requirement)? { return Ok(true); } } Ok(false) }, - RequireRule::AllOf(resx_or_addr) => { - for resx_or_addr in resx_or_addr { - if !check_resource_or_non_fungible(state, scope, resx_or_addr)? { + RequireRule::AllOf(requirement) => { + for requirement in requirement { + if !check_requirement(state, scope, requirement)? { return Ok(false); } } @@ -176,13 +176,13 @@ fn check_require_rule( } } -fn check_resource_or_non_fungible( +fn check_requirement( state: &WorkingState, scope: &AuthorizationScope, - resx_or_addr: &ResourceOrNonFungibleAddress, + requirement: &RuleRequirement, ) -> Result { - match resx_or_addr { - ResourceOrNonFungibleAddress::Resource(resx) => { + match requirement { + RuleRequirement::Resource(resx) => { if scope .virtual_proofs() .iter() @@ -200,7 +200,7 @@ fn check_resource_or_non_fungible( } Ok(false) }, - ResourceOrNonFungibleAddress::NonFungibleAddress(addr) => { + RuleRequirement::NonFungibleAddress(addr) => { if scope.virtual_proofs().contains(addr) { return Ok(true); } @@ -217,5 +217,12 @@ fn check_resource_or_non_fungible( Ok(false) }, + RuleRequirement::ScopedToComponent(address) => { + Ok(state.current_component()?.map_or(false, |current| current == *address)) + }, + RuleRequirement::ScopedToTemplate(address) => { + let (current, _) = state.current_template()?; + Ok(current == address) + }, } } diff --git a/dan_layer/engine/src/runtime/working_state.rs b/dan_layer/engine/src/runtime/working_state.rs index 73d80c3d8..069bc4eaa 100644 --- a/dan_layer/engine/src/runtime/working_state.rs +++ b/dan_layer/engine/src/runtime/working_state.rs @@ -36,6 +36,7 @@ use tari_template_lib::{ args::{MintArg, ResourceDiscriminator}, constants::CONFIDENTIAL_TARI_RESOURCE_ADDRESS, models::{ + AddressAllocation, Amount, BucketId, ComponentAddress, @@ -73,6 +74,8 @@ pub(super) struct WorkingState { events: Vec, logs: Vec, buckets: HashMap, + address_allocations: HashMap, + address_allocation_id: u32, proofs: HashMap, store: WorkingStateStore, @@ -103,6 +106,8 @@ impl WorkingState { logs: Vec::new(), buckets: HashMap::new(), proofs: HashMap::new(), + address_allocation_id: 0, + address_allocations: HashMap::new(), store: WorkingStateStore::new(state_store), @@ -317,6 +322,13 @@ impl WorkingState { .into()); } + if !self.address_allocations.is_empty() { + return Err(TransactionCommitError::DanglingAddressAllocations { + count: self.address_allocations.len(), + } + .into()); + } + for (_, vault) in self.store.new_vaults() { if !vault.locked_balance().is_zero() { return Err(TransactionCommitError::DanglingLockedValueInVault { @@ -655,6 +667,28 @@ impl WorkingState { Ok(()) } + pub fn new_address_allocation + Clone>( + &mut self, + address: T, + ) -> Result, RuntimeError> { + let id = self.address_allocation_id; + self.address_allocation_id += 1; + self.address_allocations.insert(id, address.clone().into()); + let allocation = AddressAllocation::new(id, address); + Ok(allocation) + } + + pub fn take_allocated_address>( + &mut self, + id: u32, + ) -> Result { + let address = self + .address_allocations + .remove(&id) + .ok_or(RuntimeError::AddressAllocationNotFound { id })?; + T::try_from(address).map_err(|address| RuntimeError::AddressAllocationTypeMismatch { address }) + } + pub fn pay_fee(&mut self, resource: ResourceContainer, return_vault: VaultId) -> Result<(), RuntimeError> { self.fee_state.fee_payments.push((resource, return_vault)); Ok(()) @@ -845,6 +879,15 @@ impl WorkingState { .ok_or(RuntimeError::NoActiveCallScope) } + /// Returns the component that is currently in scope (if any) + pub fn current_component(&self) -> Result, RuntimeError> { + let frame = self.call_frames.last().ok_or(RuntimeError::NoActiveCallScope)?; + Ok(frame + .scope() + .get_current_component_lock() + .and_then(|lock| lock.address().as_component_address())) + } + pub fn push_frame(&mut self, mut new_frame: CallFrame, max_call_depth: usize) -> Result<(), RuntimeError> { if self.call_frame_depth() + 1 > max_call_depth { return Err(RuntimeError::MaxCallDepthExceeded { diff --git a/dan_layer/engine/tests/access_rules.rs b/dan_layer/engine/tests/access_rules.rs index 74d8ede1d..5dd1f2378 100644 --- a/dan_layer/engine/tests/access_rules.rs +++ b/dan_layer/engine/tests/access_rules.rs @@ -1,7 +1,6 @@ // Copyright 2023 The Tari Project // SPDX-License-Identifier: BSD-3-Clause - -use std::collections::BTreeMap; +use std::collections::{BTreeMap, HashMap}; use tari_dan_engine::runtime::{ActionIdent, RuntimeError}; use tari_template_lib::{ @@ -224,7 +223,6 @@ mod component_access_rules { } mod resource_access_rules { - use std::collections::HashMap; use super::*; @@ -886,4 +884,66 @@ mod resource_access_rules { vec![user_proof], ); } + + #[test] + fn it_restricts_resource_actions_to_component() { + let mut test = TemplateTest::new(["tests/templates/access_rules"]); + + // Create sender and receiver accounts + let (owner_account, owner_proof, owner_key) = test.create_empty_account(); + + let access_rules_template = test.get_template_address("AccessRulesTest"); + + let result = test.execute_expect_success( + Transaction::builder() + .call_function( + access_rules_template, + "resource_actions_restricted_to_component", + args![], + ) + .sign(&owner_key) + .build(), + vec![owner_proof.clone()], + ); + + let component_address = result.finalize.execution_results[0] + .decode::() + .unwrap(); + // Find the resource address for the tokens from the output substates + let token_resource = result + .finalize + .result + .accept() + .unwrap() + .up_iter() + .filter_map(|(addr, s)| s.substate_value().as_resource().map(|r| (addr, r))) + .filter(|(_, r)| r.resource_type().is_fungible()) + .map(|(addr, _)| addr.as_resource_address().unwrap()) + .next() + .unwrap(); + + // Minting using a template function will fail + let reason = test.execute_expect_failure( + Transaction::builder() + .call_function(access_rules_template, "mint_resource", args![token_resource]) + .put_last_instruction_output_on_workspace("tokens") + .call_method(owner_account, "deposit", args![Workspace("tokens")]) + .sign(&owner_key) + .build(), + vec![owner_proof.clone()], + ); + + assert_access_denied_for_action(reason, ResourceAuthAction::Mint); + + // Minting in a component context will succeed + test.execute_expect_success( + Transaction::builder() + .call_method(component_address, "mint_more_tokens", args![Amount(1000)]) + .put_last_instruction_output_on_workspace("tokens") + .call_method(owner_account, "deposit", args![Workspace("tokens")]) + .sign(&owner_key) + .build(), + vec![owner_proof], + ); + } } diff --git a/dan_layer/engine/tests/address_allocation.rs b/dan_layer/engine/tests/address_allocation.rs new file mode 100644 index 000000000..8ac3d7246 --- /dev/null +++ b/dan_layer/engine/tests/address_allocation.rs @@ -0,0 +1,52 @@ +// Copyright 2023 The Tari Project +// SPDX-License-Identifier: BSD-3-Clause + +use tari_dan_engine::runtime::TransactionCommitError; +use tari_template_lib::{args, models::ComponentAddress}; +use tari_template_test_tooling::{support::assert_error::assert_reject_reason, TemplateTest}; +use tari_transaction::Transaction; + +#[test] +fn it_uses_allocation_address() { + let mut test = TemplateTest::new(["tests/templates/address_allocation"]); + + let result = test.execute_expect_success( + Transaction::builder() + .call_function(test.get_template_address("AddressAllocationTest"), "create", args![]) + .sign(test.get_test_secret_key()) + .build(), + vec![], + ); + + let actual = result + .finalize + .result + .accept() + .unwrap() + .up_iter() + .find_map(|(k, _)| k.as_component_address()) + .unwrap(); + + let allocated = result.finalize.execution_results[0] + .indexed + .get_value::("$.1") + .unwrap() + .unwrap(); + assert_eq!(actual, allocated); +} + +#[test] +fn it_fails_if_allocation_is_not_used() { + let mut test = TemplateTest::new(["tests/templates/address_allocation"]); + let template_addr = test.get_template_address("AddressAllocationTest"); + + let reason = test.execute_expect_failure( + Transaction::builder() + .call_function(template_addr, "drop_allocation", args![]) + .sign(test.get_test_secret_key()) + .build(), + vec![], + ); + + assert_reject_reason(reason, TransactionCommitError::DanglingAddressAllocations { count: 1 }); +} diff --git a/dan_layer/engine/tests/templates/access_rules/src/lib.rs b/dan_layer/engine/tests/templates/access_rules/src/lib.rs index 83528242b..9e8ed20c7 100644 --- a/dan_layer/engine/tests/templates/access_rules/src/lib.rs +++ b/dan_layer/engine/tests/templates/access_rules/src/lib.rs @@ -2,13 +2,13 @@ // SPDX-License-Identifier: BSD-3-Clause use tari_template_lib::prelude::*; +const BADGE_NAMES: [&str; 4] = ["mint", "burn", "withdraw", "deposit"]; pub fn create_badge_resource(recall_rule: AccessRule) -> Bucket { ResourceBuilder::non_fungible() - .with_non_fungible(NonFungibleId::from_string("mint"), &(), &()) - .with_non_fungible(NonFungibleId::from_string("burn"), &(), &()) - .with_non_fungible(NonFungibleId::from_string("withdraw"), &(), &()) - .with_non_fungible(NonFungibleId::from_string("deposit"), &(), &()) + .mint_many_with(BADGE_NAMES, |name| { + (NonFungibleId::from_string(name), (Vec::new(), Vec::new())) + }) .recallable(recall_rule) .build_bucket() } @@ -126,6 +126,29 @@ mod access_rules_template { .create() } + pub fn resource_actions_restricted_to_component() -> Component { + let badges = create_badge_resource(AccessRule::AllowAll); + + let allocation = CallerContext::allocate_component_address(); + let tokens = ResourceBuilder::fungible() + .initial_supply(1000) + .mintable(AccessRule::Restricted(RestrictedAccessRule::Require( + RequireRule::Require(allocation.address().clone().into()), + ))) + // Only access rules apply, this just makes the test simpler because we do not need to change the transaction signer + .with_owner_rule(OwnerRule::None) + .build_bucket(); + + Component::new(Self { + value: 0, + tokens: Vault::from_bucket(tokens), + badges: Vault::from_bucket(badges), + }) + .with_address_allocation(allocation) + .with_access_rules(ComponentAccessRules::new().default(AccessRule::AllowAll)) + .create() + } + pub fn take_badge_by_name(&mut self, name: String) -> Bucket { self.badges.withdraw_non_fungible(NonFungibleId::from_string(&name)) } @@ -178,5 +201,18 @@ mod access_rules_template { pub fn create_proof_from_bucket(bucket: Bucket) -> Proof { bucket.create_proof() } + + pub fn mint_resource(resource: ResourceAddress) -> Bucket { + let manager = ResourceManager::get(resource); + match manager.resource_type() { + ResourceType::Fungible => manager.mint_fungible(1000.into()), + ResourceType::NonFungible => manager.mint_non_fungible(NonFungibleId::random(), &(), &()), + ty => panic!("Unsupported resource type {:?}", ty), + } + } + + pub fn mint_more_tokens(&mut self, amount: Amount) -> Bucket { + ResourceManager::get(self.tokens.resource_address()).mint_fungible(amount) + } } } diff --git a/dan_layer/engine/tests/templates/address_allocation/Cargo.toml b/dan_layer/engine/tests/templates/address_allocation/Cargo.toml new file mode 100644 index 000000000..6def99a37 --- /dev/null +++ b/dan_layer/engine/tests/templates/address_allocation/Cargo.toml @@ -0,0 +1,13 @@ +[workspace] +[package] +name = "address_allocation" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +tari_template_lib = { path = "../../../../template_lib" } + +[lib] +crate-type = ["cdylib", "lib"] diff --git a/dan_layer/engine/tests/templates/address_allocation/src/lib.rs b/dan_layer/engine/tests/templates/address_allocation/src/lib.rs new file mode 100644 index 000000000..803c9d0a0 --- /dev/null +++ b/dan_layer/engine/tests/templates/address_allocation/src/lib.rs @@ -0,0 +1,26 @@ +// Copyright 2023 The Tari Project +// SPDX-License-Identifier: BSD-3-Clause + +use tari_template_lib::prelude::*; + +#[template] +mod template { + use super::*; + + pub struct AddressAllocationTest {} + + impl AddressAllocationTest { + pub fn create() -> (Component, ComponentAddress) { + let allocation = CallerContext::allocate_component_address(); + let address = allocation.address().clone(); + ( + Component::new(Self {}).with_address_allocation(allocation).create(), + address, + ) + } + + pub fn drop_allocation() { + let _allocation = CallerContext::allocate_component_address(); + } + } +} diff --git a/dan_layer/engine_types/src/lib.rs b/dan_layer/engine_types/src/lib.rs index 4e6bc2b14..4a701e528 100644 --- a/dan_layer/engine_types/src/lib.rs +++ b/dan_layer/engine_types/src/lib.rs @@ -30,4 +30,5 @@ mod template; pub use template::{calculate_template_binary_hash, parse_template_address, TemplateAddress}; mod argument_parser; + pub use argument_parser::parse_arg; diff --git a/dan_layer/engine_types/src/substate.rs b/dan_layer/engine_types/src/substate.rs index 3daa57d14..26f74e440 100644 --- a/dan_layer/engine_types/src/substate.rs +++ b/dan_layer/engine_types/src/substate.rs @@ -263,6 +263,17 @@ impl From for SubstateAddress { } } +impl TryFrom for ComponentAddress { + type Error = SubstateAddress; + + fn try_from(value: SubstateAddress) -> Result { + match value { + SubstateAddress::Component(addr) => Ok(addr), + _ => Err(value), + } + } +} + impl Display for SubstateAddress { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { diff --git a/dan_layer/template_lib/src/args/types.rs b/dan_layer/template_lib/src/args/types.rs index 685e426cc..e75f8fab8 100644 --- a/dan_layer/template_lib/src/args/types.rs +++ b/dan_layer/template_lib/src/args/types.rs @@ -33,6 +33,7 @@ use crate::{ auth::{OwnerRule, ResourceAccessRules}, crypto::PedersonCommitmentBytes, models::{ + AddressAllocation, Amount, BucketId, ComponentAddress, @@ -157,6 +158,7 @@ pub struct CreateComponentArg { pub owner_rule: OwnerRule, pub access_rules: ComponentAccessRules, pub component_id: Option, + pub address_allocation: Option>, } // -------------------------------- Events -------------------------------- // @@ -478,6 +480,7 @@ pub struct CallerContextInvokeArg { pub enum CallerContextAction { GetCallerPublicKey, GetComponentAddress, + AllocateNewComponentAddress, } // -------------------------------- CallInvoke -------------------------------- // diff --git a/dan_layer/template_lib/src/auth/access_rules.rs b/dan_layer/template_lib/src/auth/access_rules.rs index 0c942e7a6..80b98c463 100644 --- a/dan_layer/template_lib/src/auth/access_rules.rs +++ b/dan_layer/template_lib/src/auth/access_rules.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; use tari_template_abi::rust::collections::BTreeMap; -use crate::models::{NonFungibleAddress, ResourceAddress}; +use crate::models::{ComponentAddress, NonFungibleAddress, ResourceAddress, TemplateAddress}; /// Represents the types of possible access control rules over a component method or resource #[derive(Debug, Clone, Serialize, Deserialize)] @@ -54,31 +54,49 @@ impl RestrictedAccessRule { } } -/// An enum that allows passing either a resource or a non-fungible argument +/// Specifies a requirement for a [RequireRule]. #[derive(Debug, Clone, Serialize, Deserialize)] -pub enum ResourceOrNonFungibleAddress { +pub enum RuleRequirement { + /// Requires ownership of a specific resource Resource(ResourceAddress), + /// Requires ownership of a specific non-fungible token NonFungibleAddress(NonFungibleAddress), + /// Requires execution within a specific component + ScopedToComponent(ComponentAddress), + /// Requires execution within a specific template + ScopedToTemplate(TemplateAddress), } -impl From for ResourceOrNonFungibleAddress { +impl From for RuleRequirement { fn from(address: ResourceAddress) -> Self { Self::Resource(address) } } -impl From for ResourceOrNonFungibleAddress { +impl From for RuleRequirement { fn from(address: NonFungibleAddress) -> Self { Self::NonFungibleAddress(address) } } +impl From for RuleRequirement { + fn from(address: ComponentAddress) -> Self { + Self::ScopedToComponent(address) + } +} + +impl From for RuleRequirement { + fn from(address: TemplateAddress) -> Self { + Self::ScopedToTemplate(address) + } +} + /// An enum that represents the possible ways to require access to components or resources #[derive(Debug, Clone, Serialize, Deserialize)] pub enum RequireRule { - Require(ResourceOrNonFungibleAddress), - AnyOf(Vec), - AllOf(Vec), + Require(RuleRequirement), + AnyOf(Vec), + AllOf(Vec), } /// Information needed to specify access rules to methods of a component diff --git a/dan_layer/template_lib/src/caller_context.rs b/dan_layer/template_lib/src/caller_context.rs index ed7355ab4..2204234ae 100644 --- a/dan_layer/template_lib/src/caller_context.rs +++ b/dan_layer/template_lib/src/caller_context.rs @@ -8,7 +8,7 @@ use tari_template_abi::{call_engine, EngineOp}; use crate::{ args::{CallerContextAction, CallerContextInvokeArg, InvokeResult}, crypto::RistrettoPublicKeyBytes, - models::ComponentAddress, + models::{AddressAllocation, ComponentAddress}, }; /// Allows a template to access information about the current instruction's caller @@ -35,4 +35,13 @@ impl CallerContext { .expect("Failed to decode Option") .expect("Not in a component instance context") } + + pub fn allocate_component_address() -> AddressAllocation { + let resp: InvokeResult = call_engine(EngineOp::CallerContextInvoke, &CallerContextInvokeArg { + action: CallerContextAction::AllocateNewComponentAddress, + }); + + resp.decode() + .expect("Failed to decode AddressAllocation") + } } diff --git a/dan_layer/template_lib/src/component/instance.rs b/dan_layer/template_lib/src/component/instance.rs index f39e8f299..22171ac31 100644 --- a/dan_layer/template_lib/src/component/instance.rs +++ b/dan_layer/template_lib/src/component/instance.rs @@ -6,7 +6,7 @@ use std::marker::PhantomData; use crate::{ auth::{ComponentAccessRules, OwnerRule}, engine, - models::ComponentAddress, + models::{AddressAllocation, ComponentAddress}, Hash, }; @@ -16,6 +16,7 @@ pub struct ComponentBuilder { owner_rule: OwnerRule, access_rules: ComponentAccessRules, component_id: Option, + address_allocation: Option>, } impl ComponentBuilder { @@ -26,9 +27,16 @@ impl ComponentBuilder { owner_rule: OwnerRule::default(), access_rules: ComponentAccessRules::new(), component_id: None, + address_allocation: None, } } + /// Use an allocated address for the component. + pub fn with_address_allocation(mut self, allocation: AddressAllocation) -> Self { + self.address_allocation = Some(allocation); + self + } + /// Sets up who will be the owner of the component. /// Component owners are the only ones allowed to update the component's access rules after creation pub fn with_owner_rule(mut self, owner_rule: OwnerRule) -> Self { @@ -50,7 +58,13 @@ impl ComponentBuilder { /// Creates the new component and returns it pub fn create(self) -> Component { - let address = engine().create_component(self.component, self.owner_rule, self.access_rules, self.component_id); + let address = engine().create_component( + self.component, + self.owner_rule, + self.access_rules, + self.component_id, + self.address_allocation, + ); Component::from_address(address) } } diff --git a/dan_layer/template_lib/src/component/manager.rs b/dan_layer/template_lib/src/component/manager.rs index d1843d430..9454c346f 100644 --- a/dan_layer/template_lib/src/component/manager.rs +++ b/dan_layer/template_lib/src/component/manager.rs @@ -134,4 +134,8 @@ impl ComponentManager { .decode() .expect("failed to decode component template address from engine") } + + pub fn component_address(&self) -> ComponentAddress { + self.address + } } diff --git a/dan_layer/template_lib/src/engine.rs b/dan_layer/template_lib/src/engine.rs index 9f3c7fb07..d24b88800 100644 --- a/dan_layer/template_lib/src/engine.rs +++ b/dan_layer/template_lib/src/engine.rs @@ -30,7 +30,7 @@ use crate::{ component::ComponentManager, context::Context, get_context, - models::ComponentAddress, + models::{AddressAllocation, ComponentAddress}, prelude::ComponentAccessRules, Hash, }; @@ -57,6 +57,7 @@ impl TariEngine { owner_rule: OwnerRule, access_rules: ComponentAccessRules, component_id: Option, + address_allocation: Option>, ) -> ComponentAddress { let encoded_state = to_value(&initial_state).unwrap(); @@ -68,6 +69,7 @@ impl TariEngine { owner_rule, access_rules, component_id, + address_allocation }], }); diff --git a/dan_layer/template_lib/src/models/address_allocation.rs b/dan_layer/template_lib/src/models/address_allocation.rs new file mode 100644 index 000000000..7f1f65c29 --- /dev/null +++ b/dan_layer/template_lib/src/models/address_allocation.rs @@ -0,0 +1,22 @@ +// Copyright 2024 The Tari Project +// SPDX-License-Identifier: BSD-3-Clause + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct AddressAllocation { + id: u32, + address: T, +} + +impl AddressAllocation { + pub fn new(id: u32, address: T) -> Self { + Self { id, address } + } + + pub fn id(&self) -> u32 { + self.id + } + + pub fn address(&self) -> &T { + &self.address + } +} diff --git a/dan_layer/template_lib/src/models/mod.rs b/dan_layer/template_lib/src/models/mod.rs index 025acb98d..09d22254e 100644 --- a/dan_layer/template_lib/src/models/mod.rs +++ b/dan_layer/template_lib/src/models/mod.rs @@ -23,6 +23,9 @@ //! The collection of all struct definitions that represent data in the Tari network (e.g., resources, components, //! proofs, etc.) +mod address_allocation; +pub use address_allocation::AddressAllocation; + mod non_fungible_index; pub use non_fungible_index::NonFungibleIndexAddress; diff --git a/dan_layer/template_lib/src/prelude.rs b/dan_layer/template_lib/src/prelude.rs index 95bef383f..3b83dbc76 100644 --- a/dan_layer/template_lib/src/prelude.rs +++ b/dan_layer/template_lib/src/prelude.rs @@ -40,6 +40,7 @@ pub use crate::{ events::emit_event, invoke_args, models::{ + AddressAllocation, Amount, Bucket, BucketId, diff --git a/dan_layer/template_lib/src/resource/builder/non_fungible.rs b/dan_layer/template_lib/src/resource/builder/non_fungible.rs index 343c5de12..6adf51e56 100644 --- a/dan_layer/template_lib/src/resource/builder/non_fungible.rs +++ b/dan_layer/template_lib/src/resource/builder/non_fungible.rs @@ -5,7 +5,6 @@ use std::collections::BTreeMap; use serde::Serialize; use tari_bor::encode; -use tari_template_abi::rust::{fmt, ops::RangeInclusive}; use super::TOKEN_SYMBOL; use crate::{ @@ -130,13 +129,12 @@ impl NonFungibleResourceBuilder { /// Sets up multiple initial non-fungible tokens to be minted on resource creation by applying the provided function /// N times - pub fn mint_many_with(mut self, bounds: RangeInclusive, mut f: F) -> Self + pub fn mint_many_with(mut self, iter: I, f: F) -> Self where - F: FnMut(T) -> (NonFungibleId, (Vec, Vec)), - T: TryFrom, - T::Error: fmt::Debug, + F: FnMut(V) -> (NonFungibleId, (Vec, Vec)), + I: IntoIterator, { - self.tokens_ids.extend(bounds.map(|n| f(n.try_into().unwrap()))); + self.tokens_ids.extend(iter.into_iter().map(f)); self } diff --git a/dan_layer/template_lib/src/resource/manager.rs b/dan_layer/template_lib/src/resource/manager.rs index 525d6ef6f..c48532c99 100644 --- a/dan_layer/template_lib/src/resource/manager.rs +++ b/dan_layer/template_lib/src/resource/manager.rs @@ -96,7 +96,7 @@ impl ResourceManager { /// * `metadata` - Collection of information used to describe the resource /// * `mint_arg` - Specification of the initial tokens that will be minted on resource creation pub fn create( - &mut self, + &self, resource_type: ResourceType, owner_rule: OwnerRule, access_rules: ResourceAccessRules, @@ -130,7 +130,7 @@ impl ResourceManager { /// /// * `proof` - A zero-knowledge proof that specifies the amount of tokens to be minted and returned back to the /// caller - pub fn mint_confidential(&mut self, proof: ConfidentialOutputProof) -> Bucket { + pub fn mint_confidential(&self, proof: ConfidentialOutputProof) -> Bucket { self.mint_internal(MintResourceArg { mint_arg: MintArg::Confidential { proof: Box::new(proof) }, }) @@ -150,7 +150,7 @@ impl ResourceManager { /// * `mutable_data` - Initial data that the token will hold and that can potentially be updated in future /// instructions pub fn mint_non_fungible( - &mut self, + &self, id: NonFungibleId, metadata: &T, mutable_data: &U, @@ -178,7 +178,7 @@ impl ResourceManager { /// instructions /// * `supply` - The amount of new tokens to be minted pub fn mint_many_non_fungible( - &mut self, + &self, metadata: &T, mutable_data: &U, supply: usize, @@ -204,7 +204,7 @@ impl ResourceManager { /// # Arguments /// /// * `amount` - The amount of new tokens to be minted - pub fn mint_fungible(&mut self, amount: Amount) -> Bucket { + pub fn mint_fungible(&self, amount: Amount) -> Bucket { self.mint_internal(MintResourceArg { mint_arg: MintArg::Fungible { amount }, }) @@ -368,7 +368,7 @@ impl ResourceManager { Bucket::from_id(bucket_id) } - fn mint_internal(&mut self, arg: MintResourceArg) -> Bucket { + fn mint_internal(&self, arg: MintResourceArg) -> Bucket { let resp: InvokeResult = call_engine(EngineOp::ResourceInvoke, &ResourceInvokeArg { resource_ref: self.expect_resource_address(), action: ResourceAction::Mint, diff --git a/dan_layer/template_macros/src/template/dispatcher.rs b/dan_layer/template_macros/src/template/dispatcher.rs index 2f9f2876b..108f378f7 100644 --- a/dan_layer/template_macros/src/template/dispatcher.rs +++ b/dan_layer/template_macros/src/template/dispatcher.rs @@ -201,7 +201,13 @@ fn replace_self_in_single_value(type_path: &TypePath) -> Option { if type_ident == "Self" { // When we return self we use default rules - which only permit the owner of the component to call methods return Some(parse_quote! { - let rtn = engine().create_component(rtn, ::tari_template_lib::auth::OwnerRule::default(), ::tari_template_lib::auth::ComponentAccessRules::new(), None); + let rtn = engine().create_component( + rtn, + ::tari_template_lib::auth::OwnerRule::default(), + ::tari_template_lib::auth::ComponentAccessRules::new(), + None, + None, + ); }); } @@ -219,9 +225,16 @@ fn replace_self_in_tuple(type_tuple: &TypeTuple) -> Stmt { let ident = path.path.segments[0].ident.clone(); let field_expr = build_tuple_field_expr("rtn".to_string(), i as u32); if ident == "Self" { - // When we return self we use default rules - which only permit the owner of the component to call methods + // When we return self we use default rules - which only permit the owner of the component to call + // methods parse_quote! { - engine().create_component(#field_expr, ::tari_template_lib::auth::OwnerRule::default(), ::tari_template_lib::auth::ComponentAccessRules::new(), None) + engine().create_component( + #field_expr, + ::tari_template_lib::auth::OwnerRule::default(), + :tari_template_lib::auth::ComponentAccessRules::new(), + None, + None, + ) } } else { field_expr