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, SponsoringRateLimit37};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 }430 431 let limits = collection.unwrap().limits;432 let effective_limits = CollectionLimits {433 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),434 sponsored_data_size: Some(limits.sponsored_data_size()),435 sponsored_data_rate_limit: Some(436 limits.sponsored_data_rate_limit437 .unwrap_or(SponsoringRateLimit::SponsoringDisabled)),438 token_limit: Some(limits.token_limit()),439 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(MAX_SPONSOR_TIMEOUT)),440 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),441 owner_can_transfer: Some(limits.owner_can_transfer()),442 owner_can_destroy: Some(limits.owner_can_destroy()),443 transfers_enabled: Some(limits.transfers_enabled()),444 };445446 Some(effective_limits)447 }448}449450impl<T: Config> Pallet<T> {451 pub fn init_collection(452 owner: T::AccountId,453 data: CreateCollectionData<T::AccountId>,454 ) -> Result<CollectionId, DispatchError> {455 {456 ensure!(457 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,458 Error::<T>::CollectionTokenPrefixLimitExceeded459 );460 }461462 let created_count = <CreatedCollectionCount<T>>::get()463 .0464 .checked_add(1)465 .ok_or(ArithmeticError::Overflow)?;466 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;467 let id = CollectionId(created_count);468469 470 ensure!(471 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,472 <Error<T>>::TotalCollectionsLimitExceeded473 );474475 476477 let collection = Collection {478 owner: owner.clone(),479 name: data.name,480 mode: data.mode.clone(),481 mint_mode: false,482 access: data.access.unwrap_or_default(),483 description: data.description,484 token_prefix: data.token_prefix,485 offchain_schema: data.offchain_schema,486 schema_version: data.schema_version.unwrap_or_default(),487 sponsorship: data488 .pending_sponsor489 .map(SponsorshipState::Unconfirmed)490 .unwrap_or_default(),491 variable_on_chain_schema: data.variable_on_chain_schema,492 const_on_chain_schema: data.const_on_chain_schema,493 limits: data494 .limits495 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))496 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,497 meta_update_permission: data.meta_update_permission.unwrap_or_default(),498 };499500 501 {502 let mut imbalance =503 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();504 imbalance.subsume(505 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(506 &T::TreasuryAccountId::get(),507 T::CollectionCreationPrice::get(),508 ),509 );510 <T as Config>::Currency::settle(511 &owner,512 imbalance,513 WithdrawReasons::TRANSFER,514 ExistenceRequirement::KeepAlive,515 )516 .map_err(|_| Error::<T>::NotSufficientFounds)?;517 }518519 <CreatedCollectionCount<T>>::put(created_count);520 <Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));521 <CollectionById<T>>::insert(id, collection);522 Ok(id)523 }524525 pub fn destroy_collection(526 collection: CollectionHandle<T>,527 sender: &T::CrossAccountId,528 ) -> DispatchResult {529 ensure!(530 collection.limits.owner_can_destroy(),531 <Error<T>>::NoPermission,532 );533 collection.check_is_owner(sender)?;534535 let destroyed_collections = <DestroyedCollectionCount<T>>::get()536 .0537 .checked_add(1)538 .ok_or(ArithmeticError::Overflow)?;539540 541542 <DestroyedCollectionCount<T>>::put(destroyed_collections);543 <CollectionById<T>>::remove(collection.id);544 <AdminAmount<T>>::remove(collection.id);545 <IsAdmin<T>>::remove_prefix((collection.id,), None);546 <Allowlist<T>>::remove_prefix((collection.id,), None);547548 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));549 Ok(())550 }551552 pub fn toggle_allowlist(553 collection: &CollectionHandle<T>,554 sender: &T::CrossAccountId,555 user: &T::CrossAccountId,556 allowed: bool,557 ) -> DispatchResult {558 collection.check_is_owner_or_admin(sender)?;559560 561562 if allowed {563 <Allowlist<T>>::insert((collection.id, user), true);564 } else {565 <Allowlist<T>>::remove((collection.id, user));566 }567568 Ok(())569 }570571 pub fn toggle_admin(572 collection: &CollectionHandle<T>,573 sender: &T::CrossAccountId,574 user: &T::CrossAccountId,575 admin: bool,576 ) -> DispatchResult {577 collection.check_is_owner_or_admin(sender)?;578579 let was_admin = <IsAdmin<T>>::get((collection.id, user));580 if was_admin == admin {581 return Ok(());582 }583 let amount = <AdminAmount<T>>::get(collection.id);584585 if admin {586 let amount = amount587 .checked_add(1)588 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;589 ensure!(590 amount <= Self::collection_admins_limit(),591 <Error<T>>::CollectionAdminCountExceeded,592 );593594 595596 <AdminAmount<T>>::insert(collection.id, amount);597 <IsAdmin<T>>::insert((collection.id, user), true);598 } else {599 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));600 <IsAdmin<T>>::remove((collection.id, user));601 }602603 Ok(())604 }605606 pub fn clamp_limits(607 mode: CollectionMode,608 old_limit: &CollectionLimits,609 mut new_limit: CollectionLimits,610 ) -> Result<CollectionLimits, DispatchError> {611 macro_rules! limit_default {612 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{613 $(614 if let Some($new) = $new.$field {615 let $old = $old.$field($($arg)?);616 let _ = $new;617 let _ = $old;618 $check619 } else {620 $new.$field = $old.$field621 }622 )*623 }};624 }625626 limit_default!(old_limit, new_limit,627 account_token_ownership_limit => ensure!(628 new_limit <= MAX_TOKEN_OWNERSHIP,629 <Error<T>>::CollectionLimitBoundsExceeded,630 ),631 sponsor_transfer_timeout(match mode {632 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,633 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,634 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,635 }) => ensure!(636 new_limit <= MAX_SPONSOR_TIMEOUT,637 <Error<T>>::CollectionLimitBoundsExceeded,638 ),639 sponsored_data_size => ensure!(640 new_limit <= CUSTOM_DATA_LIMIT,641 <Error<T>>::CollectionLimitBoundsExceeded,642 ),643 token_limit => ensure!(644 old_limit >= new_limit && new_limit > 0,645 <Error<T>>::CollectionTokenLimitExceeded646 ),647 owner_can_transfer => ensure!(648 old_limit || !new_limit,649 <Error<T>>::OwnerPermissionsCantBeReverted,650 ),651 owner_can_destroy => ensure!(652 old_limit || !new_limit,653 <Error<T>>::OwnerPermissionsCantBeReverted,654 ),655 sponsored_data_rate_limit => {},656 transfers_enabled => {},657 );658 Ok(new_limit)659 }660}661662#[macro_export]663macro_rules! unsupported {664 () => {665 Err(<Error<T>>::UnsupportedOperation.into())666 };667}668669670pub trait CommonWeightInfo<CrossAccountId> {671 fn create_item() -> Weight;672 fn create_multiple_items(amount: u32) -> Weight;673 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;674 fn burn_item() -> Weight;675 fn transfer() -> Weight;676 fn approve() -> Weight;677 fn transfer_from() -> Weight;678 fn burn_from() -> Weight;679 fn set_variable_metadata(bytes: u32) -> Weight;680}681682pub trait CommonCollectionOperations<T: Config> {683 fn create_item(684 &self,685 sender: T::CrossAccountId,686 to: T::CrossAccountId,687 data: CreateItemData,688 ) -> DispatchResultWithPostInfo;689 fn create_multiple_items(690 &self,691 sender: T::CrossAccountId,692 to: T::CrossAccountId,693 data: Vec<CreateItemData>,694 ) -> DispatchResultWithPostInfo;695 fn create_multiple_items_ex(696 &self,697 sender: T::CrossAccountId,698 data: CreateItemExData<T::CrossAccountId>,699 ) -> DispatchResultWithPostInfo;700 fn burn_item(701 &self,702 sender: T::CrossAccountId,703 token: TokenId,704 amount: u128,705 ) -> DispatchResultWithPostInfo;706707 fn transfer(708 &self,709 sender: T::CrossAccountId,710 to: T::CrossAccountId,711 token: TokenId,712 amount: u128,713 ) -> DispatchResultWithPostInfo;714 fn approve(715 &self,716 sender: T::CrossAccountId,717 spender: T::CrossAccountId,718 token: TokenId,719 amount: u128,720 ) -> DispatchResultWithPostInfo;721 fn transfer_from(722 &self,723 sender: T::CrossAccountId,724 from: T::CrossAccountId,725 to: T::CrossAccountId,726 token: TokenId,727 amount: u128,728 ) -> DispatchResultWithPostInfo;729 fn burn_from(730 &self,731 sender: T::CrossAccountId,732 from: T::CrossAccountId,733 token: TokenId,734 amount: u128,735 ) -> DispatchResultWithPostInfo;736737 fn set_variable_metadata(738 &self,739 sender: T::CrossAccountId,740 token: TokenId,741 data: BoundedVec<u8, CustomDataLimit>,742 ) -> DispatchResultWithPostInfo;743744 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;745 fn token_exists(&self, token: TokenId) -> bool;746 fn last_token_id(&self) -> TokenId;747748 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;749 fn const_metadata(&self, token: TokenId) -> Vec<u8>;750 fn variable_metadata(&self, token: TokenId) -> Vec<u8>;751752 753 fn collection_tokens(&self) -> u32;754 755 fn account_balance(&self, account: T::CrossAccountId) -> u32;756 757 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;758 fn allowance(759 &self,760 sender: T::CrossAccountId,761 spender: T::CrossAccountId,762 token: TokenId,763 ) -> u128;764}765766767pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {768 let post_info = PostDispatchInfo {769 actual_weight: Some(weight),770 pays_fee: Pays::Yes,771 };772 match res {773 Ok(()) => Ok(post_info),774 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),775 }776}