1234567891011121314151617#![cfg_attr(not(feature = "std"), no_std)]1819use core::ops::{Deref, DerefMut};20use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};21use sp_std::vec::Vec;22use pallet_evm::account::CrossAccountId;23use frame_support::{24 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},25 ensure, 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, MAX_TOKEN_PREFIX_LENGTH,33 COLLECTION_ADMINS_LIMIT, MetaUpdatePermission, TokenId, CollectionStats, MAX_TOKEN_OWNERSHIP,34 CollectionMode, NFT_SPONSOR_TRANSFER_TIMEOUT, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,35 REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, MAX_SPONSOR_TIMEOUT, CUSTOM_DATA_LIMIT, CollectionLimits,36 CustomDataLimit, CreateCollectionData, SponsorshipState, CreateItemExData, SponsoringRateLimit, budget::Budget, COLLECTION_FIELD_LIMIT, CollectionField, PhantomType,37};38pub use pallet::*;39use sp_core::H160;40use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};41#[cfg(feature = "runtime-benchmarks")]42pub mod benchmarking;43pub mod dispatch;44pub mod erc;45pub mod eth;4647#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]48pub struct CollectionHandle<T: Config> {49 pub id: CollectionId,50 collection: Collection<T::AccountId>,51 pub recorder: SubstrateRecorder<T>,52}53impl<T: Config> WithRecorder<T> for CollectionHandle<T> {54 fn recorder(&self) -> &SubstrateRecorder<T> {55 &self.recorder56 }57 fn into_recorder(self) -> SubstrateRecorder<T> {58 self.recorder59 }60}61impl<T: Config> CollectionHandle<T> {62 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {63 <CollectionById<T>>::get(id).map(|collection| Self {64 id,65 collection,66 recorder: SubstrateRecorder::new(eth::collection_id_to_address(id), gas_limit),67 })68 }69 pub fn new(id: CollectionId) -> Option<Self> {70 Self::new_with_gas_limit(id, u64::MAX)71 }72 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {73 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)74 }75 pub fn log_mirrored(&self, log: impl evm_coder::ToLog) {76 self.recorder.log_mirrored(log)77 }78 pub fn log_direct(&self, log: impl evm_coder::ToLog) {79 self.recorder.log_direct(log)80 }81 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {82 self.recorder83 .consume_gas(T::GasWeightMapping::weight_to_gas(84 <T as frame_system::Config>::DbWeight::get()85 .read86 .saturating_mul(reads),87 ))88 }89 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {90 self.recorder91 .consume_gas(T::GasWeightMapping::weight_to_gas(92 <T as frame_system::Config>::DbWeight::get()93 .write94 .saturating_mul(writes),95 ))96 }97 pub fn submit_logs(self) {98 self.recorder.submit_logs()99 }100 pub fn save(self) -> DispatchResult {101 self.recorder.submit_logs();102 <CollectionById<T>>::insert(self.id, self.collection);103 Ok(())104 }105}106impl<T: Config> Deref for CollectionHandle<T> {107 type Target = Collection<T::AccountId>;108109 fn deref(&self) -> &Self::Target {110 &self.collection111 }112}113114impl<T: Config> DerefMut for CollectionHandle<T> {115 fn deref_mut(&mut self) -> &mut Self::Target {116 &mut self.collection117 }118}119120impl<T: Config> CollectionHandle<T> {121 pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {122 ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);123 Ok(())124 }125 pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {126 *subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))127 }128 pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {129 ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);130 Ok(())131 }132 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {133 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)134 }135 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {136 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)137 }138 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {139 ensure!(140 <Allowlist<T>>::get((self.id, user)),141 <Error<T>>::AddressNotInAllowlist142 );143 Ok(())144 }145146 pub fn check_can_update_meta(147 &self,148 subject: &T::CrossAccountId,149 item_owner: &T::CrossAccountId,150 ) -> DispatchResult {151 match self.meta_update_permission {152 MetaUpdatePermission::ItemOwner => {153 ensure!(subject == item_owner, <Error<T>>::NoPermission);154 Ok(())155 }156 MetaUpdatePermission::Admin => self.check_is_owner_or_admin(subject),157 MetaUpdatePermission::None => fail!(<Error<T>>::NoPermission),158 }159 }160}161162#[frame_support::pallet]163pub mod pallet {164 use super::*;165 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key};166 use pallet_evm::account;167 use dispatch::CollectionDispatch;168 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};169 use frame_system::pallet_prelude::*;170 use frame_support::traits::Currency;171 use up_data_structs::{TokenId, mapping::TokenAddressMapping};172 use scale_info::TypeInfo;173 use up_evm_mapping::CrossAccountId;174175 #[pallet::config]176 pub trait Config:177 frame_system::Config + pallet_evm_coder_substrate::Config + TypeInfo + account::Config178 {179 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;180181 type Currency: Currency<Self::AccountId>;182183 #[pallet::constant]184 type CollectionCreationPrice: Get<185 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,186 >;187 type CollectionDispatch: CollectionDispatch<Self>;188189 type TreasuryAccountId: Get<Self::AccountId>;190191 type EvmTokenAddressMapping: TokenAddressMapping<H160>;192 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;193 }194195 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);196197 #[pallet::pallet]198 #[pallet::storage_version(STORAGE_VERSION)]199 #[pallet::generate_store(pub(super) trait Store)]200 pub struct Pallet<T>(_);201202 #[pallet::extra_constants]203 impl<T: Config> Pallet<T> {204 pub fn collection_admins_limit() -> u32 {205 COLLECTION_ADMINS_LIMIT206 }207 }208209 #[pallet::event]210 #[pallet::generate_deposit(pub fn deposit_event)]211 pub enum Event<T: Config> {212 213 214 215 216 217 218 219 220 221 CollectionCreated(CollectionId, u8, T::AccountId),222223 224 225 226 227 228 CollectionDestroyed(CollectionId),229230 231 232 233 234 235 236 237 238 239 240 241 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),242243 244 245 246 247 248 249 250 251 252 253 254 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),255256 257 258 259 260 261 262 263 264 265 266 267 Transfer(268 CollectionId,269 TokenId,270 T::CrossAccountId,271 T::CrossAccountId,272 u128,273 ),274275 276 277 278 279 280 281 282 283 284 Approved(285 CollectionId,286 TokenId,287 T::CrossAccountId,288 T::CrossAccountId,289 u128,290 ),291 }292293 #[pallet::error]294 pub enum Error<T> {295 296 CollectionNotFound,297 298 MustBeTokenOwner,299 300 NoPermission,301 302 PublicMintingNotAllowed,303 304 AddressNotInAllowlist,305306 307 CollectionNameLimitExceeded,308 309 CollectionDescriptionLimitExceeded,310 311 CollectionTokenPrefixLimitExceeded,312 313 TotalCollectionsLimitExceeded,314 315 TokenVariableDataLimitExceeded,316 317 CollectionAdminCountExceeded,318 319 CollectionLimitBoundsExceeded,320 321 OwnerPermissionsCantBeReverted,322323 324 TransferNotAllowed,325 326 AccountTokenLimitExceeded,327 328 CollectionTokenLimitExceeded,329 330 MetadataFlagFrozen,331332 333 TokenNotFound,334 335 TokenValueTooLow,336 337 ApprovedValueTooLow,338 339 CantApproveMoreThanOwned,340341 342 AddressIsZero,343 344 UnsupportedOperation,345346 347 NotSufficientFounds,348349 350 NestingIsDisabled,351 352 OnlyOwnerAllowedToNest,353 354 SourceCollectionIsNotAllowedToNest,355356 357 CollectionFieldSizeExceeded,358 }359360 #[pallet::storage]361 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;362 #[pallet::storage]363 pub type DestroyedCollectionCount<T> =364 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;365366 367 #[pallet::storage]368 pub type CollectionById<T> = StorageMap<369 Hasher = Blake2_128Concat,370 Key = CollectionId,371 Value = Collection<<T as frame_system::Config>::AccountId>,372 QueryKind = OptionQuery,373 >;374375 376 #[pallet::storage]377 pub type CollectionData<T> = StorageNMap<378 Key = (379 Key<Twox64Concat, CollectionId>,380 Key<Twox64Concat, CollectionField>,381 ),382 Value = BoundedVec<u8, ConstU32<COLLECTION_FIELD_LIMIT>>,383 QueryKind = ValueQuery,384 >;385386 #[pallet::storage]387 pub type AdminAmount<T> = StorageMap<388 Hasher = Blake2_128Concat,389 Key = CollectionId,390 Value = u32,391 QueryKind = ValueQuery,392 >;393394 395 #[pallet::storage]396 pub type IsAdmin<T: Config> = StorageNMap<397 Key = (398 Key<Blake2_128Concat, CollectionId>,399 Key<Blake2_128Concat, T::CrossAccountId>,400 ),401 Value = bool,402 QueryKind = ValueQuery,403 >;404405 406 #[pallet::storage]407 pub type Allowlist<T: Config> = StorageNMap<408 Key = (409 Key<Blake2_128Concat, CollectionId>,410 Key<Blake2_128Concat, T::CrossAccountId>,411 ),412 Value = bool,413 QueryKind = ValueQuery,414 >;415416 417 #[pallet::storage]418 pub type DummyStorageValue<T: Config> =419 StorageValue<Value = (CollectionStats, CollectionId, TokenId, PhantomType<RpcCollection<T::AccountId>>), QueryKind = OptionQuery>;420421 #[pallet::hooks]422 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {423 fn on_runtime_upgrade() -> Weight {424 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {425 use up_data_structs::{CollectionVersion1, CollectionVersion2};426 <CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {427 Self::set_field_raw(428 id,429 CollectionField::OffchainSchema,430 v.offchain_schema.clone().into_inner(),431 )432 .expect("data has lower bounds than field");433 Self::set_field_raw(434 id,435 CollectionField::VariableOnChainSchema,436 v.variable_on_chain_schema.clone().into_inner(),437 )438 .expect("data has lower bounds than field");439 Self::set_field_raw(440 id,441 CollectionField::ConstOnChainSchema,442 v.const_on_chain_schema.clone().into_inner(),443 )444 .expect("data has lower bounds than field");445446 Some(CollectionVersion2::from(v))447 });448 }449450 0451 }452 }453}454455impl<T: Config> Pallet<T> {456 457 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {458 ensure!(459 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,460 <Error<T>>::AddressIsZero461 );462 Ok(())463 }464 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {465 <IsAdmin<T>>::iter_prefix((collection,))466 .map(|(a, _)| a)467 .collect()468 }469 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {470 <Allowlist<T>>::iter_prefix((collection,))471 .map(|(a, _)| a)472 .collect()473 }474 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {475 <Allowlist<T>>::get((collection, user))476 }477 pub fn collection_stats() -> CollectionStats {478 let created = <CreatedCollectionCount<T>>::get();479 let destroyed = <DestroyedCollectionCount<T>>::get();480 CollectionStats {481 created: created.0,482 destroyed: destroyed.0,483 alive: created.0 - destroyed.0,484 }485 }486487 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {488 let collection = <CollectionById<T>>::get(collection);489 if collection.is_none() {490 return None;491 }492493 let collection = collection.unwrap();494 let limits = collection.limits;495 let effective_limits = CollectionLimits {496 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),497 sponsored_data_size: Some(limits.sponsored_data_size()),498 sponsored_data_rate_limit: Some(499 limits500 .sponsored_data_rate_limit501 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),502 ),503 token_limit: Some(limits.token_limit()),504 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(505 match collection.mode {506 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,507 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,508 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,509 },510 )),511 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),512 owner_can_transfer: Some(limits.owner_can_transfer()),513 owner_can_destroy: Some(limits.owner_can_destroy()),514 transfers_enabled: Some(limits.transfers_enabled()),515 };516517 Some(effective_limits)518 }519520 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {521 let Collection {522 name,523 description,524 owner,525 mode,526 access,527 token_prefix,528 mint_mode,529 schema_version,530 sponsorship,531 limits,532 meta_update_permission,533 } = <CollectionById<T>>::get(collection)?;534 Some(RpcCollection {535 name: name.into_inner(),536 description: description.into_inner(),537 owner,538 mode,539 access,540 token_prefix: token_prefix.into_inner(),541 mint_mode,542 schema_version,543 sponsorship,544 limits,545 meta_update_permission,546 offchain_schema: <CollectionData<T>>::get((547 collection,548 CollectionField::OffchainSchema,549 ))550 .into_inner(),551 const_on_chain_schema: <CollectionData<T>>::get((552 collection,553 CollectionField::ConstOnChainSchema,554 ))555 .into_inner(),556 variable_on_chain_schema: <CollectionData<T>>::get((557 collection,558 CollectionField::VariableOnChainSchema,559 ))560 .into_inner(),561 })562 }563}564565impl<T: Config> Pallet<T> {566 pub fn init_collection(567 owner: T::AccountId,568 data: CreateCollectionData<T::AccountId>,569 ) -> Result<CollectionId, DispatchError> {570 {571 ensure!(572 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,573 Error::<T>::CollectionTokenPrefixLimitExceeded574 );575 }576577 let created_count = <CreatedCollectionCount<T>>::get()578 .0579 .checked_add(1)580 .ok_or(ArithmeticError::Overflow)?;581 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;582 let id = CollectionId(created_count);583584 585 ensure!(586 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,587 <Error<T>>::TotalCollectionsLimitExceeded588 );589590 591592 let collection = Collection {593 owner: owner.clone(),594 name: data.name,595 mode: data.mode.clone(),596 mint_mode: false,597 access: data.access.unwrap_or_default(),598 description: data.description,599 token_prefix: data.token_prefix,600 schema_version: data.schema_version.unwrap_or_default(),601 sponsorship: data602 .pending_sponsor603 .map(SponsorshipState::Unconfirmed)604 .unwrap_or_default(),605 limits: data606 .limits607 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))608 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,609 meta_update_permission: data.meta_update_permission.unwrap_or_default(),610 };611612 613 {614 let mut imbalance =615 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();616 imbalance.subsume(617 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(618 &T::TreasuryAccountId::get(),619 T::CollectionCreationPrice::get(),620 ),621 );622 <T as Config>::Currency::settle(623 &owner,624 imbalance,625 WithdrawReasons::TRANSFER,626 ExistenceRequirement::KeepAlive,627 )628 .map_err(|_| Error::<T>::NotSufficientFounds)?;629 }630631 <CreatedCollectionCount<T>>::put(created_count);632 <Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));633 <CollectionById<T>>::insert(id, collection);634 Self::set_field_raw(635 id,636 CollectionField::OffchainSchema,637 data.offchain_schema.into_inner(),638 )639 .expect("data has lower bounds than field");640 Self::set_field_raw(641 id,642 CollectionField::VariableOnChainSchema,643 data.variable_on_chain_schema.into_inner(),644 )645 .expect("data has lower bounds than field");646 Self::set_field_raw(647 id,648 CollectionField::ConstOnChainSchema,649 data.const_on_chain_schema.into_inner(),650 )651 .expect("data has lower bounds than field");652 Ok(id)653 }654655 pub fn destroy_collection(656 collection: CollectionHandle<T>,657 sender: &T::CrossAccountId,658 ) -> DispatchResult {659 ensure!(660 collection.limits.owner_can_destroy(),661 <Error<T>>::NoPermission,662 );663 collection.check_is_owner(sender)?;664665 let destroyed_collections = <DestroyedCollectionCount<T>>::get()666 .0667 .checked_add(1)668 .ok_or(ArithmeticError::Overflow)?;669670 671672 <DestroyedCollectionCount<T>>::put(destroyed_collections);673 <CollectionById<T>>::remove(collection.id);674 <CollectionData<T>>::remove_prefix((collection.id,), None);675 <AdminAmount<T>>::remove(collection.id);676 <IsAdmin<T>>::remove_prefix((collection.id,), None);677 <Allowlist<T>>::remove_prefix((collection.id,), None);678679 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));680 Ok(())681 }682683 fn set_field_raw(684 collection_id: CollectionId,685 field: CollectionField,686 value: Vec<u8>,687 ) -> DispatchResult {688 if !value.is_empty() {689 <CollectionData<T>>::insert(690 (collection_id, field),691 BoundedVec::try_from(value).map_err(|_| <Error<T>>::CollectionFieldSizeExceeded)?,692 )693 } else {694 <CollectionData<T>>::remove((collection_id, field));695 }696 Ok(())697 }698699 pub fn set_field(700 collection: &CollectionHandle<T>,701 sender: &T::CrossAccountId,702 field: CollectionField,703 value: Vec<u8>,704 ) -> DispatchResult {705 collection.check_is_owner_or_admin(sender)?;706707 708709 Self::set_field_raw(collection.id, field, value)710 }711712 pub fn toggle_allowlist(713 collection: &CollectionHandle<T>,714 sender: &T::CrossAccountId,715 user: &T::CrossAccountId,716 allowed: bool,717 ) -> DispatchResult {718 collection.check_is_owner_or_admin(sender)?;719720 721722 if allowed {723 <Allowlist<T>>::insert((collection.id, user), true);724 } else {725 <Allowlist<T>>::remove((collection.id, user));726 }727728 Ok(())729 }730731 pub fn toggle_admin(732 collection: &CollectionHandle<T>,733 sender: &T::CrossAccountId,734 user: &T::CrossAccountId,735 admin: bool,736 ) -> DispatchResult {737 collection.check_is_owner_or_admin(sender)?;738739 let was_admin = <IsAdmin<T>>::get((collection.id, user));740 if was_admin == admin {741 return Ok(());742 }743 let amount = <AdminAmount<T>>::get(collection.id);744745 if admin {746 let amount = amount747 .checked_add(1)748 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;749 ensure!(750 amount <= Self::collection_admins_limit(),751 <Error<T>>::CollectionAdminCountExceeded,752 );753754 755756 <AdminAmount<T>>::insert(collection.id, amount);757 <IsAdmin<T>>::insert((collection.id, user), true);758 } else {759 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));760 <IsAdmin<T>>::remove((collection.id, user));761 }762763 Ok(())764 }765766 pub fn clamp_limits(767 mode: CollectionMode,768 old_limit: &CollectionLimits,769 mut new_limit: CollectionLimits,770 ) -> Result<CollectionLimits, DispatchError> {771 macro_rules! limit_default {772 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{773 $(774 if let Some($new) = $new.$field {775 let $old = $old.$field($($arg)?);776 let _ = $new;777 let _ = $old;778 $check779 } else {780 $new.$field = $old.$field781 }782 )*783 }};784 }785786 limit_default!(old_limit, new_limit,787 account_token_ownership_limit => ensure!(788 new_limit <= MAX_TOKEN_OWNERSHIP,789 <Error<T>>::CollectionLimitBoundsExceeded,790 ),791 sponsor_transfer_timeout(match mode {792 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,793 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,794 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,795 }) => ensure!(796 new_limit <= MAX_SPONSOR_TIMEOUT,797 <Error<T>>::CollectionLimitBoundsExceeded,798 ),799 sponsored_data_size => ensure!(800 new_limit <= CUSTOM_DATA_LIMIT,801 <Error<T>>::CollectionLimitBoundsExceeded,802 ),803 token_limit => ensure!(804 old_limit >= new_limit && new_limit > 0,805 <Error<T>>::CollectionTokenLimitExceeded806 ),807 owner_can_transfer => ensure!(808 old_limit || !new_limit,809 <Error<T>>::OwnerPermissionsCantBeReverted,810 ),811 owner_can_destroy => ensure!(812 old_limit || !new_limit,813 <Error<T>>::OwnerPermissionsCantBeReverted,814 ),815 sponsored_data_rate_limit => {},816 transfers_enabled => {},817 );818 Ok(new_limit)819 }820}821822#[macro_export]823macro_rules! unsupported {824 () => {825 Err(<Error<T>>::UnsupportedOperation.into())826 };827}828829830pub trait CommonWeightInfo<CrossAccountId> {831 fn create_item() -> Weight;832 fn create_multiple_items(amount: u32) -> Weight;833 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;834 fn burn_item() -> Weight;835 fn transfer() -> Weight;836 fn approve() -> Weight;837 fn transfer_from() -> Weight;838 fn burn_from() -> Weight;839 fn set_variable_metadata(bytes: u32) -> Weight;840}841842pub trait CommonCollectionOperations<T: Config> {843 fn create_item(844 &self,845 sender: T::CrossAccountId,846 to: T::CrossAccountId,847 data: CreateItemData,848 nesting_budget: &dyn Budget,849 ) -> DispatchResultWithPostInfo;850 fn create_multiple_items(851 &self,852 sender: T::CrossAccountId,853 to: T::CrossAccountId,854 data: Vec<CreateItemData>,855 nesting_budget: &dyn Budget,856 ) -> DispatchResultWithPostInfo;857 fn create_multiple_items_ex(858 &self,859 sender: T::CrossAccountId,860 data: CreateItemExData<T::CrossAccountId>,861 nesting_budget: &dyn Budget,862 ) -> DispatchResultWithPostInfo;863 fn burn_item(864 &self,865 sender: T::CrossAccountId,866 token: TokenId,867 amount: u128,868 ) -> DispatchResultWithPostInfo;869870 fn transfer(871 &self,872 sender: T::CrossAccountId,873 to: T::CrossAccountId,874 token: TokenId,875 amount: u128,876 nesting_budget: &dyn Budget,877 ) -> DispatchResultWithPostInfo;878 fn approve(879 &self,880 sender: T::CrossAccountId,881 spender: T::CrossAccountId,882 token: TokenId,883 amount: u128,884 ) -> DispatchResultWithPostInfo;885 fn transfer_from(886 &self,887 sender: T::CrossAccountId,888 from: T::CrossAccountId,889 to: T::CrossAccountId,890 token: TokenId,891 amount: u128,892 nesting_budget: &dyn Budget,893 ) -> DispatchResultWithPostInfo;894 fn burn_from(895 &self,896 sender: T::CrossAccountId,897 from: T::CrossAccountId,898 token: TokenId,899 amount: u128,900 nesting_budget: &dyn Budget,901 ) -> DispatchResultWithPostInfo;902903 fn set_variable_metadata(904 &self,905 sender: T::CrossAccountId,906 token: TokenId,907 data: BoundedVec<u8, CustomDataLimit>,908 ) -> DispatchResultWithPostInfo;909910 fn check_nesting(911 &self,912 sender: T::CrossAccountId,913 from: CollectionId,914 under: TokenId,915 budget: &dyn Budget,916 ) -> DispatchResult;917918 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;919 fn token_exists(&self, token: TokenId) -> bool;920 fn last_token_id(&self) -> TokenId;921922 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;923 fn const_metadata(&self, token: TokenId) -> Vec<u8>;924 fn variable_metadata(&self, token: TokenId) -> Vec<u8>;925926 927 fn collection_tokens(&self) -> u32;928 929 fn account_balance(&self, account: T::CrossAccountId) -> u32;930 931 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;932 fn allowance(933 &self,934 sender: T::CrossAccountId,935 spender: T::CrossAccountId,936 token: TokenId,937 ) -> u128;938}939940941pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {942 let post_info = PostDispatchInfo {943 actual_weight: Some(weight),944 pays_fee: Pays::Yes,945 };946 match res {947 Ok(()) => Ok(post_info),948 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),949 }950}