From 33283f881777537403af8004ec1fc870fb17c6c5 Mon Sep 17 00:00:00 2001 From: Miguel Naveira <47919901+mrnaveira@users.noreply.github.com> Date: Tue, 16 Jan 2024 04:03:47 +0000 Subject: [PATCH] feat(template_lib): improve API for extracting data from NFTs (#897) Description --- * Added new `get_non_fungibles` method to vaults and buckets in `template_lib`, that returns all the `NonFungible` objects contained within. * Added new engine operations to support the new method in both vaults and buckets Motivation and Context --- We expect templates to often read NFT data from buckets or vaults. Currently the only way to do it is doing something like: ``` let seller_badge_id = &seller_badge_bucket.get_non_fungible_ids()[0]; let seller_badge = ResourceManager::get(self.seller_badge_resource).get_non_fungible(&seller_badge_id); let nft_metadata = seller_badge.get_data::(); ``` This PR adds a new method `get_non_fungibles` to vaults and buckets to we can write the previous example as: ``` let seller_badge = seller_badge_bucket.get_non_fungibles()[0]; let nft_metadata = seller_badge.get_mutable_data::(); ``` How Has This Been Tested? --- New unit test in the `engine` crate for the new methods What process can a PR reviewer use to test or verify this change? --- Call the new methods inside a template Breaking Changes --- - [x] None - [ ] Requires data directory to be deleted - [ ] Other - Please specify --- dan_layer/engine/src/runtime/impl.rs | 44 ++++++++++++++++++- .../tests/templates/nft/basic_nft/src/lib.rs | 13 ++++++ dan_layer/engine/tests/test.rs | 41 +++++++++++++++++ dan_layer/template_lib/src/args/types.rs | 2 + dan_layer/template_lib/src/models/bucket.rs | 13 +++++- dan_layer/template_lib/src/models/vault.rs | 13 +++++- 6 files changed, 123 insertions(+), 3 deletions(-) diff --git a/dan_layer/engine/src/runtime/impl.rs b/dan_layer/engine/src/runtime/impl.rs index 76d78ede0..fbe71a3f3 100644 --- a/dan_layer/engine/src/runtime/impl.rs +++ b/dan_layer/engine/src/runtime/impl.rs @@ -82,7 +82,7 @@ use tari_template_lib::{ auth::{ComponentAccessRules, ResourceAccessRules, ResourceAuthAction}, constants::CONFIDENTIAL_TARI_RESOURCE_ADDRESS, crypto::RistrettoPublicKeyBytes, - models::{Amount, BucketId, ComponentAddress, Metadata, NonFungibleAddress, NotAuthorized, VaultRef}, + models::{Amount, BucketId, ComponentAddress, Metadata, NonFungible, NonFungibleAddress, NotAuthorized, VaultRef}, prelude::ResourceType, template::BuiltinTemplate, }; @@ -1086,6 +1086,28 @@ impl> RuntimeInte }) }, VaultAction::CreateProofByConfidentialResource => todo!("CreateProofByConfidentialResource"), + VaultAction::GetNonFungibles => { + let vault_id = vault_ref.vault_id().ok_or_else(|| RuntimeError::InvalidArgument { + argument: "vault_ref", + reason: "GetNonFungibles vault action requires a vault id".to_string(), + })?; + args.assert_no_args("Vault::GetNonFungibles")?; + + self.tracker.write_with(|state| { + let vault_lock = state.lock_substate(&SubstateAddress::Vault(vault_id), LockFlag::Read)?; + let resource_address = state.get_vault(&vault_lock)?.resource_address(); + let nft_ids = state.get_vault(&vault_lock)?.get_non_fungible_ids(); + let nfts: Vec = nft_ids + .iter() + .map(|id| NonFungibleAddress::new(*resource_address, id.clone())) + .map(NonFungible::new) + .collect(); + + let result = InvokeResult::encode(&nfts)?; + state.unlock_substate(vault_lock)?; + Ok(result) + }) + }, } } @@ -1246,6 +1268,26 @@ impl> RuntimeInte Ok(InvokeResult::encode(bucket.non_fungible_ids())?) }) }, + BucketAction::GetNonFungibles => { + let bucket_id = bucket_ref.bucket_id().ok_or_else(|| RuntimeError::InvalidArgument { + argument: "bucket_ref", + reason: "GetNonFungibles bucket action requires a bucket id".to_string(), + })?; + args.assert_no_args("Bucket::GetNonFungibles")?; + + self.tracker.write_with(|state| { + let bucket = state.get_bucket(bucket_id)?; + let resource_address = bucket.resource_address(); + let nft_ids = bucket.non_fungible_ids(); + let nfts: Vec = nft_ids + .iter() + .map(|id| NonFungibleAddress::new(*resource_address, id.clone())) + .map(NonFungible::new) + .collect(); + + Ok(InvokeResult::encode(&nfts)?) + }) + }, } } diff --git a/dan_layer/engine/tests/templates/nft/basic_nft/src/lib.rs b/dan_layer/engine/tests/templates/nft/basic_nft/src/lib.rs index 9d1818f71..b11267fa2 100644 --- a/dan_layer/engine/tests/templates/nft/basic_nft/src/lib.rs +++ b/dan_layer/engine/tests/templates/nft/basic_nft/src/lib.rs @@ -129,5 +129,18 @@ mod sparkle_nft_template { // native instruction can be used instead bucket.burn(); } + + pub fn get_non_fungibles_from_bucket(&mut self) -> Vec { + let bucket = self.vault.withdraw_all(); + let nfts = bucket.get_non_fungibles(); + // deposit the nfts back into the vault + self.vault.deposit(bucket); + + nfts + } + + pub fn get_non_fungibles_from_vault(&self) -> Vec { + self.vault.get_non_fungibles() + } } } diff --git a/dan_layer/engine/tests/test.rs b/dan_layer/engine/tests/test.rs index 80d3fb002..822c8de24 100644 --- a/dan_layer/engine/tests/test.rs +++ b/dan_layer/engine/tests/test.rs @@ -478,6 +478,7 @@ mod fungible { mod basic_nft { use serde::{Deserialize, Serialize}; + use tari_template_lib::models::NonFungible; use super::*; @@ -840,6 +841,46 @@ mod basic_nft { ) .unwrap_err(); } + + #[test] + fn get_non_fungibles_from_containers() { + let (mut template_test, (account_address, account_owner), nft_component, nft_resx) = setup(); + + let vars = vec![ + ("account", account_address.into()), + ("nft", nft_component.into()), + ("nft_resx", nft_resx.into()), + ]; + + let total_supply: Amount = template_test.call_method(nft_component, "total_supply", args![], vec![]); + assert_eq!(total_supply, Amount(4)); + + let result = template_test + .execute_and_commit_manifest( + r#" + let sparkle_nft = var!["nft"]; + sparkle_nft.get_non_fungibles_from_bucket(); + sparkle_nft.get_non_fungibles_from_vault(); + "#, + vars.clone(), + vec![account_owner], + ) + .unwrap(); + + result.finalize.result.expect("execution failed"); + + // sparkle_nft.get_non_fungibles_from_bucket() + let nfts_from_bucket = result.finalize.execution_results[0] + .decode::>() + .unwrap(); + assert_eq!(nfts_from_bucket.len(), 4); + + // sparkle_nft.get_non_fungibles_from_vault() + let nfts_from_bucket = result.finalize.execution_results[1] + .decode::>() + .unwrap(); + assert_eq!(nfts_from_bucket.len(), 4); + } } mod emoji_id { diff --git a/dan_layer/template_lib/src/args/types.rs b/dan_layer/template_lib/src/args/types.rs index f450a7242..8311a4a8e 100644 --- a/dan_layer/template_lib/src/args/types.rs +++ b/dan_layer/template_lib/src/args/types.rs @@ -316,6 +316,7 @@ pub enum VaultAction { CreateProofByFungibleAmount, CreateProofByNonFungibles, CreateProofByConfidentialResource, + GetNonFungibles, } /// A vault withdraw operation argument @@ -397,6 +398,7 @@ pub enum BucketAction { Burn, CreateProof, GetNonFungibleIds, + GetNonFungibles, } /// A bucket burn operation argument diff --git a/dan_layer/template_lib/src/models/bucket.rs b/dan_layer/template_lib/src/models/bucket.rs index ce8cbcc8c..d2aff84b3 100644 --- a/dan_layer/template_lib/src/models/bucket.rs +++ b/dan_layer/template_lib/src/models/bucket.rs @@ -24,7 +24,7 @@ use serde::{Deserialize, Serialize}; use tari_bor::BorTag; use tari_template_abi::{call_engine, rust::fmt, EngineOp}; -use super::NonFungibleId; +use super::{NonFungible, NonFungibleId}; use crate::{ args::{BucketAction, BucketInvokeArg, BucketRef, InvokeResult}, models::{Amount, BinaryTag, ConfidentialWithdrawProof, Proof, ResourceAddress}, @@ -188,4 +188,15 @@ impl Bucket { resp.decode() .expect("get_non_fungible_ids returned invalid non fungible ids") } + + /// Returns all the non-fungibles in this bucket + pub fn get_non_fungibles(&self) -> Vec { + let resp: InvokeResult = call_engine(EngineOp::BucketInvoke, &BucketInvokeArg { + bucket_ref: BucketRef::Ref(self.id), + action: BucketAction::GetNonFungibles, + args: invoke_args![], + }); + + resp.decode().expect("get_non_fungibles returned invalid non fungibles") + } } diff --git a/dan_layer/template_lib/src/models/vault.rs b/dan_layer/template_lib/src/models/vault.rs index 9047e642a..2c6a61c6f 100644 --- a/dan_layer/template_lib/src/models/vault.rs +++ b/dan_layer/template_lib/src/models/vault.rs @@ -33,7 +33,7 @@ use tari_template_abi::{ EngineOp, }; -use super::{BinaryTag, Proof, ProofAuth}; +use super::{BinaryTag, NonFungible, Proof, ProofAuth}; use crate::{ args::{ ConfidentialRevealArg, @@ -273,6 +273,17 @@ impl Vault { .expect("get_non_fungible_ids returned invalid non fungible ids") } + /// Returns all the non-fungibles in this vault + pub fn get_non_fungibles(&self) -> Vec { + let resp: InvokeResult = call_engine(EngineOp::VaultInvoke, &VaultInvokeArg { + vault_ref: self.vault_ref(), + action: VaultAction::GetNonFungibles, + args: invoke_args![], + }); + + resp.decode().expect("get_non_fungibles returned invalid non fungibles") + } + /// Returns the resource address of the tokens that this vault holds pub fn resource_address(&self) -> ResourceAddress { let resp: InvokeResult = call_engine(EngineOp::VaultInvoke, &VaultInvokeArg {