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 frame_common::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 frame_common::account::CrossAccountId;166 use frame_support::traits::Currency;167 use up_data_structs::TokenId;168 use scale_info::TypeInfo;169170 #[pallet::config]171 pub trait Config: frame_system::Config + pallet_evm_coder_substrate::Config + TypeInfo {172 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;173174 type CrossAccountId: CrossAccountId<Self::AccountId>;175176 type EvmAddressMapping: pallet_evm::AddressMapping<Self::AccountId>;177 type EvmBackwardsAddressMapping: up_evm_mapping::EvmBackwardsAddressMapping<Self::AccountId>;178179 type Currency: Currency<Self::AccountId>;180181 #[pallet::constant]182 type CollectionCreationPrice: Get<183 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,184 >;185186 type TreasuryAccountId: Get<Self::AccountId>;187 }188189 #[pallet::pallet]190 #[pallet::generate_store(pub(super) trait Store)]191 pub struct Pallet<T>(_);192193 #[pallet::extra_constants]194 impl<T: Config> Pallet<T> {195 pub fn collection_admins_limit() -> u32 {196 COLLECTION_ADMINS_LIMIT197 }198 }199200 #[pallet::event]201 #[pallet::generate_deposit(pub fn deposit_event)]202 pub enum Event<T: Config> {203 204 205 206 207 208 209 210 211 212 CollectionCreated(CollectionId, u8, T::AccountId),213214 215 216 217 218 219 CollectionDestroyed(CollectionId),220221 222 223 224 225 226 227 228 229 230 231 232 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),233234 235 236 237 238 239 240 241 242 243 244 245 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),246247 248 249 250 251 252 253 254 255 256 257 258 Transfer(259 CollectionId,260 TokenId,261 T::CrossAccountId,262 T::CrossAccountId,263 u128,264 ),265266 267 268 269 270 271 272 273 274 275 Approved(276 CollectionId,277 TokenId,278 T::CrossAccountId,279 T::CrossAccountId,280 u128,281 ),282 }283284 #[pallet::error]285 pub enum Error<T> {286 287 CollectionNotFound,288 289 MustBeTokenOwner,290 291 NoPermission,292 293 PublicMintingNotAllowed,294 295 AddressNotInAllowlist,296297 298 CollectionNameLimitExceeded,299 300 CollectionDescriptionLimitExceeded,301 302 CollectionTokenPrefixLimitExceeded,303 304 TotalCollectionsLimitExceeded,305 306 TokenVariableDataLimitExceeded,307 308 CollectionAdminCountExceeded,309 310 CollectionLimitBoundsExceeded,311 312 OwnerPermissionsCantBeReverted,313314 315 TransferNotAllowed,316 317 AccountTokenLimitExceeded,318 319 CollectionTokenLimitExceeded,320 321 MetadataFlagFrozen,322323 324 TokenNotFound,325 326 TokenValueTooLow,327 328 ApprovedValueTooLow,329 330 CantApproveMoreThanOwned,331332 333 AddressIsZero,334 335 UnsupportedOperation,336337 338 NotSufficientFounds,339 }340341 #[pallet::storage]342 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;343 #[pallet::storage]344 pub type DestroyedCollectionCount<T> =345 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;346347 348 #[pallet::storage]349 pub type CollectionById<T> = StorageMap<350 Hasher = Blake2_128Concat,351 Key = CollectionId,352 Value = Collection<<T as frame_system::Config>::AccountId>,353 QueryKind = OptionQuery,354 >;355356 #[pallet::storage]357 pub type AdminAmount<T> = StorageMap<358 Hasher = Blake2_128Concat,359 Key = CollectionId,360 Value = u32,361 QueryKind = ValueQuery,362 >;363364 365 #[pallet::storage]366 pub type IsAdmin<T: Config> = StorageNMap<367 Key = (368 Key<Blake2_128Concat, CollectionId>,369 Key<Blake2_128Concat, T::CrossAccountId>,370 ),371 Value = bool,372 QueryKind = ValueQuery,373 >;374375 376 #[pallet::storage]377 pub type Allowlist<T: Config> = StorageNMap<378 Key = (379 Key<Blake2_128Concat, CollectionId>,380 Key<Blake2_128Concat, T::CrossAccountId>,381 ),382 Value = bool,383 QueryKind = ValueQuery,384 >;385386 387 #[pallet::storage]388 pub type DummyStorageValue<T> =389 StorageValue<Value = (CollectionStats, CollectionId, TokenId), QueryKind = OptionQuery>;390}391392impl<T: Config> Pallet<T> {393 394 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {395 ensure!(396 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,397 <Error<T>>::AddressIsZero398 );399 Ok(())400 }401 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {402 <IsAdmin<T>>::iter_prefix((collection,))403 .map(|(a, _)| a)404 .collect()405 }406 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {407 <Allowlist<T>>::iter_prefix((collection,))408 .map(|(a, _)| a)409 .collect()410 }411 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {412 <Allowlist<T>>::get((collection, user))413 }414 pub fn collection_stats() -> CollectionStats {415 let created = <CreatedCollectionCount<T>>::get();416 let destroyed = <DestroyedCollectionCount<T>>::get();417 CollectionStats {418 created: created.0,419 destroyed: destroyed.0,420 alive: created.0 - destroyed.0,421 }422 }423}424425impl<T: Config> Pallet<T> {426 pub fn init_collection(427 owner: T::AccountId,428 data: CreateCollectionData<T::AccountId>,429 ) -> Result<CollectionId, DispatchError> {430 {431 ensure!(432 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,433 Error::<T>::CollectionTokenPrefixLimitExceeded434 );435 }436437 let created_count = <CreatedCollectionCount<T>>::get()438 .0439 .checked_add(1)440 .ok_or(ArithmeticError::Overflow)?;441 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;442 let id = CollectionId(created_count);443444 445 ensure!(446 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,447 <Error<T>>::TotalCollectionsLimitExceeded448 );449450 451452 let collection = Collection {453 owner: owner.clone(),454 name: data.name,455 mode: data.mode.clone(),456 mint_mode: false,457 access: data.access.unwrap_or_default(),458 description: data.description,459 token_prefix: data.token_prefix,460 offchain_schema: data.offchain_schema,461 schema_version: data.schema_version.unwrap_or_default(),462 sponsorship: data463 .pending_sponsor464 .map(SponsorshipState::Unconfirmed)465 .unwrap_or_default(),466 variable_on_chain_schema: data.variable_on_chain_schema,467 const_on_chain_schema: data.const_on_chain_schema,468 limits: data469 .limits470 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))471 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,472 meta_update_permission: data.meta_update_permission.unwrap_or_default(),473 };474475 476 {477 let mut imbalance =478 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();479 imbalance.subsume(480 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(481 &T::TreasuryAccountId::get(),482 T::CollectionCreationPrice::get(),483 ),484 );485 <T as Config>::Currency::settle(486 &owner,487 imbalance,488 WithdrawReasons::TRANSFER,489 ExistenceRequirement::KeepAlive,490 )491 .map_err(|_| Error::<T>::NotSufficientFounds)?;492 }493494 <CreatedCollectionCount<T>>::put(created_count);495 <Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));496 <CollectionById<T>>::insert(id, collection);497 Ok(id)498 }499500 pub fn destroy_collection(501 collection: CollectionHandle<T>,502 sender: &T::CrossAccountId,503 ) -> DispatchResult {504 ensure!(505 collection.limits.owner_can_destroy(),506 <Error<T>>::NoPermission,507 );508 collection.check_is_owner(sender)?;509510 let destroyed_collections = <DestroyedCollectionCount<T>>::get()511 .0512 .checked_add(1)513 .ok_or(ArithmeticError::Overflow)?;514515 516517 <DestroyedCollectionCount<T>>::put(destroyed_collections);518 <CollectionById<T>>::remove(collection.id);519 <AdminAmount<T>>::remove(collection.id);520 <IsAdmin<T>>::remove_prefix((collection.id,), None);521 <Allowlist<T>>::remove_prefix((collection.id,), None);522523 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));524 Ok(())525 }526527 pub fn toggle_allowlist(528 collection: &CollectionHandle<T>,529 sender: &T::CrossAccountId,530 user: &T::CrossAccountId,531 allowed: bool,532 ) -> DispatchResult {533 collection.check_is_owner_or_admin(sender)?;534535 536537 if allowed {538 <Allowlist<T>>::insert((collection.id, user), true);539 } else {540 <Allowlist<T>>::remove((collection.id, user));541 }542543 Ok(())544 }545546 pub fn toggle_admin(547 collection: &CollectionHandle<T>,548 sender: &T::CrossAccountId,549 user: &T::CrossAccountId,550 admin: bool,551 ) -> DispatchResult {552 collection.check_is_owner_or_admin(sender)?;553554 let was_admin = <IsAdmin<T>>::get((collection.id, user));555 if was_admin == admin {556 return Ok(());557 }558 let amount = <AdminAmount<T>>::get(collection.id);559560 if admin {561 let amount = amount562 .checked_add(1)563 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;564 ensure!(565 amount <= Self::collection_admins_limit(),566 <Error<T>>::CollectionAdminCountExceeded,567 );568569 570571 <AdminAmount<T>>::insert(collection.id, amount);572 <IsAdmin<T>>::insert((collection.id, user), true);573 } else {574 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));575 <IsAdmin<T>>::remove((collection.id, user));576 }577578 Ok(())579 }580581 pub fn clamp_limits(582 mode: CollectionMode,583 old_limit: &CollectionLimits,584 mut new_limit: CollectionLimits,585 ) -> Result<CollectionLimits, DispatchError> {586 macro_rules! limit_default {587 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{588 $(589 if let Some($new) = $new.$field {590 let $old = $old.$field($($arg)?);591 let _ = $new;592 let _ = $old;593 $check594 } else {595 $new.$field = $old.$field596 }597 )*598 }};599 }600601 limit_default!(old_limit, new_limit,602 account_token_ownership_limit => ensure!(603 new_limit <= MAX_TOKEN_OWNERSHIP,604 <Error<T>>::CollectionLimitBoundsExceeded,605 ),606 sponsor_transfer_timeout(match mode {607 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,608 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,609 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,610 }) => ensure!(611 new_limit <= MAX_SPONSOR_TIMEOUT,612 <Error<T>>::CollectionLimitBoundsExceeded,613 ),614 sponsored_data_size => ensure!(615 new_limit <= CUSTOM_DATA_LIMIT,616 <Error<T>>::CollectionLimitBoundsExceeded,617 ),618 token_limit => ensure!(619 old_limit >= new_limit && new_limit > 0,620 <Error<T>>::CollectionTokenLimitExceeded621 ),622 owner_can_transfer => ensure!(623 old_limit || !new_limit,624 <Error<T>>::OwnerPermissionsCantBeReverted,625 ),626 owner_can_destroy => ensure!(627 old_limit || !new_limit,628 <Error<T>>::OwnerPermissionsCantBeReverted,629 ),630 sponsored_data_rate_limit => {},631 transfers_enabled => {},632 );633 Ok(new_limit)634 }635}636637#[macro_export]638macro_rules! unsupported {639 () => {640 Err(<Error<T>>::UnsupportedOperation.into())641 };642}643644645pub trait CommonWeightInfo<CrossAccountId> {646 fn create_item() -> Weight;647 fn create_multiple_items(amount: u32) -> Weight;648 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;649 fn burn_item() -> Weight;650 fn transfer() -> Weight;651 fn approve() -> Weight;652 fn transfer_from() -> Weight;653 fn burn_from() -> Weight;654 fn set_variable_metadata(bytes: u32) -> Weight;655}656657pub trait CommonCollectionOperations<T: Config> {658 fn create_item(659 &self,660 sender: T::CrossAccountId,661 to: T::CrossAccountId,662 data: CreateItemData,663 ) -> DispatchResultWithPostInfo;664 fn create_multiple_items(665 &self,666 sender: T::CrossAccountId,667 to: T::CrossAccountId,668 data: Vec<CreateItemData>,669 ) -> DispatchResultWithPostInfo;670 fn create_multiple_items_ex(671 &self,672 sender: T::CrossAccountId,673 data: CreateItemExData<T::CrossAccountId>,674 ) -> DispatchResultWithPostInfo;675 fn burn_item(676 &self,677 sender: T::CrossAccountId,678 token: TokenId,679 amount: u128,680 ) -> DispatchResultWithPostInfo;681682 fn transfer(683 &self,684 sender: T::CrossAccountId,685 to: T::CrossAccountId,686 token: TokenId,687 amount: u128,688 ) -> DispatchResultWithPostInfo;689 fn approve(690 &self,691 sender: T::CrossAccountId,692 spender: T::CrossAccountId,693 token: TokenId,694 amount: u128,695 ) -> DispatchResultWithPostInfo;696 fn transfer_from(697 &self,698 sender: T::CrossAccountId,699 from: T::CrossAccountId,700 to: T::CrossAccountId,701 token: TokenId,702 amount: u128,703 ) -> DispatchResultWithPostInfo;704 fn burn_from(705 &self,706 sender: T::CrossAccountId,707 from: T::CrossAccountId,708 token: TokenId,709 amount: u128,710 ) -> DispatchResultWithPostInfo;711712 fn set_variable_metadata(713 &self,714 sender: T::CrossAccountId,715 token: TokenId,716 data: BoundedVec<u8, CustomDataLimit>,717 ) -> DispatchResultWithPostInfo;718719 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;720 fn token_exists(&self, token: TokenId) -> bool;721 fn last_token_id(&self) -> TokenId;722723 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;724 fn const_metadata(&self, token: TokenId) -> Vec<u8>;725 fn variable_metadata(&self, token: TokenId) -> Vec<u8>;726727 728 fn collection_tokens(&self) -> u32;729 730 fn account_balance(&self, account: T::CrossAccountId) -> u32;731 732 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;733 fn allowance(734 &self,735 sender: T::CrossAccountId,736 spender: T::CrossAccountId,737 token: TokenId,738 ) -> u128;739}740741742pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {743 let post_info = PostDispatchInfo {744 actual_weight: Some(weight),745 pays_fee: Pays::Yes,746 };747 match res {748 Ok(()) => Ok(post_info),749 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),750 }751}