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::*;3334pub mod misc;35pub mod property;3637use misc::*;38pub use property::*;3940use RmrkProperty::*;4142const NESTING_BUDGET: u32 = 5;4344#[frame_support::pallet]45pub mod pallet {46 use super::*;47 use pallet_evm::account;4849 #[pallet::config]50 pub trait Config:51 frame_system::Config + pallet_common::Config + pallet_nonfungible::Config + account::Config52 {53 type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;54 }5556 #[pallet::storage]57 #[pallet::getter(fn collection_index)]58 pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;5960 #[pallet::storage]61 pub type UniqueCollectionId<T: Config> =62 StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;6364 #[pallet::storage]65 pub type RmrkInernalCollectionId<T: Config> =66 StorageMap<_, Twox64Concat, CollectionId, RmrkCollectionId, ValueQuery>;6768 #[pallet::pallet]69 #[pallet::generate_store(pub(super) trait Store)]70 pub struct Pallet<T>(_);7172 #[pallet::event]73 #[pallet::generate_deposit(pub(super) fn deposit_event)]74 pub enum Event<T: Config> {75 CollectionCreated {76 issuer: T::AccountId,77 collection_id: RmrkCollectionId,78 },79 CollectionDestroyed {80 issuer: T::AccountId,81 collection_id: RmrkCollectionId,82 },83 IssuerChanged {84 old_issuer: T::AccountId,85 new_issuer: T::AccountId,86 collection_id: RmrkCollectionId,87 },88 CollectionLocked {89 issuer: T::AccountId,90 collection_id: RmrkCollectionId,91 },92 NftMinted {93 owner: T::AccountId,94 collection_id: RmrkCollectionId,95 nft_id: RmrkNftId,96 },97 NFTBurned {98 owner: T::AccountId,99 nft_id: RmrkNftId,100 },101 PropertySet {102 collection_id: RmrkCollectionId,103 maybe_nft_id: Option<RmrkNftId>,104 key: RmrkKeyString,105 value: RmrkValueString,106 },107 ResourceAdded {108 nft_id: RmrkNftId,109 resource_id: RmrkResourceId,110 },111 ResourceRemoval {112 nft_id: RmrkNftId,113 resource_id: RmrkResourceId,114 },115 }116117 #[pallet::error]118 pub enum Error<T> {119 120 CorruptedCollectionType,121 NftTypeEncodeError,122 RmrkPropertyKeyIsTooLong,123 RmrkPropertyValueIsTooLong,124125 126 CollectionNotEmpty,127 NoAvailableCollectionId,128 NoAvailableNftId,129 CollectionUnknown,130 NoPermission,131 NonTransferable,132 CollectionFullOrLocked,133 ResourceDoesntExist,134 CannotSendToDescendentOrSelf,135 CannotAcceptNonOwnedNft,136 CannotRejectNonOwnedNft,137 ResourceNotPending,138 }139140 #[pallet::call]141 impl<T: Config> Pallet<T> {142 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]143 #[transactional]144 pub fn create_collection(145 origin: OriginFor<T>,146 metadata: RmrkString,147 max: Option<u32>,148 symbol: RmrkCollectionSymbol,149 ) -> DispatchResult {150 let sender = ensure_signed(origin)?;151152 let limits = CollectionLimits {153 owner_can_transfer: Some(false),154 token_limit: max,155 ..Default::default()156 };157158 let data = CreateCollectionData {159 limits: Some(limits),160 token_prefix: symbol161 .into_inner()162 .try_into()163 .map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,164 permissions: Some(CollectionPermissions {165 nesting: Some(NestingRule::Owner),166 ..Default::default()167 }),168 ..Default::default()169 };170171 let unique_collection_id = Self::init_collection(172 T::CrossAccountId::from_sub(sender.clone()),173 data,174 [175 Self::rmrk_property(Metadata, &metadata)?,176 Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,177 ]178 .into_iter(),179 )?;180 let rmrk_collection_id = <CollectionIndex<T>>::get();181182 <UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);183 <RmrkInernalCollectionId<T>>::insert(unique_collection_id, rmrk_collection_id);184185 <CollectionIndex<T>>::mutate(|n| *n += 1);186187 Self::deposit_event(Event::CollectionCreated {188 issuer: sender,189 collection_id: rmrk_collection_id,190 });191192 Ok(())193 }194195 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]196 #[transactional]197 pub fn destroy_collection(198 origin: OriginFor<T>,199 collection_id: RmrkCollectionId,200 ) -> DispatchResult {201 let sender = ensure_signed(origin)?;202 let cross_sender = T::CrossAccountId::from_sub(sender.clone());203204 let collection = Self::get_typed_nft_collection(205 Self::unique_collection_id(collection_id)?,206 misc::CollectionType::Regular,207 )?;208209 <PalletNft<T>>::destroy_collection(collection, &cross_sender)210 .map_err(Self::map_unique_err_to_proxy)?;211212 Self::deposit_event(Event::CollectionDestroyed {213 issuer: sender,214 collection_id,215 });216217 Ok(())218 }219220 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]221 #[transactional]222 pub fn change_collection_issuer(223 origin: OriginFor<T>,224 collection_id: RmrkCollectionId,225 new_issuer: <T::Lookup as StaticLookup>::Source,226 ) -> DispatchResult {227 let sender = ensure_signed(origin)?;228229 let new_issuer = T::Lookup::lookup(new_issuer)?;230231 Self::change_collection_owner(232 Self::unique_collection_id(collection_id)?,233 misc::CollectionType::Regular,234 sender.clone(),235 new_issuer.clone(),236 )?;237238 Self::deposit_event(Event::IssuerChanged {239 old_issuer: sender,240 new_issuer,241 collection_id,242 });243244 Ok(())245 }246247 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]248 #[transactional]249 pub fn lock_collection(250 origin: OriginFor<T>,251 collection_id: RmrkCollectionId,252 ) -> DispatchResult {253 let sender = ensure_signed(origin)?;254 let cross_sender = T::CrossAccountId::from_sub(sender.clone());255256 let collection = Self::get_typed_nft_collection(257 Self::unique_collection_id(collection_id)?,258 misc::CollectionType::Regular,259 )?;260261 Self::check_collection_owner(&collection, &cross_sender)?;262263 let token_count = collection.total_supply();264265 let mut collection = collection.into_inner();266 collection.limits.token_limit = Some(token_count);267 collection.save()?;268269 Self::deposit_event(Event::CollectionLocked {270 issuer: sender,271 collection_id,272 });273274 Ok(())275 }276277 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]278 #[transactional]279 pub fn mint_nft(280 origin: OriginFor<T>,281 owner: T::AccountId,282 collection_id: RmrkCollectionId,283 recipient: Option<T::AccountId>,284 royalty_amount: Option<Permill>,285 metadata: RmrkString,286 transferable: bool,287 ) -> DispatchResult {288 let sender = ensure_signed(origin)?;289 let sender = T::CrossAccountId::from_sub(sender);290 let cross_owner = T::CrossAccountId::from_sub(owner.clone());291292 let royalty_info = royalty_amount.map(|amount| rmrk::RoyaltyInfo {293 recipient: recipient.unwrap_or_else(|| owner.clone()),294 amount,295 });296297 let collection = Self::get_typed_nft_collection(298 Self::unique_collection_id(collection_id)?,299 misc::CollectionType::Regular,300 )?;301302 let nft_id = Self::create_nft(303 &sender,304 &cross_owner,305 &collection,306 [307 Self::rmrk_property(TokenType, &NftType::Regular)?,308 Self::rmrk_property(Transferable, &transferable)?,309 Self::rmrk_property(PendingNftAccept, &false)?,310 Self::rmrk_property(RoyaltyInfo, &royalty_info)?,311 Self::rmrk_property(Metadata, &metadata)?,312 Self::rmrk_property(Equipped, &false)?,313 Self::rmrk_property(314 ResourceCollection,315 &Self::init_collection(316 sender.clone(),317 CreateCollectionData {318 ..Default::default()319 },320 [Self::rmrk_property(321 CollectionType,322 &misc::CollectionType::Resource,323 )?]324 .into_iter(),325 )?,326 )?, 327 Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,328 ]329 .into_iter(),330 )331 .map_err(|err| match err {332 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),333 err => Self::map_unique_err_to_proxy(err),334 })?;335336 Self::deposit_event(Event::NftMinted {337 owner,338 collection_id,339 nft_id: nft_id.0,340 });341342 Ok(())343 }344345 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]346 #[transactional]347 pub fn burn_nft(348 origin: OriginFor<T>,349 collection_id: RmrkCollectionId,350 nft_id: RmrkNftId,351 ) -> DispatchResult {352 let sender = ensure_signed(origin)?;353 let cross_sender = T::CrossAccountId::from_sub(sender.clone());354355 Self::destroy_nft(356 cross_sender,357 Self::unique_collection_id(collection_id)?,358 nft_id.into(),359 )360 .map_err(Self::map_unique_err_to_proxy)?;361362 Self::deposit_event(Event::NFTBurned {363 owner: sender,364 nft_id,365 });366367 Ok(())368 }369370 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]371 #[transactional]372 pub fn send(373 origin: OriginFor<T>,374 rmrk_collection_id: RmrkCollectionId,375 rmrk_nft_id: RmrkNftId,376 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,377 ) -> DispatchResult {378 let sender = ensure_signed(origin.clone())?;379 let cross_sender = T::CrossAccountId::from_sub(sender);380381 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;382 let nft_id = rmrk_nft_id.into();383384 let token_data =385 <TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;386387 let from = token_data.owner;388389 let collection =390 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;391392 if !Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)? {393 return Err(<Error<T>>::NonTransferable.into());394 }395396 if Self::get_nft_property_decoded(397 collection_id,398 nft_id,399 RmrkProperty::PendingNftAccept,400 )? {401 return Err(<Error<T>>::NoPermission.into());402 }403404 let target_owner;405406 match new_owner {407 RmrkAccountIdOrCollectionNftTuple::AccountId(account_id) => {408 target_owner = T::CrossAccountId::from_sub(account_id);409 }410 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(411 target_collection_id,412 target_nft_id,413 ) => {414 let target_collection_id = Self::unique_collection_id(target_collection_id)?;415416 let target_nft_budget = budget::Value::new(NESTING_BUDGET);417418 let target_nft_owner = <PalletStructure<T>>::get_checked_indirect_owner(419 target_collection_id,420 target_nft_id.into(),421 Some((collection_id, nft_id)),422 &target_nft_budget,423 )424 .map_err(Self::map_unique_err_to_proxy)?;425426 let is_approval_required = cross_sender != target_nft_owner;427428 if is_approval_required {429 target_owner = target_nft_owner;430431 <PalletNft<T>>::set_scoped_token_property(432 collection.id,433 nft_id,434 PropertyScope::Rmrk,435 Self::rmrk_property(PendingNftAccept, &is_approval_required)?,436 )?;437 } else {438 target_owner = T::CrossTokenAddressMapping::token_to_address(439 target_collection_id,440 target_nft_id.into(),441 );442 }443 }444 }445446 let src_nft_budget = budget::Value::new(NESTING_BUDGET);447448 <PalletNft<T>>::transfer_from(449 &collection,450 &cross_sender,451 &from,452 &target_owner,453 nft_id,454 &src_nft_budget,455 )456 .map_err(Self::map_unique_err_to_proxy)?;457458 Ok(())459 }460461 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]462 #[transactional]463 pub fn accept_nft(464 origin: OriginFor<T>,465 rmrk_collection_id: RmrkCollectionId,466 rmrk_nft_id: RmrkNftId,467 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,468 ) -> DispatchResult {469 let sender = ensure_signed(origin.clone())?;470 let cross_sender = T::CrossAccountId::from_sub(sender);471472 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;473 let nft_id = rmrk_nft_id.into();474475 let collection =476 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;477478 let new_owner = match new_owner {479 RmrkAccountIdOrCollectionNftTuple::AccountId(account_id) => {480 T::CrossAccountId::from_sub(account_id)481 }482 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(483 target_collection_id,484 target_nft_id,485 ) => {486 let target_collection_id = Self::unique_collection_id(target_collection_id)?;487488 T::CrossTokenAddressMapping::token_to_address(489 target_collection_id,490 TokenId(target_nft_id),491 )492 }493 };494495 let budget = budget::Value::new(NESTING_BUDGET);496497 <PalletNft<T>>::transfer(&collection, &cross_sender, &new_owner, nft_id, &budget)498 .map_err(|err| {499 if err == <CommonError<T>>::OnlyOwnerAllowedToNest.into() {500 <Error<T>>::CannotAcceptNonOwnedNft.into()501 } else {502 Self::map_unique_err_to_proxy(err)503 }504 })?;505506 <PalletNft<T>>::set_scoped_token_property(507 collection.id,508 nft_id,509 PropertyScope::Rmrk,510 Self::rmrk_property(PendingNftAccept, &false)?,511 )?;512513 Ok(())514 }515516 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]517 #[transactional]518 pub fn reject_nft(519 origin: OriginFor<T>,520 collection_id: RmrkCollectionId,521 nft_id: RmrkNftId,522 ) -> DispatchResult {523 let sender = ensure_signed(origin)?;524 let cross_sender = T::CrossAccountId::from_sub(sender);525526 let collection_id = Self::unique_collection_id(collection_id)?;527 let nft_id = nft_id.into();528529 Self::destroy_nft(cross_sender, collection_id, nft_id).map_err(|err| {530 if err == <CommonError<T>>::NoPermission.into()531 || err == <CommonError<T>>::ApprovedValueTooLow.into()532 {533 <Error<T>>::CannotRejectNonOwnedNft.into()534 } else {535 Self::map_unique_err_to_proxy(err)536 }537 })?;538539 Ok(())540 }541542 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]543 #[transactional]544 pub fn accept_resource(545 origin: OriginFor<T>,546 rmrk_collection_id: RmrkCollectionId,547 rmrk_nft_id: RmrkNftId,548 rmrk_resource_id: RmrkResourceId,549 ) -> DispatchResult {550 let sender = ensure_signed(origin)?;551 let cross_sender = T::CrossAccountId::from_sub(sender);552553 let collection_id = Self::unique_collection_id(rmrk_collection_id)554 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;555556 let nft_id = rmrk_nft_id.into();557 let resource_id = rmrk_resource_id.into();558559 let budget = budget::Value::new(NESTING_BUDGET);560561 let nft_owner =562 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)563 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;564565 let resource_collection_id: CollectionId =566 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)567 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;568569 let is_pending: bool = Self::get_nft_property_decoded(570 resource_collection_id,571 resource_id,572 PendingResourceAccept,573 )574 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;575576 ensure!(is_pending, <Error<T>>::ResourceNotPending);577578 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);579580 <PalletNft<T>>::set_scoped_token_property(581 resource_collection_id,582 rmrk_resource_id.into(),583 PropertyScope::Rmrk,584 Self::rmrk_property(PendingResourceAccept, &false)?,585 )?;586587 Ok(())588 }589590 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]591 #[transactional]592 pub fn accept_resource_removal(593 origin: OriginFor<T>,594 rmrk_collection_id: RmrkCollectionId,595 rmrk_nft_id: RmrkNftId,596 rmrk_resource_id: RmrkResourceId,597 ) -> DispatchResult {598 let sender = ensure_signed(origin)?;599 let cross_sender = T::CrossAccountId::from_sub(sender);600601 let collection_id = Self::unique_collection_id(rmrk_collection_id)602 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;603604 let nft_id = rmrk_nft_id.into();605 let resource_id = rmrk_resource_id.into();606607 let budget = budget::Value::new(NESTING_BUDGET);608609 let nft_owner =610 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)611 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;612613 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);614615 let resource_collection_id: CollectionId =616 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)617 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;618619 let is_pending: bool = Self::get_nft_property_decoded(620 resource_collection_id,621 resource_id,622 PendingResourceRemoval,623 )624 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;625626 ensure!(is_pending, <Error<T>>::ResourceNotPending);627628 let resource_collection = Self::get_typed_nft_collection(629 resource_collection_id,630 misc::CollectionType::Resource,631 )?;632633 <PalletNft<T>>::burn(&resource_collection, &cross_sender, rmrk_resource_id.into())634 .map_err(Self::map_unique_err_to_proxy)?;635636 Ok(())637 }638639 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]640 #[transactional]641 pub fn set_property(642 origin: OriginFor<T>,643 #[pallet::compact] rmrk_collection_id: RmrkCollectionId,644 maybe_nft_id: Option<RmrkNftId>,645 key: RmrkKeyString,646 value: RmrkValueString,647 ) -> DispatchResult {648 let sender = ensure_signed(origin)?;649 let sender = T::CrossAccountId::from_sub(sender);650651 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;652 let budget = budget::Value::new(NESTING_BUDGET);653654 match maybe_nft_id {655 Some(nft_id) => {656 let token_id: TokenId = nft_id.into();657658 Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;659 Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;660661 <PalletNft<T>>::set_scoped_token_property(662 collection_id,663 token_id,664 PropertyScope::Rmrk,665 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,666 )?;667 }668 None => {669 let collection = Self::get_typed_nft_collection(670 collection_id,671 misc::CollectionType::Regular,672 )?;673674 Self::check_collection_owner(&collection, &sender)?;675676 <PalletCommon<T>>::set_scoped_collection_property(677 collection_id,678 PropertyScope::Rmrk,679 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,680 )?;681 }682 }683684 Self::deposit_event(Event::PropertySet {685 collection_id: rmrk_collection_id,686 maybe_nft_id,687 key,688 value,689 });690691 Ok(())692 }693694 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]695 #[transactional]696 pub fn add_basic_resource(697 origin: OriginFor<T>,698 collection_id: RmrkCollectionId,699 nft_id: RmrkNftId,700 resource: RmrkBasicResource,701 ) -> DispatchResult {702 let sender = ensure_signed(origin.clone())?;703704 let resource_id = Self::resource_add(705 sender,706 Self::unique_collection_id(collection_id)?,707 nft_id.into(),708 [709 Self::rmrk_property(TokenType, &NftType::Resource)?,710 Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,711 Self::rmrk_property(Src, &resource.src)?,712 Self::rmrk_property(Metadata, &resource.metadata)?,713 Self::rmrk_property(License, &resource.license)?,714 Self::rmrk_property(Thumb, &resource.thumb)?,715 ]716 .into_iter(),717 )?;718719 Self::deposit_event(Event::ResourceAdded {720 nft_id,721 resource_id,722 });723 Ok(())724 }725726 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]727 #[transactional]728 pub fn add_composable_resource(729 origin: OriginFor<T>,730 collection_id: RmrkCollectionId,731 nft_id: RmrkNftId,732 _resource_id: RmrkBoundedResource,733 resource: RmrkComposableResource,734 ) -> DispatchResult {735 let sender = ensure_signed(origin.clone())?;736737 let resource_id = Self::resource_add(738 sender,739 Self::unique_collection_id(collection_id)?,740 nft_id.into(),741 [742 Self::rmrk_property(TokenType, &NftType::Resource)?,743 Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,744 Self::rmrk_property(Parts, &resource.parts)?,745 Self::rmrk_property(Base, &resource.base)?,746 Self::rmrk_property(Src, &resource.src)?,747 Self::rmrk_property(Metadata, &resource.metadata)?,748 Self::rmrk_property(License, &resource.license)?,749 Self::rmrk_property(Thumb, &resource.thumb)?,750 ]751 .into_iter(),752 )?;753754 Self::deposit_event(Event::ResourceAdded {755 nft_id,756 resource_id,757 });758 Ok(())759 }760761 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]762 #[transactional]763 pub fn add_slot_resource(764 origin: OriginFor<T>,765 collection_id: RmrkCollectionId,766 nft_id: RmrkNftId,767 resource: RmrkSlotResource,768 ) -> DispatchResult {769 let sender = ensure_signed(origin.clone())?;770771 let resource_id = Self::resource_add(772 sender,773 Self::unique_collection_id(collection_id)?,774 nft_id.into(),775 [776 Self::rmrk_property(TokenType, &NftType::Resource)?,777 Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,778 Self::rmrk_property(Base, &resource.base)?,779 Self::rmrk_property(Src, &resource.src)?,780 Self::rmrk_property(Metadata, &resource.metadata)?,781 Self::rmrk_property(Slot, &resource.slot)?,782 Self::rmrk_property(License, &resource.license)?,783 Self::rmrk_property(Thumb, &resource.thumb)?,784 ]785 .into_iter(),786 )?;787788 Self::deposit_event(Event::ResourceAdded {789 nft_id,790 resource_id,791 });792 Ok(())793 }794795 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]796 #[transactional]797 pub fn remove_resource(798 origin: OriginFor<T>,799 collection_id: RmrkCollectionId,800 nft_id: RmrkNftId,801 resource_id: RmrkResourceId,802 ) -> DispatchResult {803 let sender = ensure_signed(origin.clone())?;804805 Self::resource_remove(806 sender,807 Self::unique_collection_id(collection_id)?,808 nft_id.into(),809 resource_id.into(),810 )?;811812 Self::deposit_event(Event::ResourceRemoval {813 nft_id,814 resource_id,815 });816 Ok(())817 }818 }819}820821impl<T: Config> Pallet<T> {822 pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {823 let key = rmrk_key.to_key::<T>()?;824825 let scoped_key = PropertyScope::Rmrk826 .apply(key)827 .map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;828829 Ok(scoped_key)830 }831832 833 pub fn rmrk_property<E: Encode>(834 rmrk_key: RmrkProperty,835 value: &E,836 ) -> Result<Property, DispatchError> {837 let key = rmrk_key.to_key::<T>()?;838839 let value = value840 .encode()841 .try_into()842 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;843844 let property = Property { key, value };845846 Ok(property)847 }848849 pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {850 vec.decode()851 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())852 }853854 pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>855 where856 BoundedVec<u8, S>: TryFrom<Vec<u8>>,857 {858 vec.rebind()859 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())860 }861862 fn init_collection(863 sender: T::CrossAccountId,864 data: CreateCollectionData<T::AccountId>,865 properties: impl Iterator<Item = Property>,866 ) -> Result<CollectionId, DispatchError> {867 let collection_id = <PalletNft<T>>::init_collection(sender, data);868869 if let Err(DispatchError::Arithmetic(_)) = &collection_id {870 return Err(<Error<T>>::NoAvailableCollectionId.into());871 }872873 <PalletCommon<T>>::set_scoped_collection_properties(874 collection_id?,875 PropertyScope::Rmrk,876 properties,877 )?;878879 collection_id880 }881882 pub fn create_nft(883 sender: &T::CrossAccountId,884 owner: &T::CrossAccountId,885 collection: &NonfungibleHandle<T>,886 properties: impl Iterator<Item = Property>,887 ) -> Result<TokenId, DispatchError> {888 let data = CreateNftExData {889 properties: BoundedVec::default(),890 owner: owner.clone(),891 };892893 let budget = budget::Value::new(NESTING_BUDGET);894895 <PalletNft<T>>::create_item(collection, sender, data, &budget)?;896897 let nft_id = <PalletNft<T>>::current_token_id(collection.id);898899 <PalletNft<T>>::set_scoped_token_properties(900 collection.id,901 nft_id,902 PropertyScope::Rmrk,903 properties,904 )?;905906 Ok(nft_id)907 }908909 fn destroy_nft(910 sender: T::CrossAccountId,911 collection_id: CollectionId,912 token_id: TokenId,913 ) -> DispatchResult {914 let collection =915 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;916917 let token_data =918 <TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;919920 let from = token_data.owner;921922 let budget = budget::Value::new(NESTING_BUDGET);923924 <PalletNft<T>>::burn_from(&collection, &sender, &from, token_id, &budget)925 }926927 fn resource_add(928 sender: T::AccountId,929 collection_id: CollectionId,930 token_id: TokenId,931 resource_properties: impl Iterator<Item = Property>,932 ) -> Result<RmrkResourceId, DispatchError> {933 let collection =934 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;935 ensure!(collection.owner == sender, Error::<T>::NoPermission);936937 let sender = T::CrossAccountId::from_sub(sender);938 let budget = budget::Value::new(NESTING_BUDGET);939940 let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, token_id, &budget)941 .map_err(Self::map_unique_err_to_proxy)?;942943 let pending = sender != nft_owner;944945 let resource_collection_id: CollectionId =946 Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;947 let resource_collection =948 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;949950 951952 let resource_id = Self::create_nft(953 &sender,954 &nft_owner,955 &resource_collection,956 resource_properties.chain(957 [958 Self::rmrk_property(PendingResourceAccept, &pending)?,959 Self::rmrk_property(PendingResourceRemoval, &false)?,960 ]961 .into_iter(),962 ),963 )964 .map_err(|err| match err {965 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),966 err => Self::map_unique_err_to_proxy(err),967 })?;968969 Ok(resource_id.0)970 }971972 fn resource_remove(973 sender: T::AccountId,974 collection_id: CollectionId,975 nft_id: TokenId,976 resource_id: TokenId,977 ) -> DispatchResult {978 let collection =979 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;980 ensure!(collection.owner == sender, Error::<T>::NoPermission);981982 let resource_collection_id: CollectionId =983 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;984 let resource_collection =985 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;986 ensure!(987 <PalletNft<T>>::token_exists(&resource_collection, resource_id),988 Error::<T>::ResourceDoesntExist989 );990991 let budget = up_data_structs::budget::Value::new(10);992 let topmost_owner =993 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;994995 let sender = T::CrossAccountId::from_sub(sender);996 if topmost_owner == sender {997 <PalletNft<T>>::burn(&resource_collection, &sender, resource_id)998 .map_err(Self::map_unique_err_to_proxy)?;999 } else {1000 <PalletNft<T>>::set_scoped_token_property(1001 resource_collection_id,1002 resource_id,1003 PropertyScope::Rmrk,1004 Self::rmrk_property(PendingResourceRemoval, &true)?,1005 )?;1006 }10071008 Ok(())1009 }10101011 fn change_collection_owner(1012 collection_id: CollectionId,1013 collection_type: misc::CollectionType,1014 sender: T::AccountId,1015 new_owner: T::AccountId,1016 ) -> DispatchResult {1017 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1018 Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;10191020 let mut collection = collection.into_inner();10211022 collection.owner = new_owner;1023 collection.save()1024 }10251026 fn check_collection_owner(1027 collection: &NonfungibleHandle<T>,1028 account: &T::CrossAccountId,1029 ) -> DispatchResult {1030 collection1031 .check_is_owner(account)1032 .map_err(Self::map_unique_err_to_proxy)1033 }10341035 pub fn last_collection_idx() -> RmrkCollectionId {1036 <CollectionIndex<T>>::get()1037 }10381039 pub fn unique_collection_id(1040 rmrk_collection_id: RmrkCollectionId,1041 ) -> Result<CollectionId, DispatchError> {1042 <UniqueCollectionId<T>>::try_get(rmrk_collection_id)1043 .map_err(|_| <Error<T>>::CollectionUnknown.into())1044 }10451046 pub fn rmrk_collection_id(1047 unique_collection_id: CollectionId,1048 ) -> Result<RmrkCollectionId, DispatchError> {1049 <RmrkInernalCollectionId<T>>::try_get(unique_collection_id)1050 .map_err(|_| <Error<T>>::CollectionUnknown.into())1051 }10521053 pub fn get_nft_collection(1054 collection_id: CollectionId,1055 ) -> Result<NonfungibleHandle<T>, DispatchError> {1056 let collection = <CollectionHandle<T>>::try_get(collection_id)1057 .map_err(|_| <Error<T>>::CollectionUnknown)?;10581059 match collection.mode {1060 CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1061 _ => Err(<Error<T>>::CollectionUnknown.into()),1062 }1063 }10641065 pub fn collection_exists(collection_id: CollectionId) -> bool {1066 <CollectionHandle<T>>::try_get(collection_id).is_ok()1067 }10681069 pub fn get_collection_property(1070 collection_id: CollectionId,1071 key: RmrkProperty,1072 ) -> Result<PropertyValue, DispatchError> {1073 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1074 .get(&Self::rmrk_property_key(key)?)1075 .ok_or(<Error<T>>::CollectionUnknown)?1076 .clone();10771078 Ok(collection_property)1079 }10801081 pub fn get_collection_property_decoded<V: Decode>(1082 collection_id: CollectionId,1083 key: RmrkProperty,1084 ) -> Result<V, DispatchError> {1085 Self::decode_property(Self::get_collection_property(collection_id, key)?)1086 }10871088 pub fn get_collection_type(1089 collection_id: CollectionId,1090 ) -> Result<misc::CollectionType, DispatchError> {1091 Self::get_collection_property_decoded(collection_id, CollectionType)1092 .map_err(|_| <Error<T>>::CorruptedCollectionType.into())1093 }10941095 pub fn ensure_collection_type(1096 collection_id: CollectionId,1097 collection_type: misc::CollectionType,1098 ) -> DispatchResult {1099 let actual_type = Self::get_collection_type(collection_id)?;1100 ensure!(1101 actual_type == collection_type,1102 <CommonError<T>>::NoPermission1103 );11041105 Ok(())1106 }11071108 pub fn get_typed_nft_collection(1109 collection_id: CollectionId,1110 collection_type: misc::CollectionType,1111 ) -> Result<NonfungibleHandle<T>, DispatchError> {1112 Self::ensure_collection_type(collection_id, collection_type)?;11131114 Self::get_nft_collection(collection_id)1115 }11161117 pub fn get_typed_nft_collection_mapped(1118 rmrk_collection_id: RmrkCollectionId,1119 collection_type: misc::CollectionType,1120 ) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1121 let unique_collection_id = Self::unique_collection_id(rmrk_collection_id)?;11221123 let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;11241125 Ok((collection, unique_collection_id))1126 }11271128 pub fn get_nft_property(1129 collection_id: CollectionId,1130 nft_id: TokenId,1131 key: RmrkProperty,1132 ) -> Result<PropertyValue, DispatchError> {1133 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1134 .get(&Self::rmrk_property_key(key)?)1135 .ok_or(<Error<T>>::NoAvailableNftId)? 1136 .clone();11371138 Ok(nft_property)1139 }11401141 pub fn get_nft_property_decoded<V: Decode>(1142 collection_id: CollectionId,1143 nft_id: TokenId,1144 key: RmrkProperty,1145 ) -> Result<V, DispatchError> {1146 Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)1147 }11481149 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1150 <TokenData<T>>::contains_key((collection_id, nft_id))1151 }11521153 pub fn get_nft_type(1154 collection_id: CollectionId,1155 token_id: TokenId,1156 ) -> Result<NftType, DispatchError> {1157 Self::get_nft_property_decoded(collection_id, token_id, TokenType)1158 .map_err(|_| <Error<T>>::NftTypeEncodeError.into())1159 }11601161 pub fn ensure_nft_type(1162 collection_id: CollectionId,1163 token_id: TokenId,1164 nft_type: NftType,1165 ) -> DispatchResult {1166 let actual_type = Self::get_nft_type(collection_id, token_id)?;1167 ensure!(actual_type == nft_type, <Error<T>>::NoPermission);11681169 Ok(())1170 }11711172 pub fn ensure_nft_owner(1173 collection_id: CollectionId,1174 token_id: TokenId,1175 possible_owner: &T::CrossAccountId,1176 nesting_budget: &dyn budget::Budget,1177 ) -> DispatchResult {1178 let is_owned = <PalletStructure<T>>::check_indirectly_owned(1179 possible_owner.clone(),1180 collection_id,1181 token_id,1182 None,1183 nesting_budget,1184 )?;11851186 ensure!(is_owned, <Error<T>>::NoPermission);11871188 Ok(())1189 }11901191 pub fn filter_user_properties<Key, Value, R, Mapper>(1192 collection_id: CollectionId,1193 token_id: Option<TokenId>,1194 filter_keys: Option<Vec<RmrkPropertyKey>>,1195 mapper: Mapper,1196 ) -> Result<Vec<R>, DispatchError>1197 where1198 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1199 Value: Decode + Default,1200 Mapper: Fn(Key, Value) -> R,1201 {1202 filter_keys1203 .map(|keys| {1204 let properties = keys1205 .into_iter()1206 .filter_map(|key| {1207 let key: Key = key.try_into().ok()?;12081209 let value = match token_id {1210 Some(token_id) => Self::get_nft_property_decoded(1211 collection_id,1212 token_id,1213 UserProperty(key.as_ref()),1214 ),1215 None => Self::get_collection_property_decoded(1216 collection_id,1217 UserProperty(key.as_ref()),1218 ),1219 }1220 .ok()?;12211222 Some(mapper(key, value))1223 })1224 .collect();12251226 Ok(properties)1227 })1228 .unwrap_or_else(|| {1229 let properties =1230 Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();12311232 Ok(properties)1233 })1234 }12351236 pub fn iterate_user_properties<Key, Value, R, Mapper>(1237 collection_id: CollectionId,1238 token_id: Option<TokenId>,1239 mapper: Mapper,1240 ) -> Result<impl Iterator<Item = R>, DispatchError>1241 where1242 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1243 Value: Decode + Default,1244 Mapper: Fn(Key, Value) -> R,1245 {1246 let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;12471248 let properties = match token_id {1249 Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1250 None => <PalletCommon<T>>::collection_properties(collection_id),1251 };12521253 let properties = properties.into_iter().filter_map(move |(key, value)| {1254 let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;12551256 let key: Key = key.to_vec().try_into().ok()?;1257 let value: Value = value.decode().ok()?;12581259 Some(mapper(key, value))1260 });12611262 Ok(properties)1263 }12641265 fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1266 map_unique_err_to_proxy! {1267 match err {1268 CommonError::NoPermission => NoPermission,1269 CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1270 CommonError::PublicMintingNotAllowed => NoPermission,1271 CommonError::TokenNotFound => NoAvailableNftId,1272 CommonError::ApprovedValueTooLow => NoPermission,1273 CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1274 StructureError::TokenNotFound => NoAvailableNftId,1275 StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1276 }1277 }1278 }1279}