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},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};41#[cfg(feature = "runtime-benchmarks")]42pub mod benchmarking;43pub mod erc;44pub mod eth;4546#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]47pub struct CollectionHandle<T: Config> {48 pub id: CollectionId,49 collection: Collection<T::AccountId>,50 pub recorder: SubstrateRecorder<T>,51}52impl<T: Config> WithRecorder<T> for CollectionHandle<T> {53 fn recorder(&self) -> &SubstrateRecorder<T> {54 &self.recorder55 }56 fn into_recorder(self) -> SubstrateRecorder<T> {57 self.recorder58 }59}60impl<T: Config> CollectionHandle<T> {61 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {62 <CollectionById<T>>::get(id).map(|collection| Self {63 id,64 collection,65 recorder: SubstrateRecorder::new(eth::collection_id_to_address(id), gas_limit),66 })67 }68 pub fn new(id: CollectionId) -> Option<Self> {69 Self::new_with_gas_limit(id, u64::MAX)70 }71 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {72 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)73 }74 pub fn log_mirrored(&self, log: impl evm_coder::ToLog) {75 self.recorder.log_mirrored(log)76 }77 pub fn log_direct(&self, log: impl evm_coder::ToLog) {78 self.recorder.log_direct(log)79 }80 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {81 self.recorder82 .consume_gas(T::GasWeightMapping::weight_to_gas(83 <T as frame_system::Config>::DbWeight::get()84 .read85 .saturating_mul(reads),86 ))87 }88 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {89 self.recorder90 .consume_gas(T::GasWeightMapping::weight_to_gas(91 <T as frame_system::Config>::DbWeight::get()92 .write93 .saturating_mul(writes),94 ))95 }96 pub fn submit_logs(self) {97 self.recorder.submit_logs()98 }99 pub fn save(self) -> DispatchResult {100 self.recorder.submit_logs();101 <CollectionById<T>>::insert(self.id, self.collection);102 Ok(())103 }104}105impl<T: Config> Deref for CollectionHandle<T> {106 type Target = Collection<T::AccountId>;107108 fn deref(&self) -> &Self::Target {109 &self.collection110 }111}112113impl<T: Config> DerefMut for CollectionHandle<T> {114 fn deref_mut(&mut self) -> &mut Self::Target {115 &mut self.collection116 }117}118119impl<T: Config> CollectionHandle<T> {120 pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {121 ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);122 Ok(())123 }124 pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {125 *subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))126 }127 pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {128 ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);129 Ok(())130 }131 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {132 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)133 }134 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {135 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)136 }137 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {138 ensure!(139 <Allowlist<T>>::get((self.id, user)),140 <Error<T>>::AddressNotInAllowlist141 );142 Ok(())143 }144145 pub fn check_can_update_meta(146 &self,147 subject: &T::CrossAccountId,148 item_owner: &T::CrossAccountId,149 ) -> DispatchResult {150 match self.meta_update_permission {151 MetaUpdatePermission::ItemOwner => {152 ensure!(subject == item_owner, <Error<T>>::NoPermission);153 Ok(())154 }155 MetaUpdatePermission::Admin => self.check_is_owner_or_admin(subject),156 MetaUpdatePermission::None => fail!(<Error<T>>::NoPermission),157 }158 }159}160161#[frame_support::pallet]162pub mod pallet {163 use super::*;164 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key};165 use pallet_evm::account;166 use frame_support::traits::Currency;167 use up_data_structs::TokenId;168 use scale_info::TypeInfo;169170 #[pallet::config]171 pub trait Config:172 frame_system::Config + pallet_evm_coder_substrate::Config + TypeInfo + account::Config173 {174 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;175176 type Currency: Currency<Self::AccountId>;177178 #[pallet::constant]179 type CollectionCreationPrice: Get<180 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,181 >;182183 type TreasuryAccountId: Get<Self::AccountId>;184 }185186 #[pallet::pallet]187 #[pallet::generate_store(pub(super) trait Store)]188 pub struct Pallet<T>(_);189190 #[pallet::extra_constants]191 impl<T: Config> Pallet<T> {192 pub fn collection_admins_limit() -> u32 {193 COLLECTION_ADMINS_LIMIT194 }195 }196197 #[pallet::event]198 #[pallet::generate_deposit(pub fn deposit_event)]199 pub enum Event<T: Config> {200 201 202 203 204 205 206 207 208 209 CollectionCreated(CollectionId, u8, T::AccountId),210211 212 213 214 215 216 CollectionDestroyed(CollectionId),217218 219 220 221 222 223 224 225 226 227 228 229 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),230231 232 233 234 235 236 237 238 239 240 241 242 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),243244 245 246 247 248 249 250 251 252 253 254 255 Transfer(256 CollectionId,257 TokenId,258 T::CrossAccountId,259 T::CrossAccountId,260 u128,261 ),262263 264 265 266 267 268 269 270 271 272 Approved(273 CollectionId,274 TokenId,275 T::CrossAccountId,276 T::CrossAccountId,277 u128,278 ),279 }280281 #[pallet::error]282 pub enum Error<T> {283 284 CollectionNotFound,285 286 MustBeTokenOwner,287 288 NoPermission,289 290 PublicMintingNotAllowed,291 292 AddressNotInAllowlist,293294 295 CollectionNameLimitExceeded,296 297 CollectionDescriptionLimitExceeded,298 299 CollectionTokenPrefixLimitExceeded,300 301 TotalCollectionsLimitExceeded,302 303 TokenVariableDataLimitExceeded,304 305 CollectionAdminCountExceeded,306 307 CollectionLimitBoundsExceeded,308 309 OwnerPermissionsCantBeReverted,310311 312 TransferNotAllowed,313 314 AccountTokenLimitExceeded,315 316 CollectionTokenLimitExceeded,317 318 MetadataFlagFrozen,319320 321 TokenNotFound,322 323 TokenValueTooLow,324 325 ApprovedValueTooLow,326 327 CantApproveMoreThanOwned,328329 330 AddressIsZero,331 332 UnsupportedOperation,333334 335 NotSufficientFounds,336 }337338 #[pallet::storage]339 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;340 #[pallet::storage]341 pub type DestroyedCollectionCount<T> =342 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;343344 345 #[pallet::storage]346 pub type CollectionById<T> = StorageMap<347 Hasher = Blake2_128Concat,348 Key = CollectionId,349 Value = Collection<<T as frame_system::Config>::AccountId>,350 QueryKind = OptionQuery,351 >;352353 #[pallet::storage]354 pub type AdminAmount<T> = StorageMap<355 Hasher = Blake2_128Concat,356 Key = CollectionId,357 Value = u32,358 QueryKind = ValueQuery,359 >;360361 362 #[pallet::storage]363 pub type IsAdmin<T: Config> = StorageNMap<364 Key = (365 Key<Blake2_128Concat, CollectionId>,366 Key<Blake2_128Concat, T::CrossAccountId>,367 ),368 Value = bool,369 QueryKind = ValueQuery,370 >;371372 373 #[pallet::storage]374 pub type Allowlist<T: Config> = StorageNMap<375 Key = (376 Key<Blake2_128Concat, CollectionId>,377 Key<Blake2_128Concat, T::CrossAccountId>,378 ),379 Value = bool,380 QueryKind = ValueQuery,381 >;382383 384 #[pallet::storage]385 pub type DummyStorageValue<T> =386 StorageValue<Value = (CollectionStats, CollectionId, TokenId), QueryKind = OptionQuery>;387}388389impl<T: Config> Pallet<T> {390 391 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {392 ensure!(393 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,394 <Error<T>>::AddressIsZero395 );396 Ok(())397 }398 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {399 <IsAdmin<T>>::iter_prefix((collection,))400 .map(|(a, _)| a)401 .collect()402 }403 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {404 <Allowlist<T>>::iter_prefix((collection,))405 .map(|(a, _)| a)406 .collect()407 }408 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {409 <Allowlist<T>>::get((collection, user))410 }411 pub fn collection_stats() -> CollectionStats {412 let created = <CreatedCollectionCount<T>>::get();413 let destroyed = <DestroyedCollectionCount<T>>::get();414 CollectionStats {415 created: created.0,416 destroyed: destroyed.0,417 alive: created.0 - destroyed.0,418 }419 }420}421422impl<T: Config> Pallet<T> {423 pub fn init_collection(424 owner: T::AccountId,425 data: CreateCollectionData<T::AccountId>,426 ) -> Result<CollectionId, DispatchError> {427 {428 ensure!(429 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,430 Error::<T>::CollectionTokenPrefixLimitExceeded431 );432 }433434 let created_count = <CreatedCollectionCount<T>>::get()435 .0436 .checked_add(1)437 .ok_or(ArithmeticError::Overflow)?;438 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;439 let id = CollectionId(created_count);440441 442 ensure!(443 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,444 <Error<T>>::TotalCollectionsLimitExceeded445 );446447 448449 let collection = Collection {450 owner: owner.clone(),451 name: data.name,452 mode: data.mode.clone(),453 mint_mode: false,454 access: data.access.unwrap_or_default(),455 description: data.description,456 token_prefix: data.token_prefix,457 offchain_schema: data.offchain_schema,458 schema_version: data.schema_version.unwrap_or_default(),459 sponsorship: data460 .pending_sponsor461 .map(SponsorshipState::Unconfirmed)462 .unwrap_or_default(),463 variable_on_chain_schema: data.variable_on_chain_schema,464 const_on_chain_schema: data.const_on_chain_schema,465 limits: data466 .limits467 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))468 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,469 meta_update_permission: data.meta_update_permission.unwrap_or_default(),470 };471472 473 {474 let mut imbalance =475 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();476 imbalance.subsume(477 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(478 &T::TreasuryAccountId::get(),479 T::CollectionCreationPrice::get(),480 ),481 );482 <T as Config>::Currency::settle(483 &owner,484 imbalance,485 WithdrawReasons::TRANSFER,486 ExistenceRequirement::KeepAlive,487 )488 .map_err(|_| Error::<T>::NotSufficientFounds)?;489 }490491 <CreatedCollectionCount<T>>::put(created_count);492 <Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));493 <CollectionById<T>>::insert(id, collection);494 Ok(id)495 }496497 pub fn destroy_collection(498 collection: CollectionHandle<T>,499 sender: &T::CrossAccountId,500 ) -> DispatchResult {501 ensure!(502 collection.limits.owner_can_destroy(),503 <Error<T>>::NoPermission,504 );505 collection.check_is_owner(sender)?;506507 let destroyed_collections = <DestroyedCollectionCount<T>>::get()508 .0509 .checked_add(1)510 .ok_or(ArithmeticError::Overflow)?;511512 513514 <DestroyedCollectionCount<T>>::put(destroyed_collections);515 <CollectionById<T>>::remove(collection.id);516 <AdminAmount<T>>::remove(collection.id);517 <IsAdmin<T>>::remove_prefix((collection.id,), None);518 <Allowlist<T>>::remove_prefix((collection.id,), None);519520 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));521 Ok(())522 }523524 pub fn toggle_allowlist(525 collection: &CollectionHandle<T>,526 sender: &T::CrossAccountId,527 user: &T::CrossAccountId,528 allowed: bool,529 ) -> DispatchResult {530 collection.check_is_owner_or_admin(sender)?;531532 533534 if allowed {535 <Allowlist<T>>::insert((collection.id, user), true);536 } else {537 <Allowlist<T>>::remove((collection.id, user));538 }539540 Ok(())541 }542543 pub fn toggle_admin(544 collection: &CollectionHandle<T>,545 sender: &T::CrossAccountId,546 user: &T::CrossAccountId,547 admin: bool,548 ) -> DispatchResult {549 collection.check_is_owner_or_admin(sender)?;550551 let was_admin = <IsAdmin<T>>::get((collection.id, user));552 if was_admin == admin {553 return Ok(());554 }555 let amount = <AdminAmount<T>>::get(collection.id);556557 if admin {558 let amount = amount559 .checked_add(1)560 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;561 ensure!(562 amount <= Self::collection_admins_limit(),563 <Error<T>>::CollectionAdminCountExceeded,564 );565566 567568 <AdminAmount<T>>::insert(collection.id, amount);569 <IsAdmin<T>>::insert((collection.id, user), true);570 } else {571 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));572 <IsAdmin<T>>::remove((collection.id, user));573 }574575 Ok(())576 }577578 pub fn clamp_limits(579 mode: CollectionMode,580 old_limit: &CollectionLimits,581 mut new_limit: CollectionLimits,582 ) -> Result<CollectionLimits, DispatchError> {583 macro_rules! limit_default {584 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{585 $(586 if let Some($new) = $new.$field {587 let $old = $old.$field($($arg)?);588 let _ = $new;589 let _ = $old;590 $check591 } else {592 $new.$field = $old.$field593 }594 )*595 }};596 }597598 limit_default!(old_limit, new_limit,599 account_token_ownership_limit => ensure!(600 new_limit <= MAX_TOKEN_OWNERSHIP,601 <Error<T>>::CollectionLimitBoundsExceeded,602 ),603 sponsor_transfer_timeout(match mode {604 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,605 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,606 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,607 }) => ensure!(608 new_limit <= MAX_SPONSOR_TIMEOUT,609 <Error<T>>::CollectionLimitBoundsExceeded,610 ),611 sponsored_data_size => ensure!(612 new_limit <= CUSTOM_DATA_LIMIT,613 <Error<T>>::CollectionLimitBoundsExceeded,614 ),615 token_limit => ensure!(616 old_limit >= new_limit && new_limit > 0,617 <Error<T>>::CollectionTokenLimitExceeded618 ),619 owner_can_transfer => ensure!(620 old_limit || !new_limit,621 <Error<T>>::OwnerPermissionsCantBeReverted,622 ),623 owner_can_destroy => ensure!(624 old_limit || !new_limit,625 <Error<T>>::OwnerPermissionsCantBeReverted,626 ),627 sponsored_data_rate_limit => {},628 transfers_enabled => {},629 );630 Ok(new_limit)631 }632}633634#[macro_export]635macro_rules! unsupported {636 () => {637 Err(<Error<T>>::UnsupportedOperation.into())638 };639}640641642pub trait CommonWeightInfo<CrossAccountId> {643 fn create_item() -> Weight;644 fn create_multiple_items(amount: u32) -> Weight;645 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;646 fn burn_item() -> Weight;647 fn transfer() -> Weight;648 fn approve() -> Weight;649 fn transfer_from() -> Weight;650 fn burn_from() -> Weight;651 fn set_variable_metadata(bytes: u32) -> Weight;652}653654pub trait CommonCollectionOperations<T: Config> {655 fn create_item(656 &self,657 sender: T::CrossAccountId,658 to: T::CrossAccountId,659 data: CreateItemData,660 ) -> DispatchResultWithPostInfo;661 fn create_multiple_items(662 &self,663 sender: T::CrossAccountId,664 to: T::CrossAccountId,665 data: Vec<CreateItemData>,666 ) -> DispatchResultWithPostInfo;667 fn create_multiple_items_ex(668 &self,669 sender: T::CrossAccountId,670 data: CreateItemExData<T::CrossAccountId>,671 ) -> DispatchResultWithPostInfo;672 fn burn_item(673 &self,674 sender: T::CrossAccountId,675 token: TokenId,676 amount: u128,677 ) -> DispatchResultWithPostInfo;678679 fn transfer(680 &self,681 sender: T::CrossAccountId,682 to: T::CrossAccountId,683 token: TokenId,684 amount: u128,685 ) -> DispatchResultWithPostInfo;686 fn approve(687 &self,688 sender: T::CrossAccountId,689 spender: T::CrossAccountId,690 token: TokenId,691 amount: u128,692 ) -> DispatchResultWithPostInfo;693 fn transfer_from(694 &self,695 sender: T::CrossAccountId,696 from: T::CrossAccountId,697 to: T::CrossAccountId,698 token: TokenId,699 amount: u128,700 ) -> DispatchResultWithPostInfo;701 fn burn_from(702 &self,703 sender: T::CrossAccountId,704 from: T::CrossAccountId,705 token: TokenId,706 amount: u128,707 ) -> DispatchResultWithPostInfo;708709 fn set_variable_metadata(710 &self,711 sender: T::CrossAccountId,712 token: TokenId,713 data: BoundedVec<u8, CustomDataLimit>,714 ) -> DispatchResultWithPostInfo;715716 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;717 fn token_exists(&self, token: TokenId) -> bool;718 fn last_token_id(&self) -> TokenId;719720 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;721 fn const_metadata(&self, token: TokenId) -> Vec<u8>;722 fn variable_metadata(&self, token: TokenId) -> Vec<u8>;723724 725 fn collection_tokens(&self) -> u32;726 727 fn account_balance(&self, account: T::CrossAccountId) -> u32;728 729 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;730 fn allowance(731 &self,732 sender: T::CrossAccountId,733 spender: T::CrossAccountId,734 token: TokenId,735 ) -> u128;736}737738739pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {740 let post_info = PostDispatchInfo {741 actual_weight: Some(weight),742 pays_fee: Pays::Yes,743 };744 match res {745 Ok(()) => Ok(post_info),746 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),747 }748}