difftreelog
fix(rmrk) priority events
in: master
1 file changed
pallets/proxy-rmrk-core/src/lib.rsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![cfg_attr(not(feature = "std"), no_std)]1819use frame_support::{pallet_prelude::*, transactional, BoundedVec, dispatch::DispatchResult};20use frame_system::{pallet_prelude::*, ensure_signed};21use sp_runtime::{DispatchError, Permill, traits::StaticLookup};22use sp_std::vec::Vec;23use up_data_structs::{*, mapping::TokenAddressMapping};24use pallet_common::{25 Pallet as PalletCommon, Error as CommonError, CollectionHandle, CommonCollectionOperations,26};27use pallet_nonfungible::{Pallet as PalletNft, NonfungibleHandle, TokenData};28use pallet_structure::{Pallet as PalletStructure, Error as StructureError};29use pallet_evm::account::CrossAccountId;30use core::convert::AsRef;3132pub use pallet::*;3334pub mod misc;35pub mod property;3637use misc::*;38pub use property::*;3940use RmrkProperty::*;4142const NESTING_BUDGET: u32 = 5;4344#[frame_support::pallet]45pub mod pallet {46 use super::*;47 use pallet_evm::account;4849 #[pallet::config]50 pub trait Config:51 frame_system::Config + pallet_common::Config + pallet_nonfungible::Config + account::Config52 {53 type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;54 }5556 #[pallet::storage]57 #[pallet::getter(fn collection_index)]58 pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;5960 #[pallet::storage]61 pub type UniqueCollectionId<T: Config> =62 StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;6364 #[pallet::storage]65 pub type RmrkInernalCollectionId<T: Config> =66 StorageMap<_, Twox64Concat, CollectionId, RmrkCollectionId, ValueQuery>;6768 #[pallet::pallet]69 #[pallet::generate_store(pub(super) trait Store)]70 pub struct Pallet<T>(_);7172 #[pallet::event]73 #[pallet::generate_deposit(pub(super) fn deposit_event)]74 pub enum Event<T: Config> {75 CollectionCreated {76 issuer: T::AccountId,77 collection_id: RmrkCollectionId,78 },79 CollectionDestroyed {80 issuer: T::AccountId,81 collection_id: RmrkCollectionId,82 },83 IssuerChanged {84 old_issuer: T::AccountId,85 new_issuer: T::AccountId,86 collection_id: RmrkCollectionId,87 },88 CollectionLocked {89 issuer: T::AccountId,90 collection_id: RmrkCollectionId,91 },92 NftMinted {93 owner: T::AccountId,94 collection_id: RmrkCollectionId,95 nft_id: RmrkNftId,96 },97 NFTBurned {98 owner: T::AccountId,99 nft_id: RmrkNftId,100 },101 NFTSent {102 sender: T::AccountId,103 recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,104 collection_id: RmrkCollectionId,105 nft_id: RmrkNftId,106 approval_required: bool,107 },108 NFTAccepted {109 sender: T::AccountId,110 recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,111 collection_id: RmrkCollectionId,112 nft_id: RmrkNftId,113 },114 NFTRejected {115 sender: T::AccountId,116 collection_id: RmrkCollectionId,117 nft_id: RmrkNftId,118 },119 PropertySet {120 collection_id: RmrkCollectionId,121 maybe_nft_id: Option<RmrkNftId>,122 key: RmrkKeyString,123 value: RmrkValueString,124 },125 ResourceAdded {126 nft_id: RmrkNftId,127 resource_id: RmrkResourceId,128 },129 ResourceRemoval {130 nft_id: RmrkNftId,131 resource_id: RmrkResourceId,132 },133 ResourceAccepted {134 nft_id: RmrkNftId,135 resource_id: RmrkResourceId,136 },137 ResourceRemovalAccepted {138 nft_id: RmrkNftId,139 resource_id: RmrkResourceId,140 },141 }142143 #[pallet::error]144 pub enum Error<T> {145 /* Unique-specific events */146 CorruptedCollectionType,147 NftTypeEncodeError,148 RmrkPropertyKeyIsTooLong,149 RmrkPropertyValueIsTooLong,150151 /* RMRK compatible events */152 CollectionNotEmpty,153 NoAvailableCollectionId,154 NoAvailableNftId,155 CollectionUnknown,156 NoPermission,157 NonTransferable,158 CollectionFullOrLocked,159 ResourceDoesntExist,160 CannotSendToDescendentOrSelf,161 CannotAcceptNonOwnedNft,162 CannotRejectNonOwnedNft,163 ResourceNotPending,164 }165166 #[pallet::call]167 impl<T: Config> Pallet<T> {168 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]169 #[transactional]170 pub fn create_collection(171 origin: OriginFor<T>,172 metadata: RmrkString,173 max: Option<u32>,174 symbol: RmrkCollectionSymbol,175 ) -> DispatchResult {176 let sender = ensure_signed(origin)?;177178 let limits = CollectionLimits {179 owner_can_transfer: Some(false),180 token_limit: max,181 ..Default::default()182 };183184 let data = CreateCollectionData {185 limits: Some(limits),186 token_prefix: symbol187 .into_inner()188 .try_into()189 .map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,190 permissions: Some(CollectionPermissions {191 nesting: Some(NestingRule::Owner),192 ..Default::default()193 }),194 ..Default::default()195 };196197 let unique_collection_id = Self::init_collection(198 T::CrossAccountId::from_sub(sender.clone()),199 data,200 [201 Self::rmrk_property(Metadata, &metadata)?,202 Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,203 ]204 .into_iter(),205 )?;206 let rmrk_collection_id = <CollectionIndex<T>>::get();207208 <UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);209 <RmrkInernalCollectionId<T>>::insert(unique_collection_id, rmrk_collection_id);210211 <CollectionIndex<T>>::mutate(|n| *n += 1);212213 Self::deposit_event(Event::CollectionCreated {214 issuer: sender,215 collection_id: rmrk_collection_id,216 });217218 Ok(())219 }220221 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]222 #[transactional]223 pub fn destroy_collection(224 origin: OriginFor<T>,225 collection_id: RmrkCollectionId,226 ) -> DispatchResult {227 let sender = ensure_signed(origin)?;228 let cross_sender = T::CrossAccountId::from_sub(sender.clone());229230 let collection = Self::get_typed_nft_collection(231 Self::unique_collection_id(collection_id)?,232 misc::CollectionType::Regular,233 )?;234235 <PalletNft<T>>::destroy_collection(collection, &cross_sender)236 .map_err(Self::map_unique_err_to_proxy)?;237238 Self::deposit_event(Event::CollectionDestroyed {239 issuer: sender,240 collection_id,241 });242243 Ok(())244 }245246 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]247 #[transactional]248 pub fn change_collection_issuer(249 origin: OriginFor<T>,250 collection_id: RmrkCollectionId,251 new_issuer: <T::Lookup as StaticLookup>::Source,252 ) -> DispatchResult {253 let sender = ensure_signed(origin)?;254255 let new_issuer = T::Lookup::lookup(new_issuer)?;256257 Self::change_collection_owner(258 Self::unique_collection_id(collection_id)?,259 misc::CollectionType::Regular,260 sender.clone(),261 new_issuer.clone(),262 )?;263264 Self::deposit_event(Event::IssuerChanged {265 old_issuer: sender,266 new_issuer,267 collection_id,268 });269270 Ok(())271 }272273 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]274 #[transactional]275 pub fn lock_collection(276 origin: OriginFor<T>,277 collection_id: RmrkCollectionId,278 ) -> DispatchResult {279 let sender = ensure_signed(origin)?;280 let cross_sender = T::CrossAccountId::from_sub(sender.clone());281282 let collection = Self::get_typed_nft_collection(283 Self::unique_collection_id(collection_id)?,284 misc::CollectionType::Regular,285 )?;286287 Self::check_collection_owner(&collection, &cross_sender)?;288289 let token_count = collection.total_supply();290291 let mut collection = collection.into_inner();292 collection.limits.token_limit = Some(token_count);293 collection.save()?;294295 Self::deposit_event(Event::CollectionLocked {296 issuer: sender,297 collection_id,298 });299300 Ok(())301 }302303 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]304 #[transactional]305 pub fn mint_nft(306 origin: OriginFor<T>,307 owner: T::AccountId,308 collection_id: RmrkCollectionId,309 recipient: Option<T::AccountId>,310 royalty_amount: Option<Permill>,311 metadata: RmrkString,312 transferable: bool,313 ) -> DispatchResult {314 let sender = ensure_signed(origin)?;315 let sender = T::CrossAccountId::from_sub(sender);316 let cross_owner = T::CrossAccountId::from_sub(owner.clone());317318 let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {319 recipient: recipient.unwrap_or_else(|| owner.clone()),320 amount,321 });322323 let collection = Self::get_typed_nft_collection(324 Self::unique_collection_id(collection_id)?,325 misc::CollectionType::Regular,326 )?;327328 let nft_id = Self::create_nft(329 &sender,330 &cross_owner,331 &collection,332 [333 Self::rmrk_property(TokenType, &NftType::Regular)?,334 Self::rmrk_property(Transferable, &transferable)?,335 Self::rmrk_property(PendingNftAccept, &false)?,336 Self::rmrk_property(RoyaltyInfo, &royalty_info)?,337 Self::rmrk_property(Metadata, &metadata)?,338 Self::rmrk_property(Equipped, &false)?,339 Self::rmrk_property(340 ResourceCollection,341 &Self::init_collection(342 sender.clone(),343 CreateCollectionData {344 ..Default::default()345 },346 [Self::rmrk_property(347 CollectionType,348 &misc::CollectionType::Resource,349 )?]350 .into_iter(),351 )?,352 )?, // todo possibly add limits to the collection if rmrk warrants them353 Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,354 ]355 .into_iter(),356 )357 .map_err(|err| match err {358 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),359 err => Self::map_unique_err_to_proxy(err),360 })?;361362 Self::deposit_event(Event::NftMinted {363 owner,364 collection_id,365 nft_id: nft_id.0,366 });367368 Ok(())369 }370371 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]372 #[transactional]373 pub fn burn_nft(374 origin: OriginFor<T>,375 collection_id: RmrkCollectionId,376 nft_id: RmrkNftId,377 ) -> DispatchResult {378 let sender = ensure_signed(origin)?;379 let cross_sender = T::CrossAccountId::from_sub(sender.clone());380381 Self::destroy_nft(382 cross_sender,383 Self::unique_collection_id(collection_id)?,384 nft_id.into(),385 )386 .map_err(Self::map_unique_err_to_proxy)?;387388 Self::deposit_event(Event::NFTBurned {389 owner: sender,390 nft_id,391 });392393 Ok(())394 }395396 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]397 #[transactional]398 pub fn send(399 origin: OriginFor<T>,400 rmrk_collection_id: RmrkCollectionId,401 rmrk_nft_id: RmrkNftId,402 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,403 ) -> DispatchResult {404 let sender = ensure_signed(origin.clone())?;405 let cross_sender = T::CrossAccountId::from_sub(sender.clone());406407 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;408 let nft_id = rmrk_nft_id.into();409410 let token_data =411 <TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;412413 let from = token_data.owner;414415 let collection =416 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;417418 ensure!(419 Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,420 <Error<T>>::NonTransferable421 );422423 ensure!(424 !Self::get_nft_property_decoded(425 collection_id,426 nft_id,427 RmrkProperty::PendingNftAccept428 )?,429 <Error<T>>::NoPermission430 );431432 let target_owner;433 let approval_required;434435 match new_owner {436 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {437 target_owner = T::CrossAccountId::from_sub(account_id.clone());438 approval_required = false;439 }440 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(441 target_collection_id,442 target_nft_id,443 ) => {444 let target_collection_id = Self::unique_collection_id(target_collection_id)?;445446 let target_nft_budget = budget::Value::new(NESTING_BUDGET);447448 let target_nft_owner = <PalletStructure<T>>::get_checked_indirect_owner(449 target_collection_id,450 target_nft_id.into(),451 Some((collection_id, nft_id)),452 &target_nft_budget,453 )454 .map_err(Self::map_unique_err_to_proxy)?;455456 approval_required = cross_sender != target_nft_owner;457458 if approval_required {459 target_owner = target_nft_owner;460461 <PalletNft<T>>::set_scoped_token_property(462 collection.id,463 nft_id,464 PropertyScope::Rmrk,465 Self::rmrk_property(PendingNftAccept, &approval_required)?,466 )?;467 } else {468 target_owner = T::CrossTokenAddressMapping::token_to_address(469 target_collection_id,470 target_nft_id.into(),471 );472 }473 }474 }475476 let src_nft_budget = budget::Value::new(NESTING_BUDGET);477478 <PalletNft<T>>::transfer_from(479 &collection,480 &cross_sender,481 &from,482 &target_owner,483 nft_id,484 &src_nft_budget,485 )486 .map_err(Self::map_unique_err_to_proxy)?;487488 Self::deposit_event(Event::NFTSent {489 sender,490 recipient: new_owner,491 collection_id: rmrk_collection_id,492 nft_id: rmrk_nft_id,493 approval_required,494 });495496 Ok(())497 }498499 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]500 #[transactional]501 pub fn accept_nft(502 origin: OriginFor<T>,503 rmrk_collection_id: RmrkCollectionId,504 rmrk_nft_id: RmrkNftId,505 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,506 ) -> DispatchResult {507 let sender = ensure_signed(origin.clone())?;508 let cross_sender = T::CrossAccountId::from_sub(sender.clone());509510 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;511 let nft_id = rmrk_nft_id.into();512513 let collection =514 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;515516 let new_cross_owner = match new_owner {517 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {518 T::CrossAccountId::from_sub(account_id.clone())519 }520 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(521 target_collection_id,522 target_nft_id,523 ) => {524 let target_collection_id = Self::unique_collection_id(target_collection_id)?;525526 T::CrossTokenAddressMapping::token_to_address(527 target_collection_id,528 TokenId(target_nft_id),529 )530 }531 };532533 let budget = budget::Value::new(NESTING_BUDGET);534535 <PalletNft<T>>::transfer(&collection, &cross_sender, &new_cross_owner, nft_id, &budget)536 .map_err(|err| {537 if err == <CommonError<T>>::OnlyOwnerAllowedToNest.into() {538 <Error<T>>::CannotAcceptNonOwnedNft.into()539 } else {540 Self::map_unique_err_to_proxy(err)541 }542 })?;543544 <PalletNft<T>>::set_scoped_token_property(545 collection.id,546 nft_id,547 PropertyScope::Rmrk,548 Self::rmrk_property(PendingNftAccept, &false)?,549 )?;550551 Self::deposit_event(Event::NFTAccepted {552 sender,553 recipient: new_owner,554 collection_id: rmrk_collection_id,555 nft_id: rmrk_nft_id,556 });557558 Ok(())559 }560561 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]562 #[transactional]563 pub fn reject_nft(564 origin: OriginFor<T>,565 rmrk_collection_id: RmrkCollectionId,566 rmrk_nft_id: RmrkNftId,567 ) -> DispatchResult {568 let sender = ensure_signed(origin)?;569 let cross_sender = T::CrossAccountId::from_sub(sender.clone());570571 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;572 let nft_id = rmrk_nft_id.into();573574 Self::destroy_nft(cross_sender, collection_id, nft_id).map_err(|err| {575 if err == <CommonError<T>>::NoPermission.into()576 || err == <CommonError<T>>::ApprovedValueTooLow.into()577 {578 <Error<T>>::CannotRejectNonOwnedNft.into()579 } else {580 Self::map_unique_err_to_proxy(err)581 }582 })?;583584 Self::deposit_event(Event::NFTRejected {585 sender,586 collection_id: rmrk_collection_id,587 nft_id: rmrk_nft_id,588 });589590 Ok(())591 }592593 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]594 #[transactional]595 pub fn accept_resource(596 origin: OriginFor<T>,597 rmrk_collection_id: RmrkCollectionId,598 rmrk_nft_id: RmrkNftId,599 rmrk_resource_id: RmrkResourceId,600 ) -> DispatchResult {601 let sender = ensure_signed(origin)?;602 let cross_sender = T::CrossAccountId::from_sub(sender);603604 let collection_id = Self::unique_collection_id(rmrk_collection_id)605 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;606607 let nft_id = rmrk_nft_id.into();608 let resource_id = rmrk_resource_id.into();609610 let budget = budget::Value::new(NESTING_BUDGET);611612 let nft_owner =613 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)614 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;615616 let resource_collection_id: CollectionId =617 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)618 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;619620 let is_pending: bool = Self::get_nft_property_decoded(621 resource_collection_id,622 resource_id,623 PendingResourceAccept,624 )625 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;626627 ensure!(is_pending, <Error<T>>::ResourceNotPending);628629 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);630631 <PalletNft<T>>::set_scoped_token_property(632 resource_collection_id,633 rmrk_resource_id.into(),634 PropertyScope::Rmrk,635 Self::rmrk_property(PendingResourceAccept, &false)?,636 )?;637638 Self::deposit_event(Event::<T>::ResourceAccepted {639 nft_id: rmrk_nft_id,640 resource_id: rmrk_resource_id,641 });642643 Ok(())644 }645646 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]647 #[transactional]648 pub fn accept_resource_removal(649 origin: OriginFor<T>,650 rmrk_collection_id: RmrkCollectionId,651 rmrk_nft_id: RmrkNftId,652 rmrk_resource_id: RmrkResourceId,653 ) -> DispatchResult {654 let sender = ensure_signed(origin)?;655 let cross_sender = T::CrossAccountId::from_sub(sender);656657 let collection_id = Self::unique_collection_id(rmrk_collection_id)658 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;659660 let nft_id = rmrk_nft_id.into();661 let resource_id = rmrk_resource_id.into();662663 let budget = budget::Value::new(NESTING_BUDGET);664665 let nft_owner =666 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)667 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;668669 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);670671 let resource_collection_id: CollectionId =672 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)673 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;674675 let is_pending: bool = Self::get_nft_property_decoded(676 resource_collection_id,677 resource_id,678 PendingResourceRemoval,679 )680 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;681682 ensure!(is_pending, <Error<T>>::ResourceNotPending);683684 let resource_collection = Self::get_typed_nft_collection(685 resource_collection_id,686 misc::CollectionType::Resource,687 )?;688689 <PalletNft<T>>::burn(&resource_collection, &cross_sender, rmrk_resource_id.into())690 .map_err(Self::map_unique_err_to_proxy)?;691692 Self::deposit_event(Event::<T>::ResourceRemovalAccepted {693 nft_id: rmrk_nft_id,694 resource_id: rmrk_resource_id,695 });696697 Ok(())698 }699700 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]701 #[transactional]702 pub fn set_property(703 origin: OriginFor<T>,704 #[pallet::compact] rmrk_collection_id: RmrkCollectionId,705 maybe_nft_id: Option<RmrkNftId>,706 key: RmrkKeyString,707 value: RmrkValueString,708 ) -> DispatchResult {709 let sender = ensure_signed(origin)?;710 let sender = T::CrossAccountId::from_sub(sender);711712 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;713 let budget = budget::Value::new(NESTING_BUDGET);714715 match maybe_nft_id {716 Some(nft_id) => {717 let token_id: TokenId = nft_id.into();718719 Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;720 Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;721722 <PalletNft<T>>::set_scoped_token_property(723 collection_id,724 token_id,725 PropertyScope::Rmrk,726 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,727 )?;728 }729 None => {730 let collection = Self::get_typed_nft_collection(731 collection_id,732 misc::CollectionType::Regular,733 )?;734735 Self::check_collection_owner(&collection, &sender)?;736737 <PalletCommon<T>>::set_scoped_collection_property(738 collection_id,739 PropertyScope::Rmrk,740 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,741 )?;742 }743 }744745 Self::deposit_event(Event::PropertySet {746 collection_id: rmrk_collection_id,747 maybe_nft_id,748 key,749 value,750 });751752 Ok(())753 }754755 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]756 #[transactional]757 pub fn set_priority(758 origin: OriginFor<T>,759 rmrk_collection_id: RmrkCollectionId,760 rmrk_nft_id: RmrkNftId,761 priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,762 ) -> DispatchResult {763 let sender = ensure_signed(origin)?;764 let sender = T::CrossAccountId::from_sub(sender);765766 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;767 let nft_id = rmrk_nft_id.into();768 let budget = budget::Value::new(NESTING_BUDGET);769770 Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;771 Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;772773 <PalletNft<T>>::set_scoped_token_property(774 collection_id,775 nft_id,776 PropertyScope::Rmrk,777 Self::rmrk_property(ResourcePriorities, &priorities.into_inner())?,778 )?;779780 Ok(())781 }782783 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]784 #[transactional]785 pub fn add_basic_resource(786 origin: OriginFor<T>,787 collection_id: RmrkCollectionId,788 nft_id: RmrkNftId,789 resource: RmrkBasicResource,790 ) -> DispatchResult {791 let sender = ensure_signed(origin.clone())?;792793 let resource_id = Self::resource_add(794 sender,795 Self::unique_collection_id(collection_id)?,796 nft_id.into(),797 [798 Self::rmrk_property(TokenType, &NftType::Resource)?,799 Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,800 Self::rmrk_property(Src, &resource.src)?,801 Self::rmrk_property(Metadata, &resource.metadata)?,802 Self::rmrk_property(License, &resource.license)?,803 Self::rmrk_property(Thumb, &resource.thumb)?,804 ]805 .into_iter(),806 )?;807808 Self::deposit_event(Event::ResourceAdded {809 nft_id,810 resource_id,811 });812 Ok(())813 }814815 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]816 #[transactional]817 pub fn add_composable_resource(818 origin: OriginFor<T>,819 collection_id: RmrkCollectionId,820 nft_id: RmrkNftId,821 _resource_id: RmrkBoundedResource,822 resource: RmrkComposableResource,823 ) -> DispatchResult {824 let sender = ensure_signed(origin.clone())?;825826 let resource_id = Self::resource_add(827 sender,828 Self::unique_collection_id(collection_id)?,829 nft_id.into(),830 [831 Self::rmrk_property(TokenType, &NftType::Resource)?,832 Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,833 Self::rmrk_property(Parts, &resource.parts)?,834 Self::rmrk_property(Base, &resource.base)?,835 Self::rmrk_property(Src, &resource.src)?,836 Self::rmrk_property(Metadata, &resource.metadata)?,837 Self::rmrk_property(License, &resource.license)?,838 Self::rmrk_property(Thumb, &resource.thumb)?,839 ]840 .into_iter(),841 )?;842843 Self::deposit_event(Event::ResourceAdded {844 nft_id,845 resource_id,846 });847 Ok(())848 }849850 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]851 #[transactional]852 pub fn add_slot_resource(853 origin: OriginFor<T>,854 collection_id: RmrkCollectionId,855 nft_id: RmrkNftId,856 resource: RmrkSlotResource,857 ) -> DispatchResult {858 let sender = ensure_signed(origin.clone())?;859860 let resource_id = Self::resource_add(861 sender,862 Self::unique_collection_id(collection_id)?,863 nft_id.into(),864 [865 Self::rmrk_property(TokenType, &NftType::Resource)?,866 Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,867 Self::rmrk_property(Base, &resource.base)?,868 Self::rmrk_property(Src, &resource.src)?,869 Self::rmrk_property(Metadata, &resource.metadata)?,870 Self::rmrk_property(Slot, &resource.slot)?,871 Self::rmrk_property(License, &resource.license)?,872 Self::rmrk_property(Thumb, &resource.thumb)?,873 ]874 .into_iter(),875 )?;876877 Self::deposit_event(Event::ResourceAdded {878 nft_id,879 resource_id,880 });881 Ok(())882 }883884 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]885 #[transactional]886 pub fn remove_resource(887 origin: OriginFor<T>,888 collection_id: RmrkCollectionId,889 nft_id: RmrkNftId,890 resource_id: RmrkResourceId,891 ) -> DispatchResult {892 let sender = ensure_signed(origin.clone())?;893894 Self::resource_remove(895 sender,896 Self::unique_collection_id(collection_id)?,897 nft_id.into(),898 resource_id.into(),899 )?;900901 Self::deposit_event(Event::ResourceRemoval {902 nft_id,903 resource_id,904 });905 Ok(())906 }907 }908}909910impl<T: Config> Pallet<T> {911 pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {912 let key = rmrk_key.to_key::<T>()?;913914 let scoped_key = PropertyScope::Rmrk915 .apply(key)916 .map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;917918 Ok(scoped_key)919 }920921 // todo think about renaming these922 pub fn rmrk_property<E: Encode>(923 rmrk_key: RmrkProperty,924 value: &E,925 ) -> Result<Property, DispatchError> {926 let key = rmrk_key.to_key::<T>()?;927928 let value = value929 .encode()930 .try_into()931 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;932933 let property = Property { key, value };934935 Ok(property)936 }937938 pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {939 vec.decode()940 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())941 }942943 pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>944 where945 BoundedVec<u8, S>: TryFrom<Vec<u8>>,946 {947 vec.rebind()948 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())949 }950951 fn init_collection(952 sender: T::CrossAccountId,953 data: CreateCollectionData<T::AccountId>,954 properties: impl Iterator<Item = Property>,955 ) -> Result<CollectionId, DispatchError> {956 let collection_id = <PalletNft<T>>::init_collection(sender, data);957958 if let Err(DispatchError::Arithmetic(_)) = &collection_id {959 return Err(<Error<T>>::NoAvailableCollectionId.into());960 }961962 <PalletCommon<T>>::set_scoped_collection_properties(963 collection_id?,964 PropertyScope::Rmrk,965 properties,966 )?;967968 collection_id969 }970971 pub fn create_nft(972 sender: &T::CrossAccountId,973 owner: &T::CrossAccountId,974 collection: &NonfungibleHandle<T>,975 properties: impl Iterator<Item = Property>,976 ) -> Result<TokenId, DispatchError> {977 let data = CreateNftExData {978 properties: BoundedVec::default(),979 owner: owner.clone(),980 };981982 let budget = budget::Value::new(NESTING_BUDGET);983984 <PalletNft<T>>::create_item(collection, sender, data, &budget)?;985986 let nft_id = <PalletNft<T>>::current_token_id(collection.id);987988 <PalletNft<T>>::set_scoped_token_properties(989 collection.id,990 nft_id,991 PropertyScope::Rmrk,992 properties,993 )?;994995 Ok(nft_id)996 }997998 fn destroy_nft(999 sender: T::CrossAccountId,1000 collection_id: CollectionId,1001 token_id: TokenId,1002 ) -> DispatchResult {1003 let collection =1004 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;10051006 let token_data =1007 <TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;10081009 let from = token_data.owner;10101011 let budget = budget::Value::new(NESTING_BUDGET);10121013 <PalletNft<T>>::burn_from(&collection, &sender, &from, token_id, &budget)1014 }10151016 fn resource_add(1017 sender: T::AccountId,1018 collection_id: CollectionId,1019 token_id: TokenId,1020 resource_properties: impl Iterator<Item = Property>,1021 ) -> Result<RmrkResourceId, DispatchError> {1022 let collection =1023 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1024 ensure!(collection.owner == sender, Error::<T>::NoPermission);10251026 let sender = T::CrossAccountId::from_sub(sender);1027 let budget = budget::Value::new(NESTING_BUDGET);10281029 let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, token_id, &budget)1030 .map_err(Self::map_unique_err_to_proxy)?;10311032 let pending = sender != nft_owner;10331034 let resource_collection_id: CollectionId =1035 Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;1036 let resource_collection =1037 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;10381039 // todo probably add extra connections to bases, slots, etc., when RMRK starts to use them10401041 let resource_id = Self::create_nft(1042 &sender,1043 &nft_owner,1044 &resource_collection,1045 resource_properties.chain(1046 [1047 Self::rmrk_property(PendingResourceAccept, &pending)?,1048 Self::rmrk_property(PendingResourceRemoval, &false)?,1049 ]1050 .into_iter(),1051 ),1052 )1053 .map_err(|err| match err {1054 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),1055 err => Self::map_unique_err_to_proxy(err),1056 })?;10571058 Ok(resource_id.0)1059 }10601061 fn resource_remove(1062 sender: T::AccountId,1063 collection_id: CollectionId,1064 nft_id: TokenId,1065 resource_id: TokenId,1066 ) -> DispatchResult {1067 let collection =1068 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1069 ensure!(collection.owner == sender, Error::<T>::NoPermission);10701071 let resource_collection_id: CollectionId =1072 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;1073 let resource_collection =1074 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;1075 ensure!(1076 <PalletNft<T>>::token_exists(&resource_collection, resource_id),1077 Error::<T>::ResourceDoesntExist1078 );10791080 let budget = up_data_structs::budget::Value::new(10);1081 let topmost_owner =1082 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;10831084 let sender = T::CrossAccountId::from_sub(sender);1085 if topmost_owner == sender {1086 <PalletNft<T>>::burn(&resource_collection, &sender, resource_id)1087 .map_err(Self::map_unique_err_to_proxy)?;1088 } else {1089 <PalletNft<T>>::set_scoped_token_property(1090 resource_collection_id,1091 resource_id,1092 PropertyScope::Rmrk,1093 Self::rmrk_property(PendingResourceRemoval, &true)?,1094 )?;1095 }10961097 Ok(())1098 }10991100 fn change_collection_owner(1101 collection_id: CollectionId,1102 collection_type: misc::CollectionType,1103 sender: T::AccountId,1104 new_owner: T::AccountId,1105 ) -> DispatchResult {1106 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1107 Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;11081109 let mut collection = collection.into_inner();11101111 collection.owner = new_owner;1112 collection.save()1113 }11141115 fn check_collection_owner(1116 collection: &NonfungibleHandle<T>,1117 account: &T::CrossAccountId,1118 ) -> DispatchResult {1119 collection1120 .check_is_owner(account)1121 .map_err(Self::map_unique_err_to_proxy)1122 }11231124 pub fn last_collection_idx() -> RmrkCollectionId {1125 <CollectionIndex<T>>::get()1126 }11271128 pub fn unique_collection_id(1129 rmrk_collection_id: RmrkCollectionId,1130 ) -> Result<CollectionId, DispatchError> {1131 <UniqueCollectionId<T>>::try_get(rmrk_collection_id)1132 .map_err(|_| <Error<T>>::CollectionUnknown.into())1133 }11341135 pub fn rmrk_collection_id(1136 unique_collection_id: CollectionId,1137 ) -> Result<RmrkCollectionId, DispatchError> {1138 <RmrkInernalCollectionId<T>>::try_get(unique_collection_id)1139 .map_err(|_| <Error<T>>::CollectionUnknown.into())1140 }11411142 pub fn get_nft_collection(1143 collection_id: CollectionId,1144 ) -> Result<NonfungibleHandle<T>, DispatchError> {1145 let collection = <CollectionHandle<T>>::try_get(collection_id)1146 .map_err(|_| <Error<T>>::CollectionUnknown)?;11471148 match collection.mode {1149 CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1150 _ => Err(<Error<T>>::CollectionUnknown.into()),1151 }1152 }11531154 pub fn collection_exists(collection_id: CollectionId) -> bool {1155 <CollectionHandle<T>>::try_get(collection_id).is_ok()1156 }11571158 pub fn get_collection_property(1159 collection_id: CollectionId,1160 key: RmrkProperty,1161 ) -> Result<PropertyValue, DispatchError> {1162 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1163 .get(&Self::rmrk_property_key(key)?)1164 .ok_or(<Error<T>>::CollectionUnknown)?1165 .clone();11661167 Ok(collection_property)1168 }11691170 pub fn get_collection_property_decoded<V: Decode>(1171 collection_id: CollectionId,1172 key: RmrkProperty,1173 ) -> Result<V, DispatchError> {1174 Self::decode_property(Self::get_collection_property(collection_id, key)?)1175 }11761177 pub fn get_collection_type(1178 collection_id: CollectionId,1179 ) -> Result<misc::CollectionType, DispatchError> {1180 Self::get_collection_property_decoded(collection_id, CollectionType)1181 .map_err(|_| <Error<T>>::CorruptedCollectionType.into())1182 }11831184 pub fn ensure_collection_type(1185 collection_id: CollectionId,1186 collection_type: misc::CollectionType,1187 ) -> DispatchResult {1188 let actual_type = Self::get_collection_type(collection_id)?;1189 ensure!(1190 actual_type == collection_type,1191 <CommonError<T>>::NoPermission1192 );11931194 Ok(())1195 }11961197 pub fn get_typed_nft_collection(1198 collection_id: CollectionId,1199 collection_type: misc::CollectionType,1200 ) -> Result<NonfungibleHandle<T>, DispatchError> {1201 Self::ensure_collection_type(collection_id, collection_type)?;12021203 Self::get_nft_collection(collection_id)1204 }12051206 pub fn get_typed_nft_collection_mapped(1207 rmrk_collection_id: RmrkCollectionId,1208 collection_type: misc::CollectionType,1209 ) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1210 let unique_collection_id = match collection_type {1211 misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1212 _ => rmrk_collection_id.into(),1213 };12141215 let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;12161217 Ok((collection, unique_collection_id))1218 }12191220 pub fn get_nft_property(1221 collection_id: CollectionId,1222 nft_id: TokenId,1223 key: RmrkProperty,1224 ) -> Result<PropertyValue, DispatchError> {1225 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1226 .get(&Self::rmrk_property_key(key)?)1227 .ok_or(<Error<T>>::NoAvailableNftId)? // todo replace with better error?1228 .clone();12291230 Ok(nft_property)1231 }12321233 pub fn get_nft_property_decoded<V: Decode>(1234 collection_id: CollectionId,1235 nft_id: TokenId,1236 key: RmrkProperty,1237 ) -> Result<V, DispatchError> {1238 Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)1239 }12401241 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1242 <TokenData<T>>::contains_key((collection_id, nft_id))1243 }12441245 pub fn get_nft_type(1246 collection_id: CollectionId,1247 token_id: TokenId,1248 ) -> Result<NftType, DispatchError> {1249 Self::get_nft_property_decoded(collection_id, token_id, TokenType)1250 .map_err(|_| <Error<T>>::NoAvailableNftId.into())1251 }12521253 pub fn ensure_nft_type(1254 collection_id: CollectionId,1255 token_id: TokenId,1256 nft_type: NftType,1257 ) -> DispatchResult {1258 let actual_type = Self::get_nft_type(collection_id, token_id)?;1259 ensure!(actual_type == nft_type, <Error<T>>::NoPermission);12601261 Ok(())1262 }12631264 pub fn ensure_nft_owner(1265 collection_id: CollectionId,1266 token_id: TokenId,1267 possible_owner: &T::CrossAccountId,1268 nesting_budget: &dyn budget::Budget,1269 ) -> DispatchResult {1270 let is_owned = <PalletStructure<T>>::check_indirectly_owned(1271 possible_owner.clone(),1272 collection_id,1273 token_id,1274 None,1275 nesting_budget,1276 )1277 .map_err(Self::map_unique_err_to_proxy)?;12781279 ensure!(is_owned, <Error<T>>::NoPermission);12801281 Ok(())1282 }12831284 pub fn filter_user_properties<Key, Value, R, Mapper>(1285 collection_id: CollectionId,1286 token_id: Option<TokenId>,1287 filter_keys: Option<Vec<RmrkPropertyKey>>,1288 mapper: Mapper,1289 ) -> Result<Vec<R>, DispatchError>1290 where1291 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1292 Value: Decode + Default,1293 Mapper: Fn(Key, Value) -> R,1294 {1295 filter_keys1296 .map(|keys| {1297 let properties = keys1298 .into_iter()1299 .filter_map(|key| {1300 let key: Key = key.try_into().ok()?;13011302 let value = match token_id {1303 Some(token_id) => Self::get_nft_property_decoded(1304 collection_id,1305 token_id,1306 UserProperty(key.as_ref()),1307 ),1308 None => Self::get_collection_property_decoded(1309 collection_id,1310 UserProperty(key.as_ref()),1311 ),1312 }1313 .ok()?;13141315 Some(mapper(key, value))1316 })1317 .collect();13181319 Ok(properties)1320 })1321 .unwrap_or_else(|| {1322 let properties =1323 Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();13241325 Ok(properties)1326 })1327 }13281329 pub fn iterate_user_properties<Key, Value, R, Mapper>(1330 collection_id: CollectionId,1331 token_id: Option<TokenId>,1332 mapper: Mapper,1333 ) -> Result<impl Iterator<Item = R>, DispatchError>1334 where1335 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1336 Value: Decode + Default,1337 Mapper: Fn(Key, Value) -> R,1338 {1339 let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;13401341 let properties = match token_id {1342 Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1343 None => <PalletCommon<T>>::collection_properties(collection_id),1344 };13451346 let properties = properties.into_iter().filter_map(move |(key, value)| {1347 let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;13481349 let key: Key = key.to_vec().try_into().ok()?;1350 let value: Value = value.decode().ok()?;13511352 Some(mapper(key, value))1353 });13541355 Ok(properties)1356 }13571358 fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1359 map_unique_err_to_proxy! {1360 match err {1361 CommonError::NoPermission => NoPermission,1362 CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1363 CommonError::PublicMintingNotAllowed => NoPermission,1364 CommonError::TokenNotFound => NoAvailableNftId,1365 CommonError::ApprovedValueTooLow => NoPermission,1366 CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1367 StructureError::TokenNotFound => NoAvailableNftId,1368 StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1369 }1370 }1371 }1372}1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![cfg_attr(not(feature = "std"), no_std)]1819use frame_support::{pallet_prelude::*, transactional, BoundedVec, dispatch::DispatchResult};20use frame_system::{pallet_prelude::*, ensure_signed};21use sp_runtime::{DispatchError, Permill, traits::StaticLookup};22use sp_std::vec::Vec;23use up_data_structs::{*, mapping::TokenAddressMapping};24use pallet_common::{25 Pallet as PalletCommon, Error as CommonError, CollectionHandle, CommonCollectionOperations,26};27use pallet_nonfungible::{Pallet as PalletNft, NonfungibleHandle, TokenData};28use pallet_structure::{Pallet as PalletStructure, Error as StructureError};29use pallet_evm::account::CrossAccountId;30use core::convert::AsRef;3132pub use pallet::*;3334pub mod misc;35pub mod property;3637use misc::*;38pub use property::*;3940use RmrkProperty::*;4142const NESTING_BUDGET: u32 = 5;4344#[frame_support::pallet]45pub mod pallet {46 use super::*;47 use pallet_evm::account;4849 #[pallet::config]50 pub trait Config:51 frame_system::Config + pallet_common::Config + pallet_nonfungible::Config + account::Config52 {53 type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;54 }5556 #[pallet::storage]57 #[pallet::getter(fn collection_index)]58 pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;5960 #[pallet::storage]61 pub type UniqueCollectionId<T: Config> =62 StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;6364 #[pallet::storage]65 pub type RmrkInernalCollectionId<T: Config> =66 StorageMap<_, Twox64Concat, CollectionId, RmrkCollectionId, ValueQuery>;6768 #[pallet::pallet]69 #[pallet::generate_store(pub(super) trait Store)]70 pub struct Pallet<T>(_);7172 #[pallet::event]73 #[pallet::generate_deposit(pub(super) fn deposit_event)]74 pub enum Event<T: Config> {75 CollectionCreated {76 issuer: T::AccountId,77 collection_id: RmrkCollectionId,78 },79 CollectionDestroyed {80 issuer: T::AccountId,81 collection_id: RmrkCollectionId,82 },83 IssuerChanged {84 old_issuer: T::AccountId,85 new_issuer: T::AccountId,86 collection_id: RmrkCollectionId,87 },88 CollectionLocked {89 issuer: T::AccountId,90 collection_id: RmrkCollectionId,91 },92 NftMinted {93 owner: T::AccountId,94 collection_id: RmrkCollectionId,95 nft_id: RmrkNftId,96 },97 NFTBurned {98 owner: T::AccountId,99 nft_id: RmrkNftId,100 },101 NFTSent {102 sender: T::AccountId,103 recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,104 collection_id: RmrkCollectionId,105 nft_id: RmrkNftId,106 approval_required: bool,107 },108 NFTAccepted {109 sender: T::AccountId,110 recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,111 collection_id: RmrkCollectionId,112 nft_id: RmrkNftId,113 },114 NFTRejected {115 sender: T::AccountId,116 collection_id: RmrkCollectionId,117 nft_id: RmrkNftId,118 },119 PropertySet {120 collection_id: RmrkCollectionId,121 maybe_nft_id: Option<RmrkNftId>,122 key: RmrkKeyString,123 value: RmrkValueString,124 },125 ResourceAdded {126 nft_id: RmrkNftId,127 resource_id: RmrkResourceId,128 },129 ResourceRemoval {130 nft_id: RmrkNftId,131 resource_id: RmrkResourceId,132 },133 ResourceAccepted {134 nft_id: RmrkNftId,135 resource_id: RmrkResourceId,136 },137 ResourceRemovalAccepted {138 nft_id: RmrkNftId,139 resource_id: RmrkResourceId,140 },141 PrioritySet {142 collection_id: RmrkCollectionId,143 nft_id: RmrkNftId,144 },145 }146147 #[pallet::error]148 pub enum Error<T> {149 /* Unique-specific events */150 CorruptedCollectionType,151 NftTypeEncodeError,152 RmrkPropertyKeyIsTooLong,153 RmrkPropertyValueIsTooLong,154155 /* RMRK compatible events */156 CollectionNotEmpty,157 NoAvailableCollectionId,158 NoAvailableNftId,159 CollectionUnknown,160 NoPermission,161 NonTransferable,162 CollectionFullOrLocked,163 ResourceDoesntExist,164 CannotSendToDescendentOrSelf,165 CannotAcceptNonOwnedNft,166 CannotRejectNonOwnedNft,167 ResourceNotPending,168 }169170 #[pallet::call]171 impl<T: Config> Pallet<T> {172 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]173 #[transactional]174 pub fn create_collection(175 origin: OriginFor<T>,176 metadata: RmrkString,177 max: Option<u32>,178 symbol: RmrkCollectionSymbol,179 ) -> DispatchResult {180 let sender = ensure_signed(origin)?;181182 let limits = CollectionLimits {183 owner_can_transfer: Some(false),184 token_limit: max,185 ..Default::default()186 };187188 let data = CreateCollectionData {189 limits: Some(limits),190 token_prefix: symbol191 .into_inner()192 .try_into()193 .map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,194 permissions: Some(CollectionPermissions {195 nesting: Some(NestingRule::Owner),196 ..Default::default()197 }),198 ..Default::default()199 };200201 let unique_collection_id = Self::init_collection(202 T::CrossAccountId::from_sub(sender.clone()),203 data,204 [205 Self::rmrk_property(Metadata, &metadata)?,206 Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,207 ]208 .into_iter(),209 )?;210 let rmrk_collection_id = <CollectionIndex<T>>::get();211212 <UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);213 <RmrkInernalCollectionId<T>>::insert(unique_collection_id, rmrk_collection_id);214215 <CollectionIndex<T>>::mutate(|n| *n += 1);216217 Self::deposit_event(Event::CollectionCreated {218 issuer: sender,219 collection_id: rmrk_collection_id,220 });221222 Ok(())223 }224225 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]226 #[transactional]227 pub fn destroy_collection(228 origin: OriginFor<T>,229 collection_id: RmrkCollectionId,230 ) -> DispatchResult {231 let sender = ensure_signed(origin)?;232 let cross_sender = T::CrossAccountId::from_sub(sender.clone());233234 let collection = Self::get_typed_nft_collection(235 Self::unique_collection_id(collection_id)?,236 misc::CollectionType::Regular,237 )?;238239 <PalletNft<T>>::destroy_collection(collection, &cross_sender)240 .map_err(Self::map_unique_err_to_proxy)?;241242 Self::deposit_event(Event::CollectionDestroyed {243 issuer: sender,244 collection_id,245 });246247 Ok(())248 }249250 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]251 #[transactional]252 pub fn change_collection_issuer(253 origin: OriginFor<T>,254 collection_id: RmrkCollectionId,255 new_issuer: <T::Lookup as StaticLookup>::Source,256 ) -> DispatchResult {257 let sender = ensure_signed(origin)?;258259 let new_issuer = T::Lookup::lookup(new_issuer)?;260261 Self::change_collection_owner(262 Self::unique_collection_id(collection_id)?,263 misc::CollectionType::Regular,264 sender.clone(),265 new_issuer.clone(),266 )?;267268 Self::deposit_event(Event::IssuerChanged {269 old_issuer: sender,270 new_issuer,271 collection_id,272 });273274 Ok(())275 }276277 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]278 #[transactional]279 pub fn lock_collection(280 origin: OriginFor<T>,281 collection_id: RmrkCollectionId,282 ) -> DispatchResult {283 let sender = ensure_signed(origin)?;284 let cross_sender = T::CrossAccountId::from_sub(sender.clone());285286 let collection = Self::get_typed_nft_collection(287 Self::unique_collection_id(collection_id)?,288 misc::CollectionType::Regular,289 )?;290291 Self::check_collection_owner(&collection, &cross_sender)?;292293 let token_count = collection.total_supply();294295 let mut collection = collection.into_inner();296 collection.limits.token_limit = Some(token_count);297 collection.save()?;298299 Self::deposit_event(Event::CollectionLocked {300 issuer: sender,301 collection_id,302 });303304 Ok(())305 }306307 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]308 #[transactional]309 pub fn mint_nft(310 origin: OriginFor<T>,311 owner: T::AccountId,312 collection_id: RmrkCollectionId,313 recipient: Option<T::AccountId>,314 royalty_amount: Option<Permill>,315 metadata: RmrkString,316 transferable: bool,317 ) -> DispatchResult {318 let sender = ensure_signed(origin)?;319 let sender = T::CrossAccountId::from_sub(sender);320 let cross_owner = T::CrossAccountId::from_sub(owner.clone());321322 let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {323 recipient: recipient.unwrap_or_else(|| owner.clone()),324 amount,325 });326327 let collection = Self::get_typed_nft_collection(328 Self::unique_collection_id(collection_id)?,329 misc::CollectionType::Regular,330 )?;331332 let nft_id = Self::create_nft(333 &sender,334 &cross_owner,335 &collection,336 [337 Self::rmrk_property(TokenType, &NftType::Regular)?,338 Self::rmrk_property(Transferable, &transferable)?,339 Self::rmrk_property(PendingNftAccept, &false)?,340 Self::rmrk_property(RoyaltyInfo, &royalty_info)?,341 Self::rmrk_property(Metadata, &metadata)?,342 Self::rmrk_property(Equipped, &false)?,343 Self::rmrk_property(344 ResourceCollection,345 &Self::init_collection(346 sender.clone(),347 CreateCollectionData {348 ..Default::default()349 },350 [Self::rmrk_property(351 CollectionType,352 &misc::CollectionType::Resource,353 )?]354 .into_iter(),355 )?,356 )?, // todo possibly add limits to the collection if rmrk warrants them357 Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,358 ]359 .into_iter(),360 )361 .map_err(|err| match err {362 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),363 err => Self::map_unique_err_to_proxy(err),364 })?;365366 Self::deposit_event(Event::NftMinted {367 owner,368 collection_id,369 nft_id: nft_id.0,370 });371372 Ok(())373 }374375 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]376 #[transactional]377 pub fn burn_nft(378 origin: OriginFor<T>,379 collection_id: RmrkCollectionId,380 nft_id: RmrkNftId,381 ) -> DispatchResult {382 let sender = ensure_signed(origin)?;383 let cross_sender = T::CrossAccountId::from_sub(sender.clone());384385 Self::destroy_nft(386 cross_sender,387 Self::unique_collection_id(collection_id)?,388 nft_id.into(),389 )390 .map_err(Self::map_unique_err_to_proxy)?;391392 Self::deposit_event(Event::NFTBurned {393 owner: sender,394 nft_id,395 });396397 Ok(())398 }399400 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]401 #[transactional]402 pub fn send(403 origin: OriginFor<T>,404 rmrk_collection_id: RmrkCollectionId,405 rmrk_nft_id: RmrkNftId,406 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,407 ) -> DispatchResult {408 let sender = ensure_signed(origin.clone())?;409 let cross_sender = T::CrossAccountId::from_sub(sender.clone());410411 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;412 let nft_id = rmrk_nft_id.into();413414 let token_data =415 <TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;416417 let from = token_data.owner;418419 let collection =420 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;421422 ensure!(423 Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,424 <Error<T>>::NonTransferable425 );426427 ensure!(428 !Self::get_nft_property_decoded(429 collection_id,430 nft_id,431 RmrkProperty::PendingNftAccept432 )?,433 <Error<T>>::NoPermission434 );435436 let target_owner;437 let approval_required;438439 match new_owner {440 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {441 target_owner = T::CrossAccountId::from_sub(account_id.clone());442 approval_required = false;443 }444 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(445 target_collection_id,446 target_nft_id,447 ) => {448 let target_collection_id = Self::unique_collection_id(target_collection_id)?;449450 let target_nft_budget = budget::Value::new(NESTING_BUDGET);451452 let target_nft_owner = <PalletStructure<T>>::get_checked_indirect_owner(453 target_collection_id,454 target_nft_id.into(),455 Some((collection_id, nft_id)),456 &target_nft_budget,457 )458 .map_err(Self::map_unique_err_to_proxy)?;459460 approval_required = cross_sender != target_nft_owner;461462 if approval_required {463 target_owner = target_nft_owner;464465 <PalletNft<T>>::set_scoped_token_property(466 collection.id,467 nft_id,468 PropertyScope::Rmrk,469 Self::rmrk_property(PendingNftAccept, &approval_required)?,470 )?;471 } else {472 target_owner = T::CrossTokenAddressMapping::token_to_address(473 target_collection_id,474 target_nft_id.into(),475 );476 }477 }478 }479480 let src_nft_budget = budget::Value::new(NESTING_BUDGET);481482 <PalletNft<T>>::transfer_from(483 &collection,484 &cross_sender,485 &from,486 &target_owner,487 nft_id,488 &src_nft_budget,489 )490 .map_err(Self::map_unique_err_to_proxy)?;491492 Self::deposit_event(Event::NFTSent {493 sender,494 recipient: new_owner,495 collection_id: rmrk_collection_id,496 nft_id: rmrk_nft_id,497 approval_required,498 });499500 Ok(())501 }502503 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]504 #[transactional]505 pub fn accept_nft(506 origin: OriginFor<T>,507 rmrk_collection_id: RmrkCollectionId,508 rmrk_nft_id: RmrkNftId,509 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,510 ) -> DispatchResult {511 let sender = ensure_signed(origin.clone())?;512 let cross_sender = T::CrossAccountId::from_sub(sender.clone());513514 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;515 let nft_id = rmrk_nft_id.into();516517 let collection =518 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;519520 let new_cross_owner = match new_owner {521 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {522 T::CrossAccountId::from_sub(account_id.clone())523 }524 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(525 target_collection_id,526 target_nft_id,527 ) => {528 let target_collection_id = Self::unique_collection_id(target_collection_id)?;529530 T::CrossTokenAddressMapping::token_to_address(531 target_collection_id,532 TokenId(target_nft_id),533 )534 }535 };536537 let budget = budget::Value::new(NESTING_BUDGET);538539 <PalletNft<T>>::transfer(&collection, &cross_sender, &new_cross_owner, nft_id, &budget)540 .map_err(|err| {541 if err == <CommonError<T>>::OnlyOwnerAllowedToNest.into() {542 <Error<T>>::CannotAcceptNonOwnedNft.into()543 } else {544 Self::map_unique_err_to_proxy(err)545 }546 })?;547548 <PalletNft<T>>::set_scoped_token_property(549 collection.id,550 nft_id,551 PropertyScope::Rmrk,552 Self::rmrk_property(PendingNftAccept, &false)?,553 )?;554555 Self::deposit_event(Event::NFTAccepted {556 sender,557 recipient: new_owner,558 collection_id: rmrk_collection_id,559 nft_id: rmrk_nft_id,560 });561562 Ok(())563 }564565 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]566 #[transactional]567 pub fn reject_nft(568 origin: OriginFor<T>,569 rmrk_collection_id: RmrkCollectionId,570 rmrk_nft_id: RmrkNftId,571 ) -> DispatchResult {572 let sender = ensure_signed(origin)?;573 let cross_sender = T::CrossAccountId::from_sub(sender.clone());574575 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;576 let nft_id = rmrk_nft_id.into();577578 Self::destroy_nft(cross_sender, collection_id, nft_id).map_err(|err| {579 if err == <CommonError<T>>::NoPermission.into()580 || err == <CommonError<T>>::ApprovedValueTooLow.into()581 {582 <Error<T>>::CannotRejectNonOwnedNft.into()583 } else {584 Self::map_unique_err_to_proxy(err)585 }586 })?;587588 Self::deposit_event(Event::NFTRejected {589 sender,590 collection_id: rmrk_collection_id,591 nft_id: rmrk_nft_id,592 });593594 Ok(())595 }596597 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]598 #[transactional]599 pub fn accept_resource(600 origin: OriginFor<T>,601 rmrk_collection_id: RmrkCollectionId,602 rmrk_nft_id: RmrkNftId,603 rmrk_resource_id: RmrkResourceId,604 ) -> DispatchResult {605 let sender = ensure_signed(origin)?;606 let cross_sender = T::CrossAccountId::from_sub(sender);607608 let collection_id = Self::unique_collection_id(rmrk_collection_id)609 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;610611 let nft_id = rmrk_nft_id.into();612 let resource_id = rmrk_resource_id.into();613614 let budget = budget::Value::new(NESTING_BUDGET);615616 let nft_owner =617 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)618 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;619620 let resource_collection_id: CollectionId =621 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)622 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;623624 let is_pending: bool = Self::get_nft_property_decoded(625 resource_collection_id,626 resource_id,627 PendingResourceAccept,628 )629 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;630631 ensure!(is_pending, <Error<T>>::ResourceNotPending);632633 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);634635 <PalletNft<T>>::set_scoped_token_property(636 resource_collection_id,637 rmrk_resource_id.into(),638 PropertyScope::Rmrk,639 Self::rmrk_property(PendingResourceAccept, &false)?,640 )?;641642 Self::deposit_event(Event::<T>::ResourceAccepted {643 nft_id: rmrk_nft_id,644 resource_id: rmrk_resource_id,645 });646647 Ok(())648 }649650 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]651 #[transactional]652 pub fn accept_resource_removal(653 origin: OriginFor<T>,654 rmrk_collection_id: RmrkCollectionId,655 rmrk_nft_id: RmrkNftId,656 rmrk_resource_id: RmrkResourceId,657 ) -> DispatchResult {658 let sender = ensure_signed(origin)?;659 let cross_sender = T::CrossAccountId::from_sub(sender);660661 let collection_id = Self::unique_collection_id(rmrk_collection_id)662 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;663664 let nft_id = rmrk_nft_id.into();665 let resource_id = rmrk_resource_id.into();666667 let budget = budget::Value::new(NESTING_BUDGET);668669 let nft_owner =670 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)671 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;672673 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);674675 let resource_collection_id: CollectionId =676 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)677 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;678679 let is_pending: bool = Self::get_nft_property_decoded(680 resource_collection_id,681 resource_id,682 PendingResourceRemoval,683 )684 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;685686 ensure!(is_pending, <Error<T>>::ResourceNotPending);687688 let resource_collection = Self::get_typed_nft_collection(689 resource_collection_id,690 misc::CollectionType::Resource,691 )?;692693 <PalletNft<T>>::burn(&resource_collection, &cross_sender, rmrk_resource_id.into())694 .map_err(Self::map_unique_err_to_proxy)?;695696 Self::deposit_event(Event::<T>::ResourceRemovalAccepted {697 nft_id: rmrk_nft_id,698 resource_id: rmrk_resource_id,699 });700701 Ok(())702 }703704 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]705 #[transactional]706 pub fn set_property(707 origin: OriginFor<T>,708 #[pallet::compact] rmrk_collection_id: RmrkCollectionId,709 maybe_nft_id: Option<RmrkNftId>,710 key: RmrkKeyString,711 value: RmrkValueString,712 ) -> DispatchResult {713 let sender = ensure_signed(origin)?;714 let sender = T::CrossAccountId::from_sub(sender);715716 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;717 let budget = budget::Value::new(NESTING_BUDGET);718719 match maybe_nft_id {720 Some(nft_id) => {721 let token_id: TokenId = nft_id.into();722723 Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;724 Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;725726 <PalletNft<T>>::set_scoped_token_property(727 collection_id,728 token_id,729 PropertyScope::Rmrk,730 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,731 )?;732 }733 None => {734 let collection = Self::get_typed_nft_collection(735 collection_id,736 misc::CollectionType::Regular,737 )?;738739 Self::check_collection_owner(&collection, &sender)?;740741 <PalletCommon<T>>::set_scoped_collection_property(742 collection_id,743 PropertyScope::Rmrk,744 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,745 )?;746 }747 }748749 Self::deposit_event(Event::PropertySet {750 collection_id: rmrk_collection_id,751 maybe_nft_id,752 key,753 value,754 });755756 Ok(())757 }758759 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]760 #[transactional]761 pub fn set_priority(762 origin: OriginFor<T>,763 rmrk_collection_id: RmrkCollectionId,764 rmrk_nft_id: RmrkNftId,765 priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,766 ) -> DispatchResult {767 let sender = ensure_signed(origin)?;768 let sender = T::CrossAccountId::from_sub(sender);769770 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;771 let nft_id = rmrk_nft_id.into();772 let budget = budget::Value::new(NESTING_BUDGET);773774 Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;775 Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;776777 <PalletNft<T>>::set_scoped_token_property(778 collection_id,779 nft_id,780 PropertyScope::Rmrk,781 Self::rmrk_property(ResourcePriorities, &priorities.into_inner())?,782 )?;783784 Self::deposit_event(Event::<T>::PrioritySet {785 collection_id: rmrk_collection_id,786 nft_id: rmrk_nft_id,787 });788789 Ok(())790 }791792 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]793 #[transactional]794 pub fn add_basic_resource(795 origin: OriginFor<T>,796 collection_id: RmrkCollectionId,797 nft_id: RmrkNftId,798 resource: RmrkBasicResource,799 ) -> DispatchResult {800 let sender = ensure_signed(origin.clone())?;801802 let resource_id = Self::resource_add(803 sender,804 Self::unique_collection_id(collection_id)?,805 nft_id.into(),806 [807 Self::rmrk_property(TokenType, &NftType::Resource)?,808 Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,809 Self::rmrk_property(Src, &resource.src)?,810 Self::rmrk_property(Metadata, &resource.metadata)?,811 Self::rmrk_property(License, &resource.license)?,812 Self::rmrk_property(Thumb, &resource.thumb)?,813 ]814 .into_iter(),815 )?;816817 Self::deposit_event(Event::ResourceAdded {818 nft_id,819 resource_id,820 });821 Ok(())822 }823824 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]825 #[transactional]826 pub fn add_composable_resource(827 origin: OriginFor<T>,828 collection_id: RmrkCollectionId,829 nft_id: RmrkNftId,830 _resource_id: RmrkBoundedResource,831 resource: RmrkComposableResource,832 ) -> DispatchResult {833 let sender = ensure_signed(origin.clone())?;834835 let resource_id = Self::resource_add(836 sender,837 Self::unique_collection_id(collection_id)?,838 nft_id.into(),839 [840 Self::rmrk_property(TokenType, &NftType::Resource)?,841 Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,842 Self::rmrk_property(Parts, &resource.parts)?,843 Self::rmrk_property(Base, &resource.base)?,844 Self::rmrk_property(Src, &resource.src)?,845 Self::rmrk_property(Metadata, &resource.metadata)?,846 Self::rmrk_property(License, &resource.license)?,847 Self::rmrk_property(Thumb, &resource.thumb)?,848 ]849 .into_iter(),850 )?;851852 Self::deposit_event(Event::ResourceAdded {853 nft_id,854 resource_id,855 });856 Ok(())857 }858859 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]860 #[transactional]861 pub fn add_slot_resource(862 origin: OriginFor<T>,863 collection_id: RmrkCollectionId,864 nft_id: RmrkNftId,865 resource: RmrkSlotResource,866 ) -> DispatchResult {867 let sender = ensure_signed(origin.clone())?;868869 let resource_id = Self::resource_add(870 sender,871 Self::unique_collection_id(collection_id)?,872 nft_id.into(),873 [874 Self::rmrk_property(TokenType, &NftType::Resource)?,875 Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,876 Self::rmrk_property(Base, &resource.base)?,877 Self::rmrk_property(Src, &resource.src)?,878 Self::rmrk_property(Metadata, &resource.metadata)?,879 Self::rmrk_property(Slot, &resource.slot)?,880 Self::rmrk_property(License, &resource.license)?,881 Self::rmrk_property(Thumb, &resource.thumb)?,882 ]883 .into_iter(),884 )?;885886 Self::deposit_event(Event::ResourceAdded {887 nft_id,888 resource_id,889 });890 Ok(())891 }892893 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]894 #[transactional]895 pub fn remove_resource(896 origin: OriginFor<T>,897 collection_id: RmrkCollectionId,898 nft_id: RmrkNftId,899 resource_id: RmrkResourceId,900 ) -> DispatchResult {901 let sender = ensure_signed(origin.clone())?;902903 Self::resource_remove(904 sender,905 Self::unique_collection_id(collection_id)?,906 nft_id.into(),907 resource_id.into(),908 )?;909910 Self::deposit_event(Event::ResourceRemoval {911 nft_id,912 resource_id,913 });914 Ok(())915 }916 }917}918919impl<T: Config> Pallet<T> {920 pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {921 let key = rmrk_key.to_key::<T>()?;922923 let scoped_key = PropertyScope::Rmrk924 .apply(key)925 .map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;926927 Ok(scoped_key)928 }929930 // todo think about renaming these931 pub fn rmrk_property<E: Encode>(932 rmrk_key: RmrkProperty,933 value: &E,934 ) -> Result<Property, DispatchError> {935 let key = rmrk_key.to_key::<T>()?;936937 let value = value938 .encode()939 .try_into()940 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;941942 let property = Property { key, value };943944 Ok(property)945 }946947 pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {948 vec.decode()949 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())950 }951952 pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>953 where954 BoundedVec<u8, S>: TryFrom<Vec<u8>>,955 {956 vec.rebind()957 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())958 }959960 fn init_collection(961 sender: T::CrossAccountId,962 data: CreateCollectionData<T::AccountId>,963 properties: impl Iterator<Item = Property>,964 ) -> Result<CollectionId, DispatchError> {965 let collection_id = <PalletNft<T>>::init_collection(sender, data);966967 if let Err(DispatchError::Arithmetic(_)) = &collection_id {968 return Err(<Error<T>>::NoAvailableCollectionId.into());969 }970971 <PalletCommon<T>>::set_scoped_collection_properties(972 collection_id?,973 PropertyScope::Rmrk,974 properties,975 )?;976977 collection_id978 }979980 pub fn create_nft(981 sender: &T::CrossAccountId,982 owner: &T::CrossAccountId,983 collection: &NonfungibleHandle<T>,984 properties: impl Iterator<Item = Property>,985 ) -> Result<TokenId, DispatchError> {986 let data = CreateNftExData {987 properties: BoundedVec::default(),988 owner: owner.clone(),989 };990991 let budget = budget::Value::new(NESTING_BUDGET);992993 <PalletNft<T>>::create_item(collection, sender, data, &budget)?;994995 let nft_id = <PalletNft<T>>::current_token_id(collection.id);996997 <PalletNft<T>>::set_scoped_token_properties(998 collection.id,999 nft_id,1000 PropertyScope::Rmrk,1001 properties,1002 )?;10031004 Ok(nft_id)1005 }10061007 fn destroy_nft(1008 sender: T::CrossAccountId,1009 collection_id: CollectionId,1010 token_id: TokenId,1011 ) -> DispatchResult {1012 let collection =1013 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;10141015 let token_data =1016 <TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;10171018 let from = token_data.owner;10191020 let budget = budget::Value::new(NESTING_BUDGET);10211022 <PalletNft<T>>::burn_from(&collection, &sender, &from, token_id, &budget)1023 }10241025 fn resource_add(1026 sender: T::AccountId,1027 collection_id: CollectionId,1028 token_id: TokenId,1029 resource_properties: impl Iterator<Item = Property>,1030 ) -> Result<RmrkResourceId, DispatchError> {1031 let collection =1032 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1033 ensure!(collection.owner == sender, Error::<T>::NoPermission);10341035 let sender = T::CrossAccountId::from_sub(sender);1036 let budget = budget::Value::new(NESTING_BUDGET);10371038 let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, token_id, &budget)1039 .map_err(Self::map_unique_err_to_proxy)?;10401041 let pending = sender != nft_owner;10421043 let resource_collection_id: CollectionId =1044 Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;1045 let resource_collection =1046 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;10471048 // todo probably add extra connections to bases, slots, etc., when RMRK starts to use them10491050 let resource_id = Self::create_nft(1051 &sender,1052 &nft_owner,1053 &resource_collection,1054 resource_properties.chain(1055 [1056 Self::rmrk_property(PendingResourceAccept, &pending)?,1057 Self::rmrk_property(PendingResourceRemoval, &false)?,1058 ]1059 .into_iter(),1060 ),1061 )1062 .map_err(|err| match err {1063 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),1064 err => Self::map_unique_err_to_proxy(err),1065 })?;10661067 Ok(resource_id.0)1068 }10691070 fn resource_remove(1071 sender: T::AccountId,1072 collection_id: CollectionId,1073 nft_id: TokenId,1074 resource_id: TokenId,1075 ) -> DispatchResult {1076 let collection =1077 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1078 ensure!(collection.owner == sender, Error::<T>::NoPermission);10791080 let resource_collection_id: CollectionId =1081 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;1082 let resource_collection =1083 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;1084 ensure!(1085 <PalletNft<T>>::token_exists(&resource_collection, resource_id),1086 Error::<T>::ResourceDoesntExist1087 );10881089 let budget = up_data_structs::budget::Value::new(10);1090 let topmost_owner =1091 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;10921093 let sender = T::CrossAccountId::from_sub(sender);1094 if topmost_owner == sender {1095 <PalletNft<T>>::burn(&resource_collection, &sender, resource_id)1096 .map_err(Self::map_unique_err_to_proxy)?;1097 } else {1098 <PalletNft<T>>::set_scoped_token_property(1099 resource_collection_id,1100 resource_id,1101 PropertyScope::Rmrk,1102 Self::rmrk_property(PendingResourceRemoval, &true)?,1103 )?;1104 }11051106 Ok(())1107 }11081109 fn change_collection_owner(1110 collection_id: CollectionId,1111 collection_type: misc::CollectionType,1112 sender: T::AccountId,1113 new_owner: T::AccountId,1114 ) -> DispatchResult {1115 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1116 Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;11171118 let mut collection = collection.into_inner();11191120 collection.owner = new_owner;1121 collection.save()1122 }11231124 fn check_collection_owner(1125 collection: &NonfungibleHandle<T>,1126 account: &T::CrossAccountId,1127 ) -> DispatchResult {1128 collection1129 .check_is_owner(account)1130 .map_err(Self::map_unique_err_to_proxy)1131 }11321133 pub fn last_collection_idx() -> RmrkCollectionId {1134 <CollectionIndex<T>>::get()1135 }11361137 pub fn unique_collection_id(1138 rmrk_collection_id: RmrkCollectionId,1139 ) -> Result<CollectionId, DispatchError> {1140 <UniqueCollectionId<T>>::try_get(rmrk_collection_id)1141 .map_err(|_| <Error<T>>::CollectionUnknown.into())1142 }11431144 pub fn rmrk_collection_id(1145 unique_collection_id: CollectionId,1146 ) -> Result<RmrkCollectionId, DispatchError> {1147 <RmrkInernalCollectionId<T>>::try_get(unique_collection_id)1148 .map_err(|_| <Error<T>>::CollectionUnknown.into())1149 }11501151 pub fn get_nft_collection(1152 collection_id: CollectionId,1153 ) -> Result<NonfungibleHandle<T>, DispatchError> {1154 let collection = <CollectionHandle<T>>::try_get(collection_id)1155 .map_err(|_| <Error<T>>::CollectionUnknown)?;11561157 match collection.mode {1158 CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1159 _ => Err(<Error<T>>::CollectionUnknown.into()),1160 }1161 }11621163 pub fn collection_exists(collection_id: CollectionId) -> bool {1164 <CollectionHandle<T>>::try_get(collection_id).is_ok()1165 }11661167 pub fn get_collection_property(1168 collection_id: CollectionId,1169 key: RmrkProperty,1170 ) -> Result<PropertyValue, DispatchError> {1171 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1172 .get(&Self::rmrk_property_key(key)?)1173 .ok_or(<Error<T>>::CollectionUnknown)?1174 .clone();11751176 Ok(collection_property)1177 }11781179 pub fn get_collection_property_decoded<V: Decode>(1180 collection_id: CollectionId,1181 key: RmrkProperty,1182 ) -> Result<V, DispatchError> {1183 Self::decode_property(Self::get_collection_property(collection_id, key)?)1184 }11851186 pub fn get_collection_type(1187 collection_id: CollectionId,1188 ) -> Result<misc::CollectionType, DispatchError> {1189 Self::get_collection_property_decoded(collection_id, CollectionType)1190 .map_err(|_| <Error<T>>::CorruptedCollectionType.into())1191 }11921193 pub fn ensure_collection_type(1194 collection_id: CollectionId,1195 collection_type: misc::CollectionType,1196 ) -> DispatchResult {1197 let actual_type = Self::get_collection_type(collection_id)?;1198 ensure!(1199 actual_type == collection_type,1200 <CommonError<T>>::NoPermission1201 );12021203 Ok(())1204 }12051206 pub fn get_typed_nft_collection(1207 collection_id: CollectionId,1208 collection_type: misc::CollectionType,1209 ) -> Result<NonfungibleHandle<T>, DispatchError> {1210 Self::ensure_collection_type(collection_id, collection_type)?;12111212 Self::get_nft_collection(collection_id)1213 }12141215 pub fn get_typed_nft_collection_mapped(1216 rmrk_collection_id: RmrkCollectionId,1217 collection_type: misc::CollectionType,1218 ) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1219 let unique_collection_id = match collection_type {1220 misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1221 _ => rmrk_collection_id.into(),1222 };12231224 let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;12251226 Ok((collection, unique_collection_id))1227 }12281229 pub fn get_nft_property(1230 collection_id: CollectionId,1231 nft_id: TokenId,1232 key: RmrkProperty,1233 ) -> Result<PropertyValue, DispatchError> {1234 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1235 .get(&Self::rmrk_property_key(key)?)1236 .ok_or(<Error<T>>::NoAvailableNftId)? // todo replace with better error?1237 .clone();12381239 Ok(nft_property)1240 }12411242 pub fn get_nft_property_decoded<V: Decode>(1243 collection_id: CollectionId,1244 nft_id: TokenId,1245 key: RmrkProperty,1246 ) -> Result<V, DispatchError> {1247 Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)1248 }12491250 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1251 <TokenData<T>>::contains_key((collection_id, nft_id))1252 }12531254 pub fn get_nft_type(1255 collection_id: CollectionId,1256 token_id: TokenId,1257 ) -> Result<NftType, DispatchError> {1258 Self::get_nft_property_decoded(collection_id, token_id, TokenType)1259 .map_err(|_| <Error<T>>::NoAvailableNftId.into())1260 }12611262 pub fn ensure_nft_type(1263 collection_id: CollectionId,1264 token_id: TokenId,1265 nft_type: NftType,1266 ) -> DispatchResult {1267 let actual_type = Self::get_nft_type(collection_id, token_id)?;1268 ensure!(actual_type == nft_type, <Error<T>>::NoPermission);12691270 Ok(())1271 }12721273 pub fn ensure_nft_owner(1274 collection_id: CollectionId,1275 token_id: TokenId,1276 possible_owner: &T::CrossAccountId,1277 nesting_budget: &dyn budget::Budget,1278 ) -> DispatchResult {1279 let is_owned = <PalletStructure<T>>::check_indirectly_owned(1280 possible_owner.clone(),1281 collection_id,1282 token_id,1283 None,1284 nesting_budget,1285 )1286 .map_err(Self::map_unique_err_to_proxy)?;12871288 ensure!(is_owned, <Error<T>>::NoPermission);12891290 Ok(())1291 }12921293 pub fn filter_user_properties<Key, Value, R, Mapper>(1294 collection_id: CollectionId,1295 token_id: Option<TokenId>,1296 filter_keys: Option<Vec<RmrkPropertyKey>>,1297 mapper: Mapper,1298 ) -> Result<Vec<R>, DispatchError>1299 where1300 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1301 Value: Decode + Default,1302 Mapper: Fn(Key, Value) -> R,1303 {1304 filter_keys1305 .map(|keys| {1306 let properties = keys1307 .into_iter()1308 .filter_map(|key| {1309 let key: Key = key.try_into().ok()?;13101311 let value = match token_id {1312 Some(token_id) => Self::get_nft_property_decoded(1313 collection_id,1314 token_id,1315 UserProperty(key.as_ref()),1316 ),1317 None => Self::get_collection_property_decoded(1318 collection_id,1319 UserProperty(key.as_ref()),1320 ),1321 }1322 .ok()?;13231324 Some(mapper(key, value))1325 })1326 .collect();13271328 Ok(properties)1329 })1330 .unwrap_or_else(|| {1331 let properties =1332 Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();13331334 Ok(properties)1335 })1336 }13371338 pub fn iterate_user_properties<Key, Value, R, Mapper>(1339 collection_id: CollectionId,1340 token_id: Option<TokenId>,1341 mapper: Mapper,1342 ) -> Result<impl Iterator<Item = R>, DispatchError>1343 where1344 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1345 Value: Decode + Default,1346 Mapper: Fn(Key, Value) -> R,1347 {1348 let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;13491350 let properties = match token_id {1351 Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1352 None => <PalletCommon<T>>::collection_properties(collection_id),1353 };13541355 let properties = properties.into_iter().filter_map(move |(key, value)| {1356 let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;13571358 let key: Key = key.to_vec().try_into().ok()?;1359 let value: Value = value.decode().ok()?;13601361 Some(mapper(key, value))1362 });13631364 Ok(properties)1365 }13661367 fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1368 map_unique_err_to_proxy! {1369 match err {1370 CommonError::NoPermission => NoPermission,1371 CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1372 CommonError::PublicMintingNotAllowed => NoPermission,1373 CommonError::TokenNotFound => NoAvailableNftId,1374 CommonError::ApprovedValueTooLow => NoPermission,1375 CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1376 StructureError::TokenNotFound => NoAvailableNftId,1377 StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1378 }1379 }1380 }1381}