diff --git a/pallets/rmrk-core/src/functions.rs b/pallets/rmrk-core/src/functions.rs index a8789698..b7bb227a 100644 --- a/pallets/rmrk-core/src/functions.rs +++ b/pallets/rmrk-core/src/functions.rs @@ -10,7 +10,8 @@ use sp_runtime::{ // Randomness to generate NFT virtual accounts pub const SALT_RMRK_NFT: &[u8; 8] = b"RmrkNft/"; -impl Priority, T::AccountId> for Pallet +impl Priority, T::AccountId, BoundedVec> + for Pallet where T: pallet_uniques::Config, { @@ -18,17 +19,17 @@ where sender: T::AccountId, collection_id: CollectionId, nft_id: NftId, - priorities: Vec>, + priorities: BoundedVec, ) -> DispatchResult { let (root_owner, _) = Pallet::::lookup_root_owner(collection_id, nft_id)?; ensure!(sender == root_owner, Error::::NoPermission); // TODO : Check NFT lock status - let mut bounded_priorities = Vec::>::new(); - for priority in priorities { - let bounded_priority = Self::to_bounded_string(priority)?; - bounded_priorities.push(bounded_priority); + for _ in Priorities::::drain_prefix((collection_id, nft_id)) {} + let mut priority_index = 0; + for resource_id in priorities { + Priorities::::insert((collection_id, nft_id, resource_id), priority_index); + priority_index += 1; } - Priorities::::insert(collection_id, nft_id, bounded_priorities); Self::deposit_event(Event::PrioritySet { collection_id, nft_id }); Ok(()) } @@ -59,8 +60,12 @@ where } impl - Resource, T::AccountId, BoundedResource> - for Pallet + Resource< + BoundedVec, + T::AccountId, + BoundedResource, + BoundedVec, + > for Pallet where T: pallet_uniques::Config, { @@ -75,7 +80,7 @@ where slot: Option, license: Option>, thumb: Option>, - parts: Option>, + parts: Option>, ) -> DispatchResult { let collection = Self::collections(collection_id).ok_or(Error::::CollectionUnknown)?; ensure!(collection.issuer == sender, Error::::NoPermission); @@ -91,6 +96,7 @@ where let res = ResourceInfo::< BoundedVec, BoundedVec, + BoundedVec, > { id: resource_id.clone(), base, @@ -312,10 +318,8 @@ where for _ in Resources::::drain_prefix((collection_id, nft_id)) {} - let kids = Children::::take((collection_id, nft_id)); - for (child_collection_id, child_nft_id) in kids { - // Remove child from Children StorageMap - Pallet::::remove_child((collection_id, nft_id), (child_collection_id, child_nft_id)); + for ((child_collection_id, child_nft_id), _) in Children::::iter_prefix((collection_id, nft_id,)) { + for _ in Children::::drain_prefix((collection_id, nft_id)) {} Self::nft_burn(child_collection_id, child_nft_id, max_recursions - 1)?; } @@ -575,7 +579,7 @@ where /// Output: /// - Adding a `child` to the Children StorageMap of the `parent` pub fn add_child(parent: (CollectionId, NftId), child: (CollectionId, NftId)) { - Children::::mutate(parent, |v| v.push(child)); + Children::::insert((parent.0, parent.1), (child.0, child.1), ()); } /// Remove a child from a parent NFT @@ -587,21 +591,7 @@ where /// Output: /// - Removing a `child` from the Children StorageMap of the `parent` pub fn remove_child(parent: (CollectionId, NftId), child: (CollectionId, NftId)) { - Children::::mutate(parent, |v| { - *v = v.iter().filter(|&nft| *nft != child).cloned().collect(); - }); - } - - /// Has a child NFT present in the Children StorageMap of the parent NFT - /// - /// Parameters: - /// - `collection_id`: Collection ID of the NFT to lookup the root owner - /// - `nft_id`: NFT ID that is to be looked up for the root owner - /// - /// Output: - /// - `bool` - pub fn has_child(parent: (CollectionId, NftId)) -> bool { - !Children::::get(parent).is_empty() + Children::::remove((parent.0, parent.1), (child.0, child.1)); } /// Check whether a NFT is descends from a suspected parent NFT @@ -644,41 +634,7 @@ where found_child }, } - } - - /// `recursive_burn` function will recursively call itself to burn the NFT and all the children - /// of the NFT. Any caller functions must be #[transactional] - /// - /// Parameters: - /// - `collection_id`: Collection ID of the NFT to be burned - /// - `nft_id`: NFT ID that is to be burned - /// - `max_recursion`: Maximum number of recursion allowed - pub fn recursive_burn( - collection_id: CollectionId, - nft_id: NftId, - max_recursions: u32, - ) -> DispatchResult { - ensure!(max_recursions > 0, Error::::TooManyRecursions); - Nfts::::remove(collection_id, nft_id); - let kids = Children::::take((collection_id, nft_id)); - for (child_collection_id, child_nft_id) in kids { - Pallet::::recursive_burn(child_collection_id, child_nft_id, max_recursions - 1)?; - } - Ok(()) - } - - pub fn to_bounded_string(name: Vec) -> Result, Error> { - name.try_into().map_err(|_| Error::::TooLong) - } - - pub fn to_optional_bounded_string( - name: Option>, - ) -> Result>, Error> { - Ok(match name { - Some(n) => Some(Self::to_bounded_string(n)?), - None => None, - }) - } + } pub fn get_next_nft_id(collection_id: CollectionId) -> Result> { NextNftId::::try_mutate(collection_id, |id| { diff --git a/pallets/rmrk-core/src/lib.rs b/pallets/rmrk-core/src/lib.rs index 6a8abe53..2658ba1c 100644 --- a/pallets/rmrk-core/src/lib.rs +++ b/pallets/rmrk-core/src/lib.rs @@ -8,7 +8,7 @@ use frame_support::{ use frame_system::ensure_signed; use sp_runtime::{traits::StaticLookup, DispatchError, Permill}; -use sp_std::{convert::TryInto, vec::Vec}; +use sp_std::convert::TryInto; use rmrk_traits::{ primitives::*, AccountIdOrCollectionNftTuple, Collection, CollectionInfo, Nft, NftInfo, @@ -28,12 +28,14 @@ pub type InstanceInfoOf = NftInfo< ::AccountId, BoundedVec::StringLimit>, >; +pub type ResourceOf = ResourceInfo::< + BoundedVec, + BoundedVec::StringLimit>, + BoundedVec + >; pub type BoundedCollectionSymbolOf = BoundedVec::CollectionSymbolLimit>; -pub type ResourceOf = - ResourceInfo, BoundedVec::StringLimit>>; - pub type StringLimitOf = BoundedVec::StringLimit>; pub type BoundedResource = BoundedVec; @@ -64,6 +66,15 @@ pub mod pallet { /// The maximum resource symbol length #[pallet::constant] type ResourceSymbolLimit: Get; + + /// The maximum number of parts each resource may have + #[pallet::constant] + type PartsLimit: Get; + + /// The maximum number of resources that can be included in a setpriority extrinsic + #[pallet::constant] + type MaxPriorities: Get; + type CollectionSymbolLimit: Get; } @@ -105,21 +116,22 @@ pub mod pallet { #[pallet::storage] #[pallet::getter(fn priorities)] /// Stores priority info - pub type Priorities = StorageDoubleMap< + pub type Priorities = StorageNMap< _, - Twox64Concat, - CollectionId, - Twox64Concat, - NftId, - Vec>, + ( + NMapKey, + NMapKey, + NMapKey, + ), + u32, + OptionQuery, >; #[pallet::storage] #[pallet::getter(fn children)] /// Stores nft children info - pub type Children = - StorageMap<_, Twox64Concat, (CollectionId, NftId), Vec<(CollectionId, NftId)>, ValueQuery>; - + pub type Children = StorageDoubleMap<_, Twox64Concat, (CollectionId, NftId), Twox64Concat, (CollectionId, NftId), ()>; + #[pallet::storage] #[pallet::getter(fn resources)] /// Stores resource info @@ -130,7 +142,7 @@ pub mod pallet { NMapKey, NMapKey>, ), - ResourceOf, + ResourceOf, OptionQuery, >; @@ -149,7 +161,6 @@ pub mod pallet { >; #[pallet::pallet] - #[pallet::without_storage_info] #[pallet::generate_store(pub(super) trait Store)] pub struct Pallet(_); @@ -570,7 +581,7 @@ pub mod pallet { slot: Option, license: Option>, thumb: Option>, - parts: Option>, + parts: Option>, ) -> DispatchResult { let sender = ensure_signed(origin.clone())?; @@ -666,7 +677,7 @@ pub mod pallet { origin: OriginFor, collection_id: CollectionId, nft_id: NftId, - priorities: Vec>, + priorities: BoundedVec, ) -> DispatchResult { let sender = ensure_signed(origin.clone())?; Self::priority_set(sender, collection_id, nft_id, priorities)?; diff --git a/pallets/rmrk-core/src/mock.rs b/pallets/rmrk-core/src/mock.rs index 65acb18c..f8efa251 100644 --- a/pallets/rmrk-core/src/mock.rs +++ b/pallets/rmrk-core/src/mock.rs @@ -43,6 +43,8 @@ parameter_types! { pub MaxMetadataLength: u32 = 256; pub const MaxRecursions: u32 = 10; pub const ResourceSymbolLimit: u32 = 10; + pub const PartsLimit: u32 = 50; + pub const MaxPriorities: u32 = 3; pub const CollectionSymbolLimit: u32 = 100; } @@ -52,6 +54,8 @@ impl pallet_rmrk_core::Config for Test { type ProtocolOrigin = EnsureRoot; type MaxRecursions = MaxRecursions; type ResourceSymbolLimit = ResourceSymbolLimit; + type PartsLimit = PartsLimit; + type MaxPriorities = MaxPriorities; type CollectionSymbolLimit = CollectionSymbolLimit; } @@ -143,6 +147,9 @@ pub const COLLECTION_ID_0: ::ClassId = 0; // pub const COLLECTION_ID_1: ::ClassId = 1; pub const NFT_ID_0: ::InstanceId = 0; pub const NOT_EXISTING_CLASS_ID: ::ClassId = 999; +pub const RESOURCE_ZERO: ResourceId = 0; +pub const RESOURCE_ONE: ResourceId = 1; +pub const RESOURCE_TWO: ResourceId = 2; pub struct ExtBuilder; impl Default for ExtBuilder { diff --git a/pallets/rmrk-core/src/tests.rs b/pallets/rmrk-core/src/tests.rs index b28a5eb7..d3bdd0a3 100644 --- a/pallets/rmrk-core/src/tests.rs +++ b/pallets/rmrk-core/src/tests.rs @@ -360,7 +360,7 @@ fn send_nft_to_minted_nft_works() { // Bob-rootowned NFT (0,1) [child] is owned by Bob-rootowned NFT (0,0) [parent] assert_eq!(UNQ::Pallet::::owner(0, 1), Some(RMRKCore::nft_to_account_id(0, 0)),); // NFT (0,0) has NFT (0,1) in Children StorageMap - assert_eq!(RMRKCore::children((0, 0)), vec![(0, 1)]); + assert!(RMRKCore::children((0, 0), (0, 1)).is_some()); // Attempt to send NFT to self should fail assert_noop!( RMRKCore::send( @@ -478,7 +478,7 @@ fn send_two_nfts_to_same_nft_creates_two_children() { AccountIdOrCollectionNftTuple::CollectionAndNftTuple(0, 0), )); // NFT (0,0) has NFT (0,1) in Children StorageMap - assert_eq!(RMRKCore::children((0, 0)), vec![(0, 1)]); + assert!(RMRKCore::children((0, 0), (0, 1)).is_some()); // ALICE sends NFT (0, 2) to NFT (0, 0) assert_ok!(RMRKCore::send( Origin::signed(ALICE), @@ -487,7 +487,8 @@ fn send_two_nfts_to_same_nft_creates_two_children() { AccountIdOrCollectionNftTuple::CollectionAndNftTuple(0, 0), )); // NFT (0,0) has NFT (0,1) & (0,2) in Children StorageMap - assert_eq!(RMRKCore::children((0, 0)), vec![(0, 1), (0, 2)]); + assert!(RMRKCore::children((0, 0), (0,1)).is_some()); + assert!(RMRKCore::children((0, 0), (0,2)).is_some()); }); } @@ -516,7 +517,8 @@ fn send_nft_removes_existing_parent() { AccountIdOrCollectionNftTuple::CollectionAndNftTuple(0, 0), )); // NFT (0, 0) is parent of NFT (0, 1) - assert_eq!(RMRKCore::children((0, 0)), vec![(0, 1), (0, 2)]); + assert!(RMRKCore::children((0, 0), (0, 1)).is_some()); + assert!(RMRKCore::children((0, 0), (0, 2)).is_some()); // ALICE sends NFT (0, 1) to NFT (0, 2) assert_ok!(RMRKCore::send( Origin::signed(ALICE), @@ -525,7 +527,7 @@ fn send_nft_removes_existing_parent() { AccountIdOrCollectionNftTuple::CollectionAndNftTuple(0, 2), )); // NFT (0, 0) is no longer parent of NFT (0, 1) - assert_eq!(RMRKCore::children((0, 0)), vec![(0, 2)]); + assert!(RMRKCore::children((0, 0), (0, 1)).is_none()); }); } @@ -1028,12 +1030,13 @@ fn set_priority_works() { // Mint NFT assert_ok!(basic_mint()); // BOB cannot set priority on NFT + assert_noop!( RMRKCore::set_priority( Origin::signed(BOB), COLLECTION_ID_0, NFT_ID_0, - vec![stv("hello"), stv("world")] + bvec![100, 500] ), Error::::NoPermission ); @@ -1042,17 +1045,29 @@ fn set_priority_works() { Origin::signed(ALICE), COLLECTION_ID_0, NFT_ID_0, - vec![stv("hello"), stv("world")] + bvec![100, 500] // BoundedVec Resource 0, 1 )); // Successful priority set should trigger PrioritySet event System::assert_last_event(MockEvent::RmrkCore(crate::Event::PrioritySet { collection_id: 0, nft_id: 0, })); - // Priorities exist - assert_eq!( - RMRKCore::priorities(COLLECTION_ID_0, NFT_ID_0).unwrap(), - vec![stv("hello"), stv("world")] - ); + // Resource 100 should have priority 0 + assert_eq!(RMRKCore::priorities((COLLECTION_ID_0, NFT_ID_0, 100)).unwrap(), 0); + // Resource 500 should have priority 1 + assert_eq!(RMRKCore::priorities((COLLECTION_ID_0, NFT_ID_0, 500)).unwrap(), 1); + // Setting priority again drains and resets priorities + assert_ok!(RMRKCore::set_priority( + Origin::signed(ALICE), + COLLECTION_ID_0, + NFT_ID_0, + bvec![1000, 100] // BoundedVec Resources 2, 0 + )); + // Priorities reset, resource 100 should have priority one + assert_eq!(RMRKCore::priorities((COLLECTION_ID_0, NFT_ID_0, 100)).unwrap(), 1); + // Resource 1000 should have priority zero + assert_eq!(RMRKCore::priorities((COLLECTION_ID_0, NFT_ID_0, 1000)).unwrap(), 0); + // Resource 500 should no longer have a priority + assert!(RMRKCore::priorities((COLLECTION_ID_0, NFT_ID_0, 500)).is_none(),); }); } diff --git a/pallets/rmrk-equip/src/functions.rs b/pallets/rmrk-equip/src/functions.rs index 49f45158..45d7ba1e 100644 --- a/pallets/rmrk-equip/src/functions.rs +++ b/pallets/rmrk-equip/src/functions.rs @@ -28,7 +28,15 @@ impl Pallet { } } -impl Base> for Pallet +impl Base< + T::AccountId, + CollectionId, + NftId, + StringLimitOf, + BoundedVec, BoundedVec>, + T::PartsLimit>, + BoundedVec + > for Pallet where T: pallet_uniques::Config, { @@ -45,7 +53,7 @@ where issuer: T::AccountId, base_type: StringLimitOf, symbol: StringLimitOf, - parts: Vec>>, + parts: BoundedVec, BoundedVec>, T::PartsLimit>, ) -> Result { let base_id = Self::get_next_base_id()?; for part in parts.clone() { @@ -299,7 +307,7 @@ where issuer: T::AccountId, base_id: BaseId, part_id: PartId, - equippables: EquippableList, + equippables: EquippableList>, ) -> Result<(BaseId, SlotId), DispatchError> { // Base must exist ensure!(Bases::::get(base_id).is_some(), Error::::BaseDoesntExist); diff --git a/pallets/rmrk-equip/src/lib.rs b/pallets/rmrk-equip/src/lib.rs index c47c4270..a40e7943 100644 --- a/pallets/rmrk-equip/src/lib.rs +++ b/pallets/rmrk-equip/src/lib.rs @@ -5,7 +5,6 @@ use frame_support::{ dispatch::{DispatchError, DispatchResult}, ensure, BoundedVec, }; -use sp_std::vec::Vec; use sp_runtime::{traits::StaticLookup}; @@ -45,11 +44,11 @@ pub mod pallet { /// Maximum allowed Parts (either Fixed or Slot) per Base #[pallet::constant] - type MaxPartsPerBase: Get; + type MaxPropertiesPerTheme: Get; /// Maximum number of Properties allowed for any Theme #[pallet::constant] - type MaxPropertiesPerTheme: Get; + type MaxCollectionsEquippablePerPart: Get; } #[pallet::storage] @@ -58,7 +57,13 @@ pub mod pallet { /// TODO https://github.com/rmrk-team/rmrk-substrate/issues/98 /// Delete Parts from Bases info, as it's kept in Parts storage pub type Bases = - StorageMap<_, Twox64Concat, BaseId, BaseInfo>>; + StorageMap< + _, + Twox64Concat, BaseId, + BaseInfo< + T::AccountId, StringLimitOf, BoundedVec, BoundedVec>, + T::PartsLimit>> + >; #[pallet::storage] #[pallet::getter(fn parts)] @@ -66,7 +71,7 @@ pub mod pallet { /// - SlotPart: id, equippable (list), src, z /// - FixedPart: id, src, z pub type Parts = - StorageDoubleMap<_, Twox64Concat, BaseId, Twox64Concat, PartId, PartType>>; + StorageDoubleMap<_, Twox64Concat, BaseId, Twox64Concat, PartId, PartType, BoundedVec>>; #[pallet::storage] #[pallet::getter(fn next_base_id)] @@ -107,7 +112,6 @@ pub mod pallet { >; #[pallet::pallet] - #[pallet::without_storage_info] #[pallet::generate_store(pub(super) trait Store)] pub struct Pallet(_); @@ -289,7 +293,7 @@ pub mod pallet { origin: OriginFor, base_id: BaseId, slot_id: SlotId, - equippables: EquippableList, + equippables: EquippableList>, ) -> DispatchResult { let sender = ensure_signed(origin)?; @@ -345,13 +349,11 @@ pub mod pallet { origin: OriginFor, base_type: BoundedVec, symbol: BoundedVec, - parts: Vec>>, + parts: BoundedVec, BoundedVec>, T::PartsLimit>, ) -> DispatchResult { let sender = ensure_signed(origin)?; let part_length: u32 = parts.len().try_into().unwrap(); - ensure!(part_length <= T::MaxPartsPerBase::get(), Error::::ExceedsMaxPartsPerBase); - let base_id = Self::base_create(sender.clone(), base_type, symbol, parts)?; Self::deposit_event(Event::BaseCreated { issuer: sender, base_id }); diff --git a/pallets/rmrk-equip/src/mock.rs b/pallets/rmrk-equip/src/mock.rs index cad293b3..36264b02 100644 --- a/pallets/rmrk-equip/src/mock.rs +++ b/pallets/rmrk-equip/src/mock.rs @@ -40,14 +40,14 @@ frame_support::construct_runtime!( ); parameter_types! { - pub const MaxPartsPerBase: u32 = 5; pub const MaxPropertiesPerTheme: u32 = 5; + pub const MaxCollectionsEquippablePerPart: u32 = 10; } impl pallet_rmrk_equip::Config for Test { type Event = Event; - type MaxPartsPerBase = MaxPartsPerBase; type MaxPropertiesPerTheme = MaxPropertiesPerTheme; + type MaxCollectionsEquippablePerPart = MaxCollectionsEquippablePerPart; } parameter_types! { @@ -55,6 +55,8 @@ parameter_types! { pub MaxMetadataLength: u32 = 256; pub const MaxRecursions: u32 = 10; pub const ResourceSymbolLimit: u32 = 10; + pub const PartsLimit: u32 = 10; + pub const MaxPriorities: u32 = 3; pub const CollectionSymbolLimit: u32 = 100; } @@ -64,6 +66,8 @@ impl pallet_rmrk_core::Config for Test { type ProtocolOrigin = EnsureRoot; type MaxRecursions = MaxRecursions; type ResourceSymbolLimit = ResourceSymbolLimit; + type PartsLimit = PartsLimit; + type MaxPriorities = MaxPriorities; type CollectionSymbolLimit = CollectionSymbolLimit; } diff --git a/pallets/rmrk-equip/src/tests.rs b/pallets/rmrk-equip/src/tests.rs index 7df79f4d..426af823 100644 --- a/pallets/rmrk-equip/src/tests.rs +++ b/pallets/rmrk-equip/src/tests.rs @@ -47,7 +47,7 @@ fn create_base_works() { id: 102, z: 0, src: stb("slot_part_src"), - equippable: EquippableList::Custom(vec![ + equippable: EquippableList::Custom(bvec![ 0, // Collection 0 1, // Collection 1 ]), @@ -57,7 +57,7 @@ fn create_base_works() { Origin::signed(ALICE), // origin bvec![0u8; 20], // base_type bvec![0u8; 20], // symbol - vec![PartType::FixedPart(fixed_part), PartType::SlotPart(slot_part),], + bvec![PartType::FixedPart(fixed_part), PartType::SlotPart(slot_part),], )); // println!("{:?}", RmrkEquip::bases(0).unwrap()); @@ -73,14 +73,15 @@ fn change_base_issuer_works() { Origin::signed(ALICE), // origin bvec![0u8; 20], // base_type bvec![0u8; 20], // symbol - vec![], // parts + bvec![], // parts )); // Issuer should be Alice assert_eq!(RmrkEquip::bases(0).unwrap().issuer, ALICE); // Bob can't change issuer (no permission) assert_noop!( RmrkEquip::change_base_issuer(Origin::signed(BOB), 0, BOB), - Error::::PermissionError); + Error::::PermissionError + ); // Changing Base Issuer should be Alice assert_ok!(RmrkEquip::change_base_issuer(Origin::signed(ALICE), 0, BOB)); // Issuer should be Bob @@ -91,30 +92,19 @@ fn change_base_issuer_works() { new_issuer: BOB, base_id: 0, })); - }); } /// Base: Attempting to create a base with more the max parts fails #[test] -fn exceeding_max_parts_per_base_fails() { - ExtBuilder::default().build().execute_with(|| { - let mut parts = Vec::>>::new(); - for i in 100..110 { - let fixed_part = FixedPart { id: i, z: 0, src: stb("fixed_part_src") }; - parts.push(PartType::FixedPart(fixed_part)); - } - - assert_noop!( - RmrkEquip::create_base( - Origin::signed(ALICE), // origin - bvec![0u8; 20], // base_type - bvec![0u8; 20], // symbol - parts, - ), - Error::::ExceedsMaxPartsPerBase, - ); - }); +#[should_panic] +fn exceeding_parts_bound_panics() { + // PartsLimit bound is 50 per mock.rs, 60 should panic on unwrap + let parts_bounded_vec: BoundedVec = bvec![ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, + 9, 10, + ]; } /// Base: Basic equip tests @@ -131,7 +121,7 @@ fn equip_works() { id: 201, z: 0, src: stb("left-hand"), - equippable: EquippableList::Custom(vec![ + equippable: EquippableList::Custom(bvec![ 0, // Collection 0 1, // Collection 1 ]), @@ -141,7 +131,7 @@ fn equip_works() { id: 202, z: 0, src: stb("right-hand"), - equippable: EquippableList::Custom(vec![ + equippable: EquippableList::Custom(bvec![ 0, // Collection 2 1, // Collection 3 ]), @@ -151,7 +141,7 @@ fn equip_works() { Origin::signed(ALICE), // origin stb("svg"), // base_type stb("KANPEOPLE"), // symbol - vec![ + bvec![ PartType::FixedPart(fixed_part_body_1), PartType::FixedPart(fixed_part_body_2), PartType::SlotPart(slot_part_left_hand), @@ -164,7 +154,7 @@ fn equip_works() { Origin::signed(ALICE), stb("ipfs://col0-metadata"), // metadata Some(5), // max - sbvec!["COL0"] // symbol + sbvec!["COL0"] // symbol )); // Create collection 1 @@ -172,7 +162,7 @@ fn equip_works() { Origin::signed(ALICE), stb("ipfs://col1-metadata"), // metadata Some(5), // max - sbvec!["COL1"] // symbol + sbvec!["COL1"] // symbol )); // Mint NFT 0 from collection 0 (character-0) @@ -260,7 +250,7 @@ fn equip_works() { None, // slot None, // license None, // thumb - Some(vec![ + Some(bvec![ // parts 101, // ID of body-1 part 201, // ID of left-hand slot @@ -419,7 +409,7 @@ fn equippable_works() { id: 201, z: 0, src: stb("left-hand"), - equippable: EquippableList::Custom(vec![ + equippable: EquippableList::Custom(bvec![ 0, // Collection 0 1, // Collection 1 ]), @@ -429,7 +419,7 @@ fn equippable_works() { id: 202, z: 0, src: stb("right-hand"), - equippable: EquippableList::Custom(vec![ + equippable: EquippableList::Custom(bvec![ 2, // Collection 2 3, // Collection 3 ]), @@ -439,7 +429,7 @@ fn equippable_works() { Origin::signed(ALICE), // origin stb("svg"), // base_type stb("KANPEOPLE"), // symbol - vec![ + bvec![ PartType::FixedPart(fixed_part_body_1), PartType::FixedPart(fixed_part_body_2), PartType::SlotPart(slot_part_left_hand), @@ -450,9 +440,9 @@ fn equippable_works() { // equippable extrinsic should work assert_ok!(RmrkEquip::equippable( Origin::signed(ALICE), - 0, // base ID - 202, // slot ID - EquippableList::Custom(vec![5, 6, 7]), // equippable collections + 0, // base ID + 202, // slot ID + EquippableList::Custom(bvec![5, 6, 7]), // equippable collections )); // Last event should be EquippablesUpdated @@ -466,7 +456,7 @@ fn equippable_works() { id: 202, z: 0, src: stb("right-hand"), - equippable: EquippableList::Custom(vec![5, 6, 7]), + equippable: EquippableList::Custom(bvec![5, 6, 7]), }; assert_eq!(RmrkEquip::parts(0, 202).unwrap(), PartType::SlotPart(should_be)); @@ -474,9 +464,9 @@ fn equippable_works() { assert_noop!( RmrkEquip::equippable( Origin::signed(ALICE), - 666, // base ID - 202, // slot ID - EquippableList::Custom(vec![5, 6, 7]), // equippable collections + 666, // base ID + 202, // slot ID + EquippableList::Custom(bvec![5, 6, 7]), // equippable collections ), Error::::BaseDoesntExist ); @@ -485,9 +475,9 @@ fn equippable_works() { assert_noop!( RmrkEquip::equippable( Origin::signed(ALICE), - 0, // base ID - 200, // slot ID - EquippableList::Custom(vec![5, 6, 7]), // equippable collections + 0, // base ID + 200, // slot ID + EquippableList::Custom(bvec![5, 6, 7]), // equippable collections ), Error::::PartDoesntExist ); @@ -496,9 +486,9 @@ fn equippable_works() { assert_noop!( RmrkEquip::equippable( Origin::signed(ALICE), - 0, // base ID - 101, // slot ID - EquippableList::Custom(vec![5, 6, 7, 8, 9]), // equippable collections + 0, // base ID + 101, // slot ID + EquippableList::Custom(bvec![5, 6, 7, 8, 9]), // equippable collections ), Error::::NoEquippableOnFixedPart ); @@ -507,9 +497,9 @@ fn equippable_works() { assert_noop!( RmrkEquip::equippable( Origin::signed(BOB), - 0, // base ID - 201, // slot ID - EquippableList::Custom(vec![3, 4, 5]), // equippable collections + 0, // base ID + 201, // slot ID + EquippableList::Custom(bvec![3, 4, 5]), // equippable collections ), Error::::PermissionError ); @@ -564,7 +554,7 @@ fn theme_add_works() { Origin::signed(ALICE), // origin bvec![0u8; 20], // base_type bvec![0u8; 20], // symbol - vec![], + bvec![], )); // Add non-default theme to base (should fail w/o default) @@ -641,7 +631,7 @@ fn theme_add_too_many_properties_fails() { Origin::signed(ALICE), // origin bvec![0u8; 20], // base_type bvec![0u8; 20], // symbol - vec![], + bvec![], )); // Define a default theme with too many properties (10) diff --git a/pallets/rmrk-market/src/mock.rs b/pallets/rmrk-market/src/mock.rs index 467fb142..9e260f0b 100644 --- a/pallets/rmrk-market/src/mock.rs +++ b/pallets/rmrk-market/src/mock.rs @@ -94,6 +94,8 @@ parameter_types! { pub MaxMetadataLength: u32 = 256; pub const MaxRecursions: u32 = 10; pub const ResourceSymbolLimit: u32 = 10; + pub const PartsLimit: u32 = 10; + pub const MaxPriorities: u32 = 3; pub const CollectionSymbolLimit: u32 = 100; } @@ -103,6 +105,8 @@ impl pallet_rmrk_core::Config for Test { type ProtocolOrigin = EnsureRoot; type MaxRecursions = MaxRecursions; type ResourceSymbolLimit = ResourceSymbolLimit; + type PartsLimit = PartsLimit; + type MaxPriorities = MaxPriorities; type CollectionSymbolLimit = CollectionSymbolLimit; } diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 2b1e43c4..4b07337a 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -305,6 +305,8 @@ impl pallet_template::Config for Runtime { parameter_types! { pub const MaxRecursions: u32 = 10; pub const ResourceSymbolLimit: u32 = 10; + pub const PartsLimit: u32 = 3; + pub const MaxPriorities: u32 = 3; pub const CollectionSymbolLimit: u32 = 100; } @@ -313,6 +315,8 @@ impl pallet_rmrk_core::Config for Runtime { type ProtocolOrigin = frame_system::EnsureRoot; type MaxRecursions = MaxRecursions; type ResourceSymbolLimit = ResourceSymbolLimit; + type PartsLimit = PartsLimit; + type MaxPriorities = MaxPriorities; type CollectionSymbolLimit = CollectionSymbolLimit; } @@ -336,14 +340,14 @@ parameter_types! { pub const AttributeDepositBase: Balance = 10 * DOLLARS; pub const DepositPerByte: Balance = DOLLARS; pub const UniquesStringLimit: u32 = 128; - pub const MaxPartsPerBase: u32 = 100; pub const MaxPropertiesPerTheme: u32 = 100; + pub const MaxCollectionsEquippablePerPart: u32 = 100; } impl pallet_rmrk_equip::Config for Runtime { type Event = Event; - type MaxPartsPerBase = MaxPartsPerBase; type MaxPropertiesPerTheme = MaxPropertiesPerTheme; + type MaxCollectionsEquippablePerPart = MaxCollectionsEquippablePerPart; } impl pallet_uniques::Config for Runtime { diff --git a/traits/src/base.rs b/traits/src/base.rs index f4aa6e45..6b90c5e4 100644 --- a/traits/src/base.rs +++ b/traits/src/base.rs @@ -7,10 +7,12 @@ use codec::{Decode, Encode}; use scale_info::TypeInfo; use sp_runtime::{DispatchError, RuntimeDebug}; use sp_std::vec::Vec; +use frame_support::pallet_prelude::MaxEncodedLen; + #[cfg_attr(feature = "std", derive(PartialEq, Eq))] -#[derive(Encode, Decode, RuntimeDebug, TypeInfo)] -pub struct BaseInfo { +#[derive(Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)] +pub struct BaseInfo { /// Original creator of the Base pub issuer: AccountId, /// Specifies how an NFT should be rendered, ie "svg" @@ -18,16 +20,16 @@ pub struct BaseInfo { /// User provided symbol during Base creation pub symbol: BoundedString, /// Parts, full list of both Fixed and Slot parts - pub parts: Vec>, + pub parts: BoundedParts, } // Abstraction over a Base system. -pub trait Base { +pub trait Base { fn base_create( issuer: AccountId, base_type: BoundedString, symbol: BoundedString, - parts: Vec>, + parts: BoundedParts, ) -> Result; fn base_change_issuer( base_id: BaseId, @@ -44,7 +46,7 @@ pub trait Base { issuer: AccountId, base_id: BaseId, slot: SlotId, - equippables: EquippableList, + equippables: EquippableList, ) -> Result<(BaseId, SlotId), DispatchError>; fn add_theme( issuer: AccountId, diff --git a/traits/src/collection.rs b/traits/src/collection.rs index 2197b6ba..a7198d8e 100644 --- a/traits/src/collection.rs +++ b/traits/src/collection.rs @@ -1,13 +1,14 @@ use codec::{Decode, Encode}; use scale_info::TypeInfo; use sp_runtime::{DispatchError, DispatchResult, RuntimeDebug}; +use frame_support::pallet_prelude::MaxEncodedLen; use crate::primitives::*; use sp_std::result::Result; /// Collection info. #[cfg_attr(feature = "std", derive(PartialEq, Eq))] -#[derive(Encode, Decode, RuntimeDebug, TypeInfo)] +#[derive(Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub struct CollectionInfo { /// Current bidder and bid price. pub issuer: AccountId, diff --git a/traits/src/nft.rs b/traits/src/nft.rs index 46b81cf0..8363fc52 100644 --- a/traits/src/nft.rs +++ b/traits/src/nft.rs @@ -12,7 +12,7 @@ use sp_std::result::Result; #[cfg(feature = "std")] use serde::{Deserialize, Serialize}; -#[derive(Encode, Decode, Eq, PartialEq, Copy, Clone, RuntimeDebug, TypeInfo)] +#[derive(Encode, Decode, Eq, PartialEq, Copy, Clone, RuntimeDebug, TypeInfo, MaxEncodedLen)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub enum AccountIdOrCollectionNftTuple { AccountId(AccountId), @@ -21,7 +21,7 @@ pub enum AccountIdOrCollectionNftTuple { /// Nft info. #[cfg_attr(feature = "std", derive(PartialEq, Eq))] -#[derive(Encode, Decode, RuntimeDebug, TypeInfo)] +#[derive(Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub struct NftInfo { /// The owner of the NFT, can be either an Account or a tuple (CollectionId, NftId) pub owner: AccountIdOrCollectionNftTuple, diff --git a/traits/src/part.rs b/traits/src/part.rs index 46cffa80..6818cd07 100644 --- a/traits/src/part.rs +++ b/traits/src/part.rs @@ -3,34 +3,36 @@ use codec::{Decode, Encode}; use scale_info::TypeInfo; use sp_runtime::RuntimeDebug; use sp_std::vec::Vec; +use frame_support::pallet_prelude::MaxEncodedLen; // #[cfg_attr(feature = "std", derive(PartialEq, Eq))] -#[derive(Encode, Decode, RuntimeDebug, TypeInfo, Clone, PartialEq, Eq)] +#[derive(Encode, Decode, RuntimeDebug, TypeInfo, Clone, PartialEq, Eq, MaxEncodedLen)] pub struct FixedPart { pub id: PartId, pub z: ZIndex, pub src: BoundedString, } -#[derive(Encode, Decode, RuntimeDebug, TypeInfo, Clone, PartialEq, Eq)] -pub enum EquippableList { +#[derive(Encode, Decode, RuntimeDebug, TypeInfo, Clone, PartialEq, Eq, MaxEncodedLen)] +pub enum EquippableList { All, Empty, - Custom(Vec), + Custom(BoundedCollectionList), + // Custom(Vec), } // #[cfg_attr(feature = "std", derive(PartialEq, Eq))] -#[derive(Encode, Decode, RuntimeDebug, TypeInfo, Clone, PartialEq, Eq)] -pub struct SlotPart { +#[derive(Encode, Decode, RuntimeDebug, TypeInfo, Clone, PartialEq, Eq, MaxEncodedLen)] +pub struct SlotPart { pub id: PartId, - pub equippable: EquippableList, + pub equippable: EquippableList, pub src: BoundedString, pub z: ZIndex, } // #[cfg_attr(feature = "std", derive(PartialEq, Eq))] -#[derive(Encode, Decode, RuntimeDebug, TypeInfo, Clone, PartialEq, Eq)] -pub enum PartType { +#[derive(Encode, Decode, RuntimeDebug, TypeInfo, Clone, PartialEq, Eq, MaxEncodedLen)] +pub enum PartType { FixedPart(FixedPart), - SlotPart(SlotPart), + SlotPart(SlotPart), } diff --git a/traits/src/priority.rs b/traits/src/priority.rs index 62c7aa63..df5a8bd8 100644 --- a/traits/src/priority.rs +++ b/traits/src/priority.rs @@ -5,11 +5,11 @@ use sp_std::vec::Vec; /// Abstraction over a Priority system. #[allow(clippy::upper_case_acronyms)] -pub trait Priority { +pub trait Priority { fn priority_set( sender: AccountId, collection_id: CollectionId, nft_id: NftId, - priorities: Vec>, + priorities: BoundedPriorities, ) -> DispatchResult; } diff --git a/traits/src/resource.rs b/traits/src/resource.rs index 66e00bac..23891a73 100644 --- a/traits/src/resource.rs +++ b/traits/src/resource.rs @@ -5,13 +5,14 @@ use scale_info::TypeInfo; use serde::{Deserialize, Serialize}; use sp_runtime::{DispatchResult, RuntimeDebug}; use sp_std::{cmp::Eq, vec::Vec}; +use frame_support::pallet_prelude::MaxEncodedLen; use crate::primitives::*; -#[derive(Encode, Decode, Eq, PartialEq, Clone, RuntimeDebug, TypeInfo)] +#[derive(Encode, Decode, Eq, PartialEq, Clone, RuntimeDebug, TypeInfo, MaxEncodedLen)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -pub struct ResourceInfo { +pub struct ResourceInfo { /// id is a 5-character string of reasonable uniqueness. /// The combination of base ID and resource id should be unique across the entire RMRK /// ecosystem which @@ -24,7 +25,7 @@ pub struct ResourceInfo { pub pending_removal: bool, /// If a resource is composed, it will have an array of parts that compose it - pub parts: Option>, + pub parts: Option, /// A Base is uniquely identified by the combination of the word `base`, its minting block /// number, and user provided symbol during Base creation, glued by dashes `-`, e.g. @@ -52,7 +53,7 @@ pub struct ResourceInfo { } /// Abstraction over a Resource system. -pub trait Resource { +pub trait Resource { fn resource_add( sender: AccountId, collection_id: CollectionId, @@ -64,7 +65,7 @@ pub trait Resource { slot: Option, license: Option, thumb: Option, - parts: Option>, + parts: Option, ) -> DispatchResult; fn accept( sender: AccountId,