1234567891011121314151617#![cfg_attr(not(feature = "std"), no_std)]1819extern crate alloc;2021use core::ops::{Deref, DerefMut};22use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};23use sp_std::{vec::Vec, rc::Rc};24use pallet_evm::account::CrossAccountId;25use frame_support::{26 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},27 ensure,28 traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},29 BoundedVec,30 weights::Pays,31 transactional,32};33use pallet_evm::GasWeightMapping;34use up_data_structs::{35 COLLECTION_NUMBER_LIMIT,36 Collection,37 RpcCollection,38 CollectionId,39 CreateItemData,40 MAX_TOKEN_PREFIX_LENGTH,41 COLLECTION_ADMINS_LIMIT,42 TokenId,43 CollectionStats,44 MAX_TOKEN_OWNERSHIP,45 CollectionMode,46 NFT_SPONSOR_TRANSFER_TIMEOUT,47 FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,48 REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,49 MAX_SPONSOR_TIMEOUT,50 CUSTOM_DATA_LIMIT,51 CollectionLimits,52 CreateCollectionData,53 SponsorshipState,54 CreateItemExData,55 SponsoringRateLimit,56 budget::Budget,57 COLLECTION_FIELD_LIMIT,58 PhantomType,59 Property,60 Properties,61 PropertiesPermissionMap,62 PropertyKey,63 PropertyValue,64 PropertyPermission,65 PropertiesError,66 PropertyKeyPermission,67 TokenData,68 TrySetProperty,69 PropertyScope,70 71 RmrkCollectionInfo,72 RmrkInstanceInfo,73 RmrkResourceInfo,74 RmrkPropertyInfo,75 RmrkBaseInfo,76 RmrkPartType,77 RmrkTheme,78 RmrkNftChild,79 CollectionPermissions,80 SchemaVersion,81};8283pub use pallet::*;84use sp_core::H160;85use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};86#[cfg(feature = "runtime-benchmarks")]87pub mod benchmarking;88pub mod dispatch;89pub mod erc;90pub mod eth;91pub mod weights;9293pub type SelfWeightOf<T> = <T as Config>::WeightInfo;9495#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]96pub struct CollectionHandle<T: Config> {97 pub id: CollectionId,98 collection: Collection<T::AccountId>,99 pub recorder: SubstrateRecorder<T>,100}101impl<T: Config> WithRecorder<T> for CollectionHandle<T> {102 fn recorder(&self) -> &SubstrateRecorder<T> {103 &self.recorder104 }105 fn into_recorder(self) -> SubstrateRecorder<T> {106 self.recorder107 }108}109impl<T: Config> CollectionHandle<T> {110 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {111 <CollectionById<T>>::get(id).map(|collection| Self {112 id,113 collection,114 recorder: SubstrateRecorder::new(gas_limit),115 })116 }117118 pub fn new_with_recorder(id: CollectionId, recorder: Rc<SubstrateRecorder<T>>) -> Option<Self> {119 <CollectionById<T>>::get(id).map(|collection| Self {120 id,121 collection,122 recorder,123 })124 }125126 pub fn new(id: CollectionId) -> Option<Self> {127 Self::new_with_gas_limit(id, u64::MAX)128 }129 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {130 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)131 }132 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {133 self.recorder134 .consume_gas(T::GasWeightMapping::weight_to_gas(135 <T as frame_system::Config>::DbWeight::get()136 .read137 .saturating_mul(reads),138 ))139 }140 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {141 self.recorder142 .consume_gas(T::GasWeightMapping::weight_to_gas(143 <T as frame_system::Config>::DbWeight::get()144 .write145 .saturating_mul(writes),146 ))147 }148 pub fn save(self) -> DispatchResult {149 <CollectionById<T>>::insert(self.id, self.collection);150 Ok(())151 }152153 pub fn set_sponsor(&mut self, sponsor: T::AccountId) {154 self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor);155 }156}157impl<T: Config> Deref for CollectionHandle<T> {158 type Target = Collection<T::AccountId>;159160 fn deref(&self) -> &Self::Target {161 &self.collection162 }163}164165impl<T: Config> DerefMut for CollectionHandle<T> {166 fn deref_mut(&mut self) -> &mut Self::Target {167 &mut self.collection168 }169}170171impl<T: Config> CollectionHandle<T> {172 pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {173 ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);174 Ok(())175 }176 pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {177 *subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))178 }179 pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {180 ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);181 Ok(())182 }183 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {184 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)185 }186 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {187 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)188 }189 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {190 ensure!(191 <Allowlist<T>>::get((self.id, user)),192 <Error<T>>::AddressNotInAllowlist193 );194 Ok(())195 }196}197198#[frame_support::pallet]199pub mod pallet {200 use super::*;201 use pallet_evm::account;202 use dispatch::CollectionDispatch;203 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};204 use frame_system::pallet_prelude::*;205 use frame_support::traits::Currency;206 use up_data_structs::{TokenId, mapping::TokenAddressMapping};207 use scale_info::TypeInfo;208 use weights::WeightInfo;209210 #[pallet::config]211 pub trait Config:212 frame_system::Config + pallet_evm_coder_substrate::Config + TypeInfo + account::Config213 {214 type WeightInfo: WeightInfo;215 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;216217 type Currency: Currency<Self::AccountId>;218219 #[pallet::constant]220 type CollectionCreationPrice: Get<221 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,222 >;223 type CollectionDispatch: CollectionDispatch<Self>;224225 type TreasuryAccountId: Get<Self::AccountId>;226227 type EvmTokenAddressMapping: TokenAddressMapping<H160>;228 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;229 }230231 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);232233 #[pallet::pallet]234 #[pallet::storage_version(STORAGE_VERSION)]235 #[pallet::generate_store(pub(super) trait Store)]236 pub struct Pallet<T>(_);237238 #[pallet::extra_constants]239 impl<T: Config> Pallet<T> {240 pub fn collection_admins_limit() -> u32 {241 COLLECTION_ADMINS_LIMIT242 }243 }244245 #[pallet::event]246 #[pallet::generate_deposit(pub fn deposit_event)]247 pub enum Event<T: Config> {248 249 250 251 252 253 254 255 256 257 CollectionCreated(CollectionId, u8, T::AccountId),258259 260 261 262 263 264 CollectionDestroyed(CollectionId),265266 267 268 269 270 271 272 273 274 275 276 277 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),278279 280 281 282 283 284 285 286 287 288 289 290 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),291292 293 294 295 296 297 298 299 300 301 302 303 Transfer(304 CollectionId,305 TokenId,306 T::CrossAccountId,307 T::CrossAccountId,308 u128,309 ),310311 312 313 314 315 316 317 318 319 320 Approved(321 CollectionId,322 TokenId,323 T::CrossAccountId,324 T::CrossAccountId,325 u128,326 ),327328 CollectionPropertySet(CollectionId, PropertyKey),329330 CollectionPropertyDeleted(CollectionId, PropertyKey),331332 TokenPropertySet(CollectionId, TokenId, PropertyKey),333334 TokenPropertyDeleted(CollectionId, TokenId, PropertyKey),335336 PropertyPermissionSet(CollectionId, PropertyKey),337 }338339 #[pallet::error]340 pub enum Error<T> {341 342 CollectionNotFound,343 344 MustBeTokenOwner,345 346 NoPermission,347 348 PublicMintingNotAllowed,349 350 AddressNotInAllowlist,351352 353 CollectionNameLimitExceeded,354 355 CollectionDescriptionLimitExceeded,356 357 CollectionTokenPrefixLimitExceeded,358 359 TotalCollectionsLimitExceeded,360 361 CollectionAdminCountExceeded,362 363 CollectionLimitBoundsExceeded,364 365 OwnerPermissionsCantBeReverted,366 367 TransferNotAllowed,368 369 AccountTokenLimitExceeded,370 371 CollectionTokenLimitExceeded,372 373 MetadataFlagFrozen,374375 376 TokenNotFound,377 378 TokenValueTooLow,379 380 ApprovedValueTooLow,381 382 CantApproveMoreThanOwned,383384 385 AddressIsZero,386 387 UnsupportedOperation,388389 390 NotSufficientFounds,391392 393 NestingIsDisabled,394 395 OnlyOwnerAllowedToNest,396 397 SourceCollectionIsNotAllowedToNest,398399 400 CollectionFieldSizeExceeded,401402 403 NoSpaceForProperty,404405 406 PropertyLimitReached,407408 409 PropertyKeyIsTooLong,410411 412 InvalidCharacterInPropertyKey,413414 415 EmptyPropertyKey,416 }417418 #[pallet::storage]419 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;420 #[pallet::storage]421 pub type DestroyedCollectionCount<T> =422 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;423424 425 #[pallet::storage]426 pub type CollectionById<T> = StorageMap<427 Hasher = Blake2_128Concat,428 Key = CollectionId,429 Value = Collection<<T as frame_system::Config>::AccountId>,430 QueryKind = OptionQuery,431 >;432433 434 #[pallet::storage]435 #[pallet::getter(fn collection_properties)]436 pub type CollectionProperties<T> = StorageMap<437 Hasher = Blake2_128Concat,438 Key = CollectionId,439 Value = Properties,440 QueryKind = ValueQuery,441 OnEmpty = up_data_structs::CollectionProperties,442 >;443444 #[pallet::storage]445 #[pallet::getter(fn property_permissions)]446 pub type CollectionPropertyPermissions<T> = StorageMap<447 Hasher = Blake2_128Concat,448 Key = CollectionId,449 Value = PropertiesPermissionMap,450 QueryKind = ValueQuery,451 >;452453 #[pallet::storage]454 pub type AdminAmount<T> = StorageMap<455 Hasher = Blake2_128Concat,456 Key = CollectionId,457 Value = u32,458 QueryKind = ValueQuery,459 >;460461 462 #[pallet::storage]463 pub type IsAdmin<T: Config> = StorageNMap<464 Key = (465 Key<Blake2_128Concat, CollectionId>,466 Key<Blake2_128Concat, T::CrossAccountId>,467 ),468 Value = bool,469 QueryKind = ValueQuery,470 >;471472 473 #[pallet::storage]474 pub type Allowlist<T: Config> = StorageNMap<475 Key = (476 Key<Blake2_128Concat, CollectionId>,477 Key<Blake2_128Concat, T::CrossAccountId>,478 ),479 Value = bool,480 QueryKind = ValueQuery,481 >;482483 484 #[pallet::storage]485 pub type DummyStorageValue<T: Config> = StorageValue<486 Value = (487 CollectionStats,488 CollectionId,489 TokenId,490 PhantomType<TokenData<T::CrossAccountId>>,491 PhantomType<RpcCollection<T::AccountId>>,492 493 PhantomType<RmrkCollectionInfo<T::AccountId>>,494 PhantomType<RmrkInstanceInfo<T::AccountId>>,495 PhantomType<RmrkResourceInfo>,496 PhantomType<RmrkPropertyInfo>,497 PhantomType<RmrkBaseInfo<T::AccountId>>,498 PhantomType<RmrkPartType>,499 PhantomType<RmrkTheme>,500 PhantomType<RmrkNftChild>,501 ),502 QueryKind = OptionQuery,503 >;504505 #[pallet::hooks]506 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {507 fn on_runtime_upgrade() -> Weight {508 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {509 use up_data_structs::{CollectionVersion1, CollectionVersion2};510 <CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {511 let mut props = Vec::new();512 if !v.offchain_schema.is_empty() {513 props.push(Property {514 key: b"_old_offchainSchema".to_vec().try_into().unwrap(),515 value: v.offchain_schema.clone().into_inner().try_into().expect("offchain schema too big"),516 });517 }518 if !v.variable_on_chain_schema.is_empty() {519 props.push(Property {520 key: b"_old_variableOnChainSchema".to_vec().try_into().unwrap(),521 value: v.variable_on_chain_schema.clone().into_inner().try_into().expect("offchain schema too big"),522 });523 }524 if !v.const_on_chain_schema.is_empty() {525 props.push(Property {526 key: b"_old_constOnChainSchema".to_vec().try_into().unwrap(),527 value: v.const_on_chain_schema.clone().into_inner().try_into().expect("offchain schema too big"),528 });529 }530 props.push(Property {531 key: b"_old_schemaVersion".to_vec().try_into().unwrap(),532 value: match v.schema_version {533 SchemaVersion::ImageURL => b"ImageUrl".as_slice(),534 SchemaVersion::Unique => b"Unique".as_slice(),535 }.to_vec().try_into().unwrap(),536 });537 Self::set_scoped_collection_properties(538 id,539 PropertyScope::None,540 props.into_iter(),541 ).expect("existing data larger than properties");542 let mut new = CollectionVersion2::from(v.clone());543 new.permissions.access = Some(v.access);544 new.permissions.mint_mode = Some(v.mint_mode);545 Some(new)546 });547 }548549 0550 }551 }552}553554impl<T: Config> Pallet<T> {555 556 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {557 ensure!(558 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,559 <Error<T>>::AddressIsZero560 );561 Ok(())562 }563 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {564 <IsAdmin<T>>::iter_prefix((collection,))565 .map(|(a, _)| a)566 .collect()567 }568 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {569 <Allowlist<T>>::iter_prefix((collection,))570 .map(|(a, _)| a)571 .collect()572 }573 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {574 <Allowlist<T>>::get((collection, user))575 }576 pub fn collection_stats() -> CollectionStats {577 let created = <CreatedCollectionCount<T>>::get();578 let destroyed = <DestroyedCollectionCount<T>>::get();579 CollectionStats {580 created: created.0,581 destroyed: destroyed.0,582 alive: created.0 - destroyed.0,583 }584 }585586 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {587 let collection = <CollectionById<T>>::get(collection);588 if collection.is_none() {589 return None;590 }591592 let collection = collection.unwrap();593 let limits = collection.limits;594 let effective_limits = CollectionLimits {595 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),596 sponsored_data_size: Some(limits.sponsored_data_size()),597 sponsored_data_rate_limit: Some(598 limits599 .sponsored_data_rate_limit600 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),601 ),602 token_limit: Some(limits.token_limit()),603 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(604 match collection.mode {605 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,606 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,607 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,608 },609 )),610 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),611 owner_can_transfer: Some(limits.owner_can_transfer()),612 owner_can_destroy: Some(limits.owner_can_destroy()),613 transfers_enabled: Some(limits.transfers_enabled()),614 };615616 Some(effective_limits)617 }618619 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {620 let Collection {621 name,622 description,623 owner,624 mode,625 token_prefix,626 sponsorship,627 limits,628 permissions,629 } = <CollectionById<T>>::get(collection)?;630631 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)632 .into_iter()633 .map(|(key, permission)| PropertyKeyPermission {634 key,635 permission,636 })637 .collect();638639 let properties = <CollectionProperties<T>>::get(collection)640 .into_iter()641 .map(|(key, value)| Property {642 key,643 value,644 })645 .collect();646647 Some(RpcCollection {648 name: name.into_inner(),649 description: description.into_inner(),650 owner,651 mode,652 token_prefix: token_prefix.into_inner(),653 sponsorship,654 limits,655 permissions,656 token_property_permissions,657 properties,658 })659 }660}661662macro_rules! limit_default {663 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{664 $(665 if let Some($new) = $new.$field {666 let $old = $old.$field($($arg)?);667 let _ = $new;668 let _ = $old;669 $check670 } else {671 $new.$field = $old.$field672 }673 )*674 }};675}676macro_rules! limit_default_clone {677 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{678 $(679 if let Some($new) = $new.$field.clone() {680 let $old = $old.$field($($arg)?);681 let _ = $new;682 let _ = $old;683 $check684 } else {685 $new.$field = $old.$field.clone()686 }687 )*688 }};689}690691impl<T: Config> Pallet<T> {692 pub fn init_collection(693 owner: T::AccountId,694 data: CreateCollectionData<T::AccountId>,695 ) -> Result<CollectionId, DispatchError> {696 {697 ensure!(698 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,699 Error::<T>::CollectionTokenPrefixLimitExceeded700 );701 }702703 let created_count = <CreatedCollectionCount<T>>::get()704 .0705 .checked_add(1)706 .ok_or(ArithmeticError::Overflow)?;707 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;708 let id = CollectionId(created_count);709710 711 ensure!(712 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,713 <Error<T>>::TotalCollectionsLimitExceeded714 );715716 717718 let collection = Collection {719 owner: owner.clone(),720 name: data.name,721 mode: data.mode.clone(),722 description: data.description,723 token_prefix: data.token_prefix,724 sponsorship: data725 .pending_sponsor726 .map(SponsorshipState::Unconfirmed)727 .unwrap_or_default(),728 limits: data729 .limits730 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))731 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,732 permissions: data733 .permissions734 .map(|permissions| Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions))735 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,736 };737738 let mut collection_properties = up_data_structs::CollectionProperties::get();739 collection_properties740 .try_set_from_iter(data.properties.into_iter())741 .map_err(<Error<T>>::from)?;742743 CollectionProperties::<T>::insert(id, collection_properties);744745 let mut token_props_permissions = PropertiesPermissionMap::new();746 token_props_permissions747 .try_set_from_iter(data.token_property_permissions.into_iter())748 .map_err(<Error<T>>::from)?;749750 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);751752 753 {754 let mut imbalance =755 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();756 imbalance.subsume(757 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(758 &T::TreasuryAccountId::get(),759 T::CollectionCreationPrice::get(),760 ),761 );762 <T as Config>::Currency::settle(763 &owner,764 imbalance,765 WithdrawReasons::TRANSFER,766 ExistenceRequirement::KeepAlive,767 )768 .map_err(|_| Error::<T>::NotSufficientFounds)?;769 }770771 <CreatedCollectionCount<T>>::put(created_count);772 <Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));773 <CollectionById<T>>::insert(id, collection);774 Ok(id)775 }776777 pub fn destroy_collection(778 collection: CollectionHandle<T>,779 sender: &T::CrossAccountId,780 ) -> DispatchResult {781 ensure!(782 collection.limits.owner_can_destroy(),783 <Error<T>>::NoPermission,784 );785 collection.check_is_owner(sender)?;786787 let destroyed_collections = <DestroyedCollectionCount<T>>::get()788 .0789 .checked_add(1)790 .ok_or(ArithmeticError::Overflow)?;791792 793794 <DestroyedCollectionCount<T>>::put(destroyed_collections);795 <CollectionById<T>>::remove(collection.id);796 <AdminAmount<T>>::remove(collection.id);797 <IsAdmin<T>>::remove_prefix((collection.id,), None);798 <Allowlist<T>>::remove_prefix((collection.id,), None);799 <CollectionProperties<T>>::remove(collection.id);800801 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));802 Ok(())803 }804805 pub fn set_collection_property(806 collection: &CollectionHandle<T>,807 sender: &T::CrossAccountId,808 property: Property,809 ) -> DispatchResult {810 collection.check_is_owner_or_admin(sender)?;811812 CollectionProperties::<T>::try_mutate(collection.id, |properties| {813 let property = property.clone();814 properties.try_set(property.key, property.value)815 })816 .map_err(<Error<T>>::from)?;817818 Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));819820 Ok(())821 }822823 pub fn set_scoped_collection_property(824 collection_id: CollectionId,825 scope: PropertyScope,826 property: Property,827 ) -> DispatchResult {828 CollectionProperties::<T>::try_mutate(collection_id, |properties| {829 properties.try_scoped_set(scope, property.key, property.value)830 })831 .map_err(<Error<T>>::from)?;832833 Ok(())834 }835836 pub fn set_scoped_collection_properties(837 collection_id: CollectionId,838 scope: PropertyScope,839 properties: impl Iterator<Item = Property>,840 ) -> DispatchResult {841 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {842 stored_properties.try_scoped_set_from_iter(scope, properties)843 })844 .map_err(<Error<T>>::from)?;845846 Ok(())847 }848849 #[transactional]850 pub fn set_collection_properties(851 collection: &CollectionHandle<T>,852 sender: &T::CrossAccountId,853 properties: Vec<Property>,854 ) -> DispatchResult {855 for property in properties {856 Self::set_collection_property(collection, sender, property)?;857 }858859 Ok(())860 }861862 pub fn delete_collection_property(863 collection: &CollectionHandle<T>,864 sender: &T::CrossAccountId,865 property_key: PropertyKey,866 ) -> DispatchResult {867 collection.check_is_owner_or_admin(sender)?;868869 CollectionProperties::<T>::try_mutate(collection.id, |properties| {870 properties.remove(&property_key)871 })872 .map_err(<Error<T>>::from)?;873874 Self::deposit_event(Event::CollectionPropertyDeleted(875 collection.id,876 property_key,877 ));878879 Ok(())880 }881882 #[transactional]883 pub fn delete_collection_properties(884 collection: &CollectionHandle<T>,885 sender: &T::CrossAccountId,886 property_keys: Vec<PropertyKey>,887 ) -> DispatchResult {888 for key in property_keys {889 Self::delete_collection_property(collection, sender, key)?;890 }891892 Ok(())893 }894895 896 pub fn set_property_permission_unchecked(897 collection: CollectionId,898 property_permission: PropertyKeyPermission,899 ) -> DispatchResult {900 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {901 permissions.try_set(property_permission.key, property_permission.permission)902 })903 .map_err(<Error<T>>::from)?;904 Ok(())905 }906907 pub fn set_property_permission(908 collection: &CollectionHandle<T>,909 sender: &T::CrossAccountId,910 property_permission: PropertyKeyPermission,911 ) -> DispatchResult {912 collection.check_is_owner_or_admin(sender)?;913914 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);915 let current_permission = all_permissions.get(&property_permission.key);916 if matches![917 current_permission,918 Some(PropertyPermission { mutable: false, .. })919 ] {920 return Err(<Error<T>>::NoPermission.into());921 }922923 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {924 let property_permission = property_permission.clone();925 permissions.try_set(property_permission.key, property_permission.permission)926 })927 .map_err(<Error<T>>::from)?;928929 Self::deposit_event(Event::PropertyPermissionSet(930 collection.id,931 property_permission.key,932 ));933934 Ok(())935 }936937 #[transactional]938 pub fn set_property_permissions(939 collection: &CollectionHandle<T>,940 sender: &T::CrossAccountId,941 property_permissions: Vec<PropertyKeyPermission>,942 ) -> DispatchResult {943 for prop_pemission in property_permissions {944 Self::set_property_permission(collection, sender, prop_pemission)?;945 }946947 Ok(())948 }949950 pub fn get_collection_property(951 collection_id: CollectionId,952 key: &PropertyKey,953 ) -> Option<PropertyValue> {954 Self::collection_properties(collection_id).get(key).cloned()955 }956957 pub fn bytes_keys_to_property_keys(958 keys: Vec<Vec<u8>>,959 ) -> Result<Vec<PropertyKey>, DispatchError> {960 keys.into_iter()961 .map(|key| -> Result<PropertyKey, DispatchError> {962 key.try_into()963 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())964 })965 .collect::<Result<Vec<PropertyKey>, DispatchError>>()966 }967968 pub fn filter_collection_properties(969 collection_id: CollectionId,970 keys: Option<Vec<PropertyKey>>,971 ) -> Result<Vec<Property>, DispatchError> {972 let properties = Self::collection_properties(collection_id);973974 let properties = keys975 .map(|keys| {976 keys.into_iter()977 .filter_map(|key| {978 properties.get(&key).map(|value| Property {979 key,980 value: value.clone(),981 })982 })983 .collect()984 })985 .unwrap_or_else(|| {986 properties987 .into_iter()988 .map(|(key, value)| Property {989 key,990 value,991 })992 .collect()993 });994995 Ok(properties)996 }997998 pub fn filter_property_permissions(999 collection_id: CollectionId,1000 keys: Option<Vec<PropertyKey>>,1001 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1002 let permissions = Self::property_permissions(collection_id);10031004 let key_permissions = keys1005 .map(|keys| {1006 keys.into_iter()1007 .filter_map(|key| {1008 permissions1009 .get(&key)1010 .map(|permission| PropertyKeyPermission {1011 key,1012 permission: permission.clone(),1013 })1014 })1015 .collect()1016 })1017 .unwrap_or_else(|| {1018 permissions1019 .into_iter()1020 .map(|(key, permission)| PropertyKeyPermission {1021 key,1022 permission,1023 })1024 .collect()1025 });10261027 Ok(key_permissions)1028 }10291030 pub fn toggle_allowlist(1031 collection: &CollectionHandle<T>,1032 sender: &T::CrossAccountId,1033 user: &T::CrossAccountId,1034 allowed: bool,1035 ) -> DispatchResult {1036 collection.check_is_owner_or_admin(sender)?;10371038 10391040 if allowed {1041 <Allowlist<T>>::insert((collection.id, user), true);1042 } else {1043 <Allowlist<T>>::remove((collection.id, user));1044 }10451046 Ok(())1047 }10481049 pub fn toggle_admin(1050 collection: &CollectionHandle<T>,1051 sender: &T::CrossAccountId,1052 user: &T::CrossAccountId,1053 admin: bool,1054 ) -> DispatchResult {1055 collection.check_is_owner_or_admin(sender)?;10561057 let was_admin = <IsAdmin<T>>::get((collection.id, user));1058 if was_admin == admin {1059 return Ok(());1060 }1061 let amount = <AdminAmount<T>>::get(collection.id);10621063 if admin {1064 let amount = amount1065 .checked_add(1)1066 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1067 ensure!(1068 amount <= Self::collection_admins_limit(),1069 <Error<T>>::CollectionAdminCountExceeded,1070 );10711072 10731074 <AdminAmount<T>>::insert(collection.id, amount);1075 <IsAdmin<T>>::insert((collection.id, user), true);1076 } else {1077 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1078 <IsAdmin<T>>::remove((collection.id, user));1079 }10801081 Ok(())1082 }10831084 pub fn clamp_limits(1085 mode: CollectionMode,1086 old_limit: &CollectionLimits,1087 mut new_limit: CollectionLimits,1088 ) -> Result<CollectionLimits, DispatchError> {1089 limit_default!(old_limit, new_limit,1090 account_token_ownership_limit => ensure!(1091 new_limit <= MAX_TOKEN_OWNERSHIP,1092 <Error<T>>::CollectionLimitBoundsExceeded,1093 ),1094 sponsor_transfer_timeout(match mode {1095 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1096 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1097 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1098 }) => ensure!(1099 new_limit <= MAX_SPONSOR_TIMEOUT,1100 <Error<T>>::CollectionLimitBoundsExceeded,1101 ),1102 sponsored_data_size => ensure!(1103 new_limit <= CUSTOM_DATA_LIMIT,1104 <Error<T>>::CollectionLimitBoundsExceeded,1105 ),1106 token_limit => ensure!(1107 old_limit >= new_limit && new_limit > 0,1108 <Error<T>>::CollectionTokenLimitExceeded1109 ),1110 owner_can_transfer => ensure!(1111 old_limit || !new_limit,1112 <Error<T>>::OwnerPermissionsCantBeReverted,1113 ),1114 owner_can_destroy => ensure!(1115 old_limit || !new_limit,1116 <Error<T>>::OwnerPermissionsCantBeReverted,1117 ),1118 sponsored_data_rate_limit => {},1119 transfers_enabled => {},1120 );1121 Ok(new_limit)1122 }1123 pub fn clamp_permissions(1124 mode: CollectionMode,1125 old_limit: &CollectionPermissions,1126 mut new_limit: CollectionPermissions,1127 ) -> Result<CollectionPermissions, DispatchError> {1128 limit_default_clone!(old_limit, new_limit,1129 );1130 Ok(new_limit)1131 }1132}11331134#[macro_export]1135macro_rules! unsupported {1136 () => {1137 Err(<Error<T>>::UnsupportedOperation.into())1138 };1139}114011411142pub trait CommonWeightInfo<CrossAccountId> {1143 fn create_item() -> Weight;1144 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;1145 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1146 fn burn_item() -> Weight;1147 fn set_collection_properties(amount: u32) -> Weight;1148 fn delete_collection_properties(amount: u32) -> Weight;1149 fn set_token_properties(amount: u32) -> Weight;1150 fn delete_token_properties(amount: u32) -> Weight;1151 fn set_property_permissions(amount: u32) -> Weight;1152 fn transfer() -> Weight;1153 fn approve() -> Weight;1154 fn transfer_from() -> Weight;1155 fn burn_from() -> Weight;1156}11571158pub trait CommonCollectionOperations<T: Config> {1159 fn create_item(1160 &self,1161 sender: T::CrossAccountId,1162 to: T::CrossAccountId,1163 data: CreateItemData,1164 nesting_budget: &dyn Budget,1165 ) -> DispatchResultWithPostInfo;1166 fn create_multiple_items(1167 &self,1168 sender: T::CrossAccountId,1169 to: T::CrossAccountId,1170 data: Vec<CreateItemData>,1171 nesting_budget: &dyn Budget,1172 ) -> DispatchResultWithPostInfo;1173 fn create_multiple_items_ex(1174 &self,1175 sender: T::CrossAccountId,1176 data: CreateItemExData<T::CrossAccountId>,1177 nesting_budget: &dyn Budget,1178 ) -> DispatchResultWithPostInfo;1179 fn burn_item(1180 &self,1181 sender: T::CrossAccountId,1182 token: TokenId,1183 amount: u128,1184 ) -> DispatchResultWithPostInfo;1185 fn set_collection_properties(1186 &self,1187 sender: T::CrossAccountId,1188 properties: Vec<Property>,1189 ) -> DispatchResultWithPostInfo;1190 fn delete_collection_properties(1191 &self,1192 sender: &T::CrossAccountId,1193 property_keys: Vec<PropertyKey>,1194 ) -> DispatchResultWithPostInfo;1195 fn set_token_properties(1196 &self,1197 sender: T::CrossAccountId,1198 token_id: TokenId,1199 property: Vec<Property>,1200 ) -> DispatchResultWithPostInfo;1201 fn delete_token_properties(1202 &self,1203 sender: T::CrossAccountId,1204 token_id: TokenId,1205 property_keys: Vec<PropertyKey>,1206 ) -> DispatchResultWithPostInfo;1207 fn set_property_permissions(1208 &self,1209 sender: &T::CrossAccountId,1210 property_permissions: Vec<PropertyKeyPermission>,1211 ) -> DispatchResultWithPostInfo;1212 fn transfer(1213 &self,1214 sender: T::CrossAccountId,1215 to: T::CrossAccountId,1216 token: TokenId,1217 amount: u128,1218 nesting_budget: &dyn Budget,1219 ) -> DispatchResultWithPostInfo;1220 fn approve(1221 &self,1222 sender: T::CrossAccountId,1223 spender: T::CrossAccountId,1224 token: TokenId,1225 amount: u128,1226 ) -> DispatchResultWithPostInfo;1227 fn transfer_from(1228 &self,1229 sender: T::CrossAccountId,1230 from: T::CrossAccountId,1231 to: T::CrossAccountId,1232 token: TokenId,1233 amount: u128,1234 nesting_budget: &dyn Budget,1235 ) -> DispatchResultWithPostInfo;1236 fn burn_from(1237 &self,1238 sender: T::CrossAccountId,1239 from: T::CrossAccountId,1240 token: TokenId,1241 amount: u128,1242 nesting_budget: &dyn Budget,1243 ) -> DispatchResultWithPostInfo;12441245 fn check_nesting(1246 &self,1247 sender: T::CrossAccountId,1248 from: (CollectionId, TokenId),1249 under: TokenId,1250 budget: &dyn Budget,1251 ) -> DispatchResult;12521253 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1254 fn collection_tokens(&self) -> Vec<TokenId>;1255 fn token_exists(&self, token: TokenId) -> bool;1256 fn last_token_id(&self) -> TokenId;12571258 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1259 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;1260 fn token_properties(&self, token_id: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;1261 1262 fn total_supply(&self) -> u32;1263 1264 fn account_balance(&self, account: T::CrossAccountId) -> u32;1265 1266 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1267 fn allowance(1268 &self,1269 sender: T::CrossAccountId,1270 spender: T::CrossAccountId,1271 token: TokenId,1272 ) -> u128;1273}127412751276pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1277 let post_info = PostDispatchInfo {1278 actual_weight: Some(weight),1279 pays_fee: Pays::Yes,1280 };1281 match res {1282 Ok(()) => Ok(post_info),1283 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1284 }1285}12861287impl<T: Config> From<PropertiesError> for Error<T> {1288 fn from(error: PropertiesError) -> Self {1289 match error {1290 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1291 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1292 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1293 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1294 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1295 }1296 }1297}