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 account::CrossAccountId;23use frame_support::{24 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo},25 ensure, fail,26 traits::{Imbalance, Get, Currency},27 BoundedVec,28};29use pallet_evm::GasWeightMapping;30use up_data_structs::{31 COLLECTION_NUMBER_LIMIT, Collection, CollectionId, CreateItemData, ExistenceRequirement,32 MAX_TOKEN_PREFIX_LENGTH, COLLECTION_ADMINS_LIMIT, MetaUpdatePermission, Pays, PostDispatchInfo,33 TokenId, Weight, WithdrawReasons, CollectionStats, MAX_TOKEN_OWNERSHIP, CollectionMode,34 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,37};38pub use pallet::*;39use sp_core::H160;40use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};41pub mod account;42#[cfg(feature = "runtime-benchmarks")]43pub mod benchmarking;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 account::CrossAccountId;167 use frame_support::traits::Currency;168 use up_data_structs::TokenId;169 use scale_info::TypeInfo;170171 #[pallet::config]172 pub trait Config: frame_system::Config + pallet_evm_coder_substrate::Config + TypeInfo {173 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;174175 type CrossAccountId: CrossAccountId<Self::AccountId>;176177 type EvmAddressMapping: pallet_evm::AddressMapping<Self::AccountId>;178 type EvmBackwardsAddressMapping: up_evm_mapping::EvmBackwardsAddressMapping<Self::AccountId>;179180 type Currency: Currency<Self::AccountId>;181182 #[pallet::constant]183 type CollectionCreationPrice: Get<184 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,185 >;186187 type TreasuryAccountId: Get<Self::AccountId>;188 }189190 #[pallet::pallet]191 #[pallet::generate_store(pub(super) trait Store)]192 pub struct Pallet<T>(_);193194 #[pallet::extra_constants]195 impl<T: Config> Pallet<T> {196 pub fn collection_admins_limit() -> u32 {197 COLLECTION_ADMINS_LIMIT198 }199 }200201 #[pallet::event]202 #[pallet::generate_deposit(pub fn deposit_event)]203 pub enum Event<T: Config> {204 205 206 207 208 209 210 211 212 213 CollectionCreated(CollectionId, u8, T::AccountId),214215 216 217 218 219 220 CollectionDestroyed(CollectionId),221222 223 224 225 226 227 228 229 230 231 232 233 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),234235 236 237 238 239 240 241 242 243 244 245 246 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),247248 249 250 251 252 253 254 255 256 257 258 259 Transfer(260 CollectionId,261 TokenId,262 T::CrossAccountId,263 T::CrossAccountId,264 u128,265 ),266267 268 269 270 271 272 273 274 275 276 Approved(277 CollectionId,278 TokenId,279 T::CrossAccountId,280 T::CrossAccountId,281 u128,282 ),283 }284285 #[pallet::error]286 pub enum Error<T> {287 288 CollectionNotFound,289 290 MustBeTokenOwner,291 292 NoPermission,293 294 PublicMintingNotAllowed,295 296 AddressNotInAllowlist,297298 299 CollectionNameLimitExceeded,300 301 CollectionDescriptionLimitExceeded,302 303 CollectionTokenPrefixLimitExceeded,304 305 TotalCollectionsLimitExceeded,306 307 TokenVariableDataLimitExceeded,308 309 CollectionAdminCountExceeded,310 311 CollectionLimitBoundsExceeded,312 313 OwnerPermissionsCantBeReverted,314315 316 TransferNotAllowed,317 318 AccountTokenLimitExceeded,319 320 CollectionTokenLimitExceeded,321 322 MetadataFlagFrozen,323324 325 TokenNotFound,326 327 TokenValueTooLow,328 329 ApprovedValueTooLow,330 331 CantApproveMoreThanOwned,332333 334 AddressIsZero,335 336 UnsupportedOperation,337338 339 NotSufficientFounds,340 }341342 #[pallet::storage]343 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;344 #[pallet::storage]345 pub type DestroyedCollectionCount<T> =346 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;347348 349 #[pallet::storage]350 pub type CollectionById<T> = StorageMap<351 Hasher = Blake2_128Concat,352 Key = CollectionId,353 Value = Collection<<T as frame_system::Config>::AccountId>,354 QueryKind = OptionQuery,355 >;356357 #[pallet::storage]358 pub type AdminAmount<T> = StorageMap<359 Hasher = Blake2_128Concat,360 Key = CollectionId,361 Value = u32,362 QueryKind = ValueQuery,363 >;364365 366 #[pallet::storage]367 pub type IsAdmin<T: Config> = StorageNMap<368 Key = (369 Key<Blake2_128Concat, CollectionId>,370 Key<Blake2_128Concat, T::CrossAccountId>,371 ),372 Value = bool,373 QueryKind = ValueQuery,374 >;375376 377 #[pallet::storage]378 pub type Allowlist<T: Config> = StorageNMap<379 Key = (380 Key<Blake2_128Concat, CollectionId>,381 Key<Blake2_128Concat, T::CrossAccountId>,382 ),383 Value = bool,384 QueryKind = ValueQuery,385 >;386387 388 #[pallet::storage]389 pub type DummyStorageValue<T> =390 StorageValue<Value = (CollectionStats, CollectionId, TokenId), QueryKind = OptionQuery>;391}392393impl<T: Config> Pallet<T> {394 395 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {396 ensure!(397 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,398 <Error<T>>::AddressIsZero399 );400 Ok(())401 }402 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {403 <IsAdmin<T>>::iter_prefix((collection,))404 .map(|(a, _)| a)405 .collect()406 }407 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {408 <Allowlist<T>>::iter_prefix((collection,))409 .map(|(a, _)| a)410 .collect()411 }412 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {413 <Allowlist<T>>::get((collection, user))414 }415 pub fn collection_stats() -> CollectionStats {416 let created = <CreatedCollectionCount<T>>::get();417 let destroyed = <DestroyedCollectionCount<T>>::get();418 CollectionStats {419 created: created.0,420 destroyed: destroyed.0,421 alive: created.0 - destroyed.0,422 }423 }424425 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {426 let collection = <CollectionById<T>>::get(collection);427 if collection.is_none() {428 return None;429 }430431 let limits = collection.unwrap().limits;432 let effective_limits = CollectionLimits {433 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),434 sponsored_data_size: Some(limits.sponsored_data_size()),435 sponsored_data_rate_limit: Some(436 limits437 .sponsored_data_rate_limit438 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),439 ),440 token_limit: Some(limits.token_limit()),441 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(MAX_SPONSOR_TIMEOUT)),442 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),443 owner_can_transfer: Some(limits.owner_can_transfer()),444 owner_can_destroy: Some(limits.owner_can_destroy()),445 transfers_enabled: Some(limits.transfers_enabled()),446 };447448 Some(effective_limits)449 }450}451452impl<T: Config> Pallet<T> {453 pub fn init_collection(454 owner: T::AccountId,455 data: CreateCollectionData<T::AccountId>,456 ) -> Result<CollectionId, DispatchError> {457 {458 ensure!(459 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,460 Error::<T>::CollectionTokenPrefixLimitExceeded461 );462 }463464 let created_count = <CreatedCollectionCount<T>>::get()465 .0466 .checked_add(1)467 .ok_or(ArithmeticError::Overflow)?;468 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;469 let id = CollectionId(created_count);470471 472 ensure!(473 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,474 <Error<T>>::TotalCollectionsLimitExceeded475 );476477 478479 let collection = Collection {480 owner: owner.clone(),481 name: data.name,482 mode: data.mode.clone(),483 mint_mode: false,484 access: data.access.unwrap_or_default(),485 description: data.description,486 token_prefix: data.token_prefix,487 offchain_schema: data.offchain_schema,488 schema_version: data.schema_version.unwrap_or_default(),489 sponsorship: data490 .pending_sponsor491 .map(SponsorshipState::Unconfirmed)492 .unwrap_or_default(),493 variable_on_chain_schema: data.variable_on_chain_schema,494 const_on_chain_schema: data.const_on_chain_schema,495 limits: data496 .limits497 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))498 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,499 meta_update_permission: data.meta_update_permission.unwrap_or_default(),500 };501502 503 {504 let mut imbalance =505 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();506 imbalance.subsume(507 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(508 &T::TreasuryAccountId::get(),509 T::CollectionCreationPrice::get(),510 ),511 );512 <T as Config>::Currency::settle(513 &owner,514 imbalance,515 WithdrawReasons::TRANSFER,516 ExistenceRequirement::KeepAlive,517 )518 .map_err(|_| Error::<T>::NotSufficientFounds)?;519 }520521 <CreatedCollectionCount<T>>::put(created_count);522 <Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));523 <CollectionById<T>>::insert(id, collection);524 Ok(id)525 }526527 pub fn destroy_collection(528 collection: CollectionHandle<T>,529 sender: &T::CrossAccountId,530 ) -> DispatchResult {531 ensure!(532 collection.limits.owner_can_destroy(),533 <Error<T>>::NoPermission,534 );535 collection.check_is_owner(sender)?;536537 let destroyed_collections = <DestroyedCollectionCount<T>>::get()538 .0539 .checked_add(1)540 .ok_or(ArithmeticError::Overflow)?;541542 543544 <DestroyedCollectionCount<T>>::put(destroyed_collections);545 <CollectionById<T>>::remove(collection.id);546 <AdminAmount<T>>::remove(collection.id);547 <IsAdmin<T>>::remove_prefix((collection.id,), None);548 <Allowlist<T>>::remove_prefix((collection.id,), None);549550 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));551 Ok(())552 }553554 pub fn toggle_allowlist(555 collection: &CollectionHandle<T>,556 sender: &T::CrossAccountId,557 user: &T::CrossAccountId,558 allowed: bool,559 ) -> DispatchResult {560 collection.check_is_owner_or_admin(sender)?;561562 563564 if allowed {565 <Allowlist<T>>::insert((collection.id, user), true);566 } else {567 <Allowlist<T>>::remove((collection.id, user));568 }569570 Ok(())571 }572573 pub fn toggle_admin(574 collection: &CollectionHandle<T>,575 sender: &T::CrossAccountId,576 user: &T::CrossAccountId,577 admin: bool,578 ) -> DispatchResult {579 collection.check_is_owner_or_admin(sender)?;580581 let was_admin = <IsAdmin<T>>::get((collection.id, user));582 if was_admin == admin {583 return Ok(());584 }585 let amount = <AdminAmount<T>>::get(collection.id);586587 if admin {588 let amount = amount589 .checked_add(1)590 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;591 ensure!(592 amount <= Self::collection_admins_limit(),593 <Error<T>>::CollectionAdminCountExceeded,594 );595596 597598 <AdminAmount<T>>::insert(collection.id, amount);599 <IsAdmin<T>>::insert((collection.id, user), true);600 } else {601 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));602 <IsAdmin<T>>::remove((collection.id, user));603 }604605 Ok(())606 }607608 pub fn clamp_limits(609 mode: CollectionMode,610 old_limit: &CollectionLimits,611 mut new_limit: CollectionLimits,612 ) -> Result<CollectionLimits, DispatchError> {613 macro_rules! limit_default {614 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{615 $(616 if let Some($new) = $new.$field {617 let $old = $old.$field($($arg)?);618 let _ = $new;619 let _ = $old;620 $check621 } else {622 $new.$field = $old.$field623 }624 )*625 }};626 }627628 limit_default!(old_limit, new_limit,629 account_token_ownership_limit => ensure!(630 new_limit <= MAX_TOKEN_OWNERSHIP,631 <Error<T>>::CollectionLimitBoundsExceeded,632 ),633 sponsor_transfer_timeout(match mode {634 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,635 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,636 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,637 }) => ensure!(638 new_limit <= MAX_SPONSOR_TIMEOUT,639 <Error<T>>::CollectionLimitBoundsExceeded,640 ),641 sponsored_data_size => ensure!(642 new_limit <= CUSTOM_DATA_LIMIT,643 <Error<T>>::CollectionLimitBoundsExceeded,644 ),645 token_limit => ensure!(646 old_limit >= new_limit && new_limit > 0,647 <Error<T>>::CollectionTokenLimitExceeded648 ),649 owner_can_transfer => ensure!(650 old_limit || !new_limit,651 <Error<T>>::OwnerPermissionsCantBeReverted,652 ),653 owner_can_destroy => ensure!(654 old_limit || !new_limit,655 <Error<T>>::OwnerPermissionsCantBeReverted,656 ),657 sponsored_data_rate_limit => {},658 transfers_enabled => {},659 );660 Ok(new_limit)661 }662}663664#[macro_export]665macro_rules! unsupported {666 () => {667 Err(<Error<T>>::UnsupportedOperation.into())668 };669}670671672pub trait CommonWeightInfo<CrossAccountId> {673 fn create_item() -> Weight;674 fn create_multiple_items(amount: u32) -> Weight;675 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;676 fn burn_item() -> Weight;677 fn transfer() -> Weight;678 fn approve() -> Weight;679 fn transfer_from() -> Weight;680 fn burn_from() -> Weight;681 fn set_variable_metadata(bytes: u32) -> Weight;682}683684pub trait CommonCollectionOperations<T: Config> {685 fn create_item(686 &self,687 sender: T::CrossAccountId,688 to: T::CrossAccountId,689 data: CreateItemData,690 ) -> DispatchResultWithPostInfo;691 fn create_multiple_items(692 &self,693 sender: T::CrossAccountId,694 to: T::CrossAccountId,695 data: Vec<CreateItemData>,696 ) -> DispatchResultWithPostInfo;697 fn create_multiple_items_ex(698 &self,699 sender: T::CrossAccountId,700 data: CreateItemExData<T::CrossAccountId>,701 ) -> DispatchResultWithPostInfo;702 fn burn_item(703 &self,704 sender: T::CrossAccountId,705 token: TokenId,706 amount: u128,707 ) -> DispatchResultWithPostInfo;708709 fn transfer(710 &self,711 sender: T::CrossAccountId,712 to: T::CrossAccountId,713 token: TokenId,714 amount: u128,715 ) -> DispatchResultWithPostInfo;716 fn approve(717 &self,718 sender: T::CrossAccountId,719 spender: T::CrossAccountId,720 token: TokenId,721 amount: u128,722 ) -> DispatchResultWithPostInfo;723 fn transfer_from(724 &self,725 sender: T::CrossAccountId,726 from: T::CrossAccountId,727 to: T::CrossAccountId,728 token: TokenId,729 amount: u128,730 ) -> DispatchResultWithPostInfo;731 fn burn_from(732 &self,733 sender: T::CrossAccountId,734 from: T::CrossAccountId,735 token: TokenId,736 amount: u128,737 ) -> DispatchResultWithPostInfo;738739 fn set_variable_metadata(740 &self,741 sender: T::CrossAccountId,742 token: TokenId,743 data: BoundedVec<u8, CustomDataLimit>,744 ) -> DispatchResultWithPostInfo;745746 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;747 fn token_exists(&self, token: TokenId) -> bool;748 fn last_token_id(&self) -> TokenId;749750 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;751 fn const_metadata(&self, token: TokenId) -> Vec<u8>;752 fn variable_metadata(&self, token: TokenId) -> Vec<u8>;753754 755 fn collection_tokens(&self) -> u32;756 757 fn account_balance(&self, account: T::CrossAccountId) -> u32;758 759 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;760 fn allowance(761 &self,762 sender: T::CrossAccountId,763 spender: T::CrossAccountId,764 token: TokenId,765 ) -> u128;766}767768769pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {770 let post_info = PostDispatchInfo {771 actual_weight: Some(weight),772 pays_fee: Pays::Yes,773 };774 match res {775 Ok(()) => Ok(post_info),776 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),777 }778}