git.delta.rocks / unique-network / refs/commits / 5d01a90d47f4

difftreelog

source

pallets/proxy-rmrk-core/src/lib.rs17.3 KiBsourcehistory
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::*;24use pallet_common::{25	Pallet as PalletCommon, Error as CommonError, CollectionHandle, CommonCollectionOperations,26};27use pallet_nonfungible::{Pallet as PalletNft, NonfungibleHandle, TokenData};28use pallet_evm::account::CrossAccountId;29use core::convert::AsRef;3031pub use pallet::*;3233pub mod misc;34pub mod property;3536use misc::*;37pub use property::*;3839use RmrkProperty::*;4041#[frame_support::pallet]42pub mod pallet {43	use super::*;44	use pallet_evm::account;4546	#[pallet::config]47	pub trait Config:48		frame_system::Config + pallet_common::Config + pallet_nonfungible::Config + account::Config49	{50		type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;51	}5253	#[pallet::storage]54	#[pallet::getter(fn collection_index)]55	pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;5657	#[pallet::pallet]58	#[pallet::generate_store(pub(super) trait Store)]59	pub struct Pallet<T>(_);6061	#[pallet::event]62	#[pallet::generate_deposit(pub(super) fn deposit_event)]63	pub enum Event<T: Config> {64		CollectionCreated {65			issuer: T::AccountId,66			collection_id: RmrkCollectionId,67		},68		CollectionDestroyed {69			issuer: T::AccountId,70			collection_id: RmrkCollectionId,71		},72		IssuerChanged {73			old_issuer: T::AccountId,74			new_issuer: T::AccountId,75			collection_id: RmrkCollectionId,76		},77		CollectionLocked {78			issuer: T::AccountId,79			collection_id: RmrkCollectionId,80		},81		NftMinted {82			owner: T::AccountId,83			collection_id: RmrkCollectionId,84			nft_id: RmrkNftId,85		},86		NFTBurned {87			owner: T::AccountId,88			nft_id: RmrkNftId,89		},90		PropertySet {91			collection_id: RmrkCollectionId,92			maybe_nft_id: Option<RmrkNftId>,93			key: RmrkKeyString,94			value: RmrkValueString,95		},96	}9798	#[pallet::error]99	pub enum Error<T> {100		/* Unique-specific events */101		CorruptedCollectionType,102		NftTypeEncodeError,103		RmrkPropertyKeyIsTooLong,104		RmrkPropertyValueIsTooLong,105106		/* RMRK compatible events */107		CollectionNotEmpty,108		NoAvailableCollectionId,109		NoAvailableNftId,110		CollectionUnknown,111		NoPermission,112		CollectionFullOrLocked,113	}114115	#[pallet::call]116	impl<T: Config> Pallet<T> {117		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]118		#[transactional]119		pub fn create_collection(120			origin: OriginFor<T>,121			metadata: RmrkString,122			max: Option<u32>,123			symbol: RmrkCollectionSymbol,124		) -> DispatchResult {125			let sender = ensure_signed(origin)?;126127			let limits = CollectionLimits {128				owner_can_transfer: Some(false),129				token_limit: max,130				..Default::default()131			};132133			let data = CreateCollectionData {134				limits: Some(limits),135				token_prefix: symbol136					.into_inner()137					.try_into()138					.map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,139				..Default::default()140			};141142			let collection_id_res =143				<PalletNft<T>>::init_collection(T::CrossAccountId::from_sub(sender.clone()), data);144145			if let Err(DispatchError::Arithmetic(_)) = &collection_id_res {146				return Err(<Error<T>>::NoAvailableCollectionId.into());147			}148149			let collection_id = collection_id_res?;150151			<PalletCommon<T>>::set_scoped_collection_properties(152				collection_id,153				PropertyScope::Rmrk,154				[155					Self::rmrk_property(Metadata, &metadata)?,156					Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,157				]158				.into_iter(),159			)?;160161			<CollectionIndex<T>>::mutate(|n| *n += 1);162163			Self::deposit_event(Event::CollectionCreated {164				issuer: sender,165				collection_id: collection_id.0,166			});167168			Ok(())169		}170171		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]172		#[transactional]173		pub fn destroy_collection(174			origin: OriginFor<T>,175			collection_id: RmrkCollectionId,176		) -> DispatchResult {177			let sender = ensure_signed(origin)?;178			let cross_sender = T::CrossAccountId::from_sub(sender.clone());179180			let unique_collection_id = collection_id.into();181182			let collection = Self::get_typed_nft_collection(183				unique_collection_id,184				misc::CollectionType::Regular,185			)?;186187			ensure!(188				collection.total_supply() == 0,189				<Error<T>>::CollectionNotEmpty190			);191192			<PalletNft<T>>::destroy_collection(collection, &cross_sender)193				.map_err(Self::map_common_err_to_proxy)?;194195			Self::deposit_event(Event::CollectionDestroyed {196				issuer: sender,197				collection_id,198			});199200			Ok(())201		}202203		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]204		#[transactional]205		pub fn change_collection_issuer(206			origin: OriginFor<T>,207			collection_id: RmrkCollectionId,208			new_issuer: <T::Lookup as StaticLookup>::Source,209		) -> DispatchResult {210			let sender = ensure_signed(origin)?;211212			let new_issuer = T::Lookup::lookup(new_issuer)?;213214			Self::change_collection_owner(215				collection_id.into(),216				misc::CollectionType::Regular,217				sender.clone(),218				new_issuer.clone(),219			)?;220221			Self::deposit_event(Event::IssuerChanged {222				old_issuer: sender,223				new_issuer,224				collection_id,225			});226227			Ok(())228		}229230		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]231		#[transactional]232		pub fn lock_collection(233			origin: OriginFor<T>,234			collection_id: RmrkCollectionId,235		) -> DispatchResult {236			let sender = ensure_signed(origin)?;237			let cross_sender = T::CrossAccountId::from_sub(sender.clone());238239			let collection = Self::get_typed_nft_collection(240				collection_id.into(),241				misc::CollectionType::Regular,242			)?;243244			Self::check_collection_owner(&collection, &cross_sender)?;245246			let token_count = collection.total_supply();247248			let mut collection = collection.into_inner();249			collection.limits.token_limit = Some(token_count);250			collection.save()?;251252			Self::deposit_event(Event::CollectionLocked {253				issuer: sender,254				collection_id,255			});256257			Ok(())258		}259260		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]261		#[transactional]262		pub fn mint_nft(263			origin: OriginFor<T>,264			owner: T::AccountId,265			collection_id: RmrkCollectionId,266			recipient: Option<T::AccountId>,267			royalty_amount: Option<Permill>,268			metadata: RmrkString,269		) -> DispatchResult {270			let sender = ensure_signed(origin)?;271			let sender = T::CrossAccountId::from_sub(sender);272			let cross_owner = T::CrossAccountId::from_sub(owner.clone());273274			let royalty_info = royalty_amount.map(|amount| rmrk::RoyaltyInfo {275				recipient: recipient.unwrap_or_else(|| owner.clone()),276				amount,277			});278279			let collection = Self::get_typed_nft_collection(280				collection_id.into(),281				misc::CollectionType::Regular,282			)?;283284			let nft_id = Self::create_nft(285				&sender,286				&cross_owner,287				&collection,288				NftType::Regular,289				[290					Self::rmrk_property(RoyaltyInfo, &royalty_info)?,291					Self::rmrk_property(Metadata, &metadata)?,292					Self::rmrk_property(Equipped, &false)?,293					Self::rmrk_property(ResourceCollection, &None::<CollectionId>)?,294					Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,295				]296				.into_iter(),297			)298			.map_err(|err| match err {299				DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),300				err => Self::map_common_err_to_proxy(err),301			})?;302303			Self::deposit_event(Event::NftMinted {304				owner,305				collection_id,306				nft_id: nft_id.0,307			});308309			Ok(())310		}311312		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]313		#[transactional]314		pub fn burn_nft(315			origin: OriginFor<T>,316			collection_id: RmrkCollectionId,317			nft_id: RmrkNftId,318		) -> DispatchResult {319			let sender = ensure_signed(origin)?;320			let cross_sender = T::CrossAccountId::from_sub(sender.clone());321322			Self::destroy_nft(323				cross_sender,324				collection_id.into(),325				misc::CollectionType::Regular,326				nft_id.into(),327			)?;328329			Self::deposit_event(Event::NFTBurned {330				owner: sender,331				nft_id,332			});333334			Ok(())335		}336337		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]338		#[transactional]339		pub fn set_property(340			origin: OriginFor<T>,341			#[pallet::compact] rmrk_collection_id: RmrkCollectionId,342			maybe_nft_id: Option<RmrkNftId>,343			key: RmrkKeyString,344			value: RmrkValueString,345		) -> DispatchResult {346			let sender = ensure_signed(origin)?;347			let sender = T::CrossAccountId::from_sub(sender);348349			let collection_id: CollectionId = rmrk_collection_id.into();350351			match maybe_nft_id {352				Some(nft_id) => {353					let token_id: TokenId = nft_id.into();354355					Self::ensure_nft_owner(collection_id, token_id, &sender)?;356					Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;357358					<PalletNft<T>>::set_scoped_token_property(359						collection_id,360						token_id,361						PropertyScope::Rmrk,362						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,363					)?;364				}365				None => {366					let collection = Self::get_typed_nft_collection(367						collection_id,368						misc::CollectionType::Regular,369					)?;370371					Self::check_collection_owner(&collection, &sender)?;372373					<PalletCommon<T>>::set_scoped_collection_property(374						collection_id,375						PropertyScope::Rmrk,376						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,377					)?;378				}379			}380381			Self::deposit_event(Event::PropertySet {382				collection_id: rmrk_collection_id,383				maybe_nft_id,384				key,385				value,386			});387388			Ok(())389		}390	}391}392393impl<T: Config> Pallet<T> {394	pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {395		let key = rmrk_key.to_key::<T>()?;396397		let scoped_key = PropertyScope::Rmrk398			.apply(key)399			.map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;400401		Ok(scoped_key)402	}403404	pub fn rmrk_property<E: Encode>(405		rmrk_key: RmrkProperty,406		value: &E,407	) -> Result<Property, DispatchError> {408		let key = rmrk_key.to_key::<T>()?;409410		let value = value411			.encode()412			.try_into()413			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;414415		let property = Property { key, value };416417		Ok(property)418	}419420	pub fn create_nft(421		sender: &T::CrossAccountId,422		owner: &T::CrossAccountId,423		collection: &NonfungibleHandle<T>,424		nft_type: NftType,425		properties: impl Iterator<Item = Property>,426	) -> Result<TokenId, DispatchError> {427		todo!("store nft type");428		let data = CreateNftExData {429			properties: BoundedVec::default(),430			owner: owner.clone(),431		};432433		let budget = budget::Value::new(2);434435		<PalletNft<T>>::create_item(collection, sender, data, &budget)?;436437		let nft_id = <PalletNft<T>>::current_token_id(collection.id);438439		<PalletNft<T>>::set_scoped_token_properties(440			collection.id,441			nft_id,442			PropertyScope::Rmrk,443			properties,444		)?;445446		Ok(nft_id)447	}448449	fn destroy_nft(450		sender: T::CrossAccountId,451		collection_id: CollectionId,452		collection_type: misc::CollectionType,453		token_id: TokenId,454	) -> DispatchResult {455		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;456457		<PalletNft<T>>::burn(&collection, &sender, token_id)458			.map_err(Self::map_common_err_to_proxy)?;459460		Ok(())461	}462463	fn change_collection_owner(464		collection_id: CollectionId,465		collection_type: misc::CollectionType,466		sender: T::AccountId,467		new_owner: T::AccountId,468	) -> DispatchResult {469		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;470		Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;471472		let mut collection = collection.into_inner();473474		collection.owner = new_owner;475		collection.save()476	}477478	fn check_collection_owner(479		collection: &NonfungibleHandle<T>,480		account: &T::CrossAccountId,481	) -> DispatchResult {482		collection483			.check_is_owner(account)484			.map_err(Self::map_common_err_to_proxy)485	}486487	pub fn last_collection_idx() -> RmrkCollectionId {488		<CollectionIndex<T>>::get()489	}490491	pub fn get_nft_collection(492		collection_id: CollectionId,493	) -> Result<NonfungibleHandle<T>, DispatchError> {494		let collection = <CollectionHandle<T>>::try_get(collection_id)495			.map_err(|_| <Error<T>>::CollectionUnknown)?;496497		match collection.mode {498			CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),499			_ => Err(<Error<T>>::CollectionUnknown.into()),500		}501	}502503	pub fn collection_exists(collection_id: CollectionId) -> bool {504		<CollectionHandle<T>>::try_get(collection_id).is_ok()505	}506507	pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {508		<TokenData<T>>::contains_key((collection_id, nft_id))509	}510511	pub fn get_collection_property(512		collection_id: CollectionId,513		key: RmrkProperty,514	) -> Result<PropertyValue, DispatchError> {515		let collection_property = <PalletCommon<T>>::collection_properties(collection_id)516			.get(&Self::rmrk_property_key(key)?)517			.ok_or(<Error<T>>::CollectionUnknown)?518			.clone();519520		Ok(collection_property)521	}522523	pub fn get_collection_type(524		collection_id: CollectionId,525	) -> Result<misc::CollectionType, DispatchError> {526		let value = Self::get_collection_property(collection_id, CollectionType)?;527528		let mut value = value.as_slice();529530		misc::CollectionType::decode(&mut value)531			.map_err(|_| <Error<T>>::CorruptedCollectionType.into())532	}533534	pub fn ensure_collection_type(535		collection_id: CollectionId,536		collection_type: misc::CollectionType,537	) -> DispatchResult {538		let actual_type = Self::get_collection_type(collection_id)?;539		ensure!(540			actual_type == collection_type,541			<CommonError<T>>::NoPermission542		);543544		Ok(())545	}546547	pub fn get_nft_property(548		collection_id: CollectionId,549		nft_id: TokenId,550		key: RmrkProperty,551	) -> Result<PropertyValue, DispatchError> {552		let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))553			.get(&Self::rmrk_property_key(key)?)554			.ok_or(<Error<T>>::NoAvailableNftId)?555			.clone();556557		Ok(nft_property)558	}559560	pub fn get_nft_type(561		_collection_id: CollectionId,562		_token_id: TokenId,563	) -> Result<NftType, DispatchError> {564		todo!("should get it from properties?")565	}566567	pub fn ensure_nft_type(568		collection_id: CollectionId,569		token_id: TokenId,570		nft_type: NftType,571	) -> DispatchResult {572		let actual_type = Self::get_nft_type(collection_id, token_id)?;573		ensure!(actual_type == nft_type, <Error<T>>::NoPermission);574575		Ok(())576	}577578	pub fn ensure_nft_owner(579		collection_id: CollectionId,580		token_id: TokenId,581		possible_owner: &T::CrossAccountId,582	) -> DispatchResult {583		let token_data =584			<TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;585586		ensure!(587			token_data.owner == *possible_owner,588			<Error<T>>::NoPermission589		);590591		Ok(())592	}593594	pub fn filter_user_properties<Key, Value, R, Mapper>(595		collection_id: CollectionId,596		token_id: Option<TokenId>,597		filter_keys: Option<Vec<RmrkPropertyKey>>,598		mapper: Mapper,599	) -> Result<Vec<R>, DispatchError>600	where601		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,602		Value: Decode + Default,603		Mapper: Fn(Key, Value) -> R,604	{605		filter_keys606			.map(|keys| {607				let properties = keys608					.into_iter()609					.filter_map(|key| {610						let key: Key = key.try_into().ok()?;611612						let value = match token_id {613							Some(token_id) => Self::get_nft_property(614								collection_id,615								token_id,616								UserProperty(key.as_ref()),617							),618							None => Self::get_collection_property(619								collection_id,620								UserProperty(key.as_ref()),621							),622						}623						.ok()?624						.decode_or_default();625626						Some(mapper(key, value))627					})628					.collect();629630				Ok(properties)631			})632			.unwrap_or_else(|| {633				let properties =634					Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();635636				Ok(properties)637			})638	}639640	pub fn iterate_user_properties<Key, Value, R, Mapper>(641		collection_id: CollectionId,642		token_id: Option<TokenId>,643		mapper: Mapper,644	) -> Result<impl Iterator<Item = R>, DispatchError>645	where646		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,647		Value: Decode + Default,648		Mapper: Fn(Key, Value) -> R,649	{650		let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;651652		let properties = match token_id {653			Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),654			None => <PalletCommon<T>>::collection_properties(collection_id),655		};656657		let properties = properties.into_iter().filter_map(move |(key, value)| {658			let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;659660			let key: Key = key.to_vec().try_into().ok()?;661			let value: Value = value.decode_or_default();662663			Some(mapper(key, value))664		});665666		Ok(properties)667	}668669	pub fn get_typed_nft_collection(670		collection_id: CollectionId,671		collection_type: misc::CollectionType,672	) -> Result<NonfungibleHandle<T>, DispatchError> {673		Self::ensure_collection_type(collection_id, collection_type)?;674675		Self::get_nft_collection(collection_id)676	}677678	fn map_common_err_to_proxy(err: DispatchError) -> DispatchError {679		map_common_err_to_proxy! {680			match err {681				NoPermission => NoPermission,682				CollectionTokenLimitExceeded => CollectionFullOrLocked,683				PublicMintingNotAllowed => NoPermission,684				TokenNotFound => NoAvailableNftId685			}686		}687	}688}