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, Weight, PostDispatchInfo},25 ensure, fail,26 traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},27 BoundedVec,28 weights::Pays,29};30use pallet_evm::GasWeightMapping;31use up_data_structs::{32 COLLECTION_NUMBER_LIMIT, Collection, CollectionId, CreateItemData, MAX_TOKEN_PREFIX_LENGTH,33 COLLECTION_ADMINS_LIMIT, MetaUpdatePermission, TokenId, CollectionStats, MAX_TOKEN_OWNERSHIP,34 CollectionMode, 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 dispatch;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 pallet_evm::account;167 use dispatch::CollectionDispatch;168 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};169 use frame_system::pallet_prelude::*;170 use frame_support::traits::Currency;171 use up_data_structs::{TokenId, mapping::TokenAddressMapping};172 use scale_info::TypeInfo;173 use up_evm_mapping::CrossAccountId;174175 #[pallet::config]176 pub trait Config:177 frame_system::Config + pallet_evm_coder_substrate::Config + TypeInfo + account::Config178 {179 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;180181 type Currency: Currency<Self::AccountId>;182183 #[pallet::constant]184 type CollectionCreationPrice: Get<185 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,186 >;187 type CollectionDispatch: CollectionDispatch<Self>;188189 type TreasuryAccountId: Get<Self::AccountId>;190191 type EvmTokenAddressMapping: TokenAddressMapping<H160>;192 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;193 }194195 #[pallet::pallet]196 #[pallet::generate_store(pub(super) trait Store)]197 pub struct Pallet<T>(_);198199 #[pallet::extra_constants]200 impl<T: Config> Pallet<T> {201 pub fn collection_admins_limit() -> u32 {202 COLLECTION_ADMINS_LIMIT203 }204 }205206 #[pallet::event]207 #[pallet::generate_deposit(pub fn deposit_event)]208 pub enum Event<T: Config> {209 210 211 212 213 214 215 216 217 218 CollectionCreated(CollectionId, u8, T::AccountId),219220 221 222 223 224 225 CollectionDestroyed(CollectionId),226227 228 229 230 231 232 233 234 235 236 237 238 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),239240 241 242 243 244 245 246 247 248 249 250 251 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),252253 254 255 256 257 258 259 260 261 262 263 264 Transfer(265 CollectionId,266 TokenId,267 T::CrossAccountId,268 T::CrossAccountId,269 u128,270 ),271272 273 274 275 276 277 278 279 280 281 Approved(282 CollectionId,283 TokenId,284 T::CrossAccountId,285 T::CrossAccountId,286 u128,287 ),288 }289290 #[pallet::error]291 pub enum Error<T> {292 293 CollectionNotFound,294 295 MustBeTokenOwner,296 297 NoPermission,298 299 PublicMintingNotAllowed,300 301 AddressNotInAllowlist,302303 304 CollectionNameLimitExceeded,305 306 CollectionDescriptionLimitExceeded,307 308 CollectionTokenPrefixLimitExceeded,309 310 TotalCollectionsLimitExceeded,311 312 TokenVariableDataLimitExceeded,313 314 CollectionAdminCountExceeded,315 316 CollectionLimitBoundsExceeded,317 318 OwnerPermissionsCantBeReverted,319320 321 TransferNotAllowed,322 323 AccountTokenLimitExceeded,324 325 CollectionTokenLimitExceeded,326 327 MetadataFlagFrozen,328329 330 TokenNotFound,331 332 TokenValueTooLow,333 334 ApprovedValueTooLow,335 336 CantApproveMoreThanOwned,337338 339 AddressIsZero,340 341 UnsupportedOperation,342343 344 NotSufficientFounds,345 }346347 #[pallet::storage]348 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;349 #[pallet::storage]350 pub type DestroyedCollectionCount<T> =351 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;352353 354 #[pallet::storage]355 pub type CollectionById<T> = StorageMap<356 Hasher = Blake2_128Concat,357 Key = CollectionId,358 Value = Collection<<T as frame_system::Config>::AccountId>,359 QueryKind = OptionQuery,360 >;361362 #[pallet::storage]363 pub type AdminAmount<T> = StorageMap<364 Hasher = Blake2_128Concat,365 Key = CollectionId,366 Value = u32,367 QueryKind = ValueQuery,368 >;369370 371 #[pallet::storage]372 pub type IsAdmin<T: Config> = StorageNMap<373 Key = (374 Key<Blake2_128Concat, CollectionId>,375 Key<Blake2_128Concat, T::CrossAccountId>,376 ),377 Value = bool,378 QueryKind = ValueQuery,379 >;380381 382 #[pallet::storage]383 pub type Allowlist<T: Config> = StorageNMap<384 Key = (385 Key<Blake2_128Concat, CollectionId>,386 Key<Blake2_128Concat, T::CrossAccountId>,387 ),388 Value = bool,389 QueryKind = ValueQuery,390 >;391392 393 #[pallet::storage]394 pub type DummyStorageValue<T> =395 StorageValue<Value = (CollectionStats, CollectionId, TokenId), QueryKind = OptionQuery>;396}397398impl<T: Config> Pallet<T> {399 400 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {401 ensure!(402 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,403 <Error<T>>::AddressIsZero404 );405 Ok(())406 }407 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {408 <IsAdmin<T>>::iter_prefix((collection,))409 .map(|(a, _)| a)410 .collect()411 }412 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {413 <Allowlist<T>>::iter_prefix((collection,))414 .map(|(a, _)| a)415 .collect()416 }417 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {418 <Allowlist<T>>::get((collection, user))419 }420 pub fn collection_stats() -> CollectionStats {421 let created = <CreatedCollectionCount<T>>::get();422 let destroyed = <DestroyedCollectionCount<T>>::get();423 CollectionStats {424 created: created.0,425 destroyed: destroyed.0,426 alive: created.0 - destroyed.0,427 }428 }429430 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {431 let collection = <CollectionById<T>>::get(collection);432 if collection.is_none() {433 return None;434 }435436 let collection = collection.unwrap();437 let limits = collection.limits;438 let effective_limits = CollectionLimits {439 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),440 sponsored_data_size: Some(limits.sponsored_data_size()),441 sponsored_data_rate_limit: Some(442 limits443 .sponsored_data_rate_limit444 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),445 ),446 token_limit: Some(limits.token_limit()),447 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(448 match collection.mode {449 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,450 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,451 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,452 },453 )),454 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),455 owner_can_transfer: Some(limits.owner_can_transfer()),456 owner_can_destroy: Some(limits.owner_can_destroy()),457 transfers_enabled: Some(limits.transfers_enabled()),458 };459460 Some(effective_limits)461 }462}463464impl<T: Config> Pallet<T> {465 pub fn init_collection(466 owner: T::AccountId,467 data: CreateCollectionData<T::AccountId>,468 ) -> Result<CollectionId, DispatchError> {469 {470 ensure!(471 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,472 Error::<T>::CollectionTokenPrefixLimitExceeded473 );474 }475476 let created_count = <CreatedCollectionCount<T>>::get()477 .0478 .checked_add(1)479 .ok_or(ArithmeticError::Overflow)?;480 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;481 let id = CollectionId(created_count);482483 484 ensure!(485 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,486 <Error<T>>::TotalCollectionsLimitExceeded487 );488489 490491 let collection = Collection {492 owner: owner.clone(),493 name: data.name,494 mode: data.mode.clone(),495 mint_mode: false,496 access: data.access.unwrap_or_default(),497 description: data.description,498 token_prefix: data.token_prefix,499 offchain_schema: data.offchain_schema,500 schema_version: data.schema_version.unwrap_or_default(),501 sponsorship: data502 .pending_sponsor503 .map(SponsorshipState::Unconfirmed)504 .unwrap_or_default(),505 variable_on_chain_schema: data.variable_on_chain_schema,506 const_on_chain_schema: data.const_on_chain_schema,507 limits: data508 .limits509 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))510 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,511 meta_update_permission: data.meta_update_permission.unwrap_or_default(),512 };513514 515 {516 let mut imbalance =517 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();518 imbalance.subsume(519 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(520 &T::TreasuryAccountId::get(),521 T::CollectionCreationPrice::get(),522 ),523 );524 <T as Config>::Currency::settle(525 &owner,526 imbalance,527 WithdrawReasons::TRANSFER,528 ExistenceRequirement::KeepAlive,529 )530 .map_err(|_| Error::<T>::NotSufficientFounds)?;531 }532533 <CreatedCollectionCount<T>>::put(created_count);534 <Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));535 <CollectionById<T>>::insert(id, collection);536 Ok(id)537 }538539 pub fn destroy_collection(540 collection: CollectionHandle<T>,541 sender: &T::CrossAccountId,542 ) -> DispatchResult {543 ensure!(544 collection.limits.owner_can_destroy(),545 <Error<T>>::NoPermission,546 );547 collection.check_is_owner(sender)?;548549 let destroyed_collections = <DestroyedCollectionCount<T>>::get()550 .0551 .checked_add(1)552 .ok_or(ArithmeticError::Overflow)?;553554 555556 <DestroyedCollectionCount<T>>::put(destroyed_collections);557 <CollectionById<T>>::remove(collection.id);558 <AdminAmount<T>>::remove(collection.id);559 <IsAdmin<T>>::remove_prefix((collection.id,), None);560 <Allowlist<T>>::remove_prefix((collection.id,), None);561562 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));563 Ok(())564 }565566 pub fn toggle_allowlist(567 collection: &CollectionHandle<T>,568 sender: &T::CrossAccountId,569 user: &T::CrossAccountId,570 allowed: bool,571 ) -> DispatchResult {572 collection.check_is_owner_or_admin(sender)?;573574 575576 if allowed {577 <Allowlist<T>>::insert((collection.id, user), true);578 } else {579 <Allowlist<T>>::remove((collection.id, user));580 }581582 Ok(())583 }584585 pub fn toggle_admin(586 collection: &CollectionHandle<T>,587 sender: &T::CrossAccountId,588 user: &T::CrossAccountId,589 admin: bool,590 ) -> DispatchResult {591 collection.check_is_owner_or_admin(sender)?;592593 let was_admin = <IsAdmin<T>>::get((collection.id, user));594 if was_admin == admin {595 return Ok(());596 }597 let amount = <AdminAmount<T>>::get(collection.id);598599 if admin {600 let amount = amount601 .checked_add(1)602 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;603 ensure!(604 amount <= Self::collection_admins_limit(),605 <Error<T>>::CollectionAdminCountExceeded,606 );607608 609610 <AdminAmount<T>>::insert(collection.id, amount);611 <IsAdmin<T>>::insert((collection.id, user), true);612 } else {613 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));614 <IsAdmin<T>>::remove((collection.id, user));615 }616617 Ok(())618 }619620 pub fn clamp_limits(621 mode: CollectionMode,622 old_limit: &CollectionLimits,623 mut new_limit: CollectionLimits,624 ) -> Result<CollectionLimits, DispatchError> {625 macro_rules! limit_default {626 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{627 $(628 if let Some($new) = $new.$field {629 let $old = $old.$field($($arg)?);630 let _ = $new;631 let _ = $old;632 $check633 } else {634 $new.$field = $old.$field635 }636 )*637 }};638 }639640 limit_default!(old_limit, new_limit,641 account_token_ownership_limit => ensure!(642 new_limit <= MAX_TOKEN_OWNERSHIP,643 <Error<T>>::CollectionLimitBoundsExceeded,644 ),645 sponsor_transfer_timeout(match mode {646 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,647 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,648 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,649 }) => ensure!(650 new_limit <= MAX_SPONSOR_TIMEOUT,651 <Error<T>>::CollectionLimitBoundsExceeded,652 ),653 sponsored_data_size => ensure!(654 new_limit <= CUSTOM_DATA_LIMIT,655 <Error<T>>::CollectionLimitBoundsExceeded,656 ),657 token_limit => ensure!(658 old_limit >= new_limit && new_limit > 0,659 <Error<T>>::CollectionTokenLimitExceeded660 ),661 owner_can_transfer => ensure!(662 old_limit || !new_limit,663 <Error<T>>::OwnerPermissionsCantBeReverted,664 ),665 owner_can_destroy => ensure!(666 old_limit || !new_limit,667 <Error<T>>::OwnerPermissionsCantBeReverted,668 ),669 sponsored_data_rate_limit => {},670 transfers_enabled => {},671 );672 Ok(new_limit)673 }674}675676#[macro_export]677macro_rules! unsupported {678 () => {679 Err(<Error<T>>::UnsupportedOperation.into())680 };681}682683684pub trait CommonWeightInfo<CrossAccountId> {685 fn create_item() -> Weight;686 fn create_multiple_items(amount: u32) -> Weight;687 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;688 fn burn_item() -> Weight;689 fn transfer() -> Weight;690 fn approve() -> Weight;691 fn transfer_from() -> Weight;692 fn burn_from() -> Weight;693 fn set_variable_metadata(bytes: u32) -> Weight;694}695696pub trait CommonCollectionOperations<T: Config> {697 fn create_item(698 &self,699 sender: T::CrossAccountId,700 to: T::CrossAccountId,701 data: CreateItemData,702 ) -> DispatchResultWithPostInfo;703 fn create_multiple_items(704 &self,705 sender: T::CrossAccountId,706 to: T::CrossAccountId,707 data: Vec<CreateItemData>,708 ) -> DispatchResultWithPostInfo;709 fn create_multiple_items_ex(710 &self,711 sender: T::CrossAccountId,712 data: CreateItemExData<T::CrossAccountId>,713 ) -> DispatchResultWithPostInfo;714 fn burn_item(715 &self,716 sender: T::CrossAccountId,717 token: TokenId,718 amount: u128,719 ) -> DispatchResultWithPostInfo;720721 fn transfer(722 &self,723 sender: T::CrossAccountId,724 to: T::CrossAccountId,725 token: TokenId,726 amount: u128,727 ) -> DispatchResultWithPostInfo;728 fn approve(729 &self,730 sender: T::CrossAccountId,731 spender: T::CrossAccountId,732 token: TokenId,733 amount: u128,734 ) -> DispatchResultWithPostInfo;735 fn transfer_from(736 &self,737 sender: T::CrossAccountId,738 from: T::CrossAccountId,739 to: T::CrossAccountId,740 token: TokenId,741 amount: u128,742 ) -> DispatchResultWithPostInfo;743 fn burn_from(744 &self,745 sender: T::CrossAccountId,746 from: T::CrossAccountId,747 token: TokenId,748 amount: u128,749 ) -> DispatchResultWithPostInfo;750751 fn set_variable_metadata(752 &self,753 sender: T::CrossAccountId,754 token: TokenId,755 data: BoundedVec<u8, CustomDataLimit>,756 ) -> DispatchResultWithPostInfo;757758 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;759 fn token_exists(&self, token: TokenId) -> bool;760 fn last_token_id(&self) -> TokenId;761762 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;763 fn const_metadata(&self, token: TokenId) -> Vec<u8>;764 fn variable_metadata(&self, token: TokenId) -> Vec<u8>;765766 767 fn collection_tokens(&self) -> u32;768 769 fn account_balance(&self, account: T::CrossAccountId) -> u32;770 771 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;772 fn allowance(773 &self,774 sender: T::CrossAccountId,775 spender: T::CrossAccountId,776 token: TokenId,777 ) -> u128;778}779780781pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {782 let post_info = PostDispatchInfo {783 actual_weight: Some(weight),784 pays_fee: Pays::Yes,785 };786 match res {787 Ok(()) => Ok(post_info),788 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),789 }790}