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;24use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};25use evm_coder::ToLog;26use frame_support::{27 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},28 ensure,29 traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},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 PhantomType,58 Property,59 Properties,60 PropertiesPermissionMap,61 PropertyKey,62 PropertyValue,63 PropertyPermission,64 PropertiesError,65 PropertyKeyPermission,66 TokenData,67 TrySetProperty,68 PropertyScope,69 70 RmrkCollectionInfo,71 RmrkInstanceInfo,72 RmrkResourceInfo,73 RmrkPropertyInfo,74 RmrkBaseInfo,75 RmrkPartType,76 RmrkTheme,77 RmrkNftChild,78 CollectionPermissions,79 SchemaVersion,80};8182pub use pallet::*;83use sp_core::H160;84use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};85#[cfg(feature = "runtime-benchmarks")]86pub mod benchmarking;87pub mod dispatch;88pub mod erc;89pub mod eth;90pub mod weights;9192pub type SelfWeightOf<T> = <T as Config>::WeightInfo;9394#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]95pub struct CollectionHandle<T: Config> {96 pub id: CollectionId,97 collection: Collection<T::AccountId>,98 pub recorder: SubstrateRecorder<T>,99}100impl<T: Config> WithRecorder<T> for CollectionHandle<T> {101 fn recorder(&self) -> &SubstrateRecorder<T> {102 &self.recorder103 }104 fn into_recorder(self) -> SubstrateRecorder<T> {105 self.recorder106 }107}108impl<T: Config> CollectionHandle<T> {109 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {110 <CollectionById<T>>::get(id).map(|collection| Self {111 id,112 collection,113 recorder: SubstrateRecorder::new(gas_limit),114 })115 }116117 pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder<T>) -> Option<Self> {118 <CollectionById<T>>::get(id).map(|collection| Self {119 id,120 collection,121 recorder,122 })123 }124125 pub fn new(id: CollectionId) -> Option<Self> {126 Self::new_with_gas_limit(id, u64::MAX)127 }128 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {129 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)130 }131 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {132 self.recorder133 .consume_gas(T::GasWeightMapping::weight_to_gas(134 <T as frame_system::Config>::DbWeight::get()135 .read136 .saturating_mul(reads),137 ))138 }139 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {140 self.recorder141 .consume_gas(T::GasWeightMapping::weight_to_gas(142 <T as frame_system::Config>::DbWeight::get()143 .write144 .saturating_mul(writes),145 ))146 }147 pub fn save(self) -> DispatchResult {148 <CollectionById<T>>::insert(self.id, self.collection);149 Ok(())150 }151152 pub fn set_sponsor(&mut self, sponsor: T::AccountId) {153 self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor);154 }155156 pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> bool {157 if self.collection.sponsorship.pending_sponsor() != Some(sender) {158 return false;159 };160161 self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());162 true163 }164}165impl<T: Config> Deref for CollectionHandle<T> {166 type Target = Collection<T::AccountId>;167168 fn deref(&self) -> &Self::Target {169 &self.collection170 }171}172173impl<T: Config> DerefMut for CollectionHandle<T> {174 fn deref_mut(&mut self) -> &mut Self::Target {175 &mut self.collection176 }177}178179impl<T: Config> CollectionHandle<T> {180 pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {181 ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);182 Ok(())183 }184 pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {185 *subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))186 }187 pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {188 ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);189 Ok(())190 }191 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {192 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)193 }194 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {195 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)196 }197 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {198 ensure!(199 <Allowlist<T>>::get((self.id, user)),200 <Error<T>>::AddressNotInAllowlist201 );202 Ok(())203 }204}205206#[frame_support::pallet]207pub mod pallet {208 use super::*;209 use pallet_evm::account;210 use dispatch::CollectionDispatch;211 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};212 use frame_system::pallet_prelude::*;213 use frame_support::traits::Currency;214 use up_data_structs::{TokenId, mapping::TokenAddressMapping};215 use scale_info::TypeInfo;216 use weights::WeightInfo;217218 #[pallet::config]219 pub trait Config:220 frame_system::Config221 + pallet_evm_coder_substrate::Config222 + pallet_evm::Config223 + TypeInfo224 + account::Config225 {226 type WeightInfo: WeightInfo;227 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;228229 type Currency: Currency<Self::AccountId>;230231 #[pallet::constant]232 type CollectionCreationPrice: Get<233 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,234 >;235 type CollectionDispatch: CollectionDispatch<Self>;236237 type TreasuryAccountId: Get<Self::AccountId>;238 type ContractAddress: Get<H160>;239240 type EvmTokenAddressMapping: TokenAddressMapping<H160>;241 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;242 }243244 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);245246 #[pallet::pallet]247 #[pallet::storage_version(STORAGE_VERSION)]248 #[pallet::generate_store(pub(super) trait Store)]249 pub struct Pallet<T>(_);250251 #[pallet::extra_constants]252 impl<T: Config> Pallet<T> {253 pub fn collection_admins_limit() -> u32 {254 COLLECTION_ADMINS_LIMIT255 }256 }257258 #[pallet::event]259 #[pallet::generate_deposit(pub fn deposit_event)]260 pub enum Event<T: Config> {261 262 263 264 265 266 267 268 269 270 CollectionCreated(CollectionId, u8, T::AccountId),271272 273 274 275 276 277 CollectionDestroyed(CollectionId),278279 280 281 282 283 284 285 286 287 288 289 290 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),291292 293 294 295 296 297 298 299 300 301 302 303 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),304305 306 307 308 309 310 311 312 313 314 315 316 Transfer(317 CollectionId,318 TokenId,319 T::CrossAccountId,320 T::CrossAccountId,321 u128,322 ),323324 325 326 327 328 329 330 331 332 333 Approved(334 CollectionId,335 TokenId,336 T::CrossAccountId,337 T::CrossAccountId,338 u128,339 ),340341 CollectionPropertySet(CollectionId, PropertyKey),342343 CollectionPropertyDeleted(CollectionId, PropertyKey),344345 TokenPropertySet(CollectionId, TokenId, PropertyKey),346347 TokenPropertyDeleted(CollectionId, TokenId, PropertyKey),348349 PropertyPermissionSet(CollectionId, PropertyKey),350 }351352 #[pallet::error]353 pub enum Error<T> {354 355 CollectionNotFound,356 357 MustBeTokenOwner,358 359 NoPermission,360 361 CantDestroyNotEmptyCollection,362 363 PublicMintingNotAllowed,364 365 AddressNotInAllowlist,366367 368 CollectionNameLimitExceeded,369 370 CollectionDescriptionLimitExceeded,371 372 CollectionTokenPrefixLimitExceeded,373 374 TotalCollectionsLimitExceeded,375 376 CollectionAdminCountExceeded,377 378 CollectionLimitBoundsExceeded,379 380 OwnerPermissionsCantBeReverted,381 382 TransferNotAllowed,383 384 AccountTokenLimitExceeded,385 386 CollectionTokenLimitExceeded,387 388 MetadataFlagFrozen,389390 391 TokenNotFound,392 393 TokenValueTooLow,394 395 ApprovedValueTooLow,396 397 CantApproveMoreThanOwned,398399 400 AddressIsZero,401 402 UnsupportedOperation,403404 405 NotSufficientFounds,406407 408 NestingIsDisabled,409 410 OnlyOwnerAllowedToNest,411 412 SourceCollectionIsNotAllowedToNest,413414 415 CollectionFieldSizeExceeded,416417 418 NoSpaceForProperty,419420 421 PropertyLimitReached,422423 424 PropertyKeyIsTooLong,425426 427 InvalidCharacterInPropertyKey,428429 430 EmptyPropertyKey,431 }432433 #[pallet::storage]434 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;435 #[pallet::storage]436 pub type DestroyedCollectionCount<T> =437 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;438439 440 #[pallet::storage]441 pub type CollectionById<T> = StorageMap<442 Hasher = Blake2_128Concat,443 Key = CollectionId,444 Value = Collection<<T as frame_system::Config>::AccountId>,445 QueryKind = OptionQuery,446 >;447448 449 #[pallet::storage]450 #[pallet::getter(fn collection_properties)]451 pub type CollectionProperties<T> = StorageMap<452 Hasher = Blake2_128Concat,453 Key = CollectionId,454 Value = Properties,455 QueryKind = ValueQuery,456 OnEmpty = up_data_structs::CollectionProperties,457 >;458459 #[pallet::storage]460 #[pallet::getter(fn property_permissions)]461 pub type CollectionPropertyPermissions<T> = StorageMap<462 Hasher = Blake2_128Concat,463 Key = CollectionId,464 Value = PropertiesPermissionMap,465 QueryKind = ValueQuery,466 >;467468 #[pallet::storage]469 pub type AdminAmount<T> = StorageMap<470 Hasher = Blake2_128Concat,471 Key = CollectionId,472 Value = u32,473 QueryKind = ValueQuery,474 >;475476 477 #[pallet::storage]478 pub type IsAdmin<T: Config> = StorageNMap<479 Key = (480 Key<Blake2_128Concat, CollectionId>,481 Key<Blake2_128Concat, T::CrossAccountId>,482 ),483 Value = bool,484 QueryKind = ValueQuery,485 >;486487 488 #[pallet::storage]489 pub type Allowlist<T: Config> = StorageNMap<490 Key = (491 Key<Blake2_128Concat, CollectionId>,492 Key<Blake2_128Concat, T::CrossAccountId>,493 ),494 Value = bool,495 QueryKind = ValueQuery,496 >;497498 499 #[pallet::storage]500 pub type DummyStorageValue<T: Config> = StorageValue<501 Value = (502 CollectionStats,503 CollectionId,504 TokenId,505 PhantomType<(506 TokenData<T::CrossAccountId>,507 RpcCollection<T::AccountId>,508 509 RmrkCollectionInfo<T::AccountId>,510 RmrkInstanceInfo<T::AccountId>,511 RmrkResourceInfo,512 RmrkPropertyInfo,513 RmrkBaseInfo<T::AccountId>,514 RmrkPartType,515 RmrkTheme,516 RmrkNftChild,517 )>,518 ),519 QueryKind = OptionQuery,520 >;521522 #[pallet::hooks]523 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {524 fn on_runtime_upgrade() -> Weight {525 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {526 use up_data_structs::{CollectionVersion1, CollectionVersion2};527 <CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {528 let mut props = Vec::new();529 if !v.offchain_schema.is_empty() {530 props.push(Property {531 key: b"_old_offchainSchema".to_vec().try_into().unwrap(),532 value: v533 .offchain_schema534 .clone()535 .into_inner()536 .try_into()537 .expect("offchain schema too big"),538 });539 }540 if !v.variable_on_chain_schema.is_empty() {541 props.push(Property {542 key: b"_old_variableOnChainSchema".to_vec().try_into().unwrap(),543 value: v544 .variable_on_chain_schema545 .clone()546 .into_inner()547 .try_into()548 .expect("offchain schema too big"),549 });550 }551 if !v.const_on_chain_schema.is_empty() {552 props.push(Property {553 key: b"_old_constOnChainSchema".to_vec().try_into().unwrap(),554 value: v555 .const_on_chain_schema556 .clone()557 .into_inner()558 .try_into()559 .expect("offchain schema too big"),560 });561 }562 props.push(Property {563 key: b"_old_schemaVersion".to_vec().try_into().unwrap(),564 value: match v.schema_version {565 SchemaVersion::ImageURL => b"ImageUrl".as_slice(),566 SchemaVersion::Unique => b"Unique".as_slice(),567 }568 .to_vec()569 .try_into()570 .unwrap(),571 });572 Self::set_scoped_collection_properties(573 id,574 PropertyScope::None,575 props.into_iter(),576 )577 .expect("existing data larger than properties");578 let mut new = CollectionVersion2::from(v.clone());579 new.permissions.access = Some(v.access);580 new.permissions.mint_mode = Some(v.mint_mode);581 Some(new)582 });583 }584585 0586 }587 }588}589590impl<T: Config> Pallet<T> {591 592 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {593 ensure!(594 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,595 <Error<T>>::AddressIsZero596 );597 Ok(())598 }599 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {600 <IsAdmin<T>>::iter_prefix((collection,))601 .map(|(a, _)| a)602 .collect()603 }604 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {605 <Allowlist<T>>::iter_prefix((collection,))606 .map(|(a, _)| a)607 .collect()608 }609 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {610 <Allowlist<T>>::get((collection, user))611 }612 pub fn collection_stats() -> CollectionStats {613 let created = <CreatedCollectionCount<T>>::get();614 let destroyed = <DestroyedCollectionCount<T>>::get();615 CollectionStats {616 created: created.0,617 destroyed: destroyed.0,618 alive: created.0 - destroyed.0,619 }620 }621622 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {623 let collection = <CollectionById<T>>::get(collection);624 if collection.is_none() {625 return None;626 }627628 let collection = collection.unwrap();629 let limits = collection.limits;630 let effective_limits = CollectionLimits {631 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),632 sponsored_data_size: Some(limits.sponsored_data_size()),633 sponsored_data_rate_limit: Some(634 limits635 .sponsored_data_rate_limit636 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),637 ),638 token_limit: Some(limits.token_limit()),639 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(640 match collection.mode {641 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,642 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,643 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,644 },645 )),646 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),647 owner_can_transfer: Some(limits.owner_can_transfer()),648 owner_can_destroy: Some(limits.owner_can_destroy()),649 transfers_enabled: Some(limits.transfers_enabled()),650 };651652 Some(effective_limits)653 }654655 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {656 let Collection {657 name,658 description,659 owner,660 mode,661 token_prefix,662 sponsorship,663 limits,664 permissions,665 } = <CollectionById<T>>::get(collection)?;666667 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)668 .into_iter()669 .map(|(key, permission)| PropertyKeyPermission { key, permission })670 .collect();671672 let properties = <CollectionProperties<T>>::get(collection)673 .into_iter()674 .map(|(key, value)| Property { key, value })675 .collect();676677 let permissions = CollectionPermissions {678 access: Some(permissions.access()),679 mint_mode: Some(permissions.mint_mode()),680 nesting: Some(permissions.nesting().clone()),681 };682683 Some(RpcCollection {684 name: name.into_inner(),685 description: description.into_inner(),686 owner,687 mode,688 token_prefix: token_prefix.into_inner(),689 sponsorship,690 limits,691 permissions,692 token_property_permissions,693 properties,694 })695 }696}697698macro_rules! limit_default {699 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{700 $(701 if let Some($new) = $new.$field {702 let $old = $old.$field($($arg)?);703 let _ = $new;704 let _ = $old;705 $check706 } else {707 $new.$field = $old.$field708 }709 )*710 }};711}712macro_rules! limit_default_clone {713 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{714 $(715 if let Some($new) = $new.$field.clone() {716 let $old = $old.$field($($arg)?);717 let _ = $new;718 let _ = $old;719 $check720 } else {721 $new.$field = $old.$field.clone()722 }723 )*724 }};725}726727impl<T: Config> Pallet<T> {728 pub fn init_collection(729 owner: T::CrossAccountId,730 data: CreateCollectionData<T::AccountId>,731 ) -> Result<CollectionId, DispatchError> {732 {733 ensure!(734 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,735 Error::<T>::CollectionTokenPrefixLimitExceeded736 );737 }738739 let created_count = <CreatedCollectionCount<T>>::get()740 .0741 .checked_add(1)742 .ok_or(ArithmeticError::Overflow)?;743 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;744 let id = CollectionId(created_count);745746 747 ensure!(748 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,749 <Error<T>>::TotalCollectionsLimitExceeded750 );751752 753754 let collection = Collection {755 owner: owner.as_sub().clone(),756 name: data.name,757 mode: data.mode.clone(),758 description: data.description,759 token_prefix: data.token_prefix,760 sponsorship: data761 .pending_sponsor762 .map(SponsorshipState::Unconfirmed)763 .unwrap_or_default(),764 limits: data765 .limits766 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))767 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,768 permissions: data769 .permissions770 .map(|permissions| {771 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)772 })773 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,774 };775776 let mut collection_properties = up_data_structs::CollectionProperties::get();777 collection_properties778 .try_set_from_iter(data.properties.into_iter())779 .map_err(<Error<T>>::from)?;780781 CollectionProperties::<T>::insert(id, collection_properties);782783 let mut token_props_permissions = PropertiesPermissionMap::new();784 token_props_permissions785 .try_set_from_iter(data.token_property_permissions.into_iter())786 .map_err(<Error<T>>::from)?;787788 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);789790 791 {792 let mut imbalance =793 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();794 imbalance.subsume(795 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(796 &T::TreasuryAccountId::get(),797 T::CollectionCreationPrice::get(),798 ),799 );800 <T as Config>::Currency::settle(801 &owner.as_sub(),802 imbalance,803 WithdrawReasons::TRANSFER,804 ExistenceRequirement::KeepAlive,805 )806 .map_err(|_| Error::<T>::NotSufficientFounds)?;807 }808809 <CreatedCollectionCount<T>>::put(created_count);810 <Pallet<T>>::deposit_event(Event::CollectionCreated(811 id,812 data.mode.id(),813 owner.as_sub().clone(),814 ));815 <PalletEvm<T>>::deposit_log(816 erc::CollectionHelpersEvents::CollectionCreated {817 owner: *owner.as_eth(),818 collection_id: eth::collection_id_to_address(id),819 }820 .to_log(T::ContractAddress::get()),821 );822 <CollectionById<T>>::insert(id, collection);823 Ok(id)824 }825826 pub fn destroy_collection(827 collection: CollectionHandle<T>,828 sender: &T::CrossAccountId,829 ) -> DispatchResult {830 ensure!(831 collection.limits.owner_can_destroy(),832 <Error<T>>::NoPermission,833 );834 collection.check_is_owner(sender)?;835836 let destroyed_collections = <DestroyedCollectionCount<T>>::get()837 .0838 .checked_add(1)839 .ok_or(ArithmeticError::Overflow)?;840841 842843 <DestroyedCollectionCount<T>>::put(destroyed_collections);844 <CollectionById<T>>::remove(collection.id);845 <AdminAmount<T>>::remove(collection.id);846 <IsAdmin<T>>::remove_prefix((collection.id,), None);847 <Allowlist<T>>::remove_prefix((collection.id,), None);848 <CollectionProperties<T>>::remove(collection.id);849850 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));851 Ok(())852 }853854 pub fn set_collection_property(855 collection: &CollectionHandle<T>,856 sender: &T::CrossAccountId,857 property: Property,858 ) -> DispatchResult {859 collection.check_is_owner_or_admin(sender)?;860861 CollectionProperties::<T>::try_mutate(collection.id, |properties| {862 let property = property.clone();863 properties.try_set(property.key, property.value)864 })865 .map_err(<Error<T>>::from)?;866867 Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));868869 Ok(())870 }871872 pub fn set_scoped_collection_property(873 collection_id: CollectionId,874 scope: PropertyScope,875 property: Property,876 ) -> DispatchResult {877 CollectionProperties::<T>::try_mutate(collection_id, |properties| {878 properties.try_scoped_set(scope, property.key, property.value)879 })880 .map_err(<Error<T>>::from)?;881882 Ok(())883 }884885 pub fn set_scoped_collection_properties(886 collection_id: CollectionId,887 scope: PropertyScope,888 properties: impl Iterator<Item = Property>,889 ) -> DispatchResult {890 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {891 stored_properties.try_scoped_set_from_iter(scope, properties)892 })893 .map_err(<Error<T>>::from)?;894895 Ok(())896 }897898 #[transactional]899 pub fn set_collection_properties(900 collection: &CollectionHandle<T>,901 sender: &T::CrossAccountId,902 properties: Vec<Property>,903 ) -> DispatchResult {904 for property in properties {905 Self::set_collection_property(collection, sender, property)?;906 }907908 Ok(())909 }910911 pub fn delete_collection_property(912 collection: &CollectionHandle<T>,913 sender: &T::CrossAccountId,914 property_key: PropertyKey,915 ) -> DispatchResult {916 collection.check_is_owner_or_admin(sender)?;917918 CollectionProperties::<T>::try_mutate(collection.id, |properties| {919 properties.remove(&property_key)920 })921 .map_err(<Error<T>>::from)?;922923 Self::deposit_event(Event::CollectionPropertyDeleted(924 collection.id,925 property_key,926 ));927928 Ok(())929 }930931 #[transactional]932 pub fn delete_collection_properties(933 collection: &CollectionHandle<T>,934 sender: &T::CrossAccountId,935 property_keys: Vec<PropertyKey>,936 ) -> DispatchResult {937 for key in property_keys {938 Self::delete_collection_property(collection, sender, key)?;939 }940941 Ok(())942 }943944 945 pub fn set_property_permission_unchecked(946 collection: CollectionId,947 property_permission: PropertyKeyPermission,948 ) -> DispatchResult {949 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {950 permissions.try_set(property_permission.key, property_permission.permission)951 })952 .map_err(<Error<T>>::from)?;953 Ok(())954 }955956 pub fn set_property_permission(957 collection: &CollectionHandle<T>,958 sender: &T::CrossAccountId,959 property_permission: PropertyKeyPermission,960 ) -> DispatchResult {961 collection.check_is_owner_or_admin(sender)?;962963 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);964 let current_permission = all_permissions.get(&property_permission.key);965 if matches![966 current_permission,967 Some(PropertyPermission { mutable: false, .. })968 ] {969 return Err(<Error<T>>::NoPermission.into());970 }971972 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {973 let property_permission = property_permission.clone();974 permissions.try_set(property_permission.key, property_permission.permission)975 })976 .map_err(<Error<T>>::from)?;977978 Self::deposit_event(Event::PropertyPermissionSet(979 collection.id,980 property_permission.key,981 ));982983 Ok(())984 }985986 #[transactional]987 pub fn set_property_permissions(988 collection: &CollectionHandle<T>,989 sender: &T::CrossAccountId,990 property_permissions: Vec<PropertyKeyPermission>,991 ) -> DispatchResult {992 for prop_pemission in property_permissions {993 Self::set_property_permission(collection, sender, prop_pemission)?;994 }995996 Ok(())997 }998999 pub fn get_collection_property(1000 collection_id: CollectionId,1001 key: &PropertyKey,1002 ) -> Option<PropertyValue> {1003 Self::collection_properties(collection_id).get(key).cloned()1004 }10051006 pub fn bytes_keys_to_property_keys(1007 keys: Vec<Vec<u8>>,1008 ) -> Result<Vec<PropertyKey>, DispatchError> {1009 keys.into_iter()1010 .map(|key| -> Result<PropertyKey, DispatchError> {1011 key.try_into()1012 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1013 })1014 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1015 }10161017 pub fn filter_collection_properties(1018 collection_id: CollectionId,1019 keys: Option<Vec<PropertyKey>>,1020 ) -> Result<Vec<Property>, DispatchError> {1021 let properties = Self::collection_properties(collection_id);10221023 let properties = keys1024 .map(|keys| {1025 keys.into_iter()1026 .filter_map(|key| {1027 properties.get(&key).map(|value| Property {1028 key,1029 value: value.clone(),1030 })1031 })1032 .collect()1033 })1034 .unwrap_or_else(|| {1035 properties1036 .into_iter()1037 .map(|(key, value)| Property { key, value })1038 .collect()1039 });10401041 Ok(properties)1042 }10431044 pub fn filter_property_permissions(1045 collection_id: CollectionId,1046 keys: Option<Vec<PropertyKey>>,1047 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1048 let permissions = Self::property_permissions(collection_id);10491050 let key_permissions = keys1051 .map(|keys| {1052 keys.into_iter()1053 .filter_map(|key| {1054 permissions1055 .get(&key)1056 .map(|permission| PropertyKeyPermission {1057 key,1058 permission: permission.clone(),1059 })1060 })1061 .collect()1062 })1063 .unwrap_or_else(|| {1064 permissions1065 .into_iter()1066 .map(|(key, permission)| PropertyKeyPermission { key, permission })1067 .collect()1068 });10691070 Ok(key_permissions)1071 }10721073 pub fn toggle_allowlist(1074 collection: &CollectionHandle<T>,1075 sender: &T::CrossAccountId,1076 user: &T::CrossAccountId,1077 allowed: bool,1078 ) -> DispatchResult {1079 collection.check_is_owner_or_admin(sender)?;10801081 10821083 if allowed {1084 <Allowlist<T>>::insert((collection.id, user), true);1085 } else {1086 <Allowlist<T>>::remove((collection.id, user));1087 }10881089 Ok(())1090 }10911092 pub fn toggle_admin(1093 collection: &CollectionHandle<T>,1094 sender: &T::CrossAccountId,1095 user: &T::CrossAccountId,1096 admin: bool,1097 ) -> DispatchResult {1098 collection.check_is_owner_or_admin(sender)?;10991100 let was_admin = <IsAdmin<T>>::get((collection.id, user));1101 if was_admin == admin {1102 return Ok(());1103 }1104 let amount = <AdminAmount<T>>::get(collection.id);11051106 if admin {1107 let amount = amount1108 .checked_add(1)1109 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1110 ensure!(1111 amount <= Self::collection_admins_limit(),1112 <Error<T>>::CollectionAdminCountExceeded,1113 );11141115 11161117 <AdminAmount<T>>::insert(collection.id, amount);1118 <IsAdmin<T>>::insert((collection.id, user), true);1119 } else {1120 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1121 <IsAdmin<T>>::remove((collection.id, user));1122 }11231124 Ok(())1125 }11261127 pub fn clamp_limits(1128 mode: CollectionMode,1129 old_limit: &CollectionLimits,1130 mut new_limit: CollectionLimits,1131 ) -> Result<CollectionLimits, DispatchError> {1132 limit_default!(old_limit, new_limit,1133 account_token_ownership_limit => ensure!(1134 new_limit <= MAX_TOKEN_OWNERSHIP,1135 <Error<T>>::CollectionLimitBoundsExceeded,1136 ),1137 sponsored_data_size => ensure!(1138 new_limit <= CUSTOM_DATA_LIMIT,1139 <Error<T>>::CollectionLimitBoundsExceeded,1140 ),11411142 sponsored_data_rate_limit => {},1143 token_limit => ensure!(1144 old_limit >= new_limit && new_limit > 0,1145 <Error<T>>::CollectionTokenLimitExceeded1146 ),11471148 sponsor_transfer_timeout(match mode {1149 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1150 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1151 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1152 }) => ensure!(1153 new_limit <= MAX_SPONSOR_TIMEOUT,1154 <Error<T>>::CollectionLimitBoundsExceeded,1155 ),1156 sponsor_approve_timeout => {},1157 owner_can_transfer => ensure!(1158 old_limit || !new_limit,1159 <Error<T>>::OwnerPermissionsCantBeReverted,1160 ),1161 owner_can_destroy => ensure!(1162 old_limit || !new_limit,1163 <Error<T>>::OwnerPermissionsCantBeReverted,1164 ),1165 transfers_enabled => {},1166 );1167 Ok(new_limit)1168 }1169 pub fn clamp_permissions(1170 mode: CollectionMode,1171 old_limit: &CollectionPermissions,1172 mut new_limit: CollectionPermissions,1173 ) -> Result<CollectionPermissions, DispatchError> {1174 limit_default_clone!(old_limit, new_limit,1175 access => {},1176 mint_mode => {},1177 nesting => {},1178 );1179 Ok(new_limit)1180 }1181}11821183#[macro_export]1184macro_rules! unsupported {1185 () => {1186 Err(<Error<T>>::UnsupportedOperation.into())1187 };1188}118911901191pub trait CommonWeightInfo<CrossAccountId> {1192 fn create_item() -> Weight;1193 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;1194 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1195 fn burn_item() -> Weight;1196 fn set_collection_properties(amount: u32) -> Weight;1197 fn delete_collection_properties(amount: u32) -> Weight;1198 fn set_token_properties(amount: u32) -> Weight;1199 fn delete_token_properties(amount: u32) -> Weight;1200 fn set_property_permissions(amount: u32) -> Weight;1201 fn transfer() -> Weight;1202 fn approve() -> Weight;1203 fn transfer_from() -> Weight;1204 fn burn_from() -> Weight;1205}12061207pub trait CommonCollectionOperations<T: Config> {1208 fn create_item(1209 &self,1210 sender: T::CrossAccountId,1211 to: T::CrossAccountId,1212 data: CreateItemData,1213 nesting_budget: &dyn Budget,1214 ) -> DispatchResultWithPostInfo;1215 fn create_multiple_items(1216 &self,1217 sender: T::CrossAccountId,1218 to: T::CrossAccountId,1219 data: Vec<CreateItemData>,1220 nesting_budget: &dyn Budget,1221 ) -> DispatchResultWithPostInfo;1222 fn create_multiple_items_ex(1223 &self,1224 sender: T::CrossAccountId,1225 data: CreateItemExData<T::CrossAccountId>,1226 nesting_budget: &dyn Budget,1227 ) -> DispatchResultWithPostInfo;1228 fn burn_item(1229 &self,1230 sender: T::CrossAccountId,1231 token: TokenId,1232 amount: u128,1233 ) -> DispatchResultWithPostInfo;1234 fn set_collection_properties(1235 &self,1236 sender: T::CrossAccountId,1237 properties: Vec<Property>,1238 ) -> DispatchResultWithPostInfo;1239 fn delete_collection_properties(1240 &self,1241 sender: &T::CrossAccountId,1242 property_keys: Vec<PropertyKey>,1243 ) -> DispatchResultWithPostInfo;1244 fn set_token_properties(1245 &self,1246 sender: T::CrossAccountId,1247 token_id: TokenId,1248 property: Vec<Property>,1249 ) -> DispatchResultWithPostInfo;1250 fn delete_token_properties(1251 &self,1252 sender: T::CrossAccountId,1253 token_id: TokenId,1254 property_keys: Vec<PropertyKey>,1255 ) -> DispatchResultWithPostInfo;1256 fn set_property_permissions(1257 &self,1258 sender: &T::CrossAccountId,1259 property_permissions: Vec<PropertyKeyPermission>,1260 ) -> DispatchResultWithPostInfo;1261 fn transfer(1262 &self,1263 sender: T::CrossAccountId,1264 to: T::CrossAccountId,1265 token: TokenId,1266 amount: u128,1267 nesting_budget: &dyn Budget,1268 ) -> DispatchResultWithPostInfo;1269 fn approve(1270 &self,1271 sender: T::CrossAccountId,1272 spender: T::CrossAccountId,1273 token: TokenId,1274 amount: u128,1275 ) -> DispatchResultWithPostInfo;1276 fn transfer_from(1277 &self,1278 sender: T::CrossAccountId,1279 from: T::CrossAccountId,1280 to: T::CrossAccountId,1281 token: TokenId,1282 amount: u128,1283 nesting_budget: &dyn Budget,1284 ) -> DispatchResultWithPostInfo;1285 fn burn_from(1286 &self,1287 sender: T::CrossAccountId,1288 from: T::CrossAccountId,1289 token: TokenId,1290 amount: u128,1291 nesting_budget: &dyn Budget,1292 ) -> DispatchResultWithPostInfo;12931294 fn check_nesting(1295 &self,1296 sender: T::CrossAccountId,1297 from: (CollectionId, TokenId),1298 under: TokenId,1299 budget: &dyn Budget,1300 ) -> DispatchResult;13011302 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));13031304 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));13051306 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1307 fn collection_tokens(&self) -> Vec<TokenId>;1308 fn token_exists(&self, token: TokenId) -> bool;1309 fn last_token_id(&self) -> TokenId;13101311 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1312 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;1313 fn token_properties(&self, token_id: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;1314 1315 fn total_supply(&self) -> u32;1316 1317 fn account_balance(&self, account: T::CrossAccountId) -> u32;1318 1319 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1320 fn allowance(1321 &self,1322 sender: T::CrossAccountId,1323 spender: T::CrossAccountId,1324 token: TokenId,1325 ) -> u128;1326}132713281329pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1330 let post_info = PostDispatchInfo {1331 actual_weight: Some(weight),1332 pays_fee: Pays::Yes,1333 };1334 match res {1335 Ok(()) => Ok(post_info),1336 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1337 }1338}13391340impl<T: Config> From<PropertiesError> for Error<T> {1341 fn from(error: PropertiesError) -> Self {1342 match error {1343 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1344 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1345 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1346 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1347 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1348 }1349 }1350}