difftreelog
fix(rmrk) accept/reject nft events
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 NFTSent {102 sender: T::AccountId,103 recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,104 collection_id: RmrkCollectionId,105 nft_id: RmrkNftId,106 approval_required: bool,107 },108 PropertySet {109 collection_id: RmrkCollectionId,110 maybe_nft_id: Option<RmrkNftId>,111 key: RmrkKeyString,112 value: RmrkValueString,113 },114 ResourceAdded {115 nft_id: RmrkNftId,116 resource_id: RmrkResourceId,117 },118 ResourceRemoval {119 nft_id: RmrkNftId,120 resource_id: RmrkResourceId,121 },122 }123124 #[pallet::error]125 pub enum Error<T> {126 /* Unique-specific events */127 CorruptedCollectionType,128 NftTypeEncodeError,129 RmrkPropertyKeyIsTooLong,130 RmrkPropertyValueIsTooLong,131132 /* RMRK compatible events */133 CollectionNotEmpty,134 NoAvailableCollectionId,135 NoAvailableNftId,136 CollectionUnknown,137 NoPermission,138 NonTransferable,139 CollectionFullOrLocked,140 ResourceDoesntExist,141 CannotSendToDescendentOrSelf,142 CannotAcceptNonOwnedNft,143 CannotRejectNonOwnedNft,144 ResourceNotPending,145 }146147 #[pallet::call]148 impl<T: Config> Pallet<T> {149 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]150 #[transactional]151 pub fn create_collection(152 origin: OriginFor<T>,153 metadata: RmrkString,154 max: Option<u32>,155 symbol: RmrkCollectionSymbol,156 ) -> DispatchResult {157 let sender = ensure_signed(origin)?;158159 let limits = CollectionLimits {160 owner_can_transfer: Some(false),161 token_limit: max,162 ..Default::default()163 };164165 let data = CreateCollectionData {166 limits: Some(limits),167 token_prefix: symbol168 .into_inner()169 .try_into()170 .map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,171 permissions: Some(CollectionPermissions {172 nesting: Some(NestingRule::Owner),173 ..Default::default()174 }),175 ..Default::default()176 };177178 let unique_collection_id = Self::init_collection(179 T::CrossAccountId::from_sub(sender.clone()),180 data,181 [182 Self::rmrk_property(Metadata, &metadata)?,183 Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,184 ]185 .into_iter(),186 )?;187 let rmrk_collection_id = <CollectionIndex<T>>::get();188189 <UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);190 <RmrkInernalCollectionId<T>>::insert(unique_collection_id, rmrk_collection_id);191192 <CollectionIndex<T>>::mutate(|n| *n += 1);193194 Self::deposit_event(Event::CollectionCreated {195 issuer: sender,196 collection_id: rmrk_collection_id,197 });198199 Ok(())200 }201202 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]203 #[transactional]204 pub fn destroy_collection(205 origin: OriginFor<T>,206 collection_id: RmrkCollectionId,207 ) -> DispatchResult {208 let sender = ensure_signed(origin)?;209 let cross_sender = T::CrossAccountId::from_sub(sender.clone());210211 let collection = Self::get_typed_nft_collection(212 Self::unique_collection_id(collection_id)?,213 misc::CollectionType::Regular,214 )?;215216 <PalletNft<T>>::destroy_collection(collection, &cross_sender)217 .map_err(Self::map_unique_err_to_proxy)?;218219 Self::deposit_event(Event::CollectionDestroyed {220 issuer: sender,221 collection_id,222 });223224 Ok(())225 }226227 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]228 #[transactional]229 pub fn change_collection_issuer(230 origin: OriginFor<T>,231 collection_id: RmrkCollectionId,232 new_issuer: <T::Lookup as StaticLookup>::Source,233 ) -> DispatchResult {234 let sender = ensure_signed(origin)?;235236 let new_issuer = T::Lookup::lookup(new_issuer)?;237238 Self::change_collection_owner(239 Self::unique_collection_id(collection_id)?,240 misc::CollectionType::Regular,241 sender.clone(),242 new_issuer.clone(),243 )?;244245 Self::deposit_event(Event::IssuerChanged {246 old_issuer: sender,247 new_issuer,248 collection_id,249 });250251 Ok(())252 }253254 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]255 #[transactional]256 pub fn lock_collection(257 origin: OriginFor<T>,258 collection_id: RmrkCollectionId,259 ) -> DispatchResult {260 let sender = ensure_signed(origin)?;261 let cross_sender = T::CrossAccountId::from_sub(sender.clone());262263 let collection = Self::get_typed_nft_collection(264 Self::unique_collection_id(collection_id)?,265 misc::CollectionType::Regular,266 )?;267268 Self::check_collection_owner(&collection, &cross_sender)?;269270 let token_count = collection.total_supply();271272 let mut collection = collection.into_inner();273 collection.limits.token_limit = Some(token_count);274 collection.save()?;275276 Self::deposit_event(Event::CollectionLocked {277 issuer: sender,278 collection_id,279 });280281 Ok(())282 }283284 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]285 #[transactional]286 pub fn mint_nft(287 origin: OriginFor<T>,288 owner: T::AccountId,289 collection_id: RmrkCollectionId,290 recipient: Option<T::AccountId>,291 royalty_amount: Option<Permill>,292 metadata: RmrkString,293 transferable: bool,294 ) -> DispatchResult {295 let sender = ensure_signed(origin)?;296 let sender = T::CrossAccountId::from_sub(sender);297 let cross_owner = T::CrossAccountId::from_sub(owner.clone());298299 let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {300 recipient: recipient.unwrap_or_else(|| owner.clone()),301 amount,302 });303304 let collection = Self::get_typed_nft_collection(305 Self::unique_collection_id(collection_id)?,306 misc::CollectionType::Regular,307 )?;308309 let nft_id = Self::create_nft(310 &sender,311 &cross_owner,312 &collection,313 [314 Self::rmrk_property(TokenType, &NftType::Regular)?,315 Self::rmrk_property(Transferable, &transferable)?,316 Self::rmrk_property(PendingNftAccept, &false)?,317 Self::rmrk_property(RoyaltyInfo, &royalty_info)?,318 Self::rmrk_property(Metadata, &metadata)?,319 Self::rmrk_property(Equipped, &false)?,320 Self::rmrk_property(321 ResourceCollection,322 &Self::init_collection(323 sender.clone(),324 CreateCollectionData {325 ..Default::default()326 },327 [Self::rmrk_property(328 CollectionType,329 &misc::CollectionType::Resource,330 )?]331 .into_iter(),332 )?,333 )?, // todo possibly add limits to the collection if rmrk warrants them334 Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,335 ]336 .into_iter(),337 )338 .map_err(|err| match err {339 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),340 err => Self::map_unique_err_to_proxy(err),341 })?;342343 Self::deposit_event(Event::NftMinted {344 owner,345 collection_id,346 nft_id: nft_id.0,347 });348349 Ok(())350 }351352 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]353 #[transactional]354 pub fn burn_nft(355 origin: OriginFor<T>,356 collection_id: RmrkCollectionId,357 nft_id: RmrkNftId,358 ) -> DispatchResult {359 let sender = ensure_signed(origin)?;360 let cross_sender = T::CrossAccountId::from_sub(sender.clone());361362 Self::destroy_nft(363 cross_sender,364 Self::unique_collection_id(collection_id)?,365 nft_id.into(),366 )367 .map_err(Self::map_unique_err_to_proxy)?;368369 Self::deposit_event(Event::NFTBurned {370 owner: sender,371 nft_id,372 });373374 Ok(())375 }376377 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]378 #[transactional]379 pub fn send(380 origin: OriginFor<T>,381 rmrk_collection_id: RmrkCollectionId,382 rmrk_nft_id: RmrkNftId,383 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,384 ) -> DispatchResult {385 let sender = ensure_signed(origin.clone())?;386 let cross_sender = T::CrossAccountId::from_sub(sender.clone());387388 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;389 let nft_id = rmrk_nft_id.into();390391 let token_data =392 <TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;393394 let from = token_data.owner;395396 let collection =397 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;398399 ensure!(400 Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,401 <Error<T>>::NonTransferable402 );403404 ensure!(405 !Self::get_nft_property_decoded(406 collection_id,407 nft_id,408 RmrkProperty::PendingNftAccept409 )?,410 <Error<T>>::NoPermission411 );412413 let target_owner;414 let approval_required;415416 match new_owner {417 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {418 target_owner = T::CrossAccountId::from_sub(account_id.clone());419 approval_required = false;420 }421 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(422 target_collection_id,423 target_nft_id,424 ) => {425 let target_collection_id = Self::unique_collection_id(target_collection_id)?;426427 let target_nft_budget = budget::Value::new(NESTING_BUDGET);428429 let target_nft_owner = <PalletStructure<T>>::get_checked_indirect_owner(430 target_collection_id,431 target_nft_id.into(),432 Some((collection_id, nft_id)),433 &target_nft_budget,434 )435 .map_err(Self::map_unique_err_to_proxy)?;436437 approval_required = cross_sender != target_nft_owner;438439 if approval_required {440 target_owner = target_nft_owner;441442 <PalletNft<T>>::set_scoped_token_property(443 collection.id,444 nft_id,445 PropertyScope::Rmrk,446 Self::rmrk_property(PendingNftAccept, &approval_required)?,447 )?;448 } else {449 target_owner = T::CrossTokenAddressMapping::token_to_address(450 target_collection_id,451 target_nft_id.into(),452 );453 }454 }455 }456457 let src_nft_budget = budget::Value::new(NESTING_BUDGET);458459 <PalletNft<T>>::transfer_from(460 &collection,461 &cross_sender,462 &from,463 &target_owner,464 nft_id,465 &src_nft_budget,466 )467 .map_err(Self::map_unique_err_to_proxy)?;468469 Self::deposit_event(Event::NFTSent {470 sender,471 recipient: new_owner,472 collection_id: rmrk_collection_id,473 nft_id: rmrk_nft_id,474 approval_required,475 });476477 Ok(())478 }479480 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]481 #[transactional]482 pub fn accept_nft(483 origin: OriginFor<T>,484 rmrk_collection_id: RmrkCollectionId,485 rmrk_nft_id: RmrkNftId,486 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,487 ) -> DispatchResult {488 let sender = ensure_signed(origin.clone())?;489 let cross_sender = T::CrossAccountId::from_sub(sender);490491 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;492 let nft_id = rmrk_nft_id.into();493494 let collection =495 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;496497 let new_owner = match new_owner {498 RmrkAccountIdOrCollectionNftTuple::AccountId(account_id) => {499 T::CrossAccountId::from_sub(account_id)500 }501 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(502 target_collection_id,503 target_nft_id,504 ) => {505 let target_collection_id = Self::unique_collection_id(target_collection_id)?;506507 T::CrossTokenAddressMapping::token_to_address(508 target_collection_id,509 TokenId(target_nft_id),510 )511 }512 };513514 let budget = budget::Value::new(NESTING_BUDGET);515516 <PalletNft<T>>::transfer(&collection, &cross_sender, &new_owner, nft_id, &budget)517 .map_err(|err| {518 if err == <CommonError<T>>::OnlyOwnerAllowedToNest.into() {519 <Error<T>>::CannotAcceptNonOwnedNft.into()520 } else {521 Self::map_unique_err_to_proxy(err)522 }523 })?;524525 <PalletNft<T>>::set_scoped_token_property(526 collection.id,527 nft_id,528 PropertyScope::Rmrk,529 Self::rmrk_property(PendingNftAccept, &false)?,530 )?;531532 Ok(())533 }534535 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]536 #[transactional]537 pub fn reject_nft(538 origin: OriginFor<T>,539 collection_id: RmrkCollectionId,540 nft_id: RmrkNftId,541 ) -> DispatchResult {542 let sender = ensure_signed(origin)?;543 let cross_sender = T::CrossAccountId::from_sub(sender);544545 let collection_id = Self::unique_collection_id(collection_id)?;546 let nft_id = nft_id.into();547548 Self::destroy_nft(cross_sender, collection_id, nft_id).map_err(|err| {549 if err == <CommonError<T>>::NoPermission.into()550 || err == <CommonError<T>>::ApprovedValueTooLow.into()551 {552 <Error<T>>::CannotRejectNonOwnedNft.into()553 } else {554 Self::map_unique_err_to_proxy(err)555 }556 })?;557558 Ok(())559 }560561 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]562 #[transactional]563 pub fn accept_resource(564 origin: OriginFor<T>,565 rmrk_collection_id: RmrkCollectionId,566 rmrk_nft_id: RmrkNftId,567 rmrk_resource_id: RmrkResourceId,568 ) -> DispatchResult {569 let sender = ensure_signed(origin)?;570 let cross_sender = T::CrossAccountId::from_sub(sender);571572 let collection_id = Self::unique_collection_id(rmrk_collection_id)573 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;574575 let nft_id = rmrk_nft_id.into();576 let resource_id = rmrk_resource_id.into();577578 let budget = budget::Value::new(NESTING_BUDGET);579580 let nft_owner =581 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)582 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;583584 let resource_collection_id: CollectionId =585 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)586 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;587588 let is_pending: bool = Self::get_nft_property_decoded(589 resource_collection_id,590 resource_id,591 PendingResourceAccept,592 )593 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;594595 ensure!(is_pending, <Error<T>>::ResourceNotPending);596597 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);598599 <PalletNft<T>>::set_scoped_token_property(600 resource_collection_id,601 rmrk_resource_id.into(),602 PropertyScope::Rmrk,603 Self::rmrk_property(PendingResourceAccept, &false)?,604 )?;605606 Ok(())607 }608609 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]610 #[transactional]611 pub fn accept_resource_removal(612 origin: OriginFor<T>,613 rmrk_collection_id: RmrkCollectionId,614 rmrk_nft_id: RmrkNftId,615 rmrk_resource_id: RmrkResourceId,616 ) -> DispatchResult {617 let sender = ensure_signed(origin)?;618 let cross_sender = T::CrossAccountId::from_sub(sender);619620 let collection_id = Self::unique_collection_id(rmrk_collection_id)621 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;622623 let nft_id = rmrk_nft_id.into();624 let resource_id = rmrk_resource_id.into();625626 let budget = budget::Value::new(NESTING_BUDGET);627628 let nft_owner =629 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)630 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;631632 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);633634 let resource_collection_id: CollectionId =635 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)636 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;637638 let is_pending: bool = Self::get_nft_property_decoded(639 resource_collection_id,640 resource_id,641 PendingResourceRemoval,642 )643 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;644645 ensure!(is_pending, <Error<T>>::ResourceNotPending);646647 let resource_collection = Self::get_typed_nft_collection(648 resource_collection_id,649 misc::CollectionType::Resource,650 )?;651652 <PalletNft<T>>::burn(&resource_collection, &cross_sender, rmrk_resource_id.into())653 .map_err(Self::map_unique_err_to_proxy)?;654655 Ok(())656 }657658 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]659 #[transactional]660 pub fn set_property(661 origin: OriginFor<T>,662 #[pallet::compact] rmrk_collection_id: RmrkCollectionId,663 maybe_nft_id: Option<RmrkNftId>,664 key: RmrkKeyString,665 value: RmrkValueString,666 ) -> DispatchResult {667 let sender = ensure_signed(origin)?;668 let sender = T::CrossAccountId::from_sub(sender);669670 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;671 let budget = budget::Value::new(NESTING_BUDGET);672673 match maybe_nft_id {674 Some(nft_id) => {675 let token_id: TokenId = nft_id.into();676677 Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;678 Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;679680 <PalletNft<T>>::set_scoped_token_property(681 collection_id,682 token_id,683 PropertyScope::Rmrk,684 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,685 )?;686 }687 None => {688 let collection = Self::get_typed_nft_collection(689 collection_id,690 misc::CollectionType::Regular,691 )?;692693 Self::check_collection_owner(&collection, &sender)?;694695 <PalletCommon<T>>::set_scoped_collection_property(696 collection_id,697 PropertyScope::Rmrk,698 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,699 )?;700 }701 }702703 Self::deposit_event(Event::PropertySet {704 collection_id: rmrk_collection_id,705 maybe_nft_id,706 key,707 value,708 });709710 Ok(())711 }712713 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]714 #[transactional]715 pub fn set_priority(716 origin: OriginFor<T>,717 rmrk_collection_id: RmrkCollectionId,718 rmrk_nft_id: RmrkNftId,719 priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,720 ) -> DispatchResult {721 let sender = ensure_signed(origin)?;722 let sender = T::CrossAccountId::from_sub(sender);723724 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;725 let nft_id = rmrk_nft_id.into();726 let budget = budget::Value::new(NESTING_BUDGET);727728 Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;729 Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;730731 <PalletNft<T>>::set_scoped_token_property(732 collection_id,733 nft_id,734 PropertyScope::Rmrk,735 Self::rmrk_property(ResourcePriorities, &priorities.into_inner())?,736 )?;737738 Ok(())739 }740741 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]742 #[transactional]743 pub fn add_basic_resource(744 origin: OriginFor<T>,745 collection_id: RmrkCollectionId,746 nft_id: RmrkNftId,747 resource: RmrkBasicResource,748 ) -> DispatchResult {749 let sender = ensure_signed(origin.clone())?;750751 let resource_id = Self::resource_add(752 sender,753 Self::unique_collection_id(collection_id)?,754 nft_id.into(),755 [756 Self::rmrk_property(TokenType, &NftType::Resource)?,757 Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,758 Self::rmrk_property(Src, &resource.src)?,759 Self::rmrk_property(Metadata, &resource.metadata)?,760 Self::rmrk_property(License, &resource.license)?,761 Self::rmrk_property(Thumb, &resource.thumb)?,762 ]763 .into_iter(),764 )?;765766 Self::deposit_event(Event::ResourceAdded {767 nft_id,768 resource_id,769 });770 Ok(())771 }772773 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]774 #[transactional]775 pub fn add_composable_resource(776 origin: OriginFor<T>,777 collection_id: RmrkCollectionId,778 nft_id: RmrkNftId,779 _resource_id: RmrkBoundedResource,780 resource: RmrkComposableResource,781 ) -> DispatchResult {782 let sender = ensure_signed(origin.clone())?;783784 let resource_id = Self::resource_add(785 sender,786 Self::unique_collection_id(collection_id)?,787 nft_id.into(),788 [789 Self::rmrk_property(TokenType, &NftType::Resource)?,790 Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,791 Self::rmrk_property(Parts, &resource.parts)?,792 Self::rmrk_property(Base, &resource.base)?,793 Self::rmrk_property(Src, &resource.src)?,794 Self::rmrk_property(Metadata, &resource.metadata)?,795 Self::rmrk_property(License, &resource.license)?,796 Self::rmrk_property(Thumb, &resource.thumb)?,797 ]798 .into_iter(),799 )?;800801 Self::deposit_event(Event::ResourceAdded {802 nft_id,803 resource_id,804 });805 Ok(())806 }807808 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]809 #[transactional]810 pub fn add_slot_resource(811 origin: OriginFor<T>,812 collection_id: RmrkCollectionId,813 nft_id: RmrkNftId,814 resource: RmrkSlotResource,815 ) -> DispatchResult {816 let sender = ensure_signed(origin.clone())?;817818 let resource_id = Self::resource_add(819 sender,820 Self::unique_collection_id(collection_id)?,821 nft_id.into(),822 [823 Self::rmrk_property(TokenType, &NftType::Resource)?,824 Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,825 Self::rmrk_property(Base, &resource.base)?,826 Self::rmrk_property(Src, &resource.src)?,827 Self::rmrk_property(Metadata, &resource.metadata)?,828 Self::rmrk_property(Slot, &resource.slot)?,829 Self::rmrk_property(License, &resource.license)?,830 Self::rmrk_property(Thumb, &resource.thumb)?,831 ]832 .into_iter(),833 )?;834835 Self::deposit_event(Event::ResourceAdded {836 nft_id,837 resource_id,838 });839 Ok(())840 }841842 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]843 #[transactional]844 pub fn remove_resource(845 origin: OriginFor<T>,846 collection_id: RmrkCollectionId,847 nft_id: RmrkNftId,848 resource_id: RmrkResourceId,849 ) -> DispatchResult {850 let sender = ensure_signed(origin.clone())?;851852 Self::resource_remove(853 sender,854 Self::unique_collection_id(collection_id)?,855 nft_id.into(),856 resource_id.into(),857 )?;858859 Self::deposit_event(Event::ResourceRemoval {860 nft_id,861 resource_id,862 });863 Ok(())864 }865 }866}867868impl<T: Config> Pallet<T> {869 pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {870 let key = rmrk_key.to_key::<T>()?;871872 let scoped_key = PropertyScope::Rmrk873 .apply(key)874 .map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;875876 Ok(scoped_key)877 }878879 // todo think about renaming these880 pub fn rmrk_property<E: Encode>(881 rmrk_key: RmrkProperty,882 value: &E,883 ) -> Result<Property, DispatchError> {884 let key = rmrk_key.to_key::<T>()?;885886 let value = value887 .encode()888 .try_into()889 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;890891 let property = Property { key, value };892893 Ok(property)894 }895896 pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {897 vec.decode()898 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())899 }900901 pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>902 where903 BoundedVec<u8, S>: TryFrom<Vec<u8>>,904 {905 vec.rebind()906 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())907 }908909 fn init_collection(910 sender: T::CrossAccountId,911 data: CreateCollectionData<T::AccountId>,912 properties: impl Iterator<Item = Property>,913 ) -> Result<CollectionId, DispatchError> {914 let collection_id = <PalletNft<T>>::init_collection(sender, data);915916 if let Err(DispatchError::Arithmetic(_)) = &collection_id {917 return Err(<Error<T>>::NoAvailableCollectionId.into());918 }919920 <PalletCommon<T>>::set_scoped_collection_properties(921 collection_id?,922 PropertyScope::Rmrk,923 properties,924 )?;925926 collection_id927 }928929 pub fn create_nft(930 sender: &T::CrossAccountId,931 owner: &T::CrossAccountId,932 collection: &NonfungibleHandle<T>,933 properties: impl Iterator<Item = Property>,934 ) -> Result<TokenId, DispatchError> {935 let data = CreateNftExData {936 properties: BoundedVec::default(),937 owner: owner.clone(),938 };939940 let budget = budget::Value::new(NESTING_BUDGET);941942 <PalletNft<T>>::create_item(collection, sender, data, &budget)?;943944 let nft_id = <PalletNft<T>>::current_token_id(collection.id);945946 <PalletNft<T>>::set_scoped_token_properties(947 collection.id,948 nft_id,949 PropertyScope::Rmrk,950 properties,951 )?;952953 Ok(nft_id)954 }955956 fn destroy_nft(957 sender: T::CrossAccountId,958 collection_id: CollectionId,959 token_id: TokenId,960 ) -> DispatchResult {961 let collection =962 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;963964 let token_data =965 <TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;966967 let from = token_data.owner;968969 let budget = budget::Value::new(NESTING_BUDGET);970971 <PalletNft<T>>::burn_from(&collection, &sender, &from, token_id, &budget)972 }973974 fn resource_add(975 sender: T::AccountId,976 collection_id: CollectionId,977 token_id: TokenId,978 resource_properties: impl Iterator<Item = Property>,979 ) -> Result<RmrkResourceId, DispatchError> {980 let collection =981 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;982 ensure!(collection.owner == sender, Error::<T>::NoPermission);983984 let sender = T::CrossAccountId::from_sub(sender);985 let budget = budget::Value::new(NESTING_BUDGET);986987 let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, token_id, &budget)988 .map_err(Self::map_unique_err_to_proxy)?;989990 let pending = sender != nft_owner;991992 let resource_collection_id: CollectionId =993 Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;994 let resource_collection =995 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;996997 // todo probably add extra connections to bases, slots, etc., when RMRK starts to use them998999 let resource_id = Self::create_nft(1000 &sender,1001 &nft_owner,1002 &resource_collection,1003 resource_properties.chain(1004 [1005 Self::rmrk_property(PendingResourceAccept, &pending)?,1006 Self::rmrk_property(PendingResourceRemoval, &false)?,1007 ]1008 .into_iter(),1009 ),1010 )1011 .map_err(|err| match err {1012 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),1013 err => Self::map_unique_err_to_proxy(err),1014 })?;10151016 Ok(resource_id.0)1017 }10181019 fn resource_remove(1020 sender: T::AccountId,1021 collection_id: CollectionId,1022 nft_id: TokenId,1023 resource_id: TokenId,1024 ) -> DispatchResult {1025 let collection =1026 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1027 ensure!(collection.owner == sender, Error::<T>::NoPermission);10281029 let resource_collection_id: CollectionId =1030 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;1031 let resource_collection =1032 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;1033 ensure!(1034 <PalletNft<T>>::token_exists(&resource_collection, resource_id),1035 Error::<T>::ResourceDoesntExist1036 );10371038 let budget = up_data_structs::budget::Value::new(10);1039 let topmost_owner =1040 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;10411042 let sender = T::CrossAccountId::from_sub(sender);1043 if topmost_owner == sender {1044 <PalletNft<T>>::burn(&resource_collection, &sender, resource_id)1045 .map_err(Self::map_unique_err_to_proxy)?;1046 } else {1047 <PalletNft<T>>::set_scoped_token_property(1048 resource_collection_id,1049 resource_id,1050 PropertyScope::Rmrk,1051 Self::rmrk_property(PendingResourceRemoval, &true)?,1052 )?;1053 }10541055 Ok(())1056 }10571058 fn change_collection_owner(1059 collection_id: CollectionId,1060 collection_type: misc::CollectionType,1061 sender: T::AccountId,1062 new_owner: T::AccountId,1063 ) -> DispatchResult {1064 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1065 Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;10661067 let mut collection = collection.into_inner();10681069 collection.owner = new_owner;1070 collection.save()1071 }10721073 fn check_collection_owner(1074 collection: &NonfungibleHandle<T>,1075 account: &T::CrossAccountId,1076 ) -> DispatchResult {1077 collection1078 .check_is_owner(account)1079 .map_err(Self::map_unique_err_to_proxy)1080 }10811082 pub fn last_collection_idx() -> RmrkCollectionId {1083 <CollectionIndex<T>>::get()1084 }10851086 pub fn unique_collection_id(1087 rmrk_collection_id: RmrkCollectionId,1088 ) -> Result<CollectionId, DispatchError> {1089 <UniqueCollectionId<T>>::try_get(rmrk_collection_id)1090 .map_err(|_| <Error<T>>::CollectionUnknown.into())1091 }10921093 pub fn rmrk_collection_id(1094 unique_collection_id: CollectionId,1095 ) -> Result<RmrkCollectionId, DispatchError> {1096 <RmrkInernalCollectionId<T>>::try_get(unique_collection_id)1097 .map_err(|_| <Error<T>>::CollectionUnknown.into())1098 }10991100 pub fn get_nft_collection(1101 collection_id: CollectionId,1102 ) -> Result<NonfungibleHandle<T>, DispatchError> {1103 let collection = <CollectionHandle<T>>::try_get(collection_id)1104 .map_err(|_| <Error<T>>::CollectionUnknown)?;11051106 match collection.mode {1107 CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1108 _ => Err(<Error<T>>::CollectionUnknown.into()),1109 }1110 }11111112 pub fn collection_exists(collection_id: CollectionId) -> bool {1113 <CollectionHandle<T>>::try_get(collection_id).is_ok()1114 }11151116 pub fn get_collection_property(1117 collection_id: CollectionId,1118 key: RmrkProperty,1119 ) -> Result<PropertyValue, DispatchError> {1120 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1121 .get(&Self::rmrk_property_key(key)?)1122 .ok_or(<Error<T>>::CollectionUnknown)?1123 .clone();11241125 Ok(collection_property)1126 }11271128 pub fn get_collection_property_decoded<V: Decode>(1129 collection_id: CollectionId,1130 key: RmrkProperty,1131 ) -> Result<V, DispatchError> {1132 Self::decode_property(Self::get_collection_property(collection_id, key)?)1133 }11341135 pub fn get_collection_type(1136 collection_id: CollectionId,1137 ) -> Result<misc::CollectionType, DispatchError> {1138 Self::get_collection_property_decoded(collection_id, CollectionType)1139 .map_err(|_| <Error<T>>::CorruptedCollectionType.into())1140 }11411142 pub fn ensure_collection_type(1143 collection_id: CollectionId,1144 collection_type: misc::CollectionType,1145 ) -> DispatchResult {1146 let actual_type = Self::get_collection_type(collection_id)?;1147 ensure!(1148 actual_type == collection_type,1149 <CommonError<T>>::NoPermission1150 );11511152 Ok(())1153 }11541155 pub fn get_typed_nft_collection(1156 collection_id: CollectionId,1157 collection_type: misc::CollectionType,1158 ) -> Result<NonfungibleHandle<T>, DispatchError> {1159 Self::ensure_collection_type(collection_id, collection_type)?;11601161 Self::get_nft_collection(collection_id)1162 }11631164 pub fn get_typed_nft_collection_mapped(1165 rmrk_collection_id: RmrkCollectionId,1166 collection_type: misc::CollectionType,1167 ) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1168 let unique_collection_id = match collection_type {1169 misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1170 _ => rmrk_collection_id.into(),1171 };11721173 let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;11741175 Ok((collection, unique_collection_id))1176 }11771178 pub fn get_nft_property(1179 collection_id: CollectionId,1180 nft_id: TokenId,1181 key: RmrkProperty,1182 ) -> Result<PropertyValue, DispatchError> {1183 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1184 .get(&Self::rmrk_property_key(key)?)1185 .ok_or(<Error<T>>::NoAvailableNftId)? // todo replace with better error?1186 .clone();11871188 Ok(nft_property)1189 }11901191 pub fn get_nft_property_decoded<V: Decode>(1192 collection_id: CollectionId,1193 nft_id: TokenId,1194 key: RmrkProperty,1195 ) -> Result<V, DispatchError> {1196 Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)1197 }11981199 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1200 <TokenData<T>>::contains_key((collection_id, nft_id))1201 }12021203 pub fn get_nft_type(1204 collection_id: CollectionId,1205 token_id: TokenId,1206 ) -> Result<NftType, DispatchError> {1207 Self::get_nft_property_decoded(collection_id, token_id, TokenType)1208 .map_err(|_| <Error<T>>::NoAvailableNftId.into())1209 }12101211 pub fn ensure_nft_type(1212 collection_id: CollectionId,1213 token_id: TokenId,1214 nft_type: NftType,1215 ) -> DispatchResult {1216 let actual_type = Self::get_nft_type(collection_id, token_id)?;1217 ensure!(actual_type == nft_type, <Error<T>>::NoPermission);12181219 Ok(())1220 }12211222 pub fn ensure_nft_owner(1223 collection_id: CollectionId,1224 token_id: TokenId,1225 possible_owner: &T::CrossAccountId,1226 nesting_budget: &dyn budget::Budget,1227 ) -> DispatchResult {1228 let is_owned = <PalletStructure<T>>::check_indirectly_owned(1229 possible_owner.clone(),1230 collection_id,1231 token_id,1232 None,1233 nesting_budget,1234 )1235 .map_err(Self::map_unique_err_to_proxy)?;12361237 ensure!(is_owned, <Error<T>>::NoPermission);12381239 Ok(())1240 }12411242 pub fn filter_user_properties<Key, Value, R, Mapper>(1243 collection_id: CollectionId,1244 token_id: Option<TokenId>,1245 filter_keys: Option<Vec<RmrkPropertyKey>>,1246 mapper: Mapper,1247 ) -> Result<Vec<R>, DispatchError>1248 where1249 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1250 Value: Decode + Default,1251 Mapper: Fn(Key, Value) -> R,1252 {1253 filter_keys1254 .map(|keys| {1255 let properties = keys1256 .into_iter()1257 .filter_map(|key| {1258 let key: Key = key.try_into().ok()?;12591260 let value = match token_id {1261 Some(token_id) => Self::get_nft_property_decoded(1262 collection_id,1263 token_id,1264 UserProperty(key.as_ref()),1265 ),1266 None => Self::get_collection_property_decoded(1267 collection_id,1268 UserProperty(key.as_ref()),1269 ),1270 }1271 .ok()?;12721273 Some(mapper(key, value))1274 })1275 .collect();12761277 Ok(properties)1278 })1279 .unwrap_or_else(|| {1280 let properties =1281 Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();12821283 Ok(properties)1284 })1285 }12861287 pub fn iterate_user_properties<Key, Value, R, Mapper>(1288 collection_id: CollectionId,1289 token_id: Option<TokenId>,1290 mapper: Mapper,1291 ) -> Result<impl Iterator<Item = R>, DispatchError>1292 where1293 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1294 Value: Decode + Default,1295 Mapper: Fn(Key, Value) -> R,1296 {1297 let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;12981299 let properties = match token_id {1300 Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1301 None => <PalletCommon<T>>::collection_properties(collection_id),1302 };13031304 let properties = properties.into_iter().filter_map(move |(key, value)| {1305 let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;13061307 let key: Key = key.to_vec().try_into().ok()?;1308 let value: Value = value.decode().ok()?;13091310 Some(mapper(key, value))1311 });13121313 Ok(properties)1314 }13151316 fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1317 map_unique_err_to_proxy! {1318 match err {1319 CommonError::NoPermission => NoPermission,1320 CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1321 CommonError::PublicMintingNotAllowed => NoPermission,1322 CommonError::TokenNotFound => NoAvailableNftId,1323 CommonError::ApprovedValueTooLow => NoPermission,1324 CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1325 StructureError::TokenNotFound => NoAvailableNftId,1326 StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1327 }1328 }1329 }1330}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 NFTSent {102 sender: T::AccountId,103 recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,104 collection_id: RmrkCollectionId,105 nft_id: RmrkNftId,106 approval_required: bool,107 },108 NFTAccepted {109 sender: T::AccountId,110 recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,111 collection_id: RmrkCollectionId,112 nft_id: RmrkNftId,113 },114 NFTRejected {115 sender: T::AccountId,116 collection_id: RmrkCollectionId,117 nft_id: RmrkNftId,118 },119 PropertySet {120 collection_id: RmrkCollectionId,121 maybe_nft_id: Option<RmrkNftId>,122 key: RmrkKeyString,123 value: RmrkValueString,124 },125 ResourceAdded {126 nft_id: RmrkNftId,127 resource_id: RmrkResourceId,128 },129 ResourceRemoval {130 nft_id: RmrkNftId,131 resource_id: RmrkResourceId,132 },133 }134135 #[pallet::error]136 pub enum Error<T> {137 /* Unique-specific events */138 CorruptedCollectionType,139 NftTypeEncodeError,140 RmrkPropertyKeyIsTooLong,141 RmrkPropertyValueIsTooLong,142143 /* RMRK compatible events */144 CollectionNotEmpty,145 NoAvailableCollectionId,146 NoAvailableNftId,147 CollectionUnknown,148 NoPermission,149 NonTransferable,150 CollectionFullOrLocked,151 ResourceDoesntExist,152 CannotSendToDescendentOrSelf,153 CannotAcceptNonOwnedNft,154 CannotRejectNonOwnedNft,155 ResourceNotPending,156 }157158 #[pallet::call]159 impl<T: Config> Pallet<T> {160 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]161 #[transactional]162 pub fn create_collection(163 origin: OriginFor<T>,164 metadata: RmrkString,165 max: Option<u32>,166 symbol: RmrkCollectionSymbol,167 ) -> DispatchResult {168 let sender = ensure_signed(origin)?;169170 let limits = CollectionLimits {171 owner_can_transfer: Some(false),172 token_limit: max,173 ..Default::default()174 };175176 let data = CreateCollectionData {177 limits: Some(limits),178 token_prefix: symbol179 .into_inner()180 .try_into()181 .map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,182 permissions: Some(CollectionPermissions {183 nesting: Some(NestingRule::Owner),184 ..Default::default()185 }),186 ..Default::default()187 };188189 let unique_collection_id = Self::init_collection(190 T::CrossAccountId::from_sub(sender.clone()),191 data,192 [193 Self::rmrk_property(Metadata, &metadata)?,194 Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,195 ]196 .into_iter(),197 )?;198 let rmrk_collection_id = <CollectionIndex<T>>::get();199200 <UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);201 <RmrkInernalCollectionId<T>>::insert(unique_collection_id, rmrk_collection_id);202203 <CollectionIndex<T>>::mutate(|n| *n += 1);204205 Self::deposit_event(Event::CollectionCreated {206 issuer: sender,207 collection_id: rmrk_collection_id,208 });209210 Ok(())211 }212213 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]214 #[transactional]215 pub fn destroy_collection(216 origin: OriginFor<T>,217 collection_id: RmrkCollectionId,218 ) -> DispatchResult {219 let sender = ensure_signed(origin)?;220 let cross_sender = T::CrossAccountId::from_sub(sender.clone());221222 let collection = Self::get_typed_nft_collection(223 Self::unique_collection_id(collection_id)?,224 misc::CollectionType::Regular,225 )?;226227 <PalletNft<T>>::destroy_collection(collection, &cross_sender)228 .map_err(Self::map_unique_err_to_proxy)?;229230 Self::deposit_event(Event::CollectionDestroyed {231 issuer: sender,232 collection_id,233 });234235 Ok(())236 }237238 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]239 #[transactional]240 pub fn change_collection_issuer(241 origin: OriginFor<T>,242 collection_id: RmrkCollectionId,243 new_issuer: <T::Lookup as StaticLookup>::Source,244 ) -> DispatchResult {245 let sender = ensure_signed(origin)?;246247 let new_issuer = T::Lookup::lookup(new_issuer)?;248249 Self::change_collection_owner(250 Self::unique_collection_id(collection_id)?,251 misc::CollectionType::Regular,252 sender.clone(),253 new_issuer.clone(),254 )?;255256 Self::deposit_event(Event::IssuerChanged {257 old_issuer: sender,258 new_issuer,259 collection_id,260 });261262 Ok(())263 }264265 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]266 #[transactional]267 pub fn lock_collection(268 origin: OriginFor<T>,269 collection_id: RmrkCollectionId,270 ) -> DispatchResult {271 let sender = ensure_signed(origin)?;272 let cross_sender = T::CrossAccountId::from_sub(sender.clone());273274 let collection = Self::get_typed_nft_collection(275 Self::unique_collection_id(collection_id)?,276 misc::CollectionType::Regular,277 )?;278279 Self::check_collection_owner(&collection, &cross_sender)?;280281 let token_count = collection.total_supply();282283 let mut collection = collection.into_inner();284 collection.limits.token_limit = Some(token_count);285 collection.save()?;286287 Self::deposit_event(Event::CollectionLocked {288 issuer: sender,289 collection_id,290 });291292 Ok(())293 }294295 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]296 #[transactional]297 pub fn mint_nft(298 origin: OriginFor<T>,299 owner: T::AccountId,300 collection_id: RmrkCollectionId,301 recipient: Option<T::AccountId>,302 royalty_amount: Option<Permill>,303 metadata: RmrkString,304 transferable: bool,305 ) -> DispatchResult {306 let sender = ensure_signed(origin)?;307 let sender = T::CrossAccountId::from_sub(sender);308 let cross_owner = T::CrossAccountId::from_sub(owner.clone());309310 let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {311 recipient: recipient.unwrap_or_else(|| owner.clone()),312 amount,313 });314315 let collection = Self::get_typed_nft_collection(316 Self::unique_collection_id(collection_id)?,317 misc::CollectionType::Regular,318 )?;319320 let nft_id = Self::create_nft(321 &sender,322 &cross_owner,323 &collection,324 [325 Self::rmrk_property(TokenType, &NftType::Regular)?,326 Self::rmrk_property(Transferable, &transferable)?,327 Self::rmrk_property(PendingNftAccept, &false)?,328 Self::rmrk_property(RoyaltyInfo, &royalty_info)?,329 Self::rmrk_property(Metadata, &metadata)?,330 Self::rmrk_property(Equipped, &false)?,331 Self::rmrk_property(332 ResourceCollection,333 &Self::init_collection(334 sender.clone(),335 CreateCollectionData {336 ..Default::default()337 },338 [Self::rmrk_property(339 CollectionType,340 &misc::CollectionType::Resource,341 )?]342 .into_iter(),343 )?,344 )?, // todo possibly add limits to the collection if rmrk warrants them345 Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,346 ]347 .into_iter(),348 )349 .map_err(|err| match err {350 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),351 err => Self::map_unique_err_to_proxy(err),352 })?;353354 Self::deposit_event(Event::NftMinted {355 owner,356 collection_id,357 nft_id: nft_id.0,358 });359360 Ok(())361 }362363 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]364 #[transactional]365 pub fn burn_nft(366 origin: OriginFor<T>,367 collection_id: RmrkCollectionId,368 nft_id: RmrkNftId,369 ) -> DispatchResult {370 let sender = ensure_signed(origin)?;371 let cross_sender = T::CrossAccountId::from_sub(sender.clone());372373 Self::destroy_nft(374 cross_sender,375 Self::unique_collection_id(collection_id)?,376 nft_id.into(),377 )378 .map_err(Self::map_unique_err_to_proxy)?;379380 Self::deposit_event(Event::NFTBurned {381 owner: sender,382 nft_id,383 });384385 Ok(())386 }387388 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]389 #[transactional]390 pub fn send(391 origin: OriginFor<T>,392 rmrk_collection_id: RmrkCollectionId,393 rmrk_nft_id: RmrkNftId,394 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,395 ) -> DispatchResult {396 let sender = ensure_signed(origin.clone())?;397 let cross_sender = T::CrossAccountId::from_sub(sender.clone());398399 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;400 let nft_id = rmrk_nft_id.into();401402 let token_data =403 <TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;404405 let from = token_data.owner;406407 let collection =408 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;409410 ensure!(411 Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,412 <Error<T>>::NonTransferable413 );414415 ensure!(416 !Self::get_nft_property_decoded(417 collection_id,418 nft_id,419 RmrkProperty::PendingNftAccept420 )?,421 <Error<T>>::NoPermission422 );423424 let target_owner;425 let approval_required;426427 match new_owner {428 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {429 target_owner = T::CrossAccountId::from_sub(account_id.clone());430 approval_required = false;431 }432 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(433 target_collection_id,434 target_nft_id,435 ) => {436 let target_collection_id = Self::unique_collection_id(target_collection_id)?;437438 let target_nft_budget = budget::Value::new(NESTING_BUDGET);439440 let target_nft_owner = <PalletStructure<T>>::get_checked_indirect_owner(441 target_collection_id,442 target_nft_id.into(),443 Some((collection_id, nft_id)),444 &target_nft_budget,445 )446 .map_err(Self::map_unique_err_to_proxy)?;447448 approval_required = cross_sender != target_nft_owner;449450 if approval_required {451 target_owner = target_nft_owner;452453 <PalletNft<T>>::set_scoped_token_property(454 collection.id,455 nft_id,456 PropertyScope::Rmrk,457 Self::rmrk_property(PendingNftAccept, &approval_required)?,458 )?;459 } else {460 target_owner = T::CrossTokenAddressMapping::token_to_address(461 target_collection_id,462 target_nft_id.into(),463 );464 }465 }466 }467468 let src_nft_budget = budget::Value::new(NESTING_BUDGET);469470 <PalletNft<T>>::transfer_from(471 &collection,472 &cross_sender,473 &from,474 &target_owner,475 nft_id,476 &src_nft_budget,477 )478 .map_err(Self::map_unique_err_to_proxy)?;479480 Self::deposit_event(Event::NFTSent {481 sender,482 recipient: new_owner,483 collection_id: rmrk_collection_id,484 nft_id: rmrk_nft_id,485 approval_required,486 });487488 Ok(())489 }490491 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]492 #[transactional]493 pub fn accept_nft(494 origin: OriginFor<T>,495 rmrk_collection_id: RmrkCollectionId,496 rmrk_nft_id: RmrkNftId,497 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,498 ) -> DispatchResult {499 let sender = ensure_signed(origin.clone())?;500 let cross_sender = T::CrossAccountId::from_sub(sender.clone());501502 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;503 let nft_id = rmrk_nft_id.into();504505 let collection =506 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;507508 let new_cross_owner = match new_owner {509 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {510 T::CrossAccountId::from_sub(account_id.clone())511 }512 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(513 target_collection_id,514 target_nft_id,515 ) => {516 let target_collection_id = Self::unique_collection_id(target_collection_id)?;517518 T::CrossTokenAddressMapping::token_to_address(519 target_collection_id,520 TokenId(target_nft_id),521 )522 }523 };524525 let budget = budget::Value::new(NESTING_BUDGET);526527 <PalletNft<T>>::transfer(&collection, &cross_sender, &new_cross_owner, nft_id, &budget)528 .map_err(|err| {529 if err == <CommonError<T>>::OnlyOwnerAllowedToNest.into() {530 <Error<T>>::CannotAcceptNonOwnedNft.into()531 } else {532 Self::map_unique_err_to_proxy(err)533 }534 })?;535536 <PalletNft<T>>::set_scoped_token_property(537 collection.id,538 nft_id,539 PropertyScope::Rmrk,540 Self::rmrk_property(PendingNftAccept, &false)?,541 )?;542543 Self::deposit_event(Event::NFTAccepted {544 sender,545 recipient: new_owner,546 collection_id: rmrk_collection_id,547 nft_id: rmrk_nft_id,548 });549550 Ok(())551 }552553 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]554 #[transactional]555 pub fn reject_nft(556 origin: OriginFor<T>,557 rmrk_collection_id: RmrkCollectionId,558 rmrk_nft_id: RmrkNftId,559 ) -> DispatchResult {560 let sender = ensure_signed(origin)?;561 let cross_sender = T::CrossAccountId::from_sub(sender.clone());562563 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;564 let nft_id = rmrk_nft_id.into();565566 Self::destroy_nft(cross_sender, collection_id, nft_id).map_err(|err| {567 if err == <CommonError<T>>::NoPermission.into()568 || err == <CommonError<T>>::ApprovedValueTooLow.into()569 {570 <Error<T>>::CannotRejectNonOwnedNft.into()571 } else {572 Self::map_unique_err_to_proxy(err)573 }574 })?;575576 Self::deposit_event(Event::NFTRejected {577 sender,578 collection_id: rmrk_collection_id,579 nft_id: rmrk_nft_id,580 });581582 Ok(())583 }584585 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]586 #[transactional]587 pub fn accept_resource(588 origin: OriginFor<T>,589 rmrk_collection_id: RmrkCollectionId,590 rmrk_nft_id: RmrkNftId,591 rmrk_resource_id: RmrkResourceId,592 ) -> DispatchResult {593 let sender = ensure_signed(origin)?;594 let cross_sender = T::CrossAccountId::from_sub(sender);595596 let collection_id = Self::unique_collection_id(rmrk_collection_id)597 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;598599 let nft_id = rmrk_nft_id.into();600 let resource_id = rmrk_resource_id.into();601602 let budget = budget::Value::new(NESTING_BUDGET);603604 let nft_owner =605 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)606 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;607608 let resource_collection_id: CollectionId =609 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)610 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;611612 let is_pending: bool = Self::get_nft_property_decoded(613 resource_collection_id,614 resource_id,615 PendingResourceAccept,616 )617 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;618619 ensure!(is_pending, <Error<T>>::ResourceNotPending);620621 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);622623 <PalletNft<T>>::set_scoped_token_property(624 resource_collection_id,625 rmrk_resource_id.into(),626 PropertyScope::Rmrk,627 Self::rmrk_property(PendingResourceAccept, &false)?,628 )?;629630 Ok(())631 }632633 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]634 #[transactional]635 pub fn accept_resource_removal(636 origin: OriginFor<T>,637 rmrk_collection_id: RmrkCollectionId,638 rmrk_nft_id: RmrkNftId,639 rmrk_resource_id: RmrkResourceId,640 ) -> DispatchResult {641 let sender = ensure_signed(origin)?;642 let cross_sender = T::CrossAccountId::from_sub(sender);643644 let collection_id = Self::unique_collection_id(rmrk_collection_id)645 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;646647 let nft_id = rmrk_nft_id.into();648 let resource_id = rmrk_resource_id.into();649650 let budget = budget::Value::new(NESTING_BUDGET);651652 let nft_owner =653 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)654 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;655656 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);657658 let resource_collection_id: CollectionId =659 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)660 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;661662 let is_pending: bool = Self::get_nft_property_decoded(663 resource_collection_id,664 resource_id,665 PendingResourceRemoval,666 )667 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;668669 ensure!(is_pending, <Error<T>>::ResourceNotPending);670671 let resource_collection = Self::get_typed_nft_collection(672 resource_collection_id,673 misc::CollectionType::Resource,674 )?;675676 <PalletNft<T>>::burn(&resource_collection, &cross_sender, rmrk_resource_id.into())677 .map_err(Self::map_unique_err_to_proxy)?;678679 Ok(())680 }681682 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]683 #[transactional]684 pub fn set_property(685 origin: OriginFor<T>,686 #[pallet::compact] rmrk_collection_id: RmrkCollectionId,687 maybe_nft_id: Option<RmrkNftId>,688 key: RmrkKeyString,689 value: RmrkValueString,690 ) -> DispatchResult {691 let sender = ensure_signed(origin)?;692 let sender = T::CrossAccountId::from_sub(sender);693694 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;695 let budget = budget::Value::new(NESTING_BUDGET);696697 match maybe_nft_id {698 Some(nft_id) => {699 let token_id: TokenId = nft_id.into();700701 Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;702 Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;703704 <PalletNft<T>>::set_scoped_token_property(705 collection_id,706 token_id,707 PropertyScope::Rmrk,708 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,709 )?;710 }711 None => {712 let collection = Self::get_typed_nft_collection(713 collection_id,714 misc::CollectionType::Regular,715 )?;716717 Self::check_collection_owner(&collection, &sender)?;718719 <PalletCommon<T>>::set_scoped_collection_property(720 collection_id,721 PropertyScope::Rmrk,722 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,723 )?;724 }725 }726727 Self::deposit_event(Event::PropertySet {728 collection_id: rmrk_collection_id,729 maybe_nft_id,730 key,731 value,732 });733734 Ok(())735 }736737 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]738 #[transactional]739 pub fn set_priority(740 origin: OriginFor<T>,741 rmrk_collection_id: RmrkCollectionId,742 rmrk_nft_id: RmrkNftId,743 priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,744 ) -> DispatchResult {745 let sender = ensure_signed(origin)?;746 let sender = T::CrossAccountId::from_sub(sender);747748 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;749 let nft_id = rmrk_nft_id.into();750 let budget = budget::Value::new(NESTING_BUDGET);751752 Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;753 Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;754755 <PalletNft<T>>::set_scoped_token_property(756 collection_id,757 nft_id,758 PropertyScope::Rmrk,759 Self::rmrk_property(ResourcePriorities, &priorities.into_inner())?,760 )?;761762 Ok(())763 }764765 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]766 #[transactional]767 pub fn add_basic_resource(768 origin: OriginFor<T>,769 collection_id: RmrkCollectionId,770 nft_id: RmrkNftId,771 resource: RmrkBasicResource,772 ) -> DispatchResult {773 let sender = ensure_signed(origin.clone())?;774775 let resource_id = Self::resource_add(776 sender,777 Self::unique_collection_id(collection_id)?,778 nft_id.into(),779 [780 Self::rmrk_property(TokenType, &NftType::Resource)?,781 Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,782 Self::rmrk_property(Src, &resource.src)?,783 Self::rmrk_property(Metadata, &resource.metadata)?,784 Self::rmrk_property(License, &resource.license)?,785 Self::rmrk_property(Thumb, &resource.thumb)?,786 ]787 .into_iter(),788 )?;789790 Self::deposit_event(Event::ResourceAdded {791 nft_id,792 resource_id,793 });794 Ok(())795 }796797 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]798 #[transactional]799 pub fn add_composable_resource(800 origin: OriginFor<T>,801 collection_id: RmrkCollectionId,802 nft_id: RmrkNftId,803 _resource_id: RmrkBoundedResource,804 resource: RmrkComposableResource,805 ) -> DispatchResult {806 let sender = ensure_signed(origin.clone())?;807808 let resource_id = Self::resource_add(809 sender,810 Self::unique_collection_id(collection_id)?,811 nft_id.into(),812 [813 Self::rmrk_property(TokenType, &NftType::Resource)?,814 Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,815 Self::rmrk_property(Parts, &resource.parts)?,816 Self::rmrk_property(Base, &resource.base)?,817 Self::rmrk_property(Src, &resource.src)?,818 Self::rmrk_property(Metadata, &resource.metadata)?,819 Self::rmrk_property(License, &resource.license)?,820 Self::rmrk_property(Thumb, &resource.thumb)?,821 ]822 .into_iter(),823 )?;824825 Self::deposit_event(Event::ResourceAdded {826 nft_id,827 resource_id,828 });829 Ok(())830 }831832 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]833 #[transactional]834 pub fn add_slot_resource(835 origin: OriginFor<T>,836 collection_id: RmrkCollectionId,837 nft_id: RmrkNftId,838 resource: RmrkSlotResource,839 ) -> DispatchResult {840 let sender = ensure_signed(origin.clone())?;841842 let resource_id = Self::resource_add(843 sender,844 Self::unique_collection_id(collection_id)?,845 nft_id.into(),846 [847 Self::rmrk_property(TokenType, &NftType::Resource)?,848 Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,849 Self::rmrk_property(Base, &resource.base)?,850 Self::rmrk_property(Src, &resource.src)?,851 Self::rmrk_property(Metadata, &resource.metadata)?,852 Self::rmrk_property(Slot, &resource.slot)?,853 Self::rmrk_property(License, &resource.license)?,854 Self::rmrk_property(Thumb, &resource.thumb)?,855 ]856 .into_iter(),857 )?;858859 Self::deposit_event(Event::ResourceAdded {860 nft_id,861 resource_id,862 });863 Ok(())864 }865866 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]867 #[transactional]868 pub fn remove_resource(869 origin: OriginFor<T>,870 collection_id: RmrkCollectionId,871 nft_id: RmrkNftId,872 resource_id: RmrkResourceId,873 ) -> DispatchResult {874 let sender = ensure_signed(origin.clone())?;875876 Self::resource_remove(877 sender,878 Self::unique_collection_id(collection_id)?,879 nft_id.into(),880 resource_id.into(),881 )?;882883 Self::deposit_event(Event::ResourceRemoval {884 nft_id,885 resource_id,886 });887 Ok(())888 }889 }890}891892impl<T: Config> Pallet<T> {893 pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {894 let key = rmrk_key.to_key::<T>()?;895896 let scoped_key = PropertyScope::Rmrk897 .apply(key)898 .map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;899900 Ok(scoped_key)901 }902903 // todo think about renaming these904 pub fn rmrk_property<E: Encode>(905 rmrk_key: RmrkProperty,906 value: &E,907 ) -> Result<Property, DispatchError> {908 let key = rmrk_key.to_key::<T>()?;909910 let value = value911 .encode()912 .try_into()913 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;914915 let property = Property { key, value };916917 Ok(property)918 }919920 pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {921 vec.decode()922 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())923 }924925 pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>926 where927 BoundedVec<u8, S>: TryFrom<Vec<u8>>,928 {929 vec.rebind()930 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())931 }932933 fn init_collection(934 sender: T::CrossAccountId,935 data: CreateCollectionData<T::AccountId>,936 properties: impl Iterator<Item = Property>,937 ) -> Result<CollectionId, DispatchError> {938 let collection_id = <PalletNft<T>>::init_collection(sender, data);939940 if let Err(DispatchError::Arithmetic(_)) = &collection_id {941 return Err(<Error<T>>::NoAvailableCollectionId.into());942 }943944 <PalletCommon<T>>::set_scoped_collection_properties(945 collection_id?,946 PropertyScope::Rmrk,947 properties,948 )?;949950 collection_id951 }952953 pub fn create_nft(954 sender: &T::CrossAccountId,955 owner: &T::CrossAccountId,956 collection: &NonfungibleHandle<T>,957 properties: impl Iterator<Item = Property>,958 ) -> Result<TokenId, DispatchError> {959 let data = CreateNftExData {960 properties: BoundedVec::default(),961 owner: owner.clone(),962 };963964 let budget = budget::Value::new(NESTING_BUDGET);965966 <PalletNft<T>>::create_item(collection, sender, data, &budget)?;967968 let nft_id = <PalletNft<T>>::current_token_id(collection.id);969970 <PalletNft<T>>::set_scoped_token_properties(971 collection.id,972 nft_id,973 PropertyScope::Rmrk,974 properties,975 )?;976977 Ok(nft_id)978 }979980 fn destroy_nft(981 sender: T::CrossAccountId,982 collection_id: CollectionId,983 token_id: TokenId,984 ) -> DispatchResult {985 let collection =986 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;987988 let token_data =989 <TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;990991 let from = token_data.owner;992993 let budget = budget::Value::new(NESTING_BUDGET);994995 <PalletNft<T>>::burn_from(&collection, &sender, &from, token_id, &budget)996 }997998 fn resource_add(999 sender: T::AccountId,1000 collection_id: CollectionId,1001 token_id: TokenId,1002 resource_properties: impl Iterator<Item = Property>,1003 ) -> Result<RmrkResourceId, DispatchError> {1004 let collection =1005 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1006 ensure!(collection.owner == sender, Error::<T>::NoPermission);10071008 let sender = T::CrossAccountId::from_sub(sender);1009 let budget = budget::Value::new(NESTING_BUDGET);10101011 let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, token_id, &budget)1012 .map_err(Self::map_unique_err_to_proxy)?;10131014 let pending = sender != nft_owner;10151016 let resource_collection_id: CollectionId =1017 Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;1018 let resource_collection =1019 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;10201021 // todo probably add extra connections to bases, slots, etc., when RMRK starts to use them10221023 let resource_id = Self::create_nft(1024 &sender,1025 &nft_owner,1026 &resource_collection,1027 resource_properties.chain(1028 [1029 Self::rmrk_property(PendingResourceAccept, &pending)?,1030 Self::rmrk_property(PendingResourceRemoval, &false)?,1031 ]1032 .into_iter(),1033 ),1034 )1035 .map_err(|err| match err {1036 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),1037 err => Self::map_unique_err_to_proxy(err),1038 })?;10391040 Ok(resource_id.0)1041 }10421043 fn resource_remove(1044 sender: T::AccountId,1045 collection_id: CollectionId,1046 nft_id: TokenId,1047 resource_id: TokenId,1048 ) -> DispatchResult {1049 let collection =1050 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1051 ensure!(collection.owner == sender, Error::<T>::NoPermission);10521053 let resource_collection_id: CollectionId =1054 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;1055 let resource_collection =1056 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;1057 ensure!(1058 <PalletNft<T>>::token_exists(&resource_collection, resource_id),1059 Error::<T>::ResourceDoesntExist1060 );10611062 let budget = up_data_structs::budget::Value::new(10);1063 let topmost_owner =1064 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;10651066 let sender = T::CrossAccountId::from_sub(sender);1067 if topmost_owner == sender {1068 <PalletNft<T>>::burn(&resource_collection, &sender, resource_id)1069 .map_err(Self::map_unique_err_to_proxy)?;1070 } else {1071 <PalletNft<T>>::set_scoped_token_property(1072 resource_collection_id,1073 resource_id,1074 PropertyScope::Rmrk,1075 Self::rmrk_property(PendingResourceRemoval, &true)?,1076 )?;1077 }10781079 Ok(())1080 }10811082 fn change_collection_owner(1083 collection_id: CollectionId,1084 collection_type: misc::CollectionType,1085 sender: T::AccountId,1086 new_owner: T::AccountId,1087 ) -> DispatchResult {1088 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1089 Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;10901091 let mut collection = collection.into_inner();10921093 collection.owner = new_owner;1094 collection.save()1095 }10961097 fn check_collection_owner(1098 collection: &NonfungibleHandle<T>,1099 account: &T::CrossAccountId,1100 ) -> DispatchResult {1101 collection1102 .check_is_owner(account)1103 .map_err(Self::map_unique_err_to_proxy)1104 }11051106 pub fn last_collection_idx() -> RmrkCollectionId {1107 <CollectionIndex<T>>::get()1108 }11091110 pub fn unique_collection_id(1111 rmrk_collection_id: RmrkCollectionId,1112 ) -> Result<CollectionId, DispatchError> {1113 <UniqueCollectionId<T>>::try_get(rmrk_collection_id)1114 .map_err(|_| <Error<T>>::CollectionUnknown.into())1115 }11161117 pub fn rmrk_collection_id(1118 unique_collection_id: CollectionId,1119 ) -> Result<RmrkCollectionId, DispatchError> {1120 <RmrkInernalCollectionId<T>>::try_get(unique_collection_id)1121 .map_err(|_| <Error<T>>::CollectionUnknown.into())1122 }11231124 pub fn get_nft_collection(1125 collection_id: CollectionId,1126 ) -> Result<NonfungibleHandle<T>, DispatchError> {1127 let collection = <CollectionHandle<T>>::try_get(collection_id)1128 .map_err(|_| <Error<T>>::CollectionUnknown)?;11291130 match collection.mode {1131 CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1132 _ => Err(<Error<T>>::CollectionUnknown.into()),1133 }1134 }11351136 pub fn collection_exists(collection_id: CollectionId) -> bool {1137 <CollectionHandle<T>>::try_get(collection_id).is_ok()1138 }11391140 pub fn get_collection_property(1141 collection_id: CollectionId,1142 key: RmrkProperty,1143 ) -> Result<PropertyValue, DispatchError> {1144 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1145 .get(&Self::rmrk_property_key(key)?)1146 .ok_or(<Error<T>>::CollectionUnknown)?1147 .clone();11481149 Ok(collection_property)1150 }11511152 pub fn get_collection_property_decoded<V: Decode>(1153 collection_id: CollectionId,1154 key: RmrkProperty,1155 ) -> Result<V, DispatchError> {1156 Self::decode_property(Self::get_collection_property(collection_id, key)?)1157 }11581159 pub fn get_collection_type(1160 collection_id: CollectionId,1161 ) -> Result<misc::CollectionType, DispatchError> {1162 Self::get_collection_property_decoded(collection_id, CollectionType)1163 .map_err(|_| <Error<T>>::CorruptedCollectionType.into())1164 }11651166 pub fn ensure_collection_type(1167 collection_id: CollectionId,1168 collection_type: misc::CollectionType,1169 ) -> DispatchResult {1170 let actual_type = Self::get_collection_type(collection_id)?;1171 ensure!(1172 actual_type == collection_type,1173 <CommonError<T>>::NoPermission1174 );11751176 Ok(())1177 }11781179 pub fn get_typed_nft_collection(1180 collection_id: CollectionId,1181 collection_type: misc::CollectionType,1182 ) -> Result<NonfungibleHandle<T>, DispatchError> {1183 Self::ensure_collection_type(collection_id, collection_type)?;11841185 Self::get_nft_collection(collection_id)1186 }11871188 pub fn get_typed_nft_collection_mapped(1189 rmrk_collection_id: RmrkCollectionId,1190 collection_type: misc::CollectionType,1191 ) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1192 let unique_collection_id = match collection_type {1193 misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1194 _ => rmrk_collection_id.into(),1195 };11961197 let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;11981199 Ok((collection, unique_collection_id))1200 }12011202 pub fn get_nft_property(1203 collection_id: CollectionId,1204 nft_id: TokenId,1205 key: RmrkProperty,1206 ) -> Result<PropertyValue, DispatchError> {1207 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1208 .get(&Self::rmrk_property_key(key)?)1209 .ok_or(<Error<T>>::NoAvailableNftId)? // todo replace with better error?1210 .clone();12111212 Ok(nft_property)1213 }12141215 pub fn get_nft_property_decoded<V: Decode>(1216 collection_id: CollectionId,1217 nft_id: TokenId,1218 key: RmrkProperty,1219 ) -> Result<V, DispatchError> {1220 Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)1221 }12221223 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1224 <TokenData<T>>::contains_key((collection_id, nft_id))1225 }12261227 pub fn get_nft_type(1228 collection_id: CollectionId,1229 token_id: TokenId,1230 ) -> Result<NftType, DispatchError> {1231 Self::get_nft_property_decoded(collection_id, token_id, TokenType)1232 .map_err(|_| <Error<T>>::NoAvailableNftId.into())1233 }12341235 pub fn ensure_nft_type(1236 collection_id: CollectionId,1237 token_id: TokenId,1238 nft_type: NftType,1239 ) -> DispatchResult {1240 let actual_type = Self::get_nft_type(collection_id, token_id)?;1241 ensure!(actual_type == nft_type, <Error<T>>::NoPermission);12421243 Ok(())1244 }12451246 pub fn ensure_nft_owner(1247 collection_id: CollectionId,1248 token_id: TokenId,1249 possible_owner: &T::CrossAccountId,1250 nesting_budget: &dyn budget::Budget,1251 ) -> DispatchResult {1252 let is_owned = <PalletStructure<T>>::check_indirectly_owned(1253 possible_owner.clone(),1254 collection_id,1255 token_id,1256 None,1257 nesting_budget,1258 )1259 .map_err(Self::map_unique_err_to_proxy)?;12601261 ensure!(is_owned, <Error<T>>::NoPermission);12621263 Ok(())1264 }12651266 pub fn filter_user_properties<Key, Value, R, Mapper>(1267 collection_id: CollectionId,1268 token_id: Option<TokenId>,1269 filter_keys: Option<Vec<RmrkPropertyKey>>,1270 mapper: Mapper,1271 ) -> Result<Vec<R>, DispatchError>1272 where1273 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1274 Value: Decode + Default,1275 Mapper: Fn(Key, Value) -> R,1276 {1277 filter_keys1278 .map(|keys| {1279 let properties = keys1280 .into_iter()1281 .filter_map(|key| {1282 let key: Key = key.try_into().ok()?;12831284 let value = match token_id {1285 Some(token_id) => Self::get_nft_property_decoded(1286 collection_id,1287 token_id,1288 UserProperty(key.as_ref()),1289 ),1290 None => Self::get_collection_property_decoded(1291 collection_id,1292 UserProperty(key.as_ref()),1293 ),1294 }1295 .ok()?;12961297 Some(mapper(key, value))1298 })1299 .collect();13001301 Ok(properties)1302 })1303 .unwrap_or_else(|| {1304 let properties =1305 Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();13061307 Ok(properties)1308 })1309 }13101311 pub fn iterate_user_properties<Key, Value, R, Mapper>(1312 collection_id: CollectionId,1313 token_id: Option<TokenId>,1314 mapper: Mapper,1315 ) -> Result<impl Iterator<Item = R>, DispatchError>1316 where1317 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1318 Value: Decode + Default,1319 Mapper: Fn(Key, Value) -> R,1320 {1321 let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;13221323 let properties = match token_id {1324 Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1325 None => <PalletCommon<T>>::collection_properties(collection_id),1326 };13271328 let properties = properties.into_iter().filter_map(move |(key, value)| {1329 let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;13301331 let key: Key = key.to_vec().try_into().ok()?;1332 let value: Value = value.decode().ok()?;13331334 Some(mapper(key, value))1335 });13361337 Ok(properties)1338 }13391340 fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1341 map_unique_err_to_proxy! {1342 match err {1343 CommonError::NoPermission => NoPermission,1344 CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1345 CommonError::PublicMintingNotAllowed => NoPermission,1346 CommonError::TokenNotFound => NoAvailableNftId,1347 CommonError::ApprovedValueTooLow => NoPermission,1348 CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1349 StructureError::TokenNotFound => NoAvailableNftId,1350 StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1351 }1352 }1353 }1354}