1234567891011121314151617#![cfg_attr(not(feature = "std"), no_std)]1819use core::ops::{Deref, DerefMut};20use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};21use sp_std::vec::Vec;22use pallet_evm::account::CrossAccountId;23use frame_support::{24 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},25 ensure,26 traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},27 BoundedVec,28 weights::Pays,29};30use pallet_evm::GasWeightMapping;31use up_data_structs::{32 COLLECTION_NUMBER_LIMIT, Collection, RpcCollection, CollectionId, CreateItemData,33 MAX_TOKEN_PREFIX_LENGTH, COLLECTION_ADMINS_LIMIT, TokenId,34 CollectionStats, MAX_TOKEN_OWNERSHIP, CollectionMode, NFT_SPONSOR_TRANSFER_TIMEOUT,35 FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, MAX_SPONSOR_TIMEOUT,36 CUSTOM_DATA_LIMIT, CollectionLimits, CreateCollectionData, SponsorshipState,37 CreateItemExData, SponsoringRateLimit, budget::Budget, COLLECTION_FIELD_LIMIT, CollectionField,38 PhantomType, Property, Properties, PropertiesPermissionMap, PropertyKey, PropertyPermission,39 PropertiesError, PropertyKeyPermission, TokenData, TrySet,40};41pub use pallet::*;42use sp_core::H160;43use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};44#[cfg(feature = "runtime-benchmarks")]45pub mod benchmarking;46pub mod dispatch;47pub mod erc;48pub mod eth;4950#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]51pub struct CollectionHandle<T: Config> {52 pub id: CollectionId,53 collection: Collection<T::AccountId>,54 pub recorder: SubstrateRecorder<T>,55}56impl<T: Config> WithRecorder<T> for CollectionHandle<T> {57 fn recorder(&self) -> &SubstrateRecorder<T> {58 &self.recorder59 }60 fn into_recorder(self) -> SubstrateRecorder<T> {61 self.recorder62 }63}64impl<T: Config> CollectionHandle<T> {65 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {66 <CollectionById<T>>::get(id).map(|collection| Self {67 id,68 collection,69 recorder: SubstrateRecorder::new(gas_limit),70 })71 }72 pub fn new(id: CollectionId) -> Option<Self> {73 Self::new_with_gas_limit(id, u64::MAX)74 }75 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {76 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)77 }78 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {79 self.recorder80 .consume_gas(T::GasWeightMapping::weight_to_gas(81 <T as frame_system::Config>::DbWeight::get()82 .read83 .saturating_mul(reads),84 ))85 }86 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {87 self.recorder88 .consume_gas(T::GasWeightMapping::weight_to_gas(89 <T as frame_system::Config>::DbWeight::get()90 .write91 .saturating_mul(writes),92 ))93 }94 pub fn save(self) -> DispatchResult {95 <CollectionById<T>>::insert(self.id, self.collection);96 Ok(())97 }98}99impl<T: Config> Deref for CollectionHandle<T> {100 type Target = Collection<T::AccountId>;101102 fn deref(&self) -> &Self::Target {103 &self.collection104 }105}106107impl<T: Config> DerefMut for CollectionHandle<T> {108 fn deref_mut(&mut self) -> &mut Self::Target {109 &mut self.collection110 }111}112113impl<T: Config> CollectionHandle<T> {114 pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {115 ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);116 Ok(())117 }118 pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {119 *subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))120 }121 pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {122 ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);123 Ok(())124 }125 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {126 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)127 }128 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {129 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)130 }131 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {132 ensure!(133 <Allowlist<T>>::get((self.id, user)),134 <Error<T>>::AddressNotInAllowlist135 );136 Ok(())137 }138}139140#[frame_support::pallet]141pub mod pallet {142 use super::*;143 use pallet_evm::account;144 use dispatch::CollectionDispatch;145 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};146 use frame_system::pallet_prelude::*;147 use frame_support::traits::Currency;148 use up_data_structs::{TokenId, mapping::TokenAddressMapping};149 use scale_info::TypeInfo;150151 #[pallet::config]152 pub trait Config:153 frame_system::Config + pallet_evm_coder_substrate::Config + TypeInfo + account::Config154 {155 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;156157 type Currency: Currency<Self::AccountId>;158159 #[pallet::constant]160 type CollectionCreationPrice: Get<161 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,162 >;163 type CollectionDispatch: CollectionDispatch<Self>;164165 type TreasuryAccountId: Get<Self::AccountId>;166167 type EvmTokenAddressMapping: TokenAddressMapping<H160>;168 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;169 }170171 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);172173 #[pallet::pallet]174 #[pallet::storage_version(STORAGE_VERSION)]175 #[pallet::generate_store(pub(super) trait Store)]176 pub struct Pallet<T>(_);177178 #[pallet::extra_constants]179 impl<T: Config> Pallet<T> {180 pub fn collection_admins_limit() -> u32 {181 COLLECTION_ADMINS_LIMIT182 }183 }184185 #[pallet::event]186 #[pallet::generate_deposit(pub fn deposit_event)]187 pub enum Event<T: Config> {188 189 190 191 192 193 194 195 196 197 CollectionCreated(CollectionId, u8, T::AccountId),198199 200 201 202 203 204 CollectionDestroyed(CollectionId),205206 207 208 209 210 211 212 213 214 215 216 217 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),218219 220 221 222 223 224 225 226 227 228 229 230 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),231232 233 234 235 236 237 238 239 240 241 242 243 Transfer(244 CollectionId,245 TokenId,246 T::CrossAccountId,247 T::CrossAccountId,248 u128,249 ),250251 252 253 254 255 256 257 258 259 260 Approved(261 CollectionId,262 TokenId,263 T::CrossAccountId,264 T::CrossAccountId,265 u128,266 ),267268 CollectionPropertySet(CollectionId, PropertyKey),269270 CollectionPropertyDeleted(CollectionId, PropertyKey),271272 TokenPropertySet(CollectionId, TokenId, PropertyKey),273274 TokenPropertyDeleted(CollectionId, TokenId, PropertyKey),275276 PropertyPermissionSet(CollectionId, PropertyKey),277 }278279 #[pallet::error]280 pub enum Error<T> {281 282 CollectionNotFound,283 284 MustBeTokenOwner,285 286 NoPermission,287 288 PublicMintingNotAllowed,289 290 AddressNotInAllowlist,291292 293 CollectionNameLimitExceeded,294 295 CollectionDescriptionLimitExceeded,296 297 CollectionTokenPrefixLimitExceeded,298 299 TotalCollectionsLimitExceeded,300 301 CollectionAdminCountExceeded,302 303 CollectionLimitBoundsExceeded,304 305 OwnerPermissionsCantBeReverted,306 307 TransferNotAllowed,308 309 AccountTokenLimitExceeded,310 311 CollectionTokenLimitExceeded,312 313 MetadataFlagFrozen,314315 316 TokenNotFound,317 318 TokenValueTooLow,319 320 ApprovedValueTooLow,321 322 CantApproveMoreThanOwned,323324 325 AddressIsZero,326 327 UnsupportedOperation,328329 330 NotSufficientFounds,331332 333 NestingIsDisabled,334 335 OnlyOwnerAllowedToNest,336 337 SourceCollectionIsNotAllowedToNest,338339 340 CollectionFieldSizeExceeded,341342 343 NoSpaceForProperty,344345 346 PropertyLimitReached,347348 349 UnableToReadUnboundedKeys,350351 352 InvalidCharacterInPropertyKey,353354 355 EmptyPropertyKey,356 }357358 #[pallet::storage]359 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;360 #[pallet::storage]361 pub type DestroyedCollectionCount<T> =362 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;363364 365 #[pallet::storage]366 pub type CollectionById<T> = StorageMap<367 Hasher = Blake2_128Concat,368 Key = CollectionId,369 Value = Collection<<T as frame_system::Config>::AccountId>,370 QueryKind = OptionQuery,371 >;372373 374 #[pallet::storage]375 #[pallet::getter(fn collection_properties)]376 pub type CollectionProperties<T> = StorageMap<377 Hasher = Blake2_128Concat,378 Key = CollectionId,379 Value = Properties,380 QueryKind = ValueQuery,381 OnEmpty = up_data_structs::CollectionProperties,382 >;383384 #[pallet::storage]385 #[pallet::getter(fn property_permissions)]386 pub type CollectionPropertyPermissions<T> = StorageMap<387 Hasher = Blake2_128Concat,388 Key = CollectionId,389 Value = PropertiesPermissionMap,390 QueryKind = ValueQuery,391 >;392393 394 #[pallet::storage]395 pub type CollectionData<T> = StorageNMap<396 Key = (397 Key<Twox64Concat, CollectionId>,398 Key<Twox64Concat, CollectionField>,399 ),400 Value = BoundedVec<u8, ConstU32<COLLECTION_FIELD_LIMIT>>,401 QueryKind = ValueQuery,402 >;403404 #[pallet::storage]405 pub type AdminAmount<T> = StorageMap<406 Hasher = Blake2_128Concat,407 Key = CollectionId,408 Value = u32,409 QueryKind = ValueQuery,410 >;411412 413 #[pallet::storage]414 pub type IsAdmin<T: Config> = StorageNMap<415 Key = (416 Key<Blake2_128Concat, CollectionId>,417 Key<Blake2_128Concat, T::CrossAccountId>,418 ),419 Value = bool,420 QueryKind = ValueQuery,421 >;422423 424 #[pallet::storage]425 pub type Allowlist<T: Config> = StorageNMap<426 Key = (427 Key<Blake2_128Concat, CollectionId>,428 Key<Blake2_128Concat, T::CrossAccountId>,429 ),430 Value = bool,431 QueryKind = ValueQuery,432 >;433434 435 #[pallet::storage]436 pub type DummyStorageValue<T: Config> = StorageValue<437 Value = (438 CollectionStats,439 CollectionId,440 TokenId,441 PhantomType<TokenData<T::CrossAccountId>>,442 PhantomType<RpcCollection<T::AccountId>>,443 ),444 QueryKind = OptionQuery,445 >;446447 #[pallet::hooks]448 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {449 fn on_runtime_upgrade() -> Weight {450 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {451 use up_data_structs::{CollectionVersion1, CollectionVersion2};452 <CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {453 Self::set_field_raw(454 id,455 CollectionField::OffchainSchema,456 v.offchain_schema.clone().into_inner(),457 )458 .expect("data has lower bounds than field");459 Self::set_field_raw(460 id,461 CollectionField::ConstOnChainSchema,462 v.const_on_chain_schema.clone().into_inner(),463 )464 .expect("data has lower bounds than field");465466 Some(CollectionVersion2::from(v))467 });468 }469470 0471 }472 }473}474475impl<T: Config> Pallet<T> {476 477 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {478 ensure!(479 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,480 <Error<T>>::AddressIsZero481 );482 Ok(())483 }484 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {485 <IsAdmin<T>>::iter_prefix((collection,))486 .map(|(a, _)| a)487 .collect()488 }489 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {490 <Allowlist<T>>::iter_prefix((collection,))491 .map(|(a, _)| a)492 .collect()493 }494 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {495 <Allowlist<T>>::get((collection, user))496 }497 pub fn collection_stats() -> CollectionStats {498 let created = <CreatedCollectionCount<T>>::get();499 let destroyed = <DestroyedCollectionCount<T>>::get();500 CollectionStats {501 created: created.0,502 destroyed: destroyed.0,503 alive: created.0 - destroyed.0,504 }505 }506507 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {508 let collection = <CollectionById<T>>::get(collection);509 if collection.is_none() {510 return None;511 }512513 let collection = collection.unwrap();514 let limits = collection.limits;515 let effective_limits = CollectionLimits {516 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),517 sponsored_data_size: Some(limits.sponsored_data_size()),518 sponsored_data_rate_limit: Some(519 limits520 .sponsored_data_rate_limit521 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),522 ),523 token_limit: Some(limits.token_limit()),524 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(525 match collection.mode {526 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,527 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,528 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,529 },530 )),531 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),532 owner_can_transfer: Some(limits.owner_can_transfer()),533 owner_can_destroy: Some(limits.owner_can_destroy()),534 transfers_enabled: Some(limits.transfers_enabled()),535 nesting_rule: Some(limits.nesting_rule().clone()),536 };537538 Some(effective_limits)539 }540541 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {542 let Collection {543 name,544 description,545 owner,546 mode,547 access,548 token_prefix,549 mint_mode,550 schema_version,551 sponsorship,552 limits,553 } = <CollectionById<T>>::get(collection)?;554555 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)556 .iter()557 .map(|(key, permission)| PropertyKeyPermission {558 key: key.clone(),559 permission: permission.clone(),560 })561 .collect();562563 let properties = <CollectionProperties<T>>::get(collection)564 .iter()565 .map(|(key, value)| Property {566 key: key.clone(),567 value: value.clone(),568 })569 .collect();570571 Some(RpcCollection {572 name: name.into_inner(),573 description: description.into_inner(),574 owner,575 mode,576 access,577 token_prefix: token_prefix.into_inner(),578 mint_mode,579 schema_version,580 sponsorship,581 limits,582 offchain_schema: <CollectionData<T>>::get((583 collection,584 CollectionField::OffchainSchema,585 ))586 .into_inner(),587 const_on_chain_schema: <CollectionData<T>>::get((588 collection,589 CollectionField::ConstOnChainSchema,590 ))591 .into_inner(),592 token_property_permissions,593 properties,594 })595 }596}597598impl<T: Config> Pallet<T> {599 pub fn init_collection(600 owner: T::AccountId,601 data: CreateCollectionData<T::AccountId>,602 ) -> Result<CollectionId, DispatchError> {603 {604 ensure!(605 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,606 Error::<T>::CollectionTokenPrefixLimitExceeded607 );608 }609610 let created_count = <CreatedCollectionCount<T>>::get()611 .0612 .checked_add(1)613 .ok_or(ArithmeticError::Overflow)?;614 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;615 let id = CollectionId(created_count);616617 618 ensure!(619 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,620 <Error<T>>::TotalCollectionsLimitExceeded621 );622623 624625 let collection = Collection {626 owner: owner.clone(),627 name: data.name,628 mode: data.mode.clone(),629 mint_mode: false,630 access: data.access.unwrap_or_default(),631 description: data.description,632 token_prefix: data.token_prefix,633 schema_version: data.schema_version.unwrap_or_default(),634 sponsorship: data635 .pending_sponsor636 .map(SponsorshipState::Unconfirmed)637 .unwrap_or_default(),638 limits: data639 .limits640 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))641 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,642 };643644 let mut collection_properties = up_data_structs::CollectionProperties::get();645 collection_properties646 .try_set_from_iter(data.properties.into_iter().map(|p| (p.key, p.value)))647 .map_err(<Error<T>>::from)?;648649 CollectionProperties::<T>::insert(id, collection_properties);650651 let mut token_props_permissions = PropertiesPermissionMap::new();652 token_props_permissions653 .try_set_from_iter(654 data.token_property_permissions655 .into_iter()656 .map(|property| (property.key, property.permission)),657 )658 .map_err(<Error<T>>::from)?;659660 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);661662 663 {664 let mut imbalance =665 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();666 imbalance.subsume(667 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(668 &T::TreasuryAccountId::get(),669 T::CollectionCreationPrice::get(),670 ),671 );672 <T as Config>::Currency::settle(673 &owner,674 imbalance,675 WithdrawReasons::TRANSFER,676 ExistenceRequirement::KeepAlive,677 )678 .map_err(|_| Error::<T>::NotSufficientFounds)?;679 }680681 <CreatedCollectionCount<T>>::put(created_count);682 <Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));683 <CollectionById<T>>::insert(id, collection);684 Self::set_field_raw(685 id,686 CollectionField::OffchainSchema,687 data.offchain_schema.into_inner(),688 )689 .expect("data has lower bounds than field");690 Self::set_field_raw(691 id,692 CollectionField::ConstOnChainSchema,693 data.const_on_chain_schema.into_inner(),694 )695 .expect("data has lower bounds than field");696 Ok(id)697 }698699 pub fn destroy_collection(700 collection: CollectionHandle<T>,701 sender: &T::CrossAccountId,702 ) -> DispatchResult {703 ensure!(704 collection.limits.owner_can_destroy(),705 <Error<T>>::NoPermission,706 );707 collection.check_is_owner(sender)?;708709 let destroyed_collections = <DestroyedCollectionCount<T>>::get()710 .0711 .checked_add(1)712 .ok_or(ArithmeticError::Overflow)?;713714 715716 <DestroyedCollectionCount<T>>::put(destroyed_collections);717 <CollectionById<T>>::remove(collection.id);718 <CollectionData<T>>::remove_prefix((collection.id,), None);719 <AdminAmount<T>>::remove(collection.id);720 <IsAdmin<T>>::remove_prefix((collection.id,), None);721 <Allowlist<T>>::remove_prefix((collection.id,), None);722723 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));724 Ok(())725 }726727 pub fn set_collection_property(728 collection: &CollectionHandle<T>,729 sender: &T::CrossAccountId,730 property: Property,731 ) -> DispatchResult {732 collection.check_is_owner_or_admin(sender)?;733734 CollectionProperties::<T>::try_mutate(collection.id, |properties| {735 let property = property.clone();736 properties.try_set(property.key, property.value)737 })738 .map_err(<Error<T>>::from)?;739740 Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));741742 Ok(())743 }744745 pub fn set_collection_properties(746 collection: &CollectionHandle<T>,747 sender: &T::CrossAccountId,748 properties: Vec<Property>,749 ) -> DispatchResult {750 for property in properties {751 Self::set_collection_property(collection, sender, property)?;752 }753754 Ok(())755 }756757 pub fn delete_collection_property(758 collection: &CollectionHandle<T>,759 sender: &T::CrossAccountId,760 property_key: PropertyKey,761 ) -> DispatchResult {762 collection.check_is_owner_or_admin(sender)?;763764 CollectionProperties::<T>::try_mutate(collection.id, |properties| {765 properties.remove(&property_key)766 })767 .map_err(<Error<T>>::from)?;768769 Self::deposit_event(Event::CollectionPropertyDeleted(770 collection.id,771 property_key,772 ));773774 Ok(())775 }776777 pub fn delete_collection_properties(778 collection: &CollectionHandle<T>,779 sender: &T::CrossAccountId,780 property_keys: Vec<PropertyKey>,781 ) -> DispatchResult {782 for key in property_keys {783 Self::delete_collection_property(collection, sender, key)?;784 }785786 Ok(())787 }788789 pub fn set_property_permission(790 collection: &CollectionHandle<T>,791 sender: &T::CrossAccountId,792 property_permission: PropertyKeyPermission,793 ) -> DispatchResult {794 collection.check_is_owner_or_admin(sender)?;795796 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);797 let current_permission = all_permissions.get(&property_permission.key);798 if matches![799 current_permission,800 Some(PropertyPermission { mutable: false, .. })801 ] {802 return Err(<Error<T>>::NoPermission.into());803 }804805 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {806 let property_permission = property_permission.clone();807 permissions.try_set(property_permission.key, property_permission.permission)808 })809 .map_err(<Error<T>>::from)?;810811 Self::deposit_event(Event::PropertyPermissionSet(812 collection.id,813 property_permission.key,814 ));815816 Ok(())817 }818819 pub fn set_property_permissions(820 collection: &CollectionHandle<T>,821 sender: &T::CrossAccountId,822 property_permissions: Vec<PropertyKeyPermission>,823 ) -> DispatchResult {824 for prop_pemission in property_permissions {825 Self::set_property_permission(collection, sender, prop_pemission)?;826 }827828 Ok(())829 }830831 pub fn bytes_keys_to_property_keys(832 keys: Vec<Vec<u8>>,833 ) -> Result<Vec<PropertyKey>, DispatchError> {834 keys.into_iter()835 .map(|key| -> Result<PropertyKey, DispatchError> {836 key.try_into()837 .map_err(|_| <Error<T>>::UnableToReadUnboundedKeys.into())838 })839 .collect::<Result<Vec<PropertyKey>, DispatchError>>()840 }841842 pub fn check_property_key(key: &PropertyKey) -> Result<(), DispatchError> {843 let key_str = sp_std::str::from_utf8(key.as_slice())844 .map_err(|_| <Error<T>>::InvalidCharacterInPropertyKey)?;845846 for ch in key_str.chars() {847 if !ch.is_ascii_alphanumeric() && ch != '_' && ch != '-' {848 return Err(<Error<T>>::InvalidCharacterInPropertyKey.into());849 }850 }851852 Ok(())853 }854855 pub fn filter_collection_properties(856 collection_id: CollectionId,857 keys: Vec<PropertyKey>,858 ) -> Result<Vec<Property>, DispatchError> {859 let properties = Self::collection_properties(collection_id);860861 let properties = keys862 .into_iter()863 .filter_map(|key| {864 properties.get(&key).map(|value| Property {865 key,866 value: value.clone(),867 })868 })869 .collect();870871 Ok(properties)872 }873874 pub fn filter_property_permissions(875 collection_id: CollectionId,876 keys: Vec<PropertyKey>,877 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {878 let permissions = Self::property_permissions(collection_id);879880 let key_permissions = keys881 .into_iter()882 .filter_map(|key| {883 permissions884 .get(&key)885 .map(|permission| PropertyKeyPermission {886 key,887 permission: permission.clone(),888 })889 })890 .collect();891892 Ok(key_permissions)893 }894895 fn set_field_raw(896 collection_id: CollectionId,897 field: CollectionField,898 value: Vec<u8>,899 ) -> DispatchResult {900 if !value.is_empty() {901 <CollectionData<T>>::insert(902 (collection_id, field),903 BoundedVec::try_from(value).map_err(|_| <Error<T>>::CollectionFieldSizeExceeded)?,904 )905 } else {906 <CollectionData<T>>::remove((collection_id, field));907 }908 Ok(())909 }910911 pub fn set_field(912 collection: &CollectionHandle<T>,913 sender: &T::CrossAccountId,914 field: CollectionField,915 value: Vec<u8>,916 ) -> DispatchResult {917 collection.check_is_owner_or_admin(sender)?;918919 920921 Self::set_field_raw(collection.id, field, value)922 }923924 pub fn toggle_allowlist(925 collection: &CollectionHandle<T>,926 sender: &T::CrossAccountId,927 user: &T::CrossAccountId,928 allowed: bool,929 ) -> DispatchResult {930 collection.check_is_owner_or_admin(sender)?;931932 933934 if allowed {935 <Allowlist<T>>::insert((collection.id, user), true);936 } else {937 <Allowlist<T>>::remove((collection.id, user));938 }939940 Ok(())941 }942943 pub fn toggle_admin(944 collection: &CollectionHandle<T>,945 sender: &T::CrossAccountId,946 user: &T::CrossAccountId,947 admin: bool,948 ) -> DispatchResult {949 collection.check_is_owner_or_admin(sender)?;950951 let was_admin = <IsAdmin<T>>::get((collection.id, user));952 if was_admin == admin {953 return Ok(());954 }955 let amount = <AdminAmount<T>>::get(collection.id);956957 if admin {958 let amount = amount959 .checked_add(1)960 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;961 ensure!(962 amount <= Self::collection_admins_limit(),963 <Error<T>>::CollectionAdminCountExceeded,964 );965966 967968 <AdminAmount<T>>::insert(collection.id, amount);969 <IsAdmin<T>>::insert((collection.id, user), true);970 } else {971 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));972 <IsAdmin<T>>::remove((collection.id, user));973 }974975 Ok(())976 }977978 pub fn clamp_limits(979 mode: CollectionMode,980 old_limit: &CollectionLimits,981 mut new_limit: CollectionLimits,982 ) -> Result<CollectionLimits, DispatchError> {983 macro_rules! limit_default {984 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{985 $(986 if let Some($new) = $new.$field {987 let $old = $old.$field($($arg)?);988 let _ = $new;989 let _ = $old;990 $check991 } else {992 $new.$field = $old.$field993 }994 )*995 }};996 }997998 limit_default!(old_limit, new_limit,999 account_token_ownership_limit => ensure!(1000 new_limit <= MAX_TOKEN_OWNERSHIP,1001 <Error<T>>::CollectionLimitBoundsExceeded,1002 ),1003 sponsor_transfer_timeout(match mode {1004 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1005 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1006 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1007 }) => ensure!(1008 new_limit <= MAX_SPONSOR_TIMEOUT,1009 <Error<T>>::CollectionLimitBoundsExceeded,1010 ),1011 sponsored_data_size => ensure!(1012 new_limit <= CUSTOM_DATA_LIMIT,1013 <Error<T>>::CollectionLimitBoundsExceeded,1014 ),1015 token_limit => ensure!(1016 old_limit >= new_limit && new_limit > 0,1017 <Error<T>>::CollectionTokenLimitExceeded1018 ),1019 owner_can_transfer => ensure!(1020 old_limit || !new_limit,1021 <Error<T>>::OwnerPermissionsCantBeReverted,1022 ),1023 owner_can_destroy => ensure!(1024 old_limit || !new_limit,1025 <Error<T>>::OwnerPermissionsCantBeReverted,1026 ),1027 sponsored_data_rate_limit => {},1028 transfers_enabled => {},1029 );1030 Ok(new_limit)1031 }1032}10331034#[macro_export]1035macro_rules! unsupported {1036 () => {1037 Err(<Error<T>>::UnsupportedOperation.into())1038 };1039}104010411042pub trait CommonWeightInfo<CrossAccountId> {1043 fn create_item() -> Weight;1044 fn create_multiple_items(amount: u32) -> Weight;1045 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1046 fn burn_item() -> Weight;1047 fn set_collection_properties(amount: u32) -> Weight;1048 fn delete_collection_properties(amount: u32) -> Weight;1049 fn set_token_properties(amount: u32) -> Weight;1050 fn delete_token_properties(amount: u32) -> Weight;1051 fn set_property_permissions(amount: u32) -> Weight;1052 fn transfer() -> Weight;1053 fn approve() -> Weight;1054 fn transfer_from() -> Weight;1055 fn burn_from() -> Weight;1056}10571058pub trait CommonCollectionOperations<T: Config> {1059 fn create_item(1060 &self,1061 sender: T::CrossAccountId,1062 to: T::CrossAccountId,1063 data: CreateItemData,1064 nesting_budget: &dyn Budget,1065 ) -> DispatchResultWithPostInfo;1066 fn create_multiple_items(1067 &self,1068 sender: T::CrossAccountId,1069 to: T::CrossAccountId,1070 data: Vec<CreateItemData>,1071 nesting_budget: &dyn Budget,1072 ) -> DispatchResultWithPostInfo;1073 fn create_multiple_items_ex(1074 &self,1075 sender: T::CrossAccountId,1076 data: CreateItemExData<T::CrossAccountId>,1077 nesting_budget: &dyn Budget,1078 ) -> DispatchResultWithPostInfo;1079 fn burn_item(1080 &self,1081 sender: T::CrossAccountId,1082 token: TokenId,1083 amount: u128,1084 ) -> DispatchResultWithPostInfo;1085 fn set_collection_properties(1086 &self,1087 sender: T::CrossAccountId,1088 properties: Vec<Property>,1089 ) -> DispatchResultWithPostInfo;1090 fn delete_collection_properties(1091 &self,1092 sender: &T::CrossAccountId,1093 property_keys: Vec<PropertyKey>,1094 ) -> DispatchResultWithPostInfo;1095 fn set_token_properties(1096 &self,1097 sender: T::CrossAccountId,1098 token_id: TokenId,1099 property: Vec<Property>,1100 ) -> DispatchResultWithPostInfo;1101 fn delete_token_properties(1102 &self,1103 sender: T::CrossAccountId,1104 token_id: TokenId,1105 property_keys: Vec<PropertyKey>,1106 ) -> DispatchResultWithPostInfo;1107 fn set_property_permissions(1108 &self,1109 sender: &T::CrossAccountId,1110 property_permissions: Vec<PropertyKeyPermission>,1111 ) -> DispatchResultWithPostInfo;1112 fn transfer(1113 &self,1114 sender: T::CrossAccountId,1115 to: T::CrossAccountId,1116 token: TokenId,1117 amount: u128,1118 nesting_budget: &dyn Budget,1119 ) -> DispatchResultWithPostInfo;1120 fn approve(1121 &self,1122 sender: T::CrossAccountId,1123 spender: T::CrossAccountId,1124 token: TokenId,1125 amount: u128,1126 ) -> DispatchResultWithPostInfo;1127 fn transfer_from(1128 &self,1129 sender: T::CrossAccountId,1130 from: T::CrossAccountId,1131 to: T::CrossAccountId,1132 token: TokenId,1133 amount: u128,1134 nesting_budget: &dyn Budget,1135 ) -> DispatchResultWithPostInfo;1136 fn burn_from(1137 &self,1138 sender: T::CrossAccountId,1139 from: T::CrossAccountId,1140 token: TokenId,1141 amount: u128,1142 nesting_budget: &dyn Budget,1143 ) -> DispatchResultWithPostInfo;11441145 fn check_nesting(1146 &self,1147 sender: T::CrossAccountId,1148 from: (CollectionId, TokenId),1149 under: TokenId,1150 budget: &dyn Budget,1151 ) -> DispatchResult;11521153 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1154 fn collection_tokens(&self) -> Vec<TokenId>;1155 fn token_exists(&self, token: TokenId) -> bool;1156 fn last_token_id(&self) -> TokenId;11571158 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1159 fn const_metadata(&self, token: TokenId) -> Vec<u8>;1160 fn token_properties(&self, token_id: TokenId, keys: Vec<PropertyKey>) -> Vec<Property>;1161 1162 fn total_supply(&self) -> u32;1163 1164 fn account_balance(&self, account: T::CrossAccountId) -> u32;1165 1166 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1167 fn allowance(1168 &self,1169 sender: T::CrossAccountId,1170 spender: T::CrossAccountId,1171 token: TokenId,1172 ) -> u128;1173}117411751176pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1177 let post_info = PostDispatchInfo {1178 actual_weight: Some(weight),1179 pays_fee: Pays::Yes,1180 };1181 match res {1182 Ok(()) => Ok(post_info),1183 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1184 }1185}11861187impl<T: Config> From<PropertiesError> for Error<T> {1188 fn from(error: PropertiesError) -> Self {1189 match error {1190 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1191 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1192 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1193 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1194 }1195 }1196}