Skip to content

Commit

Permalink
feat(template_lib): improve API for extracting data from NFTs (#897)
Browse files Browse the repository at this point in the history
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::<Metadata>();
```

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::<Metadata>();
```

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
  • Loading branch information
mrnaveira authored Jan 16, 2024
1 parent 63a1563 commit 33283f8
Show file tree
Hide file tree
Showing 6 changed files with 123 additions and 3 deletions.
44 changes: 43 additions & 1 deletion dan_layer/engine/src/runtime/impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand Down Expand Up @@ -1086,6 +1086,28 @@ impl<TTemplateProvider: TemplateProvider<Template = LoadedTemplate>> 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<NonFungible> = 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)
})
},
}
}

Expand Down Expand Up @@ -1246,6 +1268,26 @@ impl<TTemplateProvider: TemplateProvider<Template = LoadedTemplate>> 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<NonFungible> = nft_ids
.iter()
.map(|id| NonFungibleAddress::new(*resource_address, id.clone()))
.map(NonFungible::new)
.collect();

Ok(InvokeResult::encode(&nfts)?)
})
},
}
}

Expand Down
13 changes: 13 additions & 0 deletions dan_layer/engine/tests/templates/nft/basic_nft/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<NonFungible> {
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<NonFungible> {
self.vault.get_non_fungibles()
}
}
}
41 changes: 41 additions & 0 deletions dan_layer/engine/tests/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,7 @@ mod fungible {

mod basic_nft {
use serde::{Deserialize, Serialize};
use tari_template_lib::models::NonFungible;

use super::*;

Expand Down Expand Up @@ -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::<Vec<NonFungible>>()
.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::<Vec<NonFungible>>()
.unwrap();
assert_eq!(nfts_from_bucket.len(), 4);
}
}

mod emoji_id {
Expand Down
2 changes: 2 additions & 0 deletions dan_layer/template_lib/src/args/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,7 @@ pub enum VaultAction {
CreateProofByFungibleAmount,
CreateProofByNonFungibles,
CreateProofByConfidentialResource,
GetNonFungibles,
}

/// A vault withdraw operation argument
Expand Down Expand Up @@ -397,6 +398,7 @@ pub enum BucketAction {
Burn,
CreateProof,
GetNonFungibleIds,
GetNonFungibles,
}

/// A bucket burn operation argument
Expand Down
13 changes: 12 additions & 1 deletion dan_layer/template_lib/src/models/bucket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -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<NonFungible> {
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")
}
}
13 changes: 12 additions & 1 deletion dan_layer/template_lib/src/models/vault.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ use tari_template_abi::{
EngineOp,
};

use super::{BinaryTag, Proof, ProofAuth};
use super::{BinaryTag, NonFungible, Proof, ProofAuth};
use crate::{
args::{
ConfidentialRevealArg,
Expand Down Expand Up @@ -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<NonFungible> {
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 {
Expand Down

0 comments on commit 33283f8

Please sign in to comment.