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,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 }424}425426impl<T: Config> Pallet<T> {427 pub fn init_collection(428 owner: T::AccountId,429 data: CreateCollectionData<T::AccountId>,430 ) -> Result<CollectionId, DispatchError> {431 {432 ensure!(433 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,434 Error::<T>::CollectionTokenPrefixLimitExceeded435 );436 }437438 let created_count = <CreatedCollectionCount<T>>::get()439 .0440 .checked_add(1)441 .ok_or(ArithmeticError::Overflow)?;442 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;443 let id = CollectionId(created_count);444445 446 ensure!(447 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,448 <Error<T>>::TotalCollectionsLimitExceeded449 );450451 452453 let collection = Collection {454 owner: owner.clone(),455 name: data.name,456 mode: data.mode.clone(),457 mint_mode: false,458 access: data.access.unwrap_or_default(),459 description: data.description,460 token_prefix: data.token_prefix,461 offchain_schema: data.offchain_schema,462 schema_version: data.schema_version.unwrap_or_default(),463 sponsorship: data464 .pending_sponsor465 .map(SponsorshipState::Unconfirmed)466 .unwrap_or_default(),467 variable_on_chain_schema: data.variable_on_chain_schema,468 const_on_chain_schema: data.const_on_chain_schema,469 limits: data470 .limits471 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))472 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,473 meta_update_permission: data.meta_update_permission.unwrap_or_default(),474 };475476 477 {478 let mut imbalance =479 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();480 imbalance.subsume(481 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(482 &T::TreasuryAccountId::get(),483 T::CollectionCreationPrice::get(),484 ),485 );486 <T as Config>::Currency::settle(487 &owner,488 imbalance,489 WithdrawReasons::TRANSFER,490 ExistenceRequirement::KeepAlive,491 )492 .map_err(|_| Error::<T>::NotSufficientFounds)?;493 }494495 <CreatedCollectionCount<T>>::put(created_count);496 <Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));497 <CollectionById<T>>::insert(id, collection);498 Ok(id)499 }500501 pub fn destroy_collection(502 collection: CollectionHandle<T>,503 sender: &T::CrossAccountId,504 ) -> DispatchResult {505 ensure!(506 collection.limits.owner_can_destroy(),507 <Error<T>>::NoPermission,508 );509 collection.check_is_owner(sender)?;510511 let destroyed_collections = <DestroyedCollectionCount<T>>::get()512 .0513 .checked_add(1)514 .ok_or(ArithmeticError::Overflow)?;515516 517518 <DestroyedCollectionCount<T>>::put(destroyed_collections);519 <CollectionById<T>>::remove(collection.id);520 <AdminAmount<T>>::remove(collection.id);521 <IsAdmin<T>>::remove_prefix((collection.id,), None);522 <Allowlist<T>>::remove_prefix((collection.id,), None);523524 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));525 Ok(())526 }527528 pub fn toggle_allowlist(529 collection: &CollectionHandle<T>,530 sender: &T::CrossAccountId,531 user: &T::CrossAccountId,532 allowed: bool,533 ) -> DispatchResult {534 collection.check_is_owner_or_admin(sender)?;535536 537538 if allowed {539 <Allowlist<T>>::insert((collection.id, user), true);540 } else {541 <Allowlist<T>>::remove((collection.id, user));542 }543544 Ok(())545 }546547 pub fn toggle_admin(548 collection: &CollectionHandle<T>,549 sender: &T::CrossAccountId,550 user: &T::CrossAccountId,551 admin: bool,552 ) -> DispatchResult {553 collection.check_is_owner_or_admin(sender)?;554555 let was_admin = <IsAdmin<T>>::get((collection.id, user));556 if was_admin == admin {557 return Ok(());558 }559 let amount = <AdminAmount<T>>::get(collection.id);560561 if admin {562 let amount = amount563 .checked_add(1)564 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;565 ensure!(566 amount <= Self::collection_admins_limit(),567 <Error<T>>::CollectionAdminCountExceeded,568 );569570 571572 <AdminAmount<T>>::insert(collection.id, amount);573 <IsAdmin<T>>::insert((collection.id, user), true);574 } else {575 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));576 <IsAdmin<T>>::remove((collection.id, user));577 }578579 Ok(())580 }581582 pub fn clamp_limits(583 mode: CollectionMode,584 old_limit: &CollectionLimits,585 mut new_limit: CollectionLimits,586 ) -> Result<CollectionLimits, DispatchError> {587 macro_rules! limit_default {588 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{589 $(590 if let Some($new) = $new.$field {591 let $old = $old.$field($($arg)?);592 let _ = $new;593 let _ = $old;594 $check595 } else {596 $new.$field = $old.$field597 }598 )*599 }};600 }601602 limit_default!(old_limit, new_limit,603 account_token_ownership_limit => ensure!(604 new_limit <= MAX_TOKEN_OWNERSHIP,605 <Error<T>>::CollectionLimitBoundsExceeded,606 ),607 sponsor_transfer_timeout(match mode {608 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,609 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,610 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,611 }) => ensure!(612 new_limit <= MAX_SPONSOR_TIMEOUT,613 <Error<T>>::CollectionLimitBoundsExceeded,614 ),615 sponsored_data_size => ensure!(616 new_limit <= CUSTOM_DATA_LIMIT,617 <Error<T>>::CollectionLimitBoundsExceeded,618 ),619 token_limit => ensure!(620 old_limit >= new_limit && new_limit > 0,621 <Error<T>>::CollectionTokenLimitExceeded622 ),623 owner_can_transfer => ensure!(624 old_limit || !new_limit,625 <Error<T>>::OwnerPermissionsCantBeReverted,626 ),627 owner_can_destroy => ensure!(628 old_limit || !new_limit,629 <Error<T>>::OwnerPermissionsCantBeReverted,630 ),631 sponsored_data_rate_limit => {},632 transfers_enabled => {},633 );634 Ok(new_limit)635 }636}637638#[macro_export]639macro_rules! unsupported {640 () => {641 Err(<Error<T>>::UnsupportedOperation.into())642 };643}644645646pub trait CommonWeightInfo<CrossAccountId> {647 fn create_item() -> Weight;648 fn create_multiple_items(amount: u32) -> Weight;649 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;650 fn burn_item() -> Weight;651 fn transfer() -> Weight;652 fn approve() -> Weight;653 fn transfer_from() -> Weight;654 fn burn_from() -> Weight;655 fn set_variable_metadata(bytes: u32) -> Weight;656}657658pub trait CommonCollectionOperations<T: Config> {659 fn create_item(660 &self,661 sender: T::CrossAccountId,662 to: T::CrossAccountId,663 data: CreateItemData,664 ) -> DispatchResultWithPostInfo;665 fn create_multiple_items(666 &self,667 sender: T::CrossAccountId,668 to: T::CrossAccountId,669 data: Vec<CreateItemData>,670 ) -> DispatchResultWithPostInfo;671 fn create_multiple_items_ex(672 &self,673 sender: T::CrossAccountId,674 data: CreateItemExData<T::CrossAccountId>,675 ) -> DispatchResultWithPostInfo;676 fn burn_item(677 &self,678 sender: T::CrossAccountId,679 token: TokenId,680 amount: u128,681 ) -> DispatchResultWithPostInfo;682683 fn transfer(684 &self,685 sender: T::CrossAccountId,686 to: T::CrossAccountId,687 token: TokenId,688 amount: u128,689 ) -> DispatchResultWithPostInfo;690 fn approve(691 &self,692 sender: T::CrossAccountId,693 spender: T::CrossAccountId,694 token: TokenId,695 amount: u128,696 ) -> DispatchResultWithPostInfo;697 fn transfer_from(698 &self,699 sender: T::CrossAccountId,700 from: T::CrossAccountId,701 to: T::CrossAccountId,702 token: TokenId,703 amount: u128,704 ) -> DispatchResultWithPostInfo;705 fn burn_from(706 &self,707 sender: T::CrossAccountId,708 from: T::CrossAccountId,709 token: TokenId,710 amount: u128,711 ) -> DispatchResultWithPostInfo;712713 fn set_variable_metadata(714 &self,715 sender: T::CrossAccountId,716 token: TokenId,717 data: BoundedVec<u8, CustomDataLimit>,718 ) -> DispatchResultWithPostInfo;719720 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;721 fn token_exists(&self, token: TokenId) -> bool;722 fn last_token_id(&self) -> TokenId;723724 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;725 fn const_metadata(&self, token: TokenId) -> Vec<u8>;726 fn variable_metadata(&self, token: TokenId) -> Vec<u8>;727728 729 fn collection_tokens(&self) -> u32;730 731 fn account_balance(&self, account: T::CrossAccountId) -> u32;732 733 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;734 fn allowance(735 &self,736 sender: T::CrossAccountId,737 spender: T::CrossAccountId,738 token: TokenId,739 ) -> u128;740}741742743pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {744 let post_info = PostDispatchInfo {745 actual_weight: Some(weight),746 pays_fee: Pays::Yes,747 };748 match res {749 Ok(()) => Ok(post_info),750 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),751 }752}