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 }138139 #[pallet::call]140 impl<T: Config> Pallet<T> {141 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]142 #[transactional]143 pub fn create_collection(144 origin: OriginFor<T>,145 metadata: RmrkString,146 max: Option<u32>,147 symbol: RmrkCollectionSymbol,148 ) -> DispatchResult {149 let sender = ensure_signed(origin)?;150151 let limits = CollectionLimits {152 owner_can_transfer: Some(false),153 token_limit: max,154 ..Default::default()155 };156157 let data = CreateCollectionData {158 limits: Some(limits),159 token_prefix: symbol160 .into_inner()161 .try_into()162 .map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,163 permissions: Some(CollectionPermissions {164 nesting: Some(NestingRule::Owner),165 ..Default::default()166 }),167 ..Default::default()168 };169170 let unique_collection_id = Self::init_collection(171 T::CrossAccountId::from_sub(sender.clone()),172 data,173 [174 Self::rmrk_property(Metadata, &metadata)?,175 Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,176 ]177 .into_iter(),178 )?;179 let rmrk_collection_id = <CollectionIndex<T>>::get();180181 <UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);182 <RmrkInernalCollectionId<T>>::insert(unique_collection_id, rmrk_collection_id);183184 <CollectionIndex<T>>::mutate(|n| *n += 1);185186 Self::deposit_event(Event::CollectionCreated {187 issuer: sender,188 collection_id: rmrk_collection_id,189 });190191 Ok(())192 }193194 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]195 #[transactional]196 pub fn destroy_collection(197 origin: OriginFor<T>,198 collection_id: RmrkCollectionId,199 ) -> DispatchResult {200 let sender = ensure_signed(origin)?;201 let cross_sender = T::CrossAccountId::from_sub(sender.clone());202203 let collection = Self::get_typed_nft_collection(204 Self::unique_collection_id(collection_id)?,205 misc::CollectionType::Regular,206 )?;207208 <PalletNft<T>>::destroy_collection(collection, &cross_sender)209 .map_err(Self::map_unique_err_to_proxy)?;210211 Self::deposit_event(Event::CollectionDestroyed {212 issuer: sender,213 collection_id,214 });215216 Ok(())217 }218219 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]220 #[transactional]221 pub fn change_collection_issuer(222 origin: OriginFor<T>,223 collection_id: RmrkCollectionId,224 new_issuer: <T::Lookup as StaticLookup>::Source,225 ) -> DispatchResult {226 let sender = ensure_signed(origin)?;227228 let new_issuer = T::Lookup::lookup(new_issuer)?;229230 Self::change_collection_owner(231 Self::unique_collection_id(collection_id)?,232 misc::CollectionType::Regular,233 sender.clone(),234 new_issuer.clone(),235 )?;236237 Self::deposit_event(Event::IssuerChanged {238 old_issuer: sender,239 new_issuer,240 collection_id,241 });242243 Ok(())244 }245246 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]247 #[transactional]248 pub fn lock_collection(249 origin: OriginFor<T>,250 collection_id: RmrkCollectionId,251 ) -> DispatchResult {252 let sender = ensure_signed(origin)?;253 let cross_sender = T::CrossAccountId::from_sub(sender.clone());254255 let collection = Self::get_typed_nft_collection(256 Self::unique_collection_id(collection_id)?,257 misc::CollectionType::Regular,258 )?;259260 Self::check_collection_owner(&collection, &cross_sender)?;261262 let token_count = collection.total_supply();263264 let mut collection = collection.into_inner();265 collection.limits.token_limit = Some(token_count);266 collection.save()?;267268 Self::deposit_event(Event::CollectionLocked {269 issuer: sender,270 collection_id,271 });272273 Ok(())274 }275276 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]277 #[transactional]278 pub fn mint_nft(279 origin: OriginFor<T>,280 owner: T::AccountId,281 collection_id: RmrkCollectionId,282 recipient: Option<T::AccountId>,283 royalty_amount: Option<Permill>,284 metadata: RmrkString,285 transferable: bool,286 ) -> DispatchResult {287 let sender = ensure_signed(origin)?;288 let sender = T::CrossAccountId::from_sub(sender);289 let cross_owner = T::CrossAccountId::from_sub(owner.clone());290291 let royalty_info = royalty_amount.map(|amount| rmrk::RoyaltyInfo {292 recipient: recipient.unwrap_or_else(|| owner.clone()),293 amount,294 });295296 let collection = Self::get_typed_nft_collection(297 Self::unique_collection_id(collection_id)?,298 misc::CollectionType::Regular,299 )?;300301 let nft_id = Self::create_nft(302 &sender,303 &cross_owner,304 &collection,305 [306 Self::rmrk_property(TokenType, &NftType::Regular)?,307 Self::rmrk_property(Transferable, &transferable)?,308 Self::rmrk_property(PendingNftAccept, &false)?,309 Self::rmrk_property(RoyaltyInfo, &royalty_info)?,310 Self::rmrk_property(Metadata, &metadata)?,311 Self::rmrk_property(Equipped, &false)?,312 Self::rmrk_property(313 ResourceCollection,314 &Self::init_collection(315 sender.clone(),316 CreateCollectionData {317 ..Default::default()318 },319 [Self::rmrk_property(320 CollectionType,321 &misc::CollectionType::Resource,322 )?]323 .into_iter(),324 )?,325 )?, 326 Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,327 ]328 .into_iter(),329 )330 .map_err(|err| match err {331 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),332 err => Self::map_unique_err_to_proxy(err),333 })?;334335 Self::deposit_event(Event::NftMinted {336 owner,337 collection_id,338 nft_id: nft_id.0,339 });340341 Ok(())342 }343344 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]345 #[transactional]346 pub fn burn_nft(347 origin: OriginFor<T>,348 collection_id: RmrkCollectionId,349 nft_id: RmrkNftId,350 ) -> DispatchResult {351 let sender = ensure_signed(origin)?;352 let cross_sender = T::CrossAccountId::from_sub(sender.clone());353354 Self::destroy_nft(355 cross_sender,356 Self::unique_collection_id(collection_id)?,357 nft_id.into(),358 ).map_err(Self::map_unique_err_to_proxy)?;359360 Self::deposit_event(Event::NFTBurned {361 owner: sender,362 nft_id,363 });364365 Ok(())366 }367368 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]369 #[transactional]370 pub fn send(371 origin: OriginFor<T>,372 rmrk_collection_id: RmrkCollectionId,373 rmrk_nft_id: RmrkNftId,374 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,375 ) -> DispatchResult {376 let sender = ensure_signed(origin.clone())?;377 let cross_sender = T::CrossAccountId::from_sub(sender);378379 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;380 let nft_id = rmrk_nft_id.into();381382 let token_data = <TokenData<T>>::get((collection_id, nft_id))383 .ok_or(<Error<T>>::NoAvailableNftId)?;384385 let from = token_data.owner;386387 let collection = Self::get_typed_nft_collection(388 collection_id,389 misc::CollectionType::Regular,390 )?;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(collection_id, nft_id, RmrkProperty::PendingNftAccept)? {397 return Err(<Error<T>>::NoPermission.into());398 }399400 let target_owner;401402 match new_owner {403 RmrkAccountIdOrCollectionNftTuple::AccountId(account_id) => {404 target_owner = T::CrossAccountId::from_sub(account_id);405 },406 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(target_collection_id, target_nft_id) => {407 let target_collection_id = Self::unique_collection_id(target_collection_id)?;408409 let target_nft_budget = budget::Value::new(NESTING_BUDGET);410411 let target_nft_owner = <PalletStructure<T>>::get_checked_indirect_owner(412 target_collection_id,413 target_nft_id.into(),414 Some((collection_id, nft_id)),415 &target_nft_budget,416 ).map_err(Self::map_unique_err_to_proxy)?;417418 let is_approval_required = cross_sender != target_nft_owner;419420 if is_approval_required {421 target_owner = target_nft_owner;422423 <PalletNft<T>>::set_scoped_token_property(424 collection.id,425 nft_id,426 PropertyScope::Rmrk,427 Self::rmrk_property(PendingNftAccept, &is_approval_required)?,428 )?;429 } else {430 target_owner = T::CrossTokenAddressMapping::token_to_address(431 target_collection_id,432 target_nft_id.into(),433 );434 }435 }436 }437438 let src_nft_budget = budget::Value::new(NESTING_BUDGET);439440 <PalletNft<T>>::transfer_from(441 &collection,442 &cross_sender,443 &from,444 &target_owner,445 nft_id,446 &src_nft_budget447 ).map_err(Self::map_unique_err_to_proxy)?;448449 Ok(())450 }451452 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]453 #[transactional]454 pub fn accept_nft(455 origin: OriginFor<T>,456 rmrk_collection_id: RmrkCollectionId,457 rmrk_nft_id: RmrkNftId,458 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,459 ) -> DispatchResult {460 let sender = ensure_signed(origin.clone())?;461 let cross_sender = T::CrossAccountId::from_sub(sender);462463 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;464 let nft_id = rmrk_nft_id.into();465466 let collection = Self::get_typed_nft_collection(467 collection_id,468 misc::CollectionType::Regular,469 )?;470471 let new_owner = match new_owner {472 RmrkAccountIdOrCollectionNftTuple::AccountId(account_id) => {473 T::CrossAccountId::from_sub(account_id)474 },475 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(target_collection_id, target_nft_id) => {476 let target_collection_id = Self::unique_collection_id(target_collection_id)?;477478 T::CrossTokenAddressMapping::token_to_address(target_collection_id, TokenId(target_nft_id))479 }480 };481482 let budget = budget::Value::new(NESTING_BUDGET);483484 <PalletNft<T>>::transfer(485 &collection,486 &cross_sender,487 &new_owner,488 nft_id,489 &budget,490 ).map_err(|err| {491 if err == <CommonError<T>>::OnlyOwnerAllowedToNest.into() {492 <Error<T>>::CannotAcceptNonOwnedNft.into()493 } else {494 Self::map_unique_err_to_proxy(err)495 }496 })?;497498 <PalletNft<T>>::set_scoped_token_property(499 collection.id,500 nft_id,501 PropertyScope::Rmrk,502 Self::rmrk_property(PendingNftAccept, &false)?,503 )?;504505 Ok(())506 }507508 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]509 #[transactional]510 pub fn reject_nft(511 origin: OriginFor<T>,512 collection_id: RmrkCollectionId,513 nft_id: RmrkNftId,514 ) -> DispatchResult {515 let sender = ensure_signed(origin)?;516 let cross_sender = T::CrossAccountId::from_sub(sender);517518 let collection_id = Self::unique_collection_id(collection_id)?;519 let nft_id = nft_id.into();520521 Self::destroy_nft(522 cross_sender,523 collection_id,524 nft_id525 ).map_err(|err| {526 if err == <CommonError<T>>::NoPermission.into()527 || err == <CommonError<T>>::ApprovedValueTooLow.into() {528 <Error<T>>::CannotRejectNonOwnedNft.into()529 } else {530 Self::map_unique_err_to_proxy(err)531 }532 })?;533534 Ok(())535 }536537 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]538 #[transactional]539 pub fn set_property(540 origin: OriginFor<T>,541 #[pallet::compact] rmrk_collection_id: RmrkCollectionId,542 maybe_nft_id: Option<RmrkNftId>,543 key: RmrkKeyString,544 value: RmrkValueString,545 ) -> DispatchResult {546 let sender = ensure_signed(origin)?;547 let sender = T::CrossAccountId::from_sub(sender);548549 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;550 let budget = budget::Value::new(NESTING_BUDGET);551552 match maybe_nft_id {553 Some(nft_id) => {554 let token_id: TokenId = nft_id.into();555556 Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;557 Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;558559 <PalletNft<T>>::set_scoped_token_property(560 collection_id,561 token_id,562 PropertyScope::Rmrk,563 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,564 )?;565 }566 None => {567 let collection = Self::get_typed_nft_collection(568 collection_id,569 misc::CollectionType::Regular,570 )?;571572 Self::check_collection_owner(&collection, &sender)?;573574 <PalletCommon<T>>::set_scoped_collection_property(575 collection_id,576 PropertyScope::Rmrk,577 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,578 )?;579 }580 }581582 Self::deposit_event(Event::PropertySet {583 collection_id: rmrk_collection_id,584 maybe_nft_id,585 key,586 value,587 });588589 Ok(())590 }591592 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]593 #[transactional]594 pub fn add_basic_resource(595 origin: OriginFor<T>,596 collection_id: RmrkCollectionId,597 nft_id: RmrkNftId,598 resource: RmrkBasicResource,599 ) -> DispatchResult {600 let sender = ensure_signed(origin.clone())?;601602 let resource_id = Self::resource_add(603 sender,604 Self::unique_collection_id(collection_id)?,605 nft_id.into(),606 [607 Self::rmrk_property(TokenType, &NftType::Resource)?,608 Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,609 Self::rmrk_property(Src, &resource.src)?,610 Self::rmrk_property(Metadata, &resource.metadata)?,611 Self::rmrk_property(License, &resource.license)?,612 Self::rmrk_property(Thumb, &resource.thumb)?,613 ]614 .into_iter(),615 )?;616617 Self::deposit_event(Event::ResourceAdded {618 nft_id,619 resource_id,620 });621 Ok(())622 }623624 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]625 #[transactional]626 pub fn add_composable_resource(627 origin: OriginFor<T>,628 collection_id: RmrkCollectionId,629 nft_id: RmrkNftId,630 _resource_id: RmrkBoundedResource,631 resource: RmrkComposableResource,632 ) -> DispatchResult {633 let sender = ensure_signed(origin.clone())?;634635 let resource_id = Self::resource_add(636 sender,637 Self::unique_collection_id(collection_id)?,638 nft_id.into(),639 [640 Self::rmrk_property(TokenType, &NftType::Resource)?,641 Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,642 Self::rmrk_property(Parts, &resource.parts)?,643 Self::rmrk_property(Base, &resource.base)?,644 Self::rmrk_property(Src, &resource.src)?,645 Self::rmrk_property(Metadata, &resource.metadata)?,646 Self::rmrk_property(License, &resource.license)?,647 Self::rmrk_property(Thumb, &resource.thumb)?,648 ]649 .into_iter(),650 )?;651652 Self::deposit_event(Event::ResourceAdded {653 nft_id,654 resource_id,655 });656 Ok(())657 }658659 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]660 #[transactional]661 pub fn add_slot_resource(662 origin: OriginFor<T>,663 collection_id: RmrkCollectionId,664 nft_id: RmrkNftId,665 resource: RmrkSlotResource,666 ) -> DispatchResult {667 let sender = ensure_signed(origin.clone())?;668669 let resource_id = Self::resource_add(670 sender,671 Self::unique_collection_id(collection_id)?,672 nft_id.into(),673 [674 Self::rmrk_property(TokenType, &NftType::Resource)?,675 Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,676 Self::rmrk_property(Base, &resource.base)?,677 Self::rmrk_property(Src, &resource.src)?,678 Self::rmrk_property(Metadata, &resource.metadata)?,679 Self::rmrk_property(Slot, &resource.slot)?,680 Self::rmrk_property(License, &resource.license)?,681 Self::rmrk_property(Thumb, &resource.thumb)?,682 ]683 .into_iter(),684 )?;685686 Self::deposit_event(Event::ResourceAdded {687 nft_id,688 resource_id,689 });690 Ok(())691 }692693 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]694 #[transactional]695 pub fn remove_resource(696 origin: OriginFor<T>,697 collection_id: RmrkCollectionId,698 nft_id: RmrkNftId,699 resource_id: RmrkResourceId,700 ) -> DispatchResult {701 let sender = ensure_signed(origin.clone())?;702703 Self::resource_remove(704 sender,705 Self::unique_collection_id(collection_id)?,706 nft_id.into(),707 resource_id.into(),708 )?;709710 Self::deposit_event(Event::ResourceRemoval {711 nft_id,712 resource_id,713 });714 Ok(())715 }716 }717}718719impl<T: Config> Pallet<T> {720 pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {721 let key = rmrk_key.to_key::<T>()?;722723 let scoped_key = PropertyScope::Rmrk724 .apply(key)725 .map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;726727 Ok(scoped_key)728 }729730 731 pub fn rmrk_property<E: Encode>(732 rmrk_key: RmrkProperty,733 value: &E,734 ) -> Result<Property, DispatchError> {735 let key = rmrk_key.to_key::<T>()?;736737 let value = value738 .encode()739 .try_into()740 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;741742 let property = Property { key, value };743744 Ok(property)745 }746747 pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {748 vec.decode()749 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())750 }751752 pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>753 where754 BoundedVec<u8, S>: TryFrom<Vec<u8>>,755 {756 vec.rebind()757 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())758 }759760 fn init_collection(761 sender: T::CrossAccountId,762 data: CreateCollectionData<T::AccountId>,763 properties: impl Iterator<Item = Property>,764 ) -> Result<CollectionId, DispatchError> {765 let collection_id = <PalletNft<T>>::init_collection(sender, data);766767 if let Err(DispatchError::Arithmetic(_)) = &collection_id {768 return Err(<Error<T>>::NoAvailableCollectionId.into());769 }770771 <PalletCommon<T>>::set_scoped_collection_properties(772 collection_id?,773 PropertyScope::Rmrk,774 properties,775 )?;776777 collection_id778 }779780 pub fn create_nft(781 sender: &T::CrossAccountId,782 owner: &T::CrossAccountId,783 collection: &NonfungibleHandle<T>,784 properties: impl Iterator<Item = Property>,785 ) -> Result<TokenId, DispatchError> {786 let data = CreateNftExData {787 properties: BoundedVec::default(),788 owner: owner.clone(),789 };790791 let budget = budget::Value::new(NESTING_BUDGET);792793 <PalletNft<T>>::create_item(collection, sender, data, &budget)?;794795 let nft_id = <PalletNft<T>>::current_token_id(collection.id);796797 <PalletNft<T>>::set_scoped_token_properties(798 collection.id,799 nft_id,800 PropertyScope::Rmrk,801 properties,802 )?;803804 Ok(nft_id)805 }806807 fn destroy_nft(808 sender: T::CrossAccountId,809 collection_id: CollectionId,810 token_id: TokenId,811 ) -> DispatchResult {812 let collection = Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;813814 let token_data = <TokenData<T>>::get((collection_id, token_id))815 .ok_or(<Error<T>>::NoAvailableNftId)?;816817 let from = token_data.owner;818819 let budget = budget::Value::new(NESTING_BUDGET);820821 <PalletNft<T>>::burn_from(&collection, &sender, &from, token_id, &budget)822 }823824 fn resource_add(825 sender: T::AccountId,826 collection_id: CollectionId,827 token_id: TokenId,828 resource_properties: impl Iterator<Item = Property>,829 ) -> Result<RmrkResourceId, DispatchError> {830 let collection =831 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;832 ensure!(collection.owner == sender, Error::<T>::NoPermission);833834 835 836837 let sender = T::CrossAccountId::from_sub(sender);838 let budget = budget::Value::new(NESTING_BUDGET);839 let pending = Self::ensure_nft_owner(collection_id, token_id, &sender, &budget).is_err();840841 let resource_collection_id: CollectionId =842 Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;843 let resource_collection =844 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;845846 847848 let resource_id = Self::create_nft(849 &sender, 850 &sender,851 &resource_collection,852 resource_properties.chain(853 [854 Self::rmrk_property(PendingResourceAccept, &pending)?,855 Self::rmrk_property(PendingResourceRemoval, &false)?,856 ]857 .into_iter(),858 ),859 )860 .map_err(|err| match err {861 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),862 err => Self::map_unique_err_to_proxy(err),863 })?;864865 Ok(resource_id.0)866 }867868 fn resource_remove(869 sender: T::AccountId,870 collection_id: CollectionId,871 nft_id: TokenId,872 resource_id: TokenId,873 ) -> DispatchResult {874 let collection =875 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;876 ensure!(collection.owner == sender, Error::<T>::NoPermission);877878 let resource_collection_id: CollectionId =879 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;880 let resource_collection =881 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;882 ensure!(883 <PalletNft<T>>::token_exists(&resource_collection, resource_id),884 Error::<T>::ResourceDoesntExist885 );886887 let budget = up_data_structs::budget::Value::new(10);888 let topmost_owner =889 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;890891 let sender = T::CrossAccountId::from_sub(sender);892 if topmost_owner == sender {893 <PalletNft<T>>::burn(&resource_collection, &sender, resource_id)894 .map_err(Self::map_unique_err_to_proxy)?;895 } else {896 <PalletNft<T>>::set_scoped_token_property(897 resource_collection_id,898 resource_id,899 PropertyScope::Rmrk,900 Self::rmrk_property(PendingResourceRemoval, &true)?,901 )?;902 }903904 Ok(())905 }906907 fn change_collection_owner(908 collection_id: CollectionId,909 collection_type: misc::CollectionType,910 sender: T::AccountId,911 new_owner: T::AccountId,912 ) -> DispatchResult {913 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;914 Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;915916 let mut collection = collection.into_inner();917918 collection.owner = new_owner;919 collection.save()920 }921922 fn check_collection_owner(923 collection: &NonfungibleHandle<T>,924 account: &T::CrossAccountId,925 ) -> DispatchResult {926 collection927 .check_is_owner(account)928 .map_err(Self::map_unique_err_to_proxy)929 }930931 pub fn last_collection_idx() -> RmrkCollectionId {932 <CollectionIndex<T>>::get()933 }934935 pub fn unique_collection_id(936 rmrk_collection_id: RmrkCollectionId,937 ) -> Result<CollectionId, DispatchError> {938 <UniqueCollectionId<T>>::try_get(rmrk_collection_id)939 .map_err(|_| <Error<T>>::CollectionUnknown.into())940 }941942 pub fn rmrk_collection_id(943 unique_collection_id: CollectionId944 ) -> Result<RmrkCollectionId, DispatchError> {945 <RmrkInernalCollectionId<T>>::try_get(unique_collection_id)946 .map_err(|_| <Error<T>>::CollectionUnknown.into())947 }948949 pub fn get_nft_collection(950 collection_id: CollectionId,951 ) -> Result<NonfungibleHandle<T>, DispatchError> {952 let collection = <CollectionHandle<T>>::try_get(collection_id)953 .map_err(|_| <Error<T>>::CollectionUnknown)?;954955 match collection.mode {956 CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),957 _ => Err(<Error<T>>::CollectionUnknown.into()),958 }959 }960961 pub fn collection_exists(collection_id: CollectionId) -> bool {962 <CollectionHandle<T>>::try_get(collection_id).is_ok()963 }964965 pub fn get_collection_property(966 collection_id: CollectionId,967 key: RmrkProperty,968 ) -> Result<PropertyValue, DispatchError> {969 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)970 .get(&Self::rmrk_property_key(key)?)971 .ok_or(<Error<T>>::CollectionUnknown)?972 .clone();973974 Ok(collection_property)975 }976977 pub fn get_collection_property_decoded<V: Decode>(978 collection_id: CollectionId,979 key: RmrkProperty,980 ) -> Result<V, DispatchError> {981 Self::decode_property(Self::get_collection_property(collection_id, key)?)982 }983984 pub fn get_collection_type(985 collection_id: CollectionId,986 ) -> Result<misc::CollectionType, DispatchError> {987 Self::get_collection_property_decoded(collection_id, CollectionType)988 .map_err(|_| <Error<T>>::CorruptedCollectionType.into())989 }990991 pub fn ensure_collection_type(992 collection_id: CollectionId,993 collection_type: misc::CollectionType,994 ) -> DispatchResult {995 let actual_type = Self::get_collection_type(collection_id)?;996 ensure!(997 actual_type == collection_type,998 <CommonError<T>>::NoPermission999 );10001001 Ok(())1002 }10031004 pub fn get_typed_nft_collection(1005 collection_id: CollectionId,1006 collection_type: misc::CollectionType,1007 ) -> Result<NonfungibleHandle<T>, DispatchError> {1008 Self::ensure_collection_type(collection_id, collection_type)?;10091010 Self::get_nft_collection(collection_id)1011 }10121013 pub fn get_typed_nft_collection_mapped(1014 rmrk_collection_id: RmrkCollectionId,1015 collection_type: misc::CollectionType,1016 ) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1017 let unique_collection_id = Self::unique_collection_id(rmrk_collection_id)?;10181019 let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;10201021 Ok((collection, unique_collection_id))1022 }10231024 pub fn get_nft_property(1025 collection_id: CollectionId,1026 nft_id: TokenId,1027 key: RmrkProperty,1028 ) -> Result<PropertyValue, DispatchError> {1029 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1030 .get(&Self::rmrk_property_key(key)?)1031 .ok_or(<Error<T>>::NoAvailableNftId)? 1032 .clone();10331034 Ok(nft_property)1035 }10361037 pub fn get_nft_property_decoded<V: Decode>(1038 collection_id: CollectionId,1039 nft_id: TokenId,1040 key: RmrkProperty,1041 ) -> Result<V, DispatchError> {1042 Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)1043 }10441045 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1046 <TokenData<T>>::contains_key((collection_id, nft_id))1047 }10481049 pub fn get_nft_type(1050 collection_id: CollectionId,1051 token_id: TokenId,1052 ) -> Result<NftType, DispatchError> {1053 Self::get_nft_property_decoded(collection_id, token_id, TokenType)1054 .map_err(|_| <Error<T>>::NftTypeEncodeError.into())1055 }10561057 pub fn ensure_nft_type(1058 collection_id: CollectionId,1059 token_id: TokenId,1060 nft_type: NftType,1061 ) -> DispatchResult {1062 let actual_type = Self::get_nft_type(collection_id, token_id)?;1063 ensure!(actual_type == nft_type, <Error<T>>::NoPermission);10641065 Ok(())1066 }10671068 pub fn ensure_nft_owner(1069 collection_id: CollectionId,1070 token_id: TokenId,1071 possible_owner: &T::CrossAccountId,1072 nesting_budget: &dyn budget::Budget1073 ) -> DispatchResult {1074 let is_owned = <PalletStructure<T>>::check_indirectly_owned(1075 possible_owner.clone(),1076 collection_id,1077 token_id,1078 None,1079 nesting_budget,1080 )?;10811082 ensure!(1083 is_owned,1084 <Error<T>>::NoPermission1085 );10861087 Ok(())1088 }10891090 pub fn filter_user_properties<Key, Value, R, Mapper>(1091 collection_id: CollectionId,1092 token_id: Option<TokenId>,1093 filter_keys: Option<Vec<RmrkPropertyKey>>,1094 mapper: Mapper,1095 ) -> Result<Vec<R>, DispatchError>1096 where1097 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1098 Value: Decode + Default,1099 Mapper: Fn(Key, Value) -> R,1100 {1101 filter_keys1102 .map(|keys| {1103 let properties = keys1104 .into_iter()1105 .filter_map(|key| {1106 let key: Key = key.try_into().ok()?;11071108 let value = match token_id {1109 Some(token_id) => Self::get_nft_property_decoded(1110 collection_id,1111 token_id,1112 UserProperty(key.as_ref()),1113 ),1114 None => Self::get_collection_property_decoded(1115 collection_id,1116 UserProperty(key.as_ref()),1117 ),1118 }1119 .ok()?;11201121 Some(mapper(key, value))1122 })1123 .collect();11241125 Ok(properties)1126 })1127 .unwrap_or_else(|| {1128 let properties =1129 Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();11301131 Ok(properties)1132 })1133 }11341135 pub fn iterate_user_properties<Key, Value, R, Mapper>(1136 collection_id: CollectionId,1137 token_id: Option<TokenId>,1138 mapper: Mapper,1139 ) -> Result<impl Iterator<Item = R>, DispatchError>1140 where1141 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1142 Value: Decode + Default,1143 Mapper: Fn(Key, Value) -> R,1144 {1145 let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;11461147 let properties = match token_id {1148 Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1149 None => <PalletCommon<T>>::collection_properties(collection_id),1150 };11511152 let properties = properties.into_iter().filter_map(move |(key, value)| {1153 let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;11541155 let key: Key = key.to_vec().try_into().ok()?;1156 let value: Value = value.decode().ok()?;11571158 Some(mapper(key, value))1159 });11601161 Ok(properties)1162 }11631164 fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1165 map_unique_err_to_proxy! {1166 match err {1167 CommonError::NoPermission => NoPermission,1168 CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1169 CommonError::PublicMintingNotAllowed => NoPermission,1170 CommonError::TokenNotFound => NoAvailableNftId,1171 CommonError::ApprovedValueTooLow => NoPermission,1172 CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1173 StructureError::TokenNotFound => NoAvailableNftId,1174 StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1175 }1176 }1177 }1178}