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 collection = collection.unwrap();432 let limits = collection.limits;433 let effective_limits = CollectionLimits {434 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),435 sponsored_data_size: Some(limits.sponsored_data_size()),436 sponsored_data_rate_limit: Some(437 limits438 .sponsored_data_rate_limit439 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),440 ),441 token_limit: Some(limits.token_limit()),442 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(443 match collection.mode {444 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,445 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,446 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,447 },448 )),449 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),450 owner_can_transfer: Some(limits.owner_can_transfer()),451 owner_can_destroy: Some(limits.owner_can_destroy()),452 transfers_enabled: Some(limits.transfers_enabled()),453 };454455 Some(effective_limits)456 }457}458459impl<T: Config> Pallet<T> {460 pub fn init_collection(461 owner: T::AccountId,462 data: CreateCollectionData<T::AccountId>,463 ) -> Result<CollectionId, DispatchError> {464 {465 ensure!(466 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,467 Error::<T>::CollectionTokenPrefixLimitExceeded468 );469 }470471 let created_count = <CreatedCollectionCount<T>>::get()472 .0473 .checked_add(1)474 .ok_or(ArithmeticError::Overflow)?;475 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;476 let id = CollectionId(created_count);477478 479 ensure!(480 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,481 <Error<T>>::TotalCollectionsLimitExceeded482 );483484 485486 let collection = Collection {487 owner: owner.clone(),488 name: data.name,489 mode: data.mode.clone(),490 mint_mode: false,491 access: data.access.unwrap_or_default(),492 description: data.description,493 token_prefix: data.token_prefix,494 offchain_schema: data.offchain_schema,495 schema_version: data.schema_version.unwrap_or_default(),496 sponsorship: data497 .pending_sponsor498 .map(SponsorshipState::Unconfirmed)499 .unwrap_or_default(),500 variable_on_chain_schema: data.variable_on_chain_schema,501 const_on_chain_schema: data.const_on_chain_schema,502 limits: data503 .limits504 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))505 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,506 meta_update_permission: data.meta_update_permission.unwrap_or_default(),507 };508509 510 {511 let mut imbalance =512 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();513 imbalance.subsume(514 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(515 &T::TreasuryAccountId::get(),516 T::CollectionCreationPrice::get(),517 ),518 );519 <T as Config>::Currency::settle(520 &owner,521 imbalance,522 WithdrawReasons::TRANSFER,523 ExistenceRequirement::KeepAlive,524 )525 .map_err(|_| Error::<T>::NotSufficientFounds)?;526 }527528 <CreatedCollectionCount<T>>::put(created_count);529 <Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));530 <CollectionById<T>>::insert(id, collection);531 Ok(id)532 }533534 pub fn destroy_collection(535 collection: CollectionHandle<T>,536 sender: &T::CrossAccountId,537 ) -> DispatchResult {538 ensure!(539 collection.limits.owner_can_destroy(),540 <Error<T>>::NoPermission,541 );542 collection.check_is_owner(sender)?;543544 let destroyed_collections = <DestroyedCollectionCount<T>>::get()545 .0546 .checked_add(1)547 .ok_or(ArithmeticError::Overflow)?;548549 550551 <DestroyedCollectionCount<T>>::put(destroyed_collections);552 <CollectionById<T>>::remove(collection.id);553 <AdminAmount<T>>::remove(collection.id);554 <IsAdmin<T>>::remove_prefix((collection.id,), None);555 <Allowlist<T>>::remove_prefix((collection.id,), None);556557 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));558 Ok(())559 }560561 pub fn toggle_allowlist(562 collection: &CollectionHandle<T>,563 sender: &T::CrossAccountId,564 user: &T::CrossAccountId,565 allowed: bool,566 ) -> DispatchResult {567 collection.check_is_owner_or_admin(sender)?;568569 570571 if allowed {572 <Allowlist<T>>::insert((collection.id, user), true);573 } else {574 <Allowlist<T>>::remove((collection.id, user));575 }576577 Ok(())578 }579580 pub fn toggle_admin(581 collection: &CollectionHandle<T>,582 sender: &T::CrossAccountId,583 user: &T::CrossAccountId,584 admin: bool,585 ) -> DispatchResult {586 collection.check_is_owner_or_admin(sender)?;587588 let was_admin = <IsAdmin<T>>::get((collection.id, user));589 if was_admin == admin {590 return Ok(());591 }592 let amount = <AdminAmount<T>>::get(collection.id);593594 if admin {595 let amount = amount596 .checked_add(1)597 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;598 ensure!(599 amount <= Self::collection_admins_limit(),600 <Error<T>>::CollectionAdminCountExceeded,601 );602603 604605 <AdminAmount<T>>::insert(collection.id, amount);606 <IsAdmin<T>>::insert((collection.id, user), true);607 } else {608 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));609 <IsAdmin<T>>::remove((collection.id, user));610 }611612 Ok(())613 }614615 pub fn clamp_limits(616 mode: CollectionMode,617 old_limit: &CollectionLimits,618 mut new_limit: CollectionLimits,619 ) -> Result<CollectionLimits, DispatchError> {620 macro_rules! limit_default {621 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{622 $(623 if let Some($new) = $new.$field {624 let $old = $old.$field($($arg)?);625 let _ = $new;626 let _ = $old;627 $check628 } else {629 $new.$field = $old.$field630 }631 )*632 }};633 }634635 limit_default!(old_limit, new_limit,636 account_token_ownership_limit => ensure!(637 new_limit <= MAX_TOKEN_OWNERSHIP,638 <Error<T>>::CollectionLimitBoundsExceeded,639 ),640 sponsor_transfer_timeout(match mode {641 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,642 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,643 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,644 }) => ensure!(645 new_limit <= MAX_SPONSOR_TIMEOUT,646 <Error<T>>::CollectionLimitBoundsExceeded,647 ),648 sponsored_data_size => ensure!(649 new_limit <= CUSTOM_DATA_LIMIT,650 <Error<T>>::CollectionLimitBoundsExceeded,651 ),652 token_limit => ensure!(653 old_limit >= new_limit && new_limit > 0,654 <Error<T>>::CollectionTokenLimitExceeded655 ),656 owner_can_transfer => ensure!(657 old_limit || !new_limit,658 <Error<T>>::OwnerPermissionsCantBeReverted,659 ),660 owner_can_destroy => ensure!(661 old_limit || !new_limit,662 <Error<T>>::OwnerPermissionsCantBeReverted,663 ),664 sponsored_data_rate_limit => {},665 transfers_enabled => {},666 );667 Ok(new_limit)668 }669}670671#[macro_export]672macro_rules! unsupported {673 () => {674 Err(<Error<T>>::UnsupportedOperation.into())675 };676}677678679pub trait CommonWeightInfo<CrossAccountId> {680 fn create_item() -> Weight;681 fn create_multiple_items(amount: u32) -> Weight;682 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;683 fn burn_item() -> Weight;684 fn transfer() -> Weight;685 fn approve() -> Weight;686 fn transfer_from() -> Weight;687 fn burn_from() -> Weight;688 fn set_variable_metadata(bytes: u32) -> Weight;689}690691pub trait CommonCollectionOperations<T: Config> {692 fn create_item(693 &self,694 sender: T::CrossAccountId,695 to: T::CrossAccountId,696 data: CreateItemData,697 ) -> DispatchResultWithPostInfo;698 fn create_multiple_items(699 &self,700 sender: T::CrossAccountId,701 to: T::CrossAccountId,702 data: Vec<CreateItemData>,703 ) -> DispatchResultWithPostInfo;704 fn create_multiple_items_ex(705 &self,706 sender: T::CrossAccountId,707 data: CreateItemExData<T::CrossAccountId>,708 ) -> DispatchResultWithPostInfo;709 fn burn_item(710 &self,711 sender: T::CrossAccountId,712 token: TokenId,713 amount: u128,714 ) -> DispatchResultWithPostInfo;715716 fn transfer(717 &self,718 sender: T::CrossAccountId,719 to: T::CrossAccountId,720 token: TokenId,721 amount: u128,722 ) -> DispatchResultWithPostInfo;723 fn approve(724 &self,725 sender: T::CrossAccountId,726 spender: T::CrossAccountId,727 token: TokenId,728 amount: u128,729 ) -> DispatchResultWithPostInfo;730 fn transfer_from(731 &self,732 sender: T::CrossAccountId,733 from: T::CrossAccountId,734 to: T::CrossAccountId,735 token: TokenId,736 amount: u128,737 ) -> DispatchResultWithPostInfo;738 fn burn_from(739 &self,740 sender: T::CrossAccountId,741 from: T::CrossAccountId,742 token: TokenId,743 amount: u128,744 ) -> DispatchResultWithPostInfo;745746 fn set_variable_metadata(747 &self,748 sender: T::CrossAccountId,749 token: TokenId,750 data: BoundedVec<u8, CustomDataLimit>,751 ) -> DispatchResultWithPostInfo;752753 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;754 fn token_exists(&self, token: TokenId) -> bool;755 fn last_token_id(&self) -> TokenId;756757 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;758 fn const_metadata(&self, token: TokenId) -> Vec<u8>;759 fn variable_metadata(&self, token: TokenId) -> Vec<u8>;760761 762 fn collection_tokens(&self) -> u32;763 764 fn account_balance(&self, account: T::CrossAccountId) -> u32;765 766 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;767 fn allowance(768 &self,769 sender: T::CrossAccountId,770 spender: T::CrossAccountId,771 token: TokenId,772 ) -> u128;773}774775776pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {777 let post_info = PostDispatchInfo {778 actual_weight: Some(weight),779 pays_fee: Pays::Yes,780 };781 match res {782 Ok(()) => Ok(post_info),783 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),784 }785}