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, SponsoringRateLimit,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 }420421 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {422 let collection = <CollectionById<T>>::get(collection);423 if collection.is_none() {424 return None;425 }426427 let collection = collection.unwrap();428 let limits = collection.limits;429 let effective_limits = CollectionLimits {430 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),431 sponsored_data_size: Some(limits.sponsored_data_size()),432 sponsored_data_rate_limit: Some(433 limits434 .sponsored_data_rate_limit435 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),436 ),437 token_limit: Some(limits.token_limit()),438 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(439 match collection.mode {440 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,441 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,442 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,443 },444 )),445 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),446 owner_can_transfer: Some(limits.owner_can_transfer()),447 owner_can_destroy: Some(limits.owner_can_destroy()),448 transfers_enabled: Some(limits.transfers_enabled()),449 };450451 Some(effective_limits)452 }453}454455impl<T: Config> Pallet<T> {456 pub fn init_collection(457 owner: T::AccountId,458 data: CreateCollectionData<T::AccountId>,459 ) -> Result<CollectionId, DispatchError> {460 {461 ensure!(462 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,463 Error::<T>::CollectionTokenPrefixLimitExceeded464 );465 }466467 let created_count = <CreatedCollectionCount<T>>::get()468 .0469 .checked_add(1)470 .ok_or(ArithmeticError::Overflow)?;471 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;472 let id = CollectionId(created_count);473474 475 ensure!(476 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,477 <Error<T>>::TotalCollectionsLimitExceeded478 );479480 481482 let collection = Collection {483 owner: owner.clone(),484 name: data.name,485 mode: data.mode.clone(),486 mint_mode: false,487 access: data.access.unwrap_or_default(),488 description: data.description,489 token_prefix: data.token_prefix,490 offchain_schema: data.offchain_schema,491 schema_version: data.schema_version.unwrap_or_default(),492 sponsorship: data493 .pending_sponsor494 .map(SponsorshipState::Unconfirmed)495 .unwrap_or_default(),496 variable_on_chain_schema: data.variable_on_chain_schema,497 const_on_chain_schema: data.const_on_chain_schema,498 limits: data499 .limits500 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))501 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,502 meta_update_permission: data.meta_update_permission.unwrap_or_default(),503 };504505 506 {507 let mut imbalance =508 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();509 imbalance.subsume(510 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(511 &T::TreasuryAccountId::get(),512 T::CollectionCreationPrice::get(),513 ),514 );515 <T as Config>::Currency::settle(516 &owner,517 imbalance,518 WithdrawReasons::TRANSFER,519 ExistenceRequirement::KeepAlive,520 )521 .map_err(|_| Error::<T>::NotSufficientFounds)?;522 }523524 <CreatedCollectionCount<T>>::put(created_count);525 <Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));526 <CollectionById<T>>::insert(id, collection);527 Ok(id)528 }529530 pub fn destroy_collection(531 collection: CollectionHandle<T>,532 sender: &T::CrossAccountId,533 ) -> DispatchResult {534 ensure!(535 collection.limits.owner_can_destroy(),536 <Error<T>>::NoPermission,537 );538 collection.check_is_owner(sender)?;539540 let destroyed_collections = <DestroyedCollectionCount<T>>::get()541 .0542 .checked_add(1)543 .ok_or(ArithmeticError::Overflow)?;544545 546547 <DestroyedCollectionCount<T>>::put(destroyed_collections);548 <CollectionById<T>>::remove(collection.id);549 <AdminAmount<T>>::remove(collection.id);550 <IsAdmin<T>>::remove_prefix((collection.id,), None);551 <Allowlist<T>>::remove_prefix((collection.id,), None);552553 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));554 Ok(())555 }556557 pub fn toggle_allowlist(558 collection: &CollectionHandle<T>,559 sender: &T::CrossAccountId,560 user: &T::CrossAccountId,561 allowed: bool,562 ) -> DispatchResult {563 collection.check_is_owner_or_admin(sender)?;564565 566567 if allowed {568 <Allowlist<T>>::insert((collection.id, user), true);569 } else {570 <Allowlist<T>>::remove((collection.id, user));571 }572573 Ok(())574 }575576 pub fn toggle_admin(577 collection: &CollectionHandle<T>,578 sender: &T::CrossAccountId,579 user: &T::CrossAccountId,580 admin: bool,581 ) -> DispatchResult {582 collection.check_is_owner_or_admin(sender)?;583584 let was_admin = <IsAdmin<T>>::get((collection.id, user));585 if was_admin == admin {586 return Ok(());587 }588 let amount = <AdminAmount<T>>::get(collection.id);589590 if admin {591 let amount = amount592 .checked_add(1)593 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;594 ensure!(595 amount <= Self::collection_admins_limit(),596 <Error<T>>::CollectionAdminCountExceeded,597 );598599 600601 <AdminAmount<T>>::insert(collection.id, amount);602 <IsAdmin<T>>::insert((collection.id, user), true);603 } else {604 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));605 <IsAdmin<T>>::remove((collection.id, user));606 }607608 Ok(())609 }610611 pub fn clamp_limits(612 mode: CollectionMode,613 old_limit: &CollectionLimits,614 mut new_limit: CollectionLimits,615 ) -> Result<CollectionLimits, DispatchError> {616 macro_rules! limit_default {617 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{618 $(619 if let Some($new) = $new.$field {620 let $old = $old.$field($($arg)?);621 let _ = $new;622 let _ = $old;623 $check624 } else {625 $new.$field = $old.$field626 }627 )*628 }};629 }630631 limit_default!(old_limit, new_limit,632 account_token_ownership_limit => ensure!(633 new_limit <= MAX_TOKEN_OWNERSHIP,634 <Error<T>>::CollectionLimitBoundsExceeded,635 ),636 sponsor_transfer_timeout(match mode {637 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,638 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,639 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,640 }) => ensure!(641 new_limit <= MAX_SPONSOR_TIMEOUT,642 <Error<T>>::CollectionLimitBoundsExceeded,643 ),644 sponsored_data_size => ensure!(645 new_limit <= CUSTOM_DATA_LIMIT,646 <Error<T>>::CollectionLimitBoundsExceeded,647 ),648 token_limit => ensure!(649 old_limit >= new_limit && new_limit > 0,650 <Error<T>>::CollectionTokenLimitExceeded651 ),652 owner_can_transfer => ensure!(653 old_limit || !new_limit,654 <Error<T>>::OwnerPermissionsCantBeReverted,655 ),656 owner_can_destroy => ensure!(657 old_limit || !new_limit,658 <Error<T>>::OwnerPermissionsCantBeReverted,659 ),660 sponsored_data_rate_limit => {},661 transfers_enabled => {},662 );663 Ok(new_limit)664 }665}666667#[macro_export]668macro_rules! unsupported {669 () => {670 Err(<Error<T>>::UnsupportedOperation.into())671 };672}673674675pub trait CommonWeightInfo<CrossAccountId> {676 fn create_item() -> Weight;677 fn create_multiple_items(amount: u32) -> Weight;678 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;679 fn burn_item() -> Weight;680 fn transfer() -> Weight;681 fn approve() -> Weight;682 fn transfer_from() -> Weight;683 fn burn_from() -> Weight;684 fn set_variable_metadata(bytes: u32) -> Weight;685}686687pub trait CommonCollectionOperations<T: Config> {688 fn create_item(689 &self,690 sender: T::CrossAccountId,691 to: T::CrossAccountId,692 data: CreateItemData,693 ) -> DispatchResultWithPostInfo;694 fn create_multiple_items(695 &self,696 sender: T::CrossAccountId,697 to: T::CrossAccountId,698 data: Vec<CreateItemData>,699 ) -> DispatchResultWithPostInfo;700 fn create_multiple_items_ex(701 &self,702 sender: T::CrossAccountId,703 data: CreateItemExData<T::CrossAccountId>,704 ) -> DispatchResultWithPostInfo;705 fn burn_item(706 &self,707 sender: T::CrossAccountId,708 token: TokenId,709 amount: u128,710 ) -> DispatchResultWithPostInfo;711712 fn transfer(713 &self,714 sender: T::CrossAccountId,715 to: T::CrossAccountId,716 token: TokenId,717 amount: u128,718 ) -> DispatchResultWithPostInfo;719 fn approve(720 &self,721 sender: T::CrossAccountId,722 spender: T::CrossAccountId,723 token: TokenId,724 amount: u128,725 ) -> DispatchResultWithPostInfo;726 fn transfer_from(727 &self,728 sender: T::CrossAccountId,729 from: T::CrossAccountId,730 to: T::CrossAccountId,731 token: TokenId,732 amount: u128,733 ) -> DispatchResultWithPostInfo;734 fn burn_from(735 &self,736 sender: T::CrossAccountId,737 from: T::CrossAccountId,738 token: TokenId,739 amount: u128,740 ) -> DispatchResultWithPostInfo;741742 fn set_variable_metadata(743 &self,744 sender: T::CrossAccountId,745 token: TokenId,746 data: BoundedVec<u8, CustomDataLimit>,747 ) -> DispatchResultWithPostInfo;748749 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;750 fn token_exists(&self, token: TokenId) -> bool;751 fn last_token_id(&self) -> TokenId;752753 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;754 fn const_metadata(&self, token: TokenId) -> Vec<u8>;755 fn variable_metadata(&self, token: TokenId) -> Vec<u8>;756757 758 fn collection_tokens(&self) -> u32;759 760 fn account_balance(&self, account: T::CrossAccountId) -> u32;761 762 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;763 fn allowance(764 &self,765 sender: T::CrossAccountId,766 spender: T::CrossAccountId,767 token: TokenId,768 ) -> u128;769}770771772pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {773 let post_info = PostDispatchInfo {774 actual_weight: Some(weight),775 pays_fee: Pays::Yes,776 };777 match res {778 Ok(()) => Ok(post_info),779 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),780 }781}