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, collections::btree_map::BTreeMap};22use pallet_evm::account::CrossAccountId;23use frame_support::{24 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},25 ensure, fail,26 traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},27 BoundedVec,28 weights::Pays,29};30use pallet_evm::GasWeightMapping;31use up_data_structs::{32 COLLECTION_NUMBER_LIMIT, Collection, RpcCollection, CollectionId, CreateItemData,33 MAX_TOKEN_PREFIX_LENGTH, COLLECTION_ADMINS_LIMIT, MetaUpdatePermission, TokenId,34 CollectionStats, MAX_TOKEN_OWNERSHIP, CollectionMode, NFT_SPONSOR_TRANSFER_TIMEOUT,35 FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, MAX_SPONSOR_TIMEOUT,36 CUSTOM_DATA_LIMIT, CollectionLimits, CustomDataLimit, CreateCollectionData, SponsorshipState,37 CreateItemExData, SponsoringRateLimit, budget::Budget, COLLECTION_FIELD_LIMIT, CollectionField,38 PhantomType, Property, Properties, PropertiesPermissionMap, PropertyKey, PropertyPermission,39 PropertiesError, PropertyKeyPermission,40};41pub use pallet::*;42use sp_core::H160;43use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};44#[cfg(feature = "runtime-benchmarks")]45pub mod benchmarking;46pub mod dispatch;47pub mod erc;48pub mod eth;4950#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]51pub struct CollectionHandle<T: Config> {52 pub id: CollectionId,53 collection: Collection<T::AccountId>,54 pub recorder: SubstrateRecorder<T>,55}56impl<T: Config> WithRecorder<T> for CollectionHandle<T> {57 fn recorder(&self) -> &SubstrateRecorder<T> {58 &self.recorder59 }60 fn into_recorder(self) -> SubstrateRecorder<T> {61 self.recorder62 }63}64impl<T: Config> CollectionHandle<T> {65 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {66 <CollectionById<T>>::get(id).map(|collection| Self {67 id,68 collection,69 recorder: SubstrateRecorder::new(eth::collection_id_to_address(id), gas_limit),70 })71 }72 pub fn new(id: CollectionId) -> Option<Self> {73 Self::new_with_gas_limit(id, u64::MAX)74 }75 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {76 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)77 }78 pub fn log_mirrored(&self, log: impl evm_coder::ToLog) {79 self.recorder.log_mirrored(log)80 }81 pub fn log_direct(&self, log: impl evm_coder::ToLog) {82 self.recorder.log_direct(log)83 }84 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {85 self.recorder86 .consume_gas(T::GasWeightMapping::weight_to_gas(87 <T as frame_system::Config>::DbWeight::get()88 .read89 .saturating_mul(reads),90 ))91 }92 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {93 self.recorder94 .consume_gas(T::GasWeightMapping::weight_to_gas(95 <T as frame_system::Config>::DbWeight::get()96 .write97 .saturating_mul(writes),98 ))99 }100 pub fn submit_logs(self) {101 self.recorder.submit_logs()102 }103 pub fn save(self) -> DispatchResult {104 self.recorder.submit_logs();105 <CollectionById<T>>::insert(self.id, self.collection);106 Ok(())107 }108}109impl<T: Config> Deref for CollectionHandle<T> {110 type Target = Collection<T::AccountId>;111112 fn deref(&self) -> &Self::Target {113 &self.collection114 }115}116117impl<T: Config> DerefMut for CollectionHandle<T> {118 fn deref_mut(&mut self) -> &mut Self::Target {119 &mut self.collection120 }121}122123impl<T: Config> CollectionHandle<T> {124 pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {125 ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);126 Ok(())127 }128 pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {129 *subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))130 }131 pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {132 ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);133 Ok(())134 }135 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {136 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)137 }138 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {139 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)140 }141 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {142 ensure!(143 <Allowlist<T>>::get((self.id, user)),144 <Error<T>>::AddressNotInAllowlist145 );146 Ok(())147 }148149 pub fn check_can_update_meta(150 &self,151 subject: &T::CrossAccountId,152 item_owner: &T::CrossAccountId,153 ) -> DispatchResult {154 match self.meta_update_permission {155 MetaUpdatePermission::ItemOwner => {156 ensure!(subject == item_owner, <Error<T>>::NoPermission);157 Ok(())158 }159 MetaUpdatePermission::Admin => self.check_is_owner_or_admin(subject),160 MetaUpdatePermission::None => fail!(<Error<T>>::NoPermission),161 }162 }163}164165#[frame_support::pallet]166pub mod pallet {167 use super::*;168 use pallet_evm::account;169 use dispatch::CollectionDispatch;170 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};171 use frame_system::pallet_prelude::*;172 use frame_support::traits::Currency;173 use up_data_structs::{TokenId, mapping::TokenAddressMapping};174 use scale_info::TypeInfo;175176 #[pallet::config]177 pub trait Config:178 frame_system::Config + pallet_evm_coder_substrate::Config + TypeInfo + account::Config179 {180 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;181182 type Currency: Currency<Self::AccountId>;183184 #[pallet::constant]185 type CollectionCreationPrice: Get<186 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,187 >;188 type CollectionDispatch: CollectionDispatch<Self>;189190 type TreasuryAccountId: Get<Self::AccountId>;191192 type EvmTokenAddressMapping: TokenAddressMapping<H160>;193 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;194 }195196 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);197198 #[pallet::pallet]199 #[pallet::storage_version(STORAGE_VERSION)]200 #[pallet::generate_store(pub(super) trait Store)]201 pub struct Pallet<T>(_);202203 #[pallet::extra_constants]204 impl<T: Config> Pallet<T> {205 pub fn collection_admins_limit() -> u32 {206 COLLECTION_ADMINS_LIMIT207 }208 }209210 #[pallet::event]211 #[pallet::generate_deposit(pub fn deposit_event)]212 pub enum Event<T: Config> {213 214 215 216 217 218 219 220 221 222 CollectionCreated(CollectionId, u8, T::AccountId),223224 225 226 227 228 229 CollectionDestroyed(CollectionId),230231 232 233 234 235 236 237 238 239 240 241 242 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),243244 245 246 247 248 249 250 251 252 253 254 255 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),256257 258 259 260 261 262 263 264 265 266 267 268 Transfer(269 CollectionId,270 TokenId,271 T::CrossAccountId,272 T::CrossAccountId,273 u128,274 ),275276 277 278 279 280 281 282 283 284 285 Approved(286 CollectionId,287 TokenId,288 T::CrossAccountId,289 T::CrossAccountId,290 u128,291 ),292293 CollectionPropertySet(CollectionId, Property),294295 TokenPropertySet(CollectionId, TokenId, Property),296297 PropertyPermissionSet(CollectionId, PropertyKeyPermission),298 }299300 #[pallet::error]301 pub enum Error<T> {302 303 CollectionNotFound,304 305 MustBeTokenOwner,306 307 NoPermission,308 309 PublicMintingNotAllowed,310 311 AddressNotInAllowlist,312313 314 CollectionNameLimitExceeded,315 316 CollectionDescriptionLimitExceeded,317 318 CollectionTokenPrefixLimitExceeded,319 320 TotalCollectionsLimitExceeded,321 322 TokenVariableDataLimitExceeded,323 324 CollectionAdminCountExceeded,325 326 CollectionLimitBoundsExceeded,327 328 OwnerPermissionsCantBeReverted,329 330 TransferNotAllowed,331 332 AccountTokenLimitExceeded,333 334 CollectionTokenLimitExceeded,335 336 MetadataFlagFrozen,337338 339 TokenNotFound,340 341 TokenValueTooLow,342 343 ApprovedValueTooLow,344 345 CantApproveMoreThanOwned,346347 348 AddressIsZero,349 350 UnsupportedOperation,351352 353 NotSufficientFounds,354355 356 NestingIsDisabled,357 358 OnlyOwnerAllowedToNest,359 360 SourceCollectionIsNotAllowedToNest,361362 363 CollectionFieldSizeExceeded,364 }365366 #[pallet::storage]367 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;368 #[pallet::storage]369 pub type DestroyedCollectionCount<T> =370 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;371372 373 #[pallet::storage]374 pub type CollectionById<T> = StorageMap<375 Hasher = Blake2_128Concat,376 Key = CollectionId,377 Value = Collection<<T as frame_system::Config>::AccountId>,378 QueryKind = OptionQuery,379 >;380381 382 #[pallet::storage]383 pub type CollectionProperties<T> = StorageMap<384 Hasher = Blake2_128Concat,385 Key = CollectionId,386 Value = Properties,387 QueryKind = ValueQuery,388 OnEmpty = up_data_structs::CollectionProperties,389 >;390391 #[pallet::storage]392 #[pallet::getter(fn property_permission)]393 pub type CollectionPropertyPermissions<T> = StorageMap<394 Hasher = Blake2_128Concat,395 Key = CollectionId,396 Value = PropertiesPermissionMap,397 QueryKind = ValueQuery,398 >;399400 401 #[pallet::storage]402 pub type CollectionData<T> = StorageNMap<403 Key = (404 Key<Twox64Concat, CollectionId>,405 Key<Twox64Concat, CollectionField>,406 ),407 Value = BoundedVec<u8, ConstU32<COLLECTION_FIELD_LIMIT>>,408 QueryKind = ValueQuery,409 >;410411 #[pallet::storage]412 pub type AdminAmount<T> = StorageMap<413 Hasher = Blake2_128Concat,414 Key = CollectionId,415 Value = u32,416 QueryKind = ValueQuery,417 >;418419 420 #[pallet::storage]421 pub type IsAdmin<T: Config> = StorageNMap<422 Key = (423 Key<Blake2_128Concat, CollectionId>,424 Key<Blake2_128Concat, T::CrossAccountId>,425 ),426 Value = bool,427 QueryKind = ValueQuery,428 >;429430 431 #[pallet::storage]432 pub type Allowlist<T: Config> = StorageNMap<433 Key = (434 Key<Blake2_128Concat, CollectionId>,435 Key<Blake2_128Concat, T::CrossAccountId>,436 ),437 Value = bool,438 QueryKind = ValueQuery,439 >;440441 442 #[pallet::storage]443 pub type DummyStorageValue<T: Config> = StorageValue<444 Value = (445 CollectionStats,446 CollectionId,447 TokenId,448 PhantomType<RpcCollection<T::AccountId>>,449 ),450 QueryKind = OptionQuery,451 >;452453 #[pallet::hooks]454 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {455 fn on_runtime_upgrade() -> Weight {456 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {457 use up_data_structs::{CollectionVersion1, CollectionVersion2};458 <CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {459 Self::set_field_raw(460 id,461 CollectionField::OffchainSchema,462 v.offchain_schema.clone().into_inner(),463 )464 .expect("data has lower bounds than field");465 Self::set_field_raw(466 id,467 CollectionField::VariableOnChainSchema,468 v.variable_on_chain_schema.clone().into_inner(),469 )470 .expect("data has lower bounds than field");471 Self::set_field_raw(472 id,473 CollectionField::ConstOnChainSchema,474 v.const_on_chain_schema.clone().into_inner(),475 )476 .expect("data has lower bounds than field");477478 Some(CollectionVersion2::from(v))479 });480 }481482 0483 }484 }485}486487impl<T: Config> Pallet<T> {488 489 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {490 ensure!(491 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,492 <Error<T>>::AddressIsZero493 );494 Ok(())495 }496 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {497 <IsAdmin<T>>::iter_prefix((collection,))498 .map(|(a, _)| a)499 .collect()500 }501 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {502 <Allowlist<T>>::iter_prefix((collection,))503 .map(|(a, _)| a)504 .collect()505 }506 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {507 <Allowlist<T>>::get((collection, user))508 }509 pub fn collection_stats() -> CollectionStats {510 let created = <CreatedCollectionCount<T>>::get();511 let destroyed = <DestroyedCollectionCount<T>>::get();512 CollectionStats {513 created: created.0,514 destroyed: destroyed.0,515 alive: created.0 - destroyed.0,516 }517 }518519 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {520 let collection = <CollectionById<T>>::get(collection);521 if collection.is_none() {522 return None;523 }524525 let collection = collection.unwrap();526 let limits = collection.limits;527 let effective_limits = CollectionLimits {528 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),529 sponsored_data_size: Some(limits.sponsored_data_size()),530 sponsored_data_rate_limit: Some(531 limits532 .sponsored_data_rate_limit533 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),534 ),535 token_limit: Some(limits.token_limit()),536 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(537 match collection.mode {538 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,539 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,540 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,541 },542 )),543 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),544 owner_can_transfer: Some(limits.owner_can_transfer()),545 owner_can_destroy: Some(limits.owner_can_destroy()),546 transfers_enabled: Some(limits.transfers_enabled()),547 nesting_rule: Some(limits.nesting_rule().clone()),548 };549550 Some(effective_limits)551 }552553 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {554 let Collection {555 name,556 description,557 owner,558 mode,559 access,560 token_prefix,561 mint_mode,562 schema_version,563 sponsorship,564 limits,565 meta_update_permission,566 ..567 } = <CollectionById<T>>::get(collection)?;568 Some(RpcCollection {569 name: name.into_inner(),570 description: description.into_inner(),571 owner,572 mode,573 access,574 token_prefix: token_prefix.into_inner(),575 mint_mode,576 schema_version,577 sponsorship,578 limits,579 meta_update_permission,580 offchain_schema: <CollectionData<T>>::get((581 collection,582 CollectionField::OffchainSchema,583 ))584 .into_inner(),585 const_on_chain_schema: <CollectionData<T>>::get((586 collection,587 CollectionField::ConstOnChainSchema,588 ))589 .into_inner(),590 variable_on_chain_schema: <CollectionData<T>>::get((591 collection,592 CollectionField::VariableOnChainSchema,593 ))594 .into_inner(),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 meta_update_permission: data.meta_update_permission.unwrap_or_default(),644 645 646 };647648 CollectionProperties::<T>::insert(649 id,650 Properties::from_collection_props_vec(data.properties)?,651 );652653 let token_props_permissions: PropertiesPermissionMap = data654 .token_property_permissions655 .into_iter()656 .map(|property| (property.key, property.permission))657 .collect::<BTreeMap<_, _>>()658 .try_into()659 .map_err(|_| PropertiesError::PropertyLimitReached)?;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::VariableOnChainSchema,694 data.variable_on_chain_schema.into_inner(),695 )696 .expect("data has lower bounds than field");697 Self::set_field_raw(698 id,699 CollectionField::ConstOnChainSchema,700 data.const_on_chain_schema.into_inner(),701 )702 .expect("data has lower bounds than field");703 Ok(id)704 }705706 pub fn destroy_collection(707 collection: CollectionHandle<T>,708 sender: &T::CrossAccountId,709 ) -> DispatchResult {710 ensure!(711 collection.limits.owner_can_destroy(),712 <Error<T>>::NoPermission,713 );714 collection.check_is_owner(sender)?;715716 let destroyed_collections = <DestroyedCollectionCount<T>>::get()717 .0718 .checked_add(1)719 .ok_or(ArithmeticError::Overflow)?;720721 722723 <DestroyedCollectionCount<T>>::put(destroyed_collections);724 <CollectionById<T>>::remove(collection.id);725 <CollectionData<T>>::remove_prefix((collection.id,), None);726 <AdminAmount<T>>::remove(collection.id);727 <IsAdmin<T>>::remove_prefix((collection.id,), None);728 <Allowlist<T>>::remove_prefix((collection.id,), None);729730 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));731 Ok(())732 }733734 pub fn set_collection_property(735 collection: &CollectionHandle<T>,736 sender: &T::CrossAccountId,737 property: Property,738 ) -> DispatchResult {739 collection.check_is_owner_or_admin(sender)?;740741 CollectionProperties::<T>::try_mutate(742 collection.id,743 |properties| properties.try_set_property(property.clone())744 )?;745746 Self::deposit_event(Event::CollectionPropertySet(collection.id, property));747748 Ok(())749 }750751 pub fn set_collection_properties(752 collection: &CollectionHandle<T>,753 sender: &T::CrossAccountId,754 properties: Vec<Property>,755 ) -> DispatchResult {756 for property in properties {757 Self::set_collection_property(collection, sender, property)?;758 }759760 Ok(())761 }762763 pub fn set_property_permission(764 collection: &CollectionHandle<T>,765 sender: &T::CrossAccountId,766 property_permission: PropertyKeyPermission767 ) -> DispatchResult {768 collection.check_is_owner_or_admin(sender)?;769770 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);771 let current_permission = all_permissions.get(&property_permission.key);772 if matches![current_permission, Some(PropertyPermission::AdminConst | PropertyPermission::ItemOwnerConst)] {773 return Err(<Error<T>>::NoPermission.into());774 }775776 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {777 let property_permission = property_permission.clone();778 permissions.try_insert(property_permission.key, property_permission.permission)779 })780 .map_err(|_| PropertiesError::PropertyLimitReached)?;781782 Self::deposit_event(Event::PropertyPermissionSet(collection.id, property_permission));783784 Ok(())785 }786787 pub fn set_property_permissions(788 collection: &CollectionHandle<T>,789 sender: &T::CrossAccountId,790 property_permissions: Vec<PropertyKeyPermission>791 ) -> DispatchResult {792 for prop_pemission in property_permissions {793 Self::set_property_permission(collection, sender, prop_pemission)?;794 }795796 Ok(())797 }798799 fn set_field_raw(800 collection_id: CollectionId,801 field: CollectionField,802 value: Vec<u8>,803 ) -> DispatchResult {804 if !value.is_empty() {805 <CollectionData<T>>::insert(806 (collection_id, field),807 BoundedVec::try_from(value).map_err(|_| <Error<T>>::CollectionFieldSizeExceeded)?,808 )809 } else {810 <CollectionData<T>>::remove((collection_id, field));811 }812 Ok(())813 }814815 pub fn set_field(816 collection: &CollectionHandle<T>,817 sender: &T::CrossAccountId,818 field: CollectionField,819 value: Vec<u8>,820 ) -> DispatchResult {821 collection.check_is_owner_or_admin(sender)?;822823 824825 Self::set_field_raw(collection.id, field, value)826 }827828 pub fn toggle_allowlist(829 collection: &CollectionHandle<T>,830 sender: &T::CrossAccountId,831 user: &T::CrossAccountId,832 allowed: bool,833 ) -> DispatchResult {834 collection.check_is_owner_or_admin(sender)?;835836 837838 if allowed {839 <Allowlist<T>>::insert((collection.id, user), true);840 } else {841 <Allowlist<T>>::remove((collection.id, user));842 }843844 Ok(())845 }846847 pub fn toggle_admin(848 collection: &CollectionHandle<T>,849 sender: &T::CrossAccountId,850 user: &T::CrossAccountId,851 admin: bool,852 ) -> DispatchResult {853 collection.check_is_owner_or_admin(sender)?;854855 let was_admin = <IsAdmin<T>>::get((collection.id, user));856 if was_admin == admin {857 return Ok(());858 }859 let amount = <AdminAmount<T>>::get(collection.id);860861 if admin {862 let amount = amount863 .checked_add(1)864 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;865 ensure!(866 amount <= Self::collection_admins_limit(),867 <Error<T>>::CollectionAdminCountExceeded,868 );869870 871872 <AdminAmount<T>>::insert(collection.id, amount);873 <IsAdmin<T>>::insert((collection.id, user), true);874 } else {875 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));876 <IsAdmin<T>>::remove((collection.id, user));877 }878879 Ok(())880 }881882 pub fn clamp_limits(883 mode: CollectionMode,884 old_limit: &CollectionLimits,885 mut new_limit: CollectionLimits,886 ) -> Result<CollectionLimits, DispatchError> {887 macro_rules! limit_default {888 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{889 $(890 if let Some($new) = $new.$field {891 let $old = $old.$field($($arg)?);892 let _ = $new;893 let _ = $old;894 $check895 } else {896 $new.$field = $old.$field897 }898 )*899 }};900 }901902 limit_default!(old_limit, new_limit,903 account_token_ownership_limit => ensure!(904 new_limit <= MAX_TOKEN_OWNERSHIP,905 <Error<T>>::CollectionLimitBoundsExceeded,906 ),907 sponsor_transfer_timeout(match mode {908 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,909 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,910 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,911 }) => ensure!(912 new_limit <= MAX_SPONSOR_TIMEOUT,913 <Error<T>>::CollectionLimitBoundsExceeded,914 ),915 sponsored_data_size => ensure!(916 new_limit <= CUSTOM_DATA_LIMIT,917 <Error<T>>::CollectionLimitBoundsExceeded,918 ),919 token_limit => ensure!(920 old_limit >= new_limit && new_limit > 0,921 <Error<T>>::CollectionTokenLimitExceeded922 ),923 owner_can_transfer => ensure!(924 old_limit || !new_limit,925 <Error<T>>::OwnerPermissionsCantBeReverted,926 ),927 owner_can_destroy => ensure!(928 old_limit || !new_limit,929 <Error<T>>::OwnerPermissionsCantBeReverted,930 ),931 sponsored_data_rate_limit => {},932 transfers_enabled => {},933 );934 Ok(new_limit)935 }936}937938#[macro_export]939macro_rules! unsupported {940 () => {941 Err(<Error<T>>::UnsupportedOperation.into())942 };943}944945946pub trait CommonWeightInfo<CrossAccountId> {947 fn create_item() -> Weight;948 fn create_multiple_items(amount: u32) -> Weight;949 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;950 fn burn_item() -> Weight;951 fn set_collection_properties(amount: u32) -> Weight;952 fn set_token_properties(amount: u32) -> Weight;953 fn set_property_permissions(amount: u32) -> Weight;954 fn transfer() -> Weight;955 fn approve() -> Weight;956 fn transfer_from() -> Weight;957 fn burn_from() -> Weight;958 fn set_variable_metadata(bytes: u32) -> Weight;959}960961pub trait CommonCollectionOperations<T: Config> {962 fn create_item(963 &self,964 sender: T::CrossAccountId,965 to: T::CrossAccountId,966 data: CreateItemData,967 nesting_budget: &dyn Budget,968 ) -> DispatchResultWithPostInfo;969 fn create_multiple_items(970 &self,971 sender: T::CrossAccountId,972 to: T::CrossAccountId,973 data: Vec<CreateItemData>,974 nesting_budget: &dyn Budget,975 ) -> DispatchResultWithPostInfo;976 fn create_multiple_items_ex(977 &self,978 sender: T::CrossAccountId,979 data: CreateItemExData<T::CrossAccountId>,980 nesting_budget: &dyn Budget,981 ) -> DispatchResultWithPostInfo;982 fn burn_item(983 &self,984 sender: T::CrossAccountId,985 token: TokenId,986 amount: u128,987 ) -> DispatchResultWithPostInfo;988 fn set_collection_properties(989 &self,990 sender: T::CrossAccountId,991 properties: Vec<Property>,992 ) -> DispatchResultWithPostInfo;993 fn set_token_properties(994 &self,995 sender: T::CrossAccountId,996 token_id: TokenId,997 property: Vec<Property>,998 ) -> DispatchResultWithPostInfo;999 fn set_property_permissions(1000 &self,1001 sender: &T::CrossAccountId,1002 property_permissions: Vec<PropertyKeyPermission>,1003 ) -> DispatchResultWithPostInfo;1004 fn transfer(1005 &self,1006 sender: T::CrossAccountId,1007 to: T::CrossAccountId,1008 token: TokenId,1009 amount: u128,1010 nesting_budget: &dyn Budget,1011 ) -> DispatchResultWithPostInfo;1012 fn approve(1013 &self,1014 sender: T::CrossAccountId,1015 spender: T::CrossAccountId,1016 token: TokenId,1017 amount: u128,1018 ) -> DispatchResultWithPostInfo;1019 fn transfer_from(1020 &self,1021 sender: T::CrossAccountId,1022 from: T::CrossAccountId,1023 to: T::CrossAccountId,1024 token: TokenId,1025 amount: u128,1026 nesting_budget: &dyn Budget,1027 ) -> DispatchResultWithPostInfo;1028 fn burn_from(1029 &self,1030 sender: T::CrossAccountId,1031 from: T::CrossAccountId,1032 token: TokenId,1033 amount: u128,1034 nesting_budget: &dyn Budget,1035 ) -> DispatchResultWithPostInfo;10361037 fn set_variable_metadata(1038 &self,1039 sender: T::CrossAccountId,1040 token: TokenId,1041 data: BoundedVec<u8, CustomDataLimit>,1042 ) -> DispatchResultWithPostInfo;10431044 fn check_nesting(1045 &self,1046 sender: T::CrossAccountId,1047 from: (CollectionId, TokenId),1048 under: TokenId,1049 budget: &dyn Budget,1050 ) -> DispatchResult;10511052 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1053 fn collection_tokens(&self) -> Vec<TokenId>;1054 fn token_exists(&self, token: TokenId) -> bool;1055 fn last_token_id(&self) -> TokenId;10561057 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1058 fn const_metadata(&self, token: TokenId) -> Vec<u8>;1059 fn variable_metadata(&self, token: TokenId) -> Vec<u8>;10601061 1062 fn total_supply(&self) -> u32;1063 1064 fn account_balance(&self, account: T::CrossAccountId) -> u32;1065 1066 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1067 fn allowance(1068 &self,1069 sender: T::CrossAccountId,1070 spender: T::CrossAccountId,1071 token: TokenId,1072 ) -> u128;1073}107410751076pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1077 let post_info = PostDispatchInfo {1078 actual_weight: Some(weight),1079 pays_fee: Pays::Yes,1080 };1081 match res {1082 Ok(()) => Ok(post_info),1083 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1084 }1085}