difftreelog
cargo fmt
in: master
3 files changed
pallets/proxy-rmrk-core/src/lib.rsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![cfg_attr(not(feature = "std"), no_std)]1819use frame_support::{pallet_prelude::*, transactional, BoundedVec, dispatch::DispatchResult};20use frame_system::{pallet_prelude::*, ensure_signed};21use sp_runtime::{DispatchError, Permill, traits::StaticLookup};22use sp_std::vec::Vec;23use up_data_structs::{*, mapping::TokenAddressMapping};24use pallet_common::{25 Pallet as PalletCommon, Error as CommonError, CollectionHandle, CommonCollectionOperations,26};27use pallet_nonfungible::{Pallet as PalletNft, NonfungibleHandle, TokenData};28use pallet_structure::{Pallet as PalletStructure, Error as StructureError};29use pallet_evm::account::CrossAccountId;30use core::convert::AsRef;3132pub use pallet::*;3334pub mod misc;35pub mod property;3637use misc::*;38pub use property::*;3940use RmrkProperty::*;4142const NESTING_BUDGET: u32 = 5;4344#[frame_support::pallet]45pub mod pallet {46 use super::*;47 use pallet_evm::account;4849 #[pallet::config]50 pub trait Config:51 frame_system::Config + pallet_common::Config + pallet_nonfungible::Config + account::Config52 {53 type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;54 }5556 #[pallet::storage]57 #[pallet::getter(fn collection_index)]58 pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;5960 #[pallet::storage]61 pub type UniqueCollectionId<T: Config> =62 StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;6364 #[pallet::storage]65 pub type RmrkInernalCollectionId<T: Config> =66 StorageMap<_, Twox64Concat, CollectionId, RmrkCollectionId, ValueQuery>;6768 #[pallet::pallet]69 #[pallet::generate_store(pub(super) trait Store)]70 pub struct Pallet<T>(_);7172 #[pallet::event]73 #[pallet::generate_deposit(pub(super) fn deposit_event)]74 pub enum Event<T: Config> {75 CollectionCreated {76 issuer: T::AccountId,77 collection_id: RmrkCollectionId,78 },79 CollectionDestroyed {80 issuer: T::AccountId,81 collection_id: RmrkCollectionId,82 },83 IssuerChanged {84 old_issuer: T::AccountId,85 new_issuer: T::AccountId,86 collection_id: RmrkCollectionId,87 },88 CollectionLocked {89 issuer: T::AccountId,90 collection_id: RmrkCollectionId,91 },92 NftMinted {93 owner: T::AccountId,94 collection_id: RmrkCollectionId,95 nft_id: RmrkNftId,96 },97 NFTBurned {98 owner: T::AccountId,99 nft_id: RmrkNftId,100 },101 PropertySet {102 collection_id: RmrkCollectionId,103 maybe_nft_id: Option<RmrkNftId>,104 key: RmrkKeyString,105 value: RmrkValueString,106 },107 ResourceAdded {108 nft_id: RmrkNftId,109 resource_id: RmrkResourceId,110 },111 ResourceRemoval {112 nft_id: RmrkNftId,113 resource_id: RmrkResourceId,114 },115 }116117 #[pallet::error]118 pub enum Error<T> {119 /* Unique-specific events */120 CorruptedCollectionType,121 NftTypeEncodeError,122 RmrkPropertyKeyIsTooLong,123 RmrkPropertyValueIsTooLong,124125 /* RMRK compatible events */126 CollectionNotEmpty,127 NoAvailableCollectionId,128 NoAvailableNftId,129 CollectionUnknown,130 NoPermission,131 NonTransferable,132 CollectionFullOrLocked,133 ResourceDoesntExist,134 CannotSendToDescendentOrSelf,135 CannotAcceptNonOwnedNft,136 CannotRejectNonOwnedNft,137 ResourceNotPending,138 }139140 #[pallet::call]141 impl<T: Config> Pallet<T> {142 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]143 #[transactional]144 pub fn create_collection(145 origin: OriginFor<T>,146 metadata: RmrkString,147 max: Option<u32>,148 symbol: RmrkCollectionSymbol,149 ) -> DispatchResult {150 let sender = ensure_signed(origin)?;151152 let limits = CollectionLimits {153 owner_can_transfer: Some(false),154 token_limit: max,155 ..Default::default()156 };157158 let data = CreateCollectionData {159 limits: Some(limits),160 token_prefix: symbol161 .into_inner()162 .try_into()163 .map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,164 permissions: Some(CollectionPermissions {165 nesting: Some(NestingRule::Owner),166 ..Default::default()167 }),168 ..Default::default()169 };170171 let unique_collection_id = Self::init_collection(172 T::CrossAccountId::from_sub(sender.clone()),173 data,174 [175 Self::rmrk_property(Metadata, &metadata)?,176 Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,177 ]178 .into_iter(),179 )?;180 let rmrk_collection_id = <CollectionIndex<T>>::get();181182 <UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);183 <RmrkInernalCollectionId<T>>::insert(unique_collection_id, rmrk_collection_id);184185 <CollectionIndex<T>>::mutate(|n| *n += 1);186187 Self::deposit_event(Event::CollectionCreated {188 issuer: sender,189 collection_id: rmrk_collection_id,190 });191192 Ok(())193 }194195 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]196 #[transactional]197 pub fn destroy_collection(198 origin: OriginFor<T>,199 collection_id: RmrkCollectionId,200 ) -> DispatchResult {201 let sender = ensure_signed(origin)?;202 let cross_sender = T::CrossAccountId::from_sub(sender.clone());203204 let collection = Self::get_typed_nft_collection(205 Self::unique_collection_id(collection_id)?,206 misc::CollectionType::Regular,207 )?;208209 <PalletNft<T>>::destroy_collection(collection, &cross_sender)210 .map_err(Self::map_unique_err_to_proxy)?;211212 Self::deposit_event(Event::CollectionDestroyed {213 issuer: sender,214 collection_id,215 });216217 Ok(())218 }219220 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]221 #[transactional]222 pub fn change_collection_issuer(223 origin: OriginFor<T>,224 collection_id: RmrkCollectionId,225 new_issuer: <T::Lookup as StaticLookup>::Source,226 ) -> DispatchResult {227 let sender = ensure_signed(origin)?;228229 let new_issuer = T::Lookup::lookup(new_issuer)?;230231 Self::change_collection_owner(232 Self::unique_collection_id(collection_id)?,233 misc::CollectionType::Regular,234 sender.clone(),235 new_issuer.clone(),236 )?;237238 Self::deposit_event(Event::IssuerChanged {239 old_issuer: sender,240 new_issuer,241 collection_id,242 });243244 Ok(())245 }246247 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]248 #[transactional]249 pub fn lock_collection(250 origin: OriginFor<T>,251 collection_id: RmrkCollectionId,252 ) -> DispatchResult {253 let sender = ensure_signed(origin)?;254 let cross_sender = T::CrossAccountId::from_sub(sender.clone());255256 let collection = Self::get_typed_nft_collection(257 Self::unique_collection_id(collection_id)?,258 misc::CollectionType::Regular,259 )?;260261 Self::check_collection_owner(&collection, &cross_sender)?;262263 let token_count = collection.total_supply();264265 let mut collection = collection.into_inner();266 collection.limits.token_limit = Some(token_count);267 collection.save()?;268269 Self::deposit_event(Event::CollectionLocked {270 issuer: sender,271 collection_id,272 });273274 Ok(())275 }276277 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]278 #[transactional]279 pub fn mint_nft(280 origin: OriginFor<T>,281 owner: T::AccountId,282 collection_id: RmrkCollectionId,283 recipient: Option<T::AccountId>,284 royalty_amount: Option<Permill>,285 metadata: RmrkString,286 transferable: bool,287 ) -> DispatchResult {288 let sender = ensure_signed(origin)?;289 let sender = T::CrossAccountId::from_sub(sender);290 let cross_owner = T::CrossAccountId::from_sub(owner.clone());291292 let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {293 recipient: recipient.unwrap_or_else(|| owner.clone()),294 amount,295 });296297 let collection = Self::get_typed_nft_collection(298 Self::unique_collection_id(collection_id)?,299 misc::CollectionType::Regular,300 )?;301302 let nft_id = Self::create_nft(303 &sender,304 &cross_owner,305 &collection,306 [307 Self::rmrk_property(TokenType, &NftType::Regular)?,308 Self::rmrk_property(Transferable, &transferable)?,309 Self::rmrk_property(PendingNftAccept, &false)?,310 Self::rmrk_property(RoyaltyInfo, &royalty_info)?,311 Self::rmrk_property(Metadata, &metadata)?,312 Self::rmrk_property(Equipped, &false)?,313 Self::rmrk_property(314 ResourceCollection,315 &Self::init_collection(316 sender.clone(),317 CreateCollectionData {318 ..Default::default()319 },320 [Self::rmrk_property(321 CollectionType,322 &misc::CollectionType::Resource,323 )?]324 .into_iter(),325 )?,326 )?, // todo possibly add limits to the collection if rmrk warrants them327 Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,328 ]329 .into_iter(),330 )331 .map_err(|err| match err {332 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),333 err => Self::map_unique_err_to_proxy(err),334 })?;335336 Self::deposit_event(Event::NftMinted {337 owner,338 collection_id,339 nft_id: nft_id.0,340 });341342 Ok(())343 }344345 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]346 #[transactional]347 pub fn burn_nft(348 origin: OriginFor<T>,349 collection_id: RmrkCollectionId,350 nft_id: RmrkNftId,351 ) -> DispatchResult {352 let sender = ensure_signed(origin)?;353 let cross_sender = T::CrossAccountId::from_sub(sender.clone());354355 Self::destroy_nft(356 cross_sender,357 Self::unique_collection_id(collection_id)?,358 nft_id.into(),359 )360 .map_err(Self::map_unique_err_to_proxy)?;361362 Self::deposit_event(Event::NFTBurned {363 owner: sender,364 nft_id,365 });366367 Ok(())368 }369370 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]371 #[transactional]372 pub fn send(373 origin: OriginFor<T>,374 rmrk_collection_id: RmrkCollectionId,375 rmrk_nft_id: RmrkNftId,376 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,377 ) -> DispatchResult {378 let sender = ensure_signed(origin.clone())?;379 let cross_sender = T::CrossAccountId::from_sub(sender);380381 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;382 let nft_id = rmrk_nft_id.into();383384 let token_data =385 <TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;386387 let from = token_data.owner;388389 let collection =390 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;391392 ensure!(393 Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,394 <Error<T>>::NonTransferable395 );396397 ensure!(398 !Self::get_nft_property_decoded(399 collection_id,400 nft_id,401 RmrkProperty::PendingNftAccept402 )?,403 <Error<T>>::NoPermission404 );405406 let target_owner;407408 match new_owner {409 RmrkAccountIdOrCollectionNftTuple::AccountId(account_id) => {410 target_owner = T::CrossAccountId::from_sub(account_id);411 }412 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(413 target_collection_id,414 target_nft_id,415 ) => {416 let target_collection_id = Self::unique_collection_id(target_collection_id)?;417418 let target_nft_budget = budget::Value::new(NESTING_BUDGET);419420 let target_nft_owner = <PalletStructure<T>>::get_checked_indirect_owner(421 target_collection_id,422 target_nft_id.into(),423 Some((collection_id, nft_id)),424 &target_nft_budget,425 )426 .map_err(Self::map_unique_err_to_proxy)?;427428 let is_approval_required = cross_sender != target_nft_owner;429430 if is_approval_required {431 target_owner = target_nft_owner;432433 <PalletNft<T>>::set_scoped_token_property(434 collection.id,435 nft_id,436 PropertyScope::Rmrk,437 Self::rmrk_property(PendingNftAccept, &is_approval_required)?,438 )?;439 } else {440 target_owner = T::CrossTokenAddressMapping::token_to_address(441 target_collection_id,442 target_nft_id.into(),443 );444 }445 }446 }447448 let src_nft_budget = budget::Value::new(NESTING_BUDGET);449450 <PalletNft<T>>::transfer_from(451 &collection,452 &cross_sender,453 &from,454 &target_owner,455 nft_id,456 &src_nft_budget,457 )458 .map_err(Self::map_unique_err_to_proxy)?;459460 Ok(())461 }462463 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]464 #[transactional]465 pub fn accept_nft(466 origin: OriginFor<T>,467 rmrk_collection_id: RmrkCollectionId,468 rmrk_nft_id: RmrkNftId,469 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,470 ) -> DispatchResult {471 let sender = ensure_signed(origin.clone())?;472 let cross_sender = T::CrossAccountId::from_sub(sender);473474 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;475 let nft_id = rmrk_nft_id.into();476477 let collection =478 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;479480 let new_owner = match new_owner {481 RmrkAccountIdOrCollectionNftTuple::AccountId(account_id) => {482 T::CrossAccountId::from_sub(account_id)483 }484 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(485 target_collection_id,486 target_nft_id,487 ) => {488 let target_collection_id = Self::unique_collection_id(target_collection_id)?;489490 T::CrossTokenAddressMapping::token_to_address(491 target_collection_id,492 TokenId(target_nft_id),493 )494 }495 };496497 let budget = budget::Value::new(NESTING_BUDGET);498499 <PalletNft<T>>::transfer(&collection, &cross_sender, &new_owner, nft_id, &budget)500 .map_err(|err| {501 if err == <CommonError<T>>::OnlyOwnerAllowedToNest.into() {502 <Error<T>>::CannotAcceptNonOwnedNft.into()503 } else {504 Self::map_unique_err_to_proxy(err)505 }506 })?;507508 <PalletNft<T>>::set_scoped_token_property(509 collection.id,510 nft_id,511 PropertyScope::Rmrk,512 Self::rmrk_property(PendingNftAccept, &false)?,513 )?;514515 Ok(())516 }517518 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]519 #[transactional]520 pub fn reject_nft(521 origin: OriginFor<T>,522 collection_id: RmrkCollectionId,523 nft_id: RmrkNftId,524 ) -> DispatchResult {525 let sender = ensure_signed(origin)?;526 let cross_sender = T::CrossAccountId::from_sub(sender);527528 let collection_id = Self::unique_collection_id(collection_id)?;529 let nft_id = nft_id.into();530531 Self::destroy_nft(cross_sender, collection_id, nft_id).map_err(|err| {532 if err == <CommonError<T>>::NoPermission.into()533 || err == <CommonError<T>>::ApprovedValueTooLow.into()534 {535 <Error<T>>::CannotRejectNonOwnedNft.into()536 } else {537 Self::map_unique_err_to_proxy(err)538 }539 })?;540541 Ok(())542 }543544 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]545 #[transactional]546 pub fn accept_resource(547 origin: OriginFor<T>,548 rmrk_collection_id: RmrkCollectionId,549 rmrk_nft_id: RmrkNftId,550 rmrk_resource_id: RmrkResourceId,551 ) -> DispatchResult {552 let sender = ensure_signed(origin)?;553 let cross_sender = T::CrossAccountId::from_sub(sender);554555 let collection_id = Self::unique_collection_id(rmrk_collection_id)556 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;557558 let nft_id = rmrk_nft_id.into();559 let resource_id = rmrk_resource_id.into();560561 let budget = budget::Value::new(NESTING_BUDGET);562563 let nft_owner =564 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)565 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;566567 let resource_collection_id: CollectionId =568 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)569 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;570571 let is_pending: bool = Self::get_nft_property_decoded(572 resource_collection_id,573 resource_id,574 PendingResourceAccept,575 )576 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;577578 ensure!(is_pending, <Error<T>>::ResourceNotPending);579580 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);581582 <PalletNft<T>>::set_scoped_token_property(583 resource_collection_id,584 rmrk_resource_id.into(),585 PropertyScope::Rmrk,586 Self::rmrk_property(PendingResourceAccept, &false)?,587 )?;588589 Ok(())590 }591592 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]593 #[transactional]594 pub fn accept_resource_removal(595 origin: OriginFor<T>,596 rmrk_collection_id: RmrkCollectionId,597 rmrk_nft_id: RmrkNftId,598 rmrk_resource_id: RmrkResourceId,599 ) -> DispatchResult {600 let sender = ensure_signed(origin)?;601 let cross_sender = T::CrossAccountId::from_sub(sender);602603 let collection_id = Self::unique_collection_id(rmrk_collection_id)604 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;605606 let nft_id = rmrk_nft_id.into();607 let resource_id = rmrk_resource_id.into();608609 let budget = budget::Value::new(NESTING_BUDGET);610611 let nft_owner =612 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)613 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;614615 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);616617 let resource_collection_id: CollectionId =618 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)619 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;620621 let is_pending: bool = Self::get_nft_property_decoded(622 resource_collection_id,623 resource_id,624 PendingResourceRemoval,625 )626 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;627628 ensure!(is_pending, <Error<T>>::ResourceNotPending);629630 let resource_collection = Self::get_typed_nft_collection(631 resource_collection_id,632 misc::CollectionType::Resource,633 )?;634635 <PalletNft<T>>::burn(&resource_collection, &cross_sender, rmrk_resource_id.into())636 .map_err(Self::map_unique_err_to_proxy)?;637638 Ok(())639 }640641 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]642 #[transactional]643 pub fn set_property(644 origin: OriginFor<T>,645 #[pallet::compact] rmrk_collection_id: RmrkCollectionId,646 maybe_nft_id: Option<RmrkNftId>,647 key: RmrkKeyString,648 value: RmrkValueString,649 ) -> DispatchResult {650 let sender = ensure_signed(origin)?;651 let sender = T::CrossAccountId::from_sub(sender);652653 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;654 let budget = budget::Value::new(NESTING_BUDGET);655656 match maybe_nft_id {657 Some(nft_id) => {658 let token_id: TokenId = nft_id.into();659660 Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;661 Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;662663 <PalletNft<T>>::set_scoped_token_property(664 collection_id,665 token_id,666 PropertyScope::Rmrk,667 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,668 )?;669 }670 None => {671 let collection = Self::get_typed_nft_collection(672 collection_id,673 misc::CollectionType::Regular,674 )?;675676 Self::check_collection_owner(&collection, &sender)?;677678 <PalletCommon<T>>::set_scoped_collection_property(679 collection_id,680 PropertyScope::Rmrk,681 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,682 )?;683 }684 }685686 Self::deposit_event(Event::PropertySet {687 collection_id: rmrk_collection_id,688 maybe_nft_id,689 key,690 value,691 });692693 Ok(())694 }695696 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]697 #[transactional]698 pub fn set_priority(699 origin: OriginFor<T>,700 rmrk_collection_id: RmrkCollectionId,701 rmrk_nft_id: RmrkNftId,702 priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,703 ) -> DispatchResult {704 let sender = ensure_signed(origin)?;705 let sender = T::CrossAccountId::from_sub(sender);706707 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;708 let nft_id = rmrk_nft_id.into();709 let budget = budget::Value::new(NESTING_BUDGET);710711 Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;712 Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;713714 <PalletNft<T>>::set_scoped_token_property(715 collection_id,716 nft_id,717 PropertyScope::Rmrk,718 Self::rmrk_property(ResourcePriorities, &priorities.into_inner())?719 )?;720721 Ok(())722 }723724 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]725 #[transactional]726 pub fn add_basic_resource(727 origin: OriginFor<T>,728 collection_id: RmrkCollectionId,729 nft_id: RmrkNftId,730 resource: RmrkBasicResource,731 ) -> DispatchResult {732 let sender = ensure_signed(origin.clone())?;733734 let resource_id = Self::resource_add(735 sender,736 Self::unique_collection_id(collection_id)?,737 nft_id.into(),738 [739 Self::rmrk_property(TokenType, &NftType::Resource)?,740 Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,741 Self::rmrk_property(Src, &resource.src)?,742 Self::rmrk_property(Metadata, &resource.metadata)?,743 Self::rmrk_property(License, &resource.license)?,744 Self::rmrk_property(Thumb, &resource.thumb)?,745 ]746 .into_iter(),747 )?;748749 Self::deposit_event(Event::ResourceAdded {750 nft_id,751 resource_id,752 });753 Ok(())754 }755756 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]757 #[transactional]758 pub fn add_composable_resource(759 origin: OriginFor<T>,760 collection_id: RmrkCollectionId,761 nft_id: RmrkNftId,762 _resource_id: RmrkBoundedResource,763 resource: RmrkComposableResource,764 ) -> DispatchResult {765 let sender = ensure_signed(origin.clone())?;766767 let resource_id = Self::resource_add(768 sender,769 Self::unique_collection_id(collection_id)?,770 nft_id.into(),771 [772 Self::rmrk_property(TokenType, &NftType::Resource)?,773 Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,774 Self::rmrk_property(Parts, &resource.parts)?,775 Self::rmrk_property(Base, &resource.base)?,776 Self::rmrk_property(Src, &resource.src)?,777 Self::rmrk_property(Metadata, &resource.metadata)?,778 Self::rmrk_property(License, &resource.license)?,779 Self::rmrk_property(Thumb, &resource.thumb)?,780 ]781 .into_iter(),782 )?;783784 Self::deposit_event(Event::ResourceAdded {785 nft_id,786 resource_id,787 });788 Ok(())789 }790791 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]792 #[transactional]793 pub fn add_slot_resource(794 origin: OriginFor<T>,795 collection_id: RmrkCollectionId,796 nft_id: RmrkNftId,797 resource: RmrkSlotResource,798 ) -> DispatchResult {799 let sender = ensure_signed(origin.clone())?;800801 let resource_id = Self::resource_add(802 sender,803 Self::unique_collection_id(collection_id)?,804 nft_id.into(),805 [806 Self::rmrk_property(TokenType, &NftType::Resource)?,807 Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,808 Self::rmrk_property(Base, &resource.base)?,809 Self::rmrk_property(Src, &resource.src)?,810 Self::rmrk_property(Metadata, &resource.metadata)?,811 Self::rmrk_property(Slot, &resource.slot)?,812 Self::rmrk_property(License, &resource.license)?,813 Self::rmrk_property(Thumb, &resource.thumb)?,814 ]815 .into_iter(),816 )?;817818 Self::deposit_event(Event::ResourceAdded {819 nft_id,820 resource_id,821 });822 Ok(())823 }824825 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]826 #[transactional]827 pub fn remove_resource(828 origin: OriginFor<T>,829 collection_id: RmrkCollectionId,830 nft_id: RmrkNftId,831 resource_id: RmrkResourceId,832 ) -> DispatchResult {833 let sender = ensure_signed(origin.clone())?;834835 Self::resource_remove(836 sender,837 Self::unique_collection_id(collection_id)?,838 nft_id.into(),839 resource_id.into(),840 )?;841842 Self::deposit_event(Event::ResourceRemoval {843 nft_id,844 resource_id,845 });846 Ok(())847 }848 }849}850851impl<T: Config> Pallet<T> {852 pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {853 let key = rmrk_key.to_key::<T>()?;854855 let scoped_key = PropertyScope::Rmrk856 .apply(key)857 .map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;858859 Ok(scoped_key)860 }861862 // todo think about renaming these863 pub fn rmrk_property<E: Encode>(864 rmrk_key: RmrkProperty,865 value: &E,866 ) -> Result<Property, DispatchError> {867 let key = rmrk_key.to_key::<T>()?;868869 let value = value870 .encode()871 .try_into()872 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;873874 let property = Property { key, value };875876 Ok(property)877 }878879 pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {880 vec.decode()881 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())882 }883884 pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>885 where886 BoundedVec<u8, S>: TryFrom<Vec<u8>>,887 {888 vec.rebind()889 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())890 }891892 fn init_collection(893 sender: T::CrossAccountId,894 data: CreateCollectionData<T::AccountId>,895 properties: impl Iterator<Item = Property>,896 ) -> Result<CollectionId, DispatchError> {897 let collection_id = <PalletNft<T>>::init_collection(sender, data);898899 if let Err(DispatchError::Arithmetic(_)) = &collection_id {900 return Err(<Error<T>>::NoAvailableCollectionId.into());901 }902903 <PalletCommon<T>>::set_scoped_collection_properties(904 collection_id?,905 PropertyScope::Rmrk,906 properties,907 )?;908909 collection_id910 }911912 pub fn create_nft(913 sender: &T::CrossAccountId,914 owner: &T::CrossAccountId,915 collection: &NonfungibleHandle<T>,916 properties: impl Iterator<Item = Property>,917 ) -> Result<TokenId, DispatchError> {918 let data = CreateNftExData {919 properties: BoundedVec::default(),920 owner: owner.clone(),921 };922923 let budget = budget::Value::new(NESTING_BUDGET);924925 <PalletNft<T>>::create_item(collection, sender, data, &budget)?;926927 let nft_id = <PalletNft<T>>::current_token_id(collection.id);928929 <PalletNft<T>>::set_scoped_token_properties(930 collection.id,931 nft_id,932 PropertyScope::Rmrk,933 properties,934 )?;935936 Ok(nft_id)937 }938939 fn destroy_nft(940 sender: T::CrossAccountId,941 collection_id: CollectionId,942 token_id: TokenId,943 ) -> DispatchResult {944 let collection =945 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;946947 let token_data =948 <TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;949950 let from = token_data.owner;951952 let budget = budget::Value::new(NESTING_BUDGET);953954 <PalletNft<T>>::burn_from(&collection, &sender, &from, token_id, &budget)955 }956957 fn resource_add(958 sender: T::AccountId,959 collection_id: CollectionId,960 token_id: TokenId,961 resource_properties: impl Iterator<Item = Property>,962 ) -> Result<RmrkResourceId, DispatchError> {963 let collection =964 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;965 ensure!(collection.owner == sender, Error::<T>::NoPermission);966967 let sender = T::CrossAccountId::from_sub(sender);968 let budget = budget::Value::new(NESTING_BUDGET);969970 let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, token_id, &budget)971 .map_err(Self::map_unique_err_to_proxy)?;972973 let pending = sender != nft_owner;974975 let resource_collection_id: CollectionId =976 Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;977 let resource_collection =978 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;979980 // todo probably add extra connections to bases, slots, etc., when RMRK starts to use them981982 let resource_id = Self::create_nft(983 &sender,984 &nft_owner,985 &resource_collection,986 resource_properties.chain(987 [988 Self::rmrk_property(PendingResourceAccept, &pending)?,989 Self::rmrk_property(PendingResourceRemoval, &false)?,990 ]991 .into_iter(),992 ),993 )994 .map_err(|err| match err {995 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),996 err => Self::map_unique_err_to_proxy(err),997 })?;998999 Ok(resource_id.0)1000 }10011002 fn resource_remove(1003 sender: T::AccountId,1004 collection_id: CollectionId,1005 nft_id: TokenId,1006 resource_id: TokenId,1007 ) -> DispatchResult {1008 let collection =1009 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1010 ensure!(collection.owner == sender, Error::<T>::NoPermission);10111012 let resource_collection_id: CollectionId =1013 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;1014 let resource_collection =1015 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;1016 ensure!(1017 <PalletNft<T>>::token_exists(&resource_collection, resource_id),1018 Error::<T>::ResourceDoesntExist1019 );10201021 let budget = up_data_structs::budget::Value::new(10);1022 let topmost_owner =1023 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;10241025 let sender = T::CrossAccountId::from_sub(sender);1026 if topmost_owner == sender {1027 <PalletNft<T>>::burn(&resource_collection, &sender, resource_id)1028 .map_err(Self::map_unique_err_to_proxy)?;1029 } else {1030 <PalletNft<T>>::set_scoped_token_property(1031 resource_collection_id,1032 resource_id,1033 PropertyScope::Rmrk,1034 Self::rmrk_property(PendingResourceRemoval, &true)?,1035 )?;1036 }10371038 Ok(())1039 }10401041 fn change_collection_owner(1042 collection_id: CollectionId,1043 collection_type: misc::CollectionType,1044 sender: T::AccountId,1045 new_owner: T::AccountId,1046 ) -> DispatchResult {1047 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1048 Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;10491050 let mut collection = collection.into_inner();10511052 collection.owner = new_owner;1053 collection.save()1054 }10551056 fn check_collection_owner(1057 collection: &NonfungibleHandle<T>,1058 account: &T::CrossAccountId,1059 ) -> DispatchResult {1060 collection1061 .check_is_owner(account)1062 .map_err(Self::map_unique_err_to_proxy)1063 }10641065 pub fn last_collection_idx() -> RmrkCollectionId {1066 <CollectionIndex<T>>::get()1067 }10681069 pub fn unique_collection_id(1070 rmrk_collection_id: RmrkCollectionId,1071 ) -> Result<CollectionId, DispatchError> {1072 <UniqueCollectionId<T>>::try_get(rmrk_collection_id)1073 .map_err(|_| <Error<T>>::CollectionUnknown.into())1074 }10751076 pub fn rmrk_collection_id(1077 unique_collection_id: CollectionId,1078 ) -> Result<RmrkCollectionId, DispatchError> {1079 <RmrkInernalCollectionId<T>>::try_get(unique_collection_id)1080 .map_err(|_| <Error<T>>::CollectionUnknown.into())1081 }10821083 pub fn get_nft_collection(1084 collection_id: CollectionId,1085 ) -> Result<NonfungibleHandle<T>, DispatchError> {1086 let collection = <CollectionHandle<T>>::try_get(collection_id)1087 .map_err(|_| <Error<T>>::CollectionUnknown)?;10881089 match collection.mode {1090 CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1091 _ => Err(<Error<T>>::CollectionUnknown.into()),1092 }1093 }10941095 pub fn collection_exists(collection_id: CollectionId) -> bool {1096 <CollectionHandle<T>>::try_get(collection_id).is_ok()1097 }10981099 pub fn get_collection_property(1100 collection_id: CollectionId,1101 key: RmrkProperty,1102 ) -> Result<PropertyValue, DispatchError> {1103 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1104 .get(&Self::rmrk_property_key(key)?)1105 .ok_or(<Error<T>>::CollectionUnknown)?1106 .clone();11071108 Ok(collection_property)1109 }11101111 pub fn get_collection_property_decoded<V: Decode>(1112 collection_id: CollectionId,1113 key: RmrkProperty,1114 ) -> Result<V, DispatchError> {1115 Self::decode_property(Self::get_collection_property(collection_id, key)?)1116 }11171118 pub fn get_collection_type(1119 collection_id: CollectionId,1120 ) -> Result<misc::CollectionType, DispatchError> {1121 Self::get_collection_property_decoded(collection_id, CollectionType)1122 .map_err(|_| <Error<T>>::CorruptedCollectionType.into())1123 }11241125 pub fn ensure_collection_type(1126 collection_id: CollectionId,1127 collection_type: misc::CollectionType,1128 ) -> DispatchResult {1129 let actual_type = Self::get_collection_type(collection_id)?;1130 ensure!(1131 actual_type == collection_type,1132 <CommonError<T>>::NoPermission1133 );11341135 Ok(())1136 }11371138 pub fn get_typed_nft_collection(1139 collection_id: CollectionId,1140 collection_type: misc::CollectionType,1141 ) -> Result<NonfungibleHandle<T>, DispatchError> {1142 Self::ensure_collection_type(collection_id, collection_type)?;11431144 Self::get_nft_collection(collection_id)1145 }11461147 pub fn get_typed_nft_collection_mapped(1148 rmrk_collection_id: RmrkCollectionId,1149 collection_type: misc::CollectionType,1150 ) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1151 let unique_collection_id = match collection_type {1152 misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1153 _ => rmrk_collection_id.into()1154 };11551156 let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;11571158 Ok((collection, unique_collection_id))1159 }11601161 pub fn get_nft_property(1162 collection_id: CollectionId,1163 nft_id: TokenId,1164 key: RmrkProperty,1165 ) -> Result<PropertyValue, DispatchError> {1166 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1167 .get(&Self::rmrk_property_key(key)?)1168 .ok_or(<Error<T>>::NoAvailableNftId)? // todo replace with better error?1169 .clone();11701171 Ok(nft_property)1172 }11731174 pub fn get_nft_property_decoded<V: Decode>(1175 collection_id: CollectionId,1176 nft_id: TokenId,1177 key: RmrkProperty,1178 ) -> Result<V, DispatchError> {1179 Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)1180 }11811182 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1183 <TokenData<T>>::contains_key((collection_id, nft_id))1184 }11851186 pub fn get_nft_type(1187 collection_id: CollectionId,1188 token_id: TokenId,1189 ) -> Result<NftType, DispatchError> {1190 Self::get_nft_property_decoded(collection_id, token_id, TokenType)1191 .map_err(|_| <Error<T>>::NoAvailableNftId.into())1192 }11931194 pub fn ensure_nft_type(1195 collection_id: CollectionId,1196 token_id: TokenId,1197 nft_type: NftType,1198 ) -> DispatchResult {1199 let actual_type = Self::get_nft_type(collection_id, token_id)?;1200 ensure!(actual_type == nft_type, <Error<T>>::NoPermission);12011202 Ok(())1203 }12041205 pub fn ensure_nft_owner(1206 collection_id: CollectionId,1207 token_id: TokenId,1208 possible_owner: &T::CrossAccountId,1209 nesting_budget: &dyn budget::Budget,1210 ) -> DispatchResult {1211 let is_owned = <PalletStructure<T>>::check_indirectly_owned(1212 possible_owner.clone(),1213 collection_id,1214 token_id,1215 None,1216 nesting_budget,1217 ).map_err(Self::map_unique_err_to_proxy)?;12181219 ensure!(is_owned, <Error<T>>::NoPermission);12201221 Ok(())1222 }12231224 pub fn filter_user_properties<Key, Value, R, Mapper>(1225 collection_id: CollectionId,1226 token_id: Option<TokenId>,1227 filter_keys: Option<Vec<RmrkPropertyKey>>,1228 mapper: Mapper,1229 ) -> Result<Vec<R>, DispatchError>1230 where1231 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1232 Value: Decode + Default,1233 Mapper: Fn(Key, Value) -> R,1234 {1235 filter_keys1236 .map(|keys| {1237 let properties = keys1238 .into_iter()1239 .filter_map(|key| {1240 let key: Key = key.try_into().ok()?;12411242 let value = match token_id {1243 Some(token_id) => Self::get_nft_property_decoded(1244 collection_id,1245 token_id,1246 UserProperty(key.as_ref()),1247 ),1248 None => Self::get_collection_property_decoded(1249 collection_id,1250 UserProperty(key.as_ref()),1251 ),1252 }1253 .ok()?;12541255 Some(mapper(key, value))1256 })1257 .collect();12581259 Ok(properties)1260 })1261 .unwrap_or_else(|| {1262 let properties =1263 Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();12641265 Ok(properties)1266 })1267 }12681269 pub fn iterate_user_properties<Key, Value, R, Mapper>(1270 collection_id: CollectionId,1271 token_id: Option<TokenId>,1272 mapper: Mapper,1273 ) -> Result<impl Iterator<Item = R>, DispatchError>1274 where1275 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1276 Value: Decode + Default,1277 Mapper: Fn(Key, Value) -> R,1278 {1279 let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;12801281 let properties = match token_id {1282 Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1283 None => <PalletCommon<T>>::collection_properties(collection_id),1284 };12851286 let properties = properties.into_iter().filter_map(move |(key, value)| {1287 let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;12881289 let key: Key = key.to_vec().try_into().ok()?;1290 let value: Value = value.decode().ok()?;12911292 Some(mapper(key, value))1293 });12941295 Ok(properties)1296 }12971298 fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1299 map_unique_err_to_proxy! {1300 match err {1301 CommonError::NoPermission => NoPermission,1302 CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1303 CommonError::PublicMintingNotAllowed => NoPermission,1304 CommonError::TokenNotFound => NoAvailableNftId,1305 CommonError::ApprovedValueTooLow => NoPermission,1306 CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1307 StructureError::TokenNotFound => NoAvailableNftId,1308 StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1309 }1310 }1311 }1312}1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![cfg_attr(not(feature = "std"), no_std)]1819use frame_support::{pallet_prelude::*, transactional, BoundedVec, dispatch::DispatchResult};20use frame_system::{pallet_prelude::*, ensure_signed};21use sp_runtime::{DispatchError, Permill, traits::StaticLookup};22use sp_std::vec::Vec;23use up_data_structs::{*, mapping::TokenAddressMapping};24use pallet_common::{25 Pallet as PalletCommon, Error as CommonError, CollectionHandle, CommonCollectionOperations,26};27use pallet_nonfungible::{Pallet as PalletNft, NonfungibleHandle, TokenData};28use pallet_structure::{Pallet as PalletStructure, Error as StructureError};29use pallet_evm::account::CrossAccountId;30use core::convert::AsRef;3132pub use pallet::*;3334pub mod misc;35pub mod property;3637use misc::*;38pub use property::*;3940use RmrkProperty::*;4142const NESTING_BUDGET: u32 = 5;4344#[frame_support::pallet]45pub mod pallet {46 use super::*;47 use pallet_evm::account;4849 #[pallet::config]50 pub trait Config:51 frame_system::Config + pallet_common::Config + pallet_nonfungible::Config + account::Config52 {53 type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;54 }5556 #[pallet::storage]57 #[pallet::getter(fn collection_index)]58 pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;5960 #[pallet::storage]61 pub type UniqueCollectionId<T: Config> =62 StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;6364 #[pallet::storage]65 pub type RmrkInernalCollectionId<T: Config> =66 StorageMap<_, Twox64Concat, CollectionId, RmrkCollectionId, ValueQuery>;6768 #[pallet::pallet]69 #[pallet::generate_store(pub(super) trait Store)]70 pub struct Pallet<T>(_);7172 #[pallet::event]73 #[pallet::generate_deposit(pub(super) fn deposit_event)]74 pub enum Event<T: Config> {75 CollectionCreated {76 issuer: T::AccountId,77 collection_id: RmrkCollectionId,78 },79 CollectionDestroyed {80 issuer: T::AccountId,81 collection_id: RmrkCollectionId,82 },83 IssuerChanged {84 old_issuer: T::AccountId,85 new_issuer: T::AccountId,86 collection_id: RmrkCollectionId,87 },88 CollectionLocked {89 issuer: T::AccountId,90 collection_id: RmrkCollectionId,91 },92 NftMinted {93 owner: T::AccountId,94 collection_id: RmrkCollectionId,95 nft_id: RmrkNftId,96 },97 NFTBurned {98 owner: T::AccountId,99 nft_id: RmrkNftId,100 },101 PropertySet {102 collection_id: RmrkCollectionId,103 maybe_nft_id: Option<RmrkNftId>,104 key: RmrkKeyString,105 value: RmrkValueString,106 },107 ResourceAdded {108 nft_id: RmrkNftId,109 resource_id: RmrkResourceId,110 },111 ResourceRemoval {112 nft_id: RmrkNftId,113 resource_id: RmrkResourceId,114 },115 }116117 #[pallet::error]118 pub enum Error<T> {119 /* Unique-specific events */120 CorruptedCollectionType,121 NftTypeEncodeError,122 RmrkPropertyKeyIsTooLong,123 RmrkPropertyValueIsTooLong,124125 /* RMRK compatible events */126 CollectionNotEmpty,127 NoAvailableCollectionId,128 NoAvailableNftId,129 CollectionUnknown,130 NoPermission,131 NonTransferable,132 CollectionFullOrLocked,133 ResourceDoesntExist,134 CannotSendToDescendentOrSelf,135 CannotAcceptNonOwnedNft,136 CannotRejectNonOwnedNft,137 ResourceNotPending,138 }139140 #[pallet::call]141 impl<T: Config> Pallet<T> {142 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]143 #[transactional]144 pub fn create_collection(145 origin: OriginFor<T>,146 metadata: RmrkString,147 max: Option<u32>,148 symbol: RmrkCollectionSymbol,149 ) -> DispatchResult {150 let sender = ensure_signed(origin)?;151152 let limits = CollectionLimits {153 owner_can_transfer: Some(false),154 token_limit: max,155 ..Default::default()156 };157158 let data = CreateCollectionData {159 limits: Some(limits),160 token_prefix: symbol161 .into_inner()162 .try_into()163 .map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,164 permissions: Some(CollectionPermissions {165 nesting: Some(NestingRule::Owner),166 ..Default::default()167 }),168 ..Default::default()169 };170171 let unique_collection_id = Self::init_collection(172 T::CrossAccountId::from_sub(sender.clone()),173 data,174 [175 Self::rmrk_property(Metadata, &metadata)?,176 Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,177 ]178 .into_iter(),179 )?;180 let rmrk_collection_id = <CollectionIndex<T>>::get();181182 <UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);183 <RmrkInernalCollectionId<T>>::insert(unique_collection_id, rmrk_collection_id);184185 <CollectionIndex<T>>::mutate(|n| *n += 1);186187 Self::deposit_event(Event::CollectionCreated {188 issuer: sender,189 collection_id: rmrk_collection_id,190 });191192 Ok(())193 }194195 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]196 #[transactional]197 pub fn destroy_collection(198 origin: OriginFor<T>,199 collection_id: RmrkCollectionId,200 ) -> DispatchResult {201 let sender = ensure_signed(origin)?;202 let cross_sender = T::CrossAccountId::from_sub(sender.clone());203204 let collection = Self::get_typed_nft_collection(205 Self::unique_collection_id(collection_id)?,206 misc::CollectionType::Regular,207 )?;208209 <PalletNft<T>>::destroy_collection(collection, &cross_sender)210 .map_err(Self::map_unique_err_to_proxy)?;211212 Self::deposit_event(Event::CollectionDestroyed {213 issuer: sender,214 collection_id,215 });216217 Ok(())218 }219220 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]221 #[transactional]222 pub fn change_collection_issuer(223 origin: OriginFor<T>,224 collection_id: RmrkCollectionId,225 new_issuer: <T::Lookup as StaticLookup>::Source,226 ) -> DispatchResult {227 let sender = ensure_signed(origin)?;228229 let new_issuer = T::Lookup::lookup(new_issuer)?;230231 Self::change_collection_owner(232 Self::unique_collection_id(collection_id)?,233 misc::CollectionType::Regular,234 sender.clone(),235 new_issuer.clone(),236 )?;237238 Self::deposit_event(Event::IssuerChanged {239 old_issuer: sender,240 new_issuer,241 collection_id,242 });243244 Ok(())245 }246247 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]248 #[transactional]249 pub fn lock_collection(250 origin: OriginFor<T>,251 collection_id: RmrkCollectionId,252 ) -> DispatchResult {253 let sender = ensure_signed(origin)?;254 let cross_sender = T::CrossAccountId::from_sub(sender.clone());255256 let collection = Self::get_typed_nft_collection(257 Self::unique_collection_id(collection_id)?,258 misc::CollectionType::Regular,259 )?;260261 Self::check_collection_owner(&collection, &cross_sender)?;262263 let token_count = collection.total_supply();264265 let mut collection = collection.into_inner();266 collection.limits.token_limit = Some(token_count);267 collection.save()?;268269 Self::deposit_event(Event::CollectionLocked {270 issuer: sender,271 collection_id,272 });273274 Ok(())275 }276277 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]278 #[transactional]279 pub fn mint_nft(280 origin: OriginFor<T>,281 owner: T::AccountId,282 collection_id: RmrkCollectionId,283 recipient: Option<T::AccountId>,284 royalty_amount: Option<Permill>,285 metadata: RmrkString,286 transferable: bool,287 ) -> DispatchResult {288 let sender = ensure_signed(origin)?;289 let sender = T::CrossAccountId::from_sub(sender);290 let cross_owner = T::CrossAccountId::from_sub(owner.clone());291292 let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {293 recipient: recipient.unwrap_or_else(|| owner.clone()),294 amount,295 });296297 let collection = Self::get_typed_nft_collection(298 Self::unique_collection_id(collection_id)?,299 misc::CollectionType::Regular,300 )?;301302 let nft_id = Self::create_nft(303 &sender,304 &cross_owner,305 &collection,306 [307 Self::rmrk_property(TokenType, &NftType::Regular)?,308 Self::rmrk_property(Transferable, &transferable)?,309 Self::rmrk_property(PendingNftAccept, &false)?,310 Self::rmrk_property(RoyaltyInfo, &royalty_info)?,311 Self::rmrk_property(Metadata, &metadata)?,312 Self::rmrk_property(Equipped, &false)?,313 Self::rmrk_property(314 ResourceCollection,315 &Self::init_collection(316 sender.clone(),317 CreateCollectionData {318 ..Default::default()319 },320 [Self::rmrk_property(321 CollectionType,322 &misc::CollectionType::Resource,323 )?]324 .into_iter(),325 )?,326 )?, // todo possibly add limits to the collection if rmrk warrants them327 Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,328 ]329 .into_iter(),330 )331 .map_err(|err| match err {332 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),333 err => Self::map_unique_err_to_proxy(err),334 })?;335336 Self::deposit_event(Event::NftMinted {337 owner,338 collection_id,339 nft_id: nft_id.0,340 });341342 Ok(())343 }344345 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]346 #[transactional]347 pub fn burn_nft(348 origin: OriginFor<T>,349 collection_id: RmrkCollectionId,350 nft_id: RmrkNftId,351 ) -> DispatchResult {352 let sender = ensure_signed(origin)?;353 let cross_sender = T::CrossAccountId::from_sub(sender.clone());354355 Self::destroy_nft(356 cross_sender,357 Self::unique_collection_id(collection_id)?,358 nft_id.into(),359 )360 .map_err(Self::map_unique_err_to_proxy)?;361362 Self::deposit_event(Event::NFTBurned {363 owner: sender,364 nft_id,365 });366367 Ok(())368 }369370 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]371 #[transactional]372 pub fn send(373 origin: OriginFor<T>,374 rmrk_collection_id: RmrkCollectionId,375 rmrk_nft_id: RmrkNftId,376 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,377 ) -> DispatchResult {378 let sender = ensure_signed(origin.clone())?;379 let cross_sender = T::CrossAccountId::from_sub(sender);380381 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;382 let nft_id = rmrk_nft_id.into();383384 let token_data =385 <TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;386387 let from = token_data.owner;388389 let collection =390 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;391392 ensure!(393 Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,394 <Error<T>>::NonTransferable395 );396397 ensure!(398 !Self::get_nft_property_decoded(399 collection_id,400 nft_id,401 RmrkProperty::PendingNftAccept402 )?,403 <Error<T>>::NoPermission404 );405406 let target_owner;407408 match new_owner {409 RmrkAccountIdOrCollectionNftTuple::AccountId(account_id) => {410 target_owner = T::CrossAccountId::from_sub(account_id);411 }412 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(413 target_collection_id,414 target_nft_id,415 ) => {416 let target_collection_id = Self::unique_collection_id(target_collection_id)?;417418 let target_nft_budget = budget::Value::new(NESTING_BUDGET);419420 let target_nft_owner = <PalletStructure<T>>::get_checked_indirect_owner(421 target_collection_id,422 target_nft_id.into(),423 Some((collection_id, nft_id)),424 &target_nft_budget,425 )426 .map_err(Self::map_unique_err_to_proxy)?;427428 let is_approval_required = cross_sender != target_nft_owner;429430 if is_approval_required {431 target_owner = target_nft_owner;432433 <PalletNft<T>>::set_scoped_token_property(434 collection.id,435 nft_id,436 PropertyScope::Rmrk,437 Self::rmrk_property(PendingNftAccept, &is_approval_required)?,438 )?;439 } else {440 target_owner = T::CrossTokenAddressMapping::token_to_address(441 target_collection_id,442 target_nft_id.into(),443 );444 }445 }446 }447448 let src_nft_budget = budget::Value::new(NESTING_BUDGET);449450 <PalletNft<T>>::transfer_from(451 &collection,452 &cross_sender,453 &from,454 &target_owner,455 nft_id,456 &src_nft_budget,457 )458 .map_err(Self::map_unique_err_to_proxy)?;459460 Ok(())461 }462463 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]464 #[transactional]465 pub fn accept_nft(466 origin: OriginFor<T>,467 rmrk_collection_id: RmrkCollectionId,468 rmrk_nft_id: RmrkNftId,469 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,470 ) -> DispatchResult {471 let sender = ensure_signed(origin.clone())?;472 let cross_sender = T::CrossAccountId::from_sub(sender);473474 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;475 let nft_id = rmrk_nft_id.into();476477 let collection =478 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;479480 let new_owner = match new_owner {481 RmrkAccountIdOrCollectionNftTuple::AccountId(account_id) => {482 T::CrossAccountId::from_sub(account_id)483 }484 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(485 target_collection_id,486 target_nft_id,487 ) => {488 let target_collection_id = Self::unique_collection_id(target_collection_id)?;489490 T::CrossTokenAddressMapping::token_to_address(491 target_collection_id,492 TokenId(target_nft_id),493 )494 }495 };496497 let budget = budget::Value::new(NESTING_BUDGET);498499 <PalletNft<T>>::transfer(&collection, &cross_sender, &new_owner, nft_id, &budget)500 .map_err(|err| {501 if err == <CommonError<T>>::OnlyOwnerAllowedToNest.into() {502 <Error<T>>::CannotAcceptNonOwnedNft.into()503 } else {504 Self::map_unique_err_to_proxy(err)505 }506 })?;507508 <PalletNft<T>>::set_scoped_token_property(509 collection.id,510 nft_id,511 PropertyScope::Rmrk,512 Self::rmrk_property(PendingNftAccept, &false)?,513 )?;514515 Ok(())516 }517518 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]519 #[transactional]520 pub fn reject_nft(521 origin: OriginFor<T>,522 collection_id: RmrkCollectionId,523 nft_id: RmrkNftId,524 ) -> DispatchResult {525 let sender = ensure_signed(origin)?;526 let cross_sender = T::CrossAccountId::from_sub(sender);527528 let collection_id = Self::unique_collection_id(collection_id)?;529 let nft_id = nft_id.into();530531 Self::destroy_nft(cross_sender, collection_id, nft_id).map_err(|err| {532 if err == <CommonError<T>>::NoPermission.into()533 || err == <CommonError<T>>::ApprovedValueTooLow.into()534 {535 <Error<T>>::CannotRejectNonOwnedNft.into()536 } else {537 Self::map_unique_err_to_proxy(err)538 }539 })?;540541 Ok(())542 }543544 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]545 #[transactional]546 pub fn accept_resource(547 origin: OriginFor<T>,548 rmrk_collection_id: RmrkCollectionId,549 rmrk_nft_id: RmrkNftId,550 rmrk_resource_id: RmrkResourceId,551 ) -> DispatchResult {552 let sender = ensure_signed(origin)?;553 let cross_sender = T::CrossAccountId::from_sub(sender);554555 let collection_id = Self::unique_collection_id(rmrk_collection_id)556 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;557558 let nft_id = rmrk_nft_id.into();559 let resource_id = rmrk_resource_id.into();560561 let budget = budget::Value::new(NESTING_BUDGET);562563 let nft_owner =564 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)565 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;566567 let resource_collection_id: CollectionId =568 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)569 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;570571 let is_pending: bool = Self::get_nft_property_decoded(572 resource_collection_id,573 resource_id,574 PendingResourceAccept,575 )576 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;577578 ensure!(is_pending, <Error<T>>::ResourceNotPending);579580 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);581582 <PalletNft<T>>::set_scoped_token_property(583 resource_collection_id,584 rmrk_resource_id.into(),585 PropertyScope::Rmrk,586 Self::rmrk_property(PendingResourceAccept, &false)?,587 )?;588589 Ok(())590 }591592 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]593 #[transactional]594 pub fn accept_resource_removal(595 origin: OriginFor<T>,596 rmrk_collection_id: RmrkCollectionId,597 rmrk_nft_id: RmrkNftId,598 rmrk_resource_id: RmrkResourceId,599 ) -> DispatchResult {600 let sender = ensure_signed(origin)?;601 let cross_sender = T::CrossAccountId::from_sub(sender);602603 let collection_id = Self::unique_collection_id(rmrk_collection_id)604 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;605606 let nft_id = rmrk_nft_id.into();607 let resource_id = rmrk_resource_id.into();608609 let budget = budget::Value::new(NESTING_BUDGET);610611 let nft_owner =612 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)613 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;614615 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);616617 let resource_collection_id: CollectionId =618 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)619 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;620621 let is_pending: bool = Self::get_nft_property_decoded(622 resource_collection_id,623 resource_id,624 PendingResourceRemoval,625 )626 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;627628 ensure!(is_pending, <Error<T>>::ResourceNotPending);629630 let resource_collection = Self::get_typed_nft_collection(631 resource_collection_id,632 misc::CollectionType::Resource,633 )?;634635 <PalletNft<T>>::burn(&resource_collection, &cross_sender, rmrk_resource_id.into())636 .map_err(Self::map_unique_err_to_proxy)?;637638 Ok(())639 }640641 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]642 #[transactional]643 pub fn set_property(644 origin: OriginFor<T>,645 #[pallet::compact] rmrk_collection_id: RmrkCollectionId,646 maybe_nft_id: Option<RmrkNftId>,647 key: RmrkKeyString,648 value: RmrkValueString,649 ) -> DispatchResult {650 let sender = ensure_signed(origin)?;651 let sender = T::CrossAccountId::from_sub(sender);652653 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;654 let budget = budget::Value::new(NESTING_BUDGET);655656 match maybe_nft_id {657 Some(nft_id) => {658 let token_id: TokenId = nft_id.into();659660 Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;661 Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;662663 <PalletNft<T>>::set_scoped_token_property(664 collection_id,665 token_id,666 PropertyScope::Rmrk,667 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,668 )?;669 }670 None => {671 let collection = Self::get_typed_nft_collection(672 collection_id,673 misc::CollectionType::Regular,674 )?;675676 Self::check_collection_owner(&collection, &sender)?;677678 <PalletCommon<T>>::set_scoped_collection_property(679 collection_id,680 PropertyScope::Rmrk,681 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,682 )?;683 }684 }685686 Self::deposit_event(Event::PropertySet {687 collection_id: rmrk_collection_id,688 maybe_nft_id,689 key,690 value,691 });692693 Ok(())694 }695696 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]697 #[transactional]698 pub fn set_priority(699 origin: OriginFor<T>,700 rmrk_collection_id: RmrkCollectionId,701 rmrk_nft_id: RmrkNftId,702 priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,703 ) -> DispatchResult {704 let sender = ensure_signed(origin)?;705 let sender = T::CrossAccountId::from_sub(sender);706707 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;708 let nft_id = rmrk_nft_id.into();709 let budget = budget::Value::new(NESTING_BUDGET);710711 Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;712 Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;713714 <PalletNft<T>>::set_scoped_token_property(715 collection_id,716 nft_id,717 PropertyScope::Rmrk,718 Self::rmrk_property(ResourcePriorities, &priorities.into_inner())?,719 )?;720721 Ok(())722 }723724 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]725 #[transactional]726 pub fn add_basic_resource(727 origin: OriginFor<T>,728 collection_id: RmrkCollectionId,729 nft_id: RmrkNftId,730 resource: RmrkBasicResource,731 ) -> DispatchResult {732 let sender = ensure_signed(origin.clone())?;733734 let resource_id = Self::resource_add(735 sender,736 Self::unique_collection_id(collection_id)?,737 nft_id.into(),738 [739 Self::rmrk_property(TokenType, &NftType::Resource)?,740 Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,741 Self::rmrk_property(Src, &resource.src)?,742 Self::rmrk_property(Metadata, &resource.metadata)?,743 Self::rmrk_property(License, &resource.license)?,744 Self::rmrk_property(Thumb, &resource.thumb)?,745 ]746 .into_iter(),747 )?;748749 Self::deposit_event(Event::ResourceAdded {750 nft_id,751 resource_id,752 });753 Ok(())754 }755756 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]757 #[transactional]758 pub fn add_composable_resource(759 origin: OriginFor<T>,760 collection_id: RmrkCollectionId,761 nft_id: RmrkNftId,762 _resource_id: RmrkBoundedResource,763 resource: RmrkComposableResource,764 ) -> DispatchResult {765 let sender = ensure_signed(origin.clone())?;766767 let resource_id = Self::resource_add(768 sender,769 Self::unique_collection_id(collection_id)?,770 nft_id.into(),771 [772 Self::rmrk_property(TokenType, &NftType::Resource)?,773 Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,774 Self::rmrk_property(Parts, &resource.parts)?,775 Self::rmrk_property(Base, &resource.base)?,776 Self::rmrk_property(Src, &resource.src)?,777 Self::rmrk_property(Metadata, &resource.metadata)?,778 Self::rmrk_property(License, &resource.license)?,779 Self::rmrk_property(Thumb, &resource.thumb)?,780 ]781 .into_iter(),782 )?;783784 Self::deposit_event(Event::ResourceAdded {785 nft_id,786 resource_id,787 });788 Ok(())789 }790791 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]792 #[transactional]793 pub fn add_slot_resource(794 origin: OriginFor<T>,795 collection_id: RmrkCollectionId,796 nft_id: RmrkNftId,797 resource: RmrkSlotResource,798 ) -> DispatchResult {799 let sender = ensure_signed(origin.clone())?;800801 let resource_id = Self::resource_add(802 sender,803 Self::unique_collection_id(collection_id)?,804 nft_id.into(),805 [806 Self::rmrk_property(TokenType, &NftType::Resource)?,807 Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,808 Self::rmrk_property(Base, &resource.base)?,809 Self::rmrk_property(Src, &resource.src)?,810 Self::rmrk_property(Metadata, &resource.metadata)?,811 Self::rmrk_property(Slot, &resource.slot)?,812 Self::rmrk_property(License, &resource.license)?,813 Self::rmrk_property(Thumb, &resource.thumb)?,814 ]815 .into_iter(),816 )?;817818 Self::deposit_event(Event::ResourceAdded {819 nft_id,820 resource_id,821 });822 Ok(())823 }824825 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]826 #[transactional]827 pub fn remove_resource(828 origin: OriginFor<T>,829 collection_id: RmrkCollectionId,830 nft_id: RmrkNftId,831 resource_id: RmrkResourceId,832 ) -> DispatchResult {833 let sender = ensure_signed(origin.clone())?;834835 Self::resource_remove(836 sender,837 Self::unique_collection_id(collection_id)?,838 nft_id.into(),839 resource_id.into(),840 )?;841842 Self::deposit_event(Event::ResourceRemoval {843 nft_id,844 resource_id,845 });846 Ok(())847 }848 }849}850851impl<T: Config> Pallet<T> {852 pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {853 let key = rmrk_key.to_key::<T>()?;854855 let scoped_key = PropertyScope::Rmrk856 .apply(key)857 .map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;858859 Ok(scoped_key)860 }861862 // todo think about renaming these863 pub fn rmrk_property<E: Encode>(864 rmrk_key: RmrkProperty,865 value: &E,866 ) -> Result<Property, DispatchError> {867 let key = rmrk_key.to_key::<T>()?;868869 let value = value870 .encode()871 .try_into()872 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;873874 let property = Property { key, value };875876 Ok(property)877 }878879 pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {880 vec.decode()881 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())882 }883884 pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>885 where886 BoundedVec<u8, S>: TryFrom<Vec<u8>>,887 {888 vec.rebind()889 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())890 }891892 fn init_collection(893 sender: T::CrossAccountId,894 data: CreateCollectionData<T::AccountId>,895 properties: impl Iterator<Item = Property>,896 ) -> Result<CollectionId, DispatchError> {897 let collection_id = <PalletNft<T>>::init_collection(sender, data);898899 if let Err(DispatchError::Arithmetic(_)) = &collection_id {900 return Err(<Error<T>>::NoAvailableCollectionId.into());901 }902903 <PalletCommon<T>>::set_scoped_collection_properties(904 collection_id?,905 PropertyScope::Rmrk,906 properties,907 )?;908909 collection_id910 }911912 pub fn create_nft(913 sender: &T::CrossAccountId,914 owner: &T::CrossAccountId,915 collection: &NonfungibleHandle<T>,916 properties: impl Iterator<Item = Property>,917 ) -> Result<TokenId, DispatchError> {918 let data = CreateNftExData {919 properties: BoundedVec::default(),920 owner: owner.clone(),921 };922923 let budget = budget::Value::new(NESTING_BUDGET);924925 <PalletNft<T>>::create_item(collection, sender, data, &budget)?;926927 let nft_id = <PalletNft<T>>::current_token_id(collection.id);928929 <PalletNft<T>>::set_scoped_token_properties(930 collection.id,931 nft_id,932 PropertyScope::Rmrk,933 properties,934 )?;935936 Ok(nft_id)937 }938939 fn destroy_nft(940 sender: T::CrossAccountId,941 collection_id: CollectionId,942 token_id: TokenId,943 ) -> DispatchResult {944 let collection =945 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;946947 let token_data =948 <TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;949950 let from = token_data.owner;951952 let budget = budget::Value::new(NESTING_BUDGET);953954 <PalletNft<T>>::burn_from(&collection, &sender, &from, token_id, &budget)955 }956957 fn resource_add(958 sender: T::AccountId,959 collection_id: CollectionId,960 token_id: TokenId,961 resource_properties: impl Iterator<Item = Property>,962 ) -> Result<RmrkResourceId, DispatchError> {963 let collection =964 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;965 ensure!(collection.owner == sender, Error::<T>::NoPermission);966967 let sender = T::CrossAccountId::from_sub(sender);968 let budget = budget::Value::new(NESTING_BUDGET);969970 let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, token_id, &budget)971 .map_err(Self::map_unique_err_to_proxy)?;972973 let pending = sender != nft_owner;974975 let resource_collection_id: CollectionId =976 Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;977 let resource_collection =978 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;979980 // todo probably add extra connections to bases, slots, etc., when RMRK starts to use them981982 let resource_id = Self::create_nft(983 &sender,984 &nft_owner,985 &resource_collection,986 resource_properties.chain(987 [988 Self::rmrk_property(PendingResourceAccept, &pending)?,989 Self::rmrk_property(PendingResourceRemoval, &false)?,990 ]991 .into_iter(),992 ),993 )994 .map_err(|err| match err {995 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),996 err => Self::map_unique_err_to_proxy(err),997 })?;998999 Ok(resource_id.0)1000 }10011002 fn resource_remove(1003 sender: T::AccountId,1004 collection_id: CollectionId,1005 nft_id: TokenId,1006 resource_id: TokenId,1007 ) -> DispatchResult {1008 let collection =1009 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1010 ensure!(collection.owner == sender, Error::<T>::NoPermission);10111012 let resource_collection_id: CollectionId =1013 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;1014 let resource_collection =1015 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;1016 ensure!(1017 <PalletNft<T>>::token_exists(&resource_collection, resource_id),1018 Error::<T>::ResourceDoesntExist1019 );10201021 let budget = up_data_structs::budget::Value::new(10);1022 let topmost_owner =1023 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;10241025 let sender = T::CrossAccountId::from_sub(sender);1026 if topmost_owner == sender {1027 <PalletNft<T>>::burn(&resource_collection, &sender, resource_id)1028 .map_err(Self::map_unique_err_to_proxy)?;1029 } else {1030 <PalletNft<T>>::set_scoped_token_property(1031 resource_collection_id,1032 resource_id,1033 PropertyScope::Rmrk,1034 Self::rmrk_property(PendingResourceRemoval, &true)?,1035 )?;1036 }10371038 Ok(())1039 }10401041 fn change_collection_owner(1042 collection_id: CollectionId,1043 collection_type: misc::CollectionType,1044 sender: T::AccountId,1045 new_owner: T::AccountId,1046 ) -> DispatchResult {1047 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1048 Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;10491050 let mut collection = collection.into_inner();10511052 collection.owner = new_owner;1053 collection.save()1054 }10551056 fn check_collection_owner(1057 collection: &NonfungibleHandle<T>,1058 account: &T::CrossAccountId,1059 ) -> DispatchResult {1060 collection1061 .check_is_owner(account)1062 .map_err(Self::map_unique_err_to_proxy)1063 }10641065 pub fn last_collection_idx() -> RmrkCollectionId {1066 <CollectionIndex<T>>::get()1067 }10681069 pub fn unique_collection_id(1070 rmrk_collection_id: RmrkCollectionId,1071 ) -> Result<CollectionId, DispatchError> {1072 <UniqueCollectionId<T>>::try_get(rmrk_collection_id)1073 .map_err(|_| <Error<T>>::CollectionUnknown.into())1074 }10751076 pub fn rmrk_collection_id(1077 unique_collection_id: CollectionId,1078 ) -> Result<RmrkCollectionId, DispatchError> {1079 <RmrkInernalCollectionId<T>>::try_get(unique_collection_id)1080 .map_err(|_| <Error<T>>::CollectionUnknown.into())1081 }10821083 pub fn get_nft_collection(1084 collection_id: CollectionId,1085 ) -> Result<NonfungibleHandle<T>, DispatchError> {1086 let collection = <CollectionHandle<T>>::try_get(collection_id)1087 .map_err(|_| <Error<T>>::CollectionUnknown)?;10881089 match collection.mode {1090 CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1091 _ => Err(<Error<T>>::CollectionUnknown.into()),1092 }1093 }10941095 pub fn collection_exists(collection_id: CollectionId) -> bool {1096 <CollectionHandle<T>>::try_get(collection_id).is_ok()1097 }10981099 pub fn get_collection_property(1100 collection_id: CollectionId,1101 key: RmrkProperty,1102 ) -> Result<PropertyValue, DispatchError> {1103 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1104 .get(&Self::rmrk_property_key(key)?)1105 .ok_or(<Error<T>>::CollectionUnknown)?1106 .clone();11071108 Ok(collection_property)1109 }11101111 pub fn get_collection_property_decoded<V: Decode>(1112 collection_id: CollectionId,1113 key: RmrkProperty,1114 ) -> Result<V, DispatchError> {1115 Self::decode_property(Self::get_collection_property(collection_id, key)?)1116 }11171118 pub fn get_collection_type(1119 collection_id: CollectionId,1120 ) -> Result<misc::CollectionType, DispatchError> {1121 Self::get_collection_property_decoded(collection_id, CollectionType)1122 .map_err(|_| <Error<T>>::CorruptedCollectionType.into())1123 }11241125 pub fn ensure_collection_type(1126 collection_id: CollectionId,1127 collection_type: misc::CollectionType,1128 ) -> DispatchResult {1129 let actual_type = Self::get_collection_type(collection_id)?;1130 ensure!(1131 actual_type == collection_type,1132 <CommonError<T>>::NoPermission1133 );11341135 Ok(())1136 }11371138 pub fn get_typed_nft_collection(1139 collection_id: CollectionId,1140 collection_type: misc::CollectionType,1141 ) -> Result<NonfungibleHandle<T>, DispatchError> {1142 Self::ensure_collection_type(collection_id, collection_type)?;11431144 Self::get_nft_collection(collection_id)1145 }11461147 pub fn get_typed_nft_collection_mapped(1148 rmrk_collection_id: RmrkCollectionId,1149 collection_type: misc::CollectionType,1150 ) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1151 let unique_collection_id = match collection_type {1152 misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1153 _ => rmrk_collection_id.into(),1154 };11551156 let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;11571158 Ok((collection, unique_collection_id))1159 }11601161 pub fn get_nft_property(1162 collection_id: CollectionId,1163 nft_id: TokenId,1164 key: RmrkProperty,1165 ) -> Result<PropertyValue, DispatchError> {1166 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1167 .get(&Self::rmrk_property_key(key)?)1168 .ok_or(<Error<T>>::NoAvailableNftId)? // todo replace with better error?1169 .clone();11701171 Ok(nft_property)1172 }11731174 pub fn get_nft_property_decoded<V: Decode>(1175 collection_id: CollectionId,1176 nft_id: TokenId,1177 key: RmrkProperty,1178 ) -> Result<V, DispatchError> {1179 Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)1180 }11811182 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1183 <TokenData<T>>::contains_key((collection_id, nft_id))1184 }11851186 pub fn get_nft_type(1187 collection_id: CollectionId,1188 token_id: TokenId,1189 ) -> Result<NftType, DispatchError> {1190 Self::get_nft_property_decoded(collection_id, token_id, TokenType)1191 .map_err(|_| <Error<T>>::NoAvailableNftId.into())1192 }11931194 pub fn ensure_nft_type(1195 collection_id: CollectionId,1196 token_id: TokenId,1197 nft_type: NftType,1198 ) -> DispatchResult {1199 let actual_type = Self::get_nft_type(collection_id, token_id)?;1200 ensure!(actual_type == nft_type, <Error<T>>::NoPermission);12011202 Ok(())1203 }12041205 pub fn ensure_nft_owner(1206 collection_id: CollectionId,1207 token_id: TokenId,1208 possible_owner: &T::CrossAccountId,1209 nesting_budget: &dyn budget::Budget,1210 ) -> DispatchResult {1211 let is_owned = <PalletStructure<T>>::check_indirectly_owned(1212 possible_owner.clone(),1213 collection_id,1214 token_id,1215 None,1216 nesting_budget,1217 )1218 .map_err(Self::map_unique_err_to_proxy)?;12191220 ensure!(is_owned, <Error<T>>::NoPermission);12211222 Ok(())1223 }12241225 pub fn filter_user_properties<Key, Value, R, Mapper>(1226 collection_id: CollectionId,1227 token_id: Option<TokenId>,1228 filter_keys: Option<Vec<RmrkPropertyKey>>,1229 mapper: Mapper,1230 ) -> Result<Vec<R>, DispatchError>1231 where1232 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1233 Value: Decode + Default,1234 Mapper: Fn(Key, Value) -> R,1235 {1236 filter_keys1237 .map(|keys| {1238 let properties = keys1239 .into_iter()1240 .filter_map(|key| {1241 let key: Key = key.try_into().ok()?;12421243 let value = match token_id {1244 Some(token_id) => Self::get_nft_property_decoded(1245 collection_id,1246 token_id,1247 UserProperty(key.as_ref()),1248 ),1249 None => Self::get_collection_property_decoded(1250 collection_id,1251 UserProperty(key.as_ref()),1252 ),1253 }1254 .ok()?;12551256 Some(mapper(key, value))1257 })1258 .collect();12591260 Ok(properties)1261 })1262 .unwrap_or_else(|| {1263 let properties =1264 Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();12651266 Ok(properties)1267 })1268 }12691270 pub fn iterate_user_properties<Key, Value, R, Mapper>(1271 collection_id: CollectionId,1272 token_id: Option<TokenId>,1273 mapper: Mapper,1274 ) -> Result<impl Iterator<Item = R>, DispatchError>1275 where1276 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1277 Value: Decode + Default,1278 Mapper: Fn(Key, Value) -> R,1279 {1280 let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;12811282 let properties = match token_id {1283 Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1284 None => <PalletCommon<T>>::collection_properties(collection_id),1285 };12861287 let properties = properties.into_iter().filter_map(move |(key, value)| {1288 let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;12891290 let key: Key = key.to_vec().try_into().ok()?;1291 let value: Value = value.decode().ok()?;12921293 Some(mapper(key, value))1294 });12951296 Ok(properties)1297 }12981299 fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1300 map_unique_err_to_proxy! {1301 match err {1302 CommonError::NoPermission => NoPermission,1303 CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1304 CommonError::PublicMintingNotAllowed => NoPermission,1305 CommonError::TokenNotFound => NoAvailableNftId,1306 CommonError::ApprovedValueTooLow => NoPermission,1307 CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1308 StructureError::TokenNotFound => NoAvailableNftId,1309 StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1310 }1311 }1312 }1313}primitives/rmrk-traits/src/lib.rsdiffbeforeafterboth--- a/primitives/rmrk-traits/src/lib.rs
+++ b/primitives/rmrk-traits/src/lib.rs
@@ -17,9 +17,7 @@
pub use collection::CollectionInfo;
pub use nft::{AccountIdOrCollectionNftTuple, NftInfo, RoyaltyInfo, NftChild};
pub use property::PropertyInfo;
-pub use resource::{
- BasicResource, ComposableResource, ResourceInfo, ResourceTypes, SlotResource,
-};
+pub use resource::{BasicResource, ComposableResource, ResourceInfo, ResourceTypes, SlotResource};
pub mod primitives {
pub type CollectionId = u32;
pub type ResourceId = u32;
primitives/rmrk-traits/src/serialize.rsdiffbeforeafterboth--- a/primitives/rmrk-traits/src/serialize.rs
+++ b/primitives/rmrk-traits/src/serialize.rs
@@ -2,30 +2,30 @@
use serde::ser::{self, Serialize};
pub mod vec {
- use super::*;
+ use super::*;
- pub fn serialize<D, V, C>(value: &C, serializer: D) -> Result<D::Ok, D::Error>
- where
- D: ser::Serializer,
- V: Serialize,
- C: AsRef<[V]>,
- {
- value.as_ref().serialize(serializer)
- }
+ pub fn serialize<D, V, C>(value: &C, serializer: D) -> Result<D::Ok, D::Error>
+ where
+ D: ser::Serializer,
+ V: Serialize,
+ C: AsRef<[V]>,
+ {
+ value.as_ref().serialize(serializer)
+ }
}
pub mod opt_vec {
- use super::*;
+ use super::*;
- pub fn serialize<D, V, C>(value: &Option<C>, serializer: D) -> Result<D::Ok, D::Error>
- where
- D: ser::Serializer,
- V: Serialize,
- C: AsRef<[V]>,
- {
- match value {
- Some(value) => super::vec::serialize(value, serializer),
- None => serializer.serialize_none(),
- }
- }
+ pub fn serialize<D, V, C>(value: &Option<C>, serializer: D) -> Result<D::Ok, D::Error>
+ where
+ D: ser::Serializer,
+ V: Serialize,
+ C: AsRef<[V]>,
+ {
+ match value {
+ Some(value) => super::vec::serialize(value, serializer),
+ None => serializer.serialize_none(),
+ }
+ }
}