From d15bc44d3ad999a4f2dfe8b6de24e0b398f696df Mon Sep 17 00:00:00 2001 From: Daniel Shiposha Date: Mon, 23 May 2022 15:42:52 +0000 Subject: [PATCH] feat: add rmrk proxy nft minting --- --- a/pallets/nonfungible/src/lib.rs +++ b/pallets/nonfungible/src/lib.rs @@ -22,7 +22,7 @@ use up_data_structs::{ AccessMode, CollectionId, CustomDataLimit, TokenId, CreateCollectionData, CreateNftExData, mapping::TokenAddressMapping, NestingRule, budget::Budget, Property, PropertyPermission, - PropertyKey, PropertyKeyPermission, Properties, TrySetProperty, + PropertyKey, PropertyKeyPermission, Properties, PropertyScope, TrySetProperty, }; use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm}; use pallet_common::{ @@ -193,6 +193,38 @@ pub fn token_exists(collection: &NonfungibleHandle, token: TokenId) -> bool { >::contains_key((collection.id, token)) } + + pub fn set_scoped_token_property( + collection: &CollectionHandle, + token_id: TokenId, + scope: PropertyScope, + property: Property, + ) -> DispatchResult { + TokenProperties::::try_mutate((collection.id, token_id), |properties| { + properties.try_scoped_set(scope, property.key, property.value) + }) + .map_err(>::from)?; + + Ok(()) + } + + pub fn set_scoped_token_properties( + collection: &CollectionHandle, + token_id: TokenId, + scope: PropertyScope, + properties: impl Iterator, + ) -> DispatchResult { + TokenProperties::::try_mutate((collection.id, token_id), |stored_properties| { + stored_properties.try_scoped_set_from_iter(scope, properties) + }) + .map_err(>::from)?; + + Ok(()) + } + + pub fn current_token_id(collection: &CollectionHandle) -> TokenId { + TokenId(>::get(collection.id)) + } } // unchecked calls skips any permission checks --- a/pallets/proxy-rmrk-core/src/lib.rs +++ b/pallets/proxy-rmrk-core/src/lib.rs @@ -18,7 +18,8 @@ use frame_support::{pallet_prelude::*, transactional, BoundedVec, dispatch::DispatchResult}; use frame_system::{pallet_prelude::*, ensure_signed}; -use sp_runtime::{DispatchError, traits::StaticLookup}; +use sp_runtime::{DispatchError, Permill, traits::StaticLookup}; +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}; @@ -69,19 +70,28 @@ issuer: T::AccountId, collection_id: RmrkCollectionId, }, + NftMinted { + owner: T::AccountId, + collection_id: RmrkCollectionId, + nft_id: RmrkNftId, + }, } #[pallet::error] pub enum Error { /* Unique-specific events */ CorruptedCollectionType, + NftTypeEncodeError, RmrkPropertyKeyIsTooLong, RmrkPropertyValueIsTooLong, /* RMRK compatible events */ CollectionNotEmpty, NoAvailableCollectionId, + NoAvailableNftId, CollectionUnknown, + NoPermission, + CollectionFullOrLocked, } #[pallet::call] @@ -147,9 +157,7 @@ let unique_collection_id = collection_id.into(); - let collection = Self::get_nft_collection(unique_collection_id)?; - - Self::check_collection_type(unique_collection_id, CollectionType::Regular)?; + let collection = Self::get_typed_nft_collection(unique_collection_id, CollectionType::Regular)?; ensure!(collection.total_supply() == 0, >::CollectionNotEmpty); @@ -196,7 +204,10 @@ let sender = ensure_signed(origin)?; let cross_sender = T::CrossAccountId::from_sub(sender.clone()); - let collection = Self::get_nft_collection(collection_id.into())?; + let collection = Self::get_typed_nft_collection( + collection_id.into(), + CollectionType::Regular + )?; collection.check_is_owner(&cross_sender)?; let token_count = collection.total_supply(); @@ -209,20 +220,113 @@ Ok(()) } + + #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))] + #[transactional] + pub fn mint_nft( + origin: OriginFor, + owner: T::AccountId, + collection_id: RmrkCollectionId, + recipient: Option, + royalty_amount: Option, + metadata: RmrkString, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + let sender = T::CrossAccountId::from_sub(sender); + let cross_owner = T::CrossAccountId::from_sub(owner.clone()); + + let royalty_info = royalty_amount.map(|amount| rmrk::RoyaltyInfo { + recipient: recipient.unwrap_or_else(|| owner.clone()), + amount + }); + + let nft_id = Self::create_nft( + sender, + cross_owner, + collection_id.into(), + CollectionType::Regular, + 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())?, + ].into_iter() + )?; + + Self::deposit_event(Event::NftMinted { + owner, + collection_id, + nft_id: nft_id.0 + }); + + Ok(()) + } } } impl Pallet { + fn create_nft( + sender: T::CrossAccountId, + owner: T::CrossAccountId, + collection_id: CollectionId, + collection_type: CollectionType, + 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() + .map_err(|_| >::NftTypeEncodeError)?, + properties: BoundedVec::default(), + owner, + }; + + let budget = budget::Value::new(2); + + >::create_item( + &collection, + &sender, + data, + &budget, + ).map_err(|err| { + map_common_err_to_proxy!( + match err { + NoPermission => NoPermission, + CollectionTokenLimitExceeded => CollectionFullOrLocked + } + ) + })?; + + let nft_id = >::current_token_id(&collection); + + >::set_scoped_token_properties( + &collection, + nft_id, + PropertyScope::Rmrk, + properties + )?; + + Ok(nft_id) + } + fn change_collection_owner( collection_id: CollectionId, collection_type: CollectionType, sender: T::AccountId, new_owner: T::AccountId, ) -> DispatchResult { - let mut collection = Self::get_nft_collection(collection_id)?.into_inner(); + let mut collection = Self::get_typed_nft_collection( + collection_id, + collection_type + )?.into_inner(); collection.check_is_owner(&T::CrossAccountId::from_sub(sender))?; - - Self::check_collection_type(collection_id, collection_type)?; collection.owner = new_owner; collection.save() @@ -257,7 +361,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)?) - .ok_or(>::CollectionUnknown)? // todo is that right? + .ok_or(>::NoAvailableNftId)? .clone(); Ok(nft_property) @@ -269,4 +373,13 @@ Ok(()) } + + fn get_typed_nft_collection( + collection_id: CollectionId, + collection_type: CollectionType + ) -> Result, DispatchError> { + Self::check_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 @@ -1,17 +1,9 @@ use super::*; use codec::{Encode, Decode}; -use pallet_nonfungible::NonfungibleHandle; +use pallet_nonfungible::{NonfungibleHandle, ItemData}; macro_rules! impl_rmrk_value { ($enum_name:path, decode_error: $error:ident) => { - impl IntoPropertyValue for $enum_name { - fn into_property_value(self) -> Result { - self.encode() - .try_into() - .map_err(|_| MiscError::RmrkPropertyValueIsTooLong) - } - } - impl TryFrom<&PropertyValue> for $enum_name { type Error = MiscError; @@ -26,6 +18,19 @@ }; } +#[macro_export] +macro_rules! map_common_err_to_proxy { + (match $err:ident { $($common_err:ident => $proxy_err:ident),+ }) => { + $( + if $err == >::$common_err.into() { + return >::$proxy_err.into() + } else + )+ { + $err + } + }; +} + pub enum MiscError { RmrkPropertyValueIsTooLong, CorruptedCollectionType, @@ -57,14 +62,26 @@ fn into_property_value(self) -> Result; } -impl> IntoPropertyValue for BoundedVec { +impl IntoPropertyValue for T { fn into_property_value(self) -> Result { - self.into_inner() + 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() + } +} + #[derive(Encode, Decode, PartialEq, Eq)] pub enum CollectionType { Regular, @@ -72,4 +89,13 @@ Base, } +#[derive(Encode, Decode, PartialEq, Eq)] +pub enum NftType { + Regular, + Resource, + FixedPart, + 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 @@ -4,8 +4,7 @@ pub enum RmrkProperty { Metadata, CollectionType, - Recipient, - Royalty, + RoyaltyInfo, Equipped, ResourceCollection, ResourcePriorities, @@ -48,8 +47,7 @@ match self { Self::Metadata => key!("metadata"), Self::CollectionType => key!("collection-type"), - Self::Recipient => key!("recipient"), - Self::Royalty => key!("royalty"), + Self::RoyaltyInfo => key!("royalty-info"), Self::Equipped => key!("equipped"), Self::ResourceCollection => key!("resource-collection"), Self::ResourcePriorities => key!("resource-priorities"), --- a/runtime/common/src/runtime_apis.rs +++ b/runtime/common/src/runtime_apis.rs @@ -188,10 +188,9 @@ }; let keys = [ - RmrkProperty::Royalty, + RmrkProperty::RoyaltyInfo, RmrkProperty::Metadata, RmrkProperty::Equipped, - RmrkProperty::Pending, // ?? "rmrk:recipient", "rmrk:nft-type", "rmrk:resource-collection", "rmrk:resource-priorities" ]; @@ -315,13 +314,13 @@ let collection_id = CollectionId(collection_id); let nft_id = TokenId(nft_id); - let keys = [ - RmrkProperty::Royalty, - RmrkProperty::Metadata, - RmrkProperty::Equipped, - RmrkProperty::Pending, - // ?? "rmrk:recipient", "rmrk:nft-type", "rmrk:resource-collection", "rmrk:resource-priorities" - ]; + // let keys = [ + // RmrkProperty::Royalty, + // RmrkProperty::Metadata, + // RmrkProperty::Equipped, + // RmrkProperty::Pending, + // // ?? "rmrk:recipient", "rmrk:nft-type", "rmrk:resource-collection", "rmrk:resource-priorities" + // ]; /*let resources = keys.into_iter().map( |key| BoundedVec::try_from( -- gitstuff