1234567891011121314151617#![cfg_attr(not(feature = "std"), no_std)]1819use frame_support::{pallet_prelude::*, transactional, BoundedVec, dispatch::DispatchResult};20use frame_system::{pallet_prelude::*, ensure_signed};21use sp_runtime::{DispatchError, Permill, traits::StaticLookup};22use sp_std::vec::Vec;23use up_data_structs::{*, mapping::TokenAddressMapping};24use pallet_common::{25 Pallet as PalletCommon, Error as CommonError, CollectionHandle, CommonCollectionOperations,26};27use pallet_nonfungible::{Pallet as PalletNft, NonfungibleHandle, TokenData};28use pallet_structure::{Pallet as PalletStructure, Error as StructureError};29use pallet_evm::account::CrossAccountId;30use core::convert::AsRef;3132pub use pallet::*;3334#[cfg(feature = "runtime-benchmarks")]35pub mod benchmarking;36pub mod misc;37pub mod property;38pub mod weights;3940pub type SelfWeightOf<T> = <T as Config>::WeightInfo;4142use weights::WeightInfo;43use misc::*;44pub use property::*;4546use RmrkProperty::*;4748pub const NESTING_BUDGET: u32 = 5;4950#[frame_support::pallet]51pub mod pallet {52 use super::*;53 use pallet_evm::account;5455 #[pallet::config]56 pub trait Config:57 frame_system::Config + pallet_common::Config + pallet_nonfungible::Config + account::Config58 {59 type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;60 type WeightInfo: WeightInfo;61 }6263 #[pallet::storage]64 #[pallet::getter(fn collection_index)]65 pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;6667 #[pallet::storage]68 pub type UniqueCollectionId<T: Config> =69 StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;7071 #[pallet::pallet]72 #[pallet::generate_store(pub(super) trait Store)]73 pub struct Pallet<T>(_);7475 #[pallet::event]76 #[pallet::generate_deposit(pub(super) fn deposit_event)]77 pub enum Event<T: Config> {78 CollectionCreated {79 issuer: T::AccountId,80 collection_id: RmrkCollectionId,81 },82 CollectionDestroyed {83 issuer: T::AccountId,84 collection_id: RmrkCollectionId,85 },86 IssuerChanged {87 old_issuer: T::AccountId,88 new_issuer: T::AccountId,89 collection_id: RmrkCollectionId,90 },91 CollectionLocked {92 issuer: T::AccountId,93 collection_id: RmrkCollectionId,94 },95 NftMinted {96 owner: T::AccountId,97 collection_id: RmrkCollectionId,98 nft_id: RmrkNftId,99 },100 NFTBurned {101 owner: T::AccountId,102 nft_id: RmrkNftId,103 },104 NFTSent {105 sender: T::AccountId,106 recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,107 collection_id: RmrkCollectionId,108 nft_id: RmrkNftId,109 approval_required: bool,110 },111 NFTAccepted {112 sender: T::AccountId,113 recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,114 collection_id: RmrkCollectionId,115 nft_id: RmrkNftId,116 },117 NFTRejected {118 sender: T::AccountId,119 collection_id: RmrkCollectionId,120 nft_id: RmrkNftId,121 },122 PropertySet {123 collection_id: RmrkCollectionId,124 maybe_nft_id: Option<RmrkNftId>,125 key: RmrkKeyString,126 value: RmrkValueString,127 },128 ResourceAdded {129 nft_id: RmrkNftId,130 resource_id: RmrkResourceId,131 },132 ResourceRemoval {133 nft_id: RmrkNftId,134 resource_id: RmrkResourceId,135 },136 ResourceAccepted {137 nft_id: RmrkNftId,138 resource_id: RmrkResourceId,139 },140 ResourceRemovalAccepted {141 nft_id: RmrkNftId,142 resource_id: RmrkResourceId,143 },144 PrioritySet {145 collection_id: RmrkCollectionId,146 nft_id: RmrkNftId,147 },148 }149150 #[pallet::error]151 pub enum Error<T> {152 153 CorruptedCollectionType,154 NftTypeEncodeError,155 RmrkPropertyKeyIsTooLong,156 RmrkPropertyValueIsTooLong,157 RmrkPropertyIsNotFound,158 UnableToDecodeRmrkData,159160 161 CollectionNotEmpty,162 NoAvailableCollectionId,163 NoAvailableNftId,164 CollectionUnknown,165 NoPermission,166 NonTransferable,167 CollectionFullOrLocked,168 ResourceDoesntExist,169 CannotSendToDescendentOrSelf,170 CannotAcceptNonOwnedNft,171 CannotRejectNonOwnedNft,172 CannotRejectNonPendingNft,173 ResourceNotPending,174 NoAvailableResourceId,175 }176177 #[pallet::call]178 impl<T: Config> Pallet<T> {179 180 #[transactional]181 #[pallet::weight(<SelfWeightOf<T>>::create_collection())]182 pub fn create_collection(183 origin: OriginFor<T>,184 metadata: RmrkString,185 max: Option<u32>,186 symbol: RmrkCollectionSymbol,187 ) -> DispatchResult {188 let sender = ensure_signed(origin)?;189190 let limits = CollectionLimits {191 owner_can_transfer: Some(false),192 token_limit: max,193 ..Default::default()194 };195196 let data = CreateCollectionData {197 limits: Some(limits),198 token_prefix: symbol199 .into_inner()200 .try_into()201 .map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,202 permissions: Some(CollectionPermissions {203 nesting: Some(NestingPermissions {204 token_owner: true,205 collection_admin: false,206 restricted: None,207208 permissive: false,209 }),210 ..Default::default()211 }),212 ..Default::default()213 };214215 let unique_collection_id = Self::init_collection(216 T::CrossAccountId::from_sub(sender.clone()),217 data,218 [219 Self::rmrk_property(Metadata, &metadata)?,220 Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,221 ]222 .into_iter(),223 )?;224 let rmrk_collection_id = <CollectionIndex<T>>::get();225226 <UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);227228 <PalletCommon<T>>::set_scoped_collection_property(229 unique_collection_id,230 PropertyScope::Rmrk,231 Self::rmrk_property(RmrkInternalCollectionId, &rmrk_collection_id)?,232 )?;233234 <CollectionIndex<T>>::mutate(|n| *n += 1);235236 Self::deposit_event(Event::CollectionCreated {237 issuer: sender,238 collection_id: rmrk_collection_id,239 });240241 Ok(())242 }243244 245 #[transactional]246 #[pallet::weight(<SelfWeightOf<T>>::destroy_collection())]247 pub fn destroy_collection(248 origin: OriginFor<T>,249 collection_id: RmrkCollectionId,250 ) -> DispatchResult {251 let sender = ensure_signed(origin)?;252 let cross_sender = T::CrossAccountId::from_sub(sender.clone());253254 let collection = Self::get_typed_nft_collection(255 Self::unique_collection_id(collection_id)?,256 misc::CollectionType::Regular,257 )?;258 collection.check_is_external()?;259260 <PalletNft<T>>::destroy_collection(collection, &cross_sender)261 .map_err(Self::map_unique_err_to_proxy)?;262263 Self::deposit_event(Event::CollectionDestroyed {264 issuer: sender,265 collection_id,266 });267268 Ok(())269 }270271 272 273 274 275 276 277 #[transactional]278 #[pallet::weight(<SelfWeightOf<T>>::change_collection_issuer())]279 pub fn change_collection_issuer(280 origin: OriginFor<T>,281 collection_id: RmrkCollectionId,282 new_issuer: <T::Lookup as StaticLookup>::Source,283 ) -> DispatchResult {284 let sender = ensure_signed(origin)?;285286 let collection = Self::get_nft_collection(Self::unique_collection_id(collection_id)?)?;287 collection.check_is_external()?;288289 let new_issuer = T::Lookup::lookup(new_issuer)?;290291 Self::change_collection_owner(292 Self::unique_collection_id(collection_id)?,293 misc::CollectionType::Regular,294 sender.clone(),295 new_issuer.clone(),296 )?;297298 Self::deposit_event(Event::IssuerChanged {299 old_issuer: sender,300 new_issuer,301 collection_id,302 });303304 Ok(())305 }306307 308 #[transactional]309 #[pallet::weight(<SelfWeightOf<T>>::lock_collection())]310 pub fn lock_collection(311 origin: OriginFor<T>,312 collection_id: RmrkCollectionId,313 ) -> DispatchResult {314 let sender = ensure_signed(origin)?;315 let cross_sender = T::CrossAccountId::from_sub(sender.clone());316317 let collection = Self::get_typed_nft_collection(318 Self::unique_collection_id(collection_id)?,319 misc::CollectionType::Regular,320 )?;321 collection.check_is_external()?;322323 Self::check_collection_owner(&collection, &cross_sender)?;324325 let token_count = collection.total_supply();326327 let mut collection = collection.into_inner();328 collection.limits.token_limit = Some(token_count);329 collection.save()?;330331 Self::deposit_event(Event::CollectionLocked {332 issuer: sender,333 collection_id,334 });335336 Ok(())337 }338339 340 341 342 343 344 345 346 347 348 349 #[transactional]350 #[pallet::weight(<SelfWeightOf<T>>::mint_nft(resources.as_ref().map(|r| r.len() as u32).unwrap_or(0)))]351 pub fn mint_nft(352 origin: OriginFor<T>,353 owner: T::AccountId,354 collection_id: RmrkCollectionId,355 recipient: Option<T::AccountId>,356 royalty_amount: Option<Permill>,357 metadata: RmrkString,358 transferable: bool,359 resources: Option<BoundedVec<RmrkResourceTypes, MaxResourcesOnMint>>,360 ) -> DispatchResult {361 let sender = ensure_signed(origin)?;362 let cross_sender = T::CrossAccountId::from_sub(sender.clone());363 let cross_owner = T::CrossAccountId::from_sub(owner.clone());364365 let collection = Self::get_typed_nft_collection(366 Self::unique_collection_id(collection_id)?,367 misc::CollectionType::Regular,368 )?;369 collection.check_is_external()?;370371 let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {372 recipient: recipient.unwrap_or_else(|| owner.clone()),373 amount,374 });375376 let nft_id = Self::create_nft(377 &cross_sender,378 &cross_owner,379 &collection,380 [381 Self::rmrk_property(TokenType, &NftType::Regular)?,382 Self::rmrk_property(Transferable, &transferable)?,383 Self::rmrk_property(PendingNftAccept, &false)?,384 Self::rmrk_property(RoyaltyInfo, &royalty_info)?,385 Self::rmrk_property(Metadata, &metadata)?,386 Self::rmrk_property(Equipped, &false)?,387 Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,388 Self::rmrk_property(NextResourceId, &(0 as RmrkResourceId))?,389 ]390 .into_iter(),391 )392 .map_err(|err| match err {393 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),394 err => Self::map_unique_err_to_proxy(err),395 })?;396397 if let Some(resources) = resources {398 for resource in resources {399 Self::resource_add(sender.clone(), collection.id, nft_id, resource)?;400 }401 }402403 Self::deposit_event(Event::NftMinted {404 owner,405 collection_id,406 nft_id: nft_id.0,407 });408409 Ok(())410 }411412 413 #[transactional]414 #[pallet::weight(<SelfWeightOf<T>>::burn_nft(*max_burns))]415 pub fn burn_nft(416 origin: OriginFor<T>,417 collection_id: RmrkCollectionId,418 nft_id: RmrkNftId,419 max_burns: u32,420 ) -> DispatchResult {421 let sender = ensure_signed(origin)?;422 let cross_sender = T::CrossAccountId::from_sub(sender.clone());423424 let collection = Self::get_typed_nft_collection(425 Self::unique_collection_id(collection_id)?,426 misc::CollectionType::Regular,427 )?;428 collection.check_is_external()?;429430 Self::destroy_nft(431 cross_sender,432 Self::unique_collection_id(collection_id)?,433 nft_id.into(),434 max_burns,435 <Error<T>>::NoPermission,436 )437 .map_err(|err| Self::map_unique_err_to_proxy(err.error))?;438439 Self::deposit_event(Event::NFTBurned {440 owner: sender,441 nft_id,442 });443444 Ok(())445 }446447 448 449 450 451 452 453 454 #[transactional]455 #[pallet::weight(<SelfWeightOf<T>>::send())]456 pub fn send(457 origin: OriginFor<T>,458 rmrk_collection_id: RmrkCollectionId,459 rmrk_nft_id: RmrkNftId,460 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,461 ) -> DispatchResult {462 let sender = ensure_signed(origin.clone())?;463 let cross_sender = T::CrossAccountId::from_sub(sender.clone());464465 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;466 let nft_id = rmrk_nft_id.into();467468 let collection =469 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;470 collection.check_is_external()?;471472 let token_data =473 <TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;474475 let from = token_data.owner;476477 ensure!(478 Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,479 <Error<T>>::NonTransferable480 );481482 ensure!(483 !Self::get_nft_property_decoded(484 collection_id,485 nft_id,486 RmrkProperty::PendingNftAccept487 )?,488 <Error<T>>::NoPermission489 );490491 let target_owner;492 let approval_required;493494 match new_owner {495 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {496 target_owner = T::CrossAccountId::from_sub(account_id.clone());497 approval_required = false;498 }499 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(500 target_collection_id,501 target_nft_id,502 ) => {503 let target_collection_id = Self::unique_collection_id(target_collection_id)?;504505 let target_nft_budget = budget::Value::new(NESTING_BUDGET);506507 let target_nft_owner = <PalletStructure<T>>::get_checked_topmost_owner(508 target_collection_id,509 target_nft_id.into(),510 Some((collection_id, nft_id)),511 &target_nft_budget,512 )513 .map_err(Self::map_unique_err_to_proxy)?;514515 approval_required = cross_sender != target_nft_owner;516517 if approval_required {518 target_owner = target_nft_owner;519520 <PalletNft<T>>::set_scoped_token_property(521 collection.id,522 nft_id,523 PropertyScope::Rmrk,524 Self::rmrk_property(PendingNftAccept, &approval_required)?,525 )?;526 } else {527 target_owner = T::CrossTokenAddressMapping::token_to_address(528 target_collection_id,529 target_nft_id.into(),530 );531 }532 }533 }534535 let src_nft_budget = budget::Value::new(NESTING_BUDGET);536537 <PalletNft<T>>::transfer_from(538 &collection,539 &cross_sender,540 &from,541 &target_owner,542 nft_id,543 &src_nft_budget,544 )545 .map_err(Self::map_unique_err_to_proxy)?;546547 Self::deposit_event(Event::NFTSent {548 sender,549 recipient: new_owner,550 collection_id: rmrk_collection_id,551 nft_id: rmrk_nft_id,552 approval_required,553 });554555 Ok(())556 }557558 559 560 561 562 563 564 565 566 #[transactional]567 #[pallet::weight(<SelfWeightOf<T>>::accept_nft())]568 pub fn accept_nft(569 origin: OriginFor<T>,570 rmrk_collection_id: RmrkCollectionId,571 rmrk_nft_id: RmrkNftId,572 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,573 ) -> DispatchResult {574 let sender = ensure_signed(origin.clone())?;575 let cross_sender = T::CrossAccountId::from_sub(sender.clone());576577 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;578 let nft_id = rmrk_nft_id.into();579580 let collection =581 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;582 collection.check_is_external()?;583584 let new_cross_owner = match new_owner {585 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {586 T::CrossAccountId::from_sub(account_id.clone())587 }588 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(589 target_collection_id,590 target_nft_id,591 ) => {592 let target_collection_id = Self::unique_collection_id(target_collection_id)?;593594 T::CrossTokenAddressMapping::token_to_address(595 target_collection_id,596 TokenId(target_nft_id),597 )598 }599 };600601 let budget = budget::Value::new(NESTING_BUDGET);602603 <PalletNft<T>>::transfer(604 &collection,605 &cross_sender,606 &new_cross_owner,607 nft_id,608 &budget,609 )610 .map_err(|err| {611 if err == <CommonError<T>>::UserIsNotAllowedToNest.into() {612 <Error<T>>::CannotAcceptNonOwnedNft.into()613 } else {614 Self::map_unique_err_to_proxy(err)615 }616 })?;617618 <PalletNft<T>>::set_scoped_token_property(619 collection.id,620 nft_id,621 PropertyScope::Rmrk,622 Self::rmrk_property(PendingNftAccept, &false)?,623 )?;624625 Self::deposit_event(Event::NFTAccepted {626 sender,627 recipient: new_owner,628 collection_id: rmrk_collection_id,629 nft_id: rmrk_nft_id,630 });631632 Ok(())633 }634635 636 637 638 639 640 641 #[transactional]642 #[pallet::weight(<SelfWeightOf<T>>::reject_nft())]643 pub fn reject_nft(644 origin: OriginFor<T>,645 rmrk_collection_id: RmrkCollectionId,646 rmrk_nft_id: RmrkNftId,647 ) -> DispatchResult {648 let sender = ensure_signed(origin)?;649 let cross_sender = T::CrossAccountId::from_sub(sender.clone());650651 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;652 let nft_id = rmrk_nft_id.into();653654 let collection =655 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;656 collection.check_is_external()?;657658 ensure!(659 <TokenData<T>>::get((collection_id, nft_id)).is_some(),660 <Error<T>>::NoAvailableNftId661 );662663 ensure!(664 Self::get_nft_property_decoded(665 collection_id,666 nft_id,667 RmrkProperty::PendingNftAccept668 )?,669 <Error<T>>::CannotRejectNonPendingNft670 );671672 Self::destroy_nft(673 cross_sender,674 collection_id,675 nft_id,676 NESTING_BUDGET,677 <Error<T>>::CannotRejectNonOwnedNft,678 )679 .map_err(|err| Self::map_unique_err_to_proxy(err.error))?;680681 Self::deposit_event(Event::NFTRejected {682 sender,683 collection_id: rmrk_collection_id,684 nft_id: rmrk_nft_id,685 });686687 Ok(())688 }689690 691 #[transactional]692 #[pallet::weight(<SelfWeightOf<T>>::accept_resource())]693 pub fn accept_resource(694 origin: OriginFor<T>,695 rmrk_collection_id: RmrkCollectionId,696 rmrk_nft_id: RmrkNftId,697 resource_id: RmrkResourceId,698 ) -> DispatchResult {699 let sender = ensure_signed(origin)?;700 let cross_sender = T::CrossAccountId::from_sub(sender);701702 let collection_id = Self::unique_collection_id(rmrk_collection_id)703 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;704 let collection =705 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;706 collection.check_is_external()?;707708 let nft_id = rmrk_nft_id.into();709710 let budget = budget::Value::new(NESTING_BUDGET);711712 let nft_owner =713 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)714 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;715716 Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {717 ensure!(res.pending, <Error<T>>::ResourceNotPending);718 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);719720 res.pending = false;721722 Ok(())723 })?;724725 Self::deposit_event(Event::<T>::ResourceAccepted {726 nft_id: rmrk_nft_id,727 resource_id,728 });729730 Ok(())731 }732733 734 #[transactional]735 #[pallet::weight(<SelfWeightOf<T>>::accept_resource_removal())]736 pub fn accept_resource_removal(737 origin: OriginFor<T>,738 rmrk_collection_id: RmrkCollectionId,739 rmrk_nft_id: RmrkNftId,740 resource_id: RmrkResourceId,741 ) -> DispatchResult {742 let sender = ensure_signed(origin)?;743 let cross_sender = T::CrossAccountId::from_sub(sender);744745 let collection_id = Self::unique_collection_id(rmrk_collection_id)746 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;747 let collection =748 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;749 collection.check_is_external()?;750751 let nft_id = rmrk_nft_id.into();752753 let budget = budget::Value::new(NESTING_BUDGET);754755 let nft_owner =756 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)757 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;758759 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);760761 let resource_id_key = Self::rmrk_property_key(ResourceId(resource_id))?;762763 let resource_info = <PalletNft<T>>::token_sys_property((764 collection_id,765 nft_id,766 PropertyScope::Rmrk,767 resource_id_key.clone(),768 ))769 .ok_or(<Error<T>>::ResourceDoesntExist)?;770771 let resource_info: RmrkResourceInfo = Self::decode_property(&resource_info)?;772773 ensure!(774 resource_info.pending_removal,775 <Error<T>>::ResourceNotPending776 );777778 <PalletNft<T>>::remove_token_sys_property(779 collection_id,780 nft_id,781 PropertyScope::Rmrk,782 resource_id_key,783 );784785 Self::deposit_event(Event::<T>::ResourceRemovalAccepted {786 nft_id: rmrk_nft_id,787 resource_id,788 });789790 Ok(())791 }792793 794 #[transactional]795 #[pallet::weight(<SelfWeightOf<T>>::set_property())]796 pub fn set_property(797 origin: OriginFor<T>,798 #[pallet::compact] rmrk_collection_id: RmrkCollectionId,799 maybe_nft_id: Option<RmrkNftId>,800 key: RmrkKeyString,801 value: RmrkValueString,802 ) -> DispatchResult {803 let sender = ensure_signed(origin)?;804 let sender = T::CrossAccountId::from_sub(sender);805806 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;807 let collection =808 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;809 collection.check_is_external()?;810811 let budget = budget::Value::new(NESTING_BUDGET);812813 match maybe_nft_id {814 Some(nft_id) => {815 let token_id: TokenId = nft_id.into();816817 Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;818 Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;819820 <PalletNft<T>>::set_scoped_token_property(821 collection_id,822 token_id,823 PropertyScope::Rmrk,824 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,825 )?;826 }827 None => {828 let collection = Self::get_typed_nft_collection(829 collection_id,830 misc::CollectionType::Regular,831 )?;832833 Self::check_collection_owner(&collection, &sender)?;834835 <PalletCommon<T>>::set_scoped_collection_property(836 collection_id,837 PropertyScope::Rmrk,838 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,839 )?;840 }841 }842843 Self::deposit_event(Event::PropertySet {844 collection_id: rmrk_collection_id,845 maybe_nft_id,846 key,847 value,848 });849850 Ok(())851 }852853 854 #[transactional]855 #[pallet::weight(<SelfWeightOf<T>>::set_priority())]856 pub fn set_priority(857 origin: OriginFor<T>,858 rmrk_collection_id: RmrkCollectionId,859 rmrk_nft_id: RmrkNftId,860 priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,861 ) -> DispatchResult {862 let sender = ensure_signed(origin)?;863 let sender = T::CrossAccountId::from_sub(sender);864865 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;866 let nft_id = rmrk_nft_id.into();867868 let collection =869 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;870 collection.check_is_external()?;871872 let budget = budget::Value::new(NESTING_BUDGET);873874 Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;875 Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;876877 <PalletNft<T>>::set_scoped_token_property(878 collection_id,879 nft_id,880 PropertyScope::Rmrk,881 Self::rmrk_property(ResourcePriorities, &priorities.into_inner())?,882 )?;883884 Self::deposit_event(Event::<T>::PrioritySet {885 collection_id: rmrk_collection_id,886 nft_id: rmrk_nft_id,887 });888889 Ok(())890 }891892 893 #[transactional]894 #[pallet::weight(<SelfWeightOf<T>>::add_basic_resource())]895 pub fn add_basic_resource(896 origin: OriginFor<T>,897 rmrk_collection_id: RmrkCollectionId,898 nft_id: RmrkNftId,899 resource: RmrkBasicResource,900 ) -> DispatchResult {901 let sender = ensure_signed(origin.clone())?;902903 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;904 let collection =905 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;906 collection.check_is_external()?;907908 let resource_id = Self::resource_add(909 sender,910 collection_id,911 nft_id.into(),912 RmrkResourceTypes::Basic(resource),913 )?;914915 Self::deposit_event(Event::ResourceAdded {916 nft_id,917 resource_id,918 });919 Ok(())920 }921922 923 #[transactional]924 #[pallet::weight(<SelfWeightOf<T>>::add_composable_resource())]925 pub fn add_composable_resource(926 origin: OriginFor<T>,927 rmrk_collection_id: RmrkCollectionId,928 nft_id: RmrkNftId,929 resource: RmrkComposableResource,930 ) -> DispatchResult {931 let sender = ensure_signed(origin.clone())?;932933 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;934 let collection =935 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;936 collection.check_is_external()?;937938 let resource_id = Self::resource_add(939 sender,940 collection_id,941 nft_id.into(),942 RmrkResourceTypes::Composable(resource),943 )?;944945 Self::deposit_event(Event::ResourceAdded {946 nft_id,947 resource_id,948 });949 Ok(())950 }951952 953 #[transactional]954 #[pallet::weight(<SelfWeightOf<T>>::add_slot_resource())]955 pub fn add_slot_resource(956 origin: OriginFor<T>,957 rmrk_collection_id: RmrkCollectionId,958 nft_id: RmrkNftId,959 resource: RmrkSlotResource,960 ) -> DispatchResult {961 let sender = ensure_signed(origin.clone())?;962963 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;964 let collection =965 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;966 collection.check_is_external()?;967968 let resource_id = Self::resource_add(969 sender,970 collection_id,971 nft_id.into(),972 RmrkResourceTypes::Slot(resource),973 )?;974975 Self::deposit_event(Event::ResourceAdded {976 nft_id,977 resource_id,978 });979 Ok(())980 }981982 983 #[transactional]984 #[pallet::weight(<SelfWeightOf<T>>::remove_resource())]985 pub fn remove_resource(986 origin: OriginFor<T>,987 rmrk_collection_id: RmrkCollectionId,988 nft_id: RmrkNftId,989 resource_id: RmrkResourceId,990 ) -> DispatchResult {991 let sender = ensure_signed(origin.clone())?;992993 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;994 let collection =995 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;996 collection.check_is_external()?;997998 Self::resource_remove(sender, collection_id, nft_id.into(), resource_id)?;9991000 Self::deposit_event(Event::ResourceRemoval {1001 nft_id,1002 resource_id,1003 });1004 Ok(())1005 }1006 }1007}10081009impl<T: Config> Pallet<T> {1010 pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {1011 let key = rmrk_key.to_key::<T>()?;10121013 let scoped_key = PropertyScope::Rmrk1014 .apply(key)1015 .map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;10161017 Ok(scoped_key)1018 }10191020 1021 pub fn rmrk_property<E: Encode>(1022 rmrk_key: RmrkProperty,1023 value: &E,1024 ) -> Result<Property, DispatchError> {1025 let key = rmrk_key.to_key::<T>()?;10261027 let value = Self::encode_property(value)?;10281029 let property = Property { key, value };10301031 Ok(property)1032 }10331034 pub fn encode_property<E: Encode, S: Get<u32>>(1035 value: &E,1036 ) -> Result<BoundedBytes<S>, DispatchError> {1037 let value = value1038 .encode()1039 .try_into()1040 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;10411042 Ok(value)1043 }10441045 pub fn decode_property<D: Decode, S: Get<u32>>(1046 vec: &BoundedBytes<S>,1047 ) -> Result<D, DispatchError> {1048 vec.decode()1049 .map_err(|_| <Error<T>>::UnableToDecodeRmrkData.into())1050 }10511052 pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>1053 where1054 BoundedVec<u8, S>: TryFrom<Vec<u8>>,1055 {1056 vec.rebind()1057 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1058 }10591060 fn init_collection(1061 sender: T::CrossAccountId,1062 data: CreateCollectionData<T::AccountId>,1063 properties: impl Iterator<Item = Property>,1064 ) -> Result<CollectionId, DispatchError> {1065 let collection_id = <PalletNft<T>>::init_collection(sender, data, true);10661067 if let Err(DispatchError::Arithmetic(_)) = &collection_id {1068 return Err(<Error<T>>::NoAvailableCollectionId.into());1069 }10701071 <PalletCommon<T>>::set_scoped_collection_properties(1072 collection_id?,1073 PropertyScope::Rmrk,1074 properties,1075 )?;10761077 collection_id1078 }10791080 pub fn create_nft(1081 sender: &T::CrossAccountId,1082 owner: &T::CrossAccountId,1083 collection: &NonfungibleHandle<T>,1084 properties: impl Iterator<Item = Property>,1085 ) -> Result<TokenId, DispatchError> {1086 let data = CreateNftExData {1087 properties: BoundedVec::default(),1088 owner: owner.clone(),1089 };10901091 let budget = budget::Value::new(NESTING_BUDGET);10921093 <PalletNft<T>>::create_item(collection, sender, data, &budget)?;10941095 let nft_id = <PalletNft<T>>::current_token_id(collection.id);10961097 <PalletNft<T>>::set_scoped_token_properties(1098 collection.id,1099 nft_id,1100 PropertyScope::Rmrk,1101 properties,1102 )?;11031104 Ok(nft_id)1105 }11061107 fn destroy_nft(1108 sender: T::CrossAccountId,1109 collection_id: CollectionId,1110 token_id: TokenId,1111 max_burns: u32,1112 error_if_not_owned: Error<T>,1113 ) -> DispatchResultWithPostInfo {1114 let collection =1115 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;11161117 let token_data =1118 <TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;11191120 let from = token_data.owner;11211122 let owner_check_budget = budget::Value::new(NESTING_BUDGET);11231124 ensure!(1125 <PalletStructure<T>>::check_indirectly_owned(1126 sender.clone(),1127 collection_id,1128 token_id,1129 None,1130 &owner_check_budget1131 )?,1132 error_if_not_owned,1133 );11341135 let burns_budget = budget::Value::new(max_burns);1136 let breadth_budget = budget::Value::new(max_burns);11371138 <PalletNft<T>>::burn_recursively(1139 &collection,1140 &from,1141 token_id,1142 &burns_budget,1143 &breadth_budget,1144 )1145 }11461147 fn acquire_next_resource_id(1148 collection_id: CollectionId,1149 nft_id: TokenId,1150 ) -> Result<RmrkResourceId, DispatchError> {1151 let resource_id: RmrkResourceId =1152 Self::get_nft_property_decoded(collection_id, nft_id, NextResourceId)?;11531154 let next_id = resource_id1155 .checked_add(1)1156 .ok_or(<Error<T>>::NoAvailableResourceId)?;11571158 <PalletNft<T>>::set_scoped_token_property(1159 collection_id,1160 nft_id,1161 PropertyScope::Rmrk,1162 Self::rmrk_property(NextResourceId, &next_id)?,1163 )?;11641165 Ok(resource_id)1166 }11671168 fn resource_add(1169 sender: T::AccountId,1170 collection_id: CollectionId,1171 nft_id: TokenId,1172 resource: RmrkResourceTypes,1173 ) -> Result<RmrkResourceId, DispatchError> {1174 let collection =1175 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1176 ensure!(collection.owner == sender, Error::<T>::NoPermission);11771178 let sender = T::CrossAccountId::from_sub(sender);1179 let budget = budget::Value::new(NESTING_BUDGET);11801181 let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)1182 .map_err(Self::map_unique_err_to_proxy)?;11831184 let pending = sender != nft_owner;11851186 let id = Self::acquire_next_resource_id(collection_id, nft_id)?;11871188 let resource_info = RmrkResourceInfo {1189 id,1190 resource,1191 pending,1192 pending_removal: false,1193 };11941195 <PalletNft<T>>::try_mutate_token_sys_property(1196 collection_id,1197 nft_id,1198 PropertyScope::Rmrk,1199 Self::rmrk_property_key(ResourceId(id))?,1200 |value| -> DispatchResult {1201 *value = Some(Self::encode_property(&resource_info)?);12021203 Ok(())1204 },1205 )?;12061207 Ok(id)1208 }12091210 fn resource_remove(1211 sender: T::AccountId,1212 collection_id: CollectionId,1213 nft_id: TokenId,1214 resource_id: RmrkResourceId,1215 ) -> DispatchResult {1216 let collection =1217 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1218 ensure!(collection.owner == sender, Error::<T>::NoPermission);12191220 let resource_id_key = Self::rmrk_property_key(ResourceId(resource_id))?;1221 let scope = PropertyScope::Rmrk;12221223 ensure!(1224 <PalletNft<T>>::token_sys_property((1225 collection_id,1226 nft_id,1227 scope,1228 resource_id_key.clone()1229 ))1230 .is_some(),1231 <Error<T>>::ResourceDoesntExist1232 );12331234 let budget = up_data_structs::budget::Value::new(NESTING_BUDGET);1235 let topmost_owner =1236 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;12371238 let sender = T::CrossAccountId::from_sub(sender);1239 if topmost_owner == sender {1240 <PalletNft<T>>::remove_token_sys_property(1241 collection_id,1242 nft_id,1243 PropertyScope::Rmrk,1244 Self::rmrk_property_key(ResourceId(resource_id))?,1245 );1246 } else {1247 Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {1248 res.pending_removal = true;12491250 Ok(())1251 })?;1252 }12531254 Ok(())1255 }12561257 fn try_mutate_resource_info(1258 collection_id: CollectionId,1259 nft_id: TokenId,1260 resource_id: RmrkResourceId,1261 f: impl FnOnce(&mut RmrkResourceInfo) -> DispatchResult,1262 ) -> DispatchResult {1263 <PalletNft<T>>::try_mutate_token_sys_property(1264 collection_id,1265 nft_id,1266 PropertyScope::Rmrk,1267 Self::rmrk_property_key(ResourceId(resource_id))?,1268 |value| match value {1269 Some(value) => {1270 let mut resource_info: RmrkResourceInfo = Self::decode_property(value)?;12711272 f(&mut resource_info)?;12731274 *value = Self::encode_property(&resource_info)?;12751276 Ok(())1277 }1278 None => Err(<Error<T>>::ResourceDoesntExist.into()),1279 },1280 )1281 }12821283 fn change_collection_owner(1284 collection_id: CollectionId,1285 collection_type: misc::CollectionType,1286 sender: T::AccountId,1287 new_owner: T::AccountId,1288 ) -> DispatchResult {1289 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1290 Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;12911292 let mut collection = collection.into_inner();12931294 collection.owner = new_owner;1295 collection.save()1296 }12971298 fn check_collection_owner(1299 collection: &NonfungibleHandle<T>,1300 account: &T::CrossAccountId,1301 ) -> DispatchResult {1302 collection1303 .check_is_owner(account)1304 .map_err(Self::map_unique_err_to_proxy)1305 }13061307 pub fn last_collection_idx() -> RmrkCollectionId {1308 <CollectionIndex<T>>::get()1309 }13101311 pub fn unique_collection_id(1312 rmrk_collection_id: RmrkCollectionId,1313 ) -> Result<CollectionId, DispatchError> {1314 <UniqueCollectionId<T>>::try_get(rmrk_collection_id)1315 .map_err(|_| <Error<T>>::CollectionUnknown.into())1316 }13171318 pub fn rmrk_collection_id(1319 unique_collection_id: CollectionId,1320 ) -> Result<RmrkCollectionId, DispatchError> {1321 Self::get_collection_property_decoded(unique_collection_id, RmrkInternalCollectionId)1322 }13231324 pub fn get_nft_collection(1325 collection_id: CollectionId,1326 ) -> Result<NonfungibleHandle<T>, DispatchError> {1327 let collection = <CollectionHandle<T>>::try_get(collection_id)1328 .map_err(|_| <Error<T>>::CollectionUnknown)?;13291330 match collection.mode {1331 CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1332 _ => Err(<Error<T>>::CollectionUnknown.into()),1333 }1334 }13351336 pub fn collection_exists(collection_id: CollectionId) -> bool {1337 <CollectionHandle<T>>::try_get(collection_id).is_ok()1338 }13391340 pub fn get_collection_property(1341 collection_id: CollectionId,1342 key: RmrkProperty,1343 ) -> Result<PropertyValue, DispatchError> {1344 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1345 .get(&Self::rmrk_property_key(key)?)1346 .ok_or(<Error<T>>::CollectionUnknown)?1347 .clone();13481349 Ok(collection_property)1350 }13511352 pub fn get_collection_property_decoded<V: Decode>(1353 collection_id: CollectionId,1354 key: RmrkProperty,1355 ) -> Result<V, DispatchError> {1356 Self::decode_property(&Self::get_collection_property(collection_id, key)?)1357 }13581359 pub fn get_collection_type(1360 collection_id: CollectionId,1361 ) -> Result<misc::CollectionType, DispatchError> {1362 Self::get_collection_property_decoded(collection_id, CollectionType)1363 .map_err(|_| <Error<T>>::CorruptedCollectionType.into())1364 }13651366 pub fn ensure_collection_type(1367 collection_id: CollectionId,1368 collection_type: misc::CollectionType,1369 ) -> DispatchResult {1370 let actual_type = Self::get_collection_type(collection_id)?;1371 ensure!(1372 actual_type == collection_type,1373 <CommonError<T>>::NoPermission1374 );13751376 Ok(())1377 }13781379 pub fn get_typed_nft_collection(1380 collection_id: CollectionId,1381 collection_type: misc::CollectionType,1382 ) -> Result<NonfungibleHandle<T>, DispatchError> {1383 Self::ensure_collection_type(collection_id, collection_type)?;13841385 Self::get_nft_collection(collection_id)1386 }13871388 pub fn get_typed_nft_collection_mapped(1389 rmrk_collection_id: RmrkCollectionId,1390 collection_type: misc::CollectionType,1391 ) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1392 let unique_collection_id = match collection_type {1393 misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1394 _ => rmrk_collection_id.into(),1395 };13961397 let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;13981399 Ok((collection, unique_collection_id))1400 }14011402 pub fn get_nft_property(1403 collection_id: CollectionId,1404 nft_id: TokenId,1405 key: RmrkProperty,1406 ) -> Result<PropertyValue, DispatchError> {1407 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1408 .get(&Self::rmrk_property_key(key)?)1409 .ok_or(<Error<T>>::RmrkPropertyIsNotFound)?1410 .clone();14111412 Ok(nft_property)1413 }14141415 pub fn get_nft_property_decoded<V: Decode>(1416 collection_id: CollectionId,1417 nft_id: TokenId,1418 key: RmrkProperty,1419 ) -> Result<V, DispatchError> {1420 Self::decode_property(&Self::get_nft_property(collection_id, nft_id, key)?)1421 }14221423 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1424 <TokenData<T>>::contains_key((collection_id, nft_id))1425 }14261427 pub fn get_nft_type(1428 collection_id: CollectionId,1429 token_id: TokenId,1430 ) -> Result<NftType, DispatchError> {1431 Self::get_nft_property_decoded(collection_id, token_id, TokenType)1432 .map_err(|_| <Error<T>>::NoAvailableNftId.into())1433 }14341435 pub fn ensure_nft_type(1436 collection_id: CollectionId,1437 token_id: TokenId,1438 nft_type: NftType,1439 ) -> DispatchResult {1440 let actual_type = Self::get_nft_type(collection_id, token_id)?;1441 ensure!(actual_type == nft_type, <Error<T>>::NoPermission);14421443 Ok(())1444 }14451446 pub fn ensure_nft_owner(1447 collection_id: CollectionId,1448 token_id: TokenId,1449 possible_owner: &T::CrossAccountId,1450 nesting_budget: &dyn budget::Budget,1451 ) -> DispatchResult {1452 let is_owned = <PalletStructure<T>>::check_indirectly_owned(1453 possible_owner.clone(),1454 collection_id,1455 token_id,1456 None,1457 nesting_budget,1458 )1459 .map_err(Self::map_unique_err_to_proxy)?;14601461 ensure!(is_owned, <Error<T>>::NoPermission);14621463 Ok(())1464 }14651466 pub fn filter_user_properties<Key, Value, R, Mapper>(1467 collection_id: CollectionId,1468 token_id: Option<TokenId>,1469 filter_keys: Option<Vec<RmrkPropertyKey>>,1470 mapper: Mapper,1471 ) -> Result<Vec<R>, DispatchError>1472 where1473 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1474 Value: Decode + Default,1475 Mapper: Fn(Key, Value) -> R,1476 {1477 filter_keys1478 .map(|keys| {1479 let properties = keys1480 .into_iter()1481 .filter_map(|key| {1482 let key: Key = key.try_into().ok()?;14831484 let value = match token_id {1485 Some(token_id) => Self::get_nft_property_decoded(1486 collection_id,1487 token_id,1488 UserProperty(key.as_ref()),1489 ),1490 None => Self::get_collection_property_decoded(1491 collection_id,1492 UserProperty(key.as_ref()),1493 ),1494 }1495 .ok()?;14961497 Some(mapper(key, value))1498 })1499 .collect();15001501 Ok(properties)1502 })1503 .unwrap_or_else(|| {1504 let properties =1505 Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();15061507 Ok(properties)1508 })1509 }15101511 pub fn iterate_user_properties<Key, Value, R, Mapper>(1512 collection_id: CollectionId,1513 token_id: Option<TokenId>,1514 mapper: Mapper,1515 ) -> Result<impl Iterator<Item = R>, DispatchError>1516 where1517 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1518 Value: Decode + Default,1519 Mapper: Fn(Key, Value) -> R,1520 {1521 let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;15221523 let properties = match token_id {1524 Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1525 None => <PalletCommon<T>>::collection_properties(collection_id),1526 };15271528 let properties = properties.into_iter().filter_map(move |(key, value)| {1529 let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;15301531 let key: Key = key.to_vec().try_into().ok()?;1532 let value: Value = value.decode().ok()?;15331534 Some(mapper(key, value))1535 });15361537 Ok(properties)1538 }15391540 fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1541 map_unique_err_to_proxy! {1542 match err {1543 CommonError::NoPermission => NoPermission,1544 CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1545 CommonError::PublicMintingNotAllowed => NoPermission,1546 CommonError::TokenNotFound => NoAvailableNftId,1547 CommonError::ApprovedValueTooLow => NoPermission,1548 CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1549 StructureError::TokenNotFound => NoAvailableNftId,1550 StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1551 }1552 }1553 }1554}