difftreelog
feat add rmrk proxt extrinsics comments
in: master
2 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::*;3334#[cfg(feature = "runtime-benchmarks")]35pub mod benchmarking;36pub mod misc;37pub mod property;38pub mod weights;3940pub type SelfWeightOf<T> = <T as Config>::WeightInfo;4142use weights::WeightInfo;43use misc::*;44pub use property::*;4546use RmrkProperty::*;4748const NESTING_BUDGET: u32 = 5;4950#[frame_support::pallet]51pub mod pallet {52 use super::*;53 use pallet_evm::account;5455 #[pallet::config]56 pub trait Config:57 frame_system::Config + pallet_common::Config + pallet_nonfungible::Config + account::Config58 {59 type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;60 type WeightInfo: WeightInfo;61 }6263 #[pallet::storage]64 #[pallet::getter(fn collection_index)]65 pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;6667 #[pallet::storage]68 pub type UniqueCollectionId<T: Config> =69 StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;7071 #[pallet::storage]72 pub type RmrkInernalCollectionId<T: Config> =73 StorageMap<_, Twox64Concat, CollectionId, RmrkCollectionId, ValueQuery>;7475 #[pallet::pallet]76 #[pallet::generate_store(pub(super) trait Store)]77 pub struct Pallet<T>(_);7879 #[pallet::event]80 #[pallet::generate_deposit(pub(super) fn deposit_event)]81 pub enum Event<T: Config> {82 CollectionCreated {83 issuer: T::AccountId,84 collection_id: RmrkCollectionId,85 },86 CollectionDestroyed {87 issuer: T::AccountId,88 collection_id: RmrkCollectionId,89 },90 IssuerChanged {91 old_issuer: T::AccountId,92 new_issuer: T::AccountId,93 collection_id: RmrkCollectionId,94 },95 CollectionLocked {96 issuer: T::AccountId,97 collection_id: RmrkCollectionId,98 },99 NftMinted {100 owner: T::AccountId,101 collection_id: RmrkCollectionId,102 nft_id: RmrkNftId,103 },104 NFTBurned {105 owner: T::AccountId,106 nft_id: RmrkNftId,107 },108 NFTSent {109 sender: T::AccountId,110 recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,111 collection_id: RmrkCollectionId,112 nft_id: RmrkNftId,113 approval_required: bool,114 },115 NFTAccepted {116 sender: T::AccountId,117 recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,118 collection_id: RmrkCollectionId,119 nft_id: RmrkNftId,120 },121 NFTRejected {122 sender: T::AccountId,123 collection_id: RmrkCollectionId,124 nft_id: RmrkNftId,125 },126 PropertySet {127 collection_id: RmrkCollectionId,128 maybe_nft_id: Option<RmrkNftId>,129 key: RmrkKeyString,130 value: RmrkValueString,131 },132 ResourceAdded {133 nft_id: RmrkNftId,134 resource_id: RmrkResourceId,135 },136 ResourceRemoval {137 nft_id: RmrkNftId,138 resource_id: RmrkResourceId,139 },140 ResourceAccepted {141 nft_id: RmrkNftId,142 resource_id: RmrkResourceId,143 },144 ResourceRemovalAccepted {145 nft_id: RmrkNftId,146 resource_id: RmrkResourceId,147 },148 PrioritySet {149 collection_id: RmrkCollectionId,150 nft_id: RmrkNftId,151 },152 }153154 #[pallet::error]155 pub enum Error<T> {156 /* Unique-specific events */157 CorruptedCollectionType,158 NftTypeEncodeError,159 RmrkPropertyKeyIsTooLong,160 RmrkPropertyValueIsTooLong,161162 /* RMRK compatible events */163 CollectionNotEmpty,164 NoAvailableCollectionId,165 NoAvailableNftId,166 CollectionUnknown,167 NoPermission,168 NonTransferable,169 CollectionFullOrLocked,170 ResourceDoesntExist,171 CannotSendToDescendentOrSelf,172 CannotAcceptNonOwnedNft,173 CannotRejectNonOwnedNft,174 ResourceNotPending,175 }176177 #[pallet::call]178 impl<T: Config> Pallet<T> {179 #[pallet::weight(<SelfWeightOf<T>>::create_collection())]180 #[transactional]181 pub fn create_collection(182 origin: OriginFor<T>,183 metadata: RmrkString,184 max: Option<u32>,185 symbol: RmrkCollectionSymbol,186 ) -> DispatchResult {187 let sender = ensure_signed(origin)?;188189 let limits = CollectionLimits {190 owner_can_transfer: Some(false),191 token_limit: max,192 ..Default::default()193 };194195 let data = CreateCollectionData {196 limits: Some(limits),197 token_prefix: symbol198 .into_inner()199 .try_into()200 .map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,201 permissions: Some(CollectionPermissions {202 nesting: Some(NestingRule::Owner),203 ..Default::default()204 }),205 ..Default::default()206 };207208 let unique_collection_id = Self::init_collection(209 T::CrossAccountId::from_sub(sender.clone()),210 data,211 [212 Self::rmrk_property(Metadata, &metadata)?,213 Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,214 ]215 .into_iter(),216 )?;217 let rmrk_collection_id = <CollectionIndex<T>>::get();218219 <UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);220 <RmrkInernalCollectionId<T>>::insert(unique_collection_id, rmrk_collection_id);221222 <CollectionIndex<T>>::mutate(|n| *n += 1);223224 Self::deposit_event(Event::CollectionCreated {225 issuer: sender,226 collection_id: rmrk_collection_id,227 });228229 Ok(())230 }231232 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]233 #[transactional]234 pub fn destroy_collection(235 origin: OriginFor<T>,236 collection_id: RmrkCollectionId,237 ) -> DispatchResult {238 let sender = ensure_signed(origin)?;239 let cross_sender = T::CrossAccountId::from_sub(sender.clone());240241 let collection = Self::get_typed_nft_collection(242 Self::unique_collection_id(collection_id)?,243 misc::CollectionType::Regular,244 )?;245 collection.check_is_external()?;246247 <PalletNft<T>>::destroy_collection(collection, &cross_sender)248 .map_err(Self::map_unique_err_to_proxy)?;249250 Self::deposit_event(Event::CollectionDestroyed {251 issuer: sender,252 collection_id,253 });254255 Ok(())256 }257258 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]259 #[transactional]260 pub fn change_collection_issuer(261 origin: OriginFor<T>,262 collection_id: RmrkCollectionId,263 new_issuer: <T::Lookup as StaticLookup>::Source,264 ) -> DispatchResult {265 let sender = ensure_signed(origin)?;266267 let collection = Self::get_nft_collection(Self::unique_collection_id(collection_id)?)?;268 collection.check_is_external()?;269270 let new_issuer = T::Lookup::lookup(new_issuer)?;271272 Self::change_collection_owner(273 Self::unique_collection_id(collection_id)?,274 misc::CollectionType::Regular,275 sender.clone(),276 new_issuer.clone(),277 )?;278279 Self::deposit_event(Event::IssuerChanged {280 old_issuer: sender,281 new_issuer,282 collection_id,283 });284285 Ok(())286 }287288 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]289 #[transactional]290 pub fn lock_collection(291 origin: OriginFor<T>,292 collection_id: RmrkCollectionId,293 ) -> DispatchResult {294 let sender = ensure_signed(origin)?;295 let cross_sender = T::CrossAccountId::from_sub(sender.clone());296297 let collection = Self::get_typed_nft_collection(298 Self::unique_collection_id(collection_id)?,299 misc::CollectionType::Regular,300 )?;301 collection.check_is_external()?;302303 Self::check_collection_owner(&collection, &cross_sender)?;304305 let token_count = collection.total_supply();306307 let mut collection = collection.into_inner();308 collection.limits.token_limit = Some(token_count);309 collection.save()?;310311 Self::deposit_event(Event::CollectionLocked {312 issuer: sender,313 collection_id,314 });315316 Ok(())317 }318319 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]320 #[transactional]321 pub fn mint_nft(322 origin: OriginFor<T>,323 owner: T::AccountId,324 collection_id: RmrkCollectionId,325 recipient: Option<T::AccountId>,326 royalty_amount: Option<Permill>,327 metadata: RmrkString,328 transferable: bool,329 ) -> DispatchResult {330 let sender = ensure_signed(origin)?;331 let sender = T::CrossAccountId::from_sub(sender);332 let cross_owner = T::CrossAccountId::from_sub(owner.clone());333334 let collection = Self::get_typed_nft_collection(335 Self::unique_collection_id(collection_id)?,336 misc::CollectionType::Regular,337 )?;338 collection.check_is_external()?;339340 let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {341 recipient: recipient.unwrap_or_else(|| owner.clone()),342 amount,343 });344345 let nft_id = Self::create_nft(346 &sender,347 &cross_owner,348 &collection,349 [350 Self::rmrk_property(TokenType, &NftType::Regular)?,351 Self::rmrk_property(Transferable, &transferable)?,352 Self::rmrk_property(PendingNftAccept, &false)?,353 Self::rmrk_property(RoyaltyInfo, &royalty_info)?,354 Self::rmrk_property(Metadata, &metadata)?,355 Self::rmrk_property(Equipped, &false)?,356 Self::rmrk_property(357 ResourceCollection,358 &Self::init_collection(359 sender.clone(),360 CreateCollectionData {361 ..Default::default()362 },363 [Self::rmrk_property(364 CollectionType,365 &misc::CollectionType::Resource,366 )?]367 .into_iter(),368 )?,369 )?, // todo possibly add limits to the collection if rmrk warrants them370 Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,371 ]372 .into_iter(),373 )374 .map_err(|err| match err {375 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),376 err => Self::map_unique_err_to_proxy(err),377 })?;378379 Self::deposit_event(Event::NftMinted {380 owner,381 collection_id,382 nft_id: nft_id.0,383 });384385 Ok(())386 }387388 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]389 #[transactional]390 pub fn burn_nft(391 origin: OriginFor<T>,392 collection_id: RmrkCollectionId,393 nft_id: RmrkNftId,394 ) -> DispatchResult {395 let sender = ensure_signed(origin)?;396 let cross_sender = T::CrossAccountId::from_sub(sender.clone());397398 let collection = Self::get_typed_nft_collection(399 Self::unique_collection_id(collection_id)?,400 misc::CollectionType::Regular,401 )?;402 collection.check_is_external()?;403404 Self::destroy_nft(405 cross_sender,406 Self::unique_collection_id(collection_id)?,407 nft_id.into(),408 )409 .map_err(Self::map_unique_err_to_proxy)?;410411 Self::deposit_event(Event::NFTBurned {412 owner: sender,413 nft_id,414 });415416 Ok(())417 }418419 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]420 #[transactional]421 pub fn send(422 origin: OriginFor<T>,423 rmrk_collection_id: RmrkCollectionId,424 rmrk_nft_id: RmrkNftId,425 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,426 ) -> DispatchResult {427 let sender = ensure_signed(origin.clone())?;428 let cross_sender = T::CrossAccountId::from_sub(sender.clone());429430 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;431 let nft_id = rmrk_nft_id.into();432433 let collection =434 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;435 collection.check_is_external()?;436437 let token_data =438 <TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;439440 let from = token_data.owner;441442 ensure!(443 Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,444 <Error<T>>::NonTransferable445 );446447 ensure!(448 !Self::get_nft_property_decoded(449 collection_id,450 nft_id,451 RmrkProperty::PendingNftAccept452 )?,453 <Error<T>>::NoPermission454 );455456 let target_owner;457 let approval_required;458459 match new_owner {460 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {461 target_owner = T::CrossAccountId::from_sub(account_id.clone());462 approval_required = false;463 }464 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(465 target_collection_id,466 target_nft_id,467 ) => {468 let target_collection_id = Self::unique_collection_id(target_collection_id)?;469470 let target_nft_budget = budget::Value::new(NESTING_BUDGET);471472 let target_nft_owner = <PalletStructure<T>>::get_checked_topmost_owner(473 target_collection_id,474 target_nft_id.into(),475 Some((collection_id, nft_id)),476 &target_nft_budget,477 )478 .map_err(Self::map_unique_err_to_proxy)?;479480 approval_required = cross_sender != target_nft_owner;481482 if approval_required {483 target_owner = target_nft_owner;484485 <PalletNft<T>>::set_scoped_token_property(486 collection.id,487 nft_id,488 PropertyScope::Rmrk,489 Self::rmrk_property(PendingNftAccept, &approval_required)?,490 )?;491 } else {492 target_owner = T::CrossTokenAddressMapping::token_to_address(493 target_collection_id,494 target_nft_id.into(),495 );496 }497 }498 }499500 let src_nft_budget = budget::Value::new(NESTING_BUDGET);501502 <PalletNft<T>>::transfer_from(503 &collection,504 &cross_sender,505 &from,506 &target_owner,507 nft_id,508 &src_nft_budget,509 )510 .map_err(Self::map_unique_err_to_proxy)?;511512 Self::deposit_event(Event::NFTSent {513 sender,514 recipient: new_owner,515 collection_id: rmrk_collection_id,516 nft_id: rmrk_nft_id,517 approval_required,518 });519520 Ok(())521 }522523 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]524 #[transactional]525 pub fn accept_nft(526 origin: OriginFor<T>,527 rmrk_collection_id: RmrkCollectionId,528 rmrk_nft_id: RmrkNftId,529 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,530 ) -> DispatchResult {531 let sender = ensure_signed(origin.clone())?;532 let cross_sender = T::CrossAccountId::from_sub(sender.clone());533534 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;535 let nft_id = rmrk_nft_id.into();536537 let collection =538 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;539 collection.check_is_external()?;540541 let new_cross_owner = match new_owner {542 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {543 T::CrossAccountId::from_sub(account_id.clone())544 }545 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(546 target_collection_id,547 target_nft_id,548 ) => {549 let target_collection_id = Self::unique_collection_id(target_collection_id)?;550551 T::CrossTokenAddressMapping::token_to_address(552 target_collection_id,553 TokenId(target_nft_id),554 )555 }556 };557558 let budget = budget::Value::new(NESTING_BUDGET);559560 <PalletNft<T>>::transfer(561 &collection,562 &cross_sender,563 &new_cross_owner,564 nft_id,565 &budget,566 )567 .map_err(|err| {568 if err == <CommonError<T>>::OnlyOwnerAllowedToNest.into() {569 <Error<T>>::CannotAcceptNonOwnedNft.into()570 } else {571 Self::map_unique_err_to_proxy(err)572 }573 })?;574575 <PalletNft<T>>::set_scoped_token_property(576 collection.id,577 nft_id,578 PropertyScope::Rmrk,579 Self::rmrk_property(PendingNftAccept, &false)?,580 )?;581582 Self::deposit_event(Event::NFTAccepted {583 sender,584 recipient: new_owner,585 collection_id: rmrk_collection_id,586 nft_id: rmrk_nft_id,587 });588589 Ok(())590 }591592 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]593 #[transactional]594 pub fn reject_nft(595 origin: OriginFor<T>,596 rmrk_collection_id: RmrkCollectionId,597 rmrk_nft_id: RmrkNftId,598 ) -> DispatchResult {599 let sender = ensure_signed(origin)?;600 let cross_sender = T::CrossAccountId::from_sub(sender.clone());601602 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;603 let nft_id = rmrk_nft_id.into();604605 let collection =606 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;607 collection.check_is_external()?;608609 Self::destroy_nft(cross_sender, collection_id, nft_id).map_err(|err| {610 if err == <CommonError<T>>::NoPermission.into()611 || err == <CommonError<T>>::ApprovedValueTooLow.into()612 {613 <Error<T>>::CannotRejectNonOwnedNft.into()614 } else {615 Self::map_unique_err_to_proxy(err)616 }617 })?;618619 Self::deposit_event(Event::NFTRejected {620 sender,621 collection_id: rmrk_collection_id,622 nft_id: rmrk_nft_id,623 });624625 Ok(())626 }627628 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]629 #[transactional]630 pub fn accept_resource(631 origin: OriginFor<T>,632 rmrk_collection_id: RmrkCollectionId,633 rmrk_nft_id: RmrkNftId,634 rmrk_resource_id: RmrkResourceId,635 ) -> DispatchResult {636 let sender = ensure_signed(origin)?;637 let cross_sender = T::CrossAccountId::from_sub(sender);638639 let collection_id = Self::unique_collection_id(rmrk_collection_id)640 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;641 let collection =642 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;643 collection.check_is_external()?;644645 let nft_id = rmrk_nft_id.into();646 let resource_id = rmrk_resource_id.into();647648 let budget = budget::Value::new(NESTING_BUDGET);649650 let nft_owner =651 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)652 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;653654 let resource_collection_id: CollectionId =655 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)656 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;657658 let is_pending: bool = Self::get_nft_property_decoded(659 resource_collection_id,660 resource_id,661 PendingResourceAccept,662 )663 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;664665 ensure!(is_pending, <Error<T>>::ResourceNotPending);666667 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);668669 <PalletNft<T>>::set_scoped_token_property(670 resource_collection_id,671 rmrk_resource_id.into(),672 PropertyScope::Rmrk,673 Self::rmrk_property(PendingResourceAccept, &false)?,674 )?;675676 Self::deposit_event(Event::<T>::ResourceAccepted {677 nft_id: rmrk_nft_id,678 resource_id: rmrk_resource_id,679 });680681 Ok(())682 }683684 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]685 #[transactional]686 pub fn accept_resource_removal(687 origin: OriginFor<T>,688 rmrk_collection_id: RmrkCollectionId,689 rmrk_nft_id: RmrkNftId,690 rmrk_resource_id: RmrkResourceId,691 ) -> DispatchResult {692 let sender = ensure_signed(origin)?;693 let cross_sender = T::CrossAccountId::from_sub(sender);694695 let collection_id = Self::unique_collection_id(rmrk_collection_id)696 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;697 let collection =698 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;699 collection.check_is_external()?;700701 let nft_id = rmrk_nft_id.into();702 let resource_id = rmrk_resource_id.into();703704 let budget = budget::Value::new(NESTING_BUDGET);705706 let nft_owner =707 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)708 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;709710 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);711712 let resource_collection_id: CollectionId =713 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)714 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;715716 let is_pending: bool = Self::get_nft_property_decoded(717 resource_collection_id,718 resource_id,719 PendingResourceRemoval,720 )721 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;722723 ensure!(is_pending, <Error<T>>::ResourceNotPending);724725 let resource_collection = Self::get_typed_nft_collection(726 resource_collection_id,727 misc::CollectionType::Resource,728 )?;729730 <PalletNft<T>>::burn(&resource_collection, &cross_sender, rmrk_resource_id.into())731 .map_err(Self::map_unique_err_to_proxy)?;732733 Self::deposit_event(Event::<T>::ResourceRemovalAccepted {734 nft_id: rmrk_nft_id,735 resource_id: rmrk_resource_id,736 });737738 Ok(())739 }740741 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]742 #[transactional]743 pub fn set_property(744 origin: OriginFor<T>,745 #[pallet::compact] rmrk_collection_id: RmrkCollectionId,746 maybe_nft_id: Option<RmrkNftId>,747 key: RmrkKeyString,748 value: RmrkValueString,749 ) -> DispatchResult {750 let sender = ensure_signed(origin)?;751 let sender = T::CrossAccountId::from_sub(sender);752753 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;754 let collection =755 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;756 collection.check_is_external()?;757758 let budget = budget::Value::new(NESTING_BUDGET);759760 match maybe_nft_id {761 Some(nft_id) => {762 let token_id: TokenId = nft_id.into();763764 Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;765 Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;766767 <PalletNft<T>>::set_scoped_token_property(768 collection_id,769 token_id,770 PropertyScope::Rmrk,771 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,772 )?;773 }774 None => {775 let collection = Self::get_typed_nft_collection(776 collection_id,777 misc::CollectionType::Regular,778 )?;779780 Self::check_collection_owner(&collection, &sender)?;781782 <PalletCommon<T>>::set_scoped_collection_property(783 collection_id,784 PropertyScope::Rmrk,785 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,786 )?;787 }788 }789790 Self::deposit_event(Event::PropertySet {791 collection_id: rmrk_collection_id,792 maybe_nft_id,793 key,794 value,795 });796797 Ok(())798 }799800 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]801 #[transactional]802 pub fn set_priority(803 origin: OriginFor<T>,804 rmrk_collection_id: RmrkCollectionId,805 rmrk_nft_id: RmrkNftId,806 priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,807 ) -> DispatchResult {808 let sender = ensure_signed(origin)?;809 let sender = T::CrossAccountId::from_sub(sender);810811 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;812 let nft_id = rmrk_nft_id.into();813814 let collection =815 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;816 collection.check_is_external()?;817818 let budget = budget::Value::new(NESTING_BUDGET);819820 Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;821 Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;822823 <PalletNft<T>>::set_scoped_token_property(824 collection_id,825 nft_id,826 PropertyScope::Rmrk,827 Self::rmrk_property(ResourcePriorities, &priorities.into_inner())?,828 )?;829830 Self::deposit_event(Event::<T>::PrioritySet {831 collection_id: rmrk_collection_id,832 nft_id: rmrk_nft_id,833 });834835 Ok(())836 }837838 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]839 #[transactional]840 pub fn add_basic_resource(841 origin: OriginFor<T>,842 rmrk_collection_id: RmrkCollectionId,843 nft_id: RmrkNftId,844 resource: RmrkBasicResource,845 ) -> DispatchResult {846 let sender = ensure_signed(origin.clone())?;847848 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;849 let collection =850 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;851 collection.check_is_external()?;852853 let resource_id = Self::resource_add(854 sender,855 collection_id,856 nft_id.into(),857 [858 Self::rmrk_property(TokenType, &NftType::Resource)?,859 Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,860 Self::rmrk_property(Src, &resource.src)?,861 Self::rmrk_property(Metadata, &resource.metadata)?,862 Self::rmrk_property(License, &resource.license)?,863 Self::rmrk_property(Thumb, &resource.thumb)?,864 ]865 .into_iter(),866 )?;867868 Self::deposit_event(Event::ResourceAdded {869 nft_id,870 resource_id,871 });872 Ok(())873 }874875 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]876 #[transactional]877 pub fn add_composable_resource(878 origin: OriginFor<T>,879 rmrk_collection_id: RmrkCollectionId,880 nft_id: RmrkNftId,881 _resource_id: RmrkBoundedResource,882 resource: RmrkComposableResource,883 ) -> DispatchResult {884 let sender = ensure_signed(origin.clone())?;885886 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;887 let collection =888 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;889 collection.check_is_external()?;890891 let resource_id = Self::resource_add(892 sender,893 collection_id,894 nft_id.into(),895 [896 Self::rmrk_property(TokenType, &NftType::Resource)?,897 Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,898 Self::rmrk_property(Parts, &resource.parts)?,899 Self::rmrk_property(Base, &resource.base)?,900 Self::rmrk_property(Src, &resource.src)?,901 Self::rmrk_property(Metadata, &resource.metadata)?,902 Self::rmrk_property(License, &resource.license)?,903 Self::rmrk_property(Thumb, &resource.thumb)?,904 ]905 .into_iter(),906 )?;907908 Self::deposit_event(Event::ResourceAdded {909 nft_id,910 resource_id,911 });912 Ok(())913 }914915 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]916 #[transactional]917 pub fn add_slot_resource(918 origin: OriginFor<T>,919 rmrk_collection_id: RmrkCollectionId,920 nft_id: RmrkNftId,921 resource: RmrkSlotResource,922 ) -> DispatchResult {923 let sender = ensure_signed(origin.clone())?;924925 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;926 let collection =927 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;928 collection.check_is_external()?;929930 let resource_id = Self::resource_add(931 sender,932 collection_id,933 nft_id.into(),934 [935 Self::rmrk_property(TokenType, &NftType::Resource)?,936 Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,937 Self::rmrk_property(Base, &resource.base)?,938 Self::rmrk_property(Src, &resource.src)?,939 Self::rmrk_property(Metadata, &resource.metadata)?,940 Self::rmrk_property(Slot, &resource.slot)?,941 Self::rmrk_property(License, &resource.license)?,942 Self::rmrk_property(Thumb, &resource.thumb)?,943 ]944 .into_iter(),945 )?;946947 Self::deposit_event(Event::ResourceAdded {948 nft_id,949 resource_id,950 });951 Ok(())952 }953954 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]955 #[transactional]956 pub fn remove_resource(957 origin: OriginFor<T>,958 rmrk_collection_id: RmrkCollectionId,959 nft_id: RmrkNftId,960 resource_id: RmrkResourceId,961 ) -> DispatchResult {962 let sender = ensure_signed(origin.clone())?;963964 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;965 let collection =966 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;967 collection.check_is_external()?;968969 Self::resource_remove(sender, collection_id, nft_id.into(), resource_id.into())?;970971 Self::deposit_event(Event::ResourceRemoval {972 nft_id,973 resource_id,974 });975 Ok(())976 }977 }978}979980impl<T: Config> Pallet<T> {981 pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {982 let key = rmrk_key.to_key::<T>()?;983984 let scoped_key = PropertyScope::Rmrk985 .apply(key)986 .map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;987988 Ok(scoped_key)989 }990991 // todo think about renaming these992 pub fn rmrk_property<E: Encode>(993 rmrk_key: RmrkProperty,994 value: &E,995 ) -> Result<Property, DispatchError> {996 let key = rmrk_key.to_key::<T>()?;997998 let value = value999 .encode()1000 .try_into()1001 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;10021003 let property = Property { key, value };10041005 Ok(property)1006 }10071008 pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {1009 vec.decode()1010 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1011 }10121013 pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>1014 where1015 BoundedVec<u8, S>: TryFrom<Vec<u8>>,1016 {1017 vec.rebind()1018 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1019 }10201021 fn init_collection(1022 sender: T::CrossAccountId,1023 data: CreateCollectionData<T::AccountId>,1024 properties: impl Iterator<Item = Property>,1025 ) -> Result<CollectionId, DispatchError> {1026 let collection_id = <PalletNft<T>>::init_collection(sender, data, true);10271028 if let Err(DispatchError::Arithmetic(_)) = &collection_id {1029 return Err(<Error<T>>::NoAvailableCollectionId.into());1030 }10311032 <PalletCommon<T>>::set_scoped_collection_properties(1033 collection_id?,1034 PropertyScope::Rmrk,1035 properties,1036 )?;10371038 collection_id1039 }10401041 pub fn create_nft(1042 sender: &T::CrossAccountId,1043 owner: &T::CrossAccountId,1044 collection: &NonfungibleHandle<T>,1045 properties: impl Iterator<Item = Property>,1046 ) -> Result<TokenId, DispatchError> {1047 let data = CreateNftExData {1048 properties: BoundedVec::default(),1049 owner: owner.clone(),1050 };10511052 let budget = budget::Value::new(NESTING_BUDGET);10531054 <PalletNft<T>>::create_item(collection, sender, data, &budget)?;10551056 let nft_id = <PalletNft<T>>::current_token_id(collection.id);10571058 <PalletNft<T>>::set_scoped_token_properties(1059 collection.id,1060 nft_id,1061 PropertyScope::Rmrk,1062 properties,1063 )?;10641065 Ok(nft_id)1066 }10671068 fn destroy_nft(1069 sender: T::CrossAccountId,1070 collection_id: CollectionId,1071 token_id: TokenId,1072 ) -> DispatchResult {1073 let collection =1074 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;10751076 let token_data =1077 <TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;10781079 let from = token_data.owner;10801081 let budget = budget::Value::new(NESTING_BUDGET);10821083 <PalletNft<T>>::burn_from(&collection, &sender, &from, token_id, &budget)1084 }10851086 fn resource_add(1087 sender: T::AccountId,1088 collection_id: CollectionId,1089 token_id: TokenId,1090 resource_properties: impl Iterator<Item = Property>,1091 ) -> Result<RmrkResourceId, DispatchError> {1092 let collection =1093 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1094 ensure!(collection.owner == sender, Error::<T>::NoPermission);10951096 let sender = T::CrossAccountId::from_sub(sender);1097 let budget = budget::Value::new(NESTING_BUDGET);10981099 let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, token_id, &budget)1100 .map_err(Self::map_unique_err_to_proxy)?;11011102 let pending = sender != nft_owner;11031104 let resource_collection_id: CollectionId =1105 Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;1106 let resource_collection =1107 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;11081109 // todo probably add extra connections to bases, slots, etc., when RMRK starts to use them11101111 let resource_id = Self::create_nft(1112 &sender,1113 &nft_owner,1114 &resource_collection,1115 resource_properties.chain(1116 [1117 Self::rmrk_property(PendingResourceAccept, &pending)?,1118 Self::rmrk_property(PendingResourceRemoval, &false)?,1119 ]1120 .into_iter(),1121 ),1122 )1123 .map_err(|err| match err {1124 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),1125 err => Self::map_unique_err_to_proxy(err),1126 })?;11271128 Ok(resource_id.0)1129 }11301131 fn resource_remove(1132 sender: T::AccountId,1133 collection_id: CollectionId,1134 nft_id: TokenId,1135 resource_id: TokenId,1136 ) -> DispatchResult {1137 let collection =1138 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1139 ensure!(collection.owner == sender, Error::<T>::NoPermission);11401141 let resource_collection_id: CollectionId =1142 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;1143 let resource_collection =1144 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;1145 ensure!(1146 <PalletNft<T>>::token_exists(&resource_collection, resource_id),1147 Error::<T>::ResourceDoesntExist1148 );11491150 let budget = up_data_structs::budget::Value::new(10);1151 let topmost_owner =1152 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;11531154 let sender = T::CrossAccountId::from_sub(sender);1155 if topmost_owner == sender {1156 <PalletNft<T>>::burn(&resource_collection, &sender, resource_id)1157 .map_err(Self::map_unique_err_to_proxy)?;1158 } else {1159 <PalletNft<T>>::set_scoped_token_property(1160 resource_collection_id,1161 resource_id,1162 PropertyScope::Rmrk,1163 Self::rmrk_property(PendingResourceRemoval, &true)?,1164 )?;1165 }11661167 Ok(())1168 }11691170 fn change_collection_owner(1171 collection_id: CollectionId,1172 collection_type: misc::CollectionType,1173 sender: T::AccountId,1174 new_owner: T::AccountId,1175 ) -> DispatchResult {1176 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1177 Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;11781179 let mut collection = collection.into_inner();11801181 collection.owner = new_owner;1182 collection.save()1183 }11841185 fn check_collection_owner(1186 collection: &NonfungibleHandle<T>,1187 account: &T::CrossAccountId,1188 ) -> DispatchResult {1189 collection1190 .check_is_owner(account)1191 .map_err(Self::map_unique_err_to_proxy)1192 }11931194 pub fn last_collection_idx() -> RmrkCollectionId {1195 <CollectionIndex<T>>::get()1196 }11971198 pub fn unique_collection_id(1199 rmrk_collection_id: RmrkCollectionId,1200 ) -> Result<CollectionId, DispatchError> {1201 <UniqueCollectionId<T>>::try_get(rmrk_collection_id)1202 .map_err(|_| <Error<T>>::CollectionUnknown.into())1203 }12041205 pub fn rmrk_collection_id(1206 unique_collection_id: CollectionId,1207 ) -> Result<RmrkCollectionId, DispatchError> {1208 <RmrkInernalCollectionId<T>>::try_get(unique_collection_id)1209 .map_err(|_| <Error<T>>::CollectionUnknown.into())1210 }12111212 pub fn get_nft_collection(1213 collection_id: CollectionId,1214 ) -> Result<NonfungibleHandle<T>, DispatchError> {1215 let collection = <CollectionHandle<T>>::try_get(collection_id)1216 .map_err(|_| <Error<T>>::CollectionUnknown)?;12171218 match collection.mode {1219 CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1220 _ => Err(<Error<T>>::CollectionUnknown.into()),1221 }1222 }12231224 pub fn collection_exists(collection_id: CollectionId) -> bool {1225 <CollectionHandle<T>>::try_get(collection_id).is_ok()1226 }12271228 pub fn get_collection_property(1229 collection_id: CollectionId,1230 key: RmrkProperty,1231 ) -> Result<PropertyValue, DispatchError> {1232 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1233 .get(&Self::rmrk_property_key(key)?)1234 .ok_or(<Error<T>>::CollectionUnknown)?1235 .clone();12361237 Ok(collection_property)1238 }12391240 pub fn get_collection_property_decoded<V: Decode>(1241 collection_id: CollectionId,1242 key: RmrkProperty,1243 ) -> Result<V, DispatchError> {1244 Self::decode_property(Self::get_collection_property(collection_id, key)?)1245 }12461247 pub fn get_collection_type(1248 collection_id: CollectionId,1249 ) -> Result<misc::CollectionType, DispatchError> {1250 Self::get_collection_property_decoded(collection_id, CollectionType)1251 .map_err(|_| <Error<T>>::CorruptedCollectionType.into())1252 }12531254 pub fn ensure_collection_type(1255 collection_id: CollectionId,1256 collection_type: misc::CollectionType,1257 ) -> DispatchResult {1258 let actual_type = Self::get_collection_type(collection_id)?;1259 ensure!(1260 actual_type == collection_type,1261 <CommonError<T>>::NoPermission1262 );12631264 Ok(())1265 }12661267 pub fn get_typed_nft_collection(1268 collection_id: CollectionId,1269 collection_type: misc::CollectionType,1270 ) -> Result<NonfungibleHandle<T>, DispatchError> {1271 Self::ensure_collection_type(collection_id, collection_type)?;12721273 Self::get_nft_collection(collection_id)1274 }12751276 pub fn get_typed_nft_collection_mapped(1277 rmrk_collection_id: RmrkCollectionId,1278 collection_type: misc::CollectionType,1279 ) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1280 let unique_collection_id = match collection_type {1281 misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1282 _ => rmrk_collection_id.into(),1283 };12841285 let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;12861287 Ok((collection, unique_collection_id))1288 }12891290 pub fn get_nft_property(1291 collection_id: CollectionId,1292 nft_id: TokenId,1293 key: RmrkProperty,1294 ) -> Result<PropertyValue, DispatchError> {1295 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1296 .get(&Self::rmrk_property_key(key)?)1297 .ok_or(<Error<T>>::NoAvailableNftId)? // todo replace with better error?1298 .clone();12991300 Ok(nft_property)1301 }13021303 pub fn get_nft_property_decoded<V: Decode>(1304 collection_id: CollectionId,1305 nft_id: TokenId,1306 key: RmrkProperty,1307 ) -> Result<V, DispatchError> {1308 Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)1309 }13101311 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1312 <TokenData<T>>::contains_key((collection_id, nft_id))1313 }13141315 pub fn get_nft_type(1316 collection_id: CollectionId,1317 token_id: TokenId,1318 ) -> Result<NftType, DispatchError> {1319 Self::get_nft_property_decoded(collection_id, token_id, TokenType)1320 .map_err(|_| <Error<T>>::NoAvailableNftId.into())1321 }13221323 pub fn ensure_nft_type(1324 collection_id: CollectionId,1325 token_id: TokenId,1326 nft_type: NftType,1327 ) -> DispatchResult {1328 let actual_type = Self::get_nft_type(collection_id, token_id)?;1329 ensure!(actual_type == nft_type, <Error<T>>::NoPermission);13301331 Ok(())1332 }13331334 pub fn ensure_nft_owner(1335 collection_id: CollectionId,1336 token_id: TokenId,1337 possible_owner: &T::CrossAccountId,1338 nesting_budget: &dyn budget::Budget,1339 ) -> DispatchResult {1340 let is_owned = <PalletStructure<T>>::check_indirectly_owned(1341 possible_owner.clone(),1342 collection_id,1343 token_id,1344 None,1345 nesting_budget,1346 )1347 .map_err(Self::map_unique_err_to_proxy)?;13481349 ensure!(is_owned, <Error<T>>::NoPermission);13501351 Ok(())1352 }13531354 pub fn filter_user_properties<Key, Value, R, Mapper>(1355 collection_id: CollectionId,1356 token_id: Option<TokenId>,1357 filter_keys: Option<Vec<RmrkPropertyKey>>,1358 mapper: Mapper,1359 ) -> Result<Vec<R>, DispatchError>1360 where1361 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1362 Value: Decode + Default,1363 Mapper: Fn(Key, Value) -> R,1364 {1365 filter_keys1366 .map(|keys| {1367 let properties = keys1368 .into_iter()1369 .filter_map(|key| {1370 let key: Key = key.try_into().ok()?;13711372 let value = match token_id {1373 Some(token_id) => Self::get_nft_property_decoded(1374 collection_id,1375 token_id,1376 UserProperty(key.as_ref()),1377 ),1378 None => Self::get_collection_property_decoded(1379 collection_id,1380 UserProperty(key.as_ref()),1381 ),1382 }1383 .ok()?;13841385 Some(mapper(key, value))1386 })1387 .collect();13881389 Ok(properties)1390 })1391 .unwrap_or_else(|| {1392 let properties =1393 Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();13941395 Ok(properties)1396 })1397 }13981399 pub fn iterate_user_properties<Key, Value, R, Mapper>(1400 collection_id: CollectionId,1401 token_id: Option<TokenId>,1402 mapper: Mapper,1403 ) -> Result<impl Iterator<Item = R>, DispatchError>1404 where1405 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1406 Value: Decode + Default,1407 Mapper: Fn(Key, Value) -> R,1408 {1409 let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;14101411 let properties = match token_id {1412 Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1413 None => <PalletCommon<T>>::collection_properties(collection_id),1414 };14151416 let properties = properties.into_iter().filter_map(move |(key, value)| {1417 let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;14181419 let key: Key = key.to_vec().try_into().ok()?;1420 let value: Value = value.decode().ok()?;14211422 Some(mapper(key, value))1423 });14241425 Ok(properties)1426 }14271428 fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1429 map_unique_err_to_proxy! {1430 match err {1431 CommonError::NoPermission => NoPermission,1432 CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1433 CommonError::PublicMintingNotAllowed => NoPermission,1434 CommonError::TokenNotFound => NoAvailableNftId,1435 CommonError::ApprovedValueTooLow => NoPermission,1436 CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1437 StructureError::TokenNotFound => NoAvailableNftId,1438 StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1439 }1440 }1441 }1442}1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![cfg_attr(not(feature = "std"), no_std)]1819use frame_support::{pallet_prelude::*, transactional, BoundedVec, dispatch::DispatchResult};20use frame_system::{pallet_prelude::*, ensure_signed};21use sp_runtime::{DispatchError, Permill, traits::StaticLookup};22use sp_std::vec::Vec;23use up_data_structs::{*, mapping::TokenAddressMapping};24use pallet_common::{25 Pallet as PalletCommon, Error as CommonError, CollectionHandle, CommonCollectionOperations,26};27use pallet_nonfungible::{Pallet as PalletNft, NonfungibleHandle, TokenData};28use pallet_structure::{Pallet as PalletStructure, Error as StructureError};29use pallet_evm::account::CrossAccountId;30use core::convert::AsRef;3132pub use pallet::*;3334#[cfg(feature = "runtime-benchmarks")]35pub mod benchmarking;36pub mod misc;37pub mod property;38pub mod weights;3940pub type SelfWeightOf<T> = <T as Config>::WeightInfo;4142use weights::WeightInfo;43use misc::*;44pub use property::*;4546use RmrkProperty::*;4748const NESTING_BUDGET: u32 = 5;4950#[frame_support::pallet]51pub mod pallet {52 use super::*;53 use pallet_evm::account;5455 #[pallet::config]56 pub trait Config:57 frame_system::Config + pallet_common::Config + pallet_nonfungible::Config + account::Config58 {59 type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;60 type WeightInfo: WeightInfo;61 }6263 #[pallet::storage]64 #[pallet::getter(fn collection_index)]65 pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;6667 #[pallet::storage]68 pub type UniqueCollectionId<T: Config> =69 StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;7071 #[pallet::storage]72 pub type RmrkInernalCollectionId<T: Config> =73 StorageMap<_, Twox64Concat, CollectionId, RmrkCollectionId, ValueQuery>;7475 #[pallet::pallet]76 #[pallet::generate_store(pub(super) trait Store)]77 pub struct Pallet<T>(_);7879 #[pallet::event]80 #[pallet::generate_deposit(pub(super) fn deposit_event)]81 pub enum Event<T: Config> {82 CollectionCreated {83 issuer: T::AccountId,84 collection_id: RmrkCollectionId,85 },86 CollectionDestroyed {87 issuer: T::AccountId,88 collection_id: RmrkCollectionId,89 },90 IssuerChanged {91 old_issuer: T::AccountId,92 new_issuer: T::AccountId,93 collection_id: RmrkCollectionId,94 },95 CollectionLocked {96 issuer: T::AccountId,97 collection_id: RmrkCollectionId,98 },99 NftMinted {100 owner: T::AccountId,101 collection_id: RmrkCollectionId,102 nft_id: RmrkNftId,103 },104 NFTBurned {105 owner: T::AccountId,106 nft_id: RmrkNftId,107 },108 NFTSent {109 sender: T::AccountId,110 recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,111 collection_id: RmrkCollectionId,112 nft_id: RmrkNftId,113 approval_required: bool,114 },115 NFTAccepted {116 sender: T::AccountId,117 recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,118 collection_id: RmrkCollectionId,119 nft_id: RmrkNftId,120 },121 NFTRejected {122 sender: T::AccountId,123 collection_id: RmrkCollectionId,124 nft_id: RmrkNftId,125 },126 PropertySet {127 collection_id: RmrkCollectionId,128 maybe_nft_id: Option<RmrkNftId>,129 key: RmrkKeyString,130 value: RmrkValueString,131 },132 ResourceAdded {133 nft_id: RmrkNftId,134 resource_id: RmrkResourceId,135 },136 ResourceRemoval {137 nft_id: RmrkNftId,138 resource_id: RmrkResourceId,139 },140 ResourceAccepted {141 nft_id: RmrkNftId,142 resource_id: RmrkResourceId,143 },144 ResourceRemovalAccepted {145 nft_id: RmrkNftId,146 resource_id: RmrkResourceId,147 },148 PrioritySet {149 collection_id: RmrkCollectionId,150 nft_id: RmrkNftId,151 },152 }153154 #[pallet::error]155 pub enum Error<T> {156 /* Unique-specific events */157 CorruptedCollectionType,158 NftTypeEncodeError,159 RmrkPropertyKeyIsTooLong,160 RmrkPropertyValueIsTooLong,161162 /* RMRK compatible events */163 CollectionNotEmpty,164 NoAvailableCollectionId,165 NoAvailableNftId,166 CollectionUnknown,167 NoPermission,168 NonTransferable,169 CollectionFullOrLocked,170 ResourceDoesntExist,171 CannotSendToDescendentOrSelf,172 CannotAcceptNonOwnedNft,173 CannotRejectNonOwnedNft,174 ResourceNotPending,175 }176177 #[pallet::call]178 impl<T: Config> Pallet<T> {179 /// Create a collection180 #[transactional]181 #[pallet::weight(<SelfWeightOf<T>>::create_collection())]182 pub fn create_collection(183 origin: OriginFor<T>,184 metadata: RmrkString,185 max: Option<u32>,186 symbol: RmrkCollectionSymbol,187 ) -> DispatchResult {188 let sender = ensure_signed(origin)?;189190 let limits = CollectionLimits {191 owner_can_transfer: Some(false),192 token_limit: max,193 ..Default::default()194 };195196 let data = CreateCollectionData {197 limits: Some(limits),198 token_prefix: symbol199 .into_inner()200 .try_into()201 .map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,202 permissions: Some(CollectionPermissions {203 nesting: Some(NestingRule::Owner),204 ..Default::default()205 }),206 ..Default::default()207 };208209 let unique_collection_id = Self::init_collection(210 T::CrossAccountId::from_sub(sender.clone()),211 data,212 [213 Self::rmrk_property(Metadata, &metadata)?,214 Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,215 ]216 .into_iter(),217 )?;218 let rmrk_collection_id = <CollectionIndex<T>>::get();219220 <UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);221 <RmrkInernalCollectionId<T>>::insert(unique_collection_id, rmrk_collection_id);222223 <CollectionIndex<T>>::mutate(|n| *n += 1);224225 Self::deposit_event(Event::CollectionCreated {226 issuer: sender,227 collection_id: rmrk_collection_id,228 });229230 Ok(())231 }232233 /// destroy collection234 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]235 #[transactional]236 pub fn destroy_collection(237 origin: OriginFor<T>,238 collection_id: RmrkCollectionId,239 ) -> DispatchResult {240 let sender = ensure_signed(origin)?;241 let cross_sender = T::CrossAccountId::from_sub(sender.clone());242243 let collection = Self::get_typed_nft_collection(244 Self::unique_collection_id(collection_id)?,245 misc::CollectionType::Regular,246 )?;247 collection.check_is_external()?;248249 <PalletNft<T>>::destroy_collection(collection, &cross_sender)250 .map_err(Self::map_unique_err_to_proxy)?;251252 Self::deposit_event(Event::CollectionDestroyed {253 issuer: sender,254 collection_id,255 });256257 Ok(())258 }259260 /// Change the issuer of a collection261 ///262 /// Parameters:263 /// - `origin`: sender of the transaction264 /// - `collection_id`: collection id of the nft to change issuer of265 /// - `new_issuer`: Collection's new issuer266 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]267 #[transactional]268 pub fn change_collection_issuer(269 origin: OriginFor<T>,270 collection_id: RmrkCollectionId,271 new_issuer: <T::Lookup as StaticLookup>::Source,272 ) -> DispatchResult {273 let sender = ensure_signed(origin)?;274275 let collection = Self::get_nft_collection(Self::unique_collection_id(collection_id)?)?;276 collection.check_is_external()?;277278 let new_issuer = T::Lookup::lookup(new_issuer)?;279280 Self::change_collection_owner(281 Self::unique_collection_id(collection_id)?,282 misc::CollectionType::Regular,283 sender.clone(),284 new_issuer.clone(),285 )?;286287 Self::deposit_event(Event::IssuerChanged {288 old_issuer: sender,289 new_issuer,290 collection_id,291 });292293 Ok(())294 }295296 /// lock collection297 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]298 #[transactional]299 pub fn lock_collection(300 origin: OriginFor<T>,301 collection_id: RmrkCollectionId,302 ) -> DispatchResult {303 let sender = ensure_signed(origin)?;304 let cross_sender = T::CrossAccountId::from_sub(sender.clone());305306 let collection = Self::get_typed_nft_collection(307 Self::unique_collection_id(collection_id)?,308 misc::CollectionType::Regular,309 )?;310 collection.check_is_external()?;311312 Self::check_collection_owner(&collection, &cross_sender)?;313314 let token_count = collection.total_supply();315316 let mut collection = collection.into_inner();317 collection.limits.token_limit = Some(token_count);318 collection.save()?;319320 Self::deposit_event(Event::CollectionLocked {321 issuer: sender,322 collection_id,323 });324325 Ok(())326 }327328 /// Mints an NFT in the specified collection329 /// Sets metadata and the royalty attribute330 ///331 /// Parameters:332 /// - `collection_id`: The class of the asset to be minted.333 /// - `nft_id`: The nft value of the asset to be minted.334 /// - `recipient`: Receiver of the royalty335 /// - `royalty`: Permillage reward from each trade for the Recipient336 /// - `metadata`: Arbitrary data about an nft, e.g. IPFS hash337 /// - `transferable`: Ability to transfer this NFT338 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]339 #[transactional]340 pub fn mint_nft(341 origin: OriginFor<T>,342 owner: T::AccountId,343 collection_id: RmrkCollectionId,344 recipient: Option<T::AccountId>,345 royalty_amount: Option<Permill>,346 metadata: RmrkString,347 transferable: bool,348 ) -> DispatchResult {349 let sender = ensure_signed(origin)?;350 let sender = T::CrossAccountId::from_sub(sender);351 let cross_owner = T::CrossAccountId::from_sub(owner.clone());352353 let collection = Self::get_typed_nft_collection(354 Self::unique_collection_id(collection_id)?,355 misc::CollectionType::Regular,356 )?;357 collection.check_is_external()?;358359 let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {360 recipient: recipient.unwrap_or_else(|| owner.clone()),361 amount,362 });363364 let nft_id = Self::create_nft(365 &sender,366 &cross_owner,367 &collection,368 [369 Self::rmrk_property(TokenType, &NftType::Regular)?,370 Self::rmrk_property(Transferable, &transferable)?,371 Self::rmrk_property(PendingNftAccept, &false)?,372 Self::rmrk_property(RoyaltyInfo, &royalty_info)?,373 Self::rmrk_property(Metadata, &metadata)?,374 Self::rmrk_property(Equipped, &false)?,375 Self::rmrk_property(376 ResourceCollection,377 &Self::init_collection(378 sender.clone(),379 CreateCollectionData {380 ..Default::default()381 },382 [Self::rmrk_property(383 CollectionType,384 &misc::CollectionType::Resource,385 )?]386 .into_iter(),387 )?,388 )?, // todo possibly add limits to the collection if rmrk warrants them389 Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,390 ]391 .into_iter(),392 )393 .map_err(|err| match err {394 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),395 err => Self::map_unique_err_to_proxy(err),396 })?;397398 Self::deposit_event(Event::NftMinted {399 owner,400 collection_id,401 nft_id: nft_id.0,402 });403404 Ok(())405 }406407 /// burn nft408 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]409 #[transactional]410 pub fn burn_nft(411 origin: OriginFor<T>,412 collection_id: RmrkCollectionId,413 nft_id: RmrkNftId,414 ) -> DispatchResult {415 let sender = ensure_signed(origin)?;416 let cross_sender = T::CrossAccountId::from_sub(sender.clone());417418 let collection = Self::get_typed_nft_collection(419 Self::unique_collection_id(collection_id)?,420 misc::CollectionType::Regular,421 )?;422 collection.check_is_external()?;423424 Self::destroy_nft(425 cross_sender,426 Self::unique_collection_id(collection_id)?,427 nft_id.into(),428 )429 .map_err(Self::map_unique_err_to_proxy)?;430431 Self::deposit_event(Event::NFTBurned {432 owner: sender,433 nft_id,434 });435436 Ok(())437 }438439 /// Transfers a NFT from an Account or NFT A to another Account or NFT B440 ///441 /// Parameters:442 /// - `origin`: sender of the transaction443 /// - `rmrk_collection_id`: collection id of the nft to be transferred444 /// - `rmrk_nft_id`: nft id of the nft to be transferred445 /// - `new_owner`: new owner of the nft which can be either an account or a NFT446 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]447 #[transactional]448 pub fn send(449 origin: OriginFor<T>,450 rmrk_collection_id: RmrkCollectionId,451 rmrk_nft_id: RmrkNftId,452 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,453 ) -> DispatchResult {454 let sender = ensure_signed(origin.clone())?;455 let cross_sender = T::CrossAccountId::from_sub(sender.clone());456457 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;458 let nft_id = rmrk_nft_id.into();459460 let collection =461 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;462 collection.check_is_external()?;463464 let token_data =465 <TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;466467 let from = token_data.owner;468469 ensure!(470 Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,471 <Error<T>>::NonTransferable472 );473474 ensure!(475 !Self::get_nft_property_decoded(476 collection_id,477 nft_id,478 RmrkProperty::PendingNftAccept479 )?,480 <Error<T>>::NoPermission481 );482483 let target_owner;484 let approval_required;485486 match new_owner {487 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {488 target_owner = T::CrossAccountId::from_sub(account_id.clone());489 approval_required = false;490 }491 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(492 target_collection_id,493 target_nft_id,494 ) => {495 let target_collection_id = Self::unique_collection_id(target_collection_id)?;496497 let target_nft_budget = budget::Value::new(NESTING_BUDGET);498499 let target_nft_owner = <PalletStructure<T>>::get_checked_topmost_owner(500 target_collection_id,501 target_nft_id.into(),502 Some((collection_id, nft_id)),503 &target_nft_budget,504 )505 .map_err(Self::map_unique_err_to_proxy)?;506507 approval_required = cross_sender != target_nft_owner;508509 if approval_required {510 target_owner = target_nft_owner;511512 <PalletNft<T>>::set_scoped_token_property(513 collection.id,514 nft_id,515 PropertyScope::Rmrk,516 Self::rmrk_property(PendingNftAccept, &approval_required)?,517 )?;518 } else {519 target_owner = T::CrossTokenAddressMapping::token_to_address(520 target_collection_id,521 target_nft_id.into(),522 );523 }524 }525 }526527 let src_nft_budget = budget::Value::new(NESTING_BUDGET);528529 <PalletNft<T>>::transfer_from(530 &collection,531 &cross_sender,532 &from,533 &target_owner,534 nft_id,535 &src_nft_budget,536 )537 .map_err(Self::map_unique_err_to_proxy)?;538539 Self::deposit_event(Event::NFTSent {540 sender,541 recipient: new_owner,542 collection_id: rmrk_collection_id,543 nft_id: rmrk_nft_id,544 approval_required,545 });546547 Ok(())548 }549550 /// Accepts an NFT sent from another account to self or owned NFT551 ///552 /// Parameters:553 /// - `origin`: sender of the transaction554 /// - `rmrk_collection_id`: collection id of the nft to be accepted555 /// - `rmrk_nft_id`: nft id of the nft to be accepted556 /// - `new_owner`: either origin's account ID or origin-owned NFT, whichever the NFT was557 /// sent to558 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]559 #[transactional]560 pub fn accept_nft(561 origin: OriginFor<T>,562 rmrk_collection_id: RmrkCollectionId,563 rmrk_nft_id: RmrkNftId,564 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,565 ) -> DispatchResult {566 let sender = ensure_signed(origin.clone())?;567 let cross_sender = T::CrossAccountId::from_sub(sender.clone());568569 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;570 let nft_id = rmrk_nft_id.into();571572 let collection =573 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;574 collection.check_is_external()?;575576 let new_cross_owner = match new_owner {577 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {578 T::CrossAccountId::from_sub(account_id.clone())579 }580 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(581 target_collection_id,582 target_nft_id,583 ) => {584 let target_collection_id = Self::unique_collection_id(target_collection_id)?;585586 T::CrossTokenAddressMapping::token_to_address(587 target_collection_id,588 TokenId(target_nft_id),589 )590 }591 };592593 let budget = budget::Value::new(NESTING_BUDGET);594595 <PalletNft<T>>::transfer(596 &collection,597 &cross_sender,598 &new_cross_owner,599 nft_id,600 &budget,601 )602 .map_err(|err| {603 if err == <CommonError<T>>::OnlyOwnerAllowedToNest.into() {604 <Error<T>>::CannotAcceptNonOwnedNft.into()605 } else {606 Self::map_unique_err_to_proxy(err)607 }608 })?;609610 <PalletNft<T>>::set_scoped_token_property(611 collection.id,612 nft_id,613 PropertyScope::Rmrk,614 Self::rmrk_property(PendingNftAccept, &false)?,615 )?;616617 Self::deposit_event(Event::NFTAccepted {618 sender,619 recipient: new_owner,620 collection_id: rmrk_collection_id,621 nft_id: rmrk_nft_id,622 });623624 Ok(())625 }626627 /// Rejects an NFT sent from another account to self or owned NFT628 ///629 /// Parameters:630 /// - `origin`: sender of the transaction631 /// - `rmrk_collection_id`: collection id of the nft to be accepted632 /// - `rmrk_nft_id`: nft id of the nft to be accepted633 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]634 #[transactional]635 pub fn reject_nft(636 origin: OriginFor<T>,637 rmrk_collection_id: RmrkCollectionId,638 rmrk_nft_id: RmrkNftId,639 ) -> DispatchResult {640 let sender = ensure_signed(origin)?;641 let cross_sender = T::CrossAccountId::from_sub(sender.clone());642643 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;644 let nft_id = rmrk_nft_id.into();645646 let collection =647 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;648 collection.check_is_external()?;649650 Self::destroy_nft(cross_sender, collection_id, nft_id).map_err(|err| {651 if err == <CommonError<T>>::NoPermission.into()652 || err == <CommonError<T>>::ApprovedValueTooLow.into()653 {654 <Error<T>>::CannotRejectNonOwnedNft.into()655 } else {656 Self::map_unique_err_to_proxy(err)657 }658 })?;659660 Self::deposit_event(Event::NFTRejected {661 sender,662 collection_id: rmrk_collection_id,663 nft_id: rmrk_nft_id,664 });665666 Ok(())667 }668669 /// accept the addition of a new resource to an existing NFT670 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]671 #[transactional]672 pub fn accept_resource(673 origin: OriginFor<T>,674 rmrk_collection_id: RmrkCollectionId,675 rmrk_nft_id: RmrkNftId,676 rmrk_resource_id: RmrkResourceId,677 ) -> DispatchResult {678 let sender = ensure_signed(origin)?;679 let cross_sender = T::CrossAccountId::from_sub(sender);680681 let collection_id = Self::unique_collection_id(rmrk_collection_id)682 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;683 let collection =684 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;685 collection.check_is_external()?;686687 let nft_id = rmrk_nft_id.into();688 let resource_id = rmrk_resource_id.into();689690 let budget = budget::Value::new(NESTING_BUDGET);691692 let nft_owner =693 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)694 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;695696 let resource_collection_id: CollectionId =697 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)698 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;699700 let is_pending: bool = Self::get_nft_property_decoded(701 resource_collection_id,702 resource_id,703 PendingResourceAccept,704 )705 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;706707 ensure!(is_pending, <Error<T>>::ResourceNotPending);708709 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);710711 <PalletNft<T>>::set_scoped_token_property(712 resource_collection_id,713 rmrk_resource_id.into(),714 PropertyScope::Rmrk,715 Self::rmrk_property(PendingResourceAccept, &false)?,716 )?;717718 Self::deposit_event(Event::<T>::ResourceAccepted {719 nft_id: rmrk_nft_id,720 resource_id: rmrk_resource_id,721 });722723 Ok(())724 }725726 /// accept the removal of a resource of an existing NFT727 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]728 #[transactional]729 pub fn accept_resource_removal(730 origin: OriginFor<T>,731 rmrk_collection_id: RmrkCollectionId,732 rmrk_nft_id: RmrkNftId,733 rmrk_resource_id: RmrkResourceId,734 ) -> DispatchResult {735 let sender = ensure_signed(origin)?;736 let cross_sender = T::CrossAccountId::from_sub(sender);737738 let collection_id = Self::unique_collection_id(rmrk_collection_id)739 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;740 let collection =741 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;742 collection.check_is_external()?;743744 let nft_id = rmrk_nft_id.into();745 let resource_id = rmrk_resource_id.into();746747 let budget = budget::Value::new(NESTING_BUDGET);748749 let nft_owner =750 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)751 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;752753 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);754755 let resource_collection_id: CollectionId =756 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)757 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;758759 let is_pending: bool = Self::get_nft_property_decoded(760 resource_collection_id,761 resource_id,762 PendingResourceRemoval,763 )764 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;765766 ensure!(is_pending, <Error<T>>::ResourceNotPending);767768 let resource_collection = Self::get_typed_nft_collection(769 resource_collection_id,770 misc::CollectionType::Resource,771 )?;772773 <PalletNft<T>>::burn(&resource_collection, &cross_sender, rmrk_resource_id.into())774 .map_err(Self::map_unique_err_to_proxy)?;775776 Self::deposit_event(Event::<T>::ResourceRemovalAccepted {777 nft_id: rmrk_nft_id,778 resource_id: rmrk_resource_id,779 });780781 Ok(())782 }783784 /// set a custom value on an NFT785 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]786 #[transactional]787 pub fn set_property(788 origin: OriginFor<T>,789 #[pallet::compact] rmrk_collection_id: RmrkCollectionId,790 maybe_nft_id: Option<RmrkNftId>,791 key: RmrkKeyString,792 value: RmrkValueString,793 ) -> DispatchResult {794 let sender = ensure_signed(origin)?;795 let sender = T::CrossAccountId::from_sub(sender);796797 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;798 let collection =799 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;800 collection.check_is_external()?;801802 let budget = budget::Value::new(NESTING_BUDGET);803804 match maybe_nft_id {805 Some(nft_id) => {806 let token_id: TokenId = nft_id.into();807808 Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;809 Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;810811 <PalletNft<T>>::set_scoped_token_property(812 collection_id,813 token_id,814 PropertyScope::Rmrk,815 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,816 )?;817 }818 None => {819 let collection = Self::get_typed_nft_collection(820 collection_id,821 misc::CollectionType::Regular,822 )?;823824 Self::check_collection_owner(&collection, &sender)?;825826 <PalletCommon<T>>::set_scoped_collection_property(827 collection_id,828 PropertyScope::Rmrk,829 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,830 )?;831 }832 }833834 Self::deposit_event(Event::PropertySet {835 collection_id: rmrk_collection_id,836 maybe_nft_id,837 key,838 value,839 });840841 Ok(())842 }843844 /// set a different order of resource priority845 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]846 #[transactional]847 pub fn set_priority(848 origin: OriginFor<T>,849 rmrk_collection_id: RmrkCollectionId,850 rmrk_nft_id: RmrkNftId,851 priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,852 ) -> DispatchResult {853 let sender = ensure_signed(origin)?;854 let sender = T::CrossAccountId::from_sub(sender);855856 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;857 let nft_id = rmrk_nft_id.into();858859 let collection =860 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;861 collection.check_is_external()?;862863 let budget = budget::Value::new(NESTING_BUDGET);864865 Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;866 Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;867868 <PalletNft<T>>::set_scoped_token_property(869 collection_id,870 nft_id,871 PropertyScope::Rmrk,872 Self::rmrk_property(ResourcePriorities, &priorities.into_inner())?,873 )?;874875 Self::deposit_event(Event::<T>::PrioritySet {876 collection_id: rmrk_collection_id,877 nft_id: rmrk_nft_id,878 });879880 Ok(())881 }882883 /// Create basic resource884 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]885 #[transactional]886 pub fn add_basic_resource(887 origin: OriginFor<T>,888 rmrk_collection_id: RmrkCollectionId,889 nft_id: RmrkNftId,890 resource: RmrkBasicResource,891 ) -> DispatchResult {892 let sender = ensure_signed(origin.clone())?;893894 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;895 let collection =896 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;897 collection.check_is_external()?;898899 let resource_id = Self::resource_add(900 sender,901 collection_id,902 nft_id.into(),903 [904 Self::rmrk_property(TokenType, &NftType::Resource)?,905 Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,906 Self::rmrk_property(Src, &resource.src)?,907 Self::rmrk_property(Metadata, &resource.metadata)?,908 Self::rmrk_property(License, &resource.license)?,909 Self::rmrk_property(Thumb, &resource.thumb)?,910 ]911 .into_iter(),912 )?;913914 Self::deposit_event(Event::ResourceAdded {915 nft_id,916 resource_id,917 });918 Ok(())919 }920921 /// Create composable resource922 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]923 #[transactional]924 pub fn add_composable_resource(925 origin: OriginFor<T>,926 rmrk_collection_id: RmrkCollectionId,927 nft_id: RmrkNftId,928 _resource_id: RmrkBoundedResource,929 resource: RmrkComposableResource,930 ) -> DispatchResult {931 let sender = ensure_signed(origin.clone())?;932933 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;934 let collection =935 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;936 collection.check_is_external()?;937938 let resource_id = Self::resource_add(939 sender,940 collection_id,941 nft_id.into(),942 [943 Self::rmrk_property(TokenType, &NftType::Resource)?,944 Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,945 Self::rmrk_property(Parts, &resource.parts)?,946 Self::rmrk_property(Base, &resource.base)?,947 Self::rmrk_property(Src, &resource.src)?,948 Self::rmrk_property(Metadata, &resource.metadata)?,949 Self::rmrk_property(License, &resource.license)?,950 Self::rmrk_property(Thumb, &resource.thumb)?,951 ]952 .into_iter(),953 )?;954955 Self::deposit_event(Event::ResourceAdded {956 nft_id,957 resource_id,958 });959 Ok(())960 }961962 /// Create slot resource963 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]964 #[transactional]965 pub fn add_slot_resource(966 origin: OriginFor<T>,967 rmrk_collection_id: RmrkCollectionId,968 nft_id: RmrkNftId,969 resource: RmrkSlotResource,970 ) -> DispatchResult {971 let sender = ensure_signed(origin.clone())?;972973 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;974 let collection =975 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;976 collection.check_is_external()?;977978 let resource_id = Self::resource_add(979 sender,980 collection_id,981 nft_id.into(),982 [983 Self::rmrk_property(TokenType, &NftType::Resource)?,984 Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,985 Self::rmrk_property(Base, &resource.base)?,986 Self::rmrk_property(Src, &resource.src)?,987 Self::rmrk_property(Metadata, &resource.metadata)?,988 Self::rmrk_property(Slot, &resource.slot)?,989 Self::rmrk_property(License, &resource.license)?,990 Self::rmrk_property(Thumb, &resource.thumb)?,991 ]992 .into_iter(),993 )?;994995 Self::deposit_event(Event::ResourceAdded {996 nft_id,997 resource_id,998 });999 Ok(())1000 }10011002 /// remove resource1003 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]1004 #[transactional]1005 pub fn remove_resource(1006 origin: OriginFor<T>,1007 rmrk_collection_id: RmrkCollectionId,1008 nft_id: RmrkNftId,1009 resource_id: RmrkResourceId,1010 ) -> DispatchResult {1011 let sender = ensure_signed(origin.clone())?;10121013 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1014 let collection =1015 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1016 collection.check_is_external()?;10171018 Self::resource_remove(sender, collection_id, nft_id.into(), resource_id.into())?;10191020 Self::deposit_event(Event::ResourceRemoval {1021 nft_id,1022 resource_id,1023 });1024 Ok(())1025 }1026 }1027}10281029impl<T: Config> Pallet<T> {1030 pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {1031 let key = rmrk_key.to_key::<T>()?;10321033 let scoped_key = PropertyScope::Rmrk1034 .apply(key)1035 .map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;10361037 Ok(scoped_key)1038 }10391040 // todo think about renaming these1041 pub fn rmrk_property<E: Encode>(1042 rmrk_key: RmrkProperty,1043 value: &E,1044 ) -> Result<Property, DispatchError> {1045 let key = rmrk_key.to_key::<T>()?;10461047 let value = value1048 .encode()1049 .try_into()1050 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;10511052 let property = Property { key, value };10531054 Ok(property)1055 }10561057 pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {1058 vec.decode()1059 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1060 }10611062 pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>1063 where1064 BoundedVec<u8, S>: TryFrom<Vec<u8>>,1065 {1066 vec.rebind()1067 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1068 }10691070 fn init_collection(1071 sender: T::CrossAccountId,1072 data: CreateCollectionData<T::AccountId>,1073 properties: impl Iterator<Item = Property>,1074 ) -> Result<CollectionId, DispatchError> {1075 let collection_id = <PalletNft<T>>::init_collection(sender, data, true);10761077 if let Err(DispatchError::Arithmetic(_)) = &collection_id {1078 return Err(<Error<T>>::NoAvailableCollectionId.into());1079 }10801081 <PalletCommon<T>>::set_scoped_collection_properties(1082 collection_id?,1083 PropertyScope::Rmrk,1084 properties,1085 )?;10861087 collection_id1088 }10891090 pub fn create_nft(1091 sender: &T::CrossAccountId,1092 owner: &T::CrossAccountId,1093 collection: &NonfungibleHandle<T>,1094 properties: impl Iterator<Item = Property>,1095 ) -> Result<TokenId, DispatchError> {1096 let data = CreateNftExData {1097 properties: BoundedVec::default(),1098 owner: owner.clone(),1099 };11001101 let budget = budget::Value::new(NESTING_BUDGET);11021103 <PalletNft<T>>::create_item(collection, sender, data, &budget)?;11041105 let nft_id = <PalletNft<T>>::current_token_id(collection.id);11061107 <PalletNft<T>>::set_scoped_token_properties(1108 collection.id,1109 nft_id,1110 PropertyScope::Rmrk,1111 properties,1112 )?;11131114 Ok(nft_id)1115 }11161117 fn destroy_nft(1118 sender: T::CrossAccountId,1119 collection_id: CollectionId,1120 token_id: TokenId,1121 ) -> DispatchResult {1122 let collection =1123 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;11241125 let token_data =1126 <TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;11271128 let from = token_data.owner;11291130 let budget = budget::Value::new(NESTING_BUDGET);11311132 <PalletNft<T>>::burn_from(&collection, &sender, &from, token_id, &budget)1133 }11341135 fn resource_add(1136 sender: T::AccountId,1137 collection_id: CollectionId,1138 token_id: TokenId,1139 resource_properties: impl Iterator<Item = Property>,1140 ) -> Result<RmrkResourceId, DispatchError> {1141 let collection =1142 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1143 ensure!(collection.owner == sender, Error::<T>::NoPermission);11441145 let sender = T::CrossAccountId::from_sub(sender);1146 let budget = budget::Value::new(NESTING_BUDGET);11471148 let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, token_id, &budget)1149 .map_err(Self::map_unique_err_to_proxy)?;11501151 let pending = sender != nft_owner;11521153 let resource_collection_id: CollectionId =1154 Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;1155 let resource_collection =1156 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;11571158 // todo probably add extra connections to bases, slots, etc., when RMRK starts to use them11591160 let resource_id = Self::create_nft(1161 &sender,1162 &nft_owner,1163 &resource_collection,1164 resource_properties.chain(1165 [1166 Self::rmrk_property(PendingResourceAccept, &pending)?,1167 Self::rmrk_property(PendingResourceRemoval, &false)?,1168 ]1169 .into_iter(),1170 ),1171 )1172 .map_err(|err| match err {1173 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),1174 err => Self::map_unique_err_to_proxy(err),1175 })?;11761177 Ok(resource_id.0)1178 }11791180 fn resource_remove(1181 sender: T::AccountId,1182 collection_id: CollectionId,1183 nft_id: TokenId,1184 resource_id: TokenId,1185 ) -> DispatchResult {1186 let collection =1187 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1188 ensure!(collection.owner == sender, Error::<T>::NoPermission);11891190 let resource_collection_id: CollectionId =1191 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;1192 let resource_collection =1193 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;1194 ensure!(1195 <PalletNft<T>>::token_exists(&resource_collection, resource_id),1196 Error::<T>::ResourceDoesntExist1197 );11981199 let budget = up_data_structs::budget::Value::new(10);1200 let topmost_owner =1201 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;12021203 let sender = T::CrossAccountId::from_sub(sender);1204 if topmost_owner == sender {1205 <PalletNft<T>>::burn(&resource_collection, &sender, resource_id)1206 .map_err(Self::map_unique_err_to_proxy)?;1207 } else {1208 <PalletNft<T>>::set_scoped_token_property(1209 resource_collection_id,1210 resource_id,1211 PropertyScope::Rmrk,1212 Self::rmrk_property(PendingResourceRemoval, &true)?,1213 )?;1214 }12151216 Ok(())1217 }12181219 fn change_collection_owner(1220 collection_id: CollectionId,1221 collection_type: misc::CollectionType,1222 sender: T::AccountId,1223 new_owner: T::AccountId,1224 ) -> DispatchResult {1225 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1226 Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;12271228 let mut collection = collection.into_inner();12291230 collection.owner = new_owner;1231 collection.save()1232 }12331234 fn check_collection_owner(1235 collection: &NonfungibleHandle<T>,1236 account: &T::CrossAccountId,1237 ) -> DispatchResult {1238 collection1239 .check_is_owner(account)1240 .map_err(Self::map_unique_err_to_proxy)1241 }12421243 pub fn last_collection_idx() -> RmrkCollectionId {1244 <CollectionIndex<T>>::get()1245 }12461247 pub fn unique_collection_id(1248 rmrk_collection_id: RmrkCollectionId,1249 ) -> Result<CollectionId, DispatchError> {1250 <UniqueCollectionId<T>>::try_get(rmrk_collection_id)1251 .map_err(|_| <Error<T>>::CollectionUnknown.into())1252 }12531254 pub fn rmrk_collection_id(1255 unique_collection_id: CollectionId,1256 ) -> Result<RmrkCollectionId, DispatchError> {1257 <RmrkInernalCollectionId<T>>::try_get(unique_collection_id)1258 .map_err(|_| <Error<T>>::CollectionUnknown.into())1259 }12601261 pub fn get_nft_collection(1262 collection_id: CollectionId,1263 ) -> Result<NonfungibleHandle<T>, DispatchError> {1264 let collection = <CollectionHandle<T>>::try_get(collection_id)1265 .map_err(|_| <Error<T>>::CollectionUnknown)?;12661267 match collection.mode {1268 CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1269 _ => Err(<Error<T>>::CollectionUnknown.into()),1270 }1271 }12721273 pub fn collection_exists(collection_id: CollectionId) -> bool {1274 <CollectionHandle<T>>::try_get(collection_id).is_ok()1275 }12761277 pub fn get_collection_property(1278 collection_id: CollectionId,1279 key: RmrkProperty,1280 ) -> Result<PropertyValue, DispatchError> {1281 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1282 .get(&Self::rmrk_property_key(key)?)1283 .ok_or(<Error<T>>::CollectionUnknown)?1284 .clone();12851286 Ok(collection_property)1287 }12881289 pub fn get_collection_property_decoded<V: Decode>(1290 collection_id: CollectionId,1291 key: RmrkProperty,1292 ) -> Result<V, DispatchError> {1293 Self::decode_property(Self::get_collection_property(collection_id, key)?)1294 }12951296 pub fn get_collection_type(1297 collection_id: CollectionId,1298 ) -> Result<misc::CollectionType, DispatchError> {1299 Self::get_collection_property_decoded(collection_id, CollectionType)1300 .map_err(|_| <Error<T>>::CorruptedCollectionType.into())1301 }13021303 pub fn ensure_collection_type(1304 collection_id: CollectionId,1305 collection_type: misc::CollectionType,1306 ) -> DispatchResult {1307 let actual_type = Self::get_collection_type(collection_id)?;1308 ensure!(1309 actual_type == collection_type,1310 <CommonError<T>>::NoPermission1311 );13121313 Ok(())1314 }13151316 pub fn get_typed_nft_collection(1317 collection_id: CollectionId,1318 collection_type: misc::CollectionType,1319 ) -> Result<NonfungibleHandle<T>, DispatchError> {1320 Self::ensure_collection_type(collection_id, collection_type)?;13211322 Self::get_nft_collection(collection_id)1323 }13241325 pub fn get_typed_nft_collection_mapped(1326 rmrk_collection_id: RmrkCollectionId,1327 collection_type: misc::CollectionType,1328 ) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1329 let unique_collection_id = match collection_type {1330 misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1331 _ => rmrk_collection_id.into(),1332 };13331334 let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;13351336 Ok((collection, unique_collection_id))1337 }13381339 pub fn get_nft_property(1340 collection_id: CollectionId,1341 nft_id: TokenId,1342 key: RmrkProperty,1343 ) -> Result<PropertyValue, DispatchError> {1344 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1345 .get(&Self::rmrk_property_key(key)?)1346 .ok_or(<Error<T>>::NoAvailableNftId)? // todo replace with better error?1347 .clone();13481349 Ok(nft_property)1350 }13511352 pub fn get_nft_property_decoded<V: Decode>(1353 collection_id: CollectionId,1354 nft_id: TokenId,1355 key: RmrkProperty,1356 ) -> Result<V, DispatchError> {1357 Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)1358 }13591360 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1361 <TokenData<T>>::contains_key((collection_id, nft_id))1362 }13631364 pub fn get_nft_type(1365 collection_id: CollectionId,1366 token_id: TokenId,1367 ) -> Result<NftType, DispatchError> {1368 Self::get_nft_property_decoded(collection_id, token_id, TokenType)1369 .map_err(|_| <Error<T>>::NoAvailableNftId.into())1370 }13711372 pub fn ensure_nft_type(1373 collection_id: CollectionId,1374 token_id: TokenId,1375 nft_type: NftType,1376 ) -> DispatchResult {1377 let actual_type = Self::get_nft_type(collection_id, token_id)?;1378 ensure!(actual_type == nft_type, <Error<T>>::NoPermission);13791380 Ok(())1381 }13821383 pub fn ensure_nft_owner(1384 collection_id: CollectionId,1385 token_id: TokenId,1386 possible_owner: &T::CrossAccountId,1387 nesting_budget: &dyn budget::Budget,1388 ) -> DispatchResult {1389 let is_owned = <PalletStructure<T>>::check_indirectly_owned(1390 possible_owner.clone(),1391 collection_id,1392 token_id,1393 None,1394 nesting_budget,1395 )1396 .map_err(Self::map_unique_err_to_proxy)?;13971398 ensure!(is_owned, <Error<T>>::NoPermission);13991400 Ok(())1401 }14021403 pub fn filter_user_properties<Key, Value, R, Mapper>(1404 collection_id: CollectionId,1405 token_id: Option<TokenId>,1406 filter_keys: Option<Vec<RmrkPropertyKey>>,1407 mapper: Mapper,1408 ) -> Result<Vec<R>, DispatchError>1409 where1410 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1411 Value: Decode + Default,1412 Mapper: Fn(Key, Value) -> R,1413 {1414 filter_keys1415 .map(|keys| {1416 let properties = keys1417 .into_iter()1418 .filter_map(|key| {1419 let key: Key = key.try_into().ok()?;14201421 let value = match token_id {1422 Some(token_id) => Self::get_nft_property_decoded(1423 collection_id,1424 token_id,1425 UserProperty(key.as_ref()),1426 ),1427 None => Self::get_collection_property_decoded(1428 collection_id,1429 UserProperty(key.as_ref()),1430 ),1431 }1432 .ok()?;14331434 Some(mapper(key, value))1435 })1436 .collect();14371438 Ok(properties)1439 })1440 .unwrap_or_else(|| {1441 let properties =1442 Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();14431444 Ok(properties)1445 })1446 }14471448 pub fn iterate_user_properties<Key, Value, R, Mapper>(1449 collection_id: CollectionId,1450 token_id: Option<TokenId>,1451 mapper: Mapper,1452 ) -> Result<impl Iterator<Item = R>, DispatchError>1453 where1454 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1455 Value: Decode + Default,1456 Mapper: Fn(Key, Value) -> R,1457 {1458 let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;14591460 let properties = match token_id {1461 Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1462 None => <PalletCommon<T>>::collection_properties(collection_id),1463 };14641465 let properties = properties.into_iter().filter_map(move |(key, value)| {1466 let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;14671468 let key: Key = key.to_vec().try_into().ok()?;1469 let value: Value = value.decode().ok()?;14701471 Some(mapper(key, value))1472 });14731474 Ok(properties)1475 }14761477 fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1478 map_unique_err_to_proxy! {1479 match err {1480 CommonError::NoPermission => NoPermission,1481 CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1482 CommonError::PublicMintingNotAllowed => NoPermission,1483 CommonError::TokenNotFound => NoAvailableNftId,1484 CommonError::ApprovedValueTooLow => NoPermission,1485 CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1486 StructureError::TokenNotFound => NoAvailableNftId,1487 StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1488 }1489 }1490 }1491}pallets/proxy-rmrk-equip/src/lib.rsdiffbeforeafterboth--- a/pallets/proxy-rmrk-equip/src/lib.rs
+++ b/pallets/proxy-rmrk-equip/src/lib.rs
@@ -74,6 +74,16 @@
#[pallet::call]
impl<T: Config> Pallet<T> {
+
+ /// Creates a new Base.
+ /// Modeled after [base interaction](https://github.com/rmrk-team/rmrk-spec/blob/master/standards/rmrk2.0.0/interactions/base.md)
+ ///
+ /// Parameters:
+ /// - origin: Caller, will be assigned as the issuer of the Base
+ /// - base_type: media type, e.g. "svg"
+ /// - symbol: arbitrary client-chosen symbol
+ /// - parts: array of Fixed and Slot parts composing the base, confined in length by
+ /// RmrkPartsLimit
#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]
#[transactional]
pub fn create_base(
@@ -137,6 +147,19 @@
Ok(())
}
+ /// Adds a Theme to a Base.
+ /// Modeled after [themeadd interaction](https://github.com/rmrk-team/rmrk-spec/blob/master/standards/rmrk2.0.0/interactions/themeadd.md)
+ /// Themes are stored in the Themes storage
+ /// A Theme named "default" is required prior to adding other Themes.
+ ///
+ /// Parameters:
+ /// - origin: The caller of the function, must be issuer of the base
+ /// - base_id: The Base containing the Theme to be updated
+ /// - theme: The Theme to add to the Base. A Theme has a name and properties, which are an
+ /// array of [key, value, inherit].
+ /// - key: arbitrary BoundedString, defined by client
+ /// - value: arbitrary BoundedString, defined by client
+ /// - inherit: optional bool
#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]
#[transactional]
pub fn theme_add(