From 9e65253a1d22eb141737897f5d3555cfbf143bbd Mon Sep 17 00:00:00 2001 From: Fahrrader Date: Wed, 25 May 2022 07:37:16 +0000 Subject: [PATCH] feat(rmrk-rpc): rpc refactoring --- --- a/pallets/proxy-rmrk-core/src/lib.rs +++ b/pallets/proxy-rmrk-core/src/lib.rs @@ -22,7 +22,7 @@ use sp_std::vec::Vec; use up_data_structs::*; use pallet_common::{Pallet as PalletCommon, Error as CommonError, CollectionHandle, CommonCollectionOperations}; -use pallet_nonfungible::{Pallet as PalletNft, NonfungibleHandle}; +use pallet_nonfungible::{Pallet as PalletNft, NonfungibleHandle, TokenData}; use pallet_evm::account::CrossAccountId; pub use pallet::*; @@ -396,6 +396,15 @@ Ok(collection) } + // should this even be here, might displace it to common/nonfungible -- but they did not need it, only rmrk does + pub fn collection_exists(collection_id: CollectionId) -> bool { + >::contains_key(collection_id) + } + + pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool { + >::contains_key((collection_id, nft_id)) + } + pub fn get_collection_property(collection_id: CollectionId, key: RmrkProperty) -> Result { let collection_property = >::collection_properties(collection_id) .get(&rmrk_property!(Config=T, key)?) @@ -414,6 +423,13 @@ Ok(collection_type) } + pub fn ensure_collection_type(collection_id: CollectionId, collection_type: CollectionType) -> DispatchResult { + let actual_type = Self::get_collection_type(collection_id)?; + ensure!(actual_type == collection_type, >::NoPermission); + + Ok(()) + } + pub fn get_nft_property(collection_id: CollectionId, nft_id: TokenId, key: RmrkProperty) -> Result { let nft_property = >::token_properties((collection_id, nft_id)) .get(&rmrk_property!(Config=T, key)?) @@ -423,9 +439,16 @@ Ok(nft_property) } - pub fn check_collection_type(collection_id: CollectionId, collection_type: CollectionType) -> DispatchResult { - let actual_type = Self::get_collection_type(collection_id)?; - ensure!(actual_type == collection_type, >::NoPermission); + pub fn get_nft_type(collection_id: CollectionId, token_id: TokenId) -> Result { + >::get((collection_id, token_id)) + .unwrap() + .rmrk_nft_type() + .ok_or(>::NoAvailableNftId.into()) + } + + pub fn ensure_nft_type(collection_id: CollectionId, token_id: TokenId, nft_type: NftType) -> DispatchResult { + let actual_type = Self::get_nft_type(collection_id, token_id)?; + ensure!(actual_type == nft_type, >::NoPermission); Ok(()) } @@ -434,7 +457,7 @@ collection_id: CollectionId, collection_type: CollectionType ) -> Result, DispatchError> { - Self::check_collection_type(collection_id, collection_type)?; + Self::ensure_collection_type(collection_id, collection_type)?; Self::get_nft_collection(collection_id) } --- a/pallets/proxy-rmrk-core/src/misc.rs +++ b/pallets/proxy-rmrk-core/src/misc.rs @@ -82,15 +82,27 @@ } } -pub trait RmrkDecode { - fn decode_property(&self) -> Option; +pub trait RmrkDecode { + fn decode_or_default(&self) -> T; } -impl RmrkDecode for RmrkString { - fn decode_property(&self) -> Option { // todo access runtime errors? // but then rmrk_nft_type must have it too +impl RmrkDecode for BoundedVec { + fn decode_or_default(&self) -> T { let mut value = self.as_slice(); - T::decode(&mut value).ok() + T::decode(&mut value).unwrap_or_default() + } +} + +pub trait RmrkRebind { + fn rebind(&self) -> BoundedVec; +} + +impl RmrkRebind for BoundedVec where BoundedVec: TryFrom> { + fn rebind(&self) -> BoundedVec { + BoundedVec::::try_from( + self.clone().into_inner() + ).unwrap_or_default() } } --- a/primitives/data-structs/src/rmrk.rs +++ b/primitives/data-structs/src/rmrk.rs @@ -360,14 +360,14 @@ } #[cfg_attr(feature = "std", derive(Serialize))] -#[derive(Encode, Decode, Debug, TypeInfo, Clone, PartialEq, Eq, MaxEncodedLen)] +#[derive(Encode, Decode, Debug, Default, TypeInfo, Clone, PartialEq, Eq, MaxEncodedLen)] #[cfg_attr( feature = "std", serde(bound = "BoundedCollectionList: AsRef<[CollectionId]>") )] pub enum EquippableList { All, - Empty, + #[default] Empty, Custom( #[cfg_attr(feature = "std", serde(with = "serialize::vec"))] BoundedCollectionList --- a/runtime/common/src/runtime_apis.rs +++ b/runtime/common/src/runtime_apis.rs @@ -38,10 +38,10 @@ keys: Option>> ) -> Result, DispatchError> { let keys = keys.map( - |keys| pallet_common::Pallet::::bytes_keys_to_property_keys(keys) + |keys| Common::bytes_keys_to_property_keys(keys) ).transpose()?; - pallet_common::Pallet::::filter_collection_properties(collection, keys) + Common::filter_collection_properties(collection, keys) } fn token_properties( @@ -50,7 +50,7 @@ keys: Option>> ) -> Result, DispatchError> { let keys = keys.map( - |keys| pallet_common::Pallet::::bytes_keys_to_property_keys(keys) + |keys| Common::bytes_keys_to_property_keys(keys) ).transpose()?; dispatch_unique_runtime!(collection.token_properties(token_id, keys)) @@ -61,10 +61,10 @@ keys: Option>> ) -> Result, DispatchError> { let keys = keys.map( - |keys| pallet_common::Pallet::::bytes_keys_to_property_keys(keys) + |keys| Common::bytes_keys_to_property_keys(keys) ).transpose()?; - pallet_common::Pallet::::filter_property_permissions(collection, keys) + Common::filter_property_permissions(collection, keys) } fn token_data( @@ -144,34 +144,30 @@ fn last_collection_idx() -> Result { Ok(RmrkCore::last_collection_idx()) } + fn collection_by_id(collection_id: RmrkCollectionId) -> Result>, DispatchError> { - // TODO decide on displacement to palettes -- does RMRK belong there, spread across common and nonfungible? use frame_support::BoundedVec; use scale_info::prelude::string::String; - use pallet_proxy_rmrk_core::{RmrkProperty, misc::CollectionType}; - - // todo check if this is a rmrk standard collection? or simply trust and provide anyway? - // client-is-always-right / enforce authority and order ? + use pallet_proxy_rmrk_core::{RmrkProperty, misc::{CollectionType, RmrkRebind, RmrkDecode}}; let collection_id = CollectionId(collection_id); - let collection = >::get_typed_nft_collection(collection_id, CollectionType::Regular)?; + let collection = match RmrkCore::get_typed_nft_collection(collection_id, CollectionType::Regular) { + Ok(c) => c, + Err(_) => return Ok(None), + }; - let metadata = BoundedVec::try_from( - >::get_collection_property(collection_id, RmrkProperty::Metadata)?.into_inner() - ).map_err(|_| >::PropertyKeyIsTooLong)?; - - let nfts_count = (dispatch_unique_runtime!(collection_id.total_supply()) as Result)?; // todo? ::total_supply(collection_id) + let nfts_count = (dispatch_unique_runtime!(collection_id.total_supply()) as Result)?; + //::total_supply(collection_id); // todo can't find UniqueApi with disabled default features Ok(Some(RmrkCollectionInfo { issuer: collection.owner.clone(), - metadata, + metadata: RmrkCore::get_collection_property(collection_id, RmrkProperty::Metadata)?.decode_or_default(), max: collection.limits.token_limit, - symbol: BoundedVec::try_from( - collection.token_prefix.clone().into_inner() - ).map_err(|_| >::PropertyKeyIsTooLong)?, + symbol: collection.token_prefix.rebind(), // change nfts_count })) } + fn nft_by_id(collection_id: RmrkCollectionId, nft_by_id: RmrkNftId) -> Result>, DispatchError> { use frame_support::BoundedVec; use up_data_structs::mapping::TokenAddressMapping; @@ -179,6 +175,7 @@ let collection_id = CollectionId(collection_id); let nft_id = TokenId(nft_by_id); + if !RmrkCore::nft_exists(collection_id, nft_id) { return Ok(None); } let owner = match (dispatch_unique_runtime!(collection_id.token_owner(nft_id)) as Result, DispatchError>)? { Some(owner) => match ::CrossTokenAddressMapping::address_to_token(&owner) { @@ -187,36 +184,23 @@ }, None => return Ok(None) }; - - // todo displace querying property key array to rmrk proxy pallet - let keys = [ - RmrkProperty::RoyaltyInfo, - RmrkProperty::Metadata, - RmrkProperty::Equipped, - // ?? "rmrk:recipient", "rmrk:nft-type", "rmrk:resource-collection", "rmrk:resource-priorities" - ]; - - let properties = keys.into_iter().map( - |key| BoundedVec::try_from( - >::get_nft_property(collection_id, nft_id, key).unwrap().into_inner() - ).unwrap() - ) - .collect::>(); - + let allowance = pallet_nonfungible::Allowance::::get((collection_id, nft_id)); Ok(Some(RmrkInstanceInfo { owner: owner, - //recipient: , // prop? - royalty: properties[0].clone().decode_property().unwrap(), - metadata: properties[1].clone(), - equipped: properties[2].clone().decode_property().unwrap(), + royalty: RmrkCore::get_nft_property(collection_id, nft_id, RmrkProperty::RoyaltyInfo)?.decode_or_default(), + metadata: RmrkCore::get_nft_property(collection_id, nft_id, RmrkProperty::Metadata)?.decode_or_default(), + equipped: RmrkCore::get_nft_property(collection_id, nft_id, RmrkProperty::Equipped)?.decode_or_default(), pending: allowance.is_some(), })) } + fn account_tokens(account_id: AccountId, collection_id: RmrkCollectionId) -> Result, DispatchError> { let cross_account_id = CrossAccountId::from_sub(account_id); let collection_id = CollectionId(collection_id); + if !RmrkCore::collection_exists(collection_id) { return Ok(Vec::new()); } + Ok( (dispatch_unique_runtime!(collection_id.account_tokens(cross_account_id)) as Result, DispatchError>)? //>::account_tokens(collection_id, cross_account_id)? @@ -225,11 +209,14 @@ .collect::>() ) } + fn nft_children(collection_id: RmrkCollectionId, nft_id: RmrkNftId) -> Result, DispatchError> { use up_data_structs::mapping::TokenAddressMapping; let collection_id = CollectionId(collection_id); let nft_id = TokenId(nft_id); + if !RmrkCore::nft_exists(collection_id, nft_id) { return Ok(Vec::new()); } + let cross_account_id = CrossAccountId::from_eth( EvmTokenAddressMapping::token_to_address(collection_id, nft_id) ); @@ -243,21 +230,25 @@ .collect() ) } + fn collection_properties(collection_id: RmrkCollectionId, filter_keys: Option>) -> Result, DispatchError> { - use frame_support::BoundedVec; + use pallet_proxy_rmrk_core::misc::RmrkDecode; let collection_id = CollectionId(collection_id); - let properties = pallet_common::Pallet::::collection_properties(collection_id); + if !RmrkCore::collection_exists(collection_id) { return Ok(Vec::new()); } + let properties = Common::collection_properties(collection_id); + + // todo repeated code return Ok(match filter_keys { Some(keys) => { - let keys = pallet_common::Pallet::::bytes_keys_to_property_keys(keys)?; + let keys = Common::bytes_keys_to_property_keys(keys)?; let properties = keys .into_iter() .filter_map(|key| { properties.get(&key).map(|value| RmrkPropertyInfo { - key: BoundedVec::try_from(key.into_inner()).unwrap(), - value: BoundedVec::try_from(value.clone().into_inner()).unwrap(), + key: key.decode_or_default(), + value: value.decode_or_default(), }) }) .collect(); @@ -268,31 +259,35 @@ properties .iter() .filter_map(|(key, value)| Some(RmrkPropertyInfo { - key: BoundedVec::try_from(key.clone().into_inner()).unwrap(), - value: BoundedVec::try_from(value.clone().into_inner()).unwrap(), + key: key.decode_or_default(), + value: value.decode_or_default(), })) .collect() } }); } + fn nft_properties(collection_id: RmrkCollectionId, nft_id: RmrkNftId, filter_keys: Option>) -> Result, DispatchError> { use frame_support::BoundedVec; + use pallet_proxy_rmrk_core::misc::RmrkDecode; let collection_id = CollectionId(collection_id); let token_id = TokenId(nft_id); + if !RmrkCore::nft_exists(collection_id, token_id) { return Ok(Vec::new()); } - let properties = pallet_nonfungible::Pallet::::token_properties((collection_id, token_id)); // todo look into usage of pallet_nonfungible + let properties = Nonfungible::token_properties((collection_id, token_id)); + // todo look into this usage of pallet_nonfungible // todo displace to a function? redundant code piece with collection props return Ok(match filter_keys { Some(keys) => { - let keys = pallet_common::Pallet::::bytes_keys_to_property_keys(keys)?; + let keys = Common::bytes_keys_to_property_keys(keys)?; let properties = keys .into_iter() .filter_map(|key| { properties.get(&key).map(|value| RmrkPropertyInfo { - key: BoundedVec::try_from(key.into_inner()).unwrap(), - value: BoundedVec::try_from(value.clone().into_inner()).unwrap(), + key: key.decode_or_default(), + value: value.decode_or_default(), }) }) .collect(); @@ -303,113 +298,76 @@ properties .iter() .filter_map(|(key, value)| Some(RmrkPropertyInfo { - key: BoundedVec::try_from(key.clone().into_inner()).unwrap(), - value: BoundedVec::try_from(value.clone().into_inner()).unwrap(), + key: key.decode_or_default(), + value: value.decode_or_default(), })) .collect() } }); } + fn nft_resources(collection_id: RmrkCollectionId, nft_id: RmrkNftId) -> Result, DispatchError> { use frame_support::BoundedVec; - use pallet_proxy_rmrk_core::RmrkProperty; + use pallet_proxy_rmrk_core::{RmrkProperty, misc::{CollectionType, NftType}}; let collection_id = CollectionId(collection_id); - let nft_id = TokenId(nft_id); - - // let keys = [ - // RmrkProperty::RoyaltyInfo, - // RmrkProperty::Metadata, - // RmrkProperty::Equipped, - // RmrkProperty::Pending, - // // ?? "rmrk:recipient", "rmrk:nft-type", "rmrk:resource-collection", "rmrk:resource-priorities" - // ]; + if RmrkCore::ensure_collection_type(collection_id, CollectionType::Resource).is_err() { return Ok(Vec::new()); } - /*let resources = keys.into_iter().map( - |key| BoundedVec::try_from( - >::get_nft_property(collection_id, nft_id, key).unwrap().into_inner() - ).unwrap() - ) - .collect::>();*/ + let nft_id = TokenId(nft_id); + if RmrkCore::ensure_nft_type(collection_id, nft_id, NftType::Resource).is_err() { return Ok(Vec::new()); } Ok(Vec::new(/*[RmrkResourceInfo { - + }]*/)) } + fn nft_resource_priorities(collection_id: RmrkCollectionId, nft_id: RmrkNftId) -> Result, DispatchError> { todo!() } + fn base(base_id: RmrkBaseId) -> Result>, DispatchError> { use frame_support::BoundedVec; use scale_info::prelude::string::String; - use pallet_proxy_rmrk_core::{RmrkProperty, misc::CollectionType}; + use pallet_proxy_rmrk_core::{ + RmrkProperty, misc::{CollectionType, RmrkRebind, RmrkDecode}, + }; let collection_id = CollectionId(base_id); - let collection = >::get_typed_nft_collection(collection_id, CollectionType::Base)?; - // todo check prop for being a base - - // todo export to macro? redundancy - let keys = [ - RmrkProperty::BaseType, - ]; - - let properties = keys.into_iter().map( - |key| BoundedVec::try_from( - >::get_collection_property(collection_id, key).unwrap().into_inner() - ) - ) - // todo not-a-rmrk-collection error - .collect::, _>>() - .map_err(|_| >::CollectionUnknown)?; + let collection = match RmrkCore::get_typed_nft_collection(collection_id, CollectionType::Base) { + Ok(c) => c, + Err(_) => return Ok(None), + }; Ok(Some(RmrkBaseInfo { issuer: collection.owner.clone(), - base_type: properties[0].clone(), - symbol: BoundedVec::try_from( - collection.token_prefix.clone().into_inner() - ).map_err(|_| >::PropertyKeyIsTooLong)?, + base_type: RmrkCore::get_collection_property(collection_id, RmrkProperty::BaseType)?.decode_or_default(), + symbol: collection.token_prefix.rebind(), })) } + fn base_parts(base_id: RmrkBaseId) -> Result, DispatchError> { use frame_support::BoundedVec; - use pallet_proxy_rmrk_core::{RmrkProperty, misc::{RmrkNft, RmrkDecode}}; + use pallet_proxy_rmrk_core::{RmrkProperty, misc::{CollectionType, RmrkNft, RmrkDecode}}; let collection_id = CollectionId(base_id); - // todo check prop for being a base + if RmrkCore::ensure_collection_type(collection_id, CollectionType::Base).is_err() { return Ok(Vec::new()); } let parts = (dispatch_unique_runtime!(collection_id.collection_tokens()) as Result, DispatchError>)? .iter() .filter_map(|token_id| { - let nft_type = >::get((collection_id, token_id)) - //.map_err(|_| ) // no need, tis a filter_map - .unwrap() - .rmrk_nft_type()?; - - // dislocate to rmrkproxycore and simply send an array of keys - let keys = [ - //RmrkProperty::PartId)?, - RmrkProperty::Src, - RmrkProperty::ZIndex, - RmrkProperty::EquippableList, - ]; - - let properties = keys.into_iter().map( - |key| BoundedVec::try_from( - >::get_nft_property(collection_id, *token_id, key).unwrap().into_inner() - ).unwrap() - ).collect::>(); + let nft_type = RmrkCore::get_nft_type(collection_id, *token_id).unwrap(); match nft_type { FixedPart => Some(RmrkPartType::FixedPart(RmrkFixedPart { id: token_id.0, - src: properties[0].clone().decode_property().unwrap(), - z: properties[1].clone().decode_property().unwrap(), + src: RmrkCore::get_nft_property(collection_id, *token_id, RmrkProperty::Src).unwrap().decode_or_default(), + z: RmrkCore::get_nft_property(collection_id, *token_id, RmrkProperty::ZIndex).unwrap().decode_or_default(), })), SlotPart => Some(RmrkPartType::SlotPart(RmrkSlotPart { id: token_id.0, - src: properties[0].clone().decode_property().unwrap(), - z: properties[1].clone().decode_property().unwrap(), - equippable: properties[2].clone().decode_property().unwrap(), + src: RmrkCore::get_nft_property(collection_id, *token_id, RmrkProperty::Src).unwrap().decode_or_default(), + z: RmrkCore::get_nft_property(collection_id, *token_id, RmrkProperty::ZIndex).unwrap().decode_or_default(), + equippable: RmrkCore::get_nft_property(collection_id, *token_id, RmrkProperty::EquippableList).unwrap().decode_or_default(), })), _ => None } @@ -418,6 +376,7 @@ Ok(parts) } + fn theme_names(base_id: RmrkBaseId) -> Result, DispatchError> { use frame_support::BoundedVec; use pallet_proxy_rmrk_core::{RmrkProperty, misc::{RmrkNft, RmrkDecode}}; @@ -428,16 +387,11 @@ let theme_names = (dispatch_unique_runtime!(collection_id.collection_tokens()) as Result, DispatchError>)? .iter() .filter_map(|token_id| { - let nft_type = >::get((collection_id, token_id)) - .unwrap() - .rmrk_nft_type()?; - + let nft_type = RmrkCore::get_nft_type(collection_id, *token_id).unwrap(); + match nft_type { Theme => Some( - >::get_nft_property( - collection_id, *token_id, RmrkProperty::ThemeName - ).unwrap() - .into_inner() + RmrkCore::get_nft_property(collection_id, *token_id, RmrkProperty::ThemeName).unwrap().decode_or_default() ), _ => None } @@ -446,6 +400,7 @@ Ok(theme_names) } + fn theme(base_id: RmrkBaseId, theme_name: RmrkThemeName, filter_keys: Option>) -> Result, DispatchError> { use frame_support::BoundedVec; @@ -457,7 +412,7 @@ let themes = (dispatch_unique_runtime!(collection_id.collection_tokens()) as Result, DispatchError>)? .iter() .filter_map(|token_id| { - let properties = pallet_nonfungible::Pallet::::token_properties((collection_id, token_id)); + let properties = Nonfungible::token_properties((collection_id, token_id)); // todo ping properties for "rmrk:nft-type" // if none, skip, None --- a/tests/package.json +++ b/tests/package.json @@ -37,6 +37,7 @@ "testUnnesting": "mocha --timeout 9999999 -r ts-node/register ./**/unnest.test.ts", "testStructure": "mocha --timeout 9999999 -r ts-node/register ./**/nesting/**.test.ts", "testProperties": "mocha --timeout 9999999 -r ts-node/register ./**/properties.test.ts", + "testGraphs": "mocha --timeout 9999999 -r ts-node/register ./**/graphs.test.ts", "testMigrationStructure": "mocha --timeout 9999999 -r ts-node/register ./**/nesting/migration-check.test.ts", "testAddCollectionAdmin": "mocha --timeout 9999999 -r ts-node/register ./**/addCollectionAdmin.test.ts", "testSetSchemaVersion": "mocha --timeout 9999999 -r ts-node/register ./**/setSchemaVersion.test.ts", --- a/tests/src/nesting/graphs.test.ts +++ b/tests/src/nesting/graphs.test.ts @@ -32,10 +32,10 @@ return collectionId; } -describe('Graphs', () => { +describe.skip('Graphs', () => { it('Ouroboros can\'t be created in a complex graph', async () => { await usingApi(async api => { - const alice = privateKey('//Alice'); + const alice = privateKey('//alice'); const collection = await buildComplexObjectGraph(api, alice); // to self @@ -55,3 +55,197 @@ }); }); }); + +import type { EventRecord } from '@polkadot/types/interfaces'; +import type { GenericEventData } from '@polkadot/types'; +import type { Option, Bytes } from '@polkadot/types-codec'; +import type { + RmrkTypesCollectionInfo as Collection, + RmrkTypesNftInfo as Nft, + RmrkTypesResourceInfo as Resource, + RmrkTypesBaseInfo as Base, + RmrkTypesPartType as PartType, + RmrkTypesNftChild as NftChild, + RmrkTypesTheme as Theme, + RmrkTypesPropertyInfo as Property, +} from '@polkadot/types/lookup'; + +interface TxResult { + success: boolean; + successData: T | null; +} + +export function extractTxResult( + events: EventRecord[], + expectSection: string, + expectMethod: string, + extractAction: (data: GenericEventData) => T +): TxResult { + let success = false; + let successData = null; + events.forEach(({event: {data, method, section}}) => { + //console.log(expectSection + " "+ " " + section + " " + expectMethod + " " + method) + if (method == 'ExtrinsicSuccess') { + success = true; + } else if ((expectSection == section) && (expectMethod == method)) { + successData = extractAction(data); + } + }); + const result: TxResult = { + success, + successData, + }; + return result; +} + +export function extractRmrkCoreTxResult( + events: EventRecord[], + expectMethod: string, + extractAction: (data: GenericEventData) => T +): TxResult { + return extractTxResult(events, 'rmrkCore', expectMethod, extractAction); +} + +export async function expectTxFailure(expectedError: RegExp, promise: Promise) { + await expect(promise).to.be.rejectedWith(expectedError); +} + +export async function getCollectionsCount(api: ApiPromise): Promise { + return (await api.rpc.rmrk.lastCollectionIdx()).toNumber(); +} + +export async function getCollection(api: ApiPromise, id: number): Promise> { + return api.rpc.rmrk.collectionById(id); +} + +export async function createCollection( + api: ApiPromise, + issuerUri: string, + metadata: string, + max: number | null, + symbol: string +): Promise { + let collectionId = 0; + + const oldCollectionCount = await getCollectionsCount(api); + const maxOptional = max ? max.toString() : null; + console.log(maxOptional) + console.log('right above me') + + const issuer = privateKey(issuerUri); + const tx = api.tx.rmrkCore.createCollection(metadata, maxOptional, symbol); + const events = await executeTransaction(api, issuer, tx); + + const collectionResult = extractRmrkCoreTxResult( + events, 'CollectionCreated', (data) => { + return parseInt(data[1].toString(), 10) + } + ); + expect(collectionResult.success, 'Error: unable to create a collection').to.be.true; + const newCollectionCount = await getCollectionsCount(api); + expect(newCollectionCount).to.be.equal(oldCollectionCount + 1, 'Error: NFT collection count should increase'); + + collectionId = collectionResult.successData ?? 0; + + console.log(collectionId); + + const collectionOption = await getCollection(api, collectionId); + + expect(collectionOption.isSome, 'Error: unable to fetch created NFT collection').to.be.true; + + const collection = collectionOption.unwrap(); + + expect(collection.metadata.toUtf8()).to.be.equal(metadata, "Error: Invalid NFT collection metadata"); + console.log(collection.max, max) + expect(collection.max.isSome).to.be.equal(max !== null, "Error: Invalid NFT collection max"); + + if (collection.max.isSome) { + expect(collection.max.unwrap().toNumber()).to.be.equal(max, "Error: Invalid NFT collection max"); + } + expect(collection.symbol.toUtf8()).to.be.equal(symbol, "Error: Invalid NFT collection's symbol"); + expect(collection.nftsCount.toNumber()).to.be.equal(0, "Error: NFT collection shoudn't have any tokens"); + expect(collection.issuer.toString()).to.be.equal(issuer.address, "Error: Invalid NFT collection issuer"); + + return collectionId; +} + +export async function deleteCollection( + api: ApiPromise, + issuerUri: string, + collectionId: string +): Promise { + const issuer = privateKey(issuerUri); + const tx = api.tx.rmrkCore.destroyCollection(collectionId); + const events = await executeTransaction(api, issuer, tx); + + const collectionTxResult = extractRmrkCoreTxResult( + events, + "CollectionDestroy", + (data) => { + return parseInt(data[1].toString(), 10); + } + ); + expect(collectionTxResult.success, 'Error: Unable to delete NFT collection').to.be.true; + + const collection = await getCollection( + api, + parseInt(collectionId, 10) + ); + expect(collection.isEmpty, 'Error: NFT collection should be deleted').to.be.true; + + return 0; +} + +describe('Something', () => { + const alice = '//Alice'; + const bob = "//Bob"; + + it('create NFT collection', async () => { + await usingApi(async api => { + await createCollection(api, alice, 'test-metadata', 42, 'test-symbol'); + //console.log((await api.rpc.rmrk.base(3)).toHuman()); + }); + }); + + it('create NFT collection without token limit', async () => { + await usingApi(async api => { + await createCollection(api, alice, 'no-limit-metadata', null, 'no-limit-symbol'); + }); + }); + + it("Delete NFT collection", async () => { + await usingApi(async api => { + await createCollection( + api, + alice, + "test-metadata", + null, + "test-symbol" + ).then(async (collectionId) => { + await deleteCollection(api, alice, collectionId.toString()); + }); + }); + }); + + it("[Negative] delete non-existing NFT collection", async () => { + await usingApi(async api => { + const tx = deleteCollection(api, alice, "99999"); + await expectTxFailure(/rmrkCore.CollectionUnknown/, tx); + }); + }); + + it("[Negative] delete not an owner NFT collection", async () => { + await usingApi(async api => { + await createCollection( + api, + alice, + "test-metadata", + null, + "test-symbol" + ).then(async (collectionId) => { + const tx = deleteCollection(api, bob, collectionId.toString()); + await expectTxFailure(/uniques.NoPermission/, tx); + }); + }); + }); +}); \ No newline at end of file -- gitstuff