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 ensure!(393 Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,394 <Error<T>>::NonTransferable395 );396397 ensure!(398 !Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::PendingNftAccept)?,399 <Error<T>>::NoPermission400 );401402 let target_owner;403404 match new_owner {405 RmrkAccountIdOrCollectionNftTuple::AccountId(account_id) => {406 target_owner = T::CrossAccountId::from_sub(account_id);407 }408 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(409 target_collection_id,410 target_nft_id,411 ) => {412 let target_collection_id = Self::unique_collection_id(target_collection_id)?;413414 let target_nft_budget = budget::Value::new(NESTING_BUDGET);415416 let target_nft_owner = <PalletStructure<T>>::get_checked_indirect_owner(417 target_collection_id,418 target_nft_id.into(),419 Some((collection_id, nft_id)),420 &target_nft_budget,421 )422 .map_err(Self::map_unique_err_to_proxy)?;423424 let is_approval_required = cross_sender != target_nft_owner;425426 if is_approval_required {427 target_owner = target_nft_owner;428429 <PalletNft<T>>::set_scoped_token_property(430 collection.id,431 nft_id,432 PropertyScope::Rmrk,433 Self::rmrk_property(PendingNftAccept, &is_approval_required)?,434 )?;435 } else {436 target_owner = T::CrossTokenAddressMapping::token_to_address(437 target_collection_id,438 target_nft_id.into(),439 );440 }441 }442 }443444 let src_nft_budget = budget::Value::new(NESTING_BUDGET);445446 <PalletNft<T>>::transfer_from(447 &collection,448 &cross_sender,449 &from,450 &target_owner,451 nft_id,452 &src_nft_budget,453 )454 .map_err(Self::map_unique_err_to_proxy)?;455456 Ok(())457 }458459 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]460 #[transactional]461 pub fn accept_nft(462 origin: OriginFor<T>,463 rmrk_collection_id: RmrkCollectionId,464 rmrk_nft_id: RmrkNftId,465 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,466 ) -> DispatchResult {467 let sender = ensure_signed(origin.clone())?;468 let cross_sender = T::CrossAccountId::from_sub(sender);469470 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;471 let nft_id = rmrk_nft_id.into();472473 let collection =474 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;475476 let new_owner = match new_owner {477 RmrkAccountIdOrCollectionNftTuple::AccountId(account_id) => {478 T::CrossAccountId::from_sub(account_id)479 }480 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(481 target_collection_id,482 target_nft_id,483 ) => {484 let target_collection_id = Self::unique_collection_id(target_collection_id)?;485486 T::CrossTokenAddressMapping::token_to_address(487 target_collection_id,488 TokenId(target_nft_id),489 )490 }491 };492493 let budget = budget::Value::new(NESTING_BUDGET);494495 <PalletNft<T>>::transfer(&collection, &cross_sender, &new_owner, nft_id, &budget)496 .map_err(|err| {497 if err == <CommonError<T>>::OnlyOwnerAllowedToNest.into() {498 <Error<T>>::CannotAcceptNonOwnedNft.into()499 } else {500 Self::map_unique_err_to_proxy(err)501 }502 })?;503504 <PalletNft<T>>::set_scoped_token_property(505 collection.id,506 nft_id,507 PropertyScope::Rmrk,508 Self::rmrk_property(PendingNftAccept, &false)?,509 )?;510511 Ok(())512 }513514 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]515 #[transactional]516 pub fn reject_nft(517 origin: OriginFor<T>,518 collection_id: RmrkCollectionId,519 nft_id: RmrkNftId,520 ) -> DispatchResult {521 let sender = ensure_signed(origin)?;522 let cross_sender = T::CrossAccountId::from_sub(sender);523524 let collection_id = Self::unique_collection_id(collection_id)?;525 let nft_id = nft_id.into();526527 Self::destroy_nft(cross_sender, collection_id, nft_id).map_err(|err| {528 if err == <CommonError<T>>::NoPermission.into()529 || err == <CommonError<T>>::ApprovedValueTooLow.into()530 {531 <Error<T>>::CannotRejectNonOwnedNft.into()532 } else {533 Self::map_unique_err_to_proxy(err)534 }535 })?;536537 Ok(())538 }539540 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]541 #[transactional]542 pub fn accept_resource(543 origin: OriginFor<T>,544 rmrk_collection_id: RmrkCollectionId,545 rmrk_nft_id: RmrkNftId,546 rmrk_resource_id: RmrkResourceId,547 ) -> DispatchResult {548 let sender = ensure_signed(origin)?;549 let cross_sender = T::CrossAccountId::from_sub(sender);550551 let collection_id = Self::unique_collection_id(rmrk_collection_id)552 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;553554 let nft_id = rmrk_nft_id.into();555 let resource_id = rmrk_resource_id.into();556557 let budget = budget::Value::new(NESTING_BUDGET);558559 let nft_owner =560 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)561 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;562563 let resource_collection_id: CollectionId =564 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)565 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;566567 let is_pending: bool = Self::get_nft_property_decoded(568 resource_collection_id,569 resource_id,570 PendingResourceAccept,571 )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 rmrk_resource_id.into(),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 accept_resource_removal(591 origin: OriginFor<T>,592 rmrk_collection_id: RmrkCollectionId,593 rmrk_nft_id: RmrkNftId,594 rmrk_resource_id: RmrkResourceId,595 ) -> DispatchResult {596 let sender = ensure_signed(origin)?;597 let cross_sender = T::CrossAccountId::from_sub(sender);598599 let collection_id = Self::unique_collection_id(rmrk_collection_id)600 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;601602 let nft_id = rmrk_nft_id.into();603 let resource_id = rmrk_resource_id.into();604605 let budget = budget::Value::new(NESTING_BUDGET);606607 let nft_owner =608 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)609 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;610611 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);612613 let resource_collection_id: CollectionId =614 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)615 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;616617 let is_pending: bool = Self::get_nft_property_decoded(618 resource_collection_id,619 resource_id,620 PendingResourceRemoval,621 )622 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;623624 ensure!(is_pending, <Error<T>>::ResourceNotPending);625626 let resource_collection = Self::get_typed_nft_collection(627 resource_collection_id,628 misc::CollectionType::Resource,629 )?;630631 <PalletNft<T>>::burn(&resource_collection, &cross_sender, rmrk_resource_id.into())632 .map_err(Self::map_unique_err_to_proxy)?;633634 Ok(())635 }636637 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]638 #[transactional]639 pub fn set_property(640 origin: OriginFor<T>,641 #[pallet::compact] rmrk_collection_id: RmrkCollectionId,642 maybe_nft_id: Option<RmrkNftId>,643 key: RmrkKeyString,644 value: RmrkValueString,645 ) -> DispatchResult {646 let sender = ensure_signed(origin)?;647 let sender = T::CrossAccountId::from_sub(sender);648649 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;650 let budget = budget::Value::new(NESTING_BUDGET);651652 match maybe_nft_id {653 Some(nft_id) => {654 let token_id: TokenId = nft_id.into();655656 Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;657 Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;658659 <PalletNft<T>>::set_scoped_token_property(660 collection_id,661 token_id,662 PropertyScope::Rmrk,663 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,664 )?;665 }666 None => {667 let collection = Self::get_typed_nft_collection(668 collection_id,669 misc::CollectionType::Regular,670 )?;671672 Self::check_collection_owner(&collection, &sender)?;673674 <PalletCommon<T>>::set_scoped_collection_property(675 collection_id,676 PropertyScope::Rmrk,677 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,678 )?;679 }680 }681682 Self::deposit_event(Event::PropertySet {683 collection_id: rmrk_collection_id,684 maybe_nft_id,685 key,686 value,687 });688689 Ok(())690 }691692 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]693 #[transactional]694 pub fn add_basic_resource(695 origin: OriginFor<T>,696 collection_id: RmrkCollectionId,697 nft_id: RmrkNftId,698 resource: RmrkBasicResource,699 ) -> DispatchResult {700 let sender = ensure_signed(origin.clone())?;701702 let resource_id = Self::resource_add(703 sender,704 Self::unique_collection_id(collection_id)?,705 nft_id.into(),706 [707 Self::rmrk_property(TokenType, &NftType::Resource)?,708 Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,709 Self::rmrk_property(Src, &resource.src)?,710 Self::rmrk_property(Metadata, &resource.metadata)?,711 Self::rmrk_property(License, &resource.license)?,712 Self::rmrk_property(Thumb, &resource.thumb)?,713 ]714 .into_iter(),715 )?;716717 Self::deposit_event(Event::ResourceAdded {718 nft_id,719 resource_id,720 });721 Ok(())722 }723724 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]725 #[transactional]726 pub fn add_composable_resource(727 origin: OriginFor<T>,728 collection_id: RmrkCollectionId,729 nft_id: RmrkNftId,730 _resource_id: RmrkBoundedResource,731 resource: RmrkComposableResource,732 ) -> DispatchResult {733 let sender = ensure_signed(origin.clone())?;734735 let resource_id = Self::resource_add(736 sender,737 Self::unique_collection_id(collection_id)?,738 nft_id.into(),739 [740 Self::rmrk_property(TokenType, &NftType::Resource)?,741 Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,742 Self::rmrk_property(Parts, &resource.parts)?,743 Self::rmrk_property(Base, &resource.base)?,744 Self::rmrk_property(Src, &resource.src)?,745 Self::rmrk_property(Metadata, &resource.metadata)?,746 Self::rmrk_property(License, &resource.license)?,747 Self::rmrk_property(Thumb, &resource.thumb)?,748 ]749 .into_iter(),750 )?;751752 Self::deposit_event(Event::ResourceAdded {753 nft_id,754 resource_id,755 });756 Ok(())757 }758759 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]760 #[transactional]761 pub fn add_slot_resource(762 origin: OriginFor<T>,763 collection_id: RmrkCollectionId,764 nft_id: RmrkNftId,765 resource: RmrkSlotResource,766 ) -> DispatchResult {767 let sender = ensure_signed(origin.clone())?;768769 let resource_id = Self::resource_add(770 sender,771 Self::unique_collection_id(collection_id)?,772 nft_id.into(),773 [774 Self::rmrk_property(TokenType, &NftType::Resource)?,775 Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,776 Self::rmrk_property(Base, &resource.base)?,777 Self::rmrk_property(Src, &resource.src)?,778 Self::rmrk_property(Metadata, &resource.metadata)?,779 Self::rmrk_property(Slot, &resource.slot)?,780 Self::rmrk_property(License, &resource.license)?,781 Self::rmrk_property(Thumb, &resource.thumb)?,782 ]783 .into_iter(),784 )?;785786 Self::deposit_event(Event::ResourceAdded {787 nft_id,788 resource_id,789 });790 Ok(())791 }792793 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]794 #[transactional]795 pub fn remove_resource(796 origin: OriginFor<T>,797 collection_id: RmrkCollectionId,798 nft_id: RmrkNftId,799 resource_id: RmrkResourceId,800 ) -> DispatchResult {801 let sender = ensure_signed(origin.clone())?;802803 Self::resource_remove(804 sender,805 Self::unique_collection_id(collection_id)?,806 nft_id.into(),807 resource_id.into(),808 )?;809810 Self::deposit_event(Event::ResourceRemoval {811 nft_id,812 resource_id,813 });814 Ok(())815 }816 }817}818819impl<T: Config> Pallet<T> {820 pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {821 let key = rmrk_key.to_key::<T>()?;822823 let scoped_key = PropertyScope::Rmrk824 .apply(key)825 .map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;826827 Ok(scoped_key)828 }829830 831 pub fn rmrk_property<E: Encode>(832 rmrk_key: RmrkProperty,833 value: &E,834 ) -> Result<Property, DispatchError> {835 let key = rmrk_key.to_key::<T>()?;836837 let value = value838 .encode()839 .try_into()840 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;841842 let property = Property { key, value };843844 Ok(property)845 }846847 pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {848 vec.decode()849 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())850 }851852 pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>853 where854 BoundedVec<u8, S>: TryFrom<Vec<u8>>,855 {856 vec.rebind()857 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())858 }859860 fn init_collection(861 sender: T::CrossAccountId,862 data: CreateCollectionData<T::AccountId>,863 properties: impl Iterator<Item = Property>,864 ) -> Result<CollectionId, DispatchError> {865 let collection_id = <PalletNft<T>>::init_collection(sender, data);866867 if let Err(DispatchError::Arithmetic(_)) = &collection_id {868 return Err(<Error<T>>::NoAvailableCollectionId.into());869 }870871 <PalletCommon<T>>::set_scoped_collection_properties(872 collection_id?,873 PropertyScope::Rmrk,874 properties,875 )?;876877 collection_id878 }879880 pub fn create_nft(881 sender: &T::CrossAccountId,882 owner: &T::CrossAccountId,883 collection: &NonfungibleHandle<T>,884 properties: impl Iterator<Item = Property>,885 ) -> Result<TokenId, DispatchError> {886 let data = CreateNftExData {887 properties: BoundedVec::default(),888 owner: owner.clone(),889 };890891 let budget = budget::Value::new(NESTING_BUDGET);892893 <PalletNft<T>>::create_item(collection, sender, data, &budget)?;894895 let nft_id = <PalletNft<T>>::current_token_id(collection.id);896897 <PalletNft<T>>::set_scoped_token_properties(898 collection.id,899 nft_id,900 PropertyScope::Rmrk,901 properties,902 )?;903904 Ok(nft_id)905 }906907 fn destroy_nft(908 sender: T::CrossAccountId,909 collection_id: CollectionId,910 token_id: TokenId,911 ) -> DispatchResult {912 let collection =913 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;914915 let token_data =916 <TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;917918 let from = token_data.owner;919920 let budget = budget::Value::new(NESTING_BUDGET);921922 <PalletNft<T>>::burn_from(&collection, &sender, &from, token_id, &budget)923 }924925 fn resource_add(926 sender: T::AccountId,927 collection_id: CollectionId,928 token_id: TokenId,929 resource_properties: impl Iterator<Item = Property>,930 ) -> Result<RmrkResourceId, DispatchError> {931 let collection =932 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;933 ensure!(collection.owner == sender, Error::<T>::NoPermission);934935 let sender = T::CrossAccountId::from_sub(sender);936 let budget = budget::Value::new(NESTING_BUDGET);937938 let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, token_id, &budget)939 .map_err(Self::map_unique_err_to_proxy)?;940941 let pending = sender != nft_owner;942943 let resource_collection_id: CollectionId =944 Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;945 let resource_collection =946 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;947948 949950 let resource_id = Self::create_nft(951 &sender,952 &nft_owner,953 &resource_collection,954 resource_properties.chain(955 [956 Self::rmrk_property(PendingResourceAccept, &pending)?,957 Self::rmrk_property(PendingResourceRemoval, &false)?,958 ]959 .into_iter(),960 ),961 )962 .map_err(|err| match err {963 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),964 err => Self::map_unique_err_to_proxy(err),965 })?;966967 Ok(resource_id.0)968 }969970 fn resource_remove(971 sender: T::AccountId,972 collection_id: CollectionId,973 nft_id: TokenId,974 resource_id: TokenId,975 ) -> DispatchResult {976 let collection =977 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;978 ensure!(collection.owner == sender, Error::<T>::NoPermission);979980 let resource_collection_id: CollectionId =981 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;982 let resource_collection =983 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;984 ensure!(985 <PalletNft<T>>::token_exists(&resource_collection, resource_id),986 Error::<T>::ResourceDoesntExist987 );988989 let budget = up_data_structs::budget::Value::new(10);990 let topmost_owner =991 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;992993 let sender = T::CrossAccountId::from_sub(sender);994 if topmost_owner == sender {995 <PalletNft<T>>::burn(&resource_collection, &sender, resource_id)996 .map_err(Self::map_unique_err_to_proxy)?;997 } else {998 <PalletNft<T>>::set_scoped_token_property(999 resource_collection_id,1000 resource_id,1001 PropertyScope::Rmrk,1002 Self::rmrk_property(PendingResourceRemoval, &true)?,1003 )?;1004 }10051006 Ok(())1007 }10081009 fn change_collection_owner(1010 collection_id: CollectionId,1011 collection_type: misc::CollectionType,1012 sender: T::AccountId,1013 new_owner: T::AccountId,1014 ) -> DispatchResult {1015 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1016 Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;10171018 let mut collection = collection.into_inner();10191020 collection.owner = new_owner;1021 collection.save()1022 }10231024 fn check_collection_owner(1025 collection: &NonfungibleHandle<T>,1026 account: &T::CrossAccountId,1027 ) -> DispatchResult {1028 collection1029 .check_is_owner(account)1030 .map_err(Self::map_unique_err_to_proxy)1031 }10321033 pub fn last_collection_idx() -> RmrkCollectionId {1034 <CollectionIndex<T>>::get()1035 }10361037 pub fn unique_collection_id(1038 rmrk_collection_id: RmrkCollectionId,1039 ) -> Result<CollectionId, DispatchError> {1040 <UniqueCollectionId<T>>::try_get(rmrk_collection_id)1041 .map_err(|_| <Error<T>>::CollectionUnknown.into())1042 }10431044 pub fn rmrk_collection_id(1045 unique_collection_id: CollectionId,1046 ) -> Result<RmrkCollectionId, DispatchError> {1047 <RmrkInernalCollectionId<T>>::try_get(unique_collection_id)1048 .map_err(|_| <Error<T>>::CollectionUnknown.into())1049 }10501051 pub fn get_nft_collection(1052 collection_id: CollectionId,1053 ) -> Result<NonfungibleHandle<T>, DispatchError> {1054 let collection = <CollectionHandle<T>>::try_get(collection_id)1055 .map_err(|_| <Error<T>>::CollectionUnknown)?;10561057 match collection.mode {1058 CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1059 _ => Err(<Error<T>>::CollectionUnknown.into()),1060 }1061 }10621063 pub fn collection_exists(collection_id: CollectionId) -> bool {1064 <CollectionHandle<T>>::try_get(collection_id).is_ok()1065 }10661067 pub fn get_collection_property(1068 collection_id: CollectionId,1069 key: RmrkProperty,1070 ) -> Result<PropertyValue, DispatchError> {1071 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1072 .get(&Self::rmrk_property_key(key)?)1073 .ok_or(<Error<T>>::CollectionUnknown)?1074 .clone();10751076 Ok(collection_property)1077 }10781079 pub fn get_collection_property_decoded<V: Decode>(1080 collection_id: CollectionId,1081 key: RmrkProperty,1082 ) -> Result<V, DispatchError> {1083 Self::decode_property(Self::get_collection_property(collection_id, key)?)1084 }10851086 pub fn get_collection_type(1087 collection_id: CollectionId,1088 ) -> Result<misc::CollectionType, DispatchError> {1089 Self::get_collection_property_decoded(collection_id, CollectionType)1090 .map_err(|_| <Error<T>>::CorruptedCollectionType.into())1091 }10921093 pub fn ensure_collection_type(1094 collection_id: CollectionId,1095 collection_type: misc::CollectionType,1096 ) -> DispatchResult {1097 let actual_type = Self::get_collection_type(collection_id)?;1098 ensure!(1099 actual_type == collection_type,1100 <CommonError<T>>::NoPermission1101 );11021103 Ok(())1104 }11051106 pub fn get_typed_nft_collection(1107 collection_id: CollectionId,1108 collection_type: misc::CollectionType,1109 ) -> Result<NonfungibleHandle<T>, DispatchError> {1110 Self::ensure_collection_type(collection_id, collection_type)?;11111112 Self::get_nft_collection(collection_id)1113 }11141115 pub fn get_typed_nft_collection_mapped(1116 rmrk_collection_id: RmrkCollectionId,1117 collection_type: misc::CollectionType,1118 ) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1119 let unique_collection_id = Self::unique_collection_id(rmrk_collection_id)?;11201121 let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;11221123 Ok((collection, unique_collection_id))1124 }11251126 pub fn get_nft_property(1127 collection_id: CollectionId,1128 nft_id: TokenId,1129 key: RmrkProperty,1130 ) -> Result<PropertyValue, DispatchError> {1131 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1132 .get(&Self::rmrk_property_key(key)?)1133 .ok_or(<Error<T>>::NoAvailableNftId)? 1134 .clone();11351136 Ok(nft_property)1137 }11381139 pub fn get_nft_property_decoded<V: Decode>(1140 collection_id: CollectionId,1141 nft_id: TokenId,1142 key: RmrkProperty,1143 ) -> Result<V, DispatchError> {1144 Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)1145 }11461147 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1148 <TokenData<T>>::contains_key((collection_id, nft_id))1149 }11501151 pub fn get_nft_type(1152 collection_id: CollectionId,1153 token_id: TokenId,1154 ) -> Result<NftType, DispatchError> {1155 Self::get_nft_property_decoded(collection_id, token_id, TokenType)1156 .map_err(|_| <Error<T>>::NftTypeEncodeError.into())1157 }11581159 pub fn ensure_nft_type(1160 collection_id: CollectionId,1161 token_id: TokenId,1162 nft_type: NftType,1163 ) -> DispatchResult {1164 let actual_type = Self::get_nft_type(collection_id, token_id)?;1165 ensure!(actual_type == nft_type, <Error<T>>::NoPermission);11661167 Ok(())1168 }11691170 pub fn ensure_nft_owner(1171 collection_id: CollectionId,1172 token_id: TokenId,1173 possible_owner: &T::CrossAccountId,1174 nesting_budget: &dyn budget::Budget,1175 ) -> DispatchResult {1176 let is_owned = <PalletStructure<T>>::check_indirectly_owned(1177 possible_owner.clone(),1178 collection_id,1179 token_id,1180 None,1181 nesting_budget,1182 )?;11831184 ensure!(is_owned, <Error<T>>::NoPermission);11851186 Ok(())1187 }11881189 pub fn filter_user_properties<Key, Value, R, Mapper>(1190 collection_id: CollectionId,1191 token_id: Option<TokenId>,1192 filter_keys: Option<Vec<RmrkPropertyKey>>,1193 mapper: Mapper,1194 ) -> Result<Vec<R>, DispatchError>1195 where1196 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1197 Value: Decode + Default,1198 Mapper: Fn(Key, Value) -> R,1199 {1200 filter_keys1201 .map(|keys| {1202 let properties = keys1203 .into_iter()1204 .filter_map(|key| {1205 let key: Key = key.try_into().ok()?;12061207 let value = match token_id {1208 Some(token_id) => Self::get_nft_property_decoded(1209 collection_id,1210 token_id,1211 UserProperty(key.as_ref()),1212 ),1213 None => Self::get_collection_property_decoded(1214 collection_id,1215 UserProperty(key.as_ref()),1216 ),1217 }1218 .ok()?;12191220 Some(mapper(key, value))1221 })1222 .collect();12231224 Ok(properties)1225 })1226 .unwrap_or_else(|| {1227 let properties =1228 Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();12291230 Ok(properties)1231 })1232 }12331234 pub fn iterate_user_properties<Key, Value, R, Mapper>(1235 collection_id: CollectionId,1236 token_id: Option<TokenId>,1237 mapper: Mapper,1238 ) -> Result<impl Iterator<Item = R>, DispatchError>1239 where1240 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1241 Value: Decode + Default,1242 Mapper: Fn(Key, Value) -> R,1243 {1244 let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;12451246 let properties = match token_id {1247 Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1248 None => <PalletCommon<T>>::collection_properties(collection_id),1249 };12501251 let properties = properties.into_iter().filter_map(move |(key, value)| {1252 let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;12531254 let key: Key = key.to_vec().try_into().ok()?;1255 let value: Value = value.decode().ok()?;12561257 Some(mapper(key, value))1258 });12591260 Ok(properties)1261 }12621263 fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1264 map_unique_err_to_proxy! {1265 match err {1266 CommonError::NoPermission => NoPermission,1267 CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1268 CommonError::PublicMintingNotAllowed => NoPermission,1269 CommonError::TokenNotFound => NoAvailableNftId,1270 CommonError::ApprovedValueTooLow => NoPermission,1271 CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1272 StructureError::TokenNotFound => NoAvailableNftId,1273 StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1274 }1275 }1276 }1277}