difftreelog
feat(rmrk) add accept_nft
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 PropertySet {102 collection_id: RmrkCollectionId,103 maybe_nft_id: Option<RmrkNftId>,104 key: RmrkKeyString,105 value: RmrkValueString,106 },107 ResourceAdded {108 nft_id: RmrkNftId,109 resource_id: RmrkResourceId,110 },111 ResourceRemoval {112 nft_id: RmrkNftId,113 resource_id: RmrkResourceId,114 },115 }116117 #[pallet::error]118 pub enum Error<T> {119 /* Unique-specific events */120 CorruptedCollectionType,121 NftTypeEncodeError,122 RmrkPropertyKeyIsTooLong,123 RmrkPropertyValueIsTooLong,124125 /* RMRK compatible events */126 CollectionNotEmpty,127 NoAvailableCollectionId,128 NoAvailableNftId,129 CollectionUnknown,130 NoPermission,131 NonTransferable,132 CollectionFullOrLocked,133 ResourceDoesntExist,134 CannotSendToDescendentOrSelf,135 }136137 #[pallet::call]138 impl<T: Config> Pallet<T> {139 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]140 #[transactional]141 pub fn create_collection(142 origin: OriginFor<T>,143 metadata: RmrkString,144 max: Option<u32>,145 symbol: RmrkCollectionSymbol,146 ) -> DispatchResult {147 let sender = ensure_signed(origin)?;148149 let limits = CollectionLimits {150 owner_can_transfer: Some(false),151 token_limit: max,152 ..Default::default()153 };154155 let data = CreateCollectionData {156 limits: Some(limits),157 token_prefix: symbol158 .into_inner()159 .try_into()160 .map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,161 permissions: Some(CollectionPermissions {162 nesting: Some(NestingRule::Owner),163 ..Default::default()164 }),165 ..Default::default()166 };167168 let unique_collection_id = Self::init_collection(169 T::CrossAccountId::from_sub(sender.clone()),170 data,171 [172 Self::rmrk_property(Metadata, &metadata)?,173 Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,174 ]175 .into_iter(),176 )?;177 let rmrk_collection_id = <CollectionIndex<T>>::get();178179 <UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);180 <RmrkInernalCollectionId<T>>::insert(unique_collection_id, rmrk_collection_id);181182 <CollectionIndex<T>>::mutate(|n| *n += 1);183184 Self::deposit_event(Event::CollectionCreated {185 issuer: sender,186 collection_id: rmrk_collection_id,187 });188189 Ok(())190 }191192 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]193 #[transactional]194 pub fn destroy_collection(195 origin: OriginFor<T>,196 collection_id: RmrkCollectionId,197 ) -> DispatchResult {198 let sender = ensure_signed(origin)?;199 let cross_sender = T::CrossAccountId::from_sub(sender.clone());200201 let collection = Self::get_typed_nft_collection(202 Self::unique_collection_id(collection_id)?,203 misc::CollectionType::Regular,204 )?;205206 ensure!(207 collection.total_supply() == 0,208 <Error<T>>::CollectionNotEmpty209 );210211 <PalletNft<T>>::destroy_collection(collection, &cross_sender)212 .map_err(Self::map_unique_err_to_proxy)?;213214 Self::deposit_event(Event::CollectionDestroyed {215 issuer: sender,216 collection_id,217 });218219 Ok(())220 }221222 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]223 #[transactional]224 pub fn change_collection_issuer(225 origin: OriginFor<T>,226 collection_id: RmrkCollectionId,227 new_issuer: <T::Lookup as StaticLookup>::Source,228 ) -> DispatchResult {229 let sender = ensure_signed(origin)?;230231 let new_issuer = T::Lookup::lookup(new_issuer)?;232233 Self::change_collection_owner(234 Self::unique_collection_id(collection_id)?,235 misc::CollectionType::Regular,236 sender.clone(),237 new_issuer.clone(),238 )?;239240 Self::deposit_event(Event::IssuerChanged {241 old_issuer: sender,242 new_issuer,243 collection_id,244 });245246 Ok(())247 }248249 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]250 #[transactional]251 pub fn lock_collection(252 origin: OriginFor<T>,253 collection_id: RmrkCollectionId,254 ) -> DispatchResult {255 let sender = ensure_signed(origin)?;256 let cross_sender = T::CrossAccountId::from_sub(sender.clone());257258 let collection = Self::get_typed_nft_collection(259 Self::unique_collection_id(collection_id)?,260 misc::CollectionType::Regular,261 )?;262263 Self::check_collection_owner(&collection, &cross_sender)?;264265 let token_count = collection.total_supply();266267 let mut collection = collection.into_inner();268 collection.limits.token_limit = Some(token_count);269 collection.save()?;270271 Self::deposit_event(Event::CollectionLocked {272 issuer: sender,273 collection_id,274 });275276 Ok(())277 }278279 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]280 #[transactional]281 pub fn mint_nft(282 origin: OriginFor<T>,283 owner: T::AccountId,284 collection_id: RmrkCollectionId,285 recipient: Option<T::AccountId>,286 royalty_amount: Option<Permill>,287 metadata: RmrkString,288 transferable: bool,289 ) -> DispatchResult {290 let sender = ensure_signed(origin)?;291 let sender = T::CrossAccountId::from_sub(sender);292 let cross_owner = T::CrossAccountId::from_sub(owner.clone());293294 let royalty_info = royalty_amount.map(|amount| rmrk::RoyaltyInfo {295 recipient: recipient.unwrap_or_else(|| owner.clone()),296 amount,297 });298299 let collection = Self::get_typed_nft_collection(300 Self::unique_collection_id(collection_id)?,301 misc::CollectionType::Regular,302 )?;303304 let nft_id = Self::create_nft(305 &sender,306 &cross_owner,307 &collection,308 [309 Self::rmrk_property(TokenType, &NftType::Regular)?,310 Self::rmrk_property(Transferable, &transferable)?,311 Self::rmrk_property(PendingNftAccept, &false)?,312 Self::rmrk_property(RoyaltyInfo, &royalty_info)?,313 Self::rmrk_property(Metadata, &metadata)?,314 Self::rmrk_property(Equipped, &false)?,315 Self::rmrk_property(316 ResourceCollection,317 &Self::init_collection(318 sender.clone(),319 CreateCollectionData {320 ..Default::default()321 },322 [Self::rmrk_property(323 CollectionType,324 &misc::CollectionType::Resource,325 )?]326 .into_iter(),327 )?,328 )?, // todo possibly add limits to the collection if rmrk warrants them329 Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,330 ]331 .into_iter(),332 )333 .map_err(|err| match err {334 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),335 err => Self::map_unique_err_to_proxy(err),336 })?;337338 Self::deposit_event(Event::NftMinted {339 owner,340 collection_id,341 nft_id: nft_id.0,342 });343344 Ok(())345 }346347 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]348 #[transactional]349 pub fn burn_nft(350 origin: OriginFor<T>,351 collection_id: RmrkCollectionId,352 nft_id: RmrkNftId,353 ) -> DispatchResult {354 let sender = ensure_signed(origin)?;355 let cross_sender = T::CrossAccountId::from_sub(sender.clone());356357 Self::destroy_nft(358 cross_sender,359 Self::unique_collection_id(collection_id)?,360 misc::CollectionType::Regular,361 nft_id.into(),362 )?;363364 Self::deposit_event(Event::NFTBurned {365 owner: sender,366 nft_id,367 });368369 Ok(())370 }371372 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]373 #[transactional]374 pub fn send(375 origin: OriginFor<T>,376 rmrk_collection_id: RmrkCollectionId,377 rmrk_nft_id: RmrkNftId,378 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,379 ) -> DispatchResult {380 let sender = ensure_signed(origin.clone())?;381 let cross_sender = T::CrossAccountId::from_sub(sender);382383 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;384 let nft_id = rmrk_nft_id.into();385386 let token_data = <TokenData<T>>::get((collection_id, nft_id))387 .ok_or(<Error<T>>::NoAvailableNftId)?;388389 let from = token_data.owner;390391 let collection = Self::get_typed_nft_collection(392 collection_id,393 misc::CollectionType::Regular,394 )?;395396 if !Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)? {397 return Err(<Error<T>>::NonTransferable.into());398 }399400 if Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::PendingNftAccept)? {401 return Err(<Error<T>>::NoPermission.into());402 }403404 let target_owner;405406 match new_owner {407 RmrkAccountIdOrCollectionNftTuple::AccountId(account_id) => {408 target_owner = T::CrossAccountId::from_sub(account_id);409 },410 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(target_collection_id, target_nft_id) => {411 let target_collection_id = Self::unique_collection_id(target_collection_id)?;412413 let target_nft_budget = budget::Value::new(NESTING_BUDGET);414415 let target_nft_owner = <PalletStructure<T>>::get_checked_indirect_owner(416 target_collection_id,417 target_nft_id.into(),418 Some((collection_id, nft_id)),419 &target_nft_budget,420 ).map_err(Self::map_unique_err_to_proxy)?;421422 let is_approval_required = cross_sender != target_nft_owner;423424 if is_approval_required {425 target_owner = target_nft_owner;426427 <PalletNft<T>>::set_scoped_token_property(428 collection.id,429 nft_id,430 PropertyScope::Rmrk,431 Self::rmrk_property(PendingNftAccept, &is_approval_required)?,432 )?;433 } else {434 target_owner = T::CrossTokenAddressMapping::token_to_address(435 target_collection_id,436 target_nft_id.into(),437 );438 }439 }440 }441442 let src_nft_budget = budget::Value::new(NESTING_BUDGET);443444 <PalletNft<T>>::transfer_from(445 &collection,446 &cross_sender,447 &from,448 &target_owner,449 nft_id,450 &src_nft_budget451 ).map_err(Self::map_unique_err_to_proxy)?;452453 Ok(())454 }455456 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]457 #[transactional]458 pub fn set_property(459 origin: OriginFor<T>,460 #[pallet::compact] rmrk_collection_id: RmrkCollectionId,461 maybe_nft_id: Option<RmrkNftId>,462 key: RmrkKeyString,463 value: RmrkValueString,464 ) -> DispatchResult {465 let sender = ensure_signed(origin)?;466 let sender = T::CrossAccountId::from_sub(sender);467468 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;469 let budget = budget::Value::new(NESTING_BUDGET);470471 match maybe_nft_id {472 Some(nft_id) => {473 let token_id: TokenId = nft_id.into();474475 Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;476 Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;477478 <PalletNft<T>>::set_scoped_token_property(479 collection_id,480 token_id,481 PropertyScope::Rmrk,482 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,483 )?;484 }485 None => {486 let collection = Self::get_typed_nft_collection(487 collection_id,488 misc::CollectionType::Regular,489 )?;490491 Self::check_collection_owner(&collection, &sender)?;492493 <PalletCommon<T>>::set_scoped_collection_property(494 collection_id,495 PropertyScope::Rmrk,496 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,497 )?;498 }499 }500501 Self::deposit_event(Event::PropertySet {502 collection_id: rmrk_collection_id,503 maybe_nft_id,504 key,505 value,506 });507508 Ok(())509 }510511 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]512 #[transactional]513 pub fn add_basic_resource(514 origin: OriginFor<T>,515 collection_id: RmrkCollectionId,516 nft_id: RmrkNftId,517 resource: RmrkBasicResource,518 ) -> DispatchResult {519 let sender = ensure_signed(origin.clone())?;520521 let resource_id = Self::resource_add(522 sender,523 Self::unique_collection_id(collection_id)?,524 nft_id.into(),525 [526 Self::rmrk_property(TokenType, &NftType::Resource)?,527 Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,528 Self::rmrk_property(Src, &resource.src)?,529 Self::rmrk_property(Metadata, &resource.metadata)?,530 Self::rmrk_property(License, &resource.license)?,531 Self::rmrk_property(Thumb, &resource.thumb)?,532 ]533 .into_iter(),534 )?;535536 Self::deposit_event(Event::ResourceAdded {537 nft_id,538 resource_id,539 });540 Ok(())541 }542543 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]544 #[transactional]545 pub fn add_composable_resource(546 origin: OriginFor<T>,547 collection_id: RmrkCollectionId,548 nft_id: RmrkNftId,549 _resource_id: RmrkBoundedResource,550 resource: RmrkComposableResource,551 ) -> DispatchResult {552 let sender = ensure_signed(origin.clone())?;553554 let resource_id = Self::resource_add(555 sender,556 Self::unique_collection_id(collection_id)?,557 nft_id.into(),558 [559 Self::rmrk_property(TokenType, &NftType::Resource)?,560 Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,561 Self::rmrk_property(Parts, &resource.parts)?,562 Self::rmrk_property(Base, &resource.base)?,563 Self::rmrk_property(Src, &resource.src)?,564 Self::rmrk_property(Metadata, &resource.metadata)?,565 Self::rmrk_property(License, &resource.license)?,566 Self::rmrk_property(Thumb, &resource.thumb)?,567 ]568 .into_iter(),569 )?;570571 Self::deposit_event(Event::ResourceAdded {572 nft_id,573 resource_id,574 });575 Ok(())576 }577578 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]579 #[transactional]580 pub fn add_slot_resource(581 origin: OriginFor<T>,582 collection_id: RmrkCollectionId,583 nft_id: RmrkNftId,584 resource: RmrkSlotResource,585 ) -> DispatchResult {586 let sender = ensure_signed(origin.clone())?;587588 let resource_id = Self::resource_add(589 sender,590 Self::unique_collection_id(collection_id)?,591 nft_id.into(),592 [593 Self::rmrk_property(TokenType, &NftType::Resource)?,594 Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,595 Self::rmrk_property(Base, &resource.base)?,596 Self::rmrk_property(Src, &resource.src)?,597 Self::rmrk_property(Metadata, &resource.metadata)?,598 Self::rmrk_property(Slot, &resource.slot)?,599 Self::rmrk_property(License, &resource.license)?,600 Self::rmrk_property(Thumb, &resource.thumb)?,601 ]602 .into_iter(),603 )?;604605 Self::deposit_event(Event::ResourceAdded {606 nft_id,607 resource_id,608 });609 Ok(())610 }611612 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]613 #[transactional]614 pub fn remove_resource(615 origin: OriginFor<T>,616 collection_id: RmrkCollectionId,617 nft_id: RmrkNftId,618 resource_id: RmrkResourceId,619 ) -> DispatchResult {620 let sender = ensure_signed(origin.clone())?;621622 Self::resource_remove(623 sender,624 Self::unique_collection_id(collection_id)?,625 nft_id.into(),626 resource_id.into(),627 )?;628629 Self::deposit_event(Event::ResourceRemoval {630 nft_id,631 resource_id,632 });633 Ok(())634 }635 }636}637638impl<T: Config> Pallet<T> {639 pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {640 let key = rmrk_key.to_key::<T>()?;641642 let scoped_key = PropertyScope::Rmrk643 .apply(key)644 .map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;645646 Ok(scoped_key)647 }648649 // todo think about renaming these650 pub fn rmrk_property<E: Encode>(651 rmrk_key: RmrkProperty,652 value: &E,653 ) -> Result<Property, DispatchError> {654 let key = rmrk_key.to_key::<T>()?;655656 let value = value657 .encode()658 .try_into()659 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;660661 let property = Property { key, value };662663 Ok(property)664 }665666 pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {667 vec.decode()668 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())669 }670671 pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>672 where673 BoundedVec<u8, S>: TryFrom<Vec<u8>>,674 {675 vec.rebind()676 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())677 }678679 fn init_collection(680 sender: T::CrossAccountId,681 data: CreateCollectionData<T::AccountId>,682 properties: impl Iterator<Item = Property>,683 ) -> Result<CollectionId, DispatchError> {684 let collection_id = <PalletNft<T>>::init_collection(sender, data);685686 if let Err(DispatchError::Arithmetic(_)) = &collection_id {687 return Err(<Error<T>>::NoAvailableCollectionId.into());688 }689690 <PalletCommon<T>>::set_scoped_collection_properties(691 collection_id?,692 PropertyScope::Rmrk,693 properties,694 )?;695696 collection_id697 }698699 pub fn create_nft(700 sender: &T::CrossAccountId,701 owner: &T::CrossAccountId,702 collection: &NonfungibleHandle<T>,703 properties: impl Iterator<Item = Property>,704 ) -> Result<TokenId, DispatchError> {705 let data = CreateNftExData {706 properties: BoundedVec::default(),707 owner: owner.clone(),708 };709710 let budget = budget::Value::new(NESTING_BUDGET);711712 <PalletNft<T>>::create_item(collection, sender, data, &budget)?;713714 let nft_id = <PalletNft<T>>::current_token_id(collection.id);715716 <PalletNft<T>>::set_scoped_token_properties(717 collection.id,718 nft_id,719 PropertyScope::Rmrk,720 properties,721 )?;722723 Ok(nft_id)724 }725726 fn destroy_nft(727 sender: T::CrossAccountId,728 collection_id: CollectionId,729 collection_type: misc::CollectionType,730 token_id: TokenId,731 ) -> DispatchResult {732 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;733734 <PalletNft<T>>::burn(&collection, &sender, token_id)735 .map_err(Self::map_unique_err_to_proxy)?;736737 Ok(())738 }739740 fn resource_add(741 sender: T::AccountId,742 collection_id: CollectionId,743 token_id: TokenId,744 resource_properties: impl Iterator<Item = Property>,745 ) -> Result<RmrkResourceId, DispatchError> {746 let collection =747 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;748 ensure!(collection.owner == sender, Error::<T>::NoPermission);749750 // Check NFT lock status // todo depends on market, maybe later751 //ensure!(!Pallet::<T>::is_locked(collection_id, nft_id), pallet_uniques::Error::<T>::Locked);752753 let sender = T::CrossAccountId::from_sub(sender);754 let budget = budget::Value::new(NESTING_BUDGET);755 let pending = Self::ensure_nft_owner(collection_id, token_id, &sender, &budget).is_err();756757 let resource_collection_id: CollectionId =758 Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;759 let resource_collection =760 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;761762 // todo probably add extra connections to bases, slots, etc., when RMRK starts to use them763764 let resource_id = Self::create_nft(765 &sender, // todo owner of the nft?766 &sender,767 &resource_collection,768 resource_properties.chain(769 [770 Self::rmrk_property(PendingResourceAccept, &pending)?,771 Self::rmrk_property(PendingResourceRemoval, &false)?,772 ]773 .into_iter(),774 ),775 )776 .map_err(|err| match err {777 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),778 err => Self::map_unique_err_to_proxy(err),779 })?;780781 Ok(resource_id.0)782 }783784 fn resource_remove(785 sender: T::AccountId,786 collection_id: CollectionId,787 nft_id: TokenId,788 resource_id: TokenId,789 ) -> DispatchResult {790 let collection =791 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;792 ensure!(collection.owner == sender, Error::<T>::NoPermission);793794 let resource_collection_id: CollectionId =795 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;796 let resource_collection =797 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;798 ensure!(799 <PalletNft<T>>::token_exists(&resource_collection, resource_id),800 Error::<T>::ResourceDoesntExist801 );802803 let budget = up_data_structs::budget::Value::new(10);804 let topmost_owner =805 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;806807 let sender = T::CrossAccountId::from_sub(sender);808 if topmost_owner == sender {809 <PalletNft<T>>::burn(&resource_collection, &sender, resource_id)810 .map_err(Self::map_unique_err_to_proxy)?;811 } else {812 <PalletNft<T>>::set_scoped_token_property(813 resource_collection_id,814 resource_id,815 PropertyScope::Rmrk,816 Self::rmrk_property(PendingResourceRemoval, &true)?,817 )?;818 }819820 Ok(())821 }822823 fn change_collection_owner(824 collection_id: CollectionId,825 collection_type: misc::CollectionType,826 sender: T::AccountId,827 new_owner: T::AccountId,828 ) -> DispatchResult {829 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;830 Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;831832 let mut collection = collection.into_inner();833834 collection.owner = new_owner;835 collection.save()836 }837838 fn check_collection_owner(839 collection: &NonfungibleHandle<T>,840 account: &T::CrossAccountId,841 ) -> DispatchResult {842 collection843 .check_is_owner(account)844 .map_err(Self::map_unique_err_to_proxy)845 }846847 pub fn last_collection_idx() -> RmrkCollectionId {848 <CollectionIndex<T>>::get()849 }850851 pub fn unique_collection_id(852 rmrk_collection_id: RmrkCollectionId,853 ) -> Result<CollectionId, DispatchError> {854 <UniqueCollectionId<T>>::try_get(rmrk_collection_id)855 .map_err(|_| <Error<T>>::CollectionUnknown.into())856 }857858 pub fn rmrk_collection_id(859 unique_collection_id: CollectionId860 ) -> Result<RmrkCollectionId, DispatchError> {861 <RmrkInernalCollectionId<T>>::try_get(unique_collection_id)862 .map_err(|_| <Error<T>>::CollectionUnknown.into())863 }864865 pub fn get_nft_collection(866 collection_id: CollectionId,867 ) -> Result<NonfungibleHandle<T>, DispatchError> {868 let collection = <CollectionHandle<T>>::try_get(collection_id)869 .map_err(|_| <Error<T>>::CollectionUnknown)?;870871 match collection.mode {872 CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),873 _ => Err(<Error<T>>::CollectionUnknown.into()),874 }875 }876877 pub fn collection_exists(collection_id: CollectionId) -> bool {878 <CollectionHandle<T>>::try_get(collection_id).is_ok()879 }880881 pub fn get_collection_property(882 collection_id: CollectionId,883 key: RmrkProperty,884 ) -> Result<PropertyValue, DispatchError> {885 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)886 .get(&Self::rmrk_property_key(key)?)887 .ok_or(<Error<T>>::CollectionUnknown)?888 .clone();889890 Ok(collection_property)891 }892893 pub fn get_collection_property_decoded<V: Decode>(894 collection_id: CollectionId,895 key: RmrkProperty,896 ) -> Result<V, DispatchError> {897 Self::decode_property(Self::get_collection_property(collection_id, key)?)898 }899900 pub fn get_collection_type(901 collection_id: CollectionId,902 ) -> Result<misc::CollectionType, DispatchError> {903 Self::get_collection_property_decoded(collection_id, CollectionType)904 .map_err(|_| <Error<T>>::CorruptedCollectionType.into())905 }906907 pub fn ensure_collection_type(908 collection_id: CollectionId,909 collection_type: misc::CollectionType,910 ) -> DispatchResult {911 let actual_type = Self::get_collection_type(collection_id)?;912 ensure!(913 actual_type == collection_type,914 <CommonError<T>>::NoPermission915 );916917 Ok(())918 }919920 pub fn get_typed_nft_collection(921 collection_id: CollectionId,922 collection_type: misc::CollectionType,923 ) -> Result<NonfungibleHandle<T>, DispatchError> {924 Self::ensure_collection_type(collection_id, collection_type)?;925926 Self::get_nft_collection(collection_id)927 }928929 pub fn get_typed_nft_collection_mapped(930 rmrk_collection_id: RmrkCollectionId,931 collection_type: misc::CollectionType,932 ) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {933 let unique_collection_id = Self::unique_collection_id(rmrk_collection_id)?;934935 let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;936937 Ok((collection, unique_collection_id))938 }939940 pub fn get_nft_property(941 collection_id: CollectionId,942 nft_id: TokenId,943 key: RmrkProperty,944 ) -> Result<PropertyValue, DispatchError> {945 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))946 .get(&Self::rmrk_property_key(key)?)947 .ok_or(<Error<T>>::NoAvailableNftId)? // todo replace with better error?948 .clone();949950 Ok(nft_property)951 }952953 pub fn get_nft_property_decoded<V: Decode>(954 collection_id: CollectionId,955 nft_id: TokenId,956 key: RmrkProperty,957 ) -> Result<V, DispatchError> {958 Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)959 }960961 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {962 <TokenData<T>>::contains_key((collection_id, nft_id))963 }964965 pub fn get_nft_type(966 collection_id: CollectionId,967 token_id: TokenId,968 ) -> Result<NftType, DispatchError> {969 Self::get_nft_property_decoded(collection_id, token_id, TokenType)970 .map_err(|_| <Error<T>>::NftTypeEncodeError.into())971 }972973 pub fn ensure_nft_type(974 collection_id: CollectionId,975 token_id: TokenId,976 nft_type: NftType,977 ) -> DispatchResult {978 let actual_type = Self::get_nft_type(collection_id, token_id)?;979 ensure!(actual_type == nft_type, <Error<T>>::NoPermission);980981 Ok(())982 }983984 pub fn ensure_nft_owner(985 collection_id: CollectionId,986 token_id: TokenId,987 possible_owner: &T::CrossAccountId,988 nesting_budget: &dyn budget::Budget989 ) -> DispatchResult {990 let is_owned = <PalletStructure<T>>::check_indirectly_owned(991 possible_owner.clone(),992 collection_id,993 token_id,994 None,995 nesting_budget,996 )?;997998 ensure!(999 is_owned,1000 <Error<T>>::NoPermission1001 );10021003 Ok(())1004 }10051006 pub fn filter_user_properties<Key, Value, R, Mapper>(1007 collection_id: CollectionId,1008 token_id: Option<TokenId>,1009 filter_keys: Option<Vec<RmrkPropertyKey>>,1010 mapper: Mapper,1011 ) -> Result<Vec<R>, DispatchError>1012 where1013 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1014 Value: Decode + Default,1015 Mapper: Fn(Key, Value) -> R,1016 {1017 filter_keys1018 .map(|keys| {1019 let properties = keys1020 .into_iter()1021 .filter_map(|key| {1022 let key: Key = key.try_into().ok()?;10231024 let value = match token_id {1025 Some(token_id) => Self::get_nft_property_decoded(1026 collection_id,1027 token_id,1028 UserProperty(key.as_ref()),1029 ),1030 None => Self::get_collection_property_decoded(1031 collection_id,1032 UserProperty(key.as_ref()),1033 ),1034 }1035 .ok()?;10361037 Some(mapper(key, value))1038 })1039 .collect();10401041 Ok(properties)1042 })1043 .unwrap_or_else(|| {1044 let properties =1045 Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();10461047 Ok(properties)1048 })1049 }10501051 pub fn iterate_user_properties<Key, Value, R, Mapper>(1052 collection_id: CollectionId,1053 token_id: Option<TokenId>,1054 mapper: Mapper,1055 ) -> Result<impl Iterator<Item = R>, DispatchError>1056 where1057 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1058 Value: Decode + Default,1059 Mapper: Fn(Key, Value) -> R,1060 {1061 let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;10621063 let properties = match token_id {1064 Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1065 None => <PalletCommon<T>>::collection_properties(collection_id),1066 };10671068 let properties = properties.into_iter().filter_map(move |(key, value)| {1069 let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;10701071 let key: Key = key.to_vec().try_into().ok()?;1072 let value: Value = value.decode().ok()?;10731074 Some(mapper(key, value))1075 });10761077 Ok(properties)1078 }10791080 fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1081 map_unique_err_to_proxy! {1082 match err {1083 CommonError::NoPermission => NoPermission,1084 CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1085 CommonError::PublicMintingNotAllowed => NoPermission,1086 CommonError::TokenNotFound => NoAvailableNftId,1087 CommonError::ApprovedValueTooLow => NoPermission,1088 StructureError::TokenNotFound => NoAvailableNftId,1089 StructureError::OuroborosDetected => CannotSendToDescendentOrSelf1090 }1091 }1092 }1093}1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![cfg_attr(not(feature = "std"), no_std)]1819use frame_support::{pallet_prelude::*, transactional, BoundedVec, dispatch::DispatchResult};20use frame_system::{pallet_prelude::*, ensure_signed};21use sp_runtime::{DispatchError, Permill, traits::StaticLookup};22use sp_std::vec::Vec;23use up_data_structs::{*, mapping::TokenAddressMapping};24use pallet_common::{25 Pallet as PalletCommon, Error as CommonError, CollectionHandle, CommonCollectionOperations,26};27use pallet_nonfungible::{Pallet as PalletNft, NonfungibleHandle, TokenData};28use pallet_structure::{Pallet as PalletStructure, Error as StructureError};29use pallet_evm::account::CrossAccountId;30use core::convert::AsRef;3132pub use pallet::*;3334pub mod misc;35pub mod property;3637use misc::*;38pub use property::*;3940use RmrkProperty::*;4142const NESTING_BUDGET: u32 = 5;4344#[frame_support::pallet]45pub mod pallet {46 use super::*;47 use pallet_evm::account;4849 #[pallet::config]50 pub trait Config:51 frame_system::Config + pallet_common::Config + pallet_nonfungible::Config + account::Config52 {53 type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;54 }5556 #[pallet::storage]57 #[pallet::getter(fn collection_index)]58 pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;5960 #[pallet::storage]61 pub type UniqueCollectionId<T: Config> =62 StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;6364 #[pallet::storage]65 pub type RmrkInernalCollectionId<T: Config> =66 StorageMap<_, Twox64Concat, CollectionId, RmrkCollectionId, ValueQuery>;6768 #[pallet::pallet]69 #[pallet::generate_store(pub(super) trait Store)]70 pub struct Pallet<T>(_);7172 #[pallet::event]73 #[pallet::generate_deposit(pub(super) fn deposit_event)]74 pub enum Event<T: Config> {75 CollectionCreated {76 issuer: T::AccountId,77 collection_id: RmrkCollectionId,78 },79 CollectionDestroyed {80 issuer: T::AccountId,81 collection_id: RmrkCollectionId,82 },83 IssuerChanged {84 old_issuer: T::AccountId,85 new_issuer: T::AccountId,86 collection_id: RmrkCollectionId,87 },88 CollectionLocked {89 issuer: T::AccountId,90 collection_id: RmrkCollectionId,91 },92 NftMinted {93 owner: T::AccountId,94 collection_id: RmrkCollectionId,95 nft_id: RmrkNftId,96 },97 NFTBurned {98 owner: T::AccountId,99 nft_id: RmrkNftId,100 },101 PropertySet {102 collection_id: RmrkCollectionId,103 maybe_nft_id: Option<RmrkNftId>,104 key: RmrkKeyString,105 value: RmrkValueString,106 },107 ResourceAdded {108 nft_id: RmrkNftId,109 resource_id: RmrkResourceId,110 },111 ResourceRemoval {112 nft_id: RmrkNftId,113 resource_id: RmrkResourceId,114 },115 }116117 #[pallet::error]118 pub enum Error<T> {119 /* Unique-specific events */120 CorruptedCollectionType,121 NftTypeEncodeError,122 RmrkPropertyKeyIsTooLong,123 RmrkPropertyValueIsTooLong,124125 /* RMRK compatible events */126 CollectionNotEmpty,127 NoAvailableCollectionId,128 NoAvailableNftId,129 CollectionUnknown,130 NoPermission,131 NonTransferable,132 CollectionFullOrLocked,133 ResourceDoesntExist,134 CannotSendToDescendentOrSelf,135 CannotAcceptNonOwnedNft,136 }137138 #[pallet::call]139 impl<T: Config> Pallet<T> {140 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]141 #[transactional]142 pub fn create_collection(143 origin: OriginFor<T>,144 metadata: RmrkString,145 max: Option<u32>,146 symbol: RmrkCollectionSymbol,147 ) -> DispatchResult {148 let sender = ensure_signed(origin)?;149150 let limits = CollectionLimits {151 owner_can_transfer: Some(false),152 token_limit: max,153 ..Default::default()154 };155156 let data = CreateCollectionData {157 limits: Some(limits),158 token_prefix: symbol159 .into_inner()160 .try_into()161 .map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,162 permissions: Some(CollectionPermissions {163 nesting: Some(NestingRule::Owner),164 ..Default::default()165 }),166 ..Default::default()167 };168169 let unique_collection_id = Self::init_collection(170 T::CrossAccountId::from_sub(sender.clone()),171 data,172 [173 Self::rmrk_property(Metadata, &metadata)?,174 Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,175 ]176 .into_iter(),177 )?;178 let rmrk_collection_id = <CollectionIndex<T>>::get();179180 <UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);181 <RmrkInernalCollectionId<T>>::insert(unique_collection_id, rmrk_collection_id);182183 <CollectionIndex<T>>::mutate(|n| *n += 1);184185 Self::deposit_event(Event::CollectionCreated {186 issuer: sender,187 collection_id: rmrk_collection_id,188 });189190 Ok(())191 }192193 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]194 #[transactional]195 pub fn destroy_collection(196 origin: OriginFor<T>,197 collection_id: RmrkCollectionId,198 ) -> DispatchResult {199 let sender = ensure_signed(origin)?;200 let cross_sender = T::CrossAccountId::from_sub(sender.clone());201202 let collection = Self::get_typed_nft_collection(203 Self::unique_collection_id(collection_id)?,204 misc::CollectionType::Regular,205 )?;206207 ensure!(208 collection.total_supply() == 0,209 <Error<T>>::CollectionNotEmpty210 );211212 <PalletNft<T>>::destroy_collection(collection, &cross_sender)213 .map_err(Self::map_unique_err_to_proxy)?;214215 Self::deposit_event(Event::CollectionDestroyed {216 issuer: sender,217 collection_id,218 });219220 Ok(())221 }222223 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]224 #[transactional]225 pub fn change_collection_issuer(226 origin: OriginFor<T>,227 collection_id: RmrkCollectionId,228 new_issuer: <T::Lookup as StaticLookup>::Source,229 ) -> DispatchResult {230 let sender = ensure_signed(origin)?;231232 let new_issuer = T::Lookup::lookup(new_issuer)?;233234 Self::change_collection_owner(235 Self::unique_collection_id(collection_id)?,236 misc::CollectionType::Regular,237 sender.clone(),238 new_issuer.clone(),239 )?;240241 Self::deposit_event(Event::IssuerChanged {242 old_issuer: sender,243 new_issuer,244 collection_id,245 });246247 Ok(())248 }249250 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]251 #[transactional]252 pub fn lock_collection(253 origin: OriginFor<T>,254 collection_id: RmrkCollectionId,255 ) -> DispatchResult {256 let sender = ensure_signed(origin)?;257 let cross_sender = T::CrossAccountId::from_sub(sender.clone());258259 let collection = Self::get_typed_nft_collection(260 Self::unique_collection_id(collection_id)?,261 misc::CollectionType::Regular,262 )?;263264 Self::check_collection_owner(&collection, &cross_sender)?;265266 let token_count = collection.total_supply();267268 let mut collection = collection.into_inner();269 collection.limits.token_limit = Some(token_count);270 collection.save()?;271272 Self::deposit_event(Event::CollectionLocked {273 issuer: sender,274 collection_id,275 });276277 Ok(())278 }279280 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]281 #[transactional]282 pub fn mint_nft(283 origin: OriginFor<T>,284 owner: T::AccountId,285 collection_id: RmrkCollectionId,286 recipient: Option<T::AccountId>,287 royalty_amount: Option<Permill>,288 metadata: RmrkString,289 transferable: bool,290 ) -> DispatchResult {291 let sender = ensure_signed(origin)?;292 let sender = T::CrossAccountId::from_sub(sender);293 let cross_owner = T::CrossAccountId::from_sub(owner.clone());294295 let royalty_info = royalty_amount.map(|amount| rmrk::RoyaltyInfo {296 recipient: recipient.unwrap_or_else(|| owner.clone()),297 amount,298 });299300 let collection = Self::get_typed_nft_collection(301 Self::unique_collection_id(collection_id)?,302 misc::CollectionType::Regular,303 )?;304305 let nft_id = Self::create_nft(306 &sender,307 &cross_owner,308 &collection,309 [310 Self::rmrk_property(TokenType, &NftType::Regular)?,311 Self::rmrk_property(Transferable, &transferable)?,312 Self::rmrk_property(PendingNftAccept, &false)?,313 Self::rmrk_property(RoyaltyInfo, &royalty_info)?,314 Self::rmrk_property(Metadata, &metadata)?,315 Self::rmrk_property(Equipped, &false)?,316 Self::rmrk_property(317 ResourceCollection,318 &Self::init_collection(319 sender.clone(),320 CreateCollectionData {321 ..Default::default()322 },323 [Self::rmrk_property(324 CollectionType,325 &misc::CollectionType::Resource,326 )?]327 .into_iter(),328 )?,329 )?, // todo possibly add limits to the collection if rmrk warrants them330 Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,331 ]332 .into_iter(),333 )334 .map_err(|err| match err {335 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),336 err => Self::map_unique_err_to_proxy(err),337 })?;338339 Self::deposit_event(Event::NftMinted {340 owner,341 collection_id,342 nft_id: nft_id.0,343 });344345 Ok(())346 }347348 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]349 #[transactional]350 pub fn burn_nft(351 origin: OriginFor<T>,352 collection_id: RmrkCollectionId,353 nft_id: RmrkNftId,354 ) -> DispatchResult {355 let sender = ensure_signed(origin)?;356 let cross_sender = T::CrossAccountId::from_sub(sender.clone());357358 Self::destroy_nft(359 cross_sender,360 Self::unique_collection_id(collection_id)?,361 misc::CollectionType::Regular,362 nft_id.into(),363 )?;364365 Self::deposit_event(Event::NFTBurned {366 owner: sender,367 nft_id,368 });369370 Ok(())371 }372373 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]374 #[transactional]375 pub fn send(376 origin: OriginFor<T>,377 rmrk_collection_id: RmrkCollectionId,378 rmrk_nft_id: RmrkNftId,379 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,380 ) -> DispatchResult {381 let sender = ensure_signed(origin.clone())?;382 let cross_sender = T::CrossAccountId::from_sub(sender);383384 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;385 let nft_id = rmrk_nft_id.into();386387 let token_data = <TokenData<T>>::get((collection_id, nft_id))388 .ok_or(<Error<T>>::NoAvailableNftId)?;389390 let from = token_data.owner;391392 let collection = Self::get_typed_nft_collection(393 collection_id,394 misc::CollectionType::Regular,395 )?;396397 if !Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)? {398 return Err(<Error<T>>::NonTransferable.into());399 }400401 if Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::PendingNftAccept)? {402 return Err(<Error<T>>::NoPermission.into());403 }404405 let target_owner;406407 match new_owner {408 RmrkAccountIdOrCollectionNftTuple::AccountId(account_id) => {409 target_owner = T::CrossAccountId::from_sub(account_id);410 },411 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(target_collection_id, target_nft_id) => {412 let target_collection_id = Self::unique_collection_id(target_collection_id)?;413414 let target_nft_budget = budget::Value::new(NESTING_BUDGET);415416 let target_nft_owner = <PalletStructure<T>>::get_checked_indirect_owner(417 target_collection_id,418 target_nft_id.into(),419 Some((collection_id, nft_id)),420 &target_nft_budget,421 ).map_err(Self::map_unique_err_to_proxy)?;422423 let is_approval_required = cross_sender != target_nft_owner;424425 if is_approval_required {426 target_owner = target_nft_owner;427428 <PalletNft<T>>::set_scoped_token_property(429 collection.id,430 nft_id,431 PropertyScope::Rmrk,432 Self::rmrk_property(PendingNftAccept, &is_approval_required)?,433 )?;434 } else {435 target_owner = T::CrossTokenAddressMapping::token_to_address(436 target_collection_id,437 target_nft_id.into(),438 );439 }440 }441 }442443 let src_nft_budget = budget::Value::new(NESTING_BUDGET);444445 <PalletNft<T>>::transfer_from(446 &collection,447 &cross_sender,448 &from,449 &target_owner,450 nft_id,451 &src_nft_budget452 ).map_err(Self::map_unique_err_to_proxy)?;453454 Ok(())455 }456457 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]458 #[transactional]459 pub fn accept_nft(460 origin: OriginFor<T>,461 rmrk_collection_id: RmrkCollectionId,462 rmrk_nft_id: RmrkNftId,463 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,464 ) -> DispatchResult {465 let sender = ensure_signed(origin.clone())?;466 let cross_sender = T::CrossAccountId::from_sub(sender);467468 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;469 let nft_id = rmrk_nft_id.into();470471 let collection = Self::get_typed_nft_collection(472 collection_id,473 misc::CollectionType::Regular,474 )?;475476 let new_owner = match new_owner {477 RmrkAccountIdOrCollectionNftTuple::AccountId(account_id) => {478 T::CrossAccountId::from_sub(account_id)479 },480 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(target_collection_id, target_nft_id) => {481 let target_collection_id = Self::unique_collection_id(target_collection_id)?;482483 T::CrossTokenAddressMapping::token_to_address(target_collection_id, TokenId(target_nft_id))484 }485 };486487 let budget = budget::Value::new(NESTING_BUDGET);488489 <PalletNft<T>>::transfer(490 &collection,491 &cross_sender,492 &new_owner,493 nft_id,494 &budget,495 ).map_err(|err| {496 if err == <CommonError<T>>::OnlyOwnerAllowedToNest.into() {497 <Error<T>>::CannotAcceptNonOwnedNft.into()498 } else {499 Self::map_unique_err_to_proxy(err)500 }501 })?;502503 <PalletNft<T>>::set_scoped_token_property(504 collection.id,505 nft_id,506 PropertyScope::Rmrk,507 Self::rmrk_property(PendingNftAccept, &false)?,508 )?;509510 Ok(())511 }512513 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]514 #[transactional]515 pub fn set_property(516 origin: OriginFor<T>,517 #[pallet::compact] rmrk_collection_id: RmrkCollectionId,518 maybe_nft_id: Option<RmrkNftId>,519 key: RmrkKeyString,520 value: RmrkValueString,521 ) -> DispatchResult {522 let sender = ensure_signed(origin)?;523 let sender = T::CrossAccountId::from_sub(sender);524525 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;526 let budget = budget::Value::new(NESTING_BUDGET);527528 match maybe_nft_id {529 Some(nft_id) => {530 let token_id: TokenId = nft_id.into();531532 Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;533 Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;534535 <PalletNft<T>>::set_scoped_token_property(536 collection_id,537 token_id,538 PropertyScope::Rmrk,539 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,540 )?;541 }542 None => {543 let collection = Self::get_typed_nft_collection(544 collection_id,545 misc::CollectionType::Regular,546 )?;547548 Self::check_collection_owner(&collection, &sender)?;549550 <PalletCommon<T>>::set_scoped_collection_property(551 collection_id,552 PropertyScope::Rmrk,553 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,554 )?;555 }556 }557558 Self::deposit_event(Event::PropertySet {559 collection_id: rmrk_collection_id,560 maybe_nft_id,561 key,562 value,563 });564565 Ok(())566 }567568 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]569 #[transactional]570 pub fn add_basic_resource(571 origin: OriginFor<T>,572 collection_id: RmrkCollectionId,573 nft_id: RmrkNftId,574 resource: RmrkBasicResource,575 ) -> DispatchResult {576 let sender = ensure_signed(origin.clone())?;577578 let resource_id = Self::resource_add(579 sender,580 Self::unique_collection_id(collection_id)?,581 nft_id.into(),582 [583 Self::rmrk_property(TokenType, &NftType::Resource)?,584 Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,585 Self::rmrk_property(Src, &resource.src)?,586 Self::rmrk_property(Metadata, &resource.metadata)?,587 Self::rmrk_property(License, &resource.license)?,588 Self::rmrk_property(Thumb, &resource.thumb)?,589 ]590 .into_iter(),591 )?;592593 Self::deposit_event(Event::ResourceAdded {594 nft_id,595 resource_id,596 });597 Ok(())598 }599600 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]601 #[transactional]602 pub fn add_composable_resource(603 origin: OriginFor<T>,604 collection_id: RmrkCollectionId,605 nft_id: RmrkNftId,606 _resource_id: RmrkBoundedResource,607 resource: RmrkComposableResource,608 ) -> DispatchResult {609 let sender = ensure_signed(origin.clone())?;610611 let resource_id = Self::resource_add(612 sender,613 Self::unique_collection_id(collection_id)?,614 nft_id.into(),615 [616 Self::rmrk_property(TokenType, &NftType::Resource)?,617 Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,618 Self::rmrk_property(Parts, &resource.parts)?,619 Self::rmrk_property(Base, &resource.base)?,620 Self::rmrk_property(Src, &resource.src)?,621 Self::rmrk_property(Metadata, &resource.metadata)?,622 Self::rmrk_property(License, &resource.license)?,623 Self::rmrk_property(Thumb, &resource.thumb)?,624 ]625 .into_iter(),626 )?;627628 Self::deposit_event(Event::ResourceAdded {629 nft_id,630 resource_id,631 });632 Ok(())633 }634635 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]636 #[transactional]637 pub fn add_slot_resource(638 origin: OriginFor<T>,639 collection_id: RmrkCollectionId,640 nft_id: RmrkNftId,641 resource: RmrkSlotResource,642 ) -> DispatchResult {643 let sender = ensure_signed(origin.clone())?;644645 let resource_id = Self::resource_add(646 sender,647 Self::unique_collection_id(collection_id)?,648 nft_id.into(),649 [650 Self::rmrk_property(TokenType, &NftType::Resource)?,651 Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,652 Self::rmrk_property(Base, &resource.base)?,653 Self::rmrk_property(Src, &resource.src)?,654 Self::rmrk_property(Metadata, &resource.metadata)?,655 Self::rmrk_property(Slot, &resource.slot)?,656 Self::rmrk_property(License, &resource.license)?,657 Self::rmrk_property(Thumb, &resource.thumb)?,658 ]659 .into_iter(),660 )?;661662 Self::deposit_event(Event::ResourceAdded {663 nft_id,664 resource_id,665 });666 Ok(())667 }668669 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]670 #[transactional]671 pub fn remove_resource(672 origin: OriginFor<T>,673 collection_id: RmrkCollectionId,674 nft_id: RmrkNftId,675 resource_id: RmrkResourceId,676 ) -> DispatchResult {677 let sender = ensure_signed(origin.clone())?;678679 Self::resource_remove(680 sender,681 Self::unique_collection_id(collection_id)?,682 nft_id.into(),683 resource_id.into(),684 )?;685686 Self::deposit_event(Event::ResourceRemoval {687 nft_id,688 resource_id,689 });690 Ok(())691 }692 }693}694695impl<T: Config> Pallet<T> {696 pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {697 let key = rmrk_key.to_key::<T>()?;698699 let scoped_key = PropertyScope::Rmrk700 .apply(key)701 .map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;702703 Ok(scoped_key)704 }705706 // todo think about renaming these707 pub fn rmrk_property<E: Encode>(708 rmrk_key: RmrkProperty,709 value: &E,710 ) -> Result<Property, DispatchError> {711 let key = rmrk_key.to_key::<T>()?;712713 let value = value714 .encode()715 .try_into()716 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;717718 let property = Property { key, value };719720 Ok(property)721 }722723 pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {724 vec.decode()725 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())726 }727728 pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>729 where730 BoundedVec<u8, S>: TryFrom<Vec<u8>>,731 {732 vec.rebind()733 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())734 }735736 fn init_collection(737 sender: T::CrossAccountId,738 data: CreateCollectionData<T::AccountId>,739 properties: impl Iterator<Item = Property>,740 ) -> Result<CollectionId, DispatchError> {741 let collection_id = <PalletNft<T>>::init_collection(sender, data);742743 if let Err(DispatchError::Arithmetic(_)) = &collection_id {744 return Err(<Error<T>>::NoAvailableCollectionId.into());745 }746747 <PalletCommon<T>>::set_scoped_collection_properties(748 collection_id?,749 PropertyScope::Rmrk,750 properties,751 )?;752753 collection_id754 }755756 pub fn create_nft(757 sender: &T::CrossAccountId,758 owner: &T::CrossAccountId,759 collection: &NonfungibleHandle<T>,760 properties: impl Iterator<Item = Property>,761 ) -> Result<TokenId, DispatchError> {762 let data = CreateNftExData {763 properties: BoundedVec::default(),764 owner: owner.clone(),765 };766767 let budget = budget::Value::new(NESTING_BUDGET);768769 <PalletNft<T>>::create_item(collection, sender, data, &budget)?;770771 let nft_id = <PalletNft<T>>::current_token_id(collection.id);772773 <PalletNft<T>>::set_scoped_token_properties(774 collection.id,775 nft_id,776 PropertyScope::Rmrk,777 properties,778 )?;779780 Ok(nft_id)781 }782783 fn destroy_nft(784 sender: T::CrossAccountId,785 collection_id: CollectionId,786 collection_type: misc::CollectionType,787 token_id: TokenId,788 ) -> DispatchResult {789 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;790791 <PalletNft<T>>::burn(&collection, &sender, token_id)792 .map_err(Self::map_unique_err_to_proxy)?;793794 Ok(())795 }796797 fn resource_add(798 sender: T::AccountId,799 collection_id: CollectionId,800 token_id: TokenId,801 resource_properties: impl Iterator<Item = Property>,802 ) -> Result<RmrkResourceId, DispatchError> {803 let collection =804 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;805 ensure!(collection.owner == sender, Error::<T>::NoPermission);806807 // Check NFT lock status // todo depends on market, maybe later808 //ensure!(!Pallet::<T>::is_locked(collection_id, nft_id), pallet_uniques::Error::<T>::Locked);809810 let sender = T::CrossAccountId::from_sub(sender);811 let budget = budget::Value::new(NESTING_BUDGET);812 let pending = Self::ensure_nft_owner(collection_id, token_id, &sender, &budget).is_err();813814 let resource_collection_id: CollectionId =815 Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;816 let resource_collection =817 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;818819 // todo probably add extra connections to bases, slots, etc., when RMRK starts to use them820821 let resource_id = Self::create_nft(822 &sender, // todo owner of the nft?823 &sender,824 &resource_collection,825 resource_properties.chain(826 [827 Self::rmrk_property(PendingResourceAccept, &pending)?,828 Self::rmrk_property(PendingResourceRemoval, &false)?,829 ]830 .into_iter(),831 ),832 )833 .map_err(|err| match err {834 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),835 err => Self::map_unique_err_to_proxy(err),836 })?;837838 Ok(resource_id.0)839 }840841 fn resource_remove(842 sender: T::AccountId,843 collection_id: CollectionId,844 nft_id: TokenId,845 resource_id: TokenId,846 ) -> DispatchResult {847 let collection =848 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;849 ensure!(collection.owner == sender, Error::<T>::NoPermission);850851 let resource_collection_id: CollectionId =852 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;853 let resource_collection =854 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;855 ensure!(856 <PalletNft<T>>::token_exists(&resource_collection, resource_id),857 Error::<T>::ResourceDoesntExist858 );859860 let budget = up_data_structs::budget::Value::new(10);861 let topmost_owner =862 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;863864 let sender = T::CrossAccountId::from_sub(sender);865 if topmost_owner == sender {866 <PalletNft<T>>::burn(&resource_collection, &sender, resource_id)867 .map_err(Self::map_unique_err_to_proxy)?;868 } else {869 <PalletNft<T>>::set_scoped_token_property(870 resource_collection_id,871 resource_id,872 PropertyScope::Rmrk,873 Self::rmrk_property(PendingResourceRemoval, &true)?,874 )?;875 }876877 Ok(())878 }879880 fn change_collection_owner(881 collection_id: CollectionId,882 collection_type: misc::CollectionType,883 sender: T::AccountId,884 new_owner: T::AccountId,885 ) -> DispatchResult {886 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;887 Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;888889 let mut collection = collection.into_inner();890891 collection.owner = new_owner;892 collection.save()893 }894895 fn check_collection_owner(896 collection: &NonfungibleHandle<T>,897 account: &T::CrossAccountId,898 ) -> DispatchResult {899 collection900 .check_is_owner(account)901 .map_err(Self::map_unique_err_to_proxy)902 }903904 pub fn last_collection_idx() -> RmrkCollectionId {905 <CollectionIndex<T>>::get()906 }907908 pub fn unique_collection_id(909 rmrk_collection_id: RmrkCollectionId,910 ) -> Result<CollectionId, DispatchError> {911 <UniqueCollectionId<T>>::try_get(rmrk_collection_id)912 .map_err(|_| <Error<T>>::CollectionUnknown.into())913 }914915 pub fn rmrk_collection_id(916 unique_collection_id: CollectionId917 ) -> Result<RmrkCollectionId, DispatchError> {918 <RmrkInernalCollectionId<T>>::try_get(unique_collection_id)919 .map_err(|_| <Error<T>>::CollectionUnknown.into())920 }921922 pub fn get_nft_collection(923 collection_id: CollectionId,924 ) -> Result<NonfungibleHandle<T>, DispatchError> {925 let collection = <CollectionHandle<T>>::try_get(collection_id)926 .map_err(|_| <Error<T>>::CollectionUnknown)?;927928 match collection.mode {929 CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),930 _ => Err(<Error<T>>::CollectionUnknown.into()),931 }932 }933934 pub fn collection_exists(collection_id: CollectionId) -> bool {935 <CollectionHandle<T>>::try_get(collection_id).is_ok()936 }937938 pub fn get_collection_property(939 collection_id: CollectionId,940 key: RmrkProperty,941 ) -> Result<PropertyValue, DispatchError> {942 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)943 .get(&Self::rmrk_property_key(key)?)944 .ok_or(<Error<T>>::CollectionUnknown)?945 .clone();946947 Ok(collection_property)948 }949950 pub fn get_collection_property_decoded<V: Decode>(951 collection_id: CollectionId,952 key: RmrkProperty,953 ) -> Result<V, DispatchError> {954 Self::decode_property(Self::get_collection_property(collection_id, key)?)955 }956957 pub fn get_collection_type(958 collection_id: CollectionId,959 ) -> Result<misc::CollectionType, DispatchError> {960 Self::get_collection_property_decoded(collection_id, CollectionType)961 .map_err(|_| <Error<T>>::CorruptedCollectionType.into())962 }963964 pub fn ensure_collection_type(965 collection_id: CollectionId,966 collection_type: misc::CollectionType,967 ) -> DispatchResult {968 let actual_type = Self::get_collection_type(collection_id)?;969 ensure!(970 actual_type == collection_type,971 <CommonError<T>>::NoPermission972 );973974 Ok(())975 }976977 pub fn get_typed_nft_collection(978 collection_id: CollectionId,979 collection_type: misc::CollectionType,980 ) -> Result<NonfungibleHandle<T>, DispatchError> {981 Self::ensure_collection_type(collection_id, collection_type)?;982983 Self::get_nft_collection(collection_id)984 }985986 pub fn get_typed_nft_collection_mapped(987 rmrk_collection_id: RmrkCollectionId,988 collection_type: misc::CollectionType,989 ) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {990 let unique_collection_id = Self::unique_collection_id(rmrk_collection_id)?;991992 let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;993994 Ok((collection, unique_collection_id))995 }996997 pub fn get_nft_property(998 collection_id: CollectionId,999 nft_id: TokenId,1000 key: RmrkProperty,1001 ) -> Result<PropertyValue, DispatchError> {1002 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1003 .get(&Self::rmrk_property_key(key)?)1004 .ok_or(<Error<T>>::NoAvailableNftId)? // todo replace with better error?1005 .clone();10061007 Ok(nft_property)1008 }10091010 pub fn get_nft_property_decoded<V: Decode>(1011 collection_id: CollectionId,1012 nft_id: TokenId,1013 key: RmrkProperty,1014 ) -> Result<V, DispatchError> {1015 Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)1016 }10171018 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1019 <TokenData<T>>::contains_key((collection_id, nft_id))1020 }10211022 pub fn get_nft_type(1023 collection_id: CollectionId,1024 token_id: TokenId,1025 ) -> Result<NftType, DispatchError> {1026 Self::get_nft_property_decoded(collection_id, token_id, TokenType)1027 .map_err(|_| <Error<T>>::NftTypeEncodeError.into())1028 }10291030 pub fn ensure_nft_type(1031 collection_id: CollectionId,1032 token_id: TokenId,1033 nft_type: NftType,1034 ) -> DispatchResult {1035 let actual_type = Self::get_nft_type(collection_id, token_id)?;1036 ensure!(actual_type == nft_type, <Error<T>>::NoPermission);10371038 Ok(())1039 }10401041 pub fn ensure_nft_owner(1042 collection_id: CollectionId,1043 token_id: TokenId,1044 possible_owner: &T::CrossAccountId,1045 nesting_budget: &dyn budget::Budget1046 ) -> DispatchResult {1047 let is_owned = <PalletStructure<T>>::check_indirectly_owned(1048 possible_owner.clone(),1049 collection_id,1050 token_id,1051 None,1052 nesting_budget,1053 )?;10541055 ensure!(1056 is_owned,1057 <Error<T>>::NoPermission1058 );10591060 Ok(())1061 }10621063 pub fn filter_user_properties<Key, Value, R, Mapper>(1064 collection_id: CollectionId,1065 token_id: Option<TokenId>,1066 filter_keys: Option<Vec<RmrkPropertyKey>>,1067 mapper: Mapper,1068 ) -> Result<Vec<R>, DispatchError>1069 where1070 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1071 Value: Decode + Default,1072 Mapper: Fn(Key, Value) -> R,1073 {1074 filter_keys1075 .map(|keys| {1076 let properties = keys1077 .into_iter()1078 .filter_map(|key| {1079 let key: Key = key.try_into().ok()?;10801081 let value = match token_id {1082 Some(token_id) => Self::get_nft_property_decoded(1083 collection_id,1084 token_id,1085 UserProperty(key.as_ref()),1086 ),1087 None => Self::get_collection_property_decoded(1088 collection_id,1089 UserProperty(key.as_ref()),1090 ),1091 }1092 .ok()?;10931094 Some(mapper(key, value))1095 })1096 .collect();10971098 Ok(properties)1099 })1100 .unwrap_or_else(|| {1101 let properties =1102 Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();11031104 Ok(properties)1105 })1106 }11071108 pub fn iterate_user_properties<Key, Value, R, Mapper>(1109 collection_id: CollectionId,1110 token_id: Option<TokenId>,1111 mapper: Mapper,1112 ) -> Result<impl Iterator<Item = R>, DispatchError>1113 where1114 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1115 Value: Decode + Default,1116 Mapper: Fn(Key, Value) -> R,1117 {1118 let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;11191120 let properties = match token_id {1121 Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1122 None => <PalletCommon<T>>::collection_properties(collection_id),1123 };11241125 let properties = properties.into_iter().filter_map(move |(key, value)| {1126 let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;11271128 let key: Key = key.to_vec().try_into().ok()?;1129 let value: Value = value.decode().ok()?;11301131 Some(mapper(key, value))1132 });11331134 Ok(properties)1135 }11361137 fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1138 map_unique_err_to_proxy! {1139 match err {1140 CommonError::NoPermission => NoPermission,1141 CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1142 CommonError::PublicMintingNotAllowed => NoPermission,1143 CommonError::TokenNotFound => NoAvailableNftId,1144 CommonError::ApprovedValueTooLow => NoPermission,1145 StructureError::TokenNotFound => NoAvailableNftId,1146 StructureError::OuroborosDetected => CannotSendToDescendentOrSelf1147 }1148 }1149 }1150}