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 transactional,30};31use pallet_evm::GasWeightMapping;32use up_data_structs::{33 COLLECTION_NUMBER_LIMIT, Collection, RpcCollection, CollectionId, CreateItemData,34 MAX_TOKEN_PREFIX_LENGTH, COLLECTION_ADMINS_LIMIT, TokenId, CollectionStats,35 MAX_TOKEN_OWNERSHIP, CollectionMode, NFT_SPONSOR_TRANSFER_TIMEOUT,36 FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, MAX_SPONSOR_TIMEOUT,37 CUSTOM_DATA_LIMIT, CollectionLimits, CreateCollectionData, SponsorshipState, CreateItemExData,38 SponsoringRateLimit, budget::Budget, COLLECTION_FIELD_LIMIT, CollectionField, PhantomType,39 Property, Properties, PropertiesPermissionMap, PropertyKey, PropertyPermission,40 PropertiesError, PropertyKeyPermission, TokenData, TrySet,41};42pub use pallet::*;43use sp_core::H160;44use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};45#[cfg(feature = "runtime-benchmarks")]46pub mod benchmarking;47pub mod dispatch;48pub mod erc;49pub mod eth;5051#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]52pub struct CollectionHandle<T: Config> {53 pub id: CollectionId,54 collection: Collection<T::AccountId>,55 pub recorder: SubstrateRecorder<T>,56}57impl<T: Config> WithRecorder<T> for CollectionHandle<T> {58 fn recorder(&self) -> &SubstrateRecorder<T> {59 &self.recorder60 }61 fn into_recorder(self) -> SubstrateRecorder<T> {62 self.recorder63 }64}65impl<T: Config> CollectionHandle<T> {66 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {67 <CollectionById<T>>::get(id).map(|collection| Self {68 id,69 collection,70 recorder: SubstrateRecorder::new(gas_limit),71 })72 }73 pub fn new(id: CollectionId) -> Option<Self> {74 Self::new_with_gas_limit(id, u64::MAX)75 }76 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {77 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)78 }79 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {80 self.recorder81 .consume_gas(T::GasWeightMapping::weight_to_gas(82 <T as frame_system::Config>::DbWeight::get()83 .read84 .saturating_mul(reads),85 ))86 }87 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {88 self.recorder89 .consume_gas(T::GasWeightMapping::weight_to_gas(90 <T as frame_system::Config>::DbWeight::get()91 .write92 .saturating_mul(writes),93 ))94 }95 pub fn save(self) -> DispatchResult {96 <CollectionById<T>>::insert(self.id, self.collection);97 Ok(())98 }99}100impl<T: Config> Deref for CollectionHandle<T> {101 type Target = Collection<T::AccountId>;102103 fn deref(&self) -> &Self::Target {104 &self.collection105 }106}107108impl<T: Config> DerefMut for CollectionHandle<T> {109 fn deref_mut(&mut self) -> &mut Self::Target {110 &mut self.collection111 }112}113114impl<T: Config> CollectionHandle<T> {115 pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {116 ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);117 Ok(())118 }119 pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {120 *subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))121 }122 pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {123 ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);124 Ok(())125 }126 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {127 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)128 }129 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {130 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)131 }132 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {133 ensure!(134 <Allowlist<T>>::get((self.id, user)),135 <Error<T>>::AddressNotInAllowlist136 );137 Ok(())138 }139}140141#[frame_support::pallet]142pub mod pallet {143 use super::*;144 use pallet_evm::account;145 use dispatch::CollectionDispatch;146 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};147 use frame_system::pallet_prelude::*;148 use frame_support::traits::Currency;149 use up_data_structs::{TokenId, mapping::TokenAddressMapping};150 use scale_info::TypeInfo;151152 #[pallet::config]153 pub trait Config:154 frame_system::Config + pallet_evm_coder_substrate::Config + TypeInfo + account::Config155 {156 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;157158 type Currency: Currency<Self::AccountId>;159160 #[pallet::constant]161 type CollectionCreationPrice: Get<162 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,163 >;164 type CollectionDispatch: CollectionDispatch<Self>;165166 type TreasuryAccountId: Get<Self::AccountId>;167168 type EvmTokenAddressMapping: TokenAddressMapping<H160>;169 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;170 }171172 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);173174 #[pallet::pallet]175 #[pallet::storage_version(STORAGE_VERSION)]176 #[pallet::generate_store(pub(super) trait Store)]177 pub struct Pallet<T>(_);178179 #[pallet::extra_constants]180 impl<T: Config> Pallet<T> {181 pub fn collection_admins_limit() -> u32 {182 COLLECTION_ADMINS_LIMIT183 }184 }185186 #[pallet::event]187 #[pallet::generate_deposit(pub fn deposit_event)]188 pub enum Event<T: Config> {189 190 191 192 193 194 195 196 197 198 CollectionCreated(CollectionId, u8, T::AccountId),199200 201 202 203 204 205 CollectionDestroyed(CollectionId),206207 208 209 210 211 212 213 214 215 216 217 218 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),219220 221 222 223 224 225 226 227 228 229 230 231 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),232233 234 235 236 237 238 239 240 241 242 243 244 Transfer(245 CollectionId,246 TokenId,247 T::CrossAccountId,248 T::CrossAccountId,249 u128,250 ),251252 253 254 255 256 257 258 259 260 261 Approved(262 CollectionId,263 TokenId,264 T::CrossAccountId,265 T::CrossAccountId,266 u128,267 ),268269 CollectionPropertySet(CollectionId, PropertyKey),270271 CollectionPropertyDeleted(CollectionId, PropertyKey),272273 TokenPropertySet(CollectionId, TokenId, PropertyKey),274275 TokenPropertyDeleted(CollectionId, TokenId, PropertyKey),276277 PropertyPermissionSet(CollectionId, PropertyKey),278 }279280 #[pallet::error]281 pub enum Error<T> {282 283 CollectionNotFound,284 285 MustBeTokenOwner,286 287 NoPermission,288 289 PublicMintingNotAllowed,290 291 AddressNotInAllowlist,292293 294 CollectionNameLimitExceeded,295 296 CollectionDescriptionLimitExceeded,297 298 CollectionTokenPrefixLimitExceeded,299 300 TotalCollectionsLimitExceeded,301 302 CollectionAdminCountExceeded,303 304 CollectionLimitBoundsExceeded,305 306 OwnerPermissionsCantBeReverted,307 308 TransferNotAllowed,309 310 AccountTokenLimitExceeded,311 312 CollectionTokenLimitExceeded,313 314 MetadataFlagFrozen,315316 317 TokenNotFound,318 319 TokenValueTooLow,320 321 ApprovedValueTooLow,322 323 CantApproveMoreThanOwned,324325 326 AddressIsZero,327 328 UnsupportedOperation,329330 331 NotSufficientFounds,332333 334 NestingIsDisabled,335 336 OnlyOwnerAllowedToNest,337 338 SourceCollectionIsNotAllowedToNest,339340 341 CollectionFieldSizeExceeded,342343 344 NoSpaceForProperty,345346 347 PropertyLimitReached,348349 350 UnableToReadUnboundedKeys,351352 353 InvalidCharacterInPropertyKey,354355 356 EmptyPropertyKey,357 }358359 #[pallet::storage]360 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;361 #[pallet::storage]362 pub type DestroyedCollectionCount<T> =363 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;364365 366 #[pallet::storage]367 pub type CollectionById<T> = StorageMap<368 Hasher = Blake2_128Concat,369 Key = CollectionId,370 Value = Collection<<T as frame_system::Config>::AccountId>,371 QueryKind = OptionQuery,372 >;373374 375 #[pallet::storage]376 #[pallet::getter(fn collection_properties)]377 pub type CollectionProperties<T> = StorageMap<378 Hasher = Blake2_128Concat,379 Key = CollectionId,380 Value = Properties,381 QueryKind = ValueQuery,382 OnEmpty = up_data_structs::CollectionProperties,383 >;384385 #[pallet::storage]386 #[pallet::getter(fn property_permissions)]387 pub type CollectionPropertyPermissions<T> = StorageMap<388 Hasher = Blake2_128Concat,389 Key = CollectionId,390 Value = PropertiesPermissionMap,391 QueryKind = ValueQuery,392 >;393394 395 #[pallet::storage]396 pub type CollectionData<T> = StorageNMap<397 Key = (398 Key<Twox64Concat, CollectionId>,399 Key<Twox64Concat, CollectionField>,400 ),401 Value = BoundedVec<u8, ConstU32<COLLECTION_FIELD_LIMIT>>,402 QueryKind = ValueQuery,403 >;404405 #[pallet::storage]406 pub type AdminAmount<T> = StorageMap<407 Hasher = Blake2_128Concat,408 Key = CollectionId,409 Value = u32,410 QueryKind = ValueQuery,411 >;412413 414 #[pallet::storage]415 pub type IsAdmin<T: Config> = StorageNMap<416 Key = (417 Key<Blake2_128Concat, CollectionId>,418 Key<Blake2_128Concat, T::CrossAccountId>,419 ),420 Value = bool,421 QueryKind = ValueQuery,422 >;423424 425 #[pallet::storage]426 pub type Allowlist<T: Config> = StorageNMap<427 Key = (428 Key<Blake2_128Concat, CollectionId>,429 Key<Blake2_128Concat, T::CrossAccountId>,430 ),431 Value = bool,432 QueryKind = ValueQuery,433 >;434435 436 #[pallet::storage]437 pub type DummyStorageValue<T: Config> = StorageValue<438 Value = (439 CollectionStats,440 CollectionId,441 TokenId,442 PhantomType<TokenData<T::CrossAccountId>>,443 PhantomType<RpcCollection<T::AccountId>>,444 ),445 QueryKind = OptionQuery,446 >;447448 #[pallet::hooks]449 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {450 fn on_runtime_upgrade() -> Weight {451 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {452 use up_data_structs::{CollectionVersion1, CollectionVersion2};453 <CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {454 Self::set_field_raw(455 id,456 CollectionField::OffchainSchema,457 v.offchain_schema.clone().into_inner(),458 )459 .expect("data has lower bounds than field");460 Self::set_field_raw(461 id,462 CollectionField::ConstOnChainSchema,463 v.const_on_chain_schema.clone().into_inner(),464 )465 .expect("data has lower bounds than field");466467 Some(CollectionVersion2::from(v))468 });469 }470471 0472 }473 }474}475476impl<T: Config> Pallet<T> {477 478 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {479 ensure!(480 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,481 <Error<T>>::AddressIsZero482 );483 Ok(())484 }485 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {486 <IsAdmin<T>>::iter_prefix((collection,))487 .map(|(a, _)| a)488 .collect()489 }490 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {491 <Allowlist<T>>::iter_prefix((collection,))492 .map(|(a, _)| a)493 .collect()494 }495 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {496 <Allowlist<T>>::get((collection, user))497 }498 pub fn collection_stats() -> CollectionStats {499 let created = <CreatedCollectionCount<T>>::get();500 let destroyed = <DestroyedCollectionCount<T>>::get();501 CollectionStats {502 created: created.0,503 destroyed: destroyed.0,504 alive: created.0 - destroyed.0,505 }506 }507508 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {509 let collection = <CollectionById<T>>::get(collection);510 if collection.is_none() {511 return None;512 }513514 let collection = collection.unwrap();515 let limits = collection.limits;516 let effective_limits = CollectionLimits {517 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),518 sponsored_data_size: Some(limits.sponsored_data_size()),519 sponsored_data_rate_limit: Some(520 limits521 .sponsored_data_rate_limit522 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),523 ),524 token_limit: Some(limits.token_limit()),525 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(526 match collection.mode {527 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,528 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,529 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,530 },531 )),532 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),533 owner_can_transfer: Some(limits.owner_can_transfer()),534 owner_can_destroy: Some(limits.owner_can_destroy()),535 transfers_enabled: Some(limits.transfers_enabled()),536 nesting_rule: Some(limits.nesting_rule().clone()),537 };538539 Some(effective_limits)540 }541542 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {543 let Collection {544 name,545 description,546 owner,547 mode,548 access,549 token_prefix,550 mint_mode,551 schema_version,552 sponsorship,553 limits,554 } = <CollectionById<T>>::get(collection)?;555556 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)557 .iter()558 .map(|(key, permission)| PropertyKeyPermission {559 key: key.clone(),560 permission: permission.clone(),561 })562 .collect();563564 let properties = <CollectionProperties<T>>::get(collection)565 .iter()566 .map(|(key, value)| Property {567 key: key.clone(),568 value: value.clone(),569 })570 .collect();571572 Some(RpcCollection {573 name: name.into_inner(),574 description: description.into_inner(),575 owner,576 mode,577 access,578 token_prefix: token_prefix.into_inner(),579 mint_mode,580 schema_version,581 sponsorship,582 limits,583 offchain_schema: <CollectionData<T>>::get((584 collection,585 CollectionField::OffchainSchema,586 ))587 .into_inner(),588 const_on_chain_schema: <CollectionData<T>>::get((589 collection,590 CollectionField::ConstOnChainSchema,591 ))592 .into_inner(),593 token_property_permissions,594 properties,595 })596 }597}598599impl<T: Config> Pallet<T> {600 pub fn init_collection(601 owner: T::AccountId,602 data: CreateCollectionData<T::AccountId>,603 ) -> Result<CollectionId, DispatchError> {604 {605 ensure!(606 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,607 Error::<T>::CollectionTokenPrefixLimitExceeded608 );609 }610611 let created_count = <CreatedCollectionCount<T>>::get()612 .0613 .checked_add(1)614 .ok_or(ArithmeticError::Overflow)?;615 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;616 let id = CollectionId(created_count);617618 619 ensure!(620 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,621 <Error<T>>::TotalCollectionsLimitExceeded622 );623624 625626 let collection = Collection {627 owner: owner.clone(),628 name: data.name,629 mode: data.mode.clone(),630 mint_mode: false,631 access: data.access.unwrap_or_default(),632 description: data.description,633 token_prefix: data.token_prefix,634 schema_version: data.schema_version.unwrap_or_default(),635 sponsorship: data636 .pending_sponsor637 .map(SponsorshipState::Unconfirmed)638 .unwrap_or_default(),639 limits: data640 .limits641 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))642 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,643 };644645 let mut collection_properties = up_data_structs::CollectionProperties::get();646 collection_properties647 .try_set_from_iter(data.properties.into_iter().map(|p| (p.key, p.value)))648 .map_err(<Error<T>>::from)?;649650 CollectionProperties::<T>::insert(id, collection_properties);651652 let mut token_props_permissions = PropertiesPermissionMap::new();653 token_props_permissions654 .try_set_from_iter(655 data.token_property_permissions656 .into_iter()657 .map(|property| (property.key, property.permission)),658 )659 .map_err(<Error<T>>::from)?;660661 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);662663 664 {665 let mut imbalance =666 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();667 imbalance.subsume(668 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(669 &T::TreasuryAccountId::get(),670 T::CollectionCreationPrice::get(),671 ),672 );673 <T as Config>::Currency::settle(674 &owner,675 imbalance,676 WithdrawReasons::TRANSFER,677 ExistenceRequirement::KeepAlive,678 )679 .map_err(|_| Error::<T>::NotSufficientFounds)?;680 }681682 <CreatedCollectionCount<T>>::put(created_count);683 <Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));684 <CollectionById<T>>::insert(id, collection);685 Self::set_field_raw(686 id,687 CollectionField::OffchainSchema,688 data.offchain_schema.into_inner(),689 )690 .expect("data has lower bounds than field");691 Self::set_field_raw(692 id,693 CollectionField::ConstOnChainSchema,694 data.const_on_chain_schema.into_inner(),695 )696 .expect("data has lower bounds than field");697 Ok(id)698 }699700 pub fn destroy_collection(701 collection: CollectionHandle<T>,702 sender: &T::CrossAccountId,703 ) -> DispatchResult {704 ensure!(705 collection.limits.owner_can_destroy(),706 <Error<T>>::NoPermission,707 );708 collection.check_is_owner(sender)?;709710 let destroyed_collections = <DestroyedCollectionCount<T>>::get()711 .0712 .checked_add(1)713 .ok_or(ArithmeticError::Overflow)?;714715 716717 <DestroyedCollectionCount<T>>::put(destroyed_collections);718 <CollectionById<T>>::remove(collection.id);719 <CollectionData<T>>::remove_prefix((collection.id,), None);720 <AdminAmount<T>>::remove(collection.id);721 <IsAdmin<T>>::remove_prefix((collection.id,), None);722 <Allowlist<T>>::remove_prefix((collection.id,), None);723724 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));725 Ok(())726 }727728 pub fn set_collection_property(729 collection: &CollectionHandle<T>,730 sender: &T::CrossAccountId,731 property: Property,732 ) -> DispatchResult {733 collection.check_is_owner_or_admin(sender)?;734735 CollectionProperties::<T>::try_mutate(collection.id, |properties| {736 let property = property.clone();737 properties.try_set(property.key, property.value)738 })739 .map_err(<Error<T>>::from)?;740741 Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));742743 Ok(())744 }745746 #[transactional]747 pub fn set_collection_properties(748 collection: &CollectionHandle<T>,749 sender: &T::CrossAccountId,750 properties: Vec<Property>,751 ) -> DispatchResult {752 for property in properties {753 Self::set_collection_property(collection, sender, property)?;754 }755756 Ok(())757 }758759 pub fn delete_collection_property(760 collection: &CollectionHandle<T>,761 sender: &T::CrossAccountId,762 property_key: PropertyKey,763 ) -> DispatchResult {764 collection.check_is_owner_or_admin(sender)?;765766 CollectionProperties::<T>::try_mutate(collection.id, |properties| {767 properties.remove(&property_key)768 })769 .map_err(<Error<T>>::from)?;770771 Self::deposit_event(Event::CollectionPropertyDeleted(772 collection.id,773 property_key,774 ));775776 Ok(())777 }778779 #[transactional]780 pub fn delete_collection_properties(781 collection: &CollectionHandle<T>,782 sender: &T::CrossAccountId,783 property_keys: Vec<PropertyKey>,784 ) -> DispatchResult {785 for key in property_keys {786 Self::delete_collection_property(collection, sender, key)?;787 }788789 Ok(())790 }791792 pub fn set_property_permission(793 collection: &CollectionHandle<T>,794 sender: &T::CrossAccountId,795 property_permission: PropertyKeyPermission,796 ) -> DispatchResult {797 collection.check_is_owner_or_admin(sender)?;798799 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);800 let current_permission = all_permissions.get(&property_permission.key);801 if matches![802 current_permission,803 Some(PropertyPermission { mutable: false, .. })804 ] {805 return Err(<Error<T>>::NoPermission.into());806 }807808 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {809 let property_permission = property_permission.clone();810 permissions.try_set(property_permission.key, property_permission.permission)811 })812 .map_err(<Error<T>>::from)?;813814 Self::deposit_event(Event::PropertyPermissionSet(815 collection.id,816 property_permission.key,817 ));818819 Ok(())820 }821822 #[transactional]823 pub fn set_property_permissions(824 collection: &CollectionHandle<T>,825 sender: &T::CrossAccountId,826 property_permissions: Vec<PropertyKeyPermission>,827 ) -> DispatchResult {828 for prop_pemission in property_permissions {829 Self::set_property_permission(collection, sender, prop_pemission)?;830 }831832 Ok(())833 }834835 pub fn bytes_keys_to_property_keys(836 keys: Vec<Vec<u8>>,837 ) -> Result<Vec<PropertyKey>, DispatchError> {838 keys.into_iter()839 .map(|key| -> Result<PropertyKey, DispatchError> {840 key.try_into()841 .map_err(|_| <Error<T>>::UnableToReadUnboundedKeys.into())842 })843 .collect::<Result<Vec<PropertyKey>, DispatchError>>()844 }845846 pub fn filter_collection_properties(847 collection_id: CollectionId,848 keys: Vec<PropertyKey>,849 ) -> Result<Vec<Property>, DispatchError> {850 let properties = Self::collection_properties(collection_id);851852 let properties = keys853 .into_iter()854 .filter_map(|key| {855 properties.get(&key).map(|value| Property {856 key,857 value: value.clone(),858 })859 })860 .collect();861862 Ok(properties)863 }864865 pub fn filter_property_permissions(866 collection_id: CollectionId,867 keys: Vec<PropertyKey>,868 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {869 let permissions = Self::property_permissions(collection_id);870871 let key_permissions = keys872 .into_iter()873 .filter_map(|key| {874 permissions875 .get(&key)876 .map(|permission| PropertyKeyPermission {877 key,878 permission: permission.clone(),879 })880 })881 .collect();882883 Ok(key_permissions)884 }885886 fn set_field_raw(887 collection_id: CollectionId,888 field: CollectionField,889 value: Vec<u8>,890 ) -> DispatchResult {891 if !value.is_empty() {892 <CollectionData<T>>::insert(893 (collection_id, field),894 BoundedVec::try_from(value).map_err(|_| <Error<T>>::CollectionFieldSizeExceeded)?,895 )896 } else {897 <CollectionData<T>>::remove((collection_id, field));898 }899 Ok(())900 }901902 pub fn set_field(903 collection: &CollectionHandle<T>,904 sender: &T::CrossAccountId,905 field: CollectionField,906 value: Vec<u8>,907 ) -> DispatchResult {908 collection.check_is_owner_or_admin(sender)?;909910 911912 Self::set_field_raw(collection.id, field, value)913 }914915 pub fn toggle_allowlist(916 collection: &CollectionHandle<T>,917 sender: &T::CrossAccountId,918 user: &T::CrossAccountId,919 allowed: bool,920 ) -> DispatchResult {921 collection.check_is_owner_or_admin(sender)?;922923 924925 if allowed {926 <Allowlist<T>>::insert((collection.id, user), true);927 } else {928 <Allowlist<T>>::remove((collection.id, user));929 }930931 Ok(())932 }933934 pub fn toggle_admin(935 collection: &CollectionHandle<T>,936 sender: &T::CrossAccountId,937 user: &T::CrossAccountId,938 admin: bool,939 ) -> DispatchResult {940 collection.check_is_owner_or_admin(sender)?;941942 let was_admin = <IsAdmin<T>>::get((collection.id, user));943 if was_admin == admin {944 return Ok(());945 }946 let amount = <AdminAmount<T>>::get(collection.id);947948 if admin {949 let amount = amount950 .checked_add(1)951 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;952 ensure!(953 amount <= Self::collection_admins_limit(),954 <Error<T>>::CollectionAdminCountExceeded,955 );956957 958959 <AdminAmount<T>>::insert(collection.id, amount);960 <IsAdmin<T>>::insert((collection.id, user), true);961 } else {962 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));963 <IsAdmin<T>>::remove((collection.id, user));964 }965966 Ok(())967 }968969 pub fn clamp_limits(970 mode: CollectionMode,971 old_limit: &CollectionLimits,972 mut new_limit: CollectionLimits,973 ) -> Result<CollectionLimits, DispatchError> {974 macro_rules! limit_default {975 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{976 $(977 if let Some($new) = $new.$field {978 let $old = $old.$field($($arg)?);979 let _ = $new;980 let _ = $old;981 $check982 } else {983 $new.$field = $old.$field984 }985 )*986 }};987 }988989 limit_default!(old_limit, new_limit,990 account_token_ownership_limit => ensure!(991 new_limit <= MAX_TOKEN_OWNERSHIP,992 <Error<T>>::CollectionLimitBoundsExceeded,993 ),994 sponsor_transfer_timeout(match mode {995 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,996 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,997 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,998 }) => ensure!(999 new_limit <= MAX_SPONSOR_TIMEOUT,1000 <Error<T>>::CollectionLimitBoundsExceeded,1001 ),1002 sponsored_data_size => ensure!(1003 new_limit <= CUSTOM_DATA_LIMIT,1004 <Error<T>>::CollectionLimitBoundsExceeded,1005 ),1006 token_limit => ensure!(1007 old_limit >= new_limit && new_limit > 0,1008 <Error<T>>::CollectionTokenLimitExceeded1009 ),1010 owner_can_transfer => ensure!(1011 old_limit || !new_limit,1012 <Error<T>>::OwnerPermissionsCantBeReverted,1013 ),1014 owner_can_destroy => ensure!(1015 old_limit || !new_limit,1016 <Error<T>>::OwnerPermissionsCantBeReverted,1017 ),1018 sponsored_data_rate_limit => {},1019 transfers_enabled => {},1020 );1021 Ok(new_limit)1022 }1023}10241025#[macro_export]1026macro_rules! unsupported {1027 () => {1028 Err(<Error<T>>::UnsupportedOperation.into())1029 };1030}103110321033pub trait CommonWeightInfo<CrossAccountId> {1034 fn create_item() -> Weight;1035 fn create_multiple_items(amount: u32) -> Weight;1036 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1037 fn burn_item() -> Weight;1038 fn set_collection_properties(amount: u32) -> Weight;1039 fn delete_collection_properties(amount: u32) -> Weight;1040 fn set_token_properties(amount: u32) -> Weight;1041 fn delete_token_properties(amount: u32) -> Weight;1042 fn set_property_permissions(amount: u32) -> Weight;1043 fn transfer() -> Weight;1044 fn approve() -> Weight;1045 fn transfer_from() -> Weight;1046 fn burn_from() -> Weight;1047}10481049pub trait CommonCollectionOperations<T: Config> {1050 fn create_item(1051 &self,1052 sender: T::CrossAccountId,1053 to: T::CrossAccountId,1054 data: CreateItemData,1055 nesting_budget: &dyn Budget,1056 ) -> DispatchResultWithPostInfo;1057 fn create_multiple_items(1058 &self,1059 sender: T::CrossAccountId,1060 to: T::CrossAccountId,1061 data: Vec<CreateItemData>,1062 nesting_budget: &dyn Budget,1063 ) -> DispatchResultWithPostInfo;1064 fn create_multiple_items_ex(1065 &self,1066 sender: T::CrossAccountId,1067 data: CreateItemExData<T::CrossAccountId>,1068 nesting_budget: &dyn Budget,1069 ) -> DispatchResultWithPostInfo;1070 fn burn_item(1071 &self,1072 sender: T::CrossAccountId,1073 token: TokenId,1074 amount: u128,1075 ) -> DispatchResultWithPostInfo;1076 fn set_collection_properties(1077 &self,1078 sender: T::CrossAccountId,1079 properties: Vec<Property>,1080 ) -> DispatchResultWithPostInfo;1081 fn delete_collection_properties(1082 &self,1083 sender: &T::CrossAccountId,1084 property_keys: Vec<PropertyKey>,1085 ) -> DispatchResultWithPostInfo;1086 fn set_token_properties(1087 &self,1088 sender: T::CrossAccountId,1089 token_id: TokenId,1090 property: Vec<Property>,1091 ) -> DispatchResultWithPostInfo;1092 fn delete_token_properties(1093 &self,1094 sender: T::CrossAccountId,1095 token_id: TokenId,1096 property_keys: Vec<PropertyKey>,1097 ) -> DispatchResultWithPostInfo;1098 fn set_property_permissions(1099 &self,1100 sender: &T::CrossAccountId,1101 property_permissions: Vec<PropertyKeyPermission>,1102 ) -> DispatchResultWithPostInfo;1103 fn transfer(1104 &self,1105 sender: T::CrossAccountId,1106 to: T::CrossAccountId,1107 token: TokenId,1108 amount: u128,1109 nesting_budget: &dyn Budget,1110 ) -> DispatchResultWithPostInfo;1111 fn approve(1112 &self,1113 sender: T::CrossAccountId,1114 spender: T::CrossAccountId,1115 token: TokenId,1116 amount: u128,1117 ) -> DispatchResultWithPostInfo;1118 fn transfer_from(1119 &self,1120 sender: T::CrossAccountId,1121 from: T::CrossAccountId,1122 to: T::CrossAccountId,1123 token: TokenId,1124 amount: u128,1125 nesting_budget: &dyn Budget,1126 ) -> DispatchResultWithPostInfo;1127 fn burn_from(1128 &self,1129 sender: T::CrossAccountId,1130 from: T::CrossAccountId,1131 token: TokenId,1132 amount: u128,1133 nesting_budget: &dyn Budget,1134 ) -> DispatchResultWithPostInfo;11351136 fn check_nesting(1137 &self,1138 sender: T::CrossAccountId,1139 from: (CollectionId, TokenId),1140 under: TokenId,1141 budget: &dyn Budget,1142 ) -> DispatchResult;11431144 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1145 fn collection_tokens(&self) -> Vec<TokenId>;1146 fn token_exists(&self, token: TokenId) -> bool;1147 fn last_token_id(&self) -> TokenId;11481149 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1150 fn const_metadata(&self, token: TokenId) -> Vec<u8>;1151 fn token_properties(&self, token_id: TokenId, keys: Vec<PropertyKey>) -> Vec<Property>;1152 1153 fn total_supply(&self) -> u32;1154 1155 fn account_balance(&self, account: T::CrossAccountId) -> u32;1156 1157 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1158 fn allowance(1159 &self,1160 sender: T::CrossAccountId,1161 spender: T::CrossAccountId,1162 token: TokenId,1163 ) -> u128;1164}116511661167pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1168 let post_info = PostDispatchInfo {1169 actual_weight: Some(weight),1170 pays_fee: Pays::Yes,1171 };1172 match res {1173 Ok(()) => Ok(post_info),1174 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1175 }1176}11771178impl<T: Config> From<PropertiesError> for Error<T> {1179 fn from(error: PropertiesError) -> Self {1180 match error {1181 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1182 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1183 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1184 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1185 }1186 }1187}