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 TokenPropertyDeleted(CollectionId, TokenId, PropertyKey),298299 PropertyPermissionSet(CollectionId, PropertyKeyPermission),300 }301302 #[pallet::error]303 pub enum Error<T> {304 305 CollectionNotFound,306 307 MustBeTokenOwner,308 309 NoPermission,310 311 PublicMintingNotAllowed,312 313 AddressNotInAllowlist,314315 316 CollectionNameLimitExceeded,317 318 CollectionDescriptionLimitExceeded,319 320 CollectionTokenPrefixLimitExceeded,321 322 TotalCollectionsLimitExceeded,323 324 TokenVariableDataLimitExceeded,325 326 CollectionAdminCountExceeded,327 328 CollectionLimitBoundsExceeded,329 330 OwnerPermissionsCantBeReverted,331 332 TransferNotAllowed,333 334 AccountTokenLimitExceeded,335 336 CollectionTokenLimitExceeded,337 338 MetadataFlagFrozen,339340 341 TokenNotFound,342 343 TokenValueTooLow,344 345 ApprovedValueTooLow,346 347 CantApproveMoreThanOwned,348349 350 AddressIsZero,351 352 UnsupportedOperation,353354 355 NotSufficientFounds,356357 358 NestingIsDisabled,359 360 OnlyOwnerAllowedToNest,361 362 SourceCollectionIsNotAllowedToNest,363364 365 CollectionFieldSizeExceeded,366 }367368 #[pallet::storage]369 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;370 #[pallet::storage]371 pub type DestroyedCollectionCount<T> =372 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;373374 375 #[pallet::storage]376 pub type CollectionById<T> = StorageMap<377 Hasher = Blake2_128Concat,378 Key = CollectionId,379 Value = Collection<<T as frame_system::Config>::AccountId>,380 QueryKind = OptionQuery,381 >;382383 384 #[pallet::storage]385 pub type CollectionProperties<T> = StorageMap<386 Hasher = Blake2_128Concat,387 Key = CollectionId,388 Value = Properties,389 QueryKind = ValueQuery,390 OnEmpty = up_data_structs::CollectionProperties,391 >;392393 #[pallet::storage]394 #[pallet::getter(fn property_permission)]395 pub type CollectionPropertyPermissions<T> = StorageMap<396 Hasher = Blake2_128Concat,397 Key = CollectionId,398 Value = PropertiesPermissionMap,399 QueryKind = ValueQuery,400 >;401402 403 #[pallet::storage]404 pub type CollectionData<T> = StorageNMap<405 Key = (406 Key<Twox64Concat, CollectionId>,407 Key<Twox64Concat, CollectionField>,408 ),409 Value = BoundedVec<u8, ConstU32<COLLECTION_FIELD_LIMIT>>,410 QueryKind = ValueQuery,411 >;412413 #[pallet::storage]414 pub type AdminAmount<T> = StorageMap<415 Hasher = Blake2_128Concat,416 Key = CollectionId,417 Value = u32,418 QueryKind = ValueQuery,419 >;420421 422 #[pallet::storage]423 pub type IsAdmin<T: Config> = StorageNMap<424 Key = (425 Key<Blake2_128Concat, CollectionId>,426 Key<Blake2_128Concat, T::CrossAccountId>,427 ),428 Value = bool,429 QueryKind = ValueQuery,430 >;431432 433 #[pallet::storage]434 pub type Allowlist<T: Config> = StorageNMap<435 Key = (436 Key<Blake2_128Concat, CollectionId>,437 Key<Blake2_128Concat, T::CrossAccountId>,438 ),439 Value = bool,440 QueryKind = ValueQuery,441 >;442443 444 #[pallet::storage]445 pub type DummyStorageValue<T: Config> = StorageValue<446 Value = (447 CollectionStats,448 CollectionId,449 TokenId,450 PhantomType<RpcCollection<T::AccountId>>,451 ),452 QueryKind = OptionQuery,453 >;454455 #[pallet::hooks]456 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {457 fn on_runtime_upgrade() -> Weight {458 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {459 use up_data_structs::{CollectionVersion1, CollectionVersion2};460 <CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {461 Self::set_field_raw(462 id,463 CollectionField::OffchainSchema,464 v.offchain_schema.clone().into_inner(),465 )466 .expect("data has lower bounds than field");467 Self::set_field_raw(468 id,469 CollectionField::VariableOnChainSchema,470 v.variable_on_chain_schema.clone().into_inner(),471 )472 .expect("data has lower bounds than field");473 Self::set_field_raw(474 id,475 CollectionField::ConstOnChainSchema,476 v.const_on_chain_schema.clone().into_inner(),477 )478 .expect("data has lower bounds than field");479480 Some(CollectionVersion2::from(v))481 });482 }483484 0485 }486 }487}488489impl<T: Config> Pallet<T> {490 491 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {492 ensure!(493 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,494 <Error<T>>::AddressIsZero495 );496 Ok(())497 }498 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {499 <IsAdmin<T>>::iter_prefix((collection,))500 .map(|(a, _)| a)501 .collect()502 }503 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {504 <Allowlist<T>>::iter_prefix((collection,))505 .map(|(a, _)| a)506 .collect()507 }508 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {509 <Allowlist<T>>::get((collection, user))510 }511 pub fn collection_stats() -> CollectionStats {512 let created = <CreatedCollectionCount<T>>::get();513 let destroyed = <DestroyedCollectionCount<T>>::get();514 CollectionStats {515 created: created.0,516 destroyed: destroyed.0,517 alive: created.0 - destroyed.0,518 }519 }520521 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {522 let collection = <CollectionById<T>>::get(collection);523 if collection.is_none() {524 return None;525 }526527 let collection = collection.unwrap();528 let limits = collection.limits;529 let effective_limits = CollectionLimits {530 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),531 sponsored_data_size: Some(limits.sponsored_data_size()),532 sponsored_data_rate_limit: Some(533 limits534 .sponsored_data_rate_limit535 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),536 ),537 token_limit: Some(limits.token_limit()),538 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(539 match collection.mode {540 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,541 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,542 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,543 },544 )),545 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),546 owner_can_transfer: Some(limits.owner_can_transfer()),547 owner_can_destroy: Some(limits.owner_can_destroy()),548 transfers_enabled: Some(limits.transfers_enabled()),549 nesting_rule: Some(limits.nesting_rule().clone()),550 };551552 Some(effective_limits)553 }554555 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {556 let Collection {557 name,558 description,559 owner,560 mode,561 access,562 token_prefix,563 mint_mode,564 schema_version,565 sponsorship,566 limits,567 meta_update_permission,568 ..569 } = <CollectionById<T>>::get(collection)?;570 Some(RpcCollection {571 name: name.into_inner(),572 description: description.into_inner(),573 owner,574 mode,575 access,576 token_prefix: token_prefix.into_inner(),577 mint_mode,578 schema_version,579 sponsorship,580 limits,581 meta_update_permission,582 offchain_schema: <CollectionData<T>>::get((583 collection,584 CollectionField::OffchainSchema,585 ))586 .into_inner(),587 const_on_chain_schema: <CollectionData<T>>::get((588 collection,589 CollectionField::ConstOnChainSchema,590 ))591 .into_inner(),592 variable_on_chain_schema: <CollectionData<T>>::get((593 collection,594 CollectionField::VariableOnChainSchema,595 ))596 .into_inner(),597 })598 }599}600601impl<T: Config> Pallet<T> {602 pub fn init_collection(603 owner: T::AccountId,604 data: CreateCollectionData<T::AccountId>,605 ) -> Result<CollectionId, DispatchError> {606 {607 ensure!(608 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,609 Error::<T>::CollectionTokenPrefixLimitExceeded610 );611 }612613 let created_count = <CreatedCollectionCount<T>>::get()614 .0615 .checked_add(1)616 .ok_or(ArithmeticError::Overflow)?;617 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;618 let id = CollectionId(created_count);619620 621 ensure!(622 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,623 <Error<T>>::TotalCollectionsLimitExceeded624 );625626 627628 let collection = Collection {629 owner: owner.clone(),630 name: data.name,631 mode: data.mode.clone(),632 mint_mode: false,633 access: data.access.unwrap_or_default(),634 description: data.description,635 token_prefix: data.token_prefix,636 schema_version: data.schema_version.unwrap_or_default(),637 sponsorship: data638 .pending_sponsor639 .map(SponsorshipState::Unconfirmed)640 .unwrap_or_default(),641 limits: data642 .limits643 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))644 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,645 meta_update_permission: data.meta_update_permission.unwrap_or_default(),646 647 648 };649650 CollectionProperties::<T>::insert(651 id,652 Properties::from_collection_props_vec(data.properties)?,653 );654655 let token_props_permissions: PropertiesPermissionMap = data656 .token_property_permissions657 .into_iter()658 .map(|property| (property.key, property.permission))659 .collect::<BTreeMap<_, _>>()660 .try_into()661 .map_err(|_| PropertiesError::PropertyLimitReached)?;662663 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);664665 666 {667 let mut imbalance =668 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();669 imbalance.subsume(670 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(671 &T::TreasuryAccountId::get(),672 T::CollectionCreationPrice::get(),673 ),674 );675 <T as Config>::Currency::settle(676 &owner,677 imbalance,678 WithdrawReasons::TRANSFER,679 ExistenceRequirement::KeepAlive,680 )681 .map_err(|_| Error::<T>::NotSufficientFounds)?;682 }683684 <CreatedCollectionCount<T>>::put(created_count);685 <Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));686 <CollectionById<T>>::insert(id, collection);687 Self::set_field_raw(688 id,689 CollectionField::OffchainSchema,690 data.offchain_schema.into_inner(),691 )692 .expect("data has lower bounds than field");693 Self::set_field_raw(694 id,695 CollectionField::VariableOnChainSchema,696 data.variable_on_chain_schema.into_inner(),697 )698 .expect("data has lower bounds than field");699 Self::set_field_raw(700 id,701 CollectionField::ConstOnChainSchema,702 data.const_on_chain_schema.into_inner(),703 )704 .expect("data has lower bounds than field");705 Ok(id)706 }707708 pub fn destroy_collection(709 collection: CollectionHandle<T>,710 sender: &T::CrossAccountId,711 ) -> DispatchResult {712 ensure!(713 collection.limits.owner_can_destroy(),714 <Error<T>>::NoPermission,715 );716 collection.check_is_owner(sender)?;717718 let destroyed_collections = <DestroyedCollectionCount<T>>::get()719 .0720 .checked_add(1)721 .ok_or(ArithmeticError::Overflow)?;722723 724725 <DestroyedCollectionCount<T>>::put(destroyed_collections);726 <CollectionById<T>>::remove(collection.id);727 <CollectionData<T>>::remove_prefix((collection.id,), None);728 <AdminAmount<T>>::remove(collection.id);729 <IsAdmin<T>>::remove_prefix((collection.id,), None);730 <Allowlist<T>>::remove_prefix((collection.id,), None);731732 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));733 Ok(())734 }735736 pub fn set_collection_property(737 collection: &CollectionHandle<T>,738 sender: &T::CrossAccountId,739 property: Property,740 ) -> DispatchResult {741 collection.check_is_owner_or_admin(sender)?;742743 CollectionProperties::<T>::try_mutate(collection.id, |properties| {744 properties.try_set_property(property.clone())745 })?;746747 Self::deposit_event(Event::CollectionPropertySet(collection.id, property));748749 Ok(())750 }751752 pub fn set_collection_properties(753 collection: &CollectionHandle<T>,754 sender: &T::CrossAccountId,755 properties: Vec<Property>,756 ) -> DispatchResult {757 for property in properties {758 Self::set_collection_property(collection, sender, property)?;759 }760761 Ok(())762 }763764 pub fn set_property_permission(765 collection: &CollectionHandle<T>,766 sender: &T::CrossAccountId,767 property_permission: PropertyKeyPermission,768 ) -> DispatchResult {769 collection.check_is_owner_or_admin(sender)?;770771 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);772 let current_permission = all_permissions.get(&property_permission.key);773 if matches![774 current_permission,775 Some(PropertyPermission::AdminConst | PropertyPermission::ItemOwnerConst)776 ] {777 return Err(<Error<T>>::NoPermission.into());778 }779780 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {781 let property_permission = property_permission.clone();782 permissions.try_insert(property_permission.key, property_permission.permission)783 })784 .map_err(|_| PropertiesError::PropertyLimitReached)?;785786 Self::deposit_event(Event::PropertyPermissionSet(787 collection.id,788 property_permission,789 ));790791 Ok(())792 }793794 pub fn set_property_permissions(795 collection: &CollectionHandle<T>,796 sender: &T::CrossAccountId,797 property_permissions: Vec<PropertyKeyPermission>,798 ) -> DispatchResult {799 for prop_pemission in property_permissions {800 Self::set_property_permission(collection, sender, prop_pemission)?;801 }802803 Ok(())804 }805806 fn set_field_raw(807 collection_id: CollectionId,808 field: CollectionField,809 value: Vec<u8>,810 ) -> DispatchResult {811 if !value.is_empty() {812 <CollectionData<T>>::insert(813 (collection_id, field),814 BoundedVec::try_from(value).map_err(|_| <Error<T>>::CollectionFieldSizeExceeded)?,815 )816 } else {817 <CollectionData<T>>::remove((collection_id, field));818 }819 Ok(())820 }821822 pub fn set_field(823 collection: &CollectionHandle<T>,824 sender: &T::CrossAccountId,825 field: CollectionField,826 value: Vec<u8>,827 ) -> DispatchResult {828 collection.check_is_owner_or_admin(sender)?;829830 831832 Self::set_field_raw(collection.id, field, value)833 }834835 pub fn toggle_allowlist(836 collection: &CollectionHandle<T>,837 sender: &T::CrossAccountId,838 user: &T::CrossAccountId,839 allowed: bool,840 ) -> DispatchResult {841 collection.check_is_owner_or_admin(sender)?;842843 844845 if allowed {846 <Allowlist<T>>::insert((collection.id, user), true);847 } else {848 <Allowlist<T>>::remove((collection.id, user));849 }850851 Ok(())852 }853854 pub fn toggle_admin(855 collection: &CollectionHandle<T>,856 sender: &T::CrossAccountId,857 user: &T::CrossAccountId,858 admin: bool,859 ) -> DispatchResult {860 collection.check_is_owner_or_admin(sender)?;861862 let was_admin = <IsAdmin<T>>::get((collection.id, user));863 if was_admin == admin {864 return Ok(());865 }866 let amount = <AdminAmount<T>>::get(collection.id);867868 if admin {869 let amount = amount870 .checked_add(1)871 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;872 ensure!(873 amount <= Self::collection_admins_limit(),874 <Error<T>>::CollectionAdminCountExceeded,875 );876877 878879 <AdminAmount<T>>::insert(collection.id, amount);880 <IsAdmin<T>>::insert((collection.id, user), true);881 } else {882 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));883 <IsAdmin<T>>::remove((collection.id, user));884 }885886 Ok(())887 }888889 pub fn clamp_limits(890 mode: CollectionMode,891 old_limit: &CollectionLimits,892 mut new_limit: CollectionLimits,893 ) -> Result<CollectionLimits, DispatchError> {894 macro_rules! limit_default {895 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{896 $(897 if let Some($new) = $new.$field {898 let $old = $old.$field($($arg)?);899 let _ = $new;900 let _ = $old;901 $check902 } else {903 $new.$field = $old.$field904 }905 )*906 }};907 }908909 limit_default!(old_limit, new_limit,910 account_token_ownership_limit => ensure!(911 new_limit <= MAX_TOKEN_OWNERSHIP,912 <Error<T>>::CollectionLimitBoundsExceeded,913 ),914 sponsor_transfer_timeout(match mode {915 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,916 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,917 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,918 }) => ensure!(919 new_limit <= MAX_SPONSOR_TIMEOUT,920 <Error<T>>::CollectionLimitBoundsExceeded,921 ),922 sponsored_data_size => ensure!(923 new_limit <= CUSTOM_DATA_LIMIT,924 <Error<T>>::CollectionLimitBoundsExceeded,925 ),926 token_limit => ensure!(927 old_limit >= new_limit && new_limit > 0,928 <Error<T>>::CollectionTokenLimitExceeded929 ),930 owner_can_transfer => ensure!(931 old_limit || !new_limit,932 <Error<T>>::OwnerPermissionsCantBeReverted,933 ),934 owner_can_destroy => ensure!(935 old_limit || !new_limit,936 <Error<T>>::OwnerPermissionsCantBeReverted,937 ),938 sponsored_data_rate_limit => {},939 transfers_enabled => {},940 );941 Ok(new_limit)942 }943}944945#[macro_export]946macro_rules! unsupported {947 () => {948 Err(<Error<T>>::UnsupportedOperation.into())949 };950}951952953pub trait CommonWeightInfo<CrossAccountId> {954 fn create_item() -> Weight;955 fn create_multiple_items(amount: u32) -> Weight;956 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;957 fn burn_item() -> Weight;958 fn set_collection_properties(amount: u32) -> Weight;959 fn set_token_properties(amount: u32) -> Weight;960 fn delete_token_properties(amount: u32) -> Weight;961 fn set_property_permissions(amount: u32) -> Weight;962 fn transfer() -> Weight;963 fn approve() -> Weight;964 fn transfer_from() -> Weight;965 fn burn_from() -> Weight;966 fn set_variable_metadata(bytes: u32) -> Weight;967}968969pub trait CommonCollectionOperations<T: Config> {970 fn create_item(971 &self,972 sender: T::CrossAccountId,973 to: T::CrossAccountId,974 data: CreateItemData,975 nesting_budget: &dyn Budget,976 ) -> DispatchResultWithPostInfo;977 fn create_multiple_items(978 &self,979 sender: T::CrossAccountId,980 to: T::CrossAccountId,981 data: Vec<CreateItemData>,982 nesting_budget: &dyn Budget,983 ) -> DispatchResultWithPostInfo;984 fn create_multiple_items_ex(985 &self,986 sender: T::CrossAccountId,987 data: CreateItemExData<T::CrossAccountId>,988 nesting_budget: &dyn Budget,989 ) -> DispatchResultWithPostInfo;990 fn burn_item(991 &self,992 sender: T::CrossAccountId,993 token: TokenId,994 amount: u128,995 ) -> DispatchResultWithPostInfo;996 fn set_collection_properties(997 &self,998 sender: T::CrossAccountId,999 properties: Vec<Property>,1000 ) -> DispatchResultWithPostInfo;1001 fn set_token_properties(1002 &self,1003 sender: T::CrossAccountId,1004 token_id: TokenId,1005 property: Vec<Property>,1006 ) -> DispatchResultWithPostInfo;1007 fn delete_token_properties(1008 &self,1009 sender: T::CrossAccountId,1010 token_id: TokenId,1011 property_keys: Vec<PropertyKey>,1012 ) -> DispatchResultWithPostInfo;1013 fn set_property_permissions(1014 &self,1015 sender: &T::CrossAccountId,1016 property_permissions: Vec<PropertyKeyPermission>,1017 ) -> DispatchResultWithPostInfo;1018 fn transfer(1019 &self,1020 sender: T::CrossAccountId,1021 to: T::CrossAccountId,1022 token: TokenId,1023 amount: u128,1024 nesting_budget: &dyn Budget,1025 ) -> DispatchResultWithPostInfo;1026 fn approve(1027 &self,1028 sender: T::CrossAccountId,1029 spender: T::CrossAccountId,1030 token: TokenId,1031 amount: u128,1032 ) -> DispatchResultWithPostInfo;1033 fn transfer_from(1034 &self,1035 sender: T::CrossAccountId,1036 from: T::CrossAccountId,1037 to: T::CrossAccountId,1038 token: TokenId,1039 amount: u128,1040 nesting_budget: &dyn Budget,1041 ) -> DispatchResultWithPostInfo;1042 fn burn_from(1043 &self,1044 sender: T::CrossAccountId,1045 from: T::CrossAccountId,1046 token: TokenId,1047 amount: u128,1048 nesting_budget: &dyn Budget,1049 ) -> DispatchResultWithPostInfo;10501051 fn set_variable_metadata(1052 &self,1053 sender: T::CrossAccountId,1054 token: TokenId,1055 data: BoundedVec<u8, CustomDataLimit>,1056 ) -> DispatchResultWithPostInfo;10571058 fn check_nesting(1059 &self,1060 sender: T::CrossAccountId,1061 from: (CollectionId, TokenId),1062 under: TokenId,1063 budget: &dyn Budget,1064 ) -> DispatchResult;10651066 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1067 fn collection_tokens(&self) -> Vec<TokenId>;1068 fn token_exists(&self, token: TokenId) -> bool;1069 fn last_token_id(&self) -> TokenId;10701071 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1072 fn const_metadata(&self, token: TokenId) -> Vec<u8>;1073 fn variable_metadata(&self, token: TokenId) -> Vec<u8>;10741075 1076 fn total_supply(&self) -> u32;1077 1078 fn account_balance(&self, account: T::CrossAccountId) -> u32;1079 1080 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1081 fn allowance(1082 &self,1083 sender: T::CrossAccountId,1084 spender: T::CrossAccountId,1085 token: TokenId,1086 ) -> u128;1087}108810891090pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1091 let post_info = PostDispatchInfo {1092 actual_weight: Some(weight),1093 pays_fee: Pays::Yes,1094 };1095 match res {1096 Ok(()) => Ok(post_info),1097 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1098 }1099}