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 = <PalletStructure<T>>::find_topmost_owner(562 collection_id,563 nft_id,564 &budget,565 ).map_err(|_| <Error<T>>::ResourceDoesntExist)?;566567 let resource_collection_id: CollectionId =568 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)569 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;570571 let is_pending: bool = Self::get_nft_property_decoded(resource_collection_id, resource_id, PendingResourceAccept)572 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;573574 ensure!(is_pending, <Error<T>>::ResourceNotPending);575576 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);577578 <PalletNft<T>>::set_scoped_token_property(579 resource_collection_id,580 resource_id,581 PropertyScope::Rmrk,582 Self::rmrk_property(PendingResourceAccept, &false)?,583 )?;584585 Ok(())586 }587588 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]589 #[transactional]590 pub fn set_property(591 origin: OriginFor<T>,592 #[pallet::compact] rmrk_collection_id: RmrkCollectionId,593 maybe_nft_id: Option<RmrkNftId>,594 key: RmrkKeyString,595 value: RmrkValueString,596 ) -> DispatchResult {597 let sender = ensure_signed(origin)?;598 let sender = T::CrossAccountId::from_sub(sender);599600 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;601 let budget = budget::Value::new(NESTING_BUDGET);602603 match maybe_nft_id {604 Some(nft_id) => {605 let token_id: TokenId = nft_id.into();606607 Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;608 Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;609610 <PalletNft<T>>::set_scoped_token_property(611 collection_id,612 token_id,613 PropertyScope::Rmrk,614 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,615 )?;616 }617 None => {618 let collection = Self::get_typed_nft_collection(619 collection_id,620 misc::CollectionType::Regular,621 )?;622623 Self::check_collection_owner(&collection, &sender)?;624625 <PalletCommon<T>>::set_scoped_collection_property(626 collection_id,627 PropertyScope::Rmrk,628 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,629 )?;630 }631 }632633 Self::deposit_event(Event::PropertySet {634 collection_id: rmrk_collection_id,635 maybe_nft_id,636 key,637 value,638 });639640 Ok(())641 }642643 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]644 #[transactional]645 pub fn add_basic_resource(646 origin: OriginFor<T>,647 collection_id: RmrkCollectionId,648 nft_id: RmrkNftId,649 resource: RmrkBasicResource,650 ) -> DispatchResult {651 let sender = ensure_signed(origin.clone())?;652653 let resource_id = Self::resource_add(654 sender,655 Self::unique_collection_id(collection_id)?,656 nft_id.into(),657 [658 Self::rmrk_property(TokenType, &NftType::Resource)?,659 Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,660 Self::rmrk_property(Src, &resource.src)?,661 Self::rmrk_property(Metadata, &resource.metadata)?,662 Self::rmrk_property(License, &resource.license)?,663 Self::rmrk_property(Thumb, &resource.thumb)?,664 ]665 .into_iter(),666 )?;667668 Self::deposit_event(Event::ResourceAdded {669 nft_id,670 resource_id,671 });672 Ok(())673 }674675 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]676 #[transactional]677 pub fn add_composable_resource(678 origin: OriginFor<T>,679 collection_id: RmrkCollectionId,680 nft_id: RmrkNftId,681 _resource_id: RmrkBoundedResource,682 resource: RmrkComposableResource,683 ) -> DispatchResult {684 let sender = ensure_signed(origin.clone())?;685686 let resource_id = Self::resource_add(687 sender,688 Self::unique_collection_id(collection_id)?,689 nft_id.into(),690 [691 Self::rmrk_property(TokenType, &NftType::Resource)?,692 Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,693 Self::rmrk_property(Parts, &resource.parts)?,694 Self::rmrk_property(Base, &resource.base)?,695 Self::rmrk_property(Src, &resource.src)?,696 Self::rmrk_property(Metadata, &resource.metadata)?,697 Self::rmrk_property(License, &resource.license)?,698 Self::rmrk_property(Thumb, &resource.thumb)?,699 ]700 .into_iter(),701 )?;702703 Self::deposit_event(Event::ResourceAdded {704 nft_id,705 resource_id,706 });707 Ok(())708 }709710 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]711 #[transactional]712 pub fn add_slot_resource(713 origin: OriginFor<T>,714 collection_id: RmrkCollectionId,715 nft_id: RmrkNftId,716 resource: RmrkSlotResource,717 ) -> DispatchResult {718 let sender = ensure_signed(origin.clone())?;719720 let resource_id = Self::resource_add(721 sender,722 Self::unique_collection_id(collection_id)?,723 nft_id.into(),724 [725 Self::rmrk_property(TokenType, &NftType::Resource)?,726 Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,727 Self::rmrk_property(Base, &resource.base)?,728 Self::rmrk_property(Src, &resource.src)?,729 Self::rmrk_property(Metadata, &resource.metadata)?,730 Self::rmrk_property(Slot, &resource.slot)?,731 Self::rmrk_property(License, &resource.license)?,732 Self::rmrk_property(Thumb, &resource.thumb)?,733 ]734 .into_iter(),735 )?;736737 Self::deposit_event(Event::ResourceAdded {738 nft_id,739 resource_id,740 });741 Ok(())742 }743744 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]745 #[transactional]746 pub fn remove_resource(747 origin: OriginFor<T>,748 collection_id: RmrkCollectionId,749 nft_id: RmrkNftId,750 resource_id: RmrkResourceId,751 ) -> DispatchResult {752 let sender = ensure_signed(origin.clone())?;753754 Self::resource_remove(755 sender,756 Self::unique_collection_id(collection_id)?,757 nft_id.into(),758 resource_id.into(),759 )?;760761 Self::deposit_event(Event::ResourceRemoval {762 nft_id,763 resource_id,764 });765 Ok(())766 }767 }768}769770impl<T: Config> Pallet<T> {771 pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {772 let key = rmrk_key.to_key::<T>()?;773774 let scoped_key = PropertyScope::Rmrk775 .apply(key)776 .map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;777778 Ok(scoped_key)779 }780781 782 pub fn rmrk_property<E: Encode>(783 rmrk_key: RmrkProperty,784 value: &E,785 ) -> Result<Property, DispatchError> {786 let key = rmrk_key.to_key::<T>()?;787788 let value = value789 .encode()790 .try_into()791 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;792793 let property = Property { key, value };794795 Ok(property)796 }797798 pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {799 vec.decode()800 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())801 }802803 pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>804 where805 BoundedVec<u8, S>: TryFrom<Vec<u8>>,806 {807 vec.rebind()808 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())809 }810811 fn init_collection(812 sender: T::CrossAccountId,813 data: CreateCollectionData<T::AccountId>,814 properties: impl Iterator<Item = Property>,815 ) -> Result<CollectionId, DispatchError> {816 let collection_id = <PalletNft<T>>::init_collection(sender, data);817818 if let Err(DispatchError::Arithmetic(_)) = &collection_id {819 return Err(<Error<T>>::NoAvailableCollectionId.into());820 }821822 <PalletCommon<T>>::set_scoped_collection_properties(823 collection_id?,824 PropertyScope::Rmrk,825 properties,826 )?;827828 collection_id829 }830831 pub fn create_nft(832 sender: &T::CrossAccountId,833 owner: &T::CrossAccountId,834 collection: &NonfungibleHandle<T>,835 properties: impl Iterator<Item = Property>,836 ) -> Result<TokenId, DispatchError> {837 let data = CreateNftExData {838 properties: BoundedVec::default(),839 owner: owner.clone(),840 };841842 let budget = budget::Value::new(NESTING_BUDGET);843844 <PalletNft<T>>::create_item(collection, sender, data, &budget)?;845846 let nft_id = <PalletNft<T>>::current_token_id(collection.id);847848 <PalletNft<T>>::set_scoped_token_properties(849 collection.id,850 nft_id,851 PropertyScope::Rmrk,852 properties,853 )?;854855 Ok(nft_id)856 }857858 fn destroy_nft(859 sender: T::CrossAccountId,860 collection_id: CollectionId,861 token_id: TokenId,862 ) -> DispatchResult {863 let collection =864 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;865866 let token_data =867 <TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;868869 let from = token_data.owner;870871 let budget = budget::Value::new(NESTING_BUDGET);872873 <PalletNft<T>>::burn_from(&collection, &sender, &from, token_id, &budget)874 }875876 fn resource_add(877 sender: T::AccountId,878 collection_id: CollectionId,879 token_id: TokenId,880 resource_properties: impl Iterator<Item = Property>,881 ) -> Result<RmrkResourceId, DispatchError> {882 let collection =883 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;884 ensure!(collection.owner == sender, Error::<T>::NoPermission);885886 let sender = T::CrossAccountId::from_sub(sender);887 let budget = budget::Value::new(NESTING_BUDGET);888889 let nft_owner = <PalletStructure<T>>::find_topmost_owner(890 collection_id,891 token_id,892 &budget,893 ).map_err(Self::map_unique_err_to_proxy)?;894895 let pending = sender != nft_owner;896897 let resource_collection_id: CollectionId =898 Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;899 let resource_collection =900 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;901902 903904 let resource_id = Self::create_nft(905 &sender,906 &nft_owner,907 &resource_collection,908 resource_properties.chain(909 [910 Self::rmrk_property(PendingResourceAccept, &pending)?,911 Self::rmrk_property(PendingResourceRemoval, &false)?,912 ]913 .into_iter(),914 ),915 )916 .map_err(|err| match err {917 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),918 err => Self::map_unique_err_to_proxy(err),919 })?;920921 Ok(resource_id.0)922 }923924 fn resource_remove(925 sender: T::AccountId,926 collection_id: CollectionId,927 nft_id: TokenId,928 resource_id: TokenId,929 ) -> DispatchResult {930 let collection =931 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;932 ensure!(collection.owner == sender, Error::<T>::NoPermission);933934 let resource_collection_id: CollectionId =935 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;936 let resource_collection =937 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;938 ensure!(939 <PalletNft<T>>::token_exists(&resource_collection, resource_id),940 Error::<T>::ResourceDoesntExist941 );942943 let budget = up_data_structs::budget::Value::new(10);944 let topmost_owner =945 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;946947 let sender = T::CrossAccountId::from_sub(sender);948 if topmost_owner == sender {949 <PalletNft<T>>::burn(&resource_collection, &sender, resource_id)950 .map_err(Self::map_unique_err_to_proxy)?;951 } else {952 <PalletNft<T>>::set_scoped_token_property(953 resource_collection_id,954 resource_id,955 PropertyScope::Rmrk,956 Self::rmrk_property(PendingResourceRemoval, &true)?,957 )?;958 }959960 Ok(())961 }962963 fn change_collection_owner(964 collection_id: CollectionId,965 collection_type: misc::CollectionType,966 sender: T::AccountId,967 new_owner: T::AccountId,968 ) -> DispatchResult {969 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;970 Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;971972 let mut collection = collection.into_inner();973974 collection.owner = new_owner;975 collection.save()976 }977978 fn check_collection_owner(979 collection: &NonfungibleHandle<T>,980 account: &T::CrossAccountId,981 ) -> DispatchResult {982 collection983 .check_is_owner(account)984 .map_err(Self::map_unique_err_to_proxy)985 }986987 pub fn last_collection_idx() -> RmrkCollectionId {988 <CollectionIndex<T>>::get()989 }990991 pub fn unique_collection_id(992 rmrk_collection_id: RmrkCollectionId,993 ) -> Result<CollectionId, DispatchError> {994 <UniqueCollectionId<T>>::try_get(rmrk_collection_id)995 .map_err(|_| <Error<T>>::CollectionUnknown.into())996 }997998 pub fn rmrk_collection_id(999 unique_collection_id: CollectionId,1000 ) -> Result<RmrkCollectionId, DispatchError> {1001 <RmrkInernalCollectionId<T>>::try_get(unique_collection_id)1002 .map_err(|_| <Error<T>>::CollectionUnknown.into())1003 }10041005 pub fn get_nft_collection(1006 collection_id: CollectionId,1007 ) -> Result<NonfungibleHandle<T>, DispatchError> {1008 let collection = <CollectionHandle<T>>::try_get(collection_id)1009 .map_err(|_| <Error<T>>::CollectionUnknown)?;10101011 match collection.mode {1012 CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1013 _ => Err(<Error<T>>::CollectionUnknown.into()),1014 }1015 }10161017 pub fn collection_exists(collection_id: CollectionId) -> bool {1018 <CollectionHandle<T>>::try_get(collection_id).is_ok()1019 }10201021 pub fn get_collection_property(1022 collection_id: CollectionId,1023 key: RmrkProperty,1024 ) -> Result<PropertyValue, DispatchError> {1025 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1026 .get(&Self::rmrk_property_key(key)?)1027 .ok_or(<Error<T>>::CollectionUnknown)?1028 .clone();10291030 Ok(collection_property)1031 }10321033 pub fn get_collection_property_decoded<V: Decode>(1034 collection_id: CollectionId,1035 key: RmrkProperty,1036 ) -> Result<V, DispatchError> {1037 Self::decode_property(Self::get_collection_property(collection_id, key)?)1038 }10391040 pub fn get_collection_type(1041 collection_id: CollectionId,1042 ) -> Result<misc::CollectionType, DispatchError> {1043 Self::get_collection_property_decoded(collection_id, CollectionType)1044 .map_err(|_| <Error<T>>::CorruptedCollectionType.into())1045 }10461047 pub fn ensure_collection_type(1048 collection_id: CollectionId,1049 collection_type: misc::CollectionType,1050 ) -> DispatchResult {1051 let actual_type = Self::get_collection_type(collection_id)?;1052 ensure!(1053 actual_type == collection_type,1054 <CommonError<T>>::NoPermission1055 );10561057 Ok(())1058 }10591060 pub fn get_typed_nft_collection(1061 collection_id: CollectionId,1062 collection_type: misc::CollectionType,1063 ) -> Result<NonfungibleHandle<T>, DispatchError> {1064 Self::ensure_collection_type(collection_id, collection_type)?;10651066 Self::get_nft_collection(collection_id)1067 }10681069 pub fn get_typed_nft_collection_mapped(1070 rmrk_collection_id: RmrkCollectionId,1071 collection_type: misc::CollectionType,1072 ) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1073 let unique_collection_id = Self::unique_collection_id(rmrk_collection_id)?;10741075 let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;10761077 Ok((collection, unique_collection_id))1078 }10791080 pub fn get_nft_property(1081 collection_id: CollectionId,1082 nft_id: TokenId,1083 key: RmrkProperty,1084 ) -> Result<PropertyValue, DispatchError> {1085 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1086 .get(&Self::rmrk_property_key(key)?)1087 .ok_or(<Error<T>>::NoAvailableNftId)? 1088 .clone();10891090 Ok(nft_property)1091 }10921093 pub fn get_nft_property_decoded<V: Decode>(1094 collection_id: CollectionId,1095 nft_id: TokenId,1096 key: RmrkProperty,1097 ) -> Result<V, DispatchError> {1098 Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)1099 }11001101 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1102 <TokenData<T>>::contains_key((collection_id, nft_id))1103 }11041105 pub fn get_nft_type(1106 collection_id: CollectionId,1107 token_id: TokenId,1108 ) -> Result<NftType, DispatchError> {1109 Self::get_nft_property_decoded(collection_id, token_id, TokenType)1110 .map_err(|_| <Error<T>>::NftTypeEncodeError.into())1111 }11121113 pub fn ensure_nft_type(1114 collection_id: CollectionId,1115 token_id: TokenId,1116 nft_type: NftType,1117 ) -> DispatchResult {1118 let actual_type = Self::get_nft_type(collection_id, token_id)?;1119 ensure!(actual_type == nft_type, <Error<T>>::NoPermission);11201121 Ok(())1122 }11231124 pub fn ensure_nft_owner(1125 collection_id: CollectionId,1126 token_id: TokenId,1127 possible_owner: &T::CrossAccountId,1128 nesting_budget: &dyn budget::Budget,1129 ) -> DispatchResult {1130 let is_owned = <PalletStructure<T>>::check_indirectly_owned(1131 possible_owner.clone(),1132 collection_id,1133 token_id,1134 None,1135 nesting_budget,1136 )?;11371138 ensure!(is_owned, <Error<T>>::NoPermission);11391140 Ok(())1141 }11421143 pub fn filter_user_properties<Key, Value, R, Mapper>(1144 collection_id: CollectionId,1145 token_id: Option<TokenId>,1146 filter_keys: Option<Vec<RmrkPropertyKey>>,1147 mapper: Mapper,1148 ) -> Result<Vec<R>, DispatchError>1149 where1150 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1151 Value: Decode + Default,1152 Mapper: Fn(Key, Value) -> R,1153 {1154 filter_keys1155 .map(|keys| {1156 let properties = keys1157 .into_iter()1158 .filter_map(|key| {1159 let key: Key = key.try_into().ok()?;11601161 let value = match token_id {1162 Some(token_id) => Self::get_nft_property_decoded(1163 collection_id,1164 token_id,1165 UserProperty(key.as_ref()),1166 ),1167 None => Self::get_collection_property_decoded(1168 collection_id,1169 UserProperty(key.as_ref()),1170 ),1171 }1172 .ok()?;11731174 Some(mapper(key, value))1175 })1176 .collect();11771178 Ok(properties)1179 })1180 .unwrap_or_else(|| {1181 let properties =1182 Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();11831184 Ok(properties)1185 })1186 }11871188 pub fn iterate_user_properties<Key, Value, R, Mapper>(1189 collection_id: CollectionId,1190 token_id: Option<TokenId>,1191 mapper: Mapper,1192 ) -> Result<impl Iterator<Item = R>, DispatchError>1193 where1194 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1195 Value: Decode + Default,1196 Mapper: Fn(Key, Value) -> R,1197 {1198 let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;11991200 let properties = match token_id {1201 Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1202 None => <PalletCommon<T>>::collection_properties(collection_id),1203 };12041205 let properties = properties.into_iter().filter_map(move |(key, value)| {1206 let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;12071208 let key: Key = key.to_vec().try_into().ok()?;1209 let value: Value = value.decode().ok()?;12101211 Some(mapper(key, value))1212 });12131214 Ok(properties)1215 }12161217 fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1218 map_unique_err_to_proxy! {1219 match err {1220 CommonError::NoPermission => NoPermission,1221 CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1222 CommonError::PublicMintingNotAllowed => NoPermission,1223 CommonError::TokenNotFound => NoAvailableNftId,1224 CommonError::ApprovedValueTooLow => NoPermission,1225 CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1226 StructureError::TokenNotFound => NoAvailableNftId,1227 StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1228 }1229 }1230 }1231}