difftreelog
cargo fmt
in: master
2 files changed
pallets/proxy-rmrk-core/src/benchmarking.rsdiffbeforeafterboth--- a/pallets/proxy-rmrk-core/src/benchmarking.rs
+++ b/pallets/proxy-rmrk-core/src/benchmarking.rs
@@ -54,16 +54,27 @@
}
fn create_max_collection<T: Config>(owner: &T::AccountId) -> DispatchResult {
- <T as pallet_common::Config>::Currency::deposit_creating(owner, T::CollectionCreationPrice::get());
+ <T as pallet_common::Config>::Currency::deposit_creating(
+ owner,
+ T::CollectionCreationPrice::get(),
+ );
let metadata = create_data();
let max = None;
let symbol = create_data();
- <Pallet<T>>::create_collection(RawOrigin::Signed(owner.clone()).into(), metadata, max, symbol)
+ <Pallet<T>>::create_collection(
+ RawOrigin::Signed(owner.clone()).into(),
+ metadata,
+ max,
+ symbol,
+ )
}
-fn create_max_nft<T: Config>(owner: &T::AccountId, collection_id: RmrkCollectionId) -> DispatchResult {
+fn create_max_nft<T: Config>(
+ owner: &T::AccountId,
+ collection_id: RmrkCollectionId,
+) -> DispatchResult {
let royalty_recipient = Some(owner.clone());
let royalty_amount = Some(Permill::from_percent(25));
let metadata = create_data();
@@ -76,20 +87,20 @@
royalty_recipient,
royalty_amount,
metadata,
- transferable
+ transferable,
)
}
struct NftBuilder {
collection_id: RmrkCollectionId,
- current_nft_id: RmrkNftId
+ current_nft_id: RmrkNftId,
}
impl NftBuilder {
fn new(collection_id: RmrkCollectionId) -> Self {
Self {
collection_id,
- current_nft_id: 0
+ current_nft_id: 0,
}
}
@@ -100,19 +111,24 @@
Ok(self.current_nft_id)
}
- fn build_tower<T: Config>(&mut self, owner: &T::AccountId, height: u32) -> Result<RmrkNftId, DispatchError> {
+ fn build_tower<T: Config>(
+ &mut self,
+ owner: &T::AccountId,
+ height: u32,
+ ) -> Result<RmrkNftId, DispatchError> {
self.build::<T>(owner)?;
let mut prev_nft_id = self.current_nft_id;
for _ in 0..height {
self.build::<T>(owner)?;
-
- let new_owner = <RmrkAccountIdOrCollectionNftTuple<T::AccountId>>::CollectionAndNftTuple(
- self.collection_id,
- prev_nft_id
- );
+ let new_owner =
+ <RmrkAccountIdOrCollectionNftTuple<T::AccountId>>::CollectionAndNftTuple(
+ self.collection_id,
+ prev_nft_id,
+ );
+
<Pallet<T>>::send(
RawOrigin::Signed(owner.clone()).into(),
self.collection_id,
@@ -140,7 +156,7 @@
destroy_collection {
let caller = account("caller", 0, SEED);
-
+
create_max_collection::<T>(&caller)?;
let collection_id = 0;
}: _(RawOrigin::Signed(caller), collection_id)
@@ -169,7 +185,7 @@
create_max_collection::<T>(&caller)?;
let collection_id = 0;
let owner = caller.clone();
-
+
let royalty_recipient = Some(caller.clone());
let royalty_amount = Some(Permill::from_percent(25));
let metadata = create_data();
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::*;3334#[cfg(feature = "runtime-benchmarks")]35pub mod benchmarking;36pub mod misc;37pub mod property;38pub mod weights;3940pub type SelfWeightOf<T> = <T as Config>::WeightInfo;4142use weights::WeightInfo;43use misc::*;44pub use property::*;4546use RmrkProperty::*;4748pub const NESTING_BUDGET: u32 = 5;4950#[frame_support::pallet]51pub mod pallet {52 use super::*;53 use pallet_evm::account;5455 #[pallet::config]56 pub trait Config:57 frame_system::Config + pallet_common::Config + pallet_nonfungible::Config + account::Config58 {59 type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;60 type WeightInfo: WeightInfo;61 }6263 #[pallet::storage]64 #[pallet::getter(fn collection_index)]65 pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;6667 #[pallet::storage]68 pub type UniqueCollectionId<T: Config> =69 StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;7071 #[pallet::storage]72 pub type RmrkInernalCollectionId<T: Config> =73 StorageMap<_, Twox64Concat, CollectionId, RmrkCollectionId, ValueQuery>;7475 #[pallet::pallet]76 #[pallet::generate_store(pub(super) trait Store)]77 pub struct Pallet<T>(_);7879 #[pallet::event]80 #[pallet::generate_deposit(pub(super) fn deposit_event)]81 pub enum Event<T: Config> {82 CollectionCreated {83 issuer: T::AccountId,84 collection_id: RmrkCollectionId,85 },86 CollectionDestroyed {87 issuer: T::AccountId,88 collection_id: RmrkCollectionId,89 },90 IssuerChanged {91 old_issuer: T::AccountId,92 new_issuer: T::AccountId,93 collection_id: RmrkCollectionId,94 },95 CollectionLocked {96 issuer: T::AccountId,97 collection_id: RmrkCollectionId,98 },99 NftMinted {100 owner: T::AccountId,101 collection_id: RmrkCollectionId,102 nft_id: RmrkNftId,103 },104 NFTBurned {105 owner: T::AccountId,106 nft_id: RmrkNftId,107 },108 NFTSent {109 sender: T::AccountId,110 recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,111 collection_id: RmrkCollectionId,112 nft_id: RmrkNftId,113 approval_required: bool,114 },115 NFTAccepted {116 sender: T::AccountId,117 recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,118 collection_id: RmrkCollectionId,119 nft_id: RmrkNftId,120 },121 NFTRejected {122 sender: T::AccountId,123 collection_id: RmrkCollectionId,124 nft_id: RmrkNftId,125 },126 PropertySet {127 collection_id: RmrkCollectionId,128 maybe_nft_id: Option<RmrkNftId>,129 key: RmrkKeyString,130 value: RmrkValueString,131 },132 ResourceAdded {133 nft_id: RmrkNftId,134 resource_id: RmrkResourceId,135 },136 ResourceRemoval {137 nft_id: RmrkNftId,138 resource_id: RmrkResourceId,139 },140 ResourceAccepted {141 nft_id: RmrkNftId,142 resource_id: RmrkResourceId,143 },144 ResourceRemovalAccepted {145 nft_id: RmrkNftId,146 resource_id: RmrkResourceId,147 },148 PrioritySet {149 collection_id: RmrkCollectionId,150 nft_id: RmrkNftId,151 },152 }153154 #[pallet::error]155 pub enum Error<T> {156 /* Unique-specific events */157 CorruptedCollectionType,158 NftTypeEncodeError,159 RmrkPropertyKeyIsTooLong,160 RmrkPropertyValueIsTooLong,161 UnableToDecodeRmrkData,162163 /* RMRK compatible events */164 CollectionNotEmpty,165 NoAvailableCollectionId,166 NoAvailableNftId,167 CollectionUnknown,168 NoPermission,169 NonTransferable,170 CollectionFullOrLocked,171 ResourceDoesntExist,172 CannotSendToDescendentOrSelf,173 CannotAcceptNonOwnedNft,174 CannotRejectNonOwnedNft,175 ResourceNotPending,176 }177178 #[pallet::call]179 impl<T: Config> Pallet<T> {180 /// Create a collection181 #[transactional]182 #[pallet::weight(<SelfWeightOf<T>>::create_collection())]183 pub fn create_collection(184 origin: OriginFor<T>,185 metadata: RmrkString,186 max: Option<u32>,187 symbol: RmrkCollectionSymbol,188 ) -> DispatchResult {189 let sender = ensure_signed(origin)?;190191 let limits = CollectionLimits {192 owner_can_transfer: Some(false),193 token_limit: max,194 ..Default::default()195 };196197 let data = CreateCollectionData {198 limits: Some(limits),199 token_prefix: symbol200 .into_inner()201 .try_into()202 .map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,203 permissions: Some(CollectionPermissions {204 nesting: Some(NestingPermissions {205 token_owner: true,206 admin: false,207 restricted: None,208209 permissive: false,210 }),211 ..Default::default()212 }),213 ..Default::default()214 };215216 let unique_collection_id = Self::init_collection(217 T::CrossAccountId::from_sub(sender.clone()),218 data,219 [220 Self::rmrk_property(Metadata, &metadata)?,221 Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,222 ]223 .into_iter(),224 )?;225 let rmrk_collection_id = <CollectionIndex<T>>::get();226227 <UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);228 <RmrkInernalCollectionId<T>>::insert(unique_collection_id, rmrk_collection_id);229230 <CollectionIndex<T>>::mutate(|n| *n += 1);231232 Self::deposit_event(Event::CollectionCreated {233 issuer: sender,234 collection_id: rmrk_collection_id,235 });236237 Ok(())238 }239240 /// destroy collection241 #[transactional]242 #[pallet::weight(<SelfWeightOf<T>>::destroy_collection())]243 pub fn destroy_collection(244 origin: OriginFor<T>,245 collection_id: RmrkCollectionId,246 ) -> DispatchResult {247 let sender = ensure_signed(origin)?;248 let cross_sender = T::CrossAccountId::from_sub(sender.clone());249250 let collection = Self::get_typed_nft_collection(251 Self::unique_collection_id(collection_id)?,252 misc::CollectionType::Regular,253 )?;254 collection.check_is_external()?;255256 <PalletNft<T>>::destroy_collection(collection, &cross_sender)257 .map_err(Self::map_unique_err_to_proxy)?;258259 Self::deposit_event(Event::CollectionDestroyed {260 issuer: sender,261 collection_id,262 });263264 Ok(())265 }266267 /// Change the issuer of a collection268 ///269 /// Parameters:270 /// - `origin`: sender of the transaction271 /// - `collection_id`: collection id of the nft to change issuer of272 /// - `new_issuer`: Collection's new issuer273 #[transactional]274 #[pallet::weight(<SelfWeightOf<T>>::change_collection_issuer())]275 pub fn change_collection_issuer(276 origin: OriginFor<T>,277 collection_id: RmrkCollectionId,278 new_issuer: <T::Lookup as StaticLookup>::Source,279 ) -> DispatchResult {280 let sender = ensure_signed(origin)?;281282 let collection = Self::get_nft_collection(Self::unique_collection_id(collection_id)?)?;283 collection.check_is_external()?;284285 let new_issuer = T::Lookup::lookup(new_issuer)?;286287 Self::change_collection_owner(288 Self::unique_collection_id(collection_id)?,289 misc::CollectionType::Regular,290 sender.clone(),291 new_issuer.clone(),292 )?;293294 Self::deposit_event(Event::IssuerChanged {295 old_issuer: sender,296 new_issuer,297 collection_id,298 });299300 Ok(())301 }302303 /// lock collection304 #[transactional]305 #[pallet::weight(<SelfWeightOf<T>>::lock_collection())]306 pub fn lock_collection(307 origin: OriginFor<T>,308 collection_id: RmrkCollectionId,309 ) -> DispatchResult {310 let sender = ensure_signed(origin)?;311 let cross_sender = T::CrossAccountId::from_sub(sender.clone());312313 let collection = Self::get_typed_nft_collection(314 Self::unique_collection_id(collection_id)?,315 misc::CollectionType::Regular,316 )?;317 collection.check_is_external()?;318319 Self::check_collection_owner(&collection, &cross_sender)?;320321 let token_count = collection.total_supply();322323 let mut collection = collection.into_inner();324 collection.limits.token_limit = Some(token_count);325 collection.save()?;326327 Self::deposit_event(Event::CollectionLocked {328 issuer: sender,329 collection_id,330 });331332 Ok(())333 }334335 /// Mints an NFT in the specified collection336 /// Sets metadata and the royalty attribute337 ///338 /// Parameters:339 /// - `collection_id`: The class of the asset to be minted.340 /// - `nft_id`: The nft value of the asset to be minted.341 /// - `recipient`: Receiver of the royalty342 /// - `royalty`: Permillage reward from each trade for the Recipient343 /// - `metadata`: Arbitrary data about an nft, e.g. IPFS hash344 /// - `transferable`: Ability to transfer this NFT345 #[transactional]346 #[pallet::weight(<SelfWeightOf<T>>::mint_nft())]347 pub fn mint_nft(348 origin: OriginFor<T>,349 owner: T::AccountId,350 collection_id: RmrkCollectionId,351 recipient: Option<T::AccountId>,352 royalty_amount: Option<Permill>,353 metadata: RmrkString,354 transferable: bool,355 ) -> DispatchResult {356 let sender = ensure_signed(origin)?;357 let sender = T::CrossAccountId::from_sub(sender);358 let cross_owner = T::CrossAccountId::from_sub(owner.clone());359360 let collection = Self::get_typed_nft_collection(361 Self::unique_collection_id(collection_id)?,362 misc::CollectionType::Regular,363 )?;364 collection.check_is_external()?;365366 let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {367 recipient: recipient.unwrap_or_else(|| owner.clone()),368 amount,369 });370371 let nft_id = Self::create_nft(372 &sender,373 &cross_owner,374 &collection,375 [376 Self::rmrk_property(TokenType, &NftType::Regular)?,377 Self::rmrk_property(Transferable, &transferable)?,378 Self::rmrk_property(PendingNftAccept, &false)?,379 Self::rmrk_property(RoyaltyInfo, &royalty_info)?,380 Self::rmrk_property(Metadata, &metadata)?,381 Self::rmrk_property(Equipped, &false)?,382 Self::rmrk_property(ResourceCollection, &None::<CollectionId>)?,383 Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,384 ]385 .into_iter(),386 )387 .map_err(|err| match err {388 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),389 err => Self::map_unique_err_to_proxy(err),390 })?;391392 Self::deposit_event(Event::NftMinted {393 owner,394 collection_id,395 nft_id: nft_id.0,396 });397398 Ok(())399 }400401 /// burn nft402 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]403 #[transactional]404 pub fn burn_nft(405 origin: OriginFor<T>,406 collection_id: RmrkCollectionId,407 nft_id: RmrkNftId,408 max_burns: u32,409 ) -> DispatchResult {410 let sender = ensure_signed(origin)?;411 let cross_sender = T::CrossAccountId::from_sub(sender.clone());412413 let collection = Self::get_typed_nft_collection(414 Self::unique_collection_id(collection_id)?,415 misc::CollectionType::Regular,416 )?;417 collection.check_is_external()?;418419 Self::destroy_nft(420 cross_sender,421 Self::unique_collection_id(collection_id)?,422 nft_id.into(),423 max_burns,424 <Error<T>>::NoPermission,425 )426 .map_err(|err| Self::map_unique_err_to_proxy(err.error))?;427428 Self::deposit_event(Event::NFTBurned {429 owner: sender,430 nft_id,431 });432433 Ok(())434 }435436 /// Transfers a NFT from an Account or NFT A to another Account or NFT B437 ///438 /// Parameters:439 /// - `origin`: sender of the transaction440 /// - `rmrk_collection_id`: collection id of the nft to be transferred441 /// - `rmrk_nft_id`: nft id of the nft to be transferred442 /// - `new_owner`: new owner of the nft which can be either an account or a NFT443 #[transactional]444 #[pallet::weight(<SelfWeightOf<T>>::send())]445 pub fn send(446 origin: OriginFor<T>,447 rmrk_collection_id: RmrkCollectionId,448 rmrk_nft_id: RmrkNftId,449 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,450 ) -> DispatchResult {451 let sender = ensure_signed(origin.clone())?;452 let cross_sender = T::CrossAccountId::from_sub(sender.clone());453454 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;455 let nft_id = rmrk_nft_id.into();456457 let collection =458 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;459 collection.check_is_external()?;460461 let token_data =462 <TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;463464 let from = token_data.owner;465466 ensure!(467 Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,468 <Error<T>>::NonTransferable469 );470471 ensure!(472 !Self::get_nft_property_decoded(473 collection_id,474 nft_id,475 RmrkProperty::PendingNftAccept476 )?,477 <Error<T>>::NoPermission478 );479480 let target_owner;481 let approval_required;482483 match new_owner {484 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {485 target_owner = T::CrossAccountId::from_sub(account_id.clone());486 approval_required = false;487 }488 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(489 target_collection_id,490 target_nft_id,491 ) => {492 let target_collection_id = Self::unique_collection_id(target_collection_id)?;493494 let target_nft_budget = budget::Value::new(NESTING_BUDGET);495496 let target_nft_owner = <PalletStructure<T>>::get_checked_topmost_owner(497 target_collection_id,498 target_nft_id.into(),499 Some((collection_id, nft_id)),500 &target_nft_budget,501 )502 .map_err(Self::map_unique_err_to_proxy)?;503504 approval_required = cross_sender != target_nft_owner;505506 if approval_required {507 target_owner = target_nft_owner;508509 <PalletNft<T>>::set_scoped_token_property(510 collection.id,511 nft_id,512 PropertyScope::Rmrk,513 Self::rmrk_property(PendingNftAccept, &approval_required)?,514 )?;515 } else {516 target_owner = T::CrossTokenAddressMapping::token_to_address(517 target_collection_id,518 target_nft_id.into(),519 );520 }521 }522 }523524 let src_nft_budget = budget::Value::new(NESTING_BUDGET);525526 <PalletNft<T>>::transfer_from(527 &collection,528 &cross_sender,529 &from,530 &target_owner,531 nft_id,532 &src_nft_budget,533 )534 .map_err(Self::map_unique_err_to_proxy)?;535536 Self::deposit_event(Event::NFTSent {537 sender,538 recipient: new_owner,539 collection_id: rmrk_collection_id,540 nft_id: rmrk_nft_id,541 approval_required,542 });543544 Ok(())545 }546547 /// Accepts an NFT sent from another account to self or owned NFT548 ///549 /// Parameters:550 /// - `origin`: sender of the transaction551 /// - `rmrk_collection_id`: collection id of the nft to be accepted552 /// - `rmrk_nft_id`: nft id of the nft to be accepted553 /// - `new_owner`: either origin's account ID or origin-owned NFT, whichever the NFT was554 /// sent to555 #[transactional]556 #[pallet::weight(<SelfWeightOf<T>>::accept_nft())]557 pub fn accept_nft(558 origin: OriginFor<T>,559 rmrk_collection_id: RmrkCollectionId,560 rmrk_nft_id: RmrkNftId,561 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,562 ) -> DispatchResult {563 let sender = ensure_signed(origin.clone())?;564 let cross_sender = T::CrossAccountId::from_sub(sender.clone());565566 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;567 let nft_id = rmrk_nft_id.into();568569 let collection =570 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;571 collection.check_is_external()?;572573 let new_cross_owner = match new_owner {574 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {575 T::CrossAccountId::from_sub(account_id.clone())576 }577 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(578 target_collection_id,579 target_nft_id,580 ) => {581 let target_collection_id = Self::unique_collection_id(target_collection_id)?;582583 T::CrossTokenAddressMapping::token_to_address(584 target_collection_id,585 TokenId(target_nft_id),586 )587 }588 };589590 let budget = budget::Value::new(NESTING_BUDGET);591592 <PalletNft<T>>::transfer(593 &collection,594 &cross_sender,595 &new_cross_owner,596 nft_id,597 &budget,598 )599 .map_err(|err| {600 if err == <CommonError<T>>::UserIsNotAllowedToNest.into() {601 <Error<T>>::CannotAcceptNonOwnedNft.into()602 } else {603 Self::map_unique_err_to_proxy(err)604 }605 })?;606607 <PalletNft<T>>::set_scoped_token_property(608 collection.id,609 nft_id,610 PropertyScope::Rmrk,611 Self::rmrk_property(PendingNftAccept, &false)?,612 )?;613614 Self::deposit_event(Event::NFTAccepted {615 sender,616 recipient: new_owner,617 collection_id: rmrk_collection_id,618 nft_id: rmrk_nft_id,619 });620621 Ok(())622 }623624 /// Rejects an NFT sent from another account to self or owned NFT625 ///626 /// Parameters:627 /// - `origin`: sender of the transaction628 /// - `rmrk_collection_id`: collection id of the nft to be accepted629 /// - `rmrk_nft_id`: nft id of the nft to be accepted630 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]631 #[transactional]632 pub fn reject_nft(633 origin: OriginFor<T>,634 rmrk_collection_id: RmrkCollectionId,635 rmrk_nft_id: RmrkNftId,636 max_burns: u32,637 ) -> DispatchResult {638 let sender = ensure_signed(origin)?;639 let cross_sender = T::CrossAccountId::from_sub(sender.clone());640641 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;642 let nft_id = rmrk_nft_id.into();643644 let collection =645 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;646 collection.check_is_external()?;647648 Self::destroy_nft(649 cross_sender,650 collection_id,651 nft_id,652 max_burns,653 <Error<T>>::CannotRejectNonOwnedNft,654 )655 .map_err(|err| Self::map_unique_err_to_proxy(err.error))?;656657 Self::deposit_event(Event::NFTRejected {658 sender,659 collection_id: rmrk_collection_id,660 nft_id: rmrk_nft_id,661 });662663 Ok(())664 }665666 /// accept the addition of a new resource to an existing NFT667 #[transactional]668 #[pallet::weight(<SelfWeightOf<T>>::accept_resource())]669 pub fn accept_resource(670 origin: OriginFor<T>,671 rmrk_collection_id: RmrkCollectionId,672 rmrk_nft_id: RmrkNftId,673 rmrk_resource_id: RmrkResourceId,674 ) -> DispatchResult {675 let sender = ensure_signed(origin)?;676 let cross_sender = T::CrossAccountId::from_sub(sender);677678 let collection_id = Self::unique_collection_id(rmrk_collection_id)679 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;680 let collection =681 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;682 collection.check_is_external()?;683684 let nft_id = rmrk_nft_id.into();685 let resource_id = rmrk_resource_id.into();686687 let budget = budget::Value::new(NESTING_BUDGET);688689 let nft_owner =690 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)691 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;692693 let resource_collection_id: Option<CollectionId> =694 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)695 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;696 697 let resource_collection_id = resource_collection_id.ok_or(<Error<T>>::ResourceDoesntExist)?;698699 let is_pending: bool = Self::get_nft_property_decoded(700 resource_collection_id,701 resource_id,702 PendingResourceAccept,703 )704 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;705706 ensure!(is_pending, <Error<T>>::ResourceNotPending);707708 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);709710 <PalletNft<T>>::set_scoped_token_property(711 resource_collection_id,712 rmrk_resource_id.into(),713 PropertyScope::Rmrk,714 Self::rmrk_property(PendingResourceAccept, &false)?,715 )?;716717 Self::deposit_event(Event::<T>::ResourceAccepted {718 nft_id: rmrk_nft_id,719 resource_id: rmrk_resource_id,720 });721722 Ok(())723 }724725 /// accept the removal of a resource of an existing NFT726 #[transactional]727 #[pallet::weight(<SelfWeightOf<T>>::accept_resource_removal())]728 pub fn accept_resource_removal(729 origin: OriginFor<T>,730 rmrk_collection_id: RmrkCollectionId,731 rmrk_nft_id: RmrkNftId,732 rmrk_resource_id: RmrkResourceId,733 ) -> DispatchResult {734 let sender = ensure_signed(origin)?;735 let cross_sender = T::CrossAccountId::from_sub(sender);736737 let collection_id = Self::unique_collection_id(rmrk_collection_id)738 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;739 let collection =740 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;741 collection.check_is_external()?;742743 let nft_id = rmrk_nft_id.into();744 let resource_id = rmrk_resource_id.into();745746 let budget = budget::Value::new(NESTING_BUDGET);747748 let nft_owner =749 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)750 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;751752 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);753754 let resource_collection_id: Option<CollectionId> =755 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)756 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;757758 let resource_collection_id = resource_collection_id.ok_or(<Error<T>>::ResourceDoesntExist)?;759760 let is_pending: bool = Self::get_nft_property_decoded(761 resource_collection_id,762 resource_id,763 PendingResourceRemoval,764 )765 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;766767 ensure!(is_pending, <Error<T>>::ResourceNotPending);768769 let resource_collection = Self::get_typed_nft_collection(770 resource_collection_id,771 misc::CollectionType::Resource,772 )?;773774 let resource_data =775 <TokenData<T>>::get((resource_collection_id, resource_id)).ok_or(<Error<T>>::ResourceDoesntExist)?;776 777 let resource_owner = resource_data.owner;778779 <PalletNft<T>>::burn(&resource_collection, &resource_owner, rmrk_resource_id.into())780 .map_err(Self::map_unique_err_to_proxy)?;781782 Self::deposit_event(Event::<T>::ResourceRemovalAccepted {783 nft_id: rmrk_nft_id,784 resource_id: rmrk_resource_id,785 });786787 Ok(())788 }789790 /// set a custom value on an NFT791 #[transactional]792 #[pallet::weight(<SelfWeightOf<T>>::set_property())]793 pub fn set_property(794 origin: OriginFor<T>,795 #[pallet::compact] rmrk_collection_id: RmrkCollectionId,796 maybe_nft_id: Option<RmrkNftId>,797 key: RmrkKeyString,798 value: RmrkValueString,799 ) -> DispatchResult {800 let sender = ensure_signed(origin)?;801 let sender = T::CrossAccountId::from_sub(sender);802803 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;804 let collection =805 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;806 collection.check_is_external()?;807808 let budget = budget::Value::new(NESTING_BUDGET);809810 match maybe_nft_id {811 Some(nft_id) => {812 let token_id: TokenId = nft_id.into();813814 Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;815 Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;816817 <PalletNft<T>>::set_scoped_token_property(818 collection_id,819 token_id,820 PropertyScope::Rmrk,821 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,822 )?;823 }824 None => {825 let collection = Self::get_typed_nft_collection(826 collection_id,827 misc::CollectionType::Regular,828 )?;829830 Self::check_collection_owner(&collection, &sender)?;831832 <PalletCommon<T>>::set_scoped_collection_property(833 collection_id,834 PropertyScope::Rmrk,835 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,836 )?;837 }838 }839840 Self::deposit_event(Event::PropertySet {841 collection_id: rmrk_collection_id,842 maybe_nft_id,843 key,844 value,845 });846847 Ok(())848 }849850 /// set a different order of resource priority851 #[transactional]852 #[pallet::weight(<SelfWeightOf<T>>::set_priority())]853 pub fn set_priority(854 origin: OriginFor<T>,855 rmrk_collection_id: RmrkCollectionId,856 rmrk_nft_id: RmrkNftId,857 priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,858 ) -> DispatchResult {859 let sender = ensure_signed(origin)?;860 let sender = T::CrossAccountId::from_sub(sender);861862 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;863 let nft_id = rmrk_nft_id.into();864865 let collection =866 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;867 collection.check_is_external()?;868869 let budget = budget::Value::new(NESTING_BUDGET);870871 Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;872 Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;873874 <PalletNft<T>>::set_scoped_token_property(875 collection_id,876 nft_id,877 PropertyScope::Rmrk,878 Self::rmrk_property(ResourcePriorities, &priorities.into_inner())?,879 )?;880881 Self::deposit_event(Event::<T>::PrioritySet {882 collection_id: rmrk_collection_id,883 nft_id: rmrk_nft_id,884 });885886 Ok(())887 }888889 /// Create basic resource890 #[transactional]891 #[pallet::weight(<SelfWeightOf<T>>::add_basic_resource())]892 pub fn add_basic_resource(893 origin: OriginFor<T>,894 rmrk_collection_id: RmrkCollectionId,895 nft_id: RmrkNftId,896 resource: RmrkBasicResource,897 ) -> DispatchResult {898 let sender = ensure_signed(origin.clone())?;899900 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;901 let collection =902 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;903 collection.check_is_external()?;904905 let resource_id = Self::resource_add(906 sender,907 collection_id,908 nft_id.into(),909 [910 Self::rmrk_property(TokenType, &NftType::Resource)?,911 Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,912 Self::rmrk_property(Src, &resource.src)?,913 Self::rmrk_property(Metadata, &resource.metadata)?,914 Self::rmrk_property(License, &resource.license)?,915 Self::rmrk_property(Thumb, &resource.thumb)?,916 ]917 .into_iter(),918 )?;919920 Self::deposit_event(Event::ResourceAdded {921 nft_id,922 resource_id,923 });924 Ok(())925 }926927 /// Create composable resource928 #[transactional]929 #[pallet::weight(<SelfWeightOf<T>>::add_composable_resource())]930 pub fn add_composable_resource(931 origin: OriginFor<T>,932 rmrk_collection_id: RmrkCollectionId,933 nft_id: RmrkNftId,934 resource: RmrkComposableResource,935 ) -> DispatchResult {936 let sender = ensure_signed(origin.clone())?;937938 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;939 let collection =940 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;941 collection.check_is_external()?;942943 let resource_id = Self::resource_add(944 sender,945 collection_id,946 nft_id.into(),947 [948 Self::rmrk_property(TokenType, &NftType::Resource)?,949 Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,950 Self::rmrk_property(Parts, &resource.parts)?,951 Self::rmrk_property(Base, &resource.base)?,952 Self::rmrk_property(Src, &resource.src)?,953 Self::rmrk_property(Metadata, &resource.metadata)?,954 Self::rmrk_property(License, &resource.license)?,955 Self::rmrk_property(Thumb, &resource.thumb)?,956 ]957 .into_iter(),958 )?;959960 Self::deposit_event(Event::ResourceAdded {961 nft_id,962 resource_id,963 });964 Ok(())965 }966967 /// Create slot resource968 #[transactional]969 #[pallet::weight(<SelfWeightOf<T>>::add_slot_resource())]970 pub fn add_slot_resource(971 origin: OriginFor<T>,972 rmrk_collection_id: RmrkCollectionId,973 nft_id: RmrkNftId,974 resource: RmrkSlotResource,975 ) -> DispatchResult {976 let sender = ensure_signed(origin.clone())?;977978 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;979 let collection =980 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;981 collection.check_is_external()?;982983 let resource_id = Self::resource_add(984 sender,985 collection_id,986 nft_id.into(),987 [988 Self::rmrk_property(TokenType, &NftType::Resource)?,989 Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,990 Self::rmrk_property(Base, &resource.base)?,991 Self::rmrk_property(Src, &resource.src)?,992 Self::rmrk_property(Metadata, &resource.metadata)?,993 Self::rmrk_property(Slot, &resource.slot)?,994 Self::rmrk_property(License, &resource.license)?,995 Self::rmrk_property(Thumb, &resource.thumb)?,996 ]997 .into_iter(),998 )?;9991000 Self::deposit_event(Event::ResourceAdded {1001 nft_id,1002 resource_id,1003 });1004 Ok(())1005 }10061007 /// remove resource1008 #[transactional]1009 #[pallet::weight(<SelfWeightOf<T>>::remove_resource())]1010 pub fn remove_resource(1011 origin: OriginFor<T>,1012 rmrk_collection_id: RmrkCollectionId,1013 nft_id: RmrkNftId,1014 resource_id: RmrkResourceId,1015 ) -> DispatchResult {1016 let sender = ensure_signed(origin.clone())?;10171018 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1019 let collection =1020 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1021 collection.check_is_external()?;10221023 Self::resource_remove(sender, collection_id, nft_id.into(), resource_id.into())?;10241025 Self::deposit_event(Event::ResourceRemoval {1026 nft_id,1027 resource_id,1028 });1029 Ok(())1030 }1031 }1032}10331034impl<T: Config> Pallet<T> {1035 pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {1036 let key = rmrk_key.to_key::<T>()?;10371038 let scoped_key = PropertyScope::Rmrk1039 .apply(key)1040 .map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;10411042 Ok(scoped_key)1043 }10441045 // todo think about renaming these1046 pub fn rmrk_property<E: Encode>(1047 rmrk_key: RmrkProperty,1048 value: &E,1049 ) -> Result<Property, DispatchError> {1050 let key = rmrk_key.to_key::<T>()?;10511052 let value = value1053 .encode()1054 .try_into()1055 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;10561057 let property = Property { key, value };10581059 Ok(property)1060 }10611062 pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {1063 vec.decode()1064 .map_err(|_| <Error<T>>::UnableToDecodeRmrkData.into())1065 }10661067 pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>1068 where1069 BoundedVec<u8, S>: TryFrom<Vec<u8>>,1070 {1071 vec.rebind()1072 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1073 }10741075 fn init_collection(1076 sender: T::CrossAccountId,1077 data: CreateCollectionData<T::AccountId>,1078 properties: impl Iterator<Item = Property>,1079 ) -> Result<CollectionId, DispatchError> {1080 let collection_id = <PalletNft<T>>::init_collection(sender, data, true);10811082 if let Err(DispatchError::Arithmetic(_)) = &collection_id {1083 return Err(<Error<T>>::NoAvailableCollectionId.into());1084 }10851086 <PalletCommon<T>>::set_scoped_collection_properties(1087 collection_id?,1088 PropertyScope::Rmrk,1089 properties,1090 )?;10911092 collection_id1093 }10941095 pub fn create_nft(1096 sender: &T::CrossAccountId,1097 owner: &T::CrossAccountId,1098 collection: &NonfungibleHandle<T>,1099 properties: impl Iterator<Item = Property>,1100 ) -> Result<TokenId, DispatchError> {1101 let data = CreateNftExData {1102 properties: BoundedVec::default(),1103 owner: owner.clone(),1104 };11051106 let budget = budget::Value::new(NESTING_BUDGET);11071108 <PalletNft<T>>::create_item(collection, sender, data, &budget)?;11091110 let nft_id = <PalletNft<T>>::current_token_id(collection.id);11111112 <PalletNft<T>>::set_scoped_token_properties(1113 collection.id,1114 nft_id,1115 PropertyScope::Rmrk,1116 properties,1117 )?;11181119 Ok(nft_id)1120 }11211122 fn destroy_nft(1123 sender: T::CrossAccountId,1124 collection_id: CollectionId,1125 token_id: TokenId,1126 max_burns: u32,1127 error_if_not_owned: Error<T>,1128 ) -> DispatchResultWithPostInfo {1129 let collection =1130 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;11311132 let token_data =1133 <TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;11341135 let from = token_data.owner;11361137 let owner_check_budget = budget::Value::new(NESTING_BUDGET);11381139 ensure!(1140 <PalletStructure<T>>::check_indirectly_owned(1141 sender.clone(),1142 collection_id,1143 token_id,1144 None,1145 &owner_check_budget1146 )?,1147 error_if_not_owned,1148 );11491150 let burns_budget = budget::Value::new(max_burns);1151 let breadth_budget = budget::Value::new(max_burns);11521153 <PalletNft<T>>::burn_recursively(1154 &collection,1155 &from,1156 token_id,1157 &burns_budget,1158 &breadth_budget,1159 )1160 }11611162 fn resource_add(1163 sender: T::AccountId,1164 collection_id: CollectionId,1165 token_id: TokenId,1166 resource_properties: impl Iterator<Item = Property>,1167 ) -> Result<RmrkResourceId, DispatchError> {1168 let collection =1169 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1170 ensure!(collection.owner == sender, Error::<T>::NoPermission);11711172 let sender = T::CrossAccountId::from_sub(sender);1173 let budget = budget::Value::new(NESTING_BUDGET);11741175 let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, token_id, &budget)1176 .map_err(Self::map_unique_err_to_proxy)?;11771178 let pending = sender != nft_owner;11791180 let resource_collection_id: Option<CollectionId> =1181 Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;11821183 let resource_collection_id = match resource_collection_id {1184 Some(id) => id,1185 None => {1186 let resource_collection_id = Self::init_collection(1187 sender.clone(),1188 CreateCollectionData {1189 ..Default::default()1190 },1191 [1192 Self::rmrk_property(1193 CollectionType,1194 &misc::CollectionType::Resource,1195 )?1196 ]1197 .into_iter(),1198 )?;11991200 <PalletNft<T>>::set_scoped_token_property(1201 collection_id,1202 token_id,1203 PropertyScope::Rmrk,1204 Self::rmrk_property(ResourceCollection, &Some(resource_collection_id))?1205 )?;12061207 resource_collection_id1208 }1209 };12101211 let resource_collection =1212 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;12131214 // todo probably add extra connections to bases, slots, etc., when RMRK starts to use them12151216 let resource_id = Self::create_nft(1217 &sender,1218 &nft_owner,1219 &resource_collection,1220 resource_properties.chain(1221 [1222 Self::rmrk_property(PendingResourceAccept, &pending)?,1223 Self::rmrk_property(PendingResourceRemoval, &false)?,1224 ]1225 .into_iter(),1226 ),1227 )1228 .map_err(|err| match err {1229 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),1230 err => Self::map_unique_err_to_proxy(err),1231 })?;12321233 Ok(resource_id.0)1234 }12351236 fn resource_remove(1237 sender: T::AccountId,1238 collection_id: CollectionId,1239 nft_id: TokenId,1240 resource_id: TokenId,1241 ) -> DispatchResult {1242 let collection =1243 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1244 ensure!(collection.owner == sender, Error::<T>::NoPermission);12451246 let resource_collection_id: Option<CollectionId> =1247 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;12481249 let resource_collection_id = resource_collection_id.ok_or(Error::<T>::ResourceDoesntExist)?;1250 1251 let resource_collection =1252 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;1253 ensure!(1254 <PalletNft<T>>::token_exists(&resource_collection, resource_id),1255 Error::<T>::ResourceDoesntExist1256 );12571258 let budget = up_data_structs::budget::Value::new(NESTING_BUDGET);1259 let topmost_owner =1260 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;12611262 let sender = T::CrossAccountId::from_sub(sender);1263 if topmost_owner == sender {1264 <PalletNft<T>>::burn(&resource_collection, &sender, resource_id)1265 .map_err(Self::map_unique_err_to_proxy)?;1266 } else {1267 <PalletNft<T>>::set_scoped_token_property(1268 resource_collection_id,1269 resource_id,1270 PropertyScope::Rmrk,1271 Self::rmrk_property(PendingResourceRemoval, &true)?,1272 )?;1273 }12741275 Ok(())1276 }12771278 fn change_collection_owner(1279 collection_id: CollectionId,1280 collection_type: misc::CollectionType,1281 sender: T::AccountId,1282 new_owner: T::AccountId,1283 ) -> DispatchResult {1284 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1285 Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;12861287 let mut collection = collection.into_inner();12881289 collection.owner = new_owner;1290 collection.save()1291 }12921293 fn check_collection_owner(1294 collection: &NonfungibleHandle<T>,1295 account: &T::CrossAccountId,1296 ) -> DispatchResult {1297 collection1298 .check_is_owner(account)1299 .map_err(Self::map_unique_err_to_proxy)1300 }13011302 pub fn last_collection_idx() -> RmrkCollectionId {1303 <CollectionIndex<T>>::get()1304 }13051306 pub fn unique_collection_id(1307 rmrk_collection_id: RmrkCollectionId,1308 ) -> Result<CollectionId, DispatchError> {1309 <UniqueCollectionId<T>>::try_get(rmrk_collection_id)1310 .map_err(|_| <Error<T>>::CollectionUnknown.into())1311 }13121313 pub fn rmrk_collection_id(1314 unique_collection_id: CollectionId,1315 ) -> Result<RmrkCollectionId, DispatchError> {1316 <RmrkInernalCollectionId<T>>::try_get(unique_collection_id)1317 .map_err(|_| <Error<T>>::CollectionUnknown.into())1318 }13191320 pub fn get_nft_collection(1321 collection_id: CollectionId,1322 ) -> Result<NonfungibleHandle<T>, DispatchError> {1323 let collection = <CollectionHandle<T>>::try_get(collection_id)1324 .map_err(|_| <Error<T>>::CollectionUnknown)?;13251326 match collection.mode {1327 CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1328 _ => Err(<Error<T>>::CollectionUnknown.into()),1329 }1330 }13311332 pub fn collection_exists(collection_id: CollectionId) -> bool {1333 <CollectionHandle<T>>::try_get(collection_id).is_ok()1334 }13351336 pub fn get_collection_property(1337 collection_id: CollectionId,1338 key: RmrkProperty,1339 ) -> Result<PropertyValue, DispatchError> {1340 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1341 .get(&Self::rmrk_property_key(key)?)1342 .ok_or(<Error<T>>::CollectionUnknown)?1343 .clone();13441345 Ok(collection_property)1346 }13471348 pub fn get_collection_property_decoded<V: Decode>(1349 collection_id: CollectionId,1350 key: RmrkProperty,1351 ) -> Result<V, DispatchError> {1352 Self::decode_property(Self::get_collection_property(collection_id, key)?)1353 }13541355 pub fn get_collection_type(1356 collection_id: CollectionId,1357 ) -> Result<misc::CollectionType, DispatchError> {1358 Self::get_collection_property_decoded(collection_id, CollectionType)1359 .map_err(|_| <Error<T>>::CorruptedCollectionType.into())1360 }13611362 pub fn ensure_collection_type(1363 collection_id: CollectionId,1364 collection_type: misc::CollectionType,1365 ) -> DispatchResult {1366 let actual_type = Self::get_collection_type(collection_id)?;1367 ensure!(1368 actual_type == collection_type,1369 <CommonError<T>>::NoPermission1370 );13711372 Ok(())1373 }13741375 pub fn get_typed_nft_collection(1376 collection_id: CollectionId,1377 collection_type: misc::CollectionType,1378 ) -> Result<NonfungibleHandle<T>, DispatchError> {1379 Self::ensure_collection_type(collection_id, collection_type)?;13801381 Self::get_nft_collection(collection_id)1382 }13831384 pub fn get_typed_nft_collection_mapped(1385 rmrk_collection_id: RmrkCollectionId,1386 collection_type: misc::CollectionType,1387 ) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1388 let unique_collection_id = match collection_type {1389 misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1390 _ => rmrk_collection_id.into(),1391 };13921393 let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;13941395 Ok((collection, unique_collection_id))1396 }13971398 pub fn get_nft_property(1399 collection_id: CollectionId,1400 nft_id: TokenId,1401 key: RmrkProperty,1402 ) -> Result<PropertyValue, DispatchError> {1403 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1404 .get(&Self::rmrk_property_key(key)?)1405 .ok_or(<Error<T>>::NoAvailableNftId)? // todo replace with better error?1406 .clone();14071408 Ok(nft_property)1409 }14101411 pub fn get_nft_property_decoded<V: Decode>(1412 collection_id: CollectionId,1413 nft_id: TokenId,1414 key: RmrkProperty,1415 ) -> Result<V, DispatchError> {1416 Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)1417 }14181419 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1420 <TokenData<T>>::contains_key((collection_id, nft_id))1421 }14221423 pub fn get_nft_type(1424 collection_id: CollectionId,1425 token_id: TokenId,1426 ) -> Result<NftType, DispatchError> {1427 Self::get_nft_property_decoded(collection_id, token_id, TokenType)1428 .map_err(|_| <Error<T>>::NoAvailableNftId.into())1429 }14301431 pub fn ensure_nft_type(1432 collection_id: CollectionId,1433 token_id: TokenId,1434 nft_type: NftType,1435 ) -> DispatchResult {1436 let actual_type = Self::get_nft_type(collection_id, token_id)?;1437 ensure!(actual_type == nft_type, <Error<T>>::NoPermission);14381439 Ok(())1440 }14411442 pub fn ensure_nft_owner(1443 collection_id: CollectionId,1444 token_id: TokenId,1445 possible_owner: &T::CrossAccountId,1446 nesting_budget: &dyn budget::Budget,1447 ) -> DispatchResult {1448 let is_owned = <PalletStructure<T>>::check_indirectly_owned(1449 possible_owner.clone(),1450 collection_id,1451 token_id,1452 None,1453 nesting_budget,1454 )1455 .map_err(Self::map_unique_err_to_proxy)?;14561457 ensure!(is_owned, <Error<T>>::NoPermission);14581459 Ok(())1460 }14611462 pub fn filter_user_properties<Key, Value, R, Mapper>(1463 collection_id: CollectionId,1464 token_id: Option<TokenId>,1465 filter_keys: Option<Vec<RmrkPropertyKey>>,1466 mapper: Mapper,1467 ) -> Result<Vec<R>, DispatchError>1468 where1469 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1470 Value: Decode + Default,1471 Mapper: Fn(Key, Value) -> R,1472 {1473 filter_keys1474 .map(|keys| {1475 let properties = keys1476 .into_iter()1477 .filter_map(|key| {1478 let key: Key = key.try_into().ok()?;14791480 let value = match token_id {1481 Some(token_id) => Self::get_nft_property_decoded(1482 collection_id,1483 token_id,1484 UserProperty(key.as_ref()),1485 ),1486 None => Self::get_collection_property_decoded(1487 collection_id,1488 UserProperty(key.as_ref()),1489 ),1490 }1491 .ok()?;14921493 Some(mapper(key, value))1494 })1495 .collect();14961497 Ok(properties)1498 })1499 .unwrap_or_else(|| {1500 let properties =1501 Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();15021503 Ok(properties)1504 })1505 }15061507 pub fn iterate_user_properties<Key, Value, R, Mapper>(1508 collection_id: CollectionId,1509 token_id: Option<TokenId>,1510 mapper: Mapper,1511 ) -> Result<impl Iterator<Item = R>, DispatchError>1512 where1513 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1514 Value: Decode + Default,1515 Mapper: Fn(Key, Value) -> R,1516 {1517 let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;15181519 let properties = match token_id {1520 Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1521 None => <PalletCommon<T>>::collection_properties(collection_id),1522 };15231524 let properties = properties.into_iter().filter_map(move |(key, value)| {1525 let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;15261527 let key: Key = key.to_vec().try_into().ok()?;1528 let value: Value = value.decode().ok()?;15291530 Some(mapper(key, value))1531 });15321533 Ok(properties)1534 }15351536 fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1537 map_unique_err_to_proxy! {1538 match err {1539 CommonError::NoPermission => NoPermission,1540 CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1541 CommonError::PublicMintingNotAllowed => NoPermission,1542 CommonError::TokenNotFound => NoAvailableNftId,1543 CommonError::ApprovedValueTooLow => NoPermission,1544 CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1545 StructureError::TokenNotFound => NoAvailableNftId,1546 StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1547 }1548 }1549 }1550}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::*;3334#[cfg(feature = "runtime-benchmarks")]35pub mod benchmarking;36pub mod misc;37pub mod property;38pub mod weights;3940pub type SelfWeightOf<T> = <T as Config>::WeightInfo;4142use weights::WeightInfo;43use misc::*;44pub use property::*;4546use RmrkProperty::*;4748pub const NESTING_BUDGET: u32 = 5;4950#[frame_support::pallet]51pub mod pallet {52 use super::*;53 use pallet_evm::account;5455 #[pallet::config]56 pub trait Config:57 frame_system::Config + pallet_common::Config + pallet_nonfungible::Config + account::Config58 {59 type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;60 type WeightInfo: WeightInfo;61 }6263 #[pallet::storage]64 #[pallet::getter(fn collection_index)]65 pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;6667 #[pallet::storage]68 pub type UniqueCollectionId<T: Config> =69 StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;7071 #[pallet::storage]72 pub type RmrkInernalCollectionId<T: Config> =73 StorageMap<_, Twox64Concat, CollectionId, RmrkCollectionId, ValueQuery>;7475 #[pallet::pallet]76 #[pallet::generate_store(pub(super) trait Store)]77 pub struct Pallet<T>(_);7879 #[pallet::event]80 #[pallet::generate_deposit(pub(super) fn deposit_event)]81 pub enum Event<T: Config> {82 CollectionCreated {83 issuer: T::AccountId,84 collection_id: RmrkCollectionId,85 },86 CollectionDestroyed {87 issuer: T::AccountId,88 collection_id: RmrkCollectionId,89 },90 IssuerChanged {91 old_issuer: T::AccountId,92 new_issuer: T::AccountId,93 collection_id: RmrkCollectionId,94 },95 CollectionLocked {96 issuer: T::AccountId,97 collection_id: RmrkCollectionId,98 },99 NftMinted {100 owner: T::AccountId,101 collection_id: RmrkCollectionId,102 nft_id: RmrkNftId,103 },104 NFTBurned {105 owner: T::AccountId,106 nft_id: RmrkNftId,107 },108 NFTSent {109 sender: T::AccountId,110 recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,111 collection_id: RmrkCollectionId,112 nft_id: RmrkNftId,113 approval_required: bool,114 },115 NFTAccepted {116 sender: T::AccountId,117 recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,118 collection_id: RmrkCollectionId,119 nft_id: RmrkNftId,120 },121 NFTRejected {122 sender: T::AccountId,123 collection_id: RmrkCollectionId,124 nft_id: RmrkNftId,125 },126 PropertySet {127 collection_id: RmrkCollectionId,128 maybe_nft_id: Option<RmrkNftId>,129 key: RmrkKeyString,130 value: RmrkValueString,131 },132 ResourceAdded {133 nft_id: RmrkNftId,134 resource_id: RmrkResourceId,135 },136 ResourceRemoval {137 nft_id: RmrkNftId,138 resource_id: RmrkResourceId,139 },140 ResourceAccepted {141 nft_id: RmrkNftId,142 resource_id: RmrkResourceId,143 },144 ResourceRemovalAccepted {145 nft_id: RmrkNftId,146 resource_id: RmrkResourceId,147 },148 PrioritySet {149 collection_id: RmrkCollectionId,150 nft_id: RmrkNftId,151 },152 }153154 #[pallet::error]155 pub enum Error<T> {156 /* Unique-specific events */157 CorruptedCollectionType,158 NftTypeEncodeError,159 RmrkPropertyKeyIsTooLong,160 RmrkPropertyValueIsTooLong,161 UnableToDecodeRmrkData,162163 /* RMRK compatible events */164 CollectionNotEmpty,165 NoAvailableCollectionId,166 NoAvailableNftId,167 CollectionUnknown,168 NoPermission,169 NonTransferable,170 CollectionFullOrLocked,171 ResourceDoesntExist,172 CannotSendToDescendentOrSelf,173 CannotAcceptNonOwnedNft,174 CannotRejectNonOwnedNft,175 ResourceNotPending,176 }177178 #[pallet::call]179 impl<T: Config> Pallet<T> {180 /// Create a collection181 #[transactional]182 #[pallet::weight(<SelfWeightOf<T>>::create_collection())]183 pub fn create_collection(184 origin: OriginFor<T>,185 metadata: RmrkString,186 max: Option<u32>,187 symbol: RmrkCollectionSymbol,188 ) -> DispatchResult {189 let sender = ensure_signed(origin)?;190191 let limits = CollectionLimits {192 owner_can_transfer: Some(false),193 token_limit: max,194 ..Default::default()195 };196197 let data = CreateCollectionData {198 limits: Some(limits),199 token_prefix: symbol200 .into_inner()201 .try_into()202 .map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,203 permissions: Some(CollectionPermissions {204 nesting: Some(NestingPermissions {205 token_owner: true,206 admin: false,207 restricted: None,208209 permissive: false,210 }),211 ..Default::default()212 }),213 ..Default::default()214 };215216 let unique_collection_id = Self::init_collection(217 T::CrossAccountId::from_sub(sender.clone()),218 data,219 [220 Self::rmrk_property(Metadata, &metadata)?,221 Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,222 ]223 .into_iter(),224 )?;225 let rmrk_collection_id = <CollectionIndex<T>>::get();226227 <UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);228 <RmrkInernalCollectionId<T>>::insert(unique_collection_id, rmrk_collection_id);229230 <CollectionIndex<T>>::mutate(|n| *n += 1);231232 Self::deposit_event(Event::CollectionCreated {233 issuer: sender,234 collection_id: rmrk_collection_id,235 });236237 Ok(())238 }239240 /// destroy collection241 #[transactional]242 #[pallet::weight(<SelfWeightOf<T>>::destroy_collection())]243 pub fn destroy_collection(244 origin: OriginFor<T>,245 collection_id: RmrkCollectionId,246 ) -> DispatchResult {247 let sender = ensure_signed(origin)?;248 let cross_sender = T::CrossAccountId::from_sub(sender.clone());249250 let collection = Self::get_typed_nft_collection(251 Self::unique_collection_id(collection_id)?,252 misc::CollectionType::Regular,253 )?;254 collection.check_is_external()?;255256 <PalletNft<T>>::destroy_collection(collection, &cross_sender)257 .map_err(Self::map_unique_err_to_proxy)?;258259 Self::deposit_event(Event::CollectionDestroyed {260 issuer: sender,261 collection_id,262 });263264 Ok(())265 }266267 /// Change the issuer of a collection268 ///269 /// Parameters:270 /// - `origin`: sender of the transaction271 /// - `collection_id`: collection id of the nft to change issuer of272 /// - `new_issuer`: Collection's new issuer273 #[transactional]274 #[pallet::weight(<SelfWeightOf<T>>::change_collection_issuer())]275 pub fn change_collection_issuer(276 origin: OriginFor<T>,277 collection_id: RmrkCollectionId,278 new_issuer: <T::Lookup as StaticLookup>::Source,279 ) -> DispatchResult {280 let sender = ensure_signed(origin)?;281282 let collection = Self::get_nft_collection(Self::unique_collection_id(collection_id)?)?;283 collection.check_is_external()?;284285 let new_issuer = T::Lookup::lookup(new_issuer)?;286287 Self::change_collection_owner(288 Self::unique_collection_id(collection_id)?,289 misc::CollectionType::Regular,290 sender.clone(),291 new_issuer.clone(),292 )?;293294 Self::deposit_event(Event::IssuerChanged {295 old_issuer: sender,296 new_issuer,297 collection_id,298 });299300 Ok(())301 }302303 /// lock collection304 #[transactional]305 #[pallet::weight(<SelfWeightOf<T>>::lock_collection())]306 pub fn lock_collection(307 origin: OriginFor<T>,308 collection_id: RmrkCollectionId,309 ) -> DispatchResult {310 let sender = ensure_signed(origin)?;311 let cross_sender = T::CrossAccountId::from_sub(sender.clone());312313 let collection = Self::get_typed_nft_collection(314 Self::unique_collection_id(collection_id)?,315 misc::CollectionType::Regular,316 )?;317 collection.check_is_external()?;318319 Self::check_collection_owner(&collection, &cross_sender)?;320321 let token_count = collection.total_supply();322323 let mut collection = collection.into_inner();324 collection.limits.token_limit = Some(token_count);325 collection.save()?;326327 Self::deposit_event(Event::CollectionLocked {328 issuer: sender,329 collection_id,330 });331332 Ok(())333 }334335 /// Mints an NFT in the specified collection336 /// Sets metadata and the royalty attribute337 ///338 /// Parameters:339 /// - `collection_id`: The class of the asset to be minted.340 /// - `nft_id`: The nft value of the asset to be minted.341 /// - `recipient`: Receiver of the royalty342 /// - `royalty`: Permillage reward from each trade for the Recipient343 /// - `metadata`: Arbitrary data about an nft, e.g. IPFS hash344 /// - `transferable`: Ability to transfer this NFT345 #[transactional]346 #[pallet::weight(<SelfWeightOf<T>>::mint_nft())]347 pub fn mint_nft(348 origin: OriginFor<T>,349 owner: T::AccountId,350 collection_id: RmrkCollectionId,351 recipient: Option<T::AccountId>,352 royalty_amount: Option<Permill>,353 metadata: RmrkString,354 transferable: bool,355 ) -> DispatchResult {356 let sender = ensure_signed(origin)?;357 let sender = T::CrossAccountId::from_sub(sender);358 let cross_owner = T::CrossAccountId::from_sub(owner.clone());359360 let collection = Self::get_typed_nft_collection(361 Self::unique_collection_id(collection_id)?,362 misc::CollectionType::Regular,363 )?;364 collection.check_is_external()?;365366 let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {367 recipient: recipient.unwrap_or_else(|| owner.clone()),368 amount,369 });370371 let nft_id = Self::create_nft(372 &sender,373 &cross_owner,374 &collection,375 [376 Self::rmrk_property(TokenType, &NftType::Regular)?,377 Self::rmrk_property(Transferable, &transferable)?,378 Self::rmrk_property(PendingNftAccept, &false)?,379 Self::rmrk_property(RoyaltyInfo, &royalty_info)?,380 Self::rmrk_property(Metadata, &metadata)?,381 Self::rmrk_property(Equipped, &false)?,382 Self::rmrk_property(ResourceCollection, &None::<CollectionId>)?,383 Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,384 ]385 .into_iter(),386 )387 .map_err(|err| match err {388 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),389 err => Self::map_unique_err_to_proxy(err),390 })?;391392 Self::deposit_event(Event::NftMinted {393 owner,394 collection_id,395 nft_id: nft_id.0,396 });397398 Ok(())399 }400401 /// burn nft402 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]403 #[transactional]404 pub fn burn_nft(405 origin: OriginFor<T>,406 collection_id: RmrkCollectionId,407 nft_id: RmrkNftId,408 max_burns: u32,409 ) -> DispatchResult {410 let sender = ensure_signed(origin)?;411 let cross_sender = T::CrossAccountId::from_sub(sender.clone());412413 let collection = Self::get_typed_nft_collection(414 Self::unique_collection_id(collection_id)?,415 misc::CollectionType::Regular,416 )?;417 collection.check_is_external()?;418419 Self::destroy_nft(420 cross_sender,421 Self::unique_collection_id(collection_id)?,422 nft_id.into(),423 max_burns,424 <Error<T>>::NoPermission,425 )426 .map_err(|err| Self::map_unique_err_to_proxy(err.error))?;427428 Self::deposit_event(Event::NFTBurned {429 owner: sender,430 nft_id,431 });432433 Ok(())434 }435436 /// Transfers a NFT from an Account or NFT A to another Account or NFT B437 ///438 /// Parameters:439 /// - `origin`: sender of the transaction440 /// - `rmrk_collection_id`: collection id of the nft to be transferred441 /// - `rmrk_nft_id`: nft id of the nft to be transferred442 /// - `new_owner`: new owner of the nft which can be either an account or a NFT443 #[transactional]444 #[pallet::weight(<SelfWeightOf<T>>::send())]445 pub fn send(446 origin: OriginFor<T>,447 rmrk_collection_id: RmrkCollectionId,448 rmrk_nft_id: RmrkNftId,449 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,450 ) -> DispatchResult {451 let sender = ensure_signed(origin.clone())?;452 let cross_sender = T::CrossAccountId::from_sub(sender.clone());453454 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;455 let nft_id = rmrk_nft_id.into();456457 let collection =458 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;459 collection.check_is_external()?;460461 let token_data =462 <TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;463464 let from = token_data.owner;465466 ensure!(467 Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,468 <Error<T>>::NonTransferable469 );470471 ensure!(472 !Self::get_nft_property_decoded(473 collection_id,474 nft_id,475 RmrkProperty::PendingNftAccept476 )?,477 <Error<T>>::NoPermission478 );479480 let target_owner;481 let approval_required;482483 match new_owner {484 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {485 target_owner = T::CrossAccountId::from_sub(account_id.clone());486 approval_required = false;487 }488 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(489 target_collection_id,490 target_nft_id,491 ) => {492 let target_collection_id = Self::unique_collection_id(target_collection_id)?;493494 let target_nft_budget = budget::Value::new(NESTING_BUDGET);495496 let target_nft_owner = <PalletStructure<T>>::get_checked_topmost_owner(497 target_collection_id,498 target_nft_id.into(),499 Some((collection_id, nft_id)),500 &target_nft_budget,501 )502 .map_err(Self::map_unique_err_to_proxy)?;503504 approval_required = cross_sender != target_nft_owner;505506 if approval_required {507 target_owner = target_nft_owner;508509 <PalletNft<T>>::set_scoped_token_property(510 collection.id,511 nft_id,512 PropertyScope::Rmrk,513 Self::rmrk_property(PendingNftAccept, &approval_required)?,514 )?;515 } else {516 target_owner = T::CrossTokenAddressMapping::token_to_address(517 target_collection_id,518 target_nft_id.into(),519 );520 }521 }522 }523524 let src_nft_budget = budget::Value::new(NESTING_BUDGET);525526 <PalletNft<T>>::transfer_from(527 &collection,528 &cross_sender,529 &from,530 &target_owner,531 nft_id,532 &src_nft_budget,533 )534 .map_err(Self::map_unique_err_to_proxy)?;535536 Self::deposit_event(Event::NFTSent {537 sender,538 recipient: new_owner,539 collection_id: rmrk_collection_id,540 nft_id: rmrk_nft_id,541 approval_required,542 });543544 Ok(())545 }546547 /// Accepts an NFT sent from another account to self or owned NFT548 ///549 /// Parameters:550 /// - `origin`: sender of the transaction551 /// - `rmrk_collection_id`: collection id of the nft to be accepted552 /// - `rmrk_nft_id`: nft id of the nft to be accepted553 /// - `new_owner`: either origin's account ID or origin-owned NFT, whichever the NFT was554 /// sent to555 #[transactional]556 #[pallet::weight(<SelfWeightOf<T>>::accept_nft())]557 pub fn accept_nft(558 origin: OriginFor<T>,559 rmrk_collection_id: RmrkCollectionId,560 rmrk_nft_id: RmrkNftId,561 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,562 ) -> DispatchResult {563 let sender = ensure_signed(origin.clone())?;564 let cross_sender = T::CrossAccountId::from_sub(sender.clone());565566 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;567 let nft_id = rmrk_nft_id.into();568569 let collection =570 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;571 collection.check_is_external()?;572573 let new_cross_owner = match new_owner {574 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {575 T::CrossAccountId::from_sub(account_id.clone())576 }577 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(578 target_collection_id,579 target_nft_id,580 ) => {581 let target_collection_id = Self::unique_collection_id(target_collection_id)?;582583 T::CrossTokenAddressMapping::token_to_address(584 target_collection_id,585 TokenId(target_nft_id),586 )587 }588 };589590 let budget = budget::Value::new(NESTING_BUDGET);591592 <PalletNft<T>>::transfer(593 &collection,594 &cross_sender,595 &new_cross_owner,596 nft_id,597 &budget,598 )599 .map_err(|err| {600 if err == <CommonError<T>>::UserIsNotAllowedToNest.into() {601 <Error<T>>::CannotAcceptNonOwnedNft.into()602 } else {603 Self::map_unique_err_to_proxy(err)604 }605 })?;606607 <PalletNft<T>>::set_scoped_token_property(608 collection.id,609 nft_id,610 PropertyScope::Rmrk,611 Self::rmrk_property(PendingNftAccept, &false)?,612 )?;613614 Self::deposit_event(Event::NFTAccepted {615 sender,616 recipient: new_owner,617 collection_id: rmrk_collection_id,618 nft_id: rmrk_nft_id,619 });620621 Ok(())622 }623624 /// Rejects an NFT sent from another account to self or owned NFT625 ///626 /// Parameters:627 /// - `origin`: sender of the transaction628 /// - `rmrk_collection_id`: collection id of the nft to be accepted629 /// - `rmrk_nft_id`: nft id of the nft to be accepted630 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]631 #[transactional]632 pub fn reject_nft(633 origin: OriginFor<T>,634 rmrk_collection_id: RmrkCollectionId,635 rmrk_nft_id: RmrkNftId,636 max_burns: u32,637 ) -> DispatchResult {638 let sender = ensure_signed(origin)?;639 let cross_sender = T::CrossAccountId::from_sub(sender.clone());640641 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;642 let nft_id = rmrk_nft_id.into();643644 let collection =645 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;646 collection.check_is_external()?;647648 Self::destroy_nft(649 cross_sender,650 collection_id,651 nft_id,652 max_burns,653 <Error<T>>::CannotRejectNonOwnedNft,654 )655 .map_err(|err| Self::map_unique_err_to_proxy(err.error))?;656657 Self::deposit_event(Event::NFTRejected {658 sender,659 collection_id: rmrk_collection_id,660 nft_id: rmrk_nft_id,661 });662663 Ok(())664 }665666 /// accept the addition of a new resource to an existing NFT667 #[transactional]668 #[pallet::weight(<SelfWeightOf<T>>::accept_resource())]669 pub fn accept_resource(670 origin: OriginFor<T>,671 rmrk_collection_id: RmrkCollectionId,672 rmrk_nft_id: RmrkNftId,673 rmrk_resource_id: RmrkResourceId,674 ) -> DispatchResult {675 let sender = ensure_signed(origin)?;676 let cross_sender = T::CrossAccountId::from_sub(sender);677678 let collection_id = Self::unique_collection_id(rmrk_collection_id)679 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;680 let collection =681 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;682 collection.check_is_external()?;683684 let nft_id = rmrk_nft_id.into();685 let resource_id = rmrk_resource_id.into();686687 let budget = budget::Value::new(NESTING_BUDGET);688689 let nft_owner =690 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)691 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;692693 let resource_collection_id: Option<CollectionId> =694 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)695 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;696697 let resource_collection_id =698 resource_collection_id.ok_or(<Error<T>>::ResourceDoesntExist)?;699700 let is_pending: bool = Self::get_nft_property_decoded(701 resource_collection_id,702 resource_id,703 PendingResourceAccept,704 )705 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;706707 ensure!(is_pending, <Error<T>>::ResourceNotPending);708709 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);710711 <PalletNft<T>>::set_scoped_token_property(712 resource_collection_id,713 rmrk_resource_id.into(),714 PropertyScope::Rmrk,715 Self::rmrk_property(PendingResourceAccept, &false)?,716 )?;717718 Self::deposit_event(Event::<T>::ResourceAccepted {719 nft_id: rmrk_nft_id,720 resource_id: rmrk_resource_id,721 });722723 Ok(())724 }725726 /// accept the removal of a resource of an existing NFT727 #[transactional]728 #[pallet::weight(<SelfWeightOf<T>>::accept_resource_removal())]729 pub fn accept_resource_removal(730 origin: OriginFor<T>,731 rmrk_collection_id: RmrkCollectionId,732 rmrk_nft_id: RmrkNftId,733 rmrk_resource_id: RmrkResourceId,734 ) -> DispatchResult {735 let sender = ensure_signed(origin)?;736 let cross_sender = T::CrossAccountId::from_sub(sender);737738 let collection_id = Self::unique_collection_id(rmrk_collection_id)739 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;740 let collection =741 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;742 collection.check_is_external()?;743744 let nft_id = rmrk_nft_id.into();745 let resource_id = rmrk_resource_id.into();746747 let budget = budget::Value::new(NESTING_BUDGET);748749 let nft_owner =750 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)751 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;752753 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);754755 let resource_collection_id: Option<CollectionId> =756 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)757 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;758759 let resource_collection_id =760 resource_collection_id.ok_or(<Error<T>>::ResourceDoesntExist)?;761762 let is_pending: bool = Self::get_nft_property_decoded(763 resource_collection_id,764 resource_id,765 PendingResourceRemoval,766 )767 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;768769 ensure!(is_pending, <Error<T>>::ResourceNotPending);770771 let resource_collection = Self::get_typed_nft_collection(772 resource_collection_id,773 misc::CollectionType::Resource,774 )?;775776 let resource_data = <TokenData<T>>::get((resource_collection_id, resource_id))777 .ok_or(<Error<T>>::ResourceDoesntExist)?;778779 let resource_owner = resource_data.owner;780781 <PalletNft<T>>::burn(782 &resource_collection,783 &resource_owner,784 rmrk_resource_id.into(),785 )786 .map_err(Self::map_unique_err_to_proxy)?;787788 Self::deposit_event(Event::<T>::ResourceRemovalAccepted {789 nft_id: rmrk_nft_id,790 resource_id: rmrk_resource_id,791 });792793 Ok(())794 }795796 /// set a custom value on an NFT797 #[transactional]798 #[pallet::weight(<SelfWeightOf<T>>::set_property())]799 pub fn set_property(800 origin: OriginFor<T>,801 #[pallet::compact] rmrk_collection_id: RmrkCollectionId,802 maybe_nft_id: Option<RmrkNftId>,803 key: RmrkKeyString,804 value: RmrkValueString,805 ) -> DispatchResult {806 let sender = ensure_signed(origin)?;807 let sender = T::CrossAccountId::from_sub(sender);808809 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;810 let collection =811 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;812 collection.check_is_external()?;813814 let budget = budget::Value::new(NESTING_BUDGET);815816 match maybe_nft_id {817 Some(nft_id) => {818 let token_id: TokenId = nft_id.into();819820 Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;821 Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;822823 <PalletNft<T>>::set_scoped_token_property(824 collection_id,825 token_id,826 PropertyScope::Rmrk,827 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,828 )?;829 }830 None => {831 let collection = Self::get_typed_nft_collection(832 collection_id,833 misc::CollectionType::Regular,834 )?;835836 Self::check_collection_owner(&collection, &sender)?;837838 <PalletCommon<T>>::set_scoped_collection_property(839 collection_id,840 PropertyScope::Rmrk,841 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,842 )?;843 }844 }845846 Self::deposit_event(Event::PropertySet {847 collection_id: rmrk_collection_id,848 maybe_nft_id,849 key,850 value,851 });852853 Ok(())854 }855856 /// set a different order of resource priority857 #[transactional]858 #[pallet::weight(<SelfWeightOf<T>>::set_priority())]859 pub fn set_priority(860 origin: OriginFor<T>,861 rmrk_collection_id: RmrkCollectionId,862 rmrk_nft_id: RmrkNftId,863 priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,864 ) -> DispatchResult {865 let sender = ensure_signed(origin)?;866 let sender = T::CrossAccountId::from_sub(sender);867868 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;869 let nft_id = rmrk_nft_id.into();870871 let collection =872 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;873 collection.check_is_external()?;874875 let budget = budget::Value::new(NESTING_BUDGET);876877 Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;878 Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;879880 <PalletNft<T>>::set_scoped_token_property(881 collection_id,882 nft_id,883 PropertyScope::Rmrk,884 Self::rmrk_property(ResourcePriorities, &priorities.into_inner())?,885 )?;886887 Self::deposit_event(Event::<T>::PrioritySet {888 collection_id: rmrk_collection_id,889 nft_id: rmrk_nft_id,890 });891892 Ok(())893 }894895 /// Create basic resource896 #[transactional]897 #[pallet::weight(<SelfWeightOf<T>>::add_basic_resource())]898 pub fn add_basic_resource(899 origin: OriginFor<T>,900 rmrk_collection_id: RmrkCollectionId,901 nft_id: RmrkNftId,902 resource: RmrkBasicResource,903 ) -> DispatchResult {904 let sender = ensure_signed(origin.clone())?;905906 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;907 let collection =908 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;909 collection.check_is_external()?;910911 let resource_id = Self::resource_add(912 sender,913 collection_id,914 nft_id.into(),915 [916 Self::rmrk_property(TokenType, &NftType::Resource)?,917 Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,918 Self::rmrk_property(Src, &resource.src)?,919 Self::rmrk_property(Metadata, &resource.metadata)?,920 Self::rmrk_property(License, &resource.license)?,921 Self::rmrk_property(Thumb, &resource.thumb)?,922 ]923 .into_iter(),924 )?;925926 Self::deposit_event(Event::ResourceAdded {927 nft_id,928 resource_id,929 });930 Ok(())931 }932933 /// Create composable resource934 #[transactional]935 #[pallet::weight(<SelfWeightOf<T>>::add_composable_resource())]936 pub fn add_composable_resource(937 origin: OriginFor<T>,938 rmrk_collection_id: RmrkCollectionId,939 nft_id: RmrkNftId,940 resource: RmrkComposableResource,941 ) -> DispatchResult {942 let sender = ensure_signed(origin.clone())?;943944 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;945 let collection =946 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;947 collection.check_is_external()?;948949 let resource_id = Self::resource_add(950 sender,951 collection_id,952 nft_id.into(),953 [954 Self::rmrk_property(TokenType, &NftType::Resource)?,955 Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,956 Self::rmrk_property(Parts, &resource.parts)?,957 Self::rmrk_property(Base, &resource.base)?,958 Self::rmrk_property(Src, &resource.src)?,959 Self::rmrk_property(Metadata, &resource.metadata)?,960 Self::rmrk_property(License, &resource.license)?,961 Self::rmrk_property(Thumb, &resource.thumb)?,962 ]963 .into_iter(),964 )?;965966 Self::deposit_event(Event::ResourceAdded {967 nft_id,968 resource_id,969 });970 Ok(())971 }972973 /// Create slot resource974 #[transactional]975 #[pallet::weight(<SelfWeightOf<T>>::add_slot_resource())]976 pub fn add_slot_resource(977 origin: OriginFor<T>,978 rmrk_collection_id: RmrkCollectionId,979 nft_id: RmrkNftId,980 resource: RmrkSlotResource,981 ) -> DispatchResult {982 let sender = ensure_signed(origin.clone())?;983984 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;985 let collection =986 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;987 collection.check_is_external()?;988989 let resource_id = Self::resource_add(990 sender,991 collection_id,992 nft_id.into(),993 [994 Self::rmrk_property(TokenType, &NftType::Resource)?,995 Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,996 Self::rmrk_property(Base, &resource.base)?,997 Self::rmrk_property(Src, &resource.src)?,998 Self::rmrk_property(Metadata, &resource.metadata)?,999 Self::rmrk_property(Slot, &resource.slot)?,1000 Self::rmrk_property(License, &resource.license)?,1001 Self::rmrk_property(Thumb, &resource.thumb)?,1002 ]1003 .into_iter(),1004 )?;10051006 Self::deposit_event(Event::ResourceAdded {1007 nft_id,1008 resource_id,1009 });1010 Ok(())1011 }10121013 /// remove resource1014 #[transactional]1015 #[pallet::weight(<SelfWeightOf<T>>::remove_resource())]1016 pub fn remove_resource(1017 origin: OriginFor<T>,1018 rmrk_collection_id: RmrkCollectionId,1019 nft_id: RmrkNftId,1020 resource_id: RmrkResourceId,1021 ) -> DispatchResult {1022 let sender = ensure_signed(origin.clone())?;10231024 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1025 let collection =1026 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1027 collection.check_is_external()?;10281029 Self::resource_remove(sender, collection_id, nft_id.into(), resource_id.into())?;10301031 Self::deposit_event(Event::ResourceRemoval {1032 nft_id,1033 resource_id,1034 });1035 Ok(())1036 }1037 }1038}10391040impl<T: Config> Pallet<T> {1041 pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {1042 let key = rmrk_key.to_key::<T>()?;10431044 let scoped_key = PropertyScope::Rmrk1045 .apply(key)1046 .map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;10471048 Ok(scoped_key)1049 }10501051 // todo think about renaming these1052 pub fn rmrk_property<E: Encode>(1053 rmrk_key: RmrkProperty,1054 value: &E,1055 ) -> Result<Property, DispatchError> {1056 let key = rmrk_key.to_key::<T>()?;10571058 let value = value1059 .encode()1060 .try_into()1061 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;10621063 let property = Property { key, value };10641065 Ok(property)1066 }10671068 pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {1069 vec.decode()1070 .map_err(|_| <Error<T>>::UnableToDecodeRmrkData.into())1071 }10721073 pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>1074 where1075 BoundedVec<u8, S>: TryFrom<Vec<u8>>,1076 {1077 vec.rebind()1078 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1079 }10801081 fn init_collection(1082 sender: T::CrossAccountId,1083 data: CreateCollectionData<T::AccountId>,1084 properties: impl Iterator<Item = Property>,1085 ) -> Result<CollectionId, DispatchError> {1086 let collection_id = <PalletNft<T>>::init_collection(sender, data, true);10871088 if let Err(DispatchError::Arithmetic(_)) = &collection_id {1089 return Err(<Error<T>>::NoAvailableCollectionId.into());1090 }10911092 <PalletCommon<T>>::set_scoped_collection_properties(1093 collection_id?,1094 PropertyScope::Rmrk,1095 properties,1096 )?;10971098 collection_id1099 }11001101 pub fn create_nft(1102 sender: &T::CrossAccountId,1103 owner: &T::CrossAccountId,1104 collection: &NonfungibleHandle<T>,1105 properties: impl Iterator<Item = Property>,1106 ) -> Result<TokenId, DispatchError> {1107 let data = CreateNftExData {1108 properties: BoundedVec::default(),1109 owner: owner.clone(),1110 };11111112 let budget = budget::Value::new(NESTING_BUDGET);11131114 <PalletNft<T>>::create_item(collection, sender, data, &budget)?;11151116 let nft_id = <PalletNft<T>>::current_token_id(collection.id);11171118 <PalletNft<T>>::set_scoped_token_properties(1119 collection.id,1120 nft_id,1121 PropertyScope::Rmrk,1122 properties,1123 )?;11241125 Ok(nft_id)1126 }11271128 fn destroy_nft(1129 sender: T::CrossAccountId,1130 collection_id: CollectionId,1131 token_id: TokenId,1132 max_burns: u32,1133 error_if_not_owned: Error<T>,1134 ) -> DispatchResultWithPostInfo {1135 let collection =1136 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;11371138 let token_data =1139 <TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;11401141 let from = token_data.owner;11421143 let owner_check_budget = budget::Value::new(NESTING_BUDGET);11441145 ensure!(1146 <PalletStructure<T>>::check_indirectly_owned(1147 sender.clone(),1148 collection_id,1149 token_id,1150 None,1151 &owner_check_budget1152 )?,1153 error_if_not_owned,1154 );11551156 let burns_budget = budget::Value::new(max_burns);1157 let breadth_budget = budget::Value::new(max_burns);11581159 <PalletNft<T>>::burn_recursively(1160 &collection,1161 &from,1162 token_id,1163 &burns_budget,1164 &breadth_budget,1165 )1166 }11671168 fn resource_add(1169 sender: T::AccountId,1170 collection_id: CollectionId,1171 token_id: TokenId,1172 resource_properties: impl Iterator<Item = Property>,1173 ) -> Result<RmrkResourceId, DispatchError> {1174 let collection =1175 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1176 ensure!(collection.owner == sender, Error::<T>::NoPermission);11771178 let sender = T::CrossAccountId::from_sub(sender);1179 let budget = budget::Value::new(NESTING_BUDGET);11801181 let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, token_id, &budget)1182 .map_err(Self::map_unique_err_to_proxy)?;11831184 let pending = sender != nft_owner;11851186 let resource_collection_id: Option<CollectionId> =1187 Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;11881189 let resource_collection_id = match resource_collection_id {1190 Some(id) => id,1191 None => {1192 let resource_collection_id = Self::init_collection(1193 sender.clone(),1194 CreateCollectionData {1195 ..Default::default()1196 },1197 [Self::rmrk_property(1198 CollectionType,1199 &misc::CollectionType::Resource,1200 )?]1201 .into_iter(),1202 )?;12031204 <PalletNft<T>>::set_scoped_token_property(1205 collection_id,1206 token_id,1207 PropertyScope::Rmrk,1208 Self::rmrk_property(ResourceCollection, &Some(resource_collection_id))?,1209 )?;12101211 resource_collection_id1212 }1213 };12141215 let resource_collection =1216 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;12171218 // todo probably add extra connections to bases, slots, etc., when RMRK starts to use them12191220 let resource_id = Self::create_nft(1221 &sender,1222 &nft_owner,1223 &resource_collection,1224 resource_properties.chain(1225 [1226 Self::rmrk_property(PendingResourceAccept, &pending)?,1227 Self::rmrk_property(PendingResourceRemoval, &false)?,1228 ]1229 .into_iter(),1230 ),1231 )1232 .map_err(|err| match err {1233 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),1234 err => Self::map_unique_err_to_proxy(err),1235 })?;12361237 Ok(resource_id.0)1238 }12391240 fn resource_remove(1241 sender: T::AccountId,1242 collection_id: CollectionId,1243 nft_id: TokenId,1244 resource_id: TokenId,1245 ) -> DispatchResult {1246 let collection =1247 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1248 ensure!(collection.owner == sender, Error::<T>::NoPermission);12491250 let resource_collection_id: Option<CollectionId> =1251 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;12521253 let resource_collection_id =1254 resource_collection_id.ok_or(Error::<T>::ResourceDoesntExist)?;12551256 let resource_collection =1257 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;1258 ensure!(1259 <PalletNft<T>>::token_exists(&resource_collection, resource_id),1260 Error::<T>::ResourceDoesntExist1261 );12621263 let budget = up_data_structs::budget::Value::new(NESTING_BUDGET);1264 let topmost_owner =1265 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;12661267 let sender = T::CrossAccountId::from_sub(sender);1268 if topmost_owner == sender {1269 <PalletNft<T>>::burn(&resource_collection, &sender, resource_id)1270 .map_err(Self::map_unique_err_to_proxy)?;1271 } else {1272 <PalletNft<T>>::set_scoped_token_property(1273 resource_collection_id,1274 resource_id,1275 PropertyScope::Rmrk,1276 Self::rmrk_property(PendingResourceRemoval, &true)?,1277 )?;1278 }12791280 Ok(())1281 }12821283 fn change_collection_owner(1284 collection_id: CollectionId,1285 collection_type: misc::CollectionType,1286 sender: T::AccountId,1287 new_owner: T::AccountId,1288 ) -> DispatchResult {1289 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1290 Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;12911292 let mut collection = collection.into_inner();12931294 collection.owner = new_owner;1295 collection.save()1296 }12971298 fn check_collection_owner(1299 collection: &NonfungibleHandle<T>,1300 account: &T::CrossAccountId,1301 ) -> DispatchResult {1302 collection1303 .check_is_owner(account)1304 .map_err(Self::map_unique_err_to_proxy)1305 }13061307 pub fn last_collection_idx() -> RmrkCollectionId {1308 <CollectionIndex<T>>::get()1309 }13101311 pub fn unique_collection_id(1312 rmrk_collection_id: RmrkCollectionId,1313 ) -> Result<CollectionId, DispatchError> {1314 <UniqueCollectionId<T>>::try_get(rmrk_collection_id)1315 .map_err(|_| <Error<T>>::CollectionUnknown.into())1316 }13171318 pub fn rmrk_collection_id(1319 unique_collection_id: CollectionId,1320 ) -> Result<RmrkCollectionId, DispatchError> {1321 <RmrkInernalCollectionId<T>>::try_get(unique_collection_id)1322 .map_err(|_| <Error<T>>::CollectionUnknown.into())1323 }13241325 pub fn get_nft_collection(1326 collection_id: CollectionId,1327 ) -> Result<NonfungibleHandle<T>, DispatchError> {1328 let collection = <CollectionHandle<T>>::try_get(collection_id)1329 .map_err(|_| <Error<T>>::CollectionUnknown)?;13301331 match collection.mode {1332 CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1333 _ => Err(<Error<T>>::CollectionUnknown.into()),1334 }1335 }13361337 pub fn collection_exists(collection_id: CollectionId) -> bool {1338 <CollectionHandle<T>>::try_get(collection_id).is_ok()1339 }13401341 pub fn get_collection_property(1342 collection_id: CollectionId,1343 key: RmrkProperty,1344 ) -> Result<PropertyValue, DispatchError> {1345 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1346 .get(&Self::rmrk_property_key(key)?)1347 .ok_or(<Error<T>>::CollectionUnknown)?1348 .clone();13491350 Ok(collection_property)1351 }13521353 pub fn get_collection_property_decoded<V: Decode>(1354 collection_id: CollectionId,1355 key: RmrkProperty,1356 ) -> Result<V, DispatchError> {1357 Self::decode_property(Self::get_collection_property(collection_id, key)?)1358 }13591360 pub fn get_collection_type(1361 collection_id: CollectionId,1362 ) -> Result<misc::CollectionType, DispatchError> {1363 Self::get_collection_property_decoded(collection_id, CollectionType)1364 .map_err(|_| <Error<T>>::CorruptedCollectionType.into())1365 }13661367 pub fn ensure_collection_type(1368 collection_id: CollectionId,1369 collection_type: misc::CollectionType,1370 ) -> DispatchResult {1371 let actual_type = Self::get_collection_type(collection_id)?;1372 ensure!(1373 actual_type == collection_type,1374 <CommonError<T>>::NoPermission1375 );13761377 Ok(())1378 }13791380 pub fn get_typed_nft_collection(1381 collection_id: CollectionId,1382 collection_type: misc::CollectionType,1383 ) -> Result<NonfungibleHandle<T>, DispatchError> {1384 Self::ensure_collection_type(collection_id, collection_type)?;13851386 Self::get_nft_collection(collection_id)1387 }13881389 pub fn get_typed_nft_collection_mapped(1390 rmrk_collection_id: RmrkCollectionId,1391 collection_type: misc::CollectionType,1392 ) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1393 let unique_collection_id = match collection_type {1394 misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1395 _ => rmrk_collection_id.into(),1396 };13971398 let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;13991400 Ok((collection, unique_collection_id))1401 }14021403 pub fn get_nft_property(1404 collection_id: CollectionId,1405 nft_id: TokenId,1406 key: RmrkProperty,1407 ) -> Result<PropertyValue, DispatchError> {1408 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1409 .get(&Self::rmrk_property_key(key)?)1410 .ok_or(<Error<T>>::NoAvailableNftId)? // todo replace with better error?1411 .clone();14121413 Ok(nft_property)1414 }14151416 pub fn get_nft_property_decoded<V: Decode>(1417 collection_id: CollectionId,1418 nft_id: TokenId,1419 key: RmrkProperty,1420 ) -> Result<V, DispatchError> {1421 Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)1422 }14231424 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1425 <TokenData<T>>::contains_key((collection_id, nft_id))1426 }14271428 pub fn get_nft_type(1429 collection_id: CollectionId,1430 token_id: TokenId,1431 ) -> Result<NftType, DispatchError> {1432 Self::get_nft_property_decoded(collection_id, token_id, TokenType)1433 .map_err(|_| <Error<T>>::NoAvailableNftId.into())1434 }14351436 pub fn ensure_nft_type(1437 collection_id: CollectionId,1438 token_id: TokenId,1439 nft_type: NftType,1440 ) -> DispatchResult {1441 let actual_type = Self::get_nft_type(collection_id, token_id)?;1442 ensure!(actual_type == nft_type, <Error<T>>::NoPermission);14431444 Ok(())1445 }14461447 pub fn ensure_nft_owner(1448 collection_id: CollectionId,1449 token_id: TokenId,1450 possible_owner: &T::CrossAccountId,1451 nesting_budget: &dyn budget::Budget,1452 ) -> DispatchResult {1453 let is_owned = <PalletStructure<T>>::check_indirectly_owned(1454 possible_owner.clone(),1455 collection_id,1456 token_id,1457 None,1458 nesting_budget,1459 )1460 .map_err(Self::map_unique_err_to_proxy)?;14611462 ensure!(is_owned, <Error<T>>::NoPermission);14631464 Ok(())1465 }14661467 pub fn filter_user_properties<Key, Value, R, Mapper>(1468 collection_id: CollectionId,1469 token_id: Option<TokenId>,1470 filter_keys: Option<Vec<RmrkPropertyKey>>,1471 mapper: Mapper,1472 ) -> Result<Vec<R>, DispatchError>1473 where1474 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1475 Value: Decode + Default,1476 Mapper: Fn(Key, Value) -> R,1477 {1478 filter_keys1479 .map(|keys| {1480 let properties = keys1481 .into_iter()1482 .filter_map(|key| {1483 let key: Key = key.try_into().ok()?;14841485 let value = match token_id {1486 Some(token_id) => Self::get_nft_property_decoded(1487 collection_id,1488 token_id,1489 UserProperty(key.as_ref()),1490 ),1491 None => Self::get_collection_property_decoded(1492 collection_id,1493 UserProperty(key.as_ref()),1494 ),1495 }1496 .ok()?;14971498 Some(mapper(key, value))1499 })1500 .collect();15011502 Ok(properties)1503 })1504 .unwrap_or_else(|| {1505 let properties =1506 Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();15071508 Ok(properties)1509 })1510 }15111512 pub fn iterate_user_properties<Key, Value, R, Mapper>(1513 collection_id: CollectionId,1514 token_id: Option<TokenId>,1515 mapper: Mapper,1516 ) -> Result<impl Iterator<Item = R>, DispatchError>1517 where1518 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1519 Value: Decode + Default,1520 Mapper: Fn(Key, Value) -> R,1521 {1522 let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;15231524 let properties = match token_id {1525 Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1526 None => <PalletCommon<T>>::collection_properties(collection_id),1527 };15281529 let properties = properties.into_iter().filter_map(move |(key, value)| {1530 let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;15311532 let key: Key = key.to_vec().try_into().ok()?;1533 let value: Value = value.decode().ok()?;15341535 Some(mapper(key, value))1536 });15371538 Ok(properties)1539 }15401541 fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1542 map_unique_err_to_proxy! {1543 match err {1544 CommonError::NoPermission => NoPermission,1545 CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1546 CommonError::PublicMintingNotAllowed => NoPermission,1547 CommonError::TokenNotFound => NoAvailableNftId,1548 CommonError::ApprovedValueTooLow => NoPermission,1549 CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1550 StructureError::TokenNotFound => NoAvailableNftId,1551 StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1552 }1553 }1554 }1555}