git.delta.rocks / unique-network / refs/commits / b46fb6fcbf40

difftreelog

source

pallets/proxy-rmrk-core/src/lib.rs17.2 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 = <PalletNft<T>>::init_collection(sender.clone(), data);143144			if let Err(DispatchError::Arithmetic(_)) = &collection_id_res {145				return Err(<Error<T>>::NoAvailableCollectionId.into());146			}147148			let collection_id = collection_id_res?;149150			<PalletCommon<T>>::set_scoped_collection_properties(151				collection_id,152				PropertyScope::Rmrk,153				[154					Self::rmrk_property(Metadata, &metadata)?,155					Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,156				]157				.into_iter(),158			)?;159160			<CollectionIndex<T>>::mutate(|n| *n += 1);161162			Self::deposit_event(Event::CollectionCreated {163				issuer: sender,164				collection_id: collection_id.0,165			});166167			Ok(())168		}169170		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]171		#[transactional]172		pub fn destroy_collection(173			origin: OriginFor<T>,174			collection_id: RmrkCollectionId,175		) -> DispatchResult {176			let sender = ensure_signed(origin)?;177			let cross_sender = T::CrossAccountId::from_sub(sender.clone());178179			let unique_collection_id = collection_id.into();180181			let collection = Self::get_typed_nft_collection(182				unique_collection_id,183				misc::CollectionType::Regular,184			)?;185186			ensure!(187				collection.total_supply() == 0,188				<Error<T>>::CollectionNotEmpty189			);190191			<PalletNft<T>>::destroy_collection(collection, &cross_sender)192				.map_err(Self::map_common_err_to_proxy)?;193194			Self::deposit_event(Event::CollectionDestroyed {195				issuer: sender,196				collection_id,197			});198199			Ok(())200		}201202		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]203		#[transactional]204		pub fn change_collection_issuer(205			origin: OriginFor<T>,206			collection_id: RmrkCollectionId,207			new_issuer: <T::Lookup as StaticLookup>::Source,208		) -> DispatchResult {209			let sender = ensure_signed(origin)?;210211			let new_issuer = T::Lookup::lookup(new_issuer)?;212213			Self::change_collection_owner(214				collection_id.into(),215				misc::CollectionType::Regular,216				sender.clone(),217				new_issuer.clone(),218			)?;219220			Self::deposit_event(Event::IssuerChanged {221				old_issuer: sender,222				new_issuer,223				collection_id,224			});225226			Ok(())227		}228229		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]230		#[transactional]231		pub fn lock_collection(232			origin: OriginFor<T>,233			collection_id: RmrkCollectionId,234		) -> DispatchResult {235			let sender = ensure_signed(origin)?;236			let cross_sender = T::CrossAccountId::from_sub(sender.clone());237238			let collection = Self::get_typed_nft_collection(239				collection_id.into(),240				misc::CollectionType::Regular,241			)?;242243			Self::check_collection_owner(&collection, &cross_sender)?;244245			let token_count = collection.total_supply();246247			let mut collection = collection.into_inner();248			collection.limits.token_limit = Some(token_count);249			collection.save()?;250251			Self::deposit_event(Event::CollectionLocked {252				issuer: sender,253				collection_id,254			});255256			Ok(())257		}258259		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]260		#[transactional]261		pub fn mint_nft(262			origin: OriginFor<T>,263			owner: T::AccountId,264			collection_id: RmrkCollectionId,265			recipient: Option<T::AccountId>,266			royalty_amount: Option<Permill>,267			metadata: RmrkString,268		) -> DispatchResult {269			let sender = ensure_signed(origin)?;270			let sender = T::CrossAccountId::from_sub(sender);271			let cross_owner = T::CrossAccountId::from_sub(owner.clone());272273			let royalty_info = royalty_amount.map(|amount| rmrk::RoyaltyInfo {274				recipient: recipient.unwrap_or_else(|| owner.clone()),275				amount,276			});277278			let collection = Self::get_typed_nft_collection(279				collection_id.into(),280				misc::CollectionType::Regular,281			)?;282283			let nft_id = Self::create_nft(284				&sender,285				&cross_owner,286				&collection,287				NftType::Regular,288				[289					Self::rmrk_property(RoyaltyInfo, &royalty_info)?,290					Self::rmrk_property(Metadata, &metadata)?,291					Self::rmrk_property(Equipped, &false)?,292					Self::rmrk_property(ResourceCollection, &None::<CollectionId>)?,293					Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,294				]295				.into_iter(),296			)297			.map_err(|err| match err {298				DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),299				err => Self::map_common_err_to_proxy(err),300			})?;301302			Self::deposit_event(Event::NftMinted {303				owner,304				collection_id,305				nft_id: nft_id.0,306			});307308			Ok(())309		}310311		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]312		#[transactional]313		pub fn burn_nft(314			origin: OriginFor<T>,315			collection_id: RmrkCollectionId,316			nft_id: RmrkNftId,317		) -> DispatchResult {318			let sender = ensure_signed(origin)?;319			let cross_sender = T::CrossAccountId::from_sub(sender.clone());320321			Self::destroy_nft(322				cross_sender,323				collection_id.into(),324				misc::CollectionType::Regular,325				nft_id.into(),326			)?;327328			Self::deposit_event(Event::NFTBurned {329				owner: sender,330				nft_id,331			});332333			Ok(())334		}335336		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]337		#[transactional]338		pub fn set_property(339			origin: OriginFor<T>,340			#[pallet::compact] rmrk_collection_id: RmrkCollectionId,341			maybe_nft_id: Option<RmrkNftId>,342			key: RmrkKeyString,343			value: RmrkValueString,344		) -> DispatchResult {345			let sender = ensure_signed(origin)?;346			let sender = T::CrossAccountId::from_sub(sender);347348			let collection_id: CollectionId = rmrk_collection_id.into();349350			match maybe_nft_id {351				Some(nft_id) => {352					let token_id: TokenId = nft_id.into();353354					Self::ensure_nft_owner(collection_id, token_id, &sender)?;355					Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;356357					<PalletNft<T>>::set_scoped_token_property(358						collection_id,359						token_id,360						PropertyScope::Rmrk,361						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,362					)?;363				}364				None => {365					let collection = Self::get_typed_nft_collection(366						collection_id,367						misc::CollectionType::Regular,368					)?;369370					Self::check_collection_owner(&collection, &sender)?;371372					<PalletCommon<T>>::set_scoped_collection_property(373						collection_id,374						PropertyScope::Rmrk,375						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,376					)?;377				}378			}379380			Self::deposit_event(Event::PropertySet {381				collection_id: rmrk_collection_id,382				maybe_nft_id,383				key,384				value,385			});386387			Ok(())388		}389	}390}391392impl<T: Config> Pallet<T> {393	pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {394		let key = rmrk_key.to_key::<T>()?;395396		let scoped_key = PropertyScope::Rmrk397			.apply(key)398			.map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;399400		Ok(scoped_key)401	}402403	pub fn rmrk_property<E: Encode>(404		rmrk_key: RmrkProperty,405		value: &E,406	) -> Result<Property, DispatchError> {407		let key = rmrk_key.to_key::<T>()?;408409		let value = value410			.encode()411			.try_into()412			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;413414		let property = Property { key, value };415416		Ok(property)417	}418419	pub fn create_nft(420		sender: &T::CrossAccountId,421		owner: &T::CrossAccountId,422		collection: &NonfungibleHandle<T>,423		nft_type: NftType,424		properties: impl Iterator<Item = Property>,425	) -> Result<TokenId, DispatchError> {426		todo!("store nft type");427		let data = CreateNftExData {428			properties: BoundedVec::default(),429			owner: owner.clone(),430		};431432		let budget = budget::Value::new(2);433434		<PalletNft<T>>::create_item(collection, sender, data, &budget)?;435436		let nft_id = <PalletNft<T>>::current_token_id(collection.id);437438		<PalletNft<T>>::set_scoped_token_properties(439			collection.id,440			nft_id,441			PropertyScope::Rmrk,442			properties,443		)?;444445		Ok(nft_id)446	}447448	fn destroy_nft(449		sender: T::CrossAccountId,450		collection_id: CollectionId,451		collection_type: misc::CollectionType,452		token_id: TokenId,453	) -> DispatchResult {454		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;455456		<PalletNft<T>>::burn(&collection, &sender, token_id)457			.map_err(Self::map_common_err_to_proxy)?;458459		Ok(())460	}461462	fn change_collection_owner(463		collection_id: CollectionId,464		collection_type: misc::CollectionType,465		sender: T::AccountId,466		new_owner: T::AccountId,467	) -> DispatchResult {468		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;469		Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;470471		let mut collection = collection.into_inner();472473		collection.owner = new_owner;474		collection.save()475	}476477	fn check_collection_owner(478		collection: &NonfungibleHandle<T>,479		account: &T::CrossAccountId,480	) -> DispatchResult {481		collection482			.check_is_owner(account)483			.map_err(Self::map_common_err_to_proxy)484	}485486	pub fn last_collection_idx() -> RmrkCollectionId {487		<CollectionIndex<T>>::get()488	}489490	pub fn get_nft_collection(491		collection_id: CollectionId,492	) -> Result<NonfungibleHandle<T>, DispatchError> {493		let collection = <CollectionHandle<T>>::try_get(collection_id)494			.map_err(|_| <Error<T>>::CollectionUnknown)?;495496		match collection.mode {497			CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),498			_ => Err(<Error<T>>::CollectionUnknown.into()),499		}500	}501502	pub fn collection_exists(collection_id: CollectionId) -> bool {503		<CollectionHandle<T>>::try_get(collection_id).is_ok()504	}505506	pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {507		<TokenData<T>>::contains_key((collection_id, nft_id))508	}509510	pub fn get_collection_property(511		collection_id: CollectionId,512		key: RmrkProperty,513	) -> Result<PropertyValue, DispatchError> {514		let collection_property = <PalletCommon<T>>::collection_properties(collection_id)515			.get(&Self::rmrk_property_key(key)?)516			.ok_or(<Error<T>>::CollectionUnknown)?517			.clone();518519		Ok(collection_property)520	}521522	pub fn get_collection_type(523		collection_id: CollectionId,524	) -> Result<misc::CollectionType, DispatchError> {525		let value = Self::get_collection_property(collection_id, CollectionType)?;526527		let mut value = value.as_slice();528529		misc::CollectionType::decode(&mut value)530			.map_err(|_| <Error<T>>::CorruptedCollectionType.into())531	}532533	pub fn ensure_collection_type(534		collection_id: CollectionId,535		collection_type: misc::CollectionType,536	) -> DispatchResult {537		let actual_type = Self::get_collection_type(collection_id)?;538		ensure!(539			actual_type == collection_type,540			<CommonError<T>>::NoPermission541		);542543		Ok(())544	}545546	pub fn get_nft_property(547		collection_id: CollectionId,548		nft_id: TokenId,549		key: RmrkProperty,550	) -> Result<PropertyValue, DispatchError> {551		let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))552			.get(&Self::rmrk_property_key(key)?)553			.ok_or(<Error<T>>::NoAvailableNftId)?554			.clone();555556		Ok(nft_property)557	}558559	pub fn get_nft_type(560		_collection_id: CollectionId,561		_token_id: TokenId,562	) -> Result<NftType, DispatchError> {563		todo!("should get it from properties?")564	}565566	pub fn ensure_nft_type(567		collection_id: CollectionId,568		token_id: TokenId,569		nft_type: NftType,570	) -> DispatchResult {571		let actual_type = Self::get_nft_type(collection_id, token_id)?;572		ensure!(actual_type == nft_type, <Error<T>>::NoPermission);573574		Ok(())575	}576577	pub fn ensure_nft_owner(578		collection_id: CollectionId,579		token_id: TokenId,580		possible_owner: &T::CrossAccountId,581	) -> DispatchResult {582		let token_data =583			<TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;584585		ensure!(586			token_data.owner == *possible_owner,587			<Error<T>>::NoPermission588		);589590		Ok(())591	}592593	pub fn filter_user_properties<Key, Value, R, Mapper>(594		collection_id: CollectionId,595		token_id: Option<TokenId>,596		filter_keys: Option<Vec<RmrkPropertyKey>>,597		mapper: Mapper,598	) -> Result<Vec<R>, DispatchError>599	where600		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,601		Value: Decode + Default,602		Mapper: Fn(Key, Value) -> R,603	{604		filter_keys605			.map(|keys| {606				let properties = keys607					.into_iter()608					.filter_map(|key| {609						let key: Key = key.try_into().ok()?;610611						let value = match token_id {612							Some(token_id) => Self::get_nft_property(613								collection_id,614								token_id,615								UserProperty(key.as_ref()),616							),617							None => Self::get_collection_property(618								collection_id,619								UserProperty(key.as_ref()),620							),621						}622						.ok()?623						.decode_or_default();624625						Some(mapper(key, value))626					})627					.collect();628629				Ok(properties)630			})631			.unwrap_or_else(|| {632				let properties =633					Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();634635				Ok(properties)636			})637	}638639	pub fn iterate_user_properties<Key, Value, R, Mapper>(640		collection_id: CollectionId,641		token_id: Option<TokenId>,642		mapper: Mapper,643	) -> Result<impl Iterator<Item = R>, DispatchError>644	where645		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,646		Value: Decode + Default,647		Mapper: Fn(Key, Value) -> R,648	{649		let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;650651		let properties = match token_id {652			Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),653			None => <PalletCommon<T>>::collection_properties(collection_id),654		};655656		let properties = properties.into_iter().filter_map(move |(key, value)| {657			let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;658659			let key: Key = key.to_vec().try_into().ok()?;660			let value: Value = value.decode_or_default();661662			Some(mapper(key, value))663		});664665		Ok(properties)666	}667668	pub fn get_typed_nft_collection(669		collection_id: CollectionId,670		collection_type: misc::CollectionType,671	) -> Result<NonfungibleHandle<T>, DispatchError> {672		Self::ensure_collection_type(collection_id, collection_type)?;673674		Self::get_nft_collection(collection_id)675	}676677	fn map_common_err_to_proxy(err: DispatchError) -> DispatchError {678		map_common_err_to_proxy! {679			match err {680				NoPermission => NoPermission,681				CollectionTokenLimitExceeded => CollectionFullOrLocked,682				PublicMintingNotAllowed => NoPermission,683				TokenNotFound => NoAvailableNftId684			}685		}686	}687}