From 34592b6036f3518d4387391cd4c1acede0bc40c2 Mon Sep 17 00:00:00 2001 From: Daniel Olano Date: Thu, 23 Nov 2023 12:05:29 +0100 Subject: [PATCH] Add traits for managing Memberships and implement for non-fungible --- substrate/frame/nfts/src/impl_nonfungibles.rs | 24 ++- substrate/frame/nfts/src/tests.rs | 28 ++-- substrate/frame/support/src/traits.rs | 5 +- substrate/frame/support/src/traits/members.rs | 141 ++++++++++++++++++ .../src/traits/tokens/nonfungible_v2.rs | 40 ++--- .../src/traits/tokens/nonfungibles_v2.rs | 6 +- 6 files changed, 186 insertions(+), 58 deletions(-) diff --git a/substrate/frame/nfts/src/impl_nonfungibles.rs b/substrate/frame/nfts/src/impl_nonfungibles.rs index ee7f42cfc689..868960d93deb 100644 --- a/substrate/frame/nfts/src/impl_nonfungibles.rs +++ b/substrate/frame/nfts/src/impl_nonfungibles.rs @@ -228,7 +228,9 @@ impl, I: 'static> Destroy<::AccountId> for Palle } } -impl, I: 'static> Mutate<::AccountId, ItemConfig> for Pallet { +impl, I: 'static> Mutate<::AccountId> for Pallet { + type ItemConfig = ItemConfig; + fn mint_into( collection: &Self::CollectionId, item: &Self::ItemId, @@ -257,7 +259,7 @@ impl, I: 'static> Mutate<::AccountId, ItemConfig Self::do_burn(*collection, *item, |d| { if let Some(check_owner) = maybe_check_owner { if &d.owner != check_owner { - return Err(Error::::NoPermission.into()) + return Err(Error::::NoPermission.into()); } } Ok(()) @@ -288,7 +290,7 @@ impl, I: 'static> Mutate<::AccountId, ItemConfig ) -> DispatchResult { key.using_encoded(|k| { value.using_encoded(|v| { - >::set_attribute(collection, item, k, v) + >::set_attribute(collection, item, k, v) }) }) } @@ -315,9 +317,7 @@ impl, I: 'static> Mutate<::AccountId, ItemConfig ) -> DispatchResult { key.using_encoded(|k| { value.using_encoded(|v| { - >::set_collection_attribute( - collection, k, v, - ) + >::set_collection_attribute(collection, k, v) }) }) } @@ -368,9 +368,7 @@ impl, I: 'static> Mutate<::AccountId, ItemConfig item: &Self::ItemId, key: &K, ) -> DispatchResult { - key.using_encoded(|k| { - >::clear_attribute(collection, item, k) - }) + key.using_encoded(|k| >::clear_attribute(collection, item, k)) } fn clear_collection_attribute(collection: &Self::CollectionId, key: &[u8]) -> DispatchResult { @@ -388,7 +386,7 @@ impl, I: 'static> Mutate<::AccountId, ItemConfig key: &K, ) -> DispatchResult { key.using_encoded(|k| { - >::clear_collection_attribute(collection, k) + >::clear_collection_attribute(collection, k) }) } @@ -422,10 +420,10 @@ impl, I: 'static> Transfer for Pallet { Self::has_system_attribute(&collection, &item, PalletAttributes::TransferDisabled)?; // Can't lock the item twice if transfer_disabled { - return Err(Error::::ItemLocked.into()) + return Err(Error::::ItemLocked.into()); } - >::set_attribute( + >::set_attribute( collection, item, &PalletAttributes::::TransferDisabled.encode(), @@ -434,7 +432,7 @@ impl, I: 'static> Transfer for Pallet { } fn enable_transfer(collection: &Self::CollectionId, item: &Self::ItemId) -> DispatchResult { - >::clear_attribute( + >::clear_attribute( collection, item, &PalletAttributes::::TransferDisabled.encode(), diff --git a/substrate/frame/nfts/src/tests.rs b/substrate/frame/nfts/src/tests.rs index aeebf51b7c78..bb49960249b7 100644 --- a/substrate/frame/nfts/src/tests.rs +++ b/substrate/frame/nfts/src/tests.rs @@ -998,7 +998,7 @@ fn set_collection_system_attributes_should_work() { let attribute_key = [0u8]; let attribute_value = [0u8]; - assert_ok!(, ItemConfig>>::set_collection_attribute( + assert_ok!(>>::set_collection_attribute( &collection_id, &attribute_key, &attribute_value @@ -1021,13 +1021,11 @@ fn set_collection_system_attributes_should_work() { struct TypedAttributeValue(u32); let typed_attribute_value = TypedAttributeValue(42); - assert_ok!( - , ItemConfig>>::set_typed_collection_attribute( - &collection_id, - &typed_attribute_key, - &typed_attribute_value - ) - ); + assert_ok!(>>::set_typed_collection_attribute( + &collection_id, + &typed_attribute_key, + &typed_attribute_value + )); assert_eq!( >>::typed_system_attribute( @@ -1384,7 +1382,7 @@ fn validate_deposit_required_setting() { bvec![2], bvec![0], )); - assert_ok!(::AccountId, ItemConfig>>::set_attribute( + assert_ok!(::AccountId>>::set_attribute( &0, &0, &[3], @@ -1403,13 +1401,11 @@ fn validate_deposit_required_setting() { assert_eq!(Balances::reserved_balance(account(2)), 3); assert_eq!(Balances::reserved_balance(account(3)), 3); - assert_ok!( - ::AccountId, ItemConfig>>::clear_attribute( - &0, - &0, - &[3], - ) - ); + assert_ok!(::AccountId>>::clear_attribute( + &0, + &0, + &[3], + )); assert_eq!( attributes(0), vec![ diff --git a/substrate/frame/support/src/traits.rs b/substrate/frame/support/src/traits.rs index 6362e750d2ab..5cd67a4aec51 100644 --- a/substrate/frame/support/src/traits.rs +++ b/substrate/frame/support/src/traits.rs @@ -36,8 +36,9 @@ mod members; pub use members::{AllowAll, DenyAll, Filter}; pub use members::{ AsContains, ChangeMembers, Contains, ContainsLengthBound, ContainsPair, Equals, Everything, - EverythingBut, FromContainsPair, InitializeMembers, InsideBoth, IsInVec, Nothing, - RankedMembers, SortedMembers, TheseExcept, + EverythingBut, FromContainsPair, GenericRank, InitializeMembers, InsideBoth, IsInVec, + Membership, MembershipInspect, MembershipMutate, Nothing, RankedMembers, RankedMembership, + SortedMembers, TheseExcept, }; mod validation; diff --git a/substrate/frame/support/src/traits/members.rs b/substrate/frame/support/src/traits/members.rs index d667eaa7e9d8..0f0ffaf12bbc 100644 --- a/substrate/frame/support/src/traits/members.rs +++ b/substrate/frame/support/src/traits/members.rs @@ -17,6 +17,9 @@ //! Traits for dealing with the idea of membership. +use core::num::NonZeroU8; + +use crate::Parameter; use impl_trait_for_tuples::impl_for_tuples; use sp_arithmetic::traits::AtLeast16BitUnsigned; use sp_runtime::DispatchResult; @@ -391,3 +394,141 @@ impl ChangeMembers for () { fn set_members_sorted(_: &[T], _: &[T]) {} fn set_prime(_: Option) {} } + +/// Access data associated to a unique membership +pub trait MembershipInspect +where + M: Membership, + MembershipId: Parameter, +{ + type MembershipsIter: Iterator; + + /// Retrieve membership data that is expected to belong to member + fn get_membership(id: impl Into, member: &AccountId) -> Option; + + /// Retrieve all memberships belonging to member + fn account_memberships(member: &AccountId) -> Self::MembershipsIter; + + /// Check membership is owned by the given account + fn has_membership(id: impl Into, member: &AccountId) -> bool { + Self::get_membership(id, member).is_some() + } +} + +/// Change data related to a unique membership +pub trait MembershipMutate +where + M: Membership, + MembershipId: Parameter, +{ + /// Update the membership possibly changing its owner + fn update( + id: impl Into, + membership: M, + maybe_member: Option, + ) -> DispatchResult; +} + +/// A unique membership +pub trait Membership: codec::Decode + codec::Encode { + type Id; + + fn id(&self) -> &Self::Id; + + fn has_expired(&self) -> bool { + false + } +} + +/// A membership with a rating system +pub trait RankedMembership: Membership +where + Rank: Eq + Ord, +{ + fn rank(&self) -> &Rank; + fn rank_mut(&mut self) -> &mut Rank; +} + +/// A generic rank in the range 0 to 100 +#[derive( + Clone, + Copy, + Debug, + Default, + Eq, + Ord, + PartialEq, + PartialOrd, + codec::Decode, + codec::Encode, + codec::MaxEncodedLen, + scale_info::TypeInfo, +)] +pub struct GenericRank(u8); +impl GenericRank { + pub const MIN: Self = GenericRank(0); + pub const MAX: Self = GenericRank(100); + pub const ADMIN: Self = Self::MAX; + + pub fn set(&mut self, n: u8) { + *self = Self(n.min(Self::MAX.0)) + } + pub fn promote_by(&mut self, n: NonZeroU8) { + *self = Self(self.0.saturating_add(n.get()).min(Self::MAX.0)) + } + pub fn demote_by(&mut self, n: NonZeroU8) { + *self = Self(self.0.saturating_sub(n.get()).max(Self::MIN.0)) + } +} +impl From for GenericRank { + fn from(value: u8) -> Self { + Self(value) + } +} + +const MEMBERSHIP_INFO_ATTRIBUTE: [u8; 10] = *b"memberinfo"; + +impl MembershipInspect for T +where + T: super::tokens::nonfungible_v2::Inspect + + super::tokens::nonfungible_v2::InspectEnumerable< + AccountId, + OwnedIterator = crate::storage::KeyPrefixIterator< + >::ItemId, + >, + >, + M: Membership, + AccountId: Eq, +{ + type MembershipsIter = crate::storage::KeyPrefixIterator; + + fn get_membership(id: impl Into, member: &AccountId) -> Option { + let id = id.into(); + T::owner(&id).and_then(|o| member.eq(&o).then_some(()))?; + T::typed_system_attribute(&id, &MEMBERSHIP_INFO_ATTRIBUTE) + } + + fn account_memberships(member: &AccountId) -> Self::MembershipsIter { + T::owned(member) + } +} + +impl MembershipMutate for T +where + T: super::tokens::nonfungible_v2::Mutate + + super::tokens::nonfungible_v2::Transfer, + M: Membership, + AccountId: Eq, +{ + fn update( + id: impl Into, + membership: M, + maybe_member: Option, + ) -> DispatchResult { + let id = id.into(); + if let Some(new_owner) = maybe_member { + T::transfer(&id, &new_owner)?; + } + T::set_typed_attribute(&id, &MEMBERSHIP_INFO_ATTRIBUTE, &membership) + } +} diff --git a/substrate/frame/support/src/traits/tokens/nonfungible_v2.rs b/substrate/frame/support/src/traits/tokens/nonfungible_v2.rs index 05f76e2859d2..e152a2ada6b5 100644 --- a/substrate/frame/support/src/traits/tokens/nonfungible_v2.rs +++ b/substrate/frame/support/src/traits/tokens/nonfungible_v2.rs @@ -120,14 +120,16 @@ pub trait InspectEnumerable: Inspect { /// Trait for providing an interface for NFT-like items which may be minted, burned and/or have /// attributes and metadata set on them. -pub trait Mutate: Inspect { +pub trait Mutate: Inspect { + type ItemConfig; + /// Mint some `item` to be owned by `who`. /// /// By default, this is not a supported operation. fn mint_into( _item: &Self::ItemId, _who: &AccountId, - _config: &ItemConfig, + _config: &Self::ItemConfig, _deposit_collection_owner: bool, ) -> DispatchResult { Err(TokenError::Unsupported.into()) @@ -270,19 +272,21 @@ impl< } impl< - F: nonfungibles::Mutate, + F: nonfungibles::Mutate, A: Get<>::CollectionId>, AccountId, ItemConfig, - > Mutate for ItemOf + > Mutate for ItemOf { + type ItemConfig = ItemConfig; + fn mint_into( item: &Self::ItemId, who: &AccountId, - config: &ItemConfig, + config: &Self::ItemConfig, deposit_collection_owner: bool, ) -> DispatchResult { - >::mint_into( + >::mint_into( &A::get(), item, who, @@ -291,37 +295,23 @@ impl< ) } fn burn(item: &Self::ItemId, maybe_check_owner: Option<&AccountId>) -> DispatchResult { - >::burn(&A::get(), item, maybe_check_owner) + >::burn(&A::get(), item, maybe_check_owner) } fn set_attribute(item: &Self::ItemId, key: &[u8], value: &[u8]) -> DispatchResult { - >::set_attribute( - &A::get(), - item, - key, - value, - ) + >::set_attribute(&A::get(), item, key, value) } fn set_typed_attribute( item: &Self::ItemId, key: &K, value: &V, ) -> DispatchResult { - >::set_typed_attribute( - &A::get(), - item, - key, - value, - ) + >::set_typed_attribute(&A::get(), item, key, value) } fn clear_attribute(item: &Self::ItemId, key: &[u8]) -> DispatchResult { - >::clear_attribute(&A::get(), item, key) + >::clear_attribute(&A::get(), item, key) } fn clear_typed_attribute(item: &Self::ItemId, key: &K) -> DispatchResult { - >::clear_typed_attribute( - &A::get(), - item, - key, - ) + >::clear_typed_attribute(&A::get(), item, key) } } diff --git a/substrate/frame/support/src/traits/tokens/nonfungibles_v2.rs b/substrate/frame/support/src/traits/tokens/nonfungibles_v2.rs index c0209b6d5123..c57f514d8bc4 100644 --- a/substrate/frame/support/src/traits/tokens/nonfungibles_v2.rs +++ b/substrate/frame/support/src/traits/tokens/nonfungibles_v2.rs @@ -237,7 +237,9 @@ pub trait Destroy: Inspect { /// Trait for providing an interface for multiple collections of NFT-like items which may be /// minted, burned and/or have attributes and metadata set on them. -pub trait Mutate: Inspect { +pub trait Mutate: Inspect { + type ItemConfig; + /// Mint some `item` of `collection` to be owned by `who`. /// /// By default, this is not a supported operation. @@ -245,7 +247,7 @@ pub trait Mutate: Inspect { _collection: &Self::CollectionId, _item: &Self::ItemId, _who: &AccountId, - _config: &ItemConfig, + _config: &Self::ItemConfig, _deposit_collection_owner: bool, ) -> DispatchResult { Err(TokenError::Unsupported.into())