difftreelog
feat(rmrk) add accept_resource
in: master
1 file changed
pallets/proxy-rmrk-core/src/lib.rsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![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 /* Unique-specific events */120 CorruptedCollectionType,121 NftTypeEncodeError,122 RmrkPropertyKeyIsTooLong,123 RmrkPropertyValueIsTooLong,124125 /* RMRK compatible events */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 )?, // todo possibly add limits to the collection if rmrk warrants them326 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 )359 .map_err(Self::map_unique_err_to_proxy)?;360361 Self::deposit_event(Event::NFTBurned {362 owner: sender,363 nft_id,364 });365366 Ok(())367 }368369 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]370 #[transactional]371 pub fn send(372 origin: OriginFor<T>,373 rmrk_collection_id: RmrkCollectionId,374 rmrk_nft_id: RmrkNftId,375 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,376 ) -> DispatchResult {377 let sender = ensure_signed(origin.clone())?;378 let cross_sender = T::CrossAccountId::from_sub(sender);379380 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;381 let nft_id = rmrk_nft_id.into();382383 let token_data =384 <TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;385386 let from = token_data.owner;387388 let collection =389 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;390391 if !Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)? {392 return Err(<Error<T>>::NonTransferable.into());393 }394395 if Self::get_nft_property_decoded(396 collection_id,397 nft_id,398 RmrkProperty::PendingNftAccept,399 )? {400 return Err(<Error<T>>::NoPermission.into());401 }402403 let target_owner;404405 match new_owner {406 RmrkAccountIdOrCollectionNftTuple::AccountId(account_id) => {407 target_owner = T::CrossAccountId::from_sub(account_id);408 }409 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(410 target_collection_id,411 target_nft_id,412 ) => {413 let target_collection_id = Self::unique_collection_id(target_collection_id)?;414415 let target_nft_budget = budget::Value::new(NESTING_BUDGET);416417 let target_nft_owner = <PalletStructure<T>>::get_checked_indirect_owner(418 target_collection_id,419 target_nft_id.into(),420 Some((collection_id, nft_id)),421 &target_nft_budget,422 )423 .map_err(Self::map_unique_err_to_proxy)?;424425 let is_approval_required = cross_sender != target_nft_owner;426427 if is_approval_required {428 target_owner = target_nft_owner;429430 <PalletNft<T>>::set_scoped_token_property(431 collection.id,432 nft_id,433 PropertyScope::Rmrk,434 Self::rmrk_property(PendingNftAccept, &is_approval_required)?,435 )?;436 } else {437 target_owner = T::CrossTokenAddressMapping::token_to_address(438 target_collection_id,439 target_nft_id.into(),440 );441 }442 }443 }444445 let src_nft_budget = budget::Value::new(NESTING_BUDGET);446447 <PalletNft<T>>::transfer_from(448 &collection,449 &cross_sender,450 &from,451 &target_owner,452 nft_id,453 &src_nft_budget,454 )455 .map_err(Self::map_unique_err_to_proxy)?;456457 Ok(())458 }459460 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]461 #[transactional]462 pub fn accept_nft(463 origin: OriginFor<T>,464 rmrk_collection_id: RmrkCollectionId,465 rmrk_nft_id: RmrkNftId,466 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,467 ) -> DispatchResult {468 let sender = ensure_signed(origin.clone())?;469 let cross_sender = T::CrossAccountId::from_sub(sender);470471 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;472 let nft_id = rmrk_nft_id.into();473474 let collection =475 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;476477 let new_owner = match new_owner {478 RmrkAccountIdOrCollectionNftTuple::AccountId(account_id) => {479 T::CrossAccountId::from_sub(account_id)480 }481 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(482 target_collection_id,483 target_nft_id,484 ) => {485 let target_collection_id = Self::unique_collection_id(target_collection_id)?;486487 T::CrossTokenAddressMapping::token_to_address(488 target_collection_id,489 TokenId(target_nft_id),490 )491 }492 };493494 let budget = budget::Value::new(NESTING_BUDGET);495496 <PalletNft<T>>::transfer(&collection, &cross_sender, &new_owner, nft_id, &budget)497 .map_err(|err| {498 if err == <CommonError<T>>::OnlyOwnerAllowedToNest.into() {499 <Error<T>>::CannotAcceptNonOwnedNft.into()500 } else {501 Self::map_unique_err_to_proxy(err)502 }503 })?;504505 <PalletNft<T>>::set_scoped_token_property(506 collection.id,507 nft_id,508 PropertyScope::Rmrk,509 Self::rmrk_property(PendingNftAccept, &false)?,510 )?;511512 Ok(())513 }514515 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]516 #[transactional]517 pub fn reject_nft(518 origin: OriginFor<T>,519 collection_id: RmrkCollectionId,520 nft_id: RmrkNftId,521 ) -> DispatchResult {522 let sender = ensure_signed(origin)?;523 let cross_sender = T::CrossAccountId::from_sub(sender);524525 let collection_id = Self::unique_collection_id(collection_id)?;526 let nft_id = nft_id.into();527528 Self::destroy_nft(cross_sender, collection_id, nft_id).map_err(|err| {529 if err == <CommonError<T>>::NoPermission.into()530 || err == <CommonError<T>>::ApprovedValueTooLow.into()531 {532 <Error<T>>::CannotRejectNonOwnedNft.into()533 } else {534 Self::map_unique_err_to_proxy(err)535 }536 })?;537538 Ok(())539 }540541 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]542 #[transactional]543 pub fn set_property(544 origin: OriginFor<T>,545 #[pallet::compact] rmrk_collection_id: RmrkCollectionId,546 maybe_nft_id: Option<RmrkNftId>,547 key: RmrkKeyString,548 value: RmrkValueString,549 ) -> DispatchResult {550 let sender = ensure_signed(origin)?;551 let sender = T::CrossAccountId::from_sub(sender);552553 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;554 let budget = budget::Value::new(NESTING_BUDGET);555556 match maybe_nft_id {557 Some(nft_id) => {558 let token_id: TokenId = nft_id.into();559560 Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;561 Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;562563 <PalletNft<T>>::set_scoped_token_property(564 collection_id,565 token_id,566 PropertyScope::Rmrk,567 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,568 )?;569 }570 None => {571 let collection = Self::get_typed_nft_collection(572 collection_id,573 misc::CollectionType::Regular,574 )?;575576 Self::check_collection_owner(&collection, &sender)?;577578 <PalletCommon<T>>::set_scoped_collection_property(579 collection_id,580 PropertyScope::Rmrk,581 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,582 )?;583 }584 }585586 Self::deposit_event(Event::PropertySet {587 collection_id: rmrk_collection_id,588 maybe_nft_id,589 key,590 value,591 });592593 Ok(())594 }595596 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]597 #[transactional]598 pub fn add_basic_resource(599 origin: OriginFor<T>,600 collection_id: RmrkCollectionId,601 nft_id: RmrkNftId,602 resource: RmrkBasicResource,603 ) -> DispatchResult {604 let sender = ensure_signed(origin.clone())?;605606 let resource_id = Self::resource_add(607 sender,608 Self::unique_collection_id(collection_id)?,609 nft_id.into(),610 [611 Self::rmrk_property(TokenType, &NftType::Resource)?,612 Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,613 Self::rmrk_property(Src, &resource.src)?,614 Self::rmrk_property(Metadata, &resource.metadata)?,615 Self::rmrk_property(License, &resource.license)?,616 Self::rmrk_property(Thumb, &resource.thumb)?,617 ]618 .into_iter(),619 )?;620621 Self::deposit_event(Event::ResourceAdded {622 nft_id,623 resource_id,624 });625 Ok(())626 }627628 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]629 #[transactional]630 pub fn add_composable_resource(631 origin: OriginFor<T>,632 collection_id: RmrkCollectionId,633 nft_id: RmrkNftId,634 _resource_id: RmrkBoundedResource,635 resource: RmrkComposableResource,636 ) -> DispatchResult {637 let sender = ensure_signed(origin.clone())?;638639 let resource_id = Self::resource_add(640 sender,641 Self::unique_collection_id(collection_id)?,642 nft_id.into(),643 [644 Self::rmrk_property(TokenType, &NftType::Resource)?,645 Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,646 Self::rmrk_property(Parts, &resource.parts)?,647 Self::rmrk_property(Base, &resource.base)?,648 Self::rmrk_property(Src, &resource.src)?,649 Self::rmrk_property(Metadata, &resource.metadata)?,650 Self::rmrk_property(License, &resource.license)?,651 Self::rmrk_property(Thumb, &resource.thumb)?,652 ]653 .into_iter(),654 )?;655656 Self::deposit_event(Event::ResourceAdded {657 nft_id,658 resource_id,659 });660 Ok(())661 }662663 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]664 #[transactional]665 pub fn add_slot_resource(666 origin: OriginFor<T>,667 collection_id: RmrkCollectionId,668 nft_id: RmrkNftId,669 resource: RmrkSlotResource,670 ) -> DispatchResult {671 let sender = ensure_signed(origin.clone())?;672673 let resource_id = Self::resource_add(674 sender,675 Self::unique_collection_id(collection_id)?,676 nft_id.into(),677 [678 Self::rmrk_property(TokenType, &NftType::Resource)?,679 Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,680 Self::rmrk_property(Base, &resource.base)?,681 Self::rmrk_property(Src, &resource.src)?,682 Self::rmrk_property(Metadata, &resource.metadata)?,683 Self::rmrk_property(Slot, &resource.slot)?,684 Self::rmrk_property(License, &resource.license)?,685 Self::rmrk_property(Thumb, &resource.thumb)?,686 ]687 .into_iter(),688 )?;689690 Self::deposit_event(Event::ResourceAdded {691 nft_id,692 resource_id,693 });694 Ok(())695 }696697 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]698 #[transactional]699 pub fn remove_resource(700 origin: OriginFor<T>,701 collection_id: RmrkCollectionId,702 nft_id: RmrkNftId,703 resource_id: RmrkResourceId,704 ) -> DispatchResult {705 let sender = ensure_signed(origin.clone())?;706707 Self::resource_remove(708 sender,709 Self::unique_collection_id(collection_id)?,710 nft_id.into(),711 resource_id.into(),712 )?;713714 Self::deposit_event(Event::ResourceRemoval {715 nft_id,716 resource_id,717 });718 Ok(())719 }720 }721}722723impl<T: Config> Pallet<T> {724 pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {725 let key = rmrk_key.to_key::<T>()?;726727 let scoped_key = PropertyScope::Rmrk728 .apply(key)729 .map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;730731 Ok(scoped_key)732 }733734 // todo think about renaming these735 pub fn rmrk_property<E: Encode>(736 rmrk_key: RmrkProperty,737 value: &E,738 ) -> Result<Property, DispatchError> {739 let key = rmrk_key.to_key::<T>()?;740741 let value = value742 .encode()743 .try_into()744 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;745746 let property = Property { key, value };747748 Ok(property)749 }750751 pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {752 vec.decode()753 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())754 }755756 pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>757 where758 BoundedVec<u8, S>: TryFrom<Vec<u8>>,759 {760 vec.rebind()761 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())762 }763764 fn init_collection(765 sender: T::CrossAccountId,766 data: CreateCollectionData<T::AccountId>,767 properties: impl Iterator<Item = Property>,768 ) -> Result<CollectionId, DispatchError> {769 let collection_id = <PalletNft<T>>::init_collection(sender, data);770771 if let Err(DispatchError::Arithmetic(_)) = &collection_id {772 return Err(<Error<T>>::NoAvailableCollectionId.into());773 }774775 <PalletCommon<T>>::set_scoped_collection_properties(776 collection_id?,777 PropertyScope::Rmrk,778 properties,779 )?;780781 collection_id782 }783784 pub fn create_nft(785 sender: &T::CrossAccountId,786 owner: &T::CrossAccountId,787 collection: &NonfungibleHandle<T>,788 properties: impl Iterator<Item = Property>,789 ) -> Result<TokenId, DispatchError> {790 let data = CreateNftExData {791 properties: BoundedVec::default(),792 owner: owner.clone(),793 };794795 let budget = budget::Value::new(NESTING_BUDGET);796797 <PalletNft<T>>::create_item(collection, sender, data, &budget)?;798799 let nft_id = <PalletNft<T>>::current_token_id(collection.id);800801 <PalletNft<T>>::set_scoped_token_properties(802 collection.id,803 nft_id,804 PropertyScope::Rmrk,805 properties,806 )?;807808 Ok(nft_id)809 }810811 fn destroy_nft(812 sender: T::CrossAccountId,813 collection_id: CollectionId,814 token_id: TokenId,815 ) -> DispatchResult {816 let collection =817 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;818819 let token_data =820 <TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;821822 let from = token_data.owner;823824 let budget = budget::Value::new(NESTING_BUDGET);825826 <PalletNft<T>>::burn_from(&collection, &sender, &from, token_id, &budget)827 }828829 fn resource_add(830 sender: T::AccountId,831 collection_id: CollectionId,832 token_id: TokenId,833 resource_properties: impl Iterator<Item = Property>,834 ) -> Result<RmrkResourceId, DispatchError> {835 let collection =836 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;837 ensure!(collection.owner == sender, Error::<T>::NoPermission);838839 // Check NFT lock status // todo depends on market, maybe later840 //ensure!(!Pallet::<T>::is_locked(collection_id, nft_id), pallet_uniques::Error::<T>::Locked);841842 let sender = T::CrossAccountId::from_sub(sender);843 let budget = budget::Value::new(NESTING_BUDGET);844 let pending = Self::ensure_nft_owner(collection_id, token_id, &sender, &budget).is_err();845846 let resource_collection_id: CollectionId =847 Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;848 let resource_collection =849 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;850851 // todo probably add extra connections to bases, slots, etc., when RMRK starts to use them852853 let resource_id = Self::create_nft(854 &sender, // todo owner of the nft?855 &sender,856 &resource_collection,857 resource_properties.chain(858 [859 Self::rmrk_property(PendingResourceAccept, &pending)?,860 Self::rmrk_property(PendingResourceRemoval, &false)?,861 ]862 .into_iter(),863 ),864 )865 .map_err(|err| match err {866 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),867 err => Self::map_unique_err_to_proxy(err),868 })?;869870 Ok(resource_id.0)871 }872873 fn resource_remove(874 sender: T::AccountId,875 collection_id: CollectionId,876 nft_id: TokenId,877 resource_id: TokenId,878 ) -> DispatchResult {879 let collection =880 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;881 ensure!(collection.owner == sender, Error::<T>::NoPermission);882883 let resource_collection_id: CollectionId =884 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;885 let resource_collection =886 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;887 ensure!(888 <PalletNft<T>>::token_exists(&resource_collection, resource_id),889 Error::<T>::ResourceDoesntExist890 );891892 let budget = up_data_structs::budget::Value::new(10);893 let topmost_owner =894 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;895896 let sender = T::CrossAccountId::from_sub(sender);897 if topmost_owner == sender {898 <PalletNft<T>>::burn(&resource_collection, &sender, resource_id)899 .map_err(Self::map_unique_err_to_proxy)?;900 } else {901 <PalletNft<T>>::set_scoped_token_property(902 resource_collection_id,903 resource_id,904 PropertyScope::Rmrk,905 Self::rmrk_property(PendingResourceRemoval, &true)?,906 )?;907 }908909 Ok(())910 }911912 fn change_collection_owner(913 collection_id: CollectionId,914 collection_type: misc::CollectionType,915 sender: T::AccountId,916 new_owner: T::AccountId,917 ) -> DispatchResult {918 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;919 Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;920921 let mut collection = collection.into_inner();922923 collection.owner = new_owner;924 collection.save()925 }926927 fn check_collection_owner(928 collection: &NonfungibleHandle<T>,929 account: &T::CrossAccountId,930 ) -> DispatchResult {931 collection932 .check_is_owner(account)933 .map_err(Self::map_unique_err_to_proxy)934 }935936 pub fn last_collection_idx() -> RmrkCollectionId {937 <CollectionIndex<T>>::get()938 }939940 pub fn unique_collection_id(941 rmrk_collection_id: RmrkCollectionId,942 ) -> Result<CollectionId, DispatchError> {943 <UniqueCollectionId<T>>::try_get(rmrk_collection_id)944 .map_err(|_| <Error<T>>::CollectionUnknown.into())945 }946947 pub fn rmrk_collection_id(948 unique_collection_id: CollectionId,949 ) -> Result<RmrkCollectionId, DispatchError> {950 <RmrkInernalCollectionId<T>>::try_get(unique_collection_id)951 .map_err(|_| <Error<T>>::CollectionUnknown.into())952 }953954 pub fn get_nft_collection(955 collection_id: CollectionId,956 ) -> Result<NonfungibleHandle<T>, DispatchError> {957 let collection = <CollectionHandle<T>>::try_get(collection_id)958 .map_err(|_| <Error<T>>::CollectionUnknown)?;959960 match collection.mode {961 CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),962 _ => Err(<Error<T>>::CollectionUnknown.into()),963 }964 }965966 pub fn collection_exists(collection_id: CollectionId) -> bool {967 <CollectionHandle<T>>::try_get(collection_id).is_ok()968 }969970 pub fn get_collection_property(971 collection_id: CollectionId,972 key: RmrkProperty,973 ) -> Result<PropertyValue, DispatchError> {974 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)975 .get(&Self::rmrk_property_key(key)?)976 .ok_or(<Error<T>>::CollectionUnknown)?977 .clone();978979 Ok(collection_property)980 }981982 pub fn get_collection_property_decoded<V: Decode>(983 collection_id: CollectionId,984 key: RmrkProperty,985 ) -> Result<V, DispatchError> {986 Self::decode_property(Self::get_collection_property(collection_id, key)?)987 }988989 pub fn get_collection_type(990 collection_id: CollectionId,991 ) -> Result<misc::CollectionType, DispatchError> {992 Self::get_collection_property_decoded(collection_id, CollectionType)993 .map_err(|_| <Error<T>>::CorruptedCollectionType.into())994 }995996 pub fn ensure_collection_type(997 collection_id: CollectionId,998 collection_type: misc::CollectionType,999 ) -> DispatchResult {1000 let actual_type = Self::get_collection_type(collection_id)?;1001 ensure!(1002 actual_type == collection_type,1003 <CommonError<T>>::NoPermission1004 );10051006 Ok(())1007 }10081009 pub fn get_typed_nft_collection(1010 collection_id: CollectionId,1011 collection_type: misc::CollectionType,1012 ) -> Result<NonfungibleHandle<T>, DispatchError> {1013 Self::ensure_collection_type(collection_id, collection_type)?;10141015 Self::get_nft_collection(collection_id)1016 }10171018 pub fn get_typed_nft_collection_mapped(1019 rmrk_collection_id: RmrkCollectionId,1020 collection_type: misc::CollectionType,1021 ) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1022 let unique_collection_id = Self::unique_collection_id(rmrk_collection_id)?;10231024 let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;10251026 Ok((collection, unique_collection_id))1027 }10281029 pub fn get_nft_property(1030 collection_id: CollectionId,1031 nft_id: TokenId,1032 key: RmrkProperty,1033 ) -> Result<PropertyValue, DispatchError> {1034 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1035 .get(&Self::rmrk_property_key(key)?)1036 .ok_or(<Error<T>>::NoAvailableNftId)? // todo replace with better error?1037 .clone();10381039 Ok(nft_property)1040 }10411042 pub fn get_nft_property_decoded<V: Decode>(1043 collection_id: CollectionId,1044 nft_id: TokenId,1045 key: RmrkProperty,1046 ) -> Result<V, DispatchError> {1047 Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)1048 }10491050 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1051 <TokenData<T>>::contains_key((collection_id, nft_id))1052 }10531054 pub fn get_nft_type(1055 collection_id: CollectionId,1056 token_id: TokenId,1057 ) -> Result<NftType, DispatchError> {1058 Self::get_nft_property_decoded(collection_id, token_id, TokenType)1059 .map_err(|_| <Error<T>>::NftTypeEncodeError.into())1060 }10611062 pub fn ensure_nft_type(1063 collection_id: CollectionId,1064 token_id: TokenId,1065 nft_type: NftType,1066 ) -> DispatchResult {1067 let actual_type = Self::get_nft_type(collection_id, token_id)?;1068 ensure!(actual_type == nft_type, <Error<T>>::NoPermission);10691070 Ok(())1071 }10721073 pub fn ensure_nft_owner(1074 collection_id: CollectionId,1075 token_id: TokenId,1076 possible_owner: &T::CrossAccountId,1077 nesting_budget: &dyn budget::Budget,1078 ) -> DispatchResult {1079 let is_owned = <PalletStructure<T>>::check_indirectly_owned(1080 possible_owner.clone(),1081 collection_id,1082 token_id,1083 None,1084 nesting_budget,1085 )?;10861087 ensure!(is_owned, <Error<T>>::NoPermission);10881089 Ok(())1090 }10911092 pub fn filter_user_properties<Key, Value, R, Mapper>(1093 collection_id: CollectionId,1094 token_id: Option<TokenId>,1095 filter_keys: Option<Vec<RmrkPropertyKey>>,1096 mapper: Mapper,1097 ) -> Result<Vec<R>, DispatchError>1098 where1099 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1100 Value: Decode + Default,1101 Mapper: Fn(Key, Value) -> R,1102 {1103 filter_keys1104 .map(|keys| {1105 let properties = keys1106 .into_iter()1107 .filter_map(|key| {1108 let key: Key = key.try_into().ok()?;11091110 let value = match token_id {1111 Some(token_id) => Self::get_nft_property_decoded(1112 collection_id,1113 token_id,1114 UserProperty(key.as_ref()),1115 ),1116 None => Self::get_collection_property_decoded(1117 collection_id,1118 UserProperty(key.as_ref()),1119 ),1120 }1121 .ok()?;11221123 Some(mapper(key, value))1124 })1125 .collect();11261127 Ok(properties)1128 })1129 .unwrap_or_else(|| {1130 let properties =1131 Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();11321133 Ok(properties)1134 })1135 }11361137 pub fn iterate_user_properties<Key, Value, R, Mapper>(1138 collection_id: CollectionId,1139 token_id: Option<TokenId>,1140 mapper: Mapper,1141 ) -> Result<impl Iterator<Item = R>, DispatchError>1142 where1143 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1144 Value: Decode + Default,1145 Mapper: Fn(Key, Value) -> R,1146 {1147 let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;11481149 let properties = match token_id {1150 Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1151 None => <PalletCommon<T>>::collection_properties(collection_id),1152 };11531154 let properties = properties.into_iter().filter_map(move |(key, value)| {1155 let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;11561157 let key: Key = key.to_vec().try_into().ok()?;1158 let value: Value = value.decode().ok()?;11591160 Some(mapper(key, value))1161 });11621163 Ok(properties)1164 }11651166 fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1167 map_unique_err_to_proxy! {1168 match err {1169 CommonError::NoPermission => NoPermission,1170 CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1171 CommonError::PublicMintingNotAllowed => NoPermission,1172 CommonError::TokenNotFound => NoAvailableNftId,1173 CommonError::ApprovedValueTooLow => NoPermission,1174 CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1175 StructureError::TokenNotFound => NoAvailableNftId,1176 StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1177 }1178 }1179 }1180}1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![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 /* Unique-specific events */120 CorruptedCollectionType,121 NftTypeEncodeError,122 RmrkPropertyKeyIsTooLong,123 RmrkPropertyValueIsTooLong,124125 /* RMRK compatible events */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 )?, // todo possibly add limits to the collection if rmrk warrants them327 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 // todo think about renaming these782 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 // todo probably add extra connections to bases, slots, etc., when RMRK starts to use them903904 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)? // todo replace with better error?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}