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: Option<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());363364 let owner = owner.unwrap_or(sender.clone());365 let cross_owner = T::CrossAccountId::from_sub(owner.clone());366367 let collection = Self::get_typed_nft_collection(368 Self::unique_collection_id(collection_id)?,369 misc::CollectionType::Regular,370 )?;371 collection.check_is_external()?;372373 let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {374 recipient: recipient.unwrap_or_else(|| owner.clone()),375 amount,376 });377378 let nft_id = Self::create_nft(379 &cross_sender,380 &cross_owner,381 &collection,382 [383 Self::rmrk_property(TokenType, &NftType::Regular)?,384 Self::rmrk_property(Transferable, &transferable)?,385 Self::rmrk_property(PendingNftAccept, &false)?,386 Self::rmrk_property(RoyaltyInfo, &royalty_info)?,387 Self::rmrk_property(Metadata, &metadata)?,388 Self::rmrk_property(Equipped, &false)?,389 Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,390 Self::rmrk_property(NextResourceId, &(0 as RmrkResourceId))?,391 ]392 .into_iter(),393 )394 .map_err(|err| match err {395 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),396 err => Self::map_unique_err_to_proxy(err),397 })?;398399 if let Some(resources) = resources {400 for resource in resources {401 Self::resource_add(sender.clone(), collection.id, nft_id, resource)?;402 }403 }404405 Self::deposit_event(Event::NftMinted {406 owner,407 collection_id,408 nft_id: nft_id.0,409 });410411 Ok(())412 }413414 415 #[transactional]416 #[pallet::weight(<SelfWeightOf<T>>::burn_nft(*max_burns))]417 pub fn burn_nft(418 origin: OriginFor<T>,419 collection_id: RmrkCollectionId,420 nft_id: RmrkNftId,421 max_burns: u32,422 ) -> DispatchResult {423 let sender = ensure_signed(origin)?;424 let cross_sender = T::CrossAccountId::from_sub(sender.clone());425426 let collection = Self::get_typed_nft_collection(427 Self::unique_collection_id(collection_id)?,428 misc::CollectionType::Regular,429 )?;430 collection.check_is_external()?;431432 Self::destroy_nft(433 cross_sender,434 Self::unique_collection_id(collection_id)?,435 nft_id.into(),436 max_burns,437 <Error<T>>::NoPermission,438 )439 .map_err(|err| Self::map_unique_err_to_proxy(err.error))?;440441 Self::deposit_event(Event::NFTBurned {442 owner: sender,443 nft_id,444 });445446 Ok(())447 }448449 450 451 452 453 454 455 456 #[transactional]457 #[pallet::weight(<SelfWeightOf<T>>::send())]458 pub fn send(459 origin: OriginFor<T>,460 rmrk_collection_id: RmrkCollectionId,461 rmrk_nft_id: RmrkNftId,462 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,463 ) -> DispatchResult {464 let sender = ensure_signed(origin.clone())?;465 let cross_sender = T::CrossAccountId::from_sub(sender.clone());466467 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;468 let nft_id = rmrk_nft_id.into();469470 let collection =471 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;472 collection.check_is_external()?;473474 let token_data =475 <TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;476477 let from = token_data.owner;478479 ensure!(480 Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,481 <Error<T>>::NonTransferable482 );483484 ensure!(485 !Self::get_nft_property_decoded(486 collection_id,487 nft_id,488 RmrkProperty::PendingNftAccept489 )?,490 <Error<T>>::NoPermission491 );492493 let target_owner;494 let approval_required;495496 match new_owner {497 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {498 target_owner = T::CrossAccountId::from_sub(account_id.clone());499 approval_required = false;500 }501 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(502 target_collection_id,503 target_nft_id,504 ) => {505 let target_collection_id = Self::unique_collection_id(target_collection_id)?;506507 let target_nft_budget = budget::Value::new(NESTING_BUDGET);508509 let target_nft_owner = <PalletStructure<T>>::get_checked_topmost_owner(510 target_collection_id,511 target_nft_id.into(),512 Some((collection_id, nft_id)),513 &target_nft_budget,514 )515 .map_err(Self::map_unique_err_to_proxy)?;516517 approval_required = cross_sender != target_nft_owner;518519 if approval_required {520 target_owner = target_nft_owner;521522 <PalletNft<T>>::set_scoped_token_property(523 collection.id,524 nft_id,525 PropertyScope::Rmrk,526 Self::rmrk_property(PendingNftAccept, &approval_required)?,527 )?;528 } else {529 target_owner = T::CrossTokenAddressMapping::token_to_address(530 target_collection_id,531 target_nft_id.into(),532 );533 }534 }535 }536537 let src_nft_budget = budget::Value::new(NESTING_BUDGET);538539 <PalletNft<T>>::transfer_from(540 &collection,541 &cross_sender,542 &from,543 &target_owner,544 nft_id,545 &src_nft_budget,546 )547 .map_err(Self::map_unique_err_to_proxy)?;548549 Self::deposit_event(Event::NFTSent {550 sender,551 recipient: new_owner,552 collection_id: rmrk_collection_id,553 nft_id: rmrk_nft_id,554 approval_required,555 });556557 Ok(())558 }559560 561 562 563 564 565 566 567 568 #[transactional]569 #[pallet::weight(<SelfWeightOf<T>>::accept_nft())]570 pub fn accept_nft(571 origin: OriginFor<T>,572 rmrk_collection_id: RmrkCollectionId,573 rmrk_nft_id: RmrkNftId,574 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,575 ) -> DispatchResult {576 let sender = ensure_signed(origin.clone())?;577 let cross_sender = T::CrossAccountId::from_sub(sender.clone());578579 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;580 let nft_id = rmrk_nft_id.into();581582 let collection =583 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;584 collection.check_is_external()?;585586 let new_cross_owner = match new_owner {587 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {588 T::CrossAccountId::from_sub(account_id.clone())589 }590 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(591 target_collection_id,592 target_nft_id,593 ) => {594 let target_collection_id = Self::unique_collection_id(target_collection_id)?;595596 T::CrossTokenAddressMapping::token_to_address(597 target_collection_id,598 TokenId(target_nft_id),599 )600 }601 };602603 let budget = budget::Value::new(NESTING_BUDGET);604605 <PalletNft<T>>::transfer(606 &collection,607 &cross_sender,608 &new_cross_owner,609 nft_id,610 &budget,611 )612 .map_err(|err| {613 if err == <CommonError<T>>::UserIsNotAllowedToNest.into() {614 <Error<T>>::CannotAcceptNonOwnedNft.into()615 } else {616 Self::map_unique_err_to_proxy(err)617 }618 })?;619620 <PalletNft<T>>::set_scoped_token_property(621 collection.id,622 nft_id,623 PropertyScope::Rmrk,624 Self::rmrk_property(PendingNftAccept, &false)?,625 )?;626627 Self::deposit_event(Event::NFTAccepted {628 sender,629 recipient: new_owner,630 collection_id: rmrk_collection_id,631 nft_id: rmrk_nft_id,632 });633634 Ok(())635 }636637 638 639 640 641 642 643 #[transactional]644 #[pallet::weight(<SelfWeightOf<T>>::reject_nft())]645 pub fn reject_nft(646 origin: OriginFor<T>,647 rmrk_collection_id: RmrkCollectionId,648 rmrk_nft_id: RmrkNftId,649 ) -> DispatchResult {650 let sender = ensure_signed(origin)?;651 let cross_sender = T::CrossAccountId::from_sub(sender.clone());652653 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;654 let nft_id = rmrk_nft_id.into();655656 let collection =657 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;658 collection.check_is_external()?;659660 ensure!(661 <TokenData<T>>::get((collection_id, nft_id)).is_some(),662 <Error<T>>::NoAvailableNftId663 );664665 ensure!(666 Self::get_nft_property_decoded(667 collection_id,668 nft_id,669 RmrkProperty::PendingNftAccept670 )?,671 <Error<T>>::CannotRejectNonPendingNft672 );673674 Self::destroy_nft(675 cross_sender,676 collection_id,677 nft_id,678 NESTING_BUDGET,679 <Error<T>>::CannotRejectNonOwnedNft,680 )681 .map_err(|err| Self::map_unique_err_to_proxy(err.error))?;682683 Self::deposit_event(Event::NFTRejected {684 sender,685 collection_id: rmrk_collection_id,686 nft_id: rmrk_nft_id,687 });688689 Ok(())690 }691692 693 #[transactional]694 #[pallet::weight(<SelfWeightOf<T>>::accept_resource())]695 pub fn accept_resource(696 origin: OriginFor<T>,697 rmrk_collection_id: RmrkCollectionId,698 rmrk_nft_id: RmrkNftId,699 resource_id: RmrkResourceId,700 ) -> DispatchResult {701 let sender = ensure_signed(origin)?;702 let cross_sender = T::CrossAccountId::from_sub(sender);703704 let collection_id = Self::unique_collection_id(rmrk_collection_id)705 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;706 let collection =707 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;708 collection.check_is_external()?;709710 let nft_id = rmrk_nft_id.into();711712 let budget = budget::Value::new(NESTING_BUDGET);713714 let nft_owner =715 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)716 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;717718 Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {719 ensure!(res.pending, <Error<T>>::ResourceNotPending);720 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);721722 res.pending = false;723724 Ok(())725 })?;726727 Self::deposit_event(Event::<T>::ResourceAccepted {728 nft_id: rmrk_nft_id,729 resource_id,730 });731732 Ok(())733 }734735 736 #[transactional]737 #[pallet::weight(<SelfWeightOf<T>>::accept_resource_removal())]738 pub fn accept_resource_removal(739 origin: OriginFor<T>,740 rmrk_collection_id: RmrkCollectionId,741 rmrk_nft_id: RmrkNftId,742 resource_id: RmrkResourceId,743 ) -> DispatchResult {744 let sender = ensure_signed(origin)?;745 let cross_sender = T::CrossAccountId::from_sub(sender);746747 let collection_id = Self::unique_collection_id(rmrk_collection_id)748 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;749 let collection =750 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;751 collection.check_is_external()?;752753 let nft_id = rmrk_nft_id.into();754755 let budget = budget::Value::new(NESTING_BUDGET);756757 let nft_owner =758 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)759 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;760761 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);762763 let resource_id_key = Self::rmrk_property_key(ResourceId(resource_id))?;764765 let resource_info = <PalletNft<T>>::token_sys_property((766 collection_id,767 nft_id,768 PropertyScope::Rmrk,769 resource_id_key.clone(),770 ))771 .ok_or(<Error<T>>::ResourceDoesntExist)?;772773 let resource_info: RmrkResourceInfo = Self::decode_property(&resource_info)?;774775 ensure!(776 resource_info.pending_removal,777 <Error<T>>::ResourceNotPending778 );779780 <PalletNft<T>>::remove_token_sys_property(781 collection_id,782 nft_id,783 PropertyScope::Rmrk,784 resource_id_key,785 );786787 Self::deposit_event(Event::<T>::ResourceRemovalAccepted {788 nft_id: rmrk_nft_id,789 resource_id,790 });791792 Ok(())793 }794795 796 #[transactional]797 #[pallet::weight(<SelfWeightOf<T>>::set_property())]798 pub fn set_property(799 origin: OriginFor<T>,800 #[pallet::compact] rmrk_collection_id: RmrkCollectionId,801 maybe_nft_id: Option<RmrkNftId>,802 key: RmrkKeyString,803 value: RmrkValueString,804 ) -> DispatchResult {805 let sender = ensure_signed(origin)?;806 let sender = T::CrossAccountId::from_sub(sender);807808 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;809 let collection =810 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;811 collection.check_is_external()?;812813 let budget = budget::Value::new(NESTING_BUDGET);814815 match maybe_nft_id {816 Some(nft_id) => {817 let token_id: TokenId = nft_id.into();818819 Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;820 Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;821822 <PalletNft<T>>::set_scoped_token_property(823 collection_id,824 token_id,825 PropertyScope::Rmrk,826 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,827 )?;828 }829 None => {830 let collection = Self::get_typed_nft_collection(831 collection_id,832 misc::CollectionType::Regular,833 )?;834835 Self::check_collection_owner(&collection, &sender)?;836837 <PalletCommon<T>>::set_scoped_collection_property(838 collection_id,839 PropertyScope::Rmrk,840 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,841 )?;842 }843 }844845 Self::deposit_event(Event::PropertySet {846 collection_id: rmrk_collection_id,847 maybe_nft_id,848 key,849 value,850 });851852 Ok(())853 }854855 856 #[transactional]857 #[pallet::weight(<SelfWeightOf<T>>::set_priority())]858 pub fn set_priority(859 origin: OriginFor<T>,860 rmrk_collection_id: RmrkCollectionId,861 rmrk_nft_id: RmrkNftId,862 priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,863 ) -> DispatchResult {864 let sender = ensure_signed(origin)?;865 let sender = T::CrossAccountId::from_sub(sender);866867 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;868 let nft_id = rmrk_nft_id.into();869870 let collection =871 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;872 collection.check_is_external()?;873874 let budget = budget::Value::new(NESTING_BUDGET);875876 Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;877 Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;878879 <PalletNft<T>>::set_scoped_token_property(880 collection_id,881 nft_id,882 PropertyScope::Rmrk,883 Self::rmrk_property(ResourcePriorities, &priorities.into_inner())?,884 )?;885886 Self::deposit_event(Event::<T>::PrioritySet {887 collection_id: rmrk_collection_id,888 nft_id: rmrk_nft_id,889 });890891 Ok(())892 }893894 895 #[transactional]896 #[pallet::weight(<SelfWeightOf<T>>::add_basic_resource())]897 pub fn add_basic_resource(898 origin: OriginFor<T>,899 rmrk_collection_id: RmrkCollectionId,900 nft_id: RmrkNftId,901 resource: RmrkBasicResource,902 ) -> DispatchResult {903 let sender = ensure_signed(origin.clone())?;904905 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;906 let collection =907 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;908 collection.check_is_external()?;909910 let resource_id = Self::resource_add(911 sender,912 collection_id,913 nft_id.into(),914 RmrkResourceTypes::Basic(resource),915 )?;916917 Self::deposit_event(Event::ResourceAdded {918 nft_id,919 resource_id,920 });921 Ok(())922 }923924 925 #[transactional]926 #[pallet::weight(<SelfWeightOf<T>>::add_composable_resource())]927 pub fn add_composable_resource(928 origin: OriginFor<T>,929 rmrk_collection_id: RmrkCollectionId,930 nft_id: RmrkNftId,931 resource: RmrkComposableResource,932 ) -> DispatchResult {933 let sender = ensure_signed(origin.clone())?;934935 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;936 let collection =937 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;938 collection.check_is_external()?;939940 let resource_id = Self::resource_add(941 sender,942 collection_id,943 nft_id.into(),944 RmrkResourceTypes::Composable(resource),945 )?;946947 Self::deposit_event(Event::ResourceAdded {948 nft_id,949 resource_id,950 });951 Ok(())952 }953954 955 #[transactional]956 #[pallet::weight(<SelfWeightOf<T>>::add_slot_resource())]957 pub fn add_slot_resource(958 origin: OriginFor<T>,959 rmrk_collection_id: RmrkCollectionId,960 nft_id: RmrkNftId,961 resource: RmrkSlotResource,962 ) -> DispatchResult {963 let sender = ensure_signed(origin.clone())?;964965 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;966 let collection =967 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;968 collection.check_is_external()?;969970 let resource_id = Self::resource_add(971 sender,972 collection_id,973 nft_id.into(),974 RmrkResourceTypes::Slot(resource),975 )?;976977 Self::deposit_event(Event::ResourceAdded {978 nft_id,979 resource_id,980 });981 Ok(())982 }983984 985 #[transactional]986 #[pallet::weight(<SelfWeightOf<T>>::remove_resource())]987 pub fn remove_resource(988 origin: OriginFor<T>,989 rmrk_collection_id: RmrkCollectionId,990 nft_id: RmrkNftId,991 resource_id: RmrkResourceId,992 ) -> DispatchResult {993 let sender = ensure_signed(origin.clone())?;994995 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;996 let collection =997 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;998 collection.check_is_external()?;9991000 Self::resource_remove(sender, collection_id, nft_id.into(), resource_id)?;10011002 Self::deposit_event(Event::ResourceRemoval {1003 nft_id,1004 resource_id,1005 });1006 Ok(())1007 }1008 }1009}10101011impl<T: Config> Pallet<T> {1012 pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {1013 let key = rmrk_key.to_key::<T>()?;10141015 let scoped_key = PropertyScope::Rmrk1016 .apply(key)1017 .map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;10181019 Ok(scoped_key)1020 }10211022 1023 pub fn rmrk_property<E: Encode>(1024 rmrk_key: RmrkProperty,1025 value: &E,1026 ) -> Result<Property, DispatchError> {1027 let key = rmrk_key.to_key::<T>()?;10281029 let value = Self::encode_property(value)?;10301031 let property = Property { key, value };10321033 Ok(property)1034 }10351036 pub fn encode_property<E: Encode, S: Get<u32>>(1037 value: &E,1038 ) -> Result<BoundedBytes<S>, DispatchError> {1039 let value = value1040 .encode()1041 .try_into()1042 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;10431044 Ok(value)1045 }10461047 pub fn decode_property<D: Decode, S: Get<u32>>(1048 vec: &BoundedBytes<S>,1049 ) -> Result<D, DispatchError> {1050 vec.decode()1051 .map_err(|_| <Error<T>>::UnableToDecodeRmrkData.into())1052 }10531054 pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>1055 where1056 BoundedVec<u8, S>: TryFrom<Vec<u8>>,1057 {1058 vec.rebind()1059 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1060 }10611062 fn init_collection(1063 sender: T::CrossAccountId,1064 data: CreateCollectionData<T::AccountId>,1065 properties: impl Iterator<Item = Property>,1066 ) -> Result<CollectionId, DispatchError> {1067 let collection_id = <PalletNft<T>>::init_collection(sender, data, true);10681069 if let Err(DispatchError::Arithmetic(_)) = &collection_id {1070 return Err(<Error<T>>::NoAvailableCollectionId.into());1071 }10721073 <PalletCommon<T>>::set_scoped_collection_properties(1074 collection_id?,1075 PropertyScope::Rmrk,1076 properties,1077 )?;10781079 collection_id1080 }10811082 pub fn create_nft(1083 sender: &T::CrossAccountId,1084 owner: &T::CrossAccountId,1085 collection: &NonfungibleHandle<T>,1086 properties: impl Iterator<Item = Property>,1087 ) -> Result<TokenId, DispatchError> {1088 let data = CreateNftExData {1089 properties: BoundedVec::default(),1090 owner: owner.clone(),1091 };10921093 let budget = budget::Value::new(NESTING_BUDGET);10941095 <PalletNft<T>>::create_item(collection, sender, data, &budget)?;10961097 let nft_id = <PalletNft<T>>::current_token_id(collection.id);10981099 <PalletNft<T>>::set_scoped_token_properties(1100 collection.id,1101 nft_id,1102 PropertyScope::Rmrk,1103 properties,1104 )?;11051106 Ok(nft_id)1107 }11081109 fn destroy_nft(1110 sender: T::CrossAccountId,1111 collection_id: CollectionId,1112 token_id: TokenId,1113 max_burns: u32,1114 error_if_not_owned: Error<T>,1115 ) -> DispatchResultWithPostInfo {1116 let collection =1117 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;11181119 let token_data =1120 <TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;11211122 let from = token_data.owner;11231124 let owner_check_budget = budget::Value::new(NESTING_BUDGET);11251126 ensure!(1127 <PalletStructure<T>>::check_indirectly_owned(1128 sender.clone(),1129 collection_id,1130 token_id,1131 None,1132 &owner_check_budget1133 )?,1134 error_if_not_owned,1135 );11361137 let burns_budget = budget::Value::new(max_burns);1138 let breadth_budget = budget::Value::new(max_burns);11391140 <PalletNft<T>>::burn_recursively(1141 &collection,1142 &from,1143 token_id,1144 &burns_budget,1145 &breadth_budget,1146 )1147 }11481149 fn acquire_next_resource_id(1150 collection_id: CollectionId,1151 nft_id: TokenId,1152 ) -> Result<RmrkResourceId, DispatchError> {1153 let resource_id: RmrkResourceId =1154 Self::get_nft_property_decoded(collection_id, nft_id, NextResourceId)?;11551156 let next_id = resource_id1157 .checked_add(1)1158 .ok_or(<Error<T>>::NoAvailableResourceId)?;11591160 <PalletNft<T>>::set_scoped_token_property(1161 collection_id,1162 nft_id,1163 PropertyScope::Rmrk,1164 Self::rmrk_property(NextResourceId, &next_id)?,1165 )?;11661167 Ok(resource_id)1168 }11691170 fn resource_add(1171 sender: T::AccountId,1172 collection_id: CollectionId,1173 nft_id: TokenId,1174 resource: RmrkResourceTypes,1175 ) -> Result<RmrkResourceId, DispatchError> {1176 let collection =1177 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1178 ensure!(collection.owner == sender, Error::<T>::NoPermission);11791180 let sender = T::CrossAccountId::from_sub(sender);1181 let budget = budget::Value::new(NESTING_BUDGET);11821183 let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)1184 .map_err(Self::map_unique_err_to_proxy)?;11851186 let pending = sender != nft_owner;11871188 let id = Self::acquire_next_resource_id(collection_id, nft_id)?;11891190 let resource_info = RmrkResourceInfo {1191 id,1192 resource,1193 pending,1194 pending_removal: false,1195 };11961197 <PalletNft<T>>::try_mutate_token_sys_property(1198 collection_id,1199 nft_id,1200 PropertyScope::Rmrk,1201 Self::rmrk_property_key(ResourceId(id))?,1202 |value| -> DispatchResult {1203 *value = Some(Self::encode_property(&resource_info)?);12041205 Ok(())1206 },1207 )?;12081209 Ok(id)1210 }12111212 fn resource_remove(1213 sender: T::AccountId,1214 collection_id: CollectionId,1215 nft_id: TokenId,1216 resource_id: RmrkResourceId,1217 ) -> DispatchResult {1218 let collection =1219 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1220 ensure!(collection.owner == sender, Error::<T>::NoPermission);12211222 let resource_id_key = Self::rmrk_property_key(ResourceId(resource_id))?;1223 let scope = PropertyScope::Rmrk;12241225 ensure!(1226 <PalletNft<T>>::token_sys_property((1227 collection_id,1228 nft_id,1229 scope,1230 resource_id_key.clone()1231 ))1232 .is_some(),1233 <Error<T>>::ResourceDoesntExist1234 );12351236 let budget = up_data_structs::budget::Value::new(NESTING_BUDGET);1237 let topmost_owner =1238 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;12391240 let sender = T::CrossAccountId::from_sub(sender);1241 if topmost_owner == sender {1242 <PalletNft<T>>::remove_token_sys_property(1243 collection_id,1244 nft_id,1245 PropertyScope::Rmrk,1246 Self::rmrk_property_key(ResourceId(resource_id))?,1247 );1248 } else {1249 Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {1250 res.pending_removal = true;12511252 Ok(())1253 })?;1254 }12551256 Ok(())1257 }12581259 fn try_mutate_resource_info(1260 collection_id: CollectionId,1261 nft_id: TokenId,1262 resource_id: RmrkResourceId,1263 f: impl FnOnce(&mut RmrkResourceInfo) -> DispatchResult,1264 ) -> DispatchResult {1265 <PalletNft<T>>::try_mutate_token_sys_property(1266 collection_id,1267 nft_id,1268 PropertyScope::Rmrk,1269 Self::rmrk_property_key(ResourceId(resource_id))?,1270 |value| match value {1271 Some(value) => {1272 let mut resource_info: RmrkResourceInfo = Self::decode_property(value)?;12731274 f(&mut resource_info)?;12751276 *value = Self::encode_property(&resource_info)?;12771278 Ok(())1279 }1280 None => Err(<Error<T>>::ResourceDoesntExist.into()),1281 },1282 )1283 }12841285 fn change_collection_owner(1286 collection_id: CollectionId,1287 collection_type: misc::CollectionType,1288 sender: T::AccountId,1289 new_owner: T::AccountId,1290 ) -> DispatchResult {1291 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1292 Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;12931294 let mut collection = collection.into_inner();12951296 collection.owner = new_owner;1297 collection.save()1298 }12991300 fn check_collection_owner(1301 collection: &NonfungibleHandle<T>,1302 account: &T::CrossAccountId,1303 ) -> DispatchResult {1304 collection1305 .check_is_owner(account)1306 .map_err(Self::map_unique_err_to_proxy)1307 }13081309 pub fn last_collection_idx() -> RmrkCollectionId {1310 <CollectionIndex<T>>::get()1311 }13121313 pub fn unique_collection_id(1314 rmrk_collection_id: RmrkCollectionId,1315 ) -> Result<CollectionId, DispatchError> {1316 <UniqueCollectionId<T>>::try_get(rmrk_collection_id)1317 .map_err(|_| <Error<T>>::CollectionUnknown.into())1318 }13191320 pub fn rmrk_collection_id(1321 unique_collection_id: CollectionId,1322 ) -> Result<RmrkCollectionId, DispatchError> {1323 Self::get_collection_property_decoded(unique_collection_id, RmrkInternalCollectionId)1324 }13251326 pub fn get_nft_collection(1327 collection_id: CollectionId,1328 ) -> Result<NonfungibleHandle<T>, DispatchError> {1329 let collection = <CollectionHandle<T>>::try_get(collection_id)1330 .map_err(|_| <Error<T>>::CollectionUnknown)?;13311332 match collection.mode {1333 CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1334 _ => Err(<Error<T>>::CollectionUnknown.into()),1335 }1336 }13371338 pub fn collection_exists(collection_id: CollectionId) -> bool {1339 <CollectionHandle<T>>::try_get(collection_id).is_ok()1340 }13411342 pub fn get_collection_property(1343 collection_id: CollectionId,1344 key: RmrkProperty,1345 ) -> Result<PropertyValue, DispatchError> {1346 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1347 .get(&Self::rmrk_property_key(key)?)1348 .ok_or(<Error<T>>::CollectionUnknown)?1349 .clone();13501351 Ok(collection_property)1352 }13531354 pub fn get_collection_property_decoded<V: Decode>(1355 collection_id: CollectionId,1356 key: RmrkProperty,1357 ) -> Result<V, DispatchError> {1358 Self::decode_property(&Self::get_collection_property(collection_id, key)?)1359 }13601361 pub fn get_collection_type(1362 collection_id: CollectionId,1363 ) -> Result<misc::CollectionType, DispatchError> {1364 Self::get_collection_property_decoded(collection_id, CollectionType)1365 .map_err(|_| <Error<T>>::CorruptedCollectionType.into())1366 }13671368 pub fn ensure_collection_type(1369 collection_id: CollectionId,1370 collection_type: misc::CollectionType,1371 ) -> DispatchResult {1372 let actual_type = Self::get_collection_type(collection_id)?;1373 ensure!(1374 actual_type == collection_type,1375 <CommonError<T>>::NoPermission1376 );13771378 Ok(())1379 }13801381 pub fn get_typed_nft_collection(1382 collection_id: CollectionId,1383 collection_type: misc::CollectionType,1384 ) -> Result<NonfungibleHandle<T>, DispatchError> {1385 Self::ensure_collection_type(collection_id, collection_type)?;13861387 Self::get_nft_collection(collection_id)1388 }13891390 pub fn get_typed_nft_collection_mapped(1391 rmrk_collection_id: RmrkCollectionId,1392 collection_type: misc::CollectionType,1393 ) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1394 let unique_collection_id = match collection_type {1395 misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1396 _ => rmrk_collection_id.into(),1397 };13981399 let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;14001401 Ok((collection, unique_collection_id))1402 }14031404 pub fn get_nft_property(1405 collection_id: CollectionId,1406 nft_id: TokenId,1407 key: RmrkProperty,1408 ) -> Result<PropertyValue, DispatchError> {1409 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1410 .get(&Self::rmrk_property_key(key)?)1411 .ok_or(<Error<T>>::RmrkPropertyIsNotFound)?1412 .clone();14131414 Ok(nft_property)1415 }14161417 pub fn get_nft_property_decoded<V: Decode>(1418 collection_id: CollectionId,1419 nft_id: TokenId,1420 key: RmrkProperty,1421 ) -> Result<V, DispatchError> {1422 Self::decode_property(&Self::get_nft_property(collection_id, nft_id, key)?)1423 }14241425 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1426 <TokenData<T>>::contains_key((collection_id, nft_id))1427 }14281429 pub fn get_nft_type(1430 collection_id: CollectionId,1431 token_id: TokenId,1432 ) -> Result<NftType, DispatchError> {1433 Self::get_nft_property_decoded(collection_id, token_id, TokenType)1434 .map_err(|_| <Error<T>>::NoAvailableNftId.into())1435 }14361437 pub fn ensure_nft_type(1438 collection_id: CollectionId,1439 token_id: TokenId,1440 nft_type: NftType,1441 ) -> DispatchResult {1442 let actual_type = Self::get_nft_type(collection_id, token_id)?;1443 ensure!(actual_type == nft_type, <Error<T>>::NoPermission);14441445 Ok(())1446 }14471448 pub fn ensure_nft_owner(1449 collection_id: CollectionId,1450 token_id: TokenId,1451 possible_owner: &T::CrossAccountId,1452 nesting_budget: &dyn budget::Budget,1453 ) -> DispatchResult {1454 let is_owned = <PalletStructure<T>>::check_indirectly_owned(1455 possible_owner.clone(),1456 collection_id,1457 token_id,1458 None,1459 nesting_budget,1460 )1461 .map_err(Self::map_unique_err_to_proxy)?;14621463 ensure!(is_owned, <Error<T>>::NoPermission);14641465 Ok(())1466 }14671468 pub fn filter_user_properties<Key, Value, R, Mapper>(1469 collection_id: CollectionId,1470 token_id: Option<TokenId>,1471 filter_keys: Option<Vec<RmrkPropertyKey>>,1472 mapper: Mapper,1473 ) -> Result<Vec<R>, DispatchError>1474 where1475 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1476 Value: Decode + Default,1477 Mapper: Fn(Key, Value) -> R,1478 {1479 filter_keys1480 .map(|keys| {1481 let properties = keys1482 .into_iter()1483 .filter_map(|key| {1484 let key: Key = key.try_into().ok()?;14851486 let value = match token_id {1487 Some(token_id) => Self::get_nft_property_decoded(1488 collection_id,1489 token_id,1490 UserProperty(key.as_ref()),1491 ),1492 None => Self::get_collection_property_decoded(1493 collection_id,1494 UserProperty(key.as_ref()),1495 ),1496 }1497 .ok()?;14981499 Some(mapper(key, value))1500 })1501 .collect();15021503 Ok(properties)1504 })1505 .unwrap_or_else(|| {1506 let properties =1507 Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();15081509 Ok(properties)1510 })1511 }15121513 pub fn iterate_user_properties<Key, Value, R, Mapper>(1514 collection_id: CollectionId,1515 token_id: Option<TokenId>,1516 mapper: Mapper,1517 ) -> Result<impl Iterator<Item = R>, DispatchError>1518 where1519 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1520 Value: Decode + Default,1521 Mapper: Fn(Key, Value) -> R,1522 {1523 let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;15241525 let properties = match token_id {1526 Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1527 None => <PalletCommon<T>>::collection_properties(collection_id),1528 };15291530 let properties = properties.into_iter().filter_map(move |(key, value)| {1531 let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;15321533 let key: Key = key.to_vec().try_into().ok()?;1534 let value: Value = value.decode().ok()?;15351536 Some(mapper(key, value))1537 });15381539 Ok(properties)1540 }15411542 fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1543 map_unique_err_to_proxy! {1544 match err {1545 CommonError::NoPermission => NoPermission,1546 CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1547 CommonError::PublicMintingNotAllowed => NoPermission,1548 CommonError::TokenNotFound => NoAvailableNftId,1549 CommonError::ApprovedValueTooLow => NoPermission,1550 CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1551 StructureError::TokenNotFound => NoAvailableNftId,1552 StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1553 }1554 }1555 }1556}