git.delta.rocks / unique-network / refs/commits / 47e3bf21a896

difftreelog

source

pallets/proxy-rmrk-core/src/lib.rs17.9 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::storage]58	#[pallet::getter(fn collection_index_map)]59	pub type CollectionIndexMap<T: Config> = 60		StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;6162	#[pallet::pallet]63	#[pallet::generate_store(pub(super) trait Store)]64	pub struct Pallet<T>(_);6566	#[pallet::event]67	#[pallet::generate_deposit(pub(super) fn deposit_event)]68	pub enum Event<T: Config> {69		CollectionCreated {70			issuer: T::AccountId,71			collection_id: RmrkCollectionId,72		},73		CollectionDestroyed {74			issuer: T::AccountId,75			collection_id: RmrkCollectionId,76		},77		IssuerChanged {78			old_issuer: T::AccountId,79			new_issuer: T::AccountId,80			collection_id: RmrkCollectionId,81		},82		CollectionLocked {83			issuer: T::AccountId,84			collection_id: RmrkCollectionId,85		},86		NftMinted {87			owner: T::AccountId,88			collection_id: RmrkCollectionId,89			nft_id: RmrkNftId,90		},91		NFTBurned {92			owner: T::AccountId,93			nft_id: RmrkNftId,94		},95		PropertySet {96			collection_id: RmrkCollectionId,97			maybe_nft_id: Option<RmrkNftId>,98			key: RmrkKeyString,99			value: RmrkValueString,100		},101	}102103	#[pallet::error]104	pub enum Error<T> {105		/* Unique-specific events */106		CorruptedCollectionType,107		NftTypeEncodeError,108		RmrkPropertyKeyIsTooLong,109		RmrkPropertyValueIsTooLong,110111		/* RMRK compatible events */112		CollectionNotEmpty,113		NoAvailableCollectionId,114		NoAvailableNftId,115		CollectionUnknown,116		NoPermission,117		CollectionFullOrLocked,118	}119120	#[pallet::call]121	impl<T: Config> Pallet<T> {122		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]123		#[transactional]124		pub fn create_collection(125			origin: OriginFor<T>,126			metadata: RmrkString,127			max: Option<u32>,128			symbol: RmrkCollectionSymbol,129		) -> DispatchResult {130			let sender = ensure_signed(origin)?;131132			let limits = CollectionLimits {133				owner_can_transfer: Some(false),134				token_limit: max,135				..Default::default()136			};137138			let data = CreateCollectionData {139				limits: Some(limits),140				token_prefix: symbol141					.into_inner()142					.try_into()143					.map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,144				..Default::default()145			};146147			let collection_id_res =148				<PalletNft<T>>::init_collection(T::CrossAccountId::from_sub(sender.clone()), data);149150			if let Err(DispatchError::Arithmetic(_)) = &collection_id_res {151				return Err(<Error<T>>::NoAvailableCollectionId.into());152			}153154			let unique_collection_id = collection_id_res?;155			let rmrk_collection_id = <CollectionIndex<T>>::get();156157			<CollectionIndex<T>>::mutate(|n| *n += 1);158			<CollectionIndexMap<T>>::insert(rmrk_collection_id, unique_collection_id);159160			<PalletCommon<T>>::set_scoped_collection_properties(161				unique_collection_id,162				PropertyScope::Rmrk,163				[164					Self::rmrk_property(Metadata, &metadata)?,165					Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,166				]167				.into_iter(),168			)?;169170			Self::deposit_event(Event::CollectionCreated {171				issuer: sender,172				collection_id: rmrk_collection_id,173			});174175			Ok(())176		}177178		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]179		#[transactional]180		pub fn destroy_collection(181			origin: OriginFor<T>,182			collection_id: RmrkCollectionId,183		) -> DispatchResult {184			let sender = ensure_signed(origin)?;185			let cross_sender = T::CrossAccountId::from_sub(sender.clone());186187			let collection = Self::get_typed_nft_collection(188				Self::unique_collection_id(collection_id)?,189				misc::CollectionType::Regular,190			)?;191192			ensure!(193				collection.total_supply() == 0,194				<Error<T>>::CollectionNotEmpty195			);196197			<PalletNft<T>>::destroy_collection(collection, &cross_sender)198				.map_err(Self::map_common_err_to_proxy)?;199200			Self::deposit_event(Event::CollectionDestroyed {201				issuer: sender,202				collection_id,203			});204205			Ok(())206		}207208		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]209		#[transactional]210		pub fn change_collection_issuer(211			origin: OriginFor<T>,212			collection_id: RmrkCollectionId,213			new_issuer: <T::Lookup as StaticLookup>::Source,214		) -> DispatchResult {215			let sender = ensure_signed(origin)?;216217			let new_issuer = T::Lookup::lookup(new_issuer)?;218219			Self::change_collection_owner(220				Self::unique_collection_id(collection_id)?,221				misc::CollectionType::Regular,222				sender.clone(),223				new_issuer.clone(),224			)?;225226			Self::deposit_event(Event::IssuerChanged {227				old_issuer: sender,228				new_issuer,229				collection_id,230			});231232			Ok(())233		}234235		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]236		#[transactional]237		pub fn lock_collection(238			origin: OriginFor<T>,239			collection_id: RmrkCollectionId,240		) -> DispatchResult {241			let sender = ensure_signed(origin)?;242			let cross_sender = T::CrossAccountId::from_sub(sender.clone());243244			let collection = Self::get_typed_nft_collection(245				Self::unique_collection_id(collection_id)?,246				misc::CollectionType::Regular,247			)?;248249			Self::check_collection_owner(&collection, &cross_sender)?;250251			let token_count = collection.total_supply();252253			let mut collection = collection.into_inner();254			collection.limits.token_limit = Some(token_count);255			collection.save()?;256257			Self::deposit_event(Event::CollectionLocked {258				issuer: sender,259				collection_id,260			});261262			Ok(())263		}264265		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]266		#[transactional]267		pub fn mint_nft(268			origin: OriginFor<T>,269			owner: T::AccountId,270			collection_id: RmrkCollectionId,271			recipient: Option<T::AccountId>,272			royalty_amount: Option<Permill>,273			metadata: RmrkString,274		) -> DispatchResult {275			let sender = ensure_signed(origin)?;276			let sender = T::CrossAccountId::from_sub(sender);277			let cross_owner = T::CrossAccountId::from_sub(owner.clone());278279			let royalty_info = royalty_amount.map(|amount| rmrk::RoyaltyInfo {280				recipient: recipient.unwrap_or_else(|| owner.clone()),281				amount,282			});283284			let collection = Self::get_typed_nft_collection(285				Self::unique_collection_id(collection_id)?,286				misc::CollectionType::Regular,287			)?;288289			let nft_id = Self::create_nft(290				&sender,291				&cross_owner,292				&collection,293				NftType::Regular,294				[295					Self::rmrk_property(RoyaltyInfo, &royalty_info)?,296					Self::rmrk_property(Metadata, &metadata)?,297					Self::rmrk_property(Equipped, &false)?,298					Self::rmrk_property(ResourceCollection, &None::<CollectionId>)?,299					Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,300				]301				.into_iter(),302			)303			.map_err(|err| match err {304				DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),305				err => Self::map_common_err_to_proxy(err),306			})?;307308			Self::deposit_event(Event::NftMinted {309				owner,310				collection_id,311				nft_id: nft_id.0,312			});313314			Ok(())315		}316317		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]318		#[transactional]319		pub fn burn_nft(320			origin: OriginFor<T>,321			collection_id: RmrkCollectionId,322			nft_id: RmrkNftId,323		) -> DispatchResult {324			let sender = ensure_signed(origin)?;325			let cross_sender = T::CrossAccountId::from_sub(sender.clone());326327			Self::destroy_nft(328				cross_sender,329				Self::unique_collection_id(collection_id)?,330				misc::CollectionType::Regular,331				nft_id.into(),332			)?;333334			Self::deposit_event(Event::NFTBurned {335				owner: sender,336				nft_id,337			});338339			Ok(())340		}341342		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]343		#[transactional]344		pub fn set_property(345			origin: OriginFor<T>,346			#[pallet::compact] rmrk_collection_id: RmrkCollectionId,347			maybe_nft_id: Option<RmrkNftId>,348			key: RmrkKeyString,349			value: RmrkValueString,350		) -> DispatchResult {351			let sender = ensure_signed(origin)?;352			let sender = T::CrossAccountId::from_sub(sender);353354			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;355356			match maybe_nft_id {357				Some(nft_id) => {358					let token_id: TokenId = nft_id.into();359360					Self::ensure_nft_owner(collection_id, token_id, &sender)?;361					Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;362363					<PalletNft<T>>::set_scoped_token_property(364						collection_id,365						token_id,366						PropertyScope::Rmrk,367						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,368					)?;369				}370				None => {371					let collection = Self::get_typed_nft_collection(372						collection_id,373						misc::CollectionType::Regular,374					)?;375376					Self::check_collection_owner(&collection, &sender)?;377378					<PalletCommon<T>>::set_scoped_collection_property(379						collection_id,380						PropertyScope::Rmrk,381						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,382					)?;383				}384			}385386			Self::deposit_event(Event::PropertySet {387				collection_id: rmrk_collection_id,388				maybe_nft_id,389				key,390				value,391			});392393			Ok(())394		}395	}396}397398impl<T: Config> Pallet<T> {399	pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {400		let key = rmrk_key.to_key::<T>()?;401402		let scoped_key = PropertyScope::Rmrk403			.apply(key)404			.map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;405406		Ok(scoped_key)407	}408409	pub fn rmrk_property<E: Encode>(410		rmrk_key: RmrkProperty,411		value: &E,412	) -> Result<Property, DispatchError> {413		let key = rmrk_key.to_key::<T>()?;414415		let value = value416			.encode()417			.try_into()418			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;419420		let property = Property { key, value };421422		Ok(property)423	}424425	pub fn create_nft(426		sender: &T::CrossAccountId,427		owner: &T::CrossAccountId,428		collection: &NonfungibleHandle<T>,429		nft_type: NftType,430		properties: impl Iterator<Item = Property>,431	) -> Result<TokenId, DispatchError> {432		todo!("store nft type");433		let data = CreateNftExData {434			properties: BoundedVec::default(),435			owner: owner.clone(),436		};437438		let budget = budget::Value::new(2);439440		<PalletNft<T>>::create_item(collection, sender, data, &budget)?;441442		let nft_id = <PalletNft<T>>::current_token_id(collection.id);443444		<PalletNft<T>>::set_scoped_token_properties(445			collection.id,446			nft_id,447			PropertyScope::Rmrk,448			properties,449		)?;450451		Ok(nft_id)452	}453454	fn destroy_nft(455		sender: T::CrossAccountId,456		collection_id: CollectionId,457		collection_type: misc::CollectionType,458		token_id: TokenId,459	) -> DispatchResult {460		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;461462		<PalletNft<T>>::burn(&collection, &sender, token_id)463			.map_err(Self::map_common_err_to_proxy)?;464465		Ok(())466	}467468	fn change_collection_owner(469		collection_id: CollectionId,470		collection_type: misc::CollectionType,471		sender: T::AccountId,472		new_owner: T::AccountId,473	) -> DispatchResult {474		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;475		Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;476477		let mut collection = collection.into_inner();478479		collection.owner = new_owner;480		collection.save()481	}482483	fn check_collection_owner(484		collection: &NonfungibleHandle<T>,485		account: &T::CrossAccountId,486	) -> DispatchResult {487		collection488			.check_is_owner(account)489			.map_err(Self::map_common_err_to_proxy)490	}491492	pub fn last_collection_idx() -> RmrkCollectionId {493		<CollectionIndex<T>>::get()494	}495496	pub fn unique_collection_id(rmrk_collection_id: RmrkCollectionId) -> Result<CollectionId, DispatchError> {497		<CollectionIndexMap<T>>::try_get(rmrk_collection_id).map_err(|_| <Error<T>>::CollectionUnknown.into())498	}499500	pub fn get_nft_collection(501		collection_id: CollectionId,502	) -> Result<NonfungibleHandle<T>, DispatchError> {503		let collection = <CollectionHandle<T>>::try_get(collection_id)504			.map_err(|_| <Error<T>>::CollectionUnknown)?;505506		match collection.mode {507			CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),508			_ => Err(<Error<T>>::CollectionUnknown.into()),509		}510	}511512	pub fn collection_exists(collection_id: CollectionId) -> bool {513		<CollectionHandle<T>>::try_get(collection_id).is_ok()514	}515516	pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {517		<TokenData<T>>::contains_key((collection_id, nft_id))518	}519520	pub fn get_collection_property(521		collection_id: CollectionId,522		key: RmrkProperty,523	) -> Result<PropertyValue, DispatchError> {524		let collection_property = <PalletCommon<T>>::collection_properties(collection_id)525			.get(&Self::rmrk_property_key(key)?)526			.ok_or(<Error<T>>::CollectionUnknown)?527			.clone();528529		Ok(collection_property)530	}531532	pub fn get_collection_type(533		collection_id: CollectionId,534	) -> Result<misc::CollectionType, DispatchError> {535		let value = Self::get_collection_property(collection_id, CollectionType)?;536537		let mut value = value.as_slice();538539		misc::CollectionType::decode(&mut value)540			.map_err(|_| <Error<T>>::CorruptedCollectionType.into())541	}542543	pub fn ensure_collection_type(544		collection_id: CollectionId,545		collection_type: misc::CollectionType,546	) -> DispatchResult {547		let actual_type = Self::get_collection_type(collection_id)?;548		ensure!(549			actual_type == collection_type,550			<CommonError<T>>::NoPermission551		);552553		Ok(())554	}555556	pub fn get_nft_property(557		collection_id: CollectionId,558		nft_id: TokenId,559		key: RmrkProperty,560	) -> Result<PropertyValue, DispatchError> {561		let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))562			.get(&Self::rmrk_property_key(key)?)563			.ok_or(<Error<T>>::NoAvailableNftId)?564			.clone();565566		Ok(nft_property)567	}568569	pub fn get_nft_type(570		_collection_id: CollectionId,571		_token_id: TokenId,572	) -> Result<NftType, DispatchError> {573		todo!("should get it from properties?")574	}575576	pub fn ensure_nft_type(577		collection_id: CollectionId,578		token_id: TokenId,579		nft_type: NftType,580	) -> DispatchResult {581		let actual_type = Self::get_nft_type(collection_id, token_id)?;582		ensure!(actual_type == nft_type, <Error<T>>::NoPermission);583584		Ok(())585	}586587	pub fn ensure_nft_owner(588		collection_id: CollectionId,589		token_id: TokenId,590		possible_owner: &T::CrossAccountId,591	) -> DispatchResult {592		let token_data =593			<TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;594595		ensure!(596			token_data.owner == *possible_owner,597			<Error<T>>::NoPermission598		);599600		Ok(())601	}602603	pub fn filter_user_properties<Key, Value, R, Mapper>(604		collection_id: CollectionId,605		token_id: Option<TokenId>,606		filter_keys: Option<Vec<RmrkPropertyKey>>,607		mapper: Mapper,608	) -> Result<Vec<R>, DispatchError>609	where610		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,611		Value: Decode + Default,612		Mapper: Fn(Key, Value) -> R,613	{614		filter_keys615			.map(|keys| {616				let properties = keys617					.into_iter()618					.filter_map(|key| {619						let key: Key = key.try_into().ok()?;620621						let value = match token_id {622							Some(token_id) => Self::get_nft_property(623								collection_id,624								token_id,625								UserProperty(key.as_ref()),626							),627							None => Self::get_collection_property(628								collection_id,629								UserProperty(key.as_ref()),630							),631						}632						.ok()?633						.decode_or_default();634635						Some(mapper(key, value))636					})637					.collect();638639				Ok(properties)640			})641			.unwrap_or_else(|| {642				let properties =643					Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();644645				Ok(properties)646			})647	}648649	pub fn iterate_user_properties<Key, Value, R, Mapper>(650		collection_id: CollectionId,651		token_id: Option<TokenId>,652		mapper: Mapper,653	) -> Result<impl Iterator<Item = R>, DispatchError>654	where655		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,656		Value: Decode + Default,657		Mapper: Fn(Key, Value) -> R,658	{659		let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;660661		let properties = match token_id {662			Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),663			None => <PalletCommon<T>>::collection_properties(collection_id),664		};665666		let properties = properties.into_iter().filter_map(move |(key, value)| {667			let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;668669			let key: Key = key.to_vec().try_into().ok()?;670			let value: Value = value.decode_or_default();671672			Some(mapper(key, value))673		});674675		Ok(properties)676	}677678	pub fn get_typed_nft_collection(679		collection_id: CollectionId,680		collection_type: misc::CollectionType,681	) -> Result<NonfungibleHandle<T>, DispatchError> {682		Self::ensure_collection_type(collection_id, collection_type)?;683684		Self::get_nft_collection(collection_id)685	}686687	fn map_common_err_to_proxy(err: DispatchError) -> DispatchError {688		map_common_err_to_proxy! {689			match err {690				NoPermission => NoPermission,691				CollectionTokenLimitExceeded => CollectionFullOrLocked,692				PublicMintingNotAllowed => NoPermission,693				TokenNotFound => NoAvailableNftId694			}695		}696	}697}