difftreelog
Merge pull request #385 from UniqueNetwork/feature/conditional-rmrk
in: master
feat(rmrk): remove RMRK proxy from unique runtime
10 files changed
node/rpc/Cargo.tomldiffbeforeafterboth--- a/node/rpc/Cargo.toml
+++ b/node/rpc/Cargo.toml
@@ -63,3 +63,4 @@
[features]
default = []
std = []
+unique-runtime = []
node/rpc/src/lib.rsdiffbeforeafterboth--- a/node/rpc/src/lib.rs
+++ b/node/rpc/src/lib.rs
@@ -169,7 +169,11 @@
Eth, EthApiServer, EthDevSigner, EthFilter, EthFilterApiServer, EthPubSub,
EthPubSubApiServer, EthSigner, Net, NetApiServer, Web3, Web3ApiServer,
};
- use uc_rpc::{UniqueApiServer, Unique, RmrkApiServer, Rmrk};
+ use uc_rpc::{UniqueApiServer, Unique};
+
+ #[cfg(not(feature = "unique-runtime"))]
+ use uc_rpc::{RmrkApiServer, Rmrk};
+
// use pallet_contracts_rpc::{Contracts, ContractsApi};
use pallet_transaction_payment_rpc::{TransactionPaymentRpc, TransactionPaymentApiServer};
use substrate_frame_rpc_system::{SystemRpc, SystemApiServer};
@@ -223,6 +227,8 @@
)?;
io.merge(Unique::new(client.clone()).into_rpc())?;
+
+ #[cfg(not(feature = "unique-runtime"))]
io.merge(Rmrk::new(client.clone()).into_rpc())?;
if let Some(filter_pool) = filter_pool {
runtime/common/src/runtime_apis.rsdiffbeforeafterboth--- a/runtime/common/src/runtime_apis.rs
+++ b/runtime/common/src/runtime_apis.rs
@@ -144,402 +144,6 @@
}
}
- impl rmrk_rpc::RmrkApi<
- Block,
- AccountId,
- RmrkCollectionInfo<AccountId>,
- RmrkInstanceInfo<AccountId>,
- RmrkResourceInfo,
- RmrkPropertyInfo,
- RmrkBaseInfo<AccountId>,
- RmrkPartType,
- RmrkTheme
- > for Runtime {
- fn last_collection_idx() -> Result<RmrkCollectionId, DispatchError> {
- Ok(RmrkCore::last_collection_idx())
- }
-
- fn collection_by_id(collection_id: RmrkCollectionId) -> Result<Option<RmrkCollectionInfo<AccountId>>, DispatchError> {
- use pallet_proxy_rmrk_core::{RmrkProperty, misc::CollectionType};
- use pallet_common::CommonCollectionOperations;
-
- let (collection, collection_id) = match RmrkCore::get_typed_nft_collection_mapped(collection_id, CollectionType::Regular) {
- Ok(c) => c,
- Err(_) => return Ok(None),
- };
-
- let nfts_count = collection.total_supply();
-
- Ok(Some(RmrkCollectionInfo {
- issuer: collection.owner.clone(),
- metadata: RmrkCore::get_collection_property_decoded(collection_id, RmrkProperty::Metadata)?,
- max: collection.limits.token_limit,
- symbol: RmrkCore::rebind(&collection.token_prefix)?,
- nfts_count
- }))
- }
-
- fn nft_by_id(collection_id: RmrkCollectionId, nft_by_id: RmrkNftId) -> Result<Option<RmrkInstanceInfo<AccountId>>, DispatchError> {
- use up_data_structs::mapping::TokenAddressMapping;
- use pallet_proxy_rmrk_core::{RmrkProperty, misc::CollectionType};
- use pallet_common::CommonCollectionOperations;
-
- let (collection, collection_id) = match RmrkCore::get_typed_nft_collection_mapped(collection_id, CollectionType::Regular) {
- Ok(c) => c,
- Err(_) => return Ok(None),
- };
-
- let nft_id = TokenId(nft_by_id);
- if !RmrkCore::nft_exists(collection_id, nft_id) { return Ok(None); }
-
- let owner = match collection.token_owner(nft_id) {
- Some(owner) => match <Runtime as pallet_common::Config>::CrossTokenAddressMapping::address_to_token(&owner) {
- Some((col, tok)) => {
- let rmrk_collection = RmrkCore::rmrk_collection_id(col)?;
-
- RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(rmrk_collection, tok.0)
- }
- None => RmrkAccountIdOrCollectionNftTuple::AccountId(owner.as_sub().clone())
- },
- None => return Ok(None)
- };
-
- Ok(Some(RmrkInstanceInfo {
- owner: owner,
- royalty: RmrkCore::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::RoyaltyInfo)?,
- metadata: RmrkCore::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Metadata)?,
- equipped: RmrkCore::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Equipped)?,
- pending: RmrkCore::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::PendingNftAccept)?,
- }))
- }
-
- fn account_tokens(account_id: AccountId, collection_id: RmrkCollectionId) -> Result<Vec<RmrkNftId>, DispatchError> {
- use pallet_proxy_rmrk_core::{RmrkProperty, misc::CollectionType};
- use pallet_common::CommonCollectionOperations;
-
- let cross_account_id = CrossAccountId::from_sub(account_id);
-
- let (collection, collection_id) = match RmrkCore::get_typed_nft_collection_mapped(collection_id, CollectionType::Regular) {
- Ok(c) => c,
- Err(_) => return Ok(Vec::new()),
- };
-
- let tokens = collection.account_tokens(cross_account_id)
- .into_iter()
- .filter(|token| {
- let is_pending = RmrkCore::get_nft_property_decoded(
- collection_id,
- *token,
- RmrkProperty::PendingNftAccept
- ).unwrap_or(true);
-
- !is_pending
- })
- .map(|token| token.0)
- .collect();
-
- Ok(tokens)
- }
-
- fn nft_children(collection_id: RmrkCollectionId, nft_id: RmrkNftId) -> Result<Vec<RmrkNftChild>, DispatchError> {
- use pallet_proxy_rmrk_core::RmrkProperty;
-
- let collection_id = match RmrkCore::unique_collection_id(collection_id) {
- Ok(id) => id,
- Err(_) => return Ok(Vec::new())
- };
- let nft_id = TokenId(nft_id);
- if !RmrkCore::nft_exists(collection_id, nft_id) { return Ok(Vec::new()); }
-
- Ok(
- pallet_nonfungible::TokenChildren::<Runtime>::iter_prefix((collection_id, nft_id))
- .filter_map(|((child_collection, child_token), _)| {
- let is_pending = RmrkCore::get_nft_property_decoded(
- child_collection,
- child_token,
- RmrkProperty::PendingNftAccept
- ).ok()?;
-
- if is_pending {
- return None;
- }
-
- let rmrk_child_collection = RmrkCore::rmrk_collection_id(
- child_collection
- ).ok()?;
-
- Some(RmrkNftChild {
- collection_id: rmrk_child_collection,
- nft_id: child_token.0,
- })
- }).collect()
- )
- }
-
- fn collection_properties(collection_id: RmrkCollectionId, filter_keys: Option<Vec<RmrkPropertyKey>>) -> Result<Vec<RmrkPropertyInfo>, DispatchError> {
- use pallet_proxy_rmrk_core::misc::CollectionType;
-
- let collection_id = match RmrkCore::unique_collection_id(collection_id) {
- Ok(id) => id,
- Err(_) => return Ok(Vec::new())
- };
- if RmrkCore::ensure_collection_type(collection_id, CollectionType::Regular).is_err() {
- return Ok(Vec::new());
- }
-
- let properties = RmrkCore::filter_user_properties(
- collection_id,
- /* token_id = */ None,
- filter_keys,
- |key, value| RmrkPropertyInfo {
- key,
- value
- }
- )?;
-
- Ok(properties)
- }
-
- fn nft_properties(collection_id: RmrkCollectionId, nft_id: RmrkNftId, filter_keys: Option<Vec<RmrkPropertyKey>>) -> Result<Vec<RmrkPropertyInfo>, DispatchError> {
- use pallet_proxy_rmrk_core::misc::NftType;
-
- let collection_id = match RmrkCore::unique_collection_id(collection_id) {
- Ok(id) => id,
- Err(_) => return Ok(Vec::new())
- };
- let token_id = TokenId(nft_id);
-
- if RmrkCore::ensure_nft_type(collection_id, token_id, NftType::Regular).is_err() {
- return Ok(Vec::new());
- }
-
- let properties = RmrkCore::filter_user_properties(
- collection_id,
- Some(token_id),
- filter_keys,
- |key, value| RmrkPropertyInfo {
- key,
- value
- }
- )?;
-
- Ok(properties)
- }
-
- fn nft_resources(collection_id: RmrkCollectionId, nft_id: RmrkNftId) -> Result<Vec<RmrkResourceInfo>, DispatchError> {
- use pallet_proxy_rmrk_core::{RmrkProperty, misc::{CollectionType, NftType, ResourceType}};
- use pallet_common::CommonCollectionOperations;
-
- let collection_id = match RmrkCore::unique_collection_id(collection_id) {
- Ok(id) => id,
- Err(_) => return Ok(Vec::new())
- };
- if RmrkCore::ensure_collection_type(collection_id, CollectionType::Regular).is_err() { return Ok(Vec::new()); }
-
- let nft_id = TokenId(nft_id);
- if RmrkCore::ensure_nft_type(collection_id, nft_id, NftType::Regular).is_err() { return Ok(Vec::new()); }
-
- let res_collection_id: Option<CollectionId> = RmrkCore::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::ResourceCollection)?;
-
- let res_collection_id = match res_collection_id {
- Some(id) => id,
- None => return Ok(Vec::new())
- };
-
- let resource_collection = RmrkCore::get_typed_nft_collection(res_collection_id, CollectionType::Resource)?;
-
- let resources = resource_collection
- .collection_tokens()
- .iter()
- .filter_map(|(res_id)| Some(RmrkResourceInfo {
- id: res_id.0,
- pending: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::PendingResourceAccept).ok()?,
- pending_removal: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::PendingResourceRemoval).ok()?,
- resource: match RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::ResourceType).ok()? {
- ResourceType::Basic => RmrkResourceTypes::Basic(RmrkBasicResource {
- src: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Src).ok()?,
- metadata: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Metadata).ok()?,
- license: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::License).ok()?,
- thumb: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Thumb).ok()?,
- }),
- ResourceType::Composable => RmrkResourceTypes::Composable(RmrkComposableResource {
- parts: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Parts).ok()?,
- base: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Base).ok()?,
- src: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Src).ok()?,
- metadata: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Metadata).ok()?,
- license: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::License).ok()?,
- thumb: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Thumb).ok()?,
- }),
- ResourceType::Slot => RmrkResourceTypes::Slot(RmrkSlotResource {
- base: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Base).ok()?,
- src: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Src).ok()?,
- metadata: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Metadata).ok()?,
- slot: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Slot).ok()?,
- license: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::License).ok()?,
- thumb: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Thumb).ok()?,
- }),
- },
- }))
- .collect();
-
- Ok(resources)
- }
-
- fn nft_resource_priority(collection_id: RmrkCollectionId, nft_id: RmrkNftId, resource_id: RmrkResourceId) -> Result<Option<u32>, DispatchError> {
- use pallet_proxy_rmrk_core::{RmrkProperty, misc::{CollectionType, NftType}};
-
- let collection_id = match RmrkCore::unique_collection_id(collection_id) {
- Ok(id) => id,
- Err(_) => return Ok(None)
- };
- if RmrkCore::ensure_collection_type(collection_id, CollectionType::Regular).is_err() { return Ok(None); }
-
- let nft_id = TokenId(nft_id);
- if RmrkCore::ensure_nft_type(collection_id, nft_id, NftType::Regular).is_err() { return Ok(None); }
-
- let priorities: Vec<_> = RmrkCore::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::ResourcePriorities)?;
- Ok(
- priorities.into_iter()
- .enumerate()
- .find(|(_, id)| *id == resource_id)
- .map(|(priority, _): (usize, RmrkResourceId)| priority as u32)
- )
- }
-
- fn base(base_id: RmrkBaseId) -> Result<Option<RmrkBaseInfo<AccountId>>, DispatchError> {
- use pallet_proxy_rmrk_core::{
- RmrkProperty, misc::{CollectionType},
- };
-
- let (collection, collection_id) = match RmrkCore::get_typed_nft_collection_mapped(base_id, CollectionType::Base) {
- Ok(c) => c,
- Err(_) => return Ok(None),
- };
-
- Ok(Some(RmrkBaseInfo {
- issuer: collection.owner.clone(),
- base_type: RmrkCore::get_collection_property_decoded(collection_id, RmrkProperty::BaseType)?,
- symbol: RmrkCore::rebind(&collection.token_prefix)?,
- }))
- }
-
- fn base_parts(base_id: RmrkBaseId) -> Result<Vec<RmrkPartType>, DispatchError> {
- use pallet_proxy_rmrk_core::{RmrkProperty, misc::{CollectionType, NftType}};
- use pallet_common::CommonCollectionOperations;
-
- let (collection, collection_id) = match RmrkCore::get_typed_nft_collection_mapped(base_id, CollectionType::Base) {
- Ok(c) => c,
- Err(_) => return Ok(Vec::new()),
- };
-
- let parts = collection.collection_tokens()
- .into_iter()
- .filter_map(|token_id| {
- let nft_type = RmrkCore::get_nft_type(collection_id, token_id).ok()?;
-
- match nft_type {
- NftType::FixedPart => Some(RmrkPartType::FixedPart(RmrkFixedPart {
- id: RmrkCore::get_nft_property_decoded(collection_id, token_id, RmrkProperty::ExternalPartId).ok()?,
- src: RmrkCore::get_nft_property_decoded(collection_id, token_id, RmrkProperty::Src).ok()?,
- z: RmrkCore::get_nft_property_decoded(collection_id, token_id, RmrkProperty::ZIndex).ok()?,
- })),
- NftType::SlotPart => Some(RmrkPartType::SlotPart(RmrkSlotPart {
- id: RmrkCore::get_nft_property_decoded(collection_id, token_id, RmrkProperty::ExternalPartId).ok()?,
- src: RmrkCore::get_nft_property_decoded(collection_id, token_id, RmrkProperty::Src).ok()?,
- z: RmrkCore::get_nft_property_decoded(collection_id, token_id, RmrkProperty::ZIndex).ok()?,
- equippable: RmrkCore::get_nft_property_decoded(collection_id, token_id, RmrkProperty::EquippableList).ok()?,
- })),
- _ => None
- }
- })
- .collect();
-
- Ok(parts)
- }
-
- fn theme_names(base_id: RmrkBaseId) -> Result<Vec<RmrkThemeName>, DispatchError> {
- use pallet_proxy_rmrk_core::{RmrkProperty, misc::CollectionType};
- use pallet_common::CommonCollectionOperations;
-
- let (collection, collection_id) = match RmrkCore::get_typed_nft_collection_mapped(base_id, CollectionType::Base) {
- Ok(c) => c,
- Err(_) => return Ok(Vec::new()),
- };
-
- let theme_names = collection.collection_tokens()
- .iter()
- .filter_map(|token_id| {
- let nft_type = RmrkCore::get_nft_type(collection_id, *token_id).ok()?;
-
- match nft_type {
- Theme => RmrkCore::get_nft_property_decoded(collection_id, *token_id, RmrkProperty::ThemeName).ok(),
- _ => None
- }
- })
- .collect();
-
- Ok(theme_names)
- }
-
- fn theme(base_id: RmrkBaseId, theme_name: RmrkThemeName, filter_keys: Option<Vec<RmrkPropertyKey>>) -> Result<Option<RmrkTheme>, DispatchError> {
- use pallet_proxy_rmrk_core::{
- RmrkProperty,
- misc::{CollectionType, NftType}
- };
- use pallet_common::CommonCollectionOperations;
-
- let (collection, collection_id) = match RmrkCore::get_typed_nft_collection_mapped(base_id, CollectionType::Base) {
- Ok(c) => c,
- Err(_) => return Ok(None),
- };
-
- let theme_info = collection.collection_tokens()
- .into_iter()
- .find_map(|token_id| {
- RmrkCore::ensure_nft_type(collection_id, token_id, NftType::Theme).ok()?;
-
- let name: RmrkString = RmrkCore::get_nft_property_decoded(
- collection_id, token_id, RmrkProperty::ThemeName
- ).ok()?;
-
- if name == theme_name {
- Some((name, token_id))
- } else {
- None
- }
- });
-
- let (name, theme_id) = match theme_info {
- Some((name, theme_id)) => (name, theme_id),
- None => return Ok(None)
- };
-
- let properties = RmrkCore::filter_user_properties(
- collection_id,
- Some(theme_id),
- filter_keys,
- |key, value| RmrkThemeProperty {
- key,
- value
- }
- )?;
-
- let inherit = RmrkCore::get_nft_property_decoded(
- collection_id,
- theme_id,
- RmrkProperty::ThemeInherit
- )?;
-
- let theme = RmrkTheme {
- name,
- properties,
- inherit,
- };
-
- Ok(Some(theme))
- }
- }
-
impl sp_api::Core<Block> for Runtime {
fn version() -> RuntimeVersion {
VERSION
runtime/opal/src/lib.rsdiffbeforeafterboth--- a/runtime/opal/src/lib.rs
+++ b/runtime/opal/src/lib.rs
@@ -1315,7 +1315,405 @@
}};
}
-impl_common_runtime_apis!();
+impl_common_runtime_apis! {
+ #![custom_apis]
+
+ impl rmrk_rpc::RmrkApi<
+ Block,
+ AccountId,
+ RmrkCollectionInfo<AccountId>,
+ RmrkInstanceInfo<AccountId>,
+ RmrkResourceInfo,
+ RmrkPropertyInfo,
+ RmrkBaseInfo<AccountId>,
+ RmrkPartType,
+ RmrkTheme
+ > for Runtime {
+ fn last_collection_idx() -> Result<RmrkCollectionId, DispatchError> {
+ Ok(RmrkCore::last_collection_idx())
+ }
+
+ fn collection_by_id(collection_id: RmrkCollectionId) -> Result<Option<RmrkCollectionInfo<AccountId>>, DispatchError> {
+ use pallet_proxy_rmrk_core::{RmrkProperty, misc::CollectionType};
+ use pallet_common::CommonCollectionOperations;
+
+ let (collection, collection_id) = match RmrkCore::get_typed_nft_collection_mapped(collection_id, CollectionType::Regular) {
+ Ok(c) => c,
+ Err(_) => return Ok(None),
+ };
+
+ let nfts_count = collection.total_supply();
+
+ Ok(Some(RmrkCollectionInfo {
+ issuer: collection.owner.clone(),
+ metadata: RmrkCore::get_collection_property_decoded(collection_id, RmrkProperty::Metadata)?,
+ max: collection.limits.token_limit,
+ symbol: RmrkCore::rebind(&collection.token_prefix)?,
+ nfts_count
+ }))
+ }
+
+ fn nft_by_id(collection_id: RmrkCollectionId, nft_by_id: RmrkNftId) -> Result<Option<RmrkInstanceInfo<AccountId>>, DispatchError> {
+ use up_data_structs::mapping::TokenAddressMapping;
+ use pallet_proxy_rmrk_core::{RmrkProperty, misc::CollectionType};
+ use pallet_common::CommonCollectionOperations;
+
+ let (collection, collection_id) = match RmrkCore::get_typed_nft_collection_mapped(collection_id, CollectionType::Regular) {
+ Ok(c) => c,
+ Err(_) => return Ok(None),
+ };
+
+ let nft_id = TokenId(nft_by_id);
+ if !RmrkCore::nft_exists(collection_id, nft_id) { return Ok(None); }
+
+ let owner = match collection.token_owner(nft_id) {
+ Some(owner) => match <Runtime as pallet_common::Config>::CrossTokenAddressMapping::address_to_token(&owner) {
+ Some((col, tok)) => {
+ let rmrk_collection = RmrkCore::rmrk_collection_id(col)?;
+
+ RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(rmrk_collection, tok.0)
+ }
+ None => RmrkAccountIdOrCollectionNftTuple::AccountId(owner.as_sub().clone())
+ },
+ None => return Ok(None)
+ };
+
+ Ok(Some(RmrkInstanceInfo {
+ owner: owner,
+ royalty: RmrkCore::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::RoyaltyInfo)?,
+ metadata: RmrkCore::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Metadata)?,
+ equipped: RmrkCore::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Equipped)?,
+ pending: RmrkCore::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::PendingNftAccept)?,
+ }))
+ }
+
+ fn account_tokens(account_id: AccountId, collection_id: RmrkCollectionId) -> Result<Vec<RmrkNftId>, DispatchError> {
+ use pallet_proxy_rmrk_core::{RmrkProperty, misc::CollectionType};
+ use pallet_common::CommonCollectionOperations;
+
+ let cross_account_id = CrossAccountId::from_sub(account_id);
+
+ let (collection, collection_id) = match RmrkCore::get_typed_nft_collection_mapped(collection_id, CollectionType::Regular) {
+ Ok(c) => c,
+ Err(_) => return Ok(Vec::new()),
+ };
+
+ let tokens = collection.account_tokens(cross_account_id)
+ .into_iter()
+ .filter(|token| {
+ let is_pending = RmrkCore::get_nft_property_decoded(
+ collection_id,
+ *token,
+ RmrkProperty::PendingNftAccept
+ ).unwrap_or(true);
+
+ !is_pending
+ })
+ .map(|token| token.0)
+ .collect();
+
+ Ok(tokens)
+ }
+
+ fn nft_children(collection_id: RmrkCollectionId, nft_id: RmrkNftId) -> Result<Vec<RmrkNftChild>, DispatchError> {
+ use pallet_proxy_rmrk_core::RmrkProperty;
+
+ let collection_id = match RmrkCore::unique_collection_id(collection_id) {
+ Ok(id) => id,
+ Err(_) => return Ok(Vec::new())
+ };
+ let nft_id = TokenId(nft_id);
+ if !RmrkCore::nft_exists(collection_id, nft_id) { return Ok(Vec::new()); }
+
+ Ok(
+ pallet_nonfungible::TokenChildren::<Runtime>::iter_prefix((collection_id, nft_id))
+ .filter_map(|((child_collection, child_token), _)| {
+ let is_pending = RmrkCore::get_nft_property_decoded(
+ child_collection,
+ child_token,
+ RmrkProperty::PendingNftAccept
+ ).ok()?;
+
+ if is_pending {
+ return None;
+ }
+
+ let rmrk_child_collection = RmrkCore::rmrk_collection_id(
+ child_collection
+ ).ok()?;
+
+ Some(RmrkNftChild {
+ collection_id: rmrk_child_collection,
+ nft_id: child_token.0,
+ })
+ }).collect()
+ )
+ }
+
+ fn collection_properties(collection_id: RmrkCollectionId, filter_keys: Option<Vec<RmrkPropertyKey>>) -> Result<Vec<RmrkPropertyInfo>, DispatchError> {
+ use pallet_proxy_rmrk_core::misc::CollectionType;
+
+ let collection_id = match RmrkCore::unique_collection_id(collection_id) {
+ Ok(id) => id,
+ Err(_) => return Ok(Vec::new())
+ };
+ if RmrkCore::ensure_collection_type(collection_id, CollectionType::Regular).is_err() {
+ return Ok(Vec::new());
+ }
+
+ let properties = RmrkCore::filter_user_properties(
+ collection_id,
+ /* token_id = */ None,
+ filter_keys,
+ |key, value| RmrkPropertyInfo {
+ key,
+ value
+ }
+ )?;
+
+ Ok(properties)
+ }
+
+ fn nft_properties(collection_id: RmrkCollectionId, nft_id: RmrkNftId, filter_keys: Option<Vec<RmrkPropertyKey>>) -> Result<Vec<RmrkPropertyInfo>, DispatchError> {
+ use pallet_proxy_rmrk_core::misc::NftType;
+
+ let collection_id = match RmrkCore::unique_collection_id(collection_id) {
+ Ok(id) => id,
+ Err(_) => return Ok(Vec::new())
+ };
+ let token_id = TokenId(nft_id);
+
+ if RmrkCore::ensure_nft_type(collection_id, token_id, NftType::Regular).is_err() {
+ return Ok(Vec::new());
+ }
+
+ let properties = RmrkCore::filter_user_properties(
+ collection_id,
+ Some(token_id),
+ filter_keys,
+ |key, value| RmrkPropertyInfo {
+ key,
+ value
+ }
+ )?;
+
+ Ok(properties)
+ }
+
+ fn nft_resources(collection_id: RmrkCollectionId, nft_id: RmrkNftId) -> Result<Vec<RmrkResourceInfo>, DispatchError> {
+ use pallet_proxy_rmrk_core::{RmrkProperty, misc::{CollectionType, NftType, ResourceType}};
+ use pallet_common::CommonCollectionOperations;
+
+ let collection_id = match RmrkCore::unique_collection_id(collection_id) {
+ Ok(id) => id,
+ Err(_) => return Ok(Vec::new())
+ };
+ if RmrkCore::ensure_collection_type(collection_id, CollectionType::Regular).is_err() { return Ok(Vec::new()); }
+
+ let nft_id = TokenId(nft_id);
+ if RmrkCore::ensure_nft_type(collection_id, nft_id, NftType::Regular).is_err() { return Ok(Vec::new()); }
+
+ let res_collection_id: Option<CollectionId> = RmrkCore::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::ResourceCollection)?;
+
+ let res_collection_id = match res_collection_id {
+ Some(id) => id,
+ None => return Ok(Vec::new())
+ };
+
+ let resource_collection = RmrkCore::get_typed_nft_collection(res_collection_id, CollectionType::Resource)?;
+
+ let resources = resource_collection
+ .collection_tokens()
+ .iter()
+ .filter_map(|res_id| Some(RmrkResourceInfo {
+ id: res_id.0,
+ pending: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::PendingResourceAccept).ok()?,
+ pending_removal: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::PendingResourceRemoval).ok()?,
+ resource: match RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::ResourceType).ok()? {
+ ResourceType::Basic => RmrkResourceTypes::Basic(RmrkBasicResource {
+ src: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Src).ok()?,
+ metadata: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Metadata).ok()?,
+ license: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::License).ok()?,
+ thumb: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Thumb).ok()?,
+ }),
+ ResourceType::Composable => RmrkResourceTypes::Composable(RmrkComposableResource {
+ parts: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Parts).ok()?,
+ base: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Base).ok()?,
+ src: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Src).ok()?,
+ metadata: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Metadata).ok()?,
+ license: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::License).ok()?,
+ thumb: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Thumb).ok()?,
+ }),
+ ResourceType::Slot => RmrkResourceTypes::Slot(RmrkSlotResource {
+ base: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Base).ok()?,
+ src: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Src).ok()?,
+ metadata: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Metadata).ok()?,
+ slot: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Slot).ok()?,
+ license: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::License).ok()?,
+ thumb: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Thumb).ok()?,
+ }),
+ },
+ }))
+ .collect();
+
+ Ok(resources)
+ }
+
+ fn nft_resource_priority(collection_id: RmrkCollectionId, nft_id: RmrkNftId, resource_id: RmrkResourceId) -> Result<Option<u32>, DispatchError> {
+ use pallet_proxy_rmrk_core::{RmrkProperty, misc::{CollectionType, NftType}};
+
+ let collection_id = match RmrkCore::unique_collection_id(collection_id) {
+ Ok(id) => id,
+ Err(_) => return Ok(None)
+ };
+ if RmrkCore::ensure_collection_type(collection_id, CollectionType::Regular).is_err() { return Ok(None); }
+
+ let nft_id = TokenId(nft_id);
+ if RmrkCore::ensure_nft_type(collection_id, nft_id, NftType::Regular).is_err() { return Ok(None); }
+
+ let priorities: Vec<_> = RmrkCore::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::ResourcePriorities)?;
+ Ok(
+ priorities.into_iter()
+ .enumerate()
+ .find(|(_, id)| *id == resource_id)
+ .map(|(priority, _): (usize, RmrkResourceId)| priority as u32)
+ )
+ }
+
+ fn base(base_id: RmrkBaseId) -> Result<Option<RmrkBaseInfo<AccountId>>, DispatchError> {
+ use pallet_proxy_rmrk_core::{
+ RmrkProperty, misc::{CollectionType},
+ };
+
+ let (collection, collection_id) = match RmrkCore::get_typed_nft_collection_mapped(base_id, CollectionType::Base) {
+ Ok(c) => c,
+ Err(_) => return Ok(None),
+ };
+
+ Ok(Some(RmrkBaseInfo {
+ issuer: collection.owner.clone(),
+ base_type: RmrkCore::get_collection_property_decoded(collection_id, RmrkProperty::BaseType)?,
+ symbol: RmrkCore::rebind(&collection.token_prefix)?,
+ }))
+ }
+
+ fn base_parts(base_id: RmrkBaseId) -> Result<Vec<RmrkPartType>, DispatchError> {
+ use pallet_proxy_rmrk_core::{RmrkProperty, misc::{CollectionType, NftType}};
+ use pallet_common::CommonCollectionOperations;
+
+ let (collection, collection_id) = match RmrkCore::get_typed_nft_collection_mapped(base_id, CollectionType::Base) {
+ Ok(c) => c,
+ Err(_) => return Ok(Vec::new()),
+ };
+
+ let parts = collection.collection_tokens()
+ .into_iter()
+ .filter_map(|token_id| {
+ let nft_type = RmrkCore::get_nft_type(collection_id, token_id).ok()?;
+
+ match nft_type {
+ NftType::FixedPart => Some(RmrkPartType::FixedPart(RmrkFixedPart {
+ id: RmrkCore::get_nft_property_decoded(collection_id, token_id, RmrkProperty::ExternalPartId).ok()?,
+ src: RmrkCore::get_nft_property_decoded(collection_id, token_id, RmrkProperty::Src).ok()?,
+ z: RmrkCore::get_nft_property_decoded(collection_id, token_id, RmrkProperty::ZIndex).ok()?,
+ })),
+ NftType::SlotPart => Some(RmrkPartType::SlotPart(RmrkSlotPart {
+ id: RmrkCore::get_nft_property_decoded(collection_id, token_id, RmrkProperty::ExternalPartId).ok()?,
+ src: RmrkCore::get_nft_property_decoded(collection_id, token_id, RmrkProperty::Src).ok()?,
+ z: RmrkCore::get_nft_property_decoded(collection_id, token_id, RmrkProperty::ZIndex).ok()?,
+ equippable: RmrkCore::get_nft_property_decoded(collection_id, token_id, RmrkProperty::EquippableList).ok()?,
+ })),
+ _ => None
+ }
+ })
+ .collect();
+
+ Ok(parts)
+ }
+
+ fn theme_names(base_id: RmrkBaseId) -> Result<Vec<RmrkThemeName>, DispatchError> {
+ use pallet_proxy_rmrk_core::{RmrkProperty, misc::{NftType, CollectionType}};
+ use pallet_common::CommonCollectionOperations;
+
+ let (collection, collection_id) = match RmrkCore::get_typed_nft_collection_mapped(base_id, CollectionType::Base) {
+ Ok(c) => c,
+ Err(_) => return Ok(Vec::new()),
+ };
+
+ let theme_names = collection.collection_tokens()
+ .iter()
+ .filter_map(|token_id| {
+ let nft_type = RmrkCore::get_nft_type(collection_id, *token_id).ok()?;
+
+ match nft_type {
+ NftType::Theme => RmrkCore::get_nft_property_decoded(collection_id, *token_id, RmrkProperty::ThemeName).ok(),
+ _ => None
+ }
+ })
+ .collect();
+
+ Ok(theme_names)
+ }
+
+ fn theme(base_id: RmrkBaseId, theme_name: RmrkThemeName, filter_keys: Option<Vec<RmrkPropertyKey>>) -> Result<Option<RmrkTheme>, DispatchError> {
+ use pallet_proxy_rmrk_core::{
+ RmrkProperty,
+ misc::{CollectionType, NftType}
+ };
+ use pallet_common::CommonCollectionOperations;
+
+ let (collection, collection_id) = match RmrkCore::get_typed_nft_collection_mapped(base_id, CollectionType::Base) {
+ Ok(c) => c,
+ Err(_) => return Ok(None),
+ };
+
+ let theme_info = collection.collection_tokens()
+ .into_iter()
+ .find_map(|token_id| {
+ RmrkCore::ensure_nft_type(collection_id, token_id, NftType::Theme).ok()?;
+
+ let name: RmrkString = RmrkCore::get_nft_property_decoded(
+ collection_id, token_id, RmrkProperty::ThemeName
+ ).ok()?;
+
+ if name == theme_name {
+ Some((name, token_id))
+ } else {
+ None
+ }
+ });
+
+ let (name, theme_id) = match theme_info {
+ Some((name, theme_id)) => (name, theme_id),
+ None => return Ok(None)
+ };
+
+ let properties = RmrkCore::filter_user_properties(
+ collection_id,
+ Some(theme_id),
+ filter_keys,
+ |key, value| RmrkThemeProperty {
+ key,
+ value
+ }
+ )?;
+
+ let inherit = RmrkCore::get_nft_property_decoded(
+ collection_id,
+ theme_id,
+ RmrkProperty::ThemeInherit
+ )?;
+
+ let theme = RmrkTheme {
+ name,
+ properties,
+ inherit,
+ };
+
+ Ok(Some(theme))
+ }
+ }
+}
struct CheckInherents;
runtime/quartz/src/lib.rsdiffbeforeafterboth--- a/runtime/quartz/src/lib.rs
+++ b/runtime/quartz/src/lib.rs
@@ -1315,7 +1315,405 @@
}};
}
-impl_common_runtime_apis!();
+impl_common_runtime_apis! {
+ #![custom_apis]
+
+ impl rmrk_rpc::RmrkApi<
+ Block,
+ AccountId,
+ RmrkCollectionInfo<AccountId>,
+ RmrkInstanceInfo<AccountId>,
+ RmrkResourceInfo,
+ RmrkPropertyInfo,
+ RmrkBaseInfo<AccountId>,
+ RmrkPartType,
+ RmrkTheme
+ > for Runtime {
+ fn last_collection_idx() -> Result<RmrkCollectionId, DispatchError> {
+ Ok(RmrkCore::last_collection_idx())
+ }
+
+ fn collection_by_id(collection_id: RmrkCollectionId) -> Result<Option<RmrkCollectionInfo<AccountId>>, DispatchError> {
+ use pallet_proxy_rmrk_core::{RmrkProperty, misc::CollectionType};
+ use pallet_common::CommonCollectionOperations;
+
+ let (collection, collection_id) = match RmrkCore::get_typed_nft_collection_mapped(collection_id, CollectionType::Regular) {
+ Ok(c) => c,
+ Err(_) => return Ok(None),
+ };
+
+ let nfts_count = collection.total_supply();
+
+ Ok(Some(RmrkCollectionInfo {
+ issuer: collection.owner.clone(),
+ metadata: RmrkCore::get_collection_property_decoded(collection_id, RmrkProperty::Metadata)?,
+ max: collection.limits.token_limit,
+ symbol: RmrkCore::rebind(&collection.token_prefix)?,
+ nfts_count
+ }))
+ }
+
+ fn nft_by_id(collection_id: RmrkCollectionId, nft_by_id: RmrkNftId) -> Result<Option<RmrkInstanceInfo<AccountId>>, DispatchError> {
+ use up_data_structs::mapping::TokenAddressMapping;
+ use pallet_proxy_rmrk_core::{RmrkProperty, misc::CollectionType};
+ use pallet_common::CommonCollectionOperations;
+
+ let (collection, collection_id) = match RmrkCore::get_typed_nft_collection_mapped(collection_id, CollectionType::Regular) {
+ Ok(c) => c,
+ Err(_) => return Ok(None),
+ };
+
+ let nft_id = TokenId(nft_by_id);
+ if !RmrkCore::nft_exists(collection_id, nft_id) { return Ok(None); }
+
+ let owner = match collection.token_owner(nft_id) {
+ Some(owner) => match <Runtime as pallet_common::Config>::CrossTokenAddressMapping::address_to_token(&owner) {
+ Some((col, tok)) => {
+ let rmrk_collection = RmrkCore::rmrk_collection_id(col)?;
+
+ RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(rmrk_collection, tok.0)
+ }
+ None => RmrkAccountIdOrCollectionNftTuple::AccountId(owner.as_sub().clone())
+ },
+ None => return Ok(None)
+ };
+
+ Ok(Some(RmrkInstanceInfo {
+ owner: owner,
+ royalty: RmrkCore::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::RoyaltyInfo)?,
+ metadata: RmrkCore::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Metadata)?,
+ equipped: RmrkCore::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Equipped)?,
+ pending: RmrkCore::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::PendingNftAccept)?,
+ }))
+ }
+
+ fn account_tokens(account_id: AccountId, collection_id: RmrkCollectionId) -> Result<Vec<RmrkNftId>, DispatchError> {
+ use pallet_proxy_rmrk_core::{RmrkProperty, misc::CollectionType};
+ use pallet_common::CommonCollectionOperations;
+
+ let cross_account_id = CrossAccountId::from_sub(account_id);
+
+ let (collection, collection_id) = match RmrkCore::get_typed_nft_collection_mapped(collection_id, CollectionType::Regular) {
+ Ok(c) => c,
+ Err(_) => return Ok(Vec::new()),
+ };
+
+ let tokens = collection.account_tokens(cross_account_id)
+ .into_iter()
+ .filter(|token| {
+ let is_pending = RmrkCore::get_nft_property_decoded(
+ collection_id,
+ *token,
+ RmrkProperty::PendingNftAccept
+ ).unwrap_or(true);
+
+ !is_pending
+ })
+ .map(|token| token.0)
+ .collect();
+
+ Ok(tokens)
+ }
+
+ fn nft_children(collection_id: RmrkCollectionId, nft_id: RmrkNftId) -> Result<Vec<RmrkNftChild>, DispatchError> {
+ use pallet_proxy_rmrk_core::RmrkProperty;
+
+ let collection_id = match RmrkCore::unique_collection_id(collection_id) {
+ Ok(id) => id,
+ Err(_) => return Ok(Vec::new())
+ };
+ let nft_id = TokenId(nft_id);
+ if !RmrkCore::nft_exists(collection_id, nft_id) { return Ok(Vec::new()); }
+
+ Ok(
+ pallet_nonfungible::TokenChildren::<Runtime>::iter_prefix((collection_id, nft_id))
+ .filter_map(|((child_collection, child_token), _)| {
+ let is_pending = RmrkCore::get_nft_property_decoded(
+ child_collection,
+ child_token,
+ RmrkProperty::PendingNftAccept
+ ).ok()?;
+
+ if is_pending {
+ return None;
+ }
+
+ let rmrk_child_collection = RmrkCore::rmrk_collection_id(
+ child_collection
+ ).ok()?;
+
+ Some(RmrkNftChild {
+ collection_id: rmrk_child_collection,
+ nft_id: child_token.0,
+ })
+ }).collect()
+ )
+ }
+
+ fn collection_properties(collection_id: RmrkCollectionId, filter_keys: Option<Vec<RmrkPropertyKey>>) -> Result<Vec<RmrkPropertyInfo>, DispatchError> {
+ use pallet_proxy_rmrk_core::misc::CollectionType;
+
+ let collection_id = match RmrkCore::unique_collection_id(collection_id) {
+ Ok(id) => id,
+ Err(_) => return Ok(Vec::new())
+ };
+ if RmrkCore::ensure_collection_type(collection_id, CollectionType::Regular).is_err() {
+ return Ok(Vec::new());
+ }
+
+ let properties = RmrkCore::filter_user_properties(
+ collection_id,
+ /* token_id = */ None,
+ filter_keys,
+ |key, value| RmrkPropertyInfo {
+ key,
+ value
+ }
+ )?;
+
+ Ok(properties)
+ }
+
+ fn nft_properties(collection_id: RmrkCollectionId, nft_id: RmrkNftId, filter_keys: Option<Vec<RmrkPropertyKey>>) -> Result<Vec<RmrkPropertyInfo>, DispatchError> {
+ use pallet_proxy_rmrk_core::misc::NftType;
+
+ let collection_id = match RmrkCore::unique_collection_id(collection_id) {
+ Ok(id) => id,
+ Err(_) => return Ok(Vec::new())
+ };
+ let token_id = TokenId(nft_id);
+
+ if RmrkCore::ensure_nft_type(collection_id, token_id, NftType::Regular).is_err() {
+ return Ok(Vec::new());
+ }
+
+ let properties = RmrkCore::filter_user_properties(
+ collection_id,
+ Some(token_id),
+ filter_keys,
+ |key, value| RmrkPropertyInfo {
+ key,
+ value
+ }
+ )?;
+
+ Ok(properties)
+ }
+
+ fn nft_resources(collection_id: RmrkCollectionId, nft_id: RmrkNftId) -> Result<Vec<RmrkResourceInfo>, DispatchError> {
+ use pallet_proxy_rmrk_core::{RmrkProperty, misc::{CollectionType, NftType, ResourceType}};
+ use pallet_common::CommonCollectionOperations;
+
+ let collection_id = match RmrkCore::unique_collection_id(collection_id) {
+ Ok(id) => id,
+ Err(_) => return Ok(Vec::new())
+ };
+ if RmrkCore::ensure_collection_type(collection_id, CollectionType::Regular).is_err() { return Ok(Vec::new()); }
+
+ let nft_id = TokenId(nft_id);
+ if RmrkCore::ensure_nft_type(collection_id, nft_id, NftType::Regular).is_err() { return Ok(Vec::new()); }
+
+ let res_collection_id: Option<CollectionId> = RmrkCore::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::ResourceCollection)?;
+
+ let res_collection_id = match res_collection_id {
+ Some(id) => id,
+ None => return Ok(Vec::new())
+ };
+
+ let resource_collection = RmrkCore::get_typed_nft_collection(res_collection_id, CollectionType::Resource)?;
+
+ let resources = resource_collection
+ .collection_tokens()
+ .iter()
+ .filter_map(|res_id| Some(RmrkResourceInfo {
+ id: res_id.0,
+ pending: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::PendingResourceAccept).ok()?,
+ pending_removal: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::PendingResourceRemoval).ok()?,
+ resource: match RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::ResourceType).ok()? {
+ ResourceType::Basic => RmrkResourceTypes::Basic(RmrkBasicResource {
+ src: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Src).ok()?,
+ metadata: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Metadata).ok()?,
+ license: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::License).ok()?,
+ thumb: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Thumb).ok()?,
+ }),
+ ResourceType::Composable => RmrkResourceTypes::Composable(RmrkComposableResource {
+ parts: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Parts).ok()?,
+ base: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Base).ok()?,
+ src: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Src).ok()?,
+ metadata: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Metadata).ok()?,
+ license: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::License).ok()?,
+ thumb: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Thumb).ok()?,
+ }),
+ ResourceType::Slot => RmrkResourceTypes::Slot(RmrkSlotResource {
+ base: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Base).ok()?,
+ src: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Src).ok()?,
+ metadata: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Metadata).ok()?,
+ slot: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Slot).ok()?,
+ license: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::License).ok()?,
+ thumb: RmrkCore::get_nft_property_decoded(res_collection_id, *res_id, RmrkProperty::Thumb).ok()?,
+ }),
+ },
+ }))
+ .collect();
+
+ Ok(resources)
+ }
+
+ fn nft_resource_priority(collection_id: RmrkCollectionId, nft_id: RmrkNftId, resource_id: RmrkResourceId) -> Result<Option<u32>, DispatchError> {
+ use pallet_proxy_rmrk_core::{RmrkProperty, misc::{CollectionType, NftType}};
+
+ let collection_id = match RmrkCore::unique_collection_id(collection_id) {
+ Ok(id) => id,
+ Err(_) => return Ok(None)
+ };
+ if RmrkCore::ensure_collection_type(collection_id, CollectionType::Regular).is_err() { return Ok(None); }
+
+ let nft_id = TokenId(nft_id);
+ if RmrkCore::ensure_nft_type(collection_id, nft_id, NftType::Regular).is_err() { return Ok(None); }
+
+ let priorities: Vec<_> = RmrkCore::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::ResourcePriorities)?;
+ Ok(
+ priorities.into_iter()
+ .enumerate()
+ .find(|(_, id)| *id == resource_id)
+ .map(|(priority, _): (usize, RmrkResourceId)| priority as u32)
+ )
+ }
+
+ fn base(base_id: RmrkBaseId) -> Result<Option<RmrkBaseInfo<AccountId>>, DispatchError> {
+ use pallet_proxy_rmrk_core::{
+ RmrkProperty, misc::{CollectionType},
+ };
+
+ let (collection, collection_id) = match RmrkCore::get_typed_nft_collection_mapped(base_id, CollectionType::Base) {
+ Ok(c) => c,
+ Err(_) => return Ok(None),
+ };
+
+ Ok(Some(RmrkBaseInfo {
+ issuer: collection.owner.clone(),
+ base_type: RmrkCore::get_collection_property_decoded(collection_id, RmrkProperty::BaseType)?,
+ symbol: RmrkCore::rebind(&collection.token_prefix)?,
+ }))
+ }
+
+ fn base_parts(base_id: RmrkBaseId) -> Result<Vec<RmrkPartType>, DispatchError> {
+ use pallet_proxy_rmrk_core::{RmrkProperty, misc::{CollectionType, NftType}};
+ use pallet_common::CommonCollectionOperations;
+
+ let (collection, collection_id) = match RmrkCore::get_typed_nft_collection_mapped(base_id, CollectionType::Base) {
+ Ok(c) => c,
+ Err(_) => return Ok(Vec::new()),
+ };
+
+ let parts = collection.collection_tokens()
+ .into_iter()
+ .filter_map(|token_id| {
+ let nft_type = RmrkCore::get_nft_type(collection_id, token_id).ok()?;
+
+ match nft_type {
+ NftType::FixedPart => Some(RmrkPartType::FixedPart(RmrkFixedPart {
+ id: RmrkCore::get_nft_property_decoded(collection_id, token_id, RmrkProperty::ExternalPartId).ok()?,
+ src: RmrkCore::get_nft_property_decoded(collection_id, token_id, RmrkProperty::Src).ok()?,
+ z: RmrkCore::get_nft_property_decoded(collection_id, token_id, RmrkProperty::ZIndex).ok()?,
+ })),
+ NftType::SlotPart => Some(RmrkPartType::SlotPart(RmrkSlotPart {
+ id: RmrkCore::get_nft_property_decoded(collection_id, token_id, RmrkProperty::ExternalPartId).ok()?,
+ src: RmrkCore::get_nft_property_decoded(collection_id, token_id, RmrkProperty::Src).ok()?,
+ z: RmrkCore::get_nft_property_decoded(collection_id, token_id, RmrkProperty::ZIndex).ok()?,
+ equippable: RmrkCore::get_nft_property_decoded(collection_id, token_id, RmrkProperty::EquippableList).ok()?,
+ })),
+ _ => None
+ }
+ })
+ .collect();
+
+ Ok(parts)
+ }
+
+ fn theme_names(base_id: RmrkBaseId) -> Result<Vec<RmrkThemeName>, DispatchError> {
+ use pallet_proxy_rmrk_core::{RmrkProperty, misc::{NftType, CollectionType}};
+ use pallet_common::CommonCollectionOperations;
+
+ let (collection, collection_id) = match RmrkCore::get_typed_nft_collection_mapped(base_id, CollectionType::Base) {
+ Ok(c) => c,
+ Err(_) => return Ok(Vec::new()),
+ };
+
+ let theme_names = collection.collection_tokens()
+ .iter()
+ .filter_map(|token_id| {
+ let nft_type = RmrkCore::get_nft_type(collection_id, *token_id).ok()?;
+
+ match nft_type {
+ NftType::Theme => RmrkCore::get_nft_property_decoded(collection_id, *token_id, RmrkProperty::ThemeName).ok(),
+ _ => None
+ }
+ })
+ .collect();
+
+ Ok(theme_names)
+ }
+
+ fn theme(base_id: RmrkBaseId, theme_name: RmrkThemeName, filter_keys: Option<Vec<RmrkPropertyKey>>) -> Result<Option<RmrkTheme>, DispatchError> {
+ use pallet_proxy_rmrk_core::{
+ RmrkProperty,
+ misc::{CollectionType, NftType}
+ };
+ use pallet_common::CommonCollectionOperations;
+
+ let (collection, collection_id) = match RmrkCore::get_typed_nft_collection_mapped(base_id, CollectionType::Base) {
+ Ok(c) => c,
+ Err(_) => return Ok(None),
+ };
+
+ let theme_info = collection.collection_tokens()
+ .into_iter()
+ .find_map(|token_id| {
+ RmrkCore::ensure_nft_type(collection_id, token_id, NftType::Theme).ok()?;
+
+ let name: RmrkString = RmrkCore::get_nft_property_decoded(
+ collection_id, token_id, RmrkProperty::ThemeName
+ ).ok()?;
+
+ if name == theme_name {
+ Some((name, token_id))
+ } else {
+ None
+ }
+ });
+
+ let (name, theme_id) = match theme_info {
+ Some((name, theme_id)) => (name, theme_id),
+ None => return Ok(None)
+ };
+
+ let properties = RmrkCore::filter_user_properties(
+ collection_id,
+ Some(theme_id),
+ filter_keys,
+ |key, value| RmrkThemeProperty {
+ key,
+ value
+ }
+ )?;
+
+ let inherit = RmrkCore::get_nft_property_decoded(
+ collection_id,
+ theme_id,
+ RmrkProperty::ThemeInherit
+ )?;
+
+ let theme = RmrkTheme {
+ name,
+ properties,
+ inherit,
+ };
+
+ Ok(Some(theme))
+ }
+ }
+}
struct CheckInherents;
runtime/unique/src/lib.rsdiffbeforeafterboth--- a/runtime/unique/src/lib.rs
+++ b/runtime/unique/src/lib.rs
@@ -74,10 +74,9 @@
CollectionStats, RpcCollection,
mapping::{EvmTokenAddressMapping, CrossTokenAddressMapping},
TokenChild, RmrkCollectionInfo, RmrkInstanceInfo, RmrkResourceInfo, RmrkPropertyInfo,
- RmrkBaseInfo, RmrkPartType, RmrkTheme, RmrkThemeName, RmrkThemeProperty, RmrkCollectionId,
- RmrkNftId, RmrkAccountIdOrCollectionNftTuple, RmrkNftChild, RmrkPropertyKey, RmrkResourceTypes,
- RmrkBasicResource, RmrkComposableResource, RmrkSlotResource, RmrkResourceId, RmrkBaseId,
- RmrkFixedPart, RmrkSlotPart, RmrkString,
+ RmrkBaseInfo, RmrkPartType, RmrkTheme, RmrkThemeName, RmrkCollectionId,
+ RmrkNftId, RmrkNftChild, RmrkPropertyKey,
+ RmrkResourceId, RmrkBaseId,
};
// use pallet_contracts::weights::WeightInfo;
@@ -914,15 +913,6 @@
}
impl pallet_nonfungible::Config for Runtime {
type WeightInfo = pallet_nonfungible::weights::SubstrateWeight<Self>;
-}
-
-impl pallet_proxy_rmrk_core::Config for Runtime {
- type WeightInfo = pallet_proxy_rmrk_core::weights::SubstrateWeight<Self>;
- type Event = Event;
-}
-
-impl pallet_proxy_rmrk_equip::Config for Runtime {
- type Event = Event;
}
impl pallet_unique::Config for Runtime {
@@ -1164,8 +1154,6 @@
Refungible: pallet_refungible::{Pallet, Storage} = 68,
Nonfungible: pallet_nonfungible::{Pallet, Storage} = 69,
Structure: pallet_structure::{Pallet, Call, Storage, Event<T>} = 70,
- RmrkCore: pallet_proxy_rmrk_core::{Pallet, Call, Storage, Event<T>} = 71,
- RmrkEquip: pallet_proxy_rmrk_equip::{Pallet, Call, Storage, Event<T>} = 72,
// Frontier
EVM: pallet_evm::{Pallet, Config, Call, Storage, Event<T>} = 100,
@@ -1313,7 +1301,73 @@
}};
}
-impl_common_runtime_apis!();
+impl_common_runtime_apis! {
+ #![custom_apis]
+
+ impl rmrk_rpc::RmrkApi<
+ Block,
+ AccountId,
+ RmrkCollectionInfo<AccountId>,
+ RmrkInstanceInfo<AccountId>,
+ RmrkResourceInfo,
+ RmrkPropertyInfo,
+ RmrkBaseInfo<AccountId>,
+ RmrkPartType,
+ RmrkTheme
+ > for Runtime {
+ fn last_collection_idx() -> Result<RmrkCollectionId, DispatchError> {
+ Ok(Default::default())
+ }
+
+ fn collection_by_id(_collection_id: RmrkCollectionId) -> Result<Option<RmrkCollectionInfo<AccountId>>, DispatchError> {
+ Ok(Default::default())
+ }
+
+ fn nft_by_id(_collection_id: RmrkCollectionId, _nft_by_id: RmrkNftId) -> Result<Option<RmrkInstanceInfo<AccountId>>, DispatchError> {
+ Ok(Default::default())
+ }
+
+ fn account_tokens(_account_id: AccountId, _collection_id: RmrkCollectionId) -> Result<Vec<RmrkNftId>, DispatchError> {
+ Ok(Default::default())
+ }
+
+ fn nft_children(_collection_id: RmrkCollectionId, _nft_id: RmrkNftId) -> Result<Vec<RmrkNftChild>, DispatchError> {
+ Ok(Default::default())
+ }
+
+ fn collection_properties(_collection_id: RmrkCollectionId, _filter_keys: Option<Vec<RmrkPropertyKey>>) -> Result<Vec<RmrkPropertyInfo>, DispatchError> {
+ Ok(Default::default())
+ }
+
+ fn nft_properties(_collection_id: RmrkCollectionId, _nft_id: RmrkNftId, _filter_keys: Option<Vec<RmrkPropertyKey>>) -> Result<Vec<RmrkPropertyInfo>, DispatchError> {
+ Ok(Default::default())
+ }
+
+ fn nft_resources(_collection_id: RmrkCollectionId, _nft_id: RmrkNftId) -> Result<Vec<RmrkResourceInfo>, DispatchError> {
+ Ok(Default::default())
+ }
+
+ fn nft_resource_priority(_collection_id: RmrkCollectionId, _nft_id: RmrkNftId, _resource_id: RmrkResourceId) -> Result<Option<u32>, DispatchError> {
+ Ok(Default::default())
+ }
+
+ fn base(_base_id: RmrkBaseId) -> Result<Option<RmrkBaseInfo<AccountId>>, DispatchError> {
+ Ok(Default::default())
+ }
+
+ fn base_parts(_base_id: RmrkBaseId) -> Result<Vec<RmrkPartType>, DispatchError> {
+ Ok(Default::default())
+ }
+
+ fn theme_names(_base_id: RmrkBaseId) -> Result<Vec<RmrkThemeName>, DispatchError> {
+ Ok(Default::default())
+ }
+
+ fn theme(_base_id: RmrkBaseId, _theme_name: RmrkThemeName, _filter_keys: Option<Vec<RmrkPropertyKey>>) -> Result<Option<RmrkTheme>, DispatchError> {
+ Ok(Default::default())
+ }
+ }
+}
struct CheckInherents;
tests/src/eth/nesting/nest.test.tsdiffbeforeafterboth--- a/tests/src/eth/nesting/nest.test.ts
+++ b/tests/src/eth/nesting/nest.test.ts
@@ -29,34 +29,37 @@
const {collectionId, contract} = await createNestingCollection(api, web3, owner);
// Create a token to be nested
- const nftTokenId = await contract.methods.nextTokenId().call();
+ const targetNFTTokenId = await contract.methods.nextTokenId().call();
await contract.methods.mint(
owner,
- nftTokenId,
+ targetNFTTokenId,
).send({from: owner});
- // Nest into a token
- const firstTargetNftTokenId = await contract.methods.nextTokenId().call();
+ const targetNftTokenAddress = tokenIdToAddress(collectionId, targetNFTTokenId);
+
+ // Create a nested token
+ const firstTokenId = await contract.methods.nextTokenId().call();
await contract.methods.mint(
- owner,
- firstTargetNftTokenId,
+ targetNftTokenAddress,
+ firstTokenId,
).send({from: owner});
-
- const targetNftTokenAddress = tokenIdToAddress(collectionId, firstTargetNftTokenId);
- await contract.methods.transfer(targetNftTokenAddress, nftTokenId).send({from: owner});
- expect(await contract.methods.ownerOf(nftTokenId).call()).to.be.equal(targetNftTokenAddress);
+ expect(await contract.methods.ownerOf(firstTokenId).call()).to.be.equal(targetNftTokenAddress);
- // Re-nest into another
- const secondTargetNftTokenId = await contract.methods.nextTokenId().call();
+ // Create a token to be nested and nest
+ const secondTokenId = await contract.methods.nextTokenId().call();
await contract.methods.mint(
owner,
- secondTargetNftTokenId,
+ secondTokenId,
).send({from: owner});
- const nextNftTokenAddress = tokenIdToAddress(collectionId, secondTargetNftTokenId);
- await contract.methods.transfer(nextNftTokenAddress, nftTokenId).send({from: owner});
- expect(await contract.methods.ownerOf(nftTokenId).call()).to.be.equal(nextNftTokenAddress);
+ await contract.methods.transfer(targetNftTokenAddress, secondTokenId).send({from: owner});
+
+ expect(await contract.methods.ownerOf(secondTokenId).call()).to.be.equal(targetNftTokenAddress);
+
+ // Unnest token back
+ await contract.methods.transferFrom(targetNftTokenAddress, owner, secondTokenId).send({from: owner});
+ expect(await contract.methods.ownerOf(secondTokenId).call()).to.be.equal(owner);
});
itWeb3('NFT: allows an Owner to nest/unnest their token (Restricted nesting)', async ({api, web3, privateKeyWrapper}) => {
@@ -125,7 +128,7 @@
.transfer(targetNftTokenAddress, nftTokenId)
.call({from: owner})).to.be.rejectedWith('UserIsNotAllowedToNest');
});
-
+
itWeb3('NFT: disallows a non-Owner to nest someone else\'s token', async ({api, web3, privateKeyWrapper}) => {
const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
const malignant = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
@@ -139,20 +142,20 @@
targetTokenId,
).send({from: owner});
const targetTokenAddress = tokenIdToAddress(collectionId, targetTokenId);
-
+
// Mint a token belonging to a different account
const tokenId = await contract.methods.nextTokenId().call();
await contract.methods.mint(
malignant,
tokenId,
).send({from: owner});
-
+
// Try to nest one token in another as a non-owner account
await expect(contract.methods
.transfer(targetTokenAddress, tokenId)
.call({from: malignant})).to.be.rejectedWith('UserIsNotAllowedToNest');
});
-
+
itWeb3('NFT: disallows a non-Owner to nest someone else\'s token (Restricted nesting)', async ({api, web3, privateKeyWrapper}) => {
const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
const malignant = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
@@ -182,7 +185,7 @@
.transfer(nftTokenAddressA, nftTokenIdB)
.call({from: malignant})).to.be.rejectedWith('UserIsNotAllowedToNest');
});
-
+
itWeb3('NFT: disallows to nest token in an unlisted collection', async ({api, web3, privateKeyWrapper}) => {
const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
tests/src/nesting/nest.test.tsdiffbeforeafterboth1import {expect} from 'chai';2import {tokenIdToAddress} from '../eth/util/helpers';3import usingApi, {executeTransaction} from '../substrate/substrate-api';4import {5 addCollectionAdminExpectSuccess,6 addToAllowListExpectSuccess,7 createCollectionExpectSuccess,8 createItemExpectSuccess,9 enableAllowListExpectSuccess,10 enablePublicMintingExpectSuccess,11 getTokenChildren,12 getTokenOwner,13 getTopmostTokenOwner,14 normalizeAccountId,15 setCollectionPermissionsExpectSuccess,16 transferExpectFailure,17 transferExpectSuccess,18 transferFromExpectSuccess,19 setCollectionLimitsExpectSuccess,20} from '../util/helpers';21import {IKeyringPair} from '@polkadot/types/types';2223let alice: IKeyringPair;24let bob: IKeyringPair;25let charlie: IKeyringPair;2627describe('Integration Test: Composite nesting tests', () => {28 before(async () => {29 await usingApi(async (_, privateKeyWrapper) => {30 alice = privateKeyWrapper('//Alice');31 bob = privateKeyWrapper('//Bob');32 });33 });3435 it('Performs the full suite: bundles a token, transfers, and unnests', async () => {36 await usingApi(async api => {37 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});38 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}});39 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');4041 // Create a nested token42 const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, targetToken)});43 expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: alice.address});44 expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});4546 // Create a token to be nested47 const newToken = await createItemExpectSuccess(alice, collection, 'NFT');4849 // Nest50 await transferExpectSuccess(collection, newToken, alice, {Ethereum: tokenIdToAddress(collection, targetToken)});51 expect(await getTopmostTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});52 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});5354 // Move bundle to different user55 await transferExpectSuccess(collection, targetToken, alice, {Substrate: bob.address});56 expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: bob.address});57 expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});5859 // Unnest60 await transferFromExpectSuccess(collection, newToken, bob, {Ethereum: tokenIdToAddress(collection, targetToken)}, {Substrate: bob.address});61 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: bob.address});62 });63 });6465 it('Transfers an already bundled token', async () => {66 await usingApi(async api => {67 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});68 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}});6970 const tokenA = await createItemExpectSuccess(alice, collection, 'NFT');71 const tokenB = await createItemExpectSuccess(alice, collection, 'NFT');7273 // Create a nested token74 const tokenC = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, tokenA)});75 expect(await getTopmostTokenOwner(api, collection, tokenC)).to.be.deep.equal({Substrate: alice.address});76 expect(await getTokenOwner(api, collection, tokenC)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, tokenA).toLowerCase()});7778 // Transfer the nested token to another token79 await expect(executeTransaction(80 api,81 alice,82 api.tx.unique.transferFrom(83 normalizeAccountId({Ethereum: tokenIdToAddress(collection, tokenA)}),84 normalizeAccountId({Ethereum: tokenIdToAddress(collection, tokenB)}),85 collection,86 tokenC,87 1,88 ),89 )).to.not.be.rejected;90 expect(await getTopmostTokenOwner(api, collection, tokenC)).to.be.deep.equal({Substrate: alice.address});91 expect(await getTokenOwner(api, collection, tokenC)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, tokenB).toLowerCase()});92 });93 });9495 it('Checks token children', async () => {96 await usingApi(async api => {97 const collectionA = await createCollectionExpectSuccess({mode: {type: 'NFT'}});98 await setCollectionLimitsExpectSuccess(alice, collectionA, {ownerCanTransfer: true});99 await setCollectionPermissionsExpectSuccess(alice, collectionA, {nesting: {tokenOwner: true}});100 const collectionB = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});101102 const targetToken = await createItemExpectSuccess(alice, collectionA, 'NFT');103 const targetAddress = {Ethereum: tokenIdToAddress(collectionA, targetToken)};104 let children = await getTokenChildren(api, collectionA, targetToken);105 expect(children.length).to.be.equal(0, 'Children length check at creation');106107 // Create a nested NFT token108 const tokenA = await createItemExpectSuccess(alice, collectionA, 'NFT', targetAddress);109 children = await getTokenChildren(api, collectionA, targetToken);110 expect(children.length).to.be.equal(1, 'Children length check at nesting #1');111 expect(children).to.have.deep.members([112 {token: tokenA, collection: collectionA},113 ], 'Children contents check at nesting #1');114115 // Create then nest116 const tokenB = await createItemExpectSuccess(alice, collectionA, 'NFT');117 await transferExpectSuccess(collectionA, tokenB, alice, targetAddress);118 children = await getTokenChildren(api, collectionA, targetToken);119 expect(children.length).to.be.equal(2, 'Children length check at nesting #2');120 expect(children).to.have.deep.members([121 {token: tokenA, collection: collectionA},122 {token: tokenB, collection: collectionA},123 ], 'Children contents check at nesting #2');124125 // Move token B to a different user outside the nesting tree126 await transferExpectSuccess(collectionA, tokenB, alice, bob);127 children = await getTokenChildren(api, collectionA, targetToken);128 expect(children.length).to.be.equal(1, 'Children length check at unnesting');129 expect(children).to.be.have.deep.members([130 {token: tokenA, collection: collectionA},131 ], 'Children contents check at unnesting');132133 // Create a fungible token in another collection and then nest134 const tokenC = await createItemExpectSuccess(alice, collectionB, 'Fungible');135 await transferExpectSuccess(collectionB, tokenC, alice, targetAddress, 1, 'Fungible');136 children = await getTokenChildren(api, collectionA, targetToken);137 expect(children.length).to.be.equal(2, 'Children length check at nesting #3 (from another collection)');138 expect(children).to.be.have.deep.members([139 {token: tokenA, collection: collectionA},140 {token: tokenC, collection: collectionB},141 ], 'Children contents check at nesting #3 (from another collection)');142143 // Move the fungible token inside token A deeper in the nesting tree144 await transferFromExpectSuccess(collectionB, tokenC, alice, targetAddress, {Ethereum: tokenIdToAddress(collectionA, tokenA)}, 1, 'Fungible');145 children = await getTokenChildren(api, collectionA, targetToken);146 expect(children.length).to.be.equal(1, 'Children length check at deeper nesting');147 expect(children).to.be.have.deep.members([148 {token: tokenA, collection: collectionA},149 ], 'Children contents check at deeper nesting');150 });151 });152});153154describe('Integration Test: Various token type nesting', async () => {155 before(async () => {156 await usingApi(async (_, privateKeyWrapper) => {157 alice = privateKeyWrapper('//Alice');158 bob = privateKeyWrapper('//Bob');159 charlie = privateKeyWrapper('//Charlie');160 });161 });162163 it('Admin (NFT): allows an Admin to nest a token', async () => {164 await usingApi(async api => {165 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});166 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {collectionAdmin: true}});167 await addCollectionAdminExpectSuccess(alice, collection, bob.address);168 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT', charlie.address);169170 // Create a nested token171 const nestedToken = await createItemExpectSuccess(bob, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, targetToken)});172 expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: charlie.address});173 expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});174175 // Create a token to be nested and nest176 const newToken = await createItemExpectSuccess(bob, collection, 'NFT');177 await transferExpectSuccess(collection, newToken, bob, {Ethereum: tokenIdToAddress(collection, targetToken)});178 expect(await getTopmostTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: charlie.address});179 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});180 });181 });182183 it('Admin (NFT): Admin and Token Owner can operate together', async () => {184 await usingApi(async api => {185 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});186 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true, collectionAdmin: true}});187 await addCollectionAdminExpectSuccess(alice, collection, bob.address);188 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT', charlie.address);189190 // Create a nested token by an administrator191 const nestedToken = await createItemExpectSuccess(bob, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, targetToken)});192 expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: charlie.address});193 expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});194195 // Create a token and allow the owner to nest too196 const newToken = await createItemExpectSuccess(alice, collection, 'NFT', charlie.address);197 await transferExpectSuccess(collection, newToken, charlie, {Ethereum: tokenIdToAddress(collection, nestedToken)});198 expect(await getTopmostTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: charlie.address});199 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, nestedToken).toLowerCase()});200 });201 });202203 it('Admin (NFT): allows an Admin to nest a token (Restricted nesting)', async () => {204 await usingApi(async api => {205 const collectionA = await createCollectionExpectSuccess({mode: {type: 'NFT'}});206 await addCollectionAdminExpectSuccess(alice, collectionA, bob.address);207 const collectionB = await createCollectionExpectSuccess({mode: {type: 'NFT'}});208 await addCollectionAdminExpectSuccess(alice, collectionB, bob.address);209 await setCollectionPermissionsExpectSuccess(alice, collectionA, {nesting: {collectionAdmin: true, restricted:[collectionA, collectionB]}});210 const targetToken = await createItemExpectSuccess(alice, collectionA, 'NFT', charlie.address);211212 // Create a nested token213 const nestedToken = await createItemExpectSuccess(bob, collectionB, 'NFT', {Ethereum: tokenIdToAddress(collectionA, targetToken)});214 expect(await getTopmostTokenOwner(api, collectionB, nestedToken)).to.be.deep.equal({Substrate: charlie.address});215 expect(await getTokenOwner(api, collectionB, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collectionA, targetToken).toLowerCase()});216217 // Create a token to be nested and nest218 const newToken = await createItemExpectSuccess(bob, collectionB, 'NFT');219 await transferExpectSuccess(collectionB, newToken, bob, {Ethereum: tokenIdToAddress(collectionA, targetToken)});220 expect(await getTopmostTokenOwner(api, collectionB, newToken)).to.be.deep.equal({Substrate: charlie.address});221 expect(await getTokenOwner(api, collectionB, newToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collectionA, targetToken).toLowerCase()});222 });223 });224225 // ---------- Non-Fungible ----------226227 it('NFT: allows an Owner to nest/unnest their token', async () => {228 await usingApi(async api => {229 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});230 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}});231 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');232233 // Create a nested token234 const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, targetToken)});235 expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: alice.address});236 expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});237238 // Create a token to be nested and nest239 const newToken = await createItemExpectSuccess(alice, collection, 'NFT');240 await transferExpectSuccess(collection, newToken, alice, {Ethereum: tokenIdToAddress(collection, targetToken)});241 expect(await getTopmostTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});242 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});243 });244 });245246 it('NFT: allows an Owner to nest/unnest their token (Restricted nesting)', async () => {247 await usingApi(async api => {248 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});249 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true, restricted:[collection]}});250 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');251252 // Create a nested token253 const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, targetToken)});254 expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: alice.address});255 expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});256257 // Create a token to be nested and nest258 const newToken = await createItemExpectSuccess(alice, collection, 'NFT');259 await transferExpectSuccess(collection, newToken, alice, {Ethereum: tokenIdToAddress(collection, targetToken)});260 expect(await getTopmostTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});261 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});262 });263 });264265 // ---------- Fungible ----------266267 it('Fungible: allows an Owner to nest/unnest their token', async () => {268 await usingApi(async api => {269 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});270 await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true}});271 const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT', {Substrate: alice.address});272 const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};273274 const collectionFT = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});275276 // Create a nested token277 await expect(executeTransaction(api, alice, api.tx.unique.createItem(278 collectionFT,279 targetAddress,280 {Fungible: {Value: 10}},281 ))).to.not.be.rejected;282283 // Nest a new token284 const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');285 await transferExpectSuccess(collectionFT, newToken, alice, targetAddress, 1, 'Fungible');286 });287 });288289 it('Fungible: allows an Owner to nest/unnest their token (Restricted nesting)', async () => {290 await usingApi(async api => {291 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});292 const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT', {Substrate: alice.address});293 const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};294295 const collectionFT = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});296297 await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true, restricted: [collectionFT]}});298299 // Create a nested token300 await expect(executeTransaction(api, alice, api.tx.unique.createItem(301 collectionFT,302 targetAddress,303 {Fungible: {Value: 10}},304 ))).to.not.be.rejected;305306 // Nest a new token307 const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');308 await transferExpectSuccess(collectionFT, newToken, alice, targetAddress, 1, 'Fungible');309 });310 });311312 // ---------- Re-Fungible ----------313314 it('ReFungible: allows an Owner to nest/unnest their token', async () => {315 await usingApi(async api => {316 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});317 await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true}});318 const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT', {Substrate: alice.address});319 const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};320321 const collectionRFT = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});322323 // Create a nested token324 await expect(executeTransaction(api, alice, api.tx.unique.createItem(325 collectionRFT,326 targetAddress,327 {ReFungible: {pieces: 100}},328 ))).to.not.be.rejected;329330 // Nest a new token331 const newToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');332 await transferExpectSuccess(collectionRFT, newToken, alice, targetAddress, 100, 'ReFungible');333 });334 });335336 it('ReFungible: allows an Owner to nest/unnest their token (Restricted nesting)', async () => {337 await usingApi(async api => {338 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});339 const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT', {Substrate: alice.address});340 const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};341342 const collectionRFT = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});343344 await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true, restricted:[collectionRFT]}});345346 // Create a nested token347 await expect(executeTransaction(api, alice, api.tx.unique.createItem(348 collectionRFT,349 targetAddress,350 {ReFungible: {pieces: 100}},351 ))).to.not.be.rejected;352353 // Nest a new token354 const newToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');355 await transferExpectSuccess(collectionRFT, newToken, alice, targetAddress, 100, 'ReFungible');356 });357 });358});359360describe('Negative Test: Nesting', async() => {361 before(async () => {362 await usingApi(async (_, privateKeyWrapper) => {363 alice = privateKeyWrapper('//Alice');364 bob = privateKeyWrapper('//Bob');365 });366 });367368 it('Disallows excessive token nesting', async () => {369 await usingApi(async api => {370 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});371 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}});372 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');373374 const maxNestingLevel = 5;375 let prevToken = targetToken;376377 // Create a nested-token matryoshka378 for (let i = 0; i < maxNestingLevel; i++) {379 const nestedToken = await createItemExpectSuccess(380 alice,381 collection,382 'NFT',383 {Ethereum: tokenIdToAddress(collection, prevToken)},384 );385386 prevToken = nestedToken;387 }388389 // The nesting depth is limited by `maxNestingLevel`390 await expect(executeTransaction(api, alice, api.tx.unique.createItem(391 collection,392 {Ethereum: tokenIdToAddress(collection, prevToken)},393 {nft: {}} as any,394 )), 'while creating nested token').to.be.rejectedWith(/^structure\.DepthLimit$/);395396 expect(await getTopmostTokenOwner(api, collection, prevToken)).to.be.deep.equal({Substrate: alice.address});397 });398 });399400 // ---------- Admin ------------401402 it('Admin (NFT): disallows an Admin to operate nesting when only TokenOwner is allowed', async () => {403 await usingApi(async api => {404 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});405 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}});406 await addCollectionAdminExpectSuccess(alice, collection, bob.address);407 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');408409 // Try to create a nested token as collection admin when it's disallowed410 await expect(executeTransaction(api, bob, api.tx.unique.createItem(411 collection,412 {Ethereum: tokenIdToAddress(collection, targetToken)},413 {nft: {}} as any,414 )), 'while creating nested token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);415416 // Try to create and nest a token in the wrong collection417 const newToken = await createItemExpectSuccess(bob, collection, 'NFT');418 await expect(executeTransaction(419 api, 420 bob, 421 api.tx.unique.transfer({Ethereum: tokenIdToAddress(collection, targetToken)}, collection, newToken, 1),422 ), 'while nesting new token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);423 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: bob.address});424 });425 });426427 it('Admin (NFT): disallows a Token Owner to operate nesting when only Admin is allowed', async () => {428 await usingApi(async api => {429 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});430 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {collectionAdmin: true}});431 await addToAllowListExpectSuccess(alice, collection, bob.address);432 await enableAllowListExpectSuccess(alice, collection);433 await enablePublicMintingExpectSuccess(alice, collection);434 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');435436 // Try to create a nested token as collection admin when it's disallowed437 await expect(executeTransaction(api, bob, api.tx.unique.createItem(438 collection,439 {Ethereum: tokenIdToAddress(collection, targetToken)},440 {nft: {}} as any,441 )), 'while creating nested token').to.be.rejectedWith(/common\.AddressNotInAllowlist/); 442443 // Try to create and nest a token in the wrong collection444 const newToken = await createItemExpectSuccess(bob, collection, 'NFT');445 await expect(executeTransaction(446 api, 447 bob, 448 api.tx.unique.transfer({Ethereum: tokenIdToAddress(collection, targetToken)}, collection, newToken, 1),449 ), 'while nesting new token').to.be.rejectedWith(/common\.AddressNotInAllowlist/);450 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: bob.address});451 });452 });453454 it('Admin (NFT): disallows an Admin to nest and unnest someone else\'s token', async () => {455 await usingApi(async api => {456 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});457 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {collectionAdmin: true}});458459 await addToAllowListExpectSuccess(alice, collection, bob.address);460 await enableAllowListExpectSuccess(alice, collection);461 await enablePublicMintingExpectSuccess(alice, collection);462463 // Create a token to attempt to be nested into464 const targetToken = await createItemExpectSuccess(bob, collection, 'NFT');465 const targetAddress = {Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()};466467 // Try to nest somebody else's token468 const newToken = await createItemExpectSuccess(bob, collection, 'NFT');469 await expect(executeTransaction(470 api, 471 alice, 472 api.tx.unique.transfer(targetAddress, collection, newToken, 1),473 ), 'while nesting another\'s token token').to.be.rejectedWith(/common\.AddressNotInAllowlist/);474 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: bob.address});475476 // Nest a token as admin and try to unnest it, now belonging to someone else477 const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', targetAddress);478 await expect(executeTransaction(479 api, 480 alice, 481 api.tx.unique.transferFrom(targetAddress, normalizeAccountId(alice), collection, nestedToken, 1),482 ), 'while unnesting another\'s token').to.be.rejectedWith(/common\.AddressNotInAllowlist/);483 expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal(targetAddress);484 expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: bob.address});485 });486 });487488 it('Admin (NFT): disallows an Admin to nest a token from an unlisted collection (Restricted nesting)', async () => {489 await usingApi(async api => {490 const collectionA = await createCollectionExpectSuccess({mode: {type: 'NFT'}});491 const collectionB = await createCollectionExpectSuccess({mode: {type: 'NFT'}});492 await setCollectionPermissionsExpectSuccess(alice, collectionA, {nesting: {collectionAdmin: true, restricted:[collectionA]}});493494 // Create a token to attempt to be nested into495 const targetToken = await createItemExpectSuccess(alice, collectionA, 'NFT');496497 // Try to create and nest a token in the wrong collection498 const newToken = await createItemExpectSuccess(alice, collectionB, 'NFT');499 await expect(executeTransaction(500 api, 501 alice, 502 api.tx.unique.transfer({Ethereum: tokenIdToAddress(collectionA, targetToken)}, collectionB, newToken, 1),503 ), 'while nesting a foreign token').to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);504 expect(await getTokenOwner(api, collectionB, newToken)).to.be.deep.equal({Substrate: alice.address});505 });506 });507508 // ---------- Non-Fungible ----------509510 it('NFT: disallows to nest token if nesting is disabled', async () => {511 await usingApi(async api => {512 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});513 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {}});514 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');515516 // Try to create a nested token517 await expect(executeTransaction(api, alice, api.tx.unique.createItem(518 collection,519 {Ethereum: tokenIdToAddress(collection, targetToken)},520 {nft: {}} as any,521 )), 'while creating nested token').to.be.rejectedWith(/^common\.UserIsNotAllowedToNest$/);522523 // Create a token to be nested524 const newToken = await createItemExpectSuccess(alice, collection, 'NFT');525 // Try to nest526 await expect(executeTransaction(api, alice, api.tx.unique.transfer({Ethereum: tokenIdToAddress(collection, targetToken)}, collection, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);527 expect(await getTopmostTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});528 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});529 });530 });531532 it('NFT: disallows a non-Owner to nest someone else\'s token', async () => {533 await usingApi(async api => {534 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});535 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}});536537 await addToAllowListExpectSuccess(alice, collection, bob.address);538 await enableAllowListExpectSuccess(alice, collection);539 await enablePublicMintingExpectSuccess(alice, collection);540541 // Create a token to attempt to be nested into542 const targetToken = await createItemExpectSuccess(bob, collection, 'NFT');543544 // Try to create a nested token in the wrong collection545 await expect(executeTransaction(api, alice, api.tx.unique.createItem(546 collection,547 {Ethereum: tokenIdToAddress(collection, targetToken)},548 {nft: {}} as any,549 )), 'while creating nested token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);550551 // Try to create and nest a token in the wrong collection552 const newToken = await createItemExpectSuccess(alice, collection, 'NFT');553 await expect(executeTransaction(api, alice, api.tx.unique.transfer({Ethereum: tokenIdToAddress(collection, targetToken)}, collection, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.AddressNotInAllowlist/);554 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});555 });556 });557558 it('NFT: disallows a non-Owner to nest someone else\'s token (Restricted nesting)', async () => {559 await usingApi(async api => {560 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});561 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true, restricted:[collection]}});562563 await addToAllowListExpectSuccess(alice, collection, bob.address);564 await enableAllowListExpectSuccess(alice, collection);565 await enablePublicMintingExpectSuccess(alice, collection);566567 // Create a token to attempt to be nested into568 const targetToken = await createItemExpectSuccess(bob, collection, 'NFT');569570 // Try to create a nested token in the wrong collection571 await expect(executeTransaction(api, alice, api.tx.unique.createItem(572 collection,573 {Ethereum: tokenIdToAddress(collection, targetToken)},574 {nft: {}} as any,575 )), 'while creating nested token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);576577 // Try to create and nest a token in the wrong collection578 const newToken = await createItemExpectSuccess(alice, collection, 'NFT');579 await expect(executeTransaction(api, alice, api.tx.unique.transfer({Ethereum: tokenIdToAddress(collection, targetToken)}, collection, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.AddressNotInAllowlist/);580 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});581 });582 });583584 it('NFT: disallows to nest token in an unlisted collection', async () => {585 await usingApi(async api => {586 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});587 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true, restricted:[]}});588589 // Create a token to attempt to be nested into590 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');591592 // Try to create a nested token in the wrong collection593 await expect(executeTransaction(api, alice, api.tx.unique.createItem(594 collection,595 {Ethereum: tokenIdToAddress(collection, targetToken)},596 {nft: {}} as any,597 )), 'while creating nested token').to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);598599 // Try to create and nest a token in the wrong collection600 const newToken = await createItemExpectSuccess(alice, collection, 'NFT');601 await expect(executeTransaction(api, alice, api.tx.unique.transfer({Ethereum: tokenIdToAddress(collection, targetToken)}, collection, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);602 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});603 });604 });605606 // ---------- Fungible ----------607608 it('Fungible: disallows to nest token if nesting is disabled', async () => {609 await usingApi(async api => {610 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});611 await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {}});612 const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT');613 const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};614615 const collectionFT = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});616617 // Try to create a nested token618 await expect(executeTransaction(api, alice, api.tx.unique.createItem(619 collectionFT,620 targetAddress,621 {Fungible: {Value: 10}},622 )), 'while creating nested token').to.be.rejectedWith(/^common\.UserIsNotAllowedToNest$/);623624 // Create a token to be nested625 const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');626 // Try to nest627 await expect(executeTransaction(api, alice, api.tx.unique.transfer(targetAddress, collectionFT, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);628629 // Create another token to be nested630 const newToken2 = await createItemExpectSuccess(alice, collectionFT, 'Fungible');631 // Try to nest inside a fungible token632 await expect(executeTransaction(api, alice, api.tx.unique.transfer({Ethereum: tokenIdToAddress(collectionFT, newToken)}, collectionFT, newToken2, 1)), 'while nesting new token inside fungible').to.be.rejectedWith(/fungible\.FungibleDisallowsNesting/);633 });634 });635636 it('Fungible: disallows a non-Owner to nest someone else\'s token', async () => {637 await usingApi(async api => {638 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});639 await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true}});640641 await addToAllowListExpectSuccess(alice, collectionNFT, bob.address);642 await enableAllowListExpectSuccess(alice, collectionNFT);643 await enablePublicMintingExpectSuccess(alice, collectionNFT);644645 // Create a token to attempt to be nested into646 const targetToken = await createItemExpectSuccess(bob, collectionNFT, 'NFT');647 const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};648649 const collectionFT = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});650651 // Try to create a nested token in the wrong collection652 await expect(executeTransaction(api, alice, api.tx.unique.createItem(653 collectionFT,654 targetAddress,655 {Fungible: {Value: 10}},656 )), 'while creating nested token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);657658 // Try to create and nest a token in the wrong collection659 const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');660 await expect(executeTransaction(api, alice, api.tx.unique.transfer(targetAddress, collectionFT, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);661 });662 });663664 it('Fungible: disallows a non-Owner to nest someone else\'s token (Restricted nesting)', async () => {665 await usingApi(async api => {666 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});667 await addToAllowListExpectSuccess(alice, collectionNFT, bob.address);668 await enableAllowListExpectSuccess(alice, collectionNFT);669 await enablePublicMintingExpectSuccess(alice, collectionNFT);670671 // Create a token to attempt to be nested into672 const targetToken = await createItemExpectSuccess(bob, collectionNFT, 'NFT');673 const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};674675 const collectionFT = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});676 await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true, restricted:[collectionFT]}});677678 // Try to create a nested token in the wrong collection679 await expect(executeTransaction(api, alice, api.tx.unique.createItem(680 collectionFT,681 targetAddress,682 {Fungible: {Value: 10}},683 )), 'while creating nested token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);684685 // Try to create and nest a token in the wrong collection686 const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');687 await expect(executeTransaction(api, alice, api.tx.unique.transfer(targetAddress, collectionFT, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);688 });689 });690691 it('Fungible: disallows to nest token in an unlisted collection', async () => {692 await usingApi(async api => {693 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});694 await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true, restricted:[]}});695696 // Create a token to attempt to be nested into697 const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT');698 const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};699700 const collectionFT = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});701702 // Try to create a nested token in the wrong collection703 await expect(executeTransaction(api, alice, api.tx.unique.createItem(704 collectionFT,705 targetAddress,706 {Fungible: {Value: 10}},707 )), 'while creating a nested token').to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);708709 // Try to create and nest a token in the wrong collection710 const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');711 await expect(executeTransaction(api, alice, api.tx.unique.transfer(targetAddress, collectionFT, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);712 });713 });714715 // ---------- Re-Fungible ----------716717 it('ReFungible: disallows to nest token if nesting is disabled', async () => {718 await usingApi(async api => {719 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});720 await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {}});721 const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT');722 const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};723724 const collectionRFT = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});725726 // Create a nested token727 await expect(executeTransaction(api, alice, api.tx.unique.createItem(728 collectionRFT,729 targetAddress,730 {ReFungible: {pieces: 100}},731 )), 'while creating a nested token').to.be.rejectedWith(/^common\.UserIsNotAllowedToNest$/);732733 // Create a token to be nested734 const newToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');735 // Try to nest736 await transferExpectFailure(collectionRFT, newToken, alice, targetAddress, 100);737 // Try to nest738 await expect(executeTransaction(api, alice, api.tx.unique.transfer(targetAddress, collectionRFT, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);739740 // Create another token to be nested741 const newToken2 = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');742 // Try to nest inside a fungible token743 await expect(executeTransaction(api, alice, api.tx.unique.transfer({Ethereum: tokenIdToAddress(collectionRFT, newToken)}, collectionRFT, newToken2, 1)), 'while nesting new token inside refungible').to.be.rejectedWith(/refungible\.RefungibleDisallowsNesting/);744 });745 });746747 it('ReFungible: disallows a non-Owner to nest someone else\'s token', async () => {748 await usingApi(async api => {749 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});750 await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true}});751752 await addToAllowListExpectSuccess(alice, collectionNFT, bob.address);753 await enableAllowListExpectSuccess(alice, collectionNFT);754 await enablePublicMintingExpectSuccess(alice, collectionNFT);755756 // Create a token to attempt to be nested into757 const targetToken = await createItemExpectSuccess(bob, collectionNFT, 'NFT');758 const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};759760 const collectionRFT = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});761762 // Try to create a nested token in the wrong collection763 await expect(executeTransaction(api, alice, api.tx.unique.createItem(764 collectionRFT,765 targetAddress,766 {ReFungible: {pieces: 100}},767 )), 'while creating a nested token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);768769 // Try to create and nest a token in the wrong collection770 const newToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');771 await expect(executeTransaction(api, alice, api.tx.unique.transfer(targetAddress, collectionRFT, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);772 });773 });774775 it('ReFungible: disallows a non-Owner to nest someone else\'s token (Restricted nesting)', async () => {776 await usingApi(async api => {777 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});778 await addToAllowListExpectSuccess(alice, collectionNFT, bob.address);779 await enableAllowListExpectSuccess(alice, collectionNFT);780 await enablePublicMintingExpectSuccess(alice, collectionNFT);781782 // Create a token to attempt to be nested into783 const targetToken = await createItemExpectSuccess(bob, collectionNFT, 'NFT');784 const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};785786 const collectionRFT = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});787 await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true, restricted:[collectionRFT]}});788789 // Try to create a nested token in the wrong collection790 await expect(executeTransaction(api, alice, api.tx.unique.createItem(791 collectionRFT,792 targetAddress,793 {ReFungible: {pieces: 100}},794 )), 'while creating a nested token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);795796 // Try to create and nest a token in the wrong collection797 const newToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');798 await expect(executeTransaction(api, alice, api.tx.unique.transfer(targetAddress, collectionRFT, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);799 });800 });801802 it('ReFungible: disallows to nest token to an unlisted collection', async () => {803 await usingApi(async api => {804 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});805 await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true, restricted:[]}});806807 // Create a token to attempt to be nested into808 const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT');809 const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};810811 const collectionRFT = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});812813 // Try to create a nested token in the wrong collection814 await expect(executeTransaction(api, alice, api.tx.unique.createItem(815 collectionRFT,816 targetAddress,817 {ReFungible: {pieces: 100}},818 )), 'while creating a nested token').to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);819820 // Try to create and nest a token in the wrong collection821 const newToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');822 await expect(executeTransaction(api, alice, api.tx.unique.transfer(targetAddress, collectionRFT, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);823 });824 });825});1import {expect} from 'chai';2import {tokenIdToAddress} from '../eth/util/helpers';3import usingApi, {executeTransaction} from '../substrate/substrate-api';4import {5 addCollectionAdminExpectSuccess,6 addToAllowListExpectSuccess,7 createCollectionExpectSuccess,8 createItemExpectSuccess,9 enableAllowListExpectSuccess,10 enablePublicMintingExpectSuccess,11 getTokenChildren,12 getTokenOwner,13 getTopmostTokenOwner,14 normalizeAccountId,15 setCollectionPermissionsExpectSuccess,16 transferExpectFailure,17 transferExpectSuccess,18 transferFromExpectSuccess,19 setCollectionLimitsExpectSuccess,20} from '../util/helpers';21import {IKeyringPair} from '@polkadot/types/types';2223let alice: IKeyringPair;24let bob: IKeyringPair;25let charlie: IKeyringPair;2627describe('Integration Test: Composite nesting tests', () => {28 before(async () => {29 await usingApi(async (_, privateKeyWrapper) => {30 alice = privateKeyWrapper('//Alice');31 bob = privateKeyWrapper('//Bob');32 });33 });3435 it('Performs the full suite: bundles a token, transfers, and unnests', async () => {36 await usingApi(async api => {37 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});38 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}});39 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');4041 // Create a nested token42 const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, targetToken)});43 expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: alice.address});44 expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});4546 // Create a token to be nested47 const newToken = await createItemExpectSuccess(alice, collection, 'NFT');4849 // Nest50 await transferExpectSuccess(collection, newToken, alice, {Ethereum: tokenIdToAddress(collection, targetToken)});51 expect(await getTopmostTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});52 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});5354 // Move bundle to different user55 await transferExpectSuccess(collection, targetToken, alice, {Substrate: bob.address});56 expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: bob.address});57 expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});5859 // Unnest60 await transferFromExpectSuccess(collection, newToken, bob, {Ethereum: tokenIdToAddress(collection, targetToken)}, {Substrate: bob.address});61 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: bob.address});62 });63 });6465 it('Transfers an already bundled token', async () => {66 await usingApi(async api => {67 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});68 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}});6970 const tokenA = await createItemExpectSuccess(alice, collection, 'NFT');71 const tokenB = await createItemExpectSuccess(alice, collection, 'NFT');7273 // Create a nested token74 const tokenC = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, tokenA)});75 expect(await getTopmostTokenOwner(api, collection, tokenC)).to.be.deep.equal({Substrate: alice.address});76 expect(await getTokenOwner(api, collection, tokenC)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, tokenA).toLowerCase()});7778 // Transfer the nested token to another token79 await expect(executeTransaction(80 api,81 alice,82 api.tx.unique.transferFrom(83 normalizeAccountId({Ethereum: tokenIdToAddress(collection, tokenA)}),84 normalizeAccountId({Ethereum: tokenIdToAddress(collection, tokenB)}),85 collection,86 tokenC,87 1,88 ),89 )).to.not.be.rejected;90 expect(await getTopmostTokenOwner(api, collection, tokenC)).to.be.deep.equal({Substrate: alice.address});91 expect(await getTokenOwner(api, collection, tokenC)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, tokenB).toLowerCase()});92 });93 });9495 it('Checks token children', async () => {96 await usingApi(async api => {97 const collectionA = await createCollectionExpectSuccess({mode: {type: 'NFT'}});98 await setCollectionLimitsExpectSuccess(alice, collectionA, {ownerCanTransfer: true});99 await setCollectionPermissionsExpectSuccess(alice, collectionA, {nesting: {tokenOwner: true}});100 const collectionB = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});101102 const targetToken = await createItemExpectSuccess(alice, collectionA, 'NFT');103 const targetAddress = {Ethereum: tokenIdToAddress(collectionA, targetToken)};104 let children = await getTokenChildren(api, collectionA, targetToken);105 expect(children.length).to.be.equal(0, 'Children length check at creation');106107 // Create a nested NFT token108 const tokenA = await createItemExpectSuccess(alice, collectionA, 'NFT', targetAddress);109 children = await getTokenChildren(api, collectionA, targetToken);110 expect(children.length).to.be.equal(1, 'Children length check at nesting #1');111 expect(children).to.have.deep.members([112 {token: tokenA, collection: collectionA},113 ], 'Children contents check at nesting #1');114115 // Create then nest116 const tokenB = await createItemExpectSuccess(alice, collectionA, 'NFT');117 await transferExpectSuccess(collectionA, tokenB, alice, targetAddress);118 children = await getTokenChildren(api, collectionA, targetToken);119 expect(children.length).to.be.equal(2, 'Children length check at nesting #2');120 expect(children).to.have.deep.members([121 {token: tokenA, collection: collectionA},122 {token: tokenB, collection: collectionA},123 ], 'Children contents check at nesting #2');124125 // Move token B to a different user outside the nesting tree126 await transferExpectSuccess(collectionA, tokenB, alice, bob);127 children = await getTokenChildren(api, collectionA, targetToken);128 expect(children.length).to.be.equal(1, 'Children length check at unnesting');129 expect(children).to.be.have.deep.members([130 {token: tokenA, collection: collectionA},131 ], 'Children contents check at unnesting');132133 // Create a fungible token in another collection and then nest134 const tokenC = await createItemExpectSuccess(alice, collectionB, 'Fungible');135 await transferExpectSuccess(collectionB, tokenC, alice, targetAddress, 1, 'Fungible');136 children = await getTokenChildren(api, collectionA, targetToken);137 expect(children.length).to.be.equal(2, 'Children length check at nesting #3 (from another collection)');138 expect(children).to.be.have.deep.members([139 {token: tokenA, collection: collectionA},140 {token: tokenC, collection: collectionB},141 ], 'Children contents check at nesting #3 (from another collection)');142143 // Move the fungible token inside token A deeper in the nesting tree144 await transferFromExpectSuccess(collectionB, tokenC, alice, targetAddress, {Ethereum: tokenIdToAddress(collectionA, tokenA)}, 1, 'Fungible');145 children = await getTokenChildren(api, collectionA, targetToken);146 expect(children.length).to.be.equal(1, 'Children length check at deeper nesting');147 expect(children).to.be.have.deep.members([148 {token: tokenA, collection: collectionA},149 ], 'Children contents check at deeper nesting');150 });151 });152});153154describe('Integration Test: Various token type nesting', async () => {155 before(async () => {156 await usingApi(async (_, privateKeyWrapper) => {157 alice = privateKeyWrapper('//Alice');158 bob = privateKeyWrapper('//Bob');159 charlie = privateKeyWrapper('//Charlie');160 });161 });162163 it('Admin (NFT): allows an Admin to nest a token', async () => {164 await usingApi(async api => {165 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});166 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {collectionAdmin: true}});167 await addCollectionAdminExpectSuccess(alice, collection, bob.address);168 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT', charlie.address);169170 // Create a nested token171 const nestedToken = await createItemExpectSuccess(bob, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, targetToken)});172 expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: charlie.address});173 expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});174175 // Create a token to be nested and nest176 const newToken = await createItemExpectSuccess(bob, collection, 'NFT');177 await transferExpectSuccess(collection, newToken, bob, {Ethereum: tokenIdToAddress(collection, targetToken)});178 expect(await getTopmostTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: charlie.address});179 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});180 });181 });182183 it('Admin (NFT): Admin and Token Owner can operate together', async () => {184 await usingApi(async api => {185 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});186 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true, collectionAdmin: true}});187 await addCollectionAdminExpectSuccess(alice, collection, bob.address);188 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT', charlie.address);189190 // Create a nested token by an administrator191 const nestedToken = await createItemExpectSuccess(bob, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, targetToken)});192 expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: charlie.address});193 expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});194195 // Create a token and allow the owner to nest too196 const newToken = await createItemExpectSuccess(alice, collection, 'NFT', charlie.address);197 await transferExpectSuccess(collection, newToken, charlie, {Ethereum: tokenIdToAddress(collection, nestedToken)});198 expect(await getTopmostTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: charlie.address});199 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, nestedToken).toLowerCase()});200 });201 });202203 it('Admin (NFT): allows an Admin to nest a token (Restricted nesting)', async () => {204 await usingApi(async api => {205 const collectionA = await createCollectionExpectSuccess({mode: {type: 'NFT'}});206 await addCollectionAdminExpectSuccess(alice, collectionA, bob.address);207 const collectionB = await createCollectionExpectSuccess({mode: {type: 'NFT'}});208 await addCollectionAdminExpectSuccess(alice, collectionB, bob.address);209 await setCollectionPermissionsExpectSuccess(alice, collectionA, {nesting: {collectionAdmin: true, restricted:[collectionA, collectionB]}});210 const targetToken = await createItemExpectSuccess(alice, collectionA, 'NFT', charlie.address);211212 // Create a nested token213 const nestedToken = await createItemExpectSuccess(bob, collectionB, 'NFT', {Ethereum: tokenIdToAddress(collectionA, targetToken)});214 expect(await getTopmostTokenOwner(api, collectionB, nestedToken)).to.be.deep.equal({Substrate: charlie.address});215 expect(await getTokenOwner(api, collectionB, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collectionA, targetToken).toLowerCase()});216217 // Create a token to be nested and nest218 const newToken = await createItemExpectSuccess(bob, collectionB, 'NFT');219 await transferExpectSuccess(collectionB, newToken, bob, {Ethereum: tokenIdToAddress(collectionA, targetToken)});220 expect(await getTopmostTokenOwner(api, collectionB, newToken)).to.be.deep.equal({Substrate: charlie.address});221 expect(await getTokenOwner(api, collectionB, newToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collectionA, targetToken).toLowerCase()});222 });223 });224225 // ---------- Non-Fungible ----------226227 it('NFT: allows an Owner to nest/unnest their token', async () => {228 await usingApi(async api => {229 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});230 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}});231 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');232233 // Create a nested token234 const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, targetToken)});235 expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: alice.address});236 expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});237238 // Create a token to be nested and nest239 const newToken = await createItemExpectSuccess(alice, collection, 'NFT');240 await transferExpectSuccess(collection, newToken, alice, {Ethereum: tokenIdToAddress(collection, targetToken)});241 expect(await getTopmostTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});242 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});243 });244 });245246 it('NFT: allows an Owner to nest/unnest their token (Restricted nesting)', async () => {247 await usingApi(async api => {248 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});249 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true, restricted:[collection]}});250 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');251252 // Create a nested token253 const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, targetToken)});254 expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: alice.address});255 expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});256257 // Create a token to be nested and nest258 const newToken = await createItemExpectSuccess(alice, collection, 'NFT');259 await transferExpectSuccess(collection, newToken, alice, {Ethereum: tokenIdToAddress(collection, targetToken)});260 expect(await getTopmostTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});261 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});262 });263 });264265 // ---------- Fungible ----------266267 it('Fungible: allows an Owner to nest/unnest their token', async () => {268 await usingApi(async api => {269 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});270 await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true}});271 const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT', {Substrate: alice.address});272 const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};273274 const collectionFT = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});275276 // Create a nested token277 await expect(executeTransaction(api, alice, api.tx.unique.createItem(278 collectionFT,279 targetAddress,280 {Fungible: {Value: 10}},281 ))).to.not.be.rejected;282283 // Nest a new token284 const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');285 await transferExpectSuccess(collectionFT, newToken, alice, targetAddress, 1, 'Fungible');286 });287 });288289 it('Fungible: allows an Owner to nest/unnest their token (Restricted nesting)', async () => {290 await usingApi(async api => {291 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});292 const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT', {Substrate: alice.address});293 const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};294295 const collectionFT = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});296297 await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true, restricted: [collectionFT]}});298299 // Create a nested token300 await expect(executeTransaction(api, alice, api.tx.unique.createItem(301 collectionFT,302 targetAddress,303 {Fungible: {Value: 10}},304 ))).to.not.be.rejected;305306 // Nest a new token307 const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');308 await transferExpectSuccess(collectionFT, newToken, alice, targetAddress, 1, 'Fungible');309 });310 });311312 // ---------- Re-Fungible ----------313314 it('ReFungible: allows an Owner to nest/unnest their token', async () => {315 await usingApi(async api => {316 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});317 await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true}});318 const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT', {Substrate: alice.address});319 const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};320321 const collectionRFT = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});322323 // Create a nested token324 await expect(executeTransaction(api, alice, api.tx.unique.createItem(325 collectionRFT,326 targetAddress,327 {ReFungible: {pieces: 100}},328 ))).to.not.be.rejected;329330 // Nest a new token331 const newToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');332 await transferExpectSuccess(collectionRFT, newToken, alice, targetAddress, 100, 'ReFungible');333 });334 });335336 it('ReFungible: allows an Owner to nest/unnest their token (Restricted nesting)', async () => {337 await usingApi(async api => {338 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});339 const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT', {Substrate: alice.address});340 const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};341342 const collectionRFT = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});343344 await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true, restricted:[collectionRFT]}});345346 // Create a nested token347 await expect(executeTransaction(api, alice, api.tx.unique.createItem(348 collectionRFT,349 targetAddress,350 {ReFungible: {pieces: 100}},351 ))).to.not.be.rejected;352353 // Nest a new token354 const newToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');355 await transferExpectSuccess(collectionRFT, newToken, alice, targetAddress, 100, 'ReFungible');356 });357 });358});359360describe('Negative Test: Nesting', async() => {361 before(async () => {362 await usingApi(async (_, privateKeyWrapper) => {363 alice = privateKeyWrapper('//Alice');364 bob = privateKeyWrapper('//Bob');365 });366 });367368 it('Disallows excessive token nesting', async () => {369 await usingApi(async api => {370 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});371 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}});372 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');373374 const maxNestingLevel = 5;375 let prevToken = targetToken;376377 // Create a nested-token matryoshka378 for (let i = 0; i < maxNestingLevel; i++) {379 const nestedToken = await createItemExpectSuccess(380 alice,381 collection,382 'NFT',383 {Ethereum: tokenIdToAddress(collection, prevToken)},384 );385386 prevToken = nestedToken;387 }388389 // The nesting depth is limited by `maxNestingLevel`390 await expect(executeTransaction(api, alice, api.tx.unique.createItem(391 collection,392 {Ethereum: tokenIdToAddress(collection, prevToken)},393 {nft: {}} as any,394 )), 'while creating nested token').to.be.rejectedWith(/^structure\.DepthLimit$/);395396 expect(await getTopmostTokenOwner(api, collection, prevToken)).to.be.deep.equal({Substrate: alice.address});397 });398 });399400 // ---------- Admin ------------401402 it('Admin (NFT): disallows an Admin to operate nesting when only TokenOwner is allowed', async () => {403 await usingApi(async api => {404 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});405 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}});406 await addCollectionAdminExpectSuccess(alice, collection, bob.address);407 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');408409 // Try to create a nested token as collection admin when it's disallowed410 await expect(executeTransaction(api, bob, api.tx.unique.createItem(411 collection,412 {Ethereum: tokenIdToAddress(collection, targetToken)},413 {nft: {}} as any,414 )), 'while creating nested token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);415416 // Try to create and nest a token in the wrong collection417 const newToken = await createItemExpectSuccess(bob, collection, 'NFT');418 await expect(executeTransaction(419 api, 420 bob, 421 api.tx.unique.transfer({Ethereum: tokenIdToAddress(collection, targetToken)}, collection, newToken, 1),422 ), 'while nesting new token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);423 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: bob.address});424 });425 });426427 it('Admin (NFT): disallows a Token Owner to operate nesting when only Admin is allowed', async () => {428 await usingApi(async api => {429 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});430 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {collectionAdmin: true}});431 await addToAllowListExpectSuccess(alice, collection, bob.address);432 await enableAllowListExpectSuccess(alice, collection);433 await enablePublicMintingExpectSuccess(alice, collection);434 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');435436 // Try to create a nested token as collection admin when it's disallowed437 await expect(executeTransaction(api, bob, api.tx.unique.createItem(438 collection,439 {Ethereum: tokenIdToAddress(collection, targetToken)},440 {nft: {}} as any,441 )), 'while creating nested token').to.be.rejectedWith(/common\.AddressNotInAllowlist/); 442443 // Try to create and nest a token in the wrong collection444 const newToken = await createItemExpectSuccess(bob, collection, 'NFT');445 await expect(executeTransaction(446 api, 447 bob, 448 api.tx.unique.transfer({Ethereum: tokenIdToAddress(collection, targetToken)}, collection, newToken, 1),449 ), 'while nesting new token').to.be.rejectedWith(/common\.AddressNotInAllowlist/);450 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: bob.address});451 });452 });453454 it('Admin (NFT): disallows an Admin to nest and unnest someone else\'s token', async () => {455 await usingApi(async api => {456 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});457 await setCollectionLimitsExpectSuccess(alice, collection, {ownerCanTransfer: true});458 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {collectionAdmin: true}});459460 await addToAllowListExpectSuccess(alice, collection, bob.address);461 await enableAllowListExpectSuccess(alice, collection);462 await enablePublicMintingExpectSuccess(alice, collection);463464 // Create a token to attempt to be nested into465 const targetToken = await createItemExpectSuccess(bob, collection, 'NFT');466 const targetAddress = {Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()};467468 // Try to nest somebody else's token469 const newToken = await createItemExpectSuccess(bob, collection, 'NFT');470 await expect(executeTransaction(471 api, 472 alice, 473 api.tx.unique.transfer(targetAddress, collection, newToken, 1),474 ), 'while nesting another\'s token token').to.be.rejectedWith(/common\.AddressNotInAllowlist/);475 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: bob.address});476477 // Nest a token as admin and try to unnest it, now belonging to someone else478 const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', targetAddress);479 await expect(executeTransaction(480 api, 481 alice, 482 api.tx.unique.transferFrom(targetAddress, normalizeAccountId(alice), collection, nestedToken, 1),483 ), 'while unnesting another\'s token').to.be.rejectedWith(/common\.AddressNotInAllowlist/);484 expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal(targetAddress);485 expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: bob.address});486 });487 });488489 it('Admin (NFT): disallows an Admin to nest a token from an unlisted collection (Restricted nesting)', async () => {490 await usingApi(async api => {491 const collectionA = await createCollectionExpectSuccess({mode: {type: 'NFT'}});492 const collectionB = await createCollectionExpectSuccess({mode: {type: 'NFT'}});493 await setCollectionPermissionsExpectSuccess(alice, collectionA, {nesting: {collectionAdmin: true, restricted:[collectionA]}});494495 // Create a token to attempt to be nested into496 const targetToken = await createItemExpectSuccess(alice, collectionA, 'NFT');497498 // Try to create and nest a token in the wrong collection499 const newToken = await createItemExpectSuccess(alice, collectionB, 'NFT');500 await expect(executeTransaction(501 api, 502 alice, 503 api.tx.unique.transfer({Ethereum: tokenIdToAddress(collectionA, targetToken)}, collectionB, newToken, 1),504 ), 'while nesting a foreign token').to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);505 expect(await getTokenOwner(api, collectionB, newToken)).to.be.deep.equal({Substrate: alice.address});506 });507 });508509 // ---------- Non-Fungible ----------510511 it('NFT: disallows to nest token if nesting is disabled', async () => {512 await usingApi(async api => {513 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});514 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {}});515 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');516517 // Try to create a nested token518 await expect(executeTransaction(api, alice, api.tx.unique.createItem(519 collection,520 {Ethereum: tokenIdToAddress(collection, targetToken)},521 {nft: {}} as any,522 )), 'while creating nested token').to.be.rejectedWith(/^common\.UserIsNotAllowedToNest$/);523524 // Create a token to be nested525 const newToken = await createItemExpectSuccess(alice, collection, 'NFT');526 // Try to nest527 await expect(executeTransaction(api, alice, api.tx.unique.transfer({Ethereum: tokenIdToAddress(collection, targetToken)}, collection, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);528 expect(await getTopmostTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});529 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});530 });531 });532533 it('NFT: disallows a non-Owner to nest someone else\'s token', async () => {534 await usingApi(async api => {535 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});536 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}});537538 await addToAllowListExpectSuccess(alice, collection, bob.address);539 await enableAllowListExpectSuccess(alice, collection);540 await enablePublicMintingExpectSuccess(alice, collection);541542 // Create a token to attempt to be nested into543 const targetToken = await createItemExpectSuccess(bob, collection, 'NFT');544545 // Try to create a nested token in the wrong collection546 await expect(executeTransaction(api, alice, api.tx.unique.createItem(547 collection,548 {Ethereum: tokenIdToAddress(collection, targetToken)},549 {nft: {}} as any,550 )), 'while creating nested token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);551552 // Try to create and nest a token in the wrong collection553 const newToken = await createItemExpectSuccess(alice, collection, 'NFT');554 await expect(executeTransaction(api, alice, api.tx.unique.transfer({Ethereum: tokenIdToAddress(collection, targetToken)}, collection, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.AddressNotInAllowlist/);555 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});556 });557 });558559 it('NFT: disallows a non-Owner to nest someone else\'s token (Restricted nesting)', async () => {560 await usingApi(async api => {561 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});562 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true, restricted:[collection]}});563564 await addToAllowListExpectSuccess(alice, collection, bob.address);565 await enableAllowListExpectSuccess(alice, collection);566 await enablePublicMintingExpectSuccess(alice, collection);567568 // Create a token to attempt to be nested into569 const targetToken = await createItemExpectSuccess(bob, collection, 'NFT');570571 // Try to create a nested token in the wrong collection572 await expect(executeTransaction(api, alice, api.tx.unique.createItem(573 collection,574 {Ethereum: tokenIdToAddress(collection, targetToken)},575 {nft: {}} as any,576 )), 'while creating nested token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);577578 // Try to create and nest a token in the wrong collection579 const newToken = await createItemExpectSuccess(alice, collection, 'NFT');580 await expect(executeTransaction(api, alice, api.tx.unique.transfer({Ethereum: tokenIdToAddress(collection, targetToken)}, collection, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.AddressNotInAllowlist/);581 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});582 });583 });584585 it('NFT: disallows to nest token in an unlisted collection', async () => {586 await usingApi(async api => {587 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});588 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true, restricted:[]}});589590 // Create a token to attempt to be nested into591 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');592593 // Try to create a nested token in the wrong collection594 await expect(executeTransaction(api, alice, api.tx.unique.createItem(595 collection,596 {Ethereum: tokenIdToAddress(collection, targetToken)},597 {nft: {}} as any,598 )), 'while creating nested token').to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);599600 // Try to create and nest a token in the wrong collection601 const newToken = await createItemExpectSuccess(alice, collection, 'NFT');602 await expect(executeTransaction(api, alice, api.tx.unique.transfer({Ethereum: tokenIdToAddress(collection, targetToken)}, collection, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);603 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});604 });605 });606607 // ---------- Fungible ----------608609 it('Fungible: disallows to nest token if nesting is disabled', async () => {610 await usingApi(async api => {611 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});612 await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {}});613 const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT');614 const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};615616 const collectionFT = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});617618 // Try to create a nested token619 await expect(executeTransaction(api, alice, api.tx.unique.createItem(620 collectionFT,621 targetAddress,622 {Fungible: {Value: 10}},623 )), 'while creating nested token').to.be.rejectedWith(/^common\.UserIsNotAllowedToNest$/);624625 // Create a token to be nested626 const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');627 // Try to nest628 await expect(executeTransaction(api, alice, api.tx.unique.transfer(targetAddress, collectionFT, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);629630 // Create another token to be nested631 const newToken2 = await createItemExpectSuccess(alice, collectionFT, 'Fungible');632 // Try to nest inside a fungible token633 await expect(executeTransaction(api, alice, api.tx.unique.transfer({Ethereum: tokenIdToAddress(collectionFT, newToken)}, collectionFT, newToken2, 1)), 'while nesting new token inside fungible').to.be.rejectedWith(/fungible\.FungibleDisallowsNesting/);634 });635 });636637 it('Fungible: disallows a non-Owner to nest someone else\'s token', async () => {638 await usingApi(async api => {639 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});640 await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true}});641642 await addToAllowListExpectSuccess(alice, collectionNFT, bob.address);643 await enableAllowListExpectSuccess(alice, collectionNFT);644 await enablePublicMintingExpectSuccess(alice, collectionNFT);645646 // Create a token to attempt to be nested into647 const targetToken = await createItemExpectSuccess(bob, collectionNFT, 'NFT');648 const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};649650 const collectionFT = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});651652 // Try to create a nested token in the wrong collection653 await expect(executeTransaction(api, alice, api.tx.unique.createItem(654 collectionFT,655 targetAddress,656 {Fungible: {Value: 10}},657 )), 'while creating nested token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);658659 // Try to create and nest a token in the wrong collection660 const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');661 await expect(executeTransaction(api, alice, api.tx.unique.transfer(targetAddress, collectionFT, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);662 });663 });664665 it('Fungible: disallows a non-Owner to nest someone else\'s token (Restricted nesting)', async () => {666 await usingApi(async api => {667 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});668 await addToAllowListExpectSuccess(alice, collectionNFT, bob.address);669 await enableAllowListExpectSuccess(alice, collectionNFT);670 await enablePublicMintingExpectSuccess(alice, collectionNFT);671672 // Create a token to attempt to be nested into673 const targetToken = await createItemExpectSuccess(bob, collectionNFT, 'NFT');674 const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};675676 const collectionFT = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});677 await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true, restricted:[collectionFT]}});678679 // Try to create a nested token in the wrong collection680 await expect(executeTransaction(api, alice, api.tx.unique.createItem(681 collectionFT,682 targetAddress,683 {Fungible: {Value: 10}},684 )), 'while creating nested token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);685686 // Try to create and nest a token in the wrong collection687 const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');688 await expect(executeTransaction(api, alice, api.tx.unique.transfer(targetAddress, collectionFT, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);689 });690 });691692 it('Fungible: disallows to nest token in an unlisted collection', async () => {693 await usingApi(async api => {694 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});695 await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true, restricted:[]}});696697 // Create a token to attempt to be nested into698 const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT');699 const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};700701 const collectionFT = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});702703 // Try to create a nested token in the wrong collection704 await expect(executeTransaction(api, alice, api.tx.unique.createItem(705 collectionFT,706 targetAddress,707 {Fungible: {Value: 10}},708 )), 'while creating a nested token').to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);709710 // Try to create and nest a token in the wrong collection711 const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');712 await expect(executeTransaction(api, alice, api.tx.unique.transfer(targetAddress, collectionFT, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);713 });714 });715716 // ---------- Re-Fungible ----------717718 it('ReFungible: disallows to nest token if nesting is disabled', async () => {719 await usingApi(async api => {720 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});721 await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {}});722 const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT');723 const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};724725 const collectionRFT = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});726727 // Create a nested token728 await expect(executeTransaction(api, alice, api.tx.unique.createItem(729 collectionRFT,730 targetAddress,731 {ReFungible: {pieces: 100}},732 )), 'while creating a nested token').to.be.rejectedWith(/^common\.UserIsNotAllowedToNest$/);733734 // Create a token to be nested735 const newToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');736 // Try to nest737 await transferExpectFailure(collectionRFT, newToken, alice, targetAddress, 100);738 // Try to nest739 await expect(executeTransaction(api, alice, api.tx.unique.transfer(targetAddress, collectionRFT, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);740741 // Create another token to be nested742 const newToken2 = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');743 // Try to nest inside a fungible token744 await expect(executeTransaction(api, alice, api.tx.unique.transfer({Ethereum: tokenIdToAddress(collectionRFT, newToken)}, collectionRFT, newToken2, 1)), 'while nesting new token inside refungible').to.be.rejectedWith(/refungible\.RefungibleDisallowsNesting/);745 });746 });747748 it('ReFungible: disallows a non-Owner to nest someone else\'s token', async () => {749 await usingApi(async api => {750 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});751 await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true}});752753 await addToAllowListExpectSuccess(alice, collectionNFT, bob.address);754 await enableAllowListExpectSuccess(alice, collectionNFT);755 await enablePublicMintingExpectSuccess(alice, collectionNFT);756757 // Create a token to attempt to be nested into758 const targetToken = await createItemExpectSuccess(bob, collectionNFT, 'NFT');759 const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};760761 const collectionRFT = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});762763 // Try to create a nested token in the wrong collection764 await expect(executeTransaction(api, alice, api.tx.unique.createItem(765 collectionRFT,766 targetAddress,767 {ReFungible: {pieces: 100}},768 )), 'while creating a nested token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);769770 // Try to create and nest a token in the wrong collection771 const newToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');772 await expect(executeTransaction(api, alice, api.tx.unique.transfer(targetAddress, collectionRFT, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);773 });774 });775776 it('ReFungible: disallows a non-Owner to nest someone else\'s token (Restricted nesting)', async () => {777 await usingApi(async api => {778 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});779 await addToAllowListExpectSuccess(alice, collectionNFT, bob.address);780 await enableAllowListExpectSuccess(alice, collectionNFT);781 await enablePublicMintingExpectSuccess(alice, collectionNFT);782783 // Create a token to attempt to be nested into784 const targetToken = await createItemExpectSuccess(bob, collectionNFT, 'NFT');785 const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};786787 const collectionRFT = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});788 await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true, restricted:[collectionRFT]}});789790 // Try to create a nested token in the wrong collection791 await expect(executeTransaction(api, alice, api.tx.unique.createItem(792 collectionRFT,793 targetAddress,794 {ReFungible: {pieces: 100}},795 )), 'while creating a nested token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);796797 // Try to create and nest a token in the wrong collection798 const newToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');799 await expect(executeTransaction(api, alice, api.tx.unique.transfer(targetAddress, collectionRFT, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);800 });801 });802803 it('ReFungible: disallows to nest token to an unlisted collection', async () => {804 await usingApi(async api => {805 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});806 await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true, restricted:[]}});807808 // Create a token to attempt to be nested into809 const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT');810 const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};811812 const collectionRFT = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});813814 // Try to create a nested token in the wrong collection815 await expect(executeTransaction(api, alice, api.tx.unique.createItem(816 collectionRFT,817 targetAddress,818 {ReFungible: {pieces: 100}},819 )), 'while creating a nested token').to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);820821 // Try to create and nest a token in the wrong collection822 const newToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');823 await expect(executeTransaction(api, alice, api.tx.unique.transfer(targetAddress, collectionRFT, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);824 });825 });826});tests/src/pallet-presence.test.tsdiffbeforeafterboth--- a/tests/src/pallet-presence.test.ts
+++ b/tests/src/pallet-presence.test.ts
@@ -50,8 +50,6 @@
'unique',
'nonfungible',
'refungible',
- 'rmrkcore',
- 'rmrkequip',
'scheduler',
'charging',
];
@@ -64,6 +62,16 @@
];
describe('Pallet presence', () => {
+ before(async () => {
+ await usingApi(async api => {
+ const chain = await api.rpc.system.chain();
+
+ if (!chain.eq('UNIQUE')) {
+ requiredPallets.push(...['rmrkcore', 'rmrkequip']);
+ }
+ });
+ });
+
it('Required pallets are present', async () => {
await usingApi(async api => {
for (let i=0; i<requiredPallets.length; i++) {
tests/src/rmrk/rmrk.test.tsdiffbeforeafterboth--- a/tests/src/rmrk/rmrk.test.ts
+++ b/tests/src/rmrk/rmrk.test.ts
@@ -11,6 +11,7 @@
} from '../util/helpers';
import {IKeyringPair} from '@polkadot/types/types';
import {ApiPromise} from '@polkadot/api';
+import {it} from 'mocha';
let alice: IKeyringPair;
let bob: IKeyringPair;
@@ -48,14 +49,26 @@
return result.data!;
}
-describe('RMRK External Integration Test', () => {
+async function isUnique(): Promise<boolean> {
+ return usingApi(async api => {
+ const chain = await api.rpc.system.chain();
+
+ return chain.eq('UNIQUE');
+ });
+}
+
+describe('RMRK External Integration Test', async () => {
+ const it_rmrk = (await isUnique() ? it : it.skip);
+
before(async () => {
await usingApi(async (api, privateKeyWrapper) => {
alice = privateKeyWrapper('//Alice');
+
+
});
});
- it('Creates a new RMRK collection that is mapped to a different ID and is tagged as external', async () => {
+ it_rmrk('Creates a new RMRK collection that is mapped to a different ID and is tagged as external', async () => {
await usingApi(async api => {
// throwaway collection to bump last Unique collection ID to test ID mapping
await createCollectionExpectSuccess();
@@ -70,11 +83,13 @@
});
});
-describe('Negative Integration Test: External Collections, Internal Ops', () => {
+describe('Negative Integration Test: External Collections, Internal Ops', async () => {
let uniqueCollectionId: number;
let rmrkCollectionId: number;
let rmrkNftId: number;
+ const it_rmrk = (await isUnique() ? it : it.skip);
+
before(async () => {
await usingApi(async (api, privateKeyWrapper) => {
alice = privateKeyWrapper('//Alice');
@@ -88,7 +103,7 @@
});
});
- it('[Negative] Forbids Unique operations with an external collection, handled by dispatch_call', async () => {
+ it_rmrk('[Negative] Forbids Unique operations with an external collection, handled by dispatch_call', async () => {
await usingApi(async api => {
// Collection item creation
@@ -147,7 +162,7 @@
});
});
- it('[Negative] Forbids Unique collection operations with an external collection', async () => {
+ it_rmrk('[Negative] Forbids Unique collection operations with an external collection', async () => {
await usingApi(async api => {
const txDestroyCollection = api.tx.unique.destroyCollection(uniqueCollectionId);
await expect(executeTransaction(api, alice, txDestroyCollection), 'destroying collection')
@@ -206,10 +221,12 @@
});
});
-describe('Negative Integration Test: Internal Collections, External Ops', () => {
+describe('Negative Integration Test: Internal Collections, External Ops', async () => {
let collectionId: number;
let nftId: number;
+ const it_rmrk = (await isUnique() ? it : it.skip);
+
before(async () => {
await usingApi(async (api, privateKeyWrapper) => {
alice = privateKeyWrapper('//Alice');
@@ -220,7 +237,7 @@
});
});
- it('[Negative] Forbids RMRK operations with the internal collection and NFT (due to the lack of mapping)', async () => {
+ it_rmrk('[Negative] Forbids RMRK operations with the internal collection and NFT (due to the lack of mapping)', async () => {
await usingApi(async api => {
const txChangeOwner = api.tx.rmrkCore.changeCollectionIssuer(collectionId, bob.address);
await expect(executeTransaction(api, alice, txChangeOwner), 'changing collection issuer')