--- a/client/rpc/src/lib.rs +++ b/client/rpc/src/lib.rs @@ -30,7 +30,7 @@ // RMRK use rmrk_rpc::RmrkApi as RmrkRuntimeApi; use up_data_structs::{ - RmrkCollectionId, RmrkNftId, RmrkBaseId, RmrkNftChild, RmrkThemeName, RmrkPropertyKey, + RmrkCollectionId, RmrkNftId, RmrkBaseId, RmrkNftChild, RmrkThemeName, RmrkResourceId, }; @@ -248,7 +248,7 @@ fn collection_properties( &self, collection_id: RmrkCollectionId, - filter_keys: Option>, //String + filter_keys: Option>, at: Option, ) -> Result>; @@ -258,7 +258,7 @@ &self, collection_id: RmrkCollectionId, nft_id: RmrkNftId, - filter_keys: Option>, + filter_keys: Option>, at: Option, ) -> Result>; @@ -299,8 +299,8 @@ fn theme( &self, base_id: RmrkBaseId, - theme_name: RmrkThemeName, // String - filter_keys: Option>, + theme_name: String, + filter_keys: Option>, at: Option, ) -> Result>; } @@ -523,11 +523,22 @@ pass_method!(account_tokens(account_id: AccountId, collection_id: RmrkCollectionId) -> Vec, rmrk_api); pass_method!(nft_children(collection_id: RmrkCollectionId, nft_id: RmrkNftId) -> Vec, rmrk_api); pass_method!( - collection_properties(collection_id: RmrkCollectionId, filter_keys: Option>) -> Vec, + collection_properties( + collection_id: RmrkCollectionId, + + #[map(|keys| string_keys_to_bytes_keys(keys))] + filter_keys: Option> + ) -> Vec, rmrk_api ); pass_method!( - nft_properties(collection_id: RmrkCollectionId, nft_id: RmrkNftId, filter_keys: Option>) -> Vec, + nft_properties( + collection_id: RmrkCollectionId, + nft_id: RmrkNftId, + + #[map(|keys| string_keys_to_bytes_keys(keys))] + filter_keys: Option> + ) -> Vec, rmrk_api ); pass_method!(nft_resources(collection_id: RmrkCollectionId, nft_id: RmrkNftId) -> Vec, rmrk_api); @@ -535,7 +546,16 @@ pass_method!(base(base_id: RmrkBaseId) -> Option, rmrk_api); pass_method!(base_parts(base_id: RmrkBaseId) -> Vec, rmrk_api); pass_method!(theme_names(base_id: RmrkBaseId) -> Vec, rmrk_api); - pass_method!(theme(base_id: RmrkBaseId, theme_name: RmrkThemeName, filter_keys: Option>) -> Option, rmrk_api); + pass_method!( + theme( + base_id: RmrkBaseId, + + #[map(|n| n.into_bytes())] + theme_name: String, + + #[map(|keys| string_keys_to_bytes_keys(keys))] + filter_keys: Option> + ) -> Option, rmrk_api); } fn string_keys_to_bytes_keys(keys: Option>) -> Option>> { --- a/pallets/common/src/lib.rs +++ b/pallets/common/src/lib.rs @@ -795,11 +795,11 @@ } pub fn set_scoped_collection_property( - collection: &CollectionHandle, + collection_id: CollectionId, scope: PropertyScope, property: Property, ) -> DispatchResult { - CollectionProperties::::try_mutate(collection.id, |properties| { + CollectionProperties::::try_mutate(collection_id, |properties| { properties.try_scoped_set(scope, property.key, property.value) }) .map_err(>::from)?; @@ -807,13 +807,12 @@ Ok(()) } - #[transactional] pub fn set_scoped_collection_properties( - collection: &CollectionHandle, + collection_id: CollectionId, scope: PropertyScope, properties: impl Iterator, ) -> DispatchResult { - CollectionProperties::::try_mutate(collection.id, |stored_properties| { + CollectionProperties::::try_mutate(collection_id, |stored_properties| { stored_properties.try_scoped_set_from_iter(scope, properties) }) .map_err(>::from)?; --- a/pallets/nonfungible/src/lib.rs +++ b/pallets/nonfungible/src/lib.rs @@ -195,12 +195,12 @@ } pub fn set_scoped_token_property( - collection: &CollectionHandle, + collection_id: CollectionId, token_id: TokenId, scope: PropertyScope, property: Property, ) -> DispatchResult { - TokenProperties::::try_mutate((collection.id, token_id), |properties| { + TokenProperties::::try_mutate((collection_id, token_id), |properties| { properties.try_scoped_set(scope, property.key, property.value) }) .map_err(>::from)?; @@ -209,12 +209,12 @@ } pub fn set_scoped_token_properties( - collection: &CollectionHandle, + collection_id: CollectionId, token_id: TokenId, scope: PropertyScope, properties: impl Iterator, ) -> DispatchResult { - TokenProperties::::try_mutate((collection.id, token_id), |stored_properties| { + TokenProperties::::try_mutate((collection_id, token_id), |stored_properties| { stored_properties.try_scoped_set_from_iter(scope, properties) }) .map_err(>::from)?; @@ -222,8 +222,8 @@ Ok(()) } - pub fn current_token_id(collection: &CollectionHandle) -> TokenId { - TokenId(>::get(collection.id)) + pub fn current_token_id(collection_id: CollectionId) -> TokenId { + TokenId(>::get(collection_id)) } } --- a/pallets/proxy-rmrk-core/src/lib.rs +++ b/pallets/proxy-rmrk-core/src/lib.rs @@ -33,6 +33,8 @@ use misc::*; pub use property::*; +use RmrkProperty::*; + #[frame_support::pallet] pub mod pallet { use super::*; @@ -135,15 +137,13 @@ } let collection_id = collection_id_res?; - - let collection = Self::get_nft_collection(collection_id)?.into_inner(); >::set_scoped_collection_properties( - &collection, + collection_id, PropertyScope::Rmrk, [ - rmrk_property!(Config=T, Metadata: metadata)?, - rmrk_property!(Config=T, CollectionType: CollectionType::Regular)?, + Self::rmrk_property(Metadata, &metadata)?, + Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?, ].into_iter() )?; @@ -168,7 +168,7 @@ let unique_collection_id = collection_id.into(); - let collection = Self::get_typed_nft_collection(unique_collection_id, CollectionType::Regular)?; + let collection = Self::get_typed_nft_collection(unique_collection_id, misc::CollectionType::Regular)?; ensure!(collection.total_supply() == 0, >::CollectionNotEmpty); @@ -193,7 +193,7 @@ Self::change_collection_owner( collection_id.into(), - CollectionType::Regular, + misc::CollectionType::Regular, sender.clone(), new_issuer.clone() )?; @@ -218,7 +218,7 @@ let collection = Self::get_typed_nft_collection( collection_id.into(), - CollectionType::Regular + misc::CollectionType::Regular )?; Self::check_collection_owner(&collection, &cross_sender)?; @@ -253,20 +253,27 @@ amount }); + let collection = Self::get_typed_nft_collection( + collection_id.into(), + misc::CollectionType::Regular, + )?; + let nft_id = Self::create_nft( &sender, &cross_owner, - collection_id.into(), - CollectionType::Regular, + &collection, NftType::Regular, [ - rmrk_property!(Config=T, RoyaltyInfo: royalty_info)?, - rmrk_property!(Config=T, Metadata: metadata)?, - rmrk_property!(Config=T, Equipped: false)?, - rmrk_property!(Config=T, ResourceCollection: None::)?, - rmrk_property!(Config=T, ResourcePriorities: >::new())?, + Self::rmrk_property(RoyaltyInfo, &royalty_info)?, + Self::rmrk_property(Metadata, &metadata)?, + Self::rmrk_property(Equipped, &false)?, + Self::rmrk_property(ResourceCollection, &None::)?, + Self::rmrk_property(ResourcePriorities, &>::new())?, ].into_iter() - )?; + ).map_err(|err| match err { + DispatchError::Arithmetic(_) => >::NoAvailableNftId.into(), + err => Self::map_common_err_to_proxy(err) + })?; Self::deposit_event(Event::NftMinted { owner, @@ -290,7 +297,7 @@ Self::destroy_nft( cross_sender, collection_id.into(), - CollectionType::Regular, + misc::CollectionType::Regular, nft_id.into() )?; @@ -302,19 +309,37 @@ } impl Pallet { + pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result { + let key = rmrk_key.to_key::()?; + + let scoped_key = PropertyScope::Rmrk.apply(key) + .map_err(|_| >::RmrkPropertyKeyIsTooLong)?; + + Ok(scoped_key) + } + + pub fn rmrk_property(rmrk_key: RmrkProperty, value: &E) -> Result { + let key = rmrk_key.to_key::()?; + + let value = value.encode() + .try_into() + .map_err(|_| >::RmrkPropertyValueIsTooLong)?; + + let property = Property { + key, + value, + }; + + Ok(property) + } + pub fn create_nft( sender: &T::CrossAccountId, owner: &T::CrossAccountId, - collection_id: CollectionId, - collection_type: CollectionType, + collection: &NonfungibleHandle, nft_type: NftType, properties: impl Iterator ) -> Result { - let collection = Self::get_typed_nft_collection( - collection_id, - collection_type - )?; - let data = CreateNftExData { const_data: nft_type.encode() .try_into() @@ -326,16 +351,16 @@ let budget = budget::Value::new(2); >::create_item( - &collection, + collection, sender, data, &budget, - ).map_err(Self::map_common_err_to_proxy)?; + )?; - let nft_id = >::current_token_id(&collection); + let nft_id = >::current_token_id(collection.id); >::set_scoped_token_properties( - &collection, + collection.id, nft_id, PropertyScope::Rmrk, properties @@ -347,7 +372,7 @@ fn destroy_nft( sender: T::CrossAccountId, collection_id: CollectionId, - collection_type: CollectionType, + collection_type: misc::CollectionType, token_id: TokenId ) -> DispatchResult { let collection = Self::get_typed_nft_collection( @@ -363,7 +388,7 @@ fn change_collection_owner( collection_id: CollectionId, - collection_type: CollectionType, + collection_type: misc::CollectionType, sender: T::AccountId, new_owner: T::AccountId, ) -> DispatchResult { @@ -390,10 +415,12 @@ pub fn get_nft_collection(collection_id: CollectionId) -> Result, DispatchError> { let collection = >::try_get(collection_id) - .map_err(|_| >::CollectionUnknown)? - .into_nft_collection()?; + .map_err(|_| >::CollectionUnknown)?; - Ok(collection) + match collection.mode { + CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)), + _ => Err(>::CollectionUnknown.into()) + } } // should this even be here, might displace it to common/nonfungible -- but they did not need it, only rmrk does @@ -407,23 +434,23 @@ pub fn get_collection_property(collection_id: CollectionId, key: RmrkProperty) -> Result { let collection_property = >::collection_properties(collection_id) - .get(&rmrk_property!(Config=T, key)?) + .get(&Self::rmrk_property_key(key)?) .ok_or(>::CollectionUnknown)? .clone(); Ok(collection_property) } - pub fn get_collection_type(collection_id: CollectionId) -> Result { - let value = Self::get_collection_property(collection_id, RmrkProperty::CollectionType)?; - let collection_type: CollectionType = (&value) - .try_into() - .map_err(>::from)?; + pub fn get_collection_type(collection_id: CollectionId) -> Result { + let value = Self::get_collection_property(collection_id, CollectionType)?; + + let mut value = value.as_slice(); - Ok(collection_type) + misc::CollectionType::decode(&mut value) + .map_err(|_| >::CorruptedCollectionType.into()) } - pub fn ensure_collection_type(collection_id: CollectionId, collection_type: CollectionType) -> DispatchResult { + pub fn ensure_collection_type(collection_id: CollectionId, collection_type: misc::CollectionType) -> DispatchResult { let actual_type = Self::get_collection_type(collection_id)?; ensure!(actual_type == collection_type, >::NoPermission); @@ -432,7 +459,7 @@ 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)?) + .get(&Self::rmrk_property_key(key)?) .ok_or(>::NoAvailableNftId)? .clone(); @@ -440,10 +467,12 @@ } pub fn get_nft_type(collection_id: CollectionId, token_id: TokenId) -> Result { - >::get((collection_id, token_id)) - .unwrap() - .rmrk_nft_type() - .ok_or_else(|| >::NoAvailableNftId.into()) + let token_data = >::get((collection_id, token_id)) + .ok_or(>::NoAvailableNftId)?; + + let mut const_data = token_data.const_data.as_slice(); + + NftType::decode(&mut const_data).map_err(|_| >::NoAvailableNftId.into()) } pub fn ensure_nft_type(collection_id: CollectionId, token_id: TokenId, nft_type: NftType) -> DispatchResult { @@ -466,7 +495,7 @@ let value = Self::get_nft_property( collection_id, token_id, - RmrkProperty::ThemeProperty(&key) + ThemeProperty(&key) ).ok()?.decode_or_default(); let property = RmrkThemeProperty { @@ -491,7 +520,7 @@ collection_id: CollectionId, token_id: TokenId ) -> Result, DispatchError> { - let key_prefix = rmrk_property!(Config=T, key: ThemeProperty(&RmrkString::default()))?; + let key_prefix = Self::rmrk_property_key(ThemeProperty(&RmrkString::default()))?; let properties = >::token_properties((collection_id, token_id)) .into_iter() @@ -514,7 +543,7 @@ pub fn get_typed_nft_collection( collection_id: CollectionId, - collection_type: CollectionType + collection_type: misc::CollectionType ) -> Result, DispatchError> { Self::ensure_collection_type(collection_id, collection_type)?; --- a/pallets/proxy-rmrk-core/src/misc.rs +++ b/pallets/proxy-rmrk-core/src/misc.rs @@ -1,23 +1,6 @@ use super::*; use codec::{Encode, Decode}; -use pallet_nonfungible::{NonfungibleHandle, ItemData}; - -macro_rules! impl_rmrk_value { - ($enum_name:path, decode_error: $error:ident) => { - impl TryFrom<&PropertyValue> for $enum_name { - type Error = MiscError; - - fn try_from(value: &PropertyValue) -> Result { - let mut value = value.as_slice(); - <$enum_name>::decode(&mut value) - .map_err(|_| MiscError::$error) - } - } - - }; -} - #[macro_export] macro_rules! map_common_err_to_proxy { (match $err:ident { $($common_err:ident => $proxy_err:ident),+ }) => { @@ -29,59 +12,8 @@ $err } }; -} - -pub enum MiscError { - RmrkPropertyValueIsTooLong, - CorruptedCollectionType, -} - -impl From for Error { - fn from(error: MiscError) -> Self { - match error { - MiscError::RmrkPropertyValueIsTooLong => Self::RmrkPropertyValueIsTooLong, - MiscError::CorruptedCollectionType => Self::CorruptedCollectionType, - } - } -} - -pub trait IntoNftCollection { - fn into_nft_collection(self) -> Result, Error>; } -impl IntoNftCollection for CollectionHandle { - fn into_nft_collection(self) -> Result, Error> { - match self.mode { - CollectionMode::NFT => Ok(NonfungibleHandle::cast(self)), - _ => Err(>::CollectionUnknown) - } - } -} - -pub trait IntoPropertyValue { - fn into_property_value(self) -> Result; -} - -impl IntoPropertyValue for T { - fn into_property_value(self) -> Result { - self.encode() - .try_into() - .map_err(|_| MiscError::RmrkPropertyValueIsTooLong) - } -} - -pub trait RmrkNft { - fn rmrk_nft_type(&self) -> Option; -} - -impl RmrkNft for ItemData { - fn rmrk_nft_type(&self) -> Option { - let mut value = self.const_data.as_slice(); - - NftType::decode(&mut value).ok() - } -} - pub trait RmrkDecode { fn decode_or_default(&self) -> T; } @@ -121,5 +53,3 @@ SlotPart, Theme } - -impl_rmrk_value!(CollectionType, decode_error: CorruptedCollectionType); --- a/pallets/proxy-rmrk-core/src/property.rs +++ b/pallets/proxy-rmrk-core/src/property.rs @@ -71,31 +71,3 @@ } } } - -#[macro_export] -macro_rules! rmrk_property { - (Config=$cfg:ty, key: $key:ident $(($key_ext:expr))?) => { - rmrk_property!(Config=$cfg, $crate::RmrkProperty::$key $(($key_ext))?) - }; - - (Config=$cfg:ty, $key:ident $(($key_ext:expr))?: $value:expr) => {{ - let key = rmrk_property!(@$cfg, $crate::RmrkProperty::$key $(($key_ext))?)?; - - let value = $value.into_property_value() - .map_err(<$crate::Error<$cfg>>::from)?; - - Ok::<_, $crate::Error<$cfg>>(Property { - key, - value, - }) - }}; - - (@$cfg:ty, $key_enum:expr) => { - $key_enum.to_key::<$cfg>() - }; - - (Config=$cfg:ty, $key_enum:expr) => { - PropertyScope::Rmrk.apply(rmrk_property!(@$cfg, $key_enum)?) - .map_err(|_| <$crate::Error<$cfg>>::RmrkPropertyKeyIsTooLong) - }; -} --- a/pallets/proxy-rmrk-equip/src/lib.rs +++ b/pallets/proxy-rmrk-equip/src/lib.rs @@ -20,9 +20,9 @@ use frame_system::{pallet_prelude::*, ensure_signed}; use sp_runtime::DispatchError; use up_data_structs::*; -use pallet_common::{Pallet as PalletCommon, Error as CommonError, CollectionHandle}; -use pallet_rmrk_core::{Pallet as PalletCore, rmrk_property, misc::*}; -use pallet_nonfungible::{Pallet as PalletNft}; +use pallet_common::{Pallet as PalletCommon, Error as CommonError}; +use pallet_rmrk_core::{Pallet as PalletCore, misc::{self, *}, property::RmrkProperty::*}; +use pallet_nonfungible::{Pallet as PalletNft, NonfungibleHandle}; use pallet_evm::account::CrossAccountId; pub use pallet::*; @@ -48,6 +48,16 @@ TokenId >; + #[pallet::storage] + #[pallet::getter(fn base_has_default_theme)] + pub type BaseHasDefaultTheme = StorageMap< + _, + Twox64Concat, + CollectionId, + bool, + ValueQuery + >; + #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] pub struct Pallet(_); @@ -63,7 +73,11 @@ #[pallet::error] pub enum Error { + PermissionError, NoAvailableBaseId, + NoAvailablePartId, + BaseDoesntExist, + NeedsDefaultThemeFirst, } #[pallet::call] @@ -95,17 +109,17 @@ let collection_id = collection_id_res?; - let collection = >::get_nft_collection(collection_id)?.into_inner(); - >::set_scoped_collection_properties( - &collection, + collection_id, PropertyScope::Rmrk, [ - rmrk_property!(Config=T, CollectionType: CollectionType::Base)?, - rmrk_property!(Config=T, BaseType: base_type)?, + >::rmrk_property(CollectionType, &misc::CollectionType::Base)?, + >::rmrk_property(BaseType, &base_type)?, ].into_iter() )?; + let collection = >::get_nft_collection(collection_id)?; + for part in parts { let part_id = part.id(); let part_token_id = Self::create_part( @@ -117,10 +131,10 @@ >::insert(collection_id, part_id, part_token_id); >::set_scoped_token_property( - &collection, + collection_id, part_token_id, PropertyScope::Rmrk, - rmrk_property!(Config=T, ExternalPartId: part_id)? + >::rmrk_property(ExternalPartId, &part_id)? )?; } @@ -128,13 +142,64 @@ Ok(()) } + + #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))] + #[transactional] + pub fn theme_add( + origin: OriginFor, + base_id: RmrkBaseId, + theme: RmrkTheme, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + + let sender = T::CrossAccountId::from_sub(sender); + let owner = &sender; + + let collection_id: CollectionId = base_id.into(); + + let collection = >::get_typed_nft_collection( + collection_id, + misc::CollectionType::Base + ).map_err(|_| >::BaseDoesntExist)?; + + if theme.name.as_slice() == b"default" { + >::insert(collection_id, true); + } else if !Self::base_has_default_theme(collection_id) { + return Err(>::NeedsDefaultThemeFirst.into()); + } + + let token_id = >::create_nft( + &sender, + owner, + &collection, + NftType::Theme, + [ + >::rmrk_property(ThemeName, &theme.name)?, + >::rmrk_property(ThemeInherit, &theme.inherit)? + ].into_iter() + ).map_err(|_| >::PermissionError)?; + + for property in theme.properties { + >::set_scoped_token_property( + collection_id, + token_id, + PropertyScope::Rmrk, + >::rmrk_property( + ThemeProperty(&property.key), + &property.value + )? + )?; + } + + Ok(()) + } } } impl Pallet { fn create_part( sender: &T::CrossAccountId, - collection: &CollectionHandle, + collection: &NonfungibleHandle, part: RmrkPartType ) -> Result { let owner = sender; @@ -150,21 +215,23 @@ let token_id = >::create_nft( sender, owner, - collection.id, - CollectionType::Base, + collection, nft_type, [ - rmrk_property!(Config=T, Src: src)?, - rmrk_property!(Config=T, ZIndex: z_index)? + >::rmrk_property(Src, &src)?, + >::rmrk_property(ZIndex, &z_index)? ].into_iter() - )?; + ).map_err(|err| match err { + DispatchError::Arithmetic(_) => >::NoAvailablePartId.into(), + err => err + })?; if let RmrkPartType::SlotPart(part) = part { >::set_scoped_token_property( - collection, + collection.id, token_id, PropertyScope::Rmrk, - rmrk_property!(Config=T, EquippableList: part.equippable)? + >::rmrk_property(EquippableList, &part.equippable)? )?; } --- a/runtime/common/src/runtime_apis.rs +++ b/runtime/common/src/runtime_apis.rs @@ -347,7 +347,7 @@ fn base_parts(base_id: RmrkBaseId) -> Result, DispatchError> { use frame_support::BoundedVec; - use pallet_proxy_rmrk_core::{RmrkProperty, misc::{CollectionType, NftType, RmrkNft, RmrkDecode}}; + use pallet_proxy_rmrk_core::{RmrkProperty, misc::{CollectionType, NftType, RmrkDecode}}; let collection_id = CollectionId(base_id); if RmrkCore::ensure_collection_type(collection_id, CollectionType::Base).is_err() { return Ok(Vec::new()); } @@ -379,7 +379,7 @@ fn theme_names(base_id: RmrkBaseId) -> Result, DispatchError> { use frame_support::BoundedVec; - use pallet_proxy_rmrk_core::{RmrkProperty, misc::{CollectionType, RmrkNft, RmrkDecode}}; + use pallet_proxy_rmrk_core::{RmrkProperty, misc::{CollectionType, RmrkDecode}}; let collection_id = CollectionId(base_id); if RmrkCore::ensure_collection_type(collection_id, CollectionType::Base).is_err() { @@ -407,7 +407,7 @@ use frame_support::BoundedVec; use pallet_proxy_rmrk_core::{ RmrkProperty, - misc::{CollectionType, NftType, RmrkNft, RmrkDecode} + misc::{CollectionType, NftType, RmrkDecode} }; let collection_id = CollectionId(base_id);