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: frame_system::Config + pallet_evm_coder_substrate::Config + TypeInfo + account::Config {172 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;173174 type Currency: Currency<Self::AccountId>;175176 #[pallet::constant]177 type CollectionCreationPrice: Get<178 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,179 >;180181 type TreasuryAccountId: Get<Self::AccountId>;182 }183184 #[pallet::pallet]185 #[pallet::generate_store(pub(super) trait Store)]186 pub struct Pallet<T>(_);187188 #[pallet::extra_constants]189 impl<T: Config> Pallet<T> {190 pub fn collection_admins_limit() -> u32 {191 COLLECTION_ADMINS_LIMIT192 }193 }194195 #[pallet::event]196 #[pallet::generate_deposit(pub fn deposit_event)]197 pub enum Event<T: Config> {198 199 200 201 202 203 204 205 206 207 CollectionCreated(CollectionId, u8, T::AccountId),208209 210 211 212 213 214 CollectionDestroyed(CollectionId),215216 217 218 219 220 221 222 223 224 225 226 227 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),228229 230 231 232 233 234 235 236 237 238 239 240 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),241242 243 244 245 246 247 248 249 250 251 252 253 Transfer(254 CollectionId,255 TokenId,256 T::CrossAccountId,257 T::CrossAccountId,258 u128,259 ),260261 262 263 264 265 266 267 268 269 270 Approved(271 CollectionId,272 TokenId,273 T::CrossAccountId,274 T::CrossAccountId,275 u128,276 ),277 }278279 #[pallet::error]280 pub enum Error<T> {281 282 CollectionNotFound,283 284 MustBeTokenOwner,285 286 NoPermission,287 288 PublicMintingNotAllowed,289 290 AddressNotInAllowlist,291292 293 CollectionNameLimitExceeded,294 295 CollectionDescriptionLimitExceeded,296 297 CollectionTokenPrefixLimitExceeded,298 299 TotalCollectionsLimitExceeded,300 301 TokenVariableDataLimitExceeded,302 303 CollectionAdminCountExceeded,304 305 CollectionLimitBoundsExceeded,306 307 OwnerPermissionsCantBeReverted,308309 310 TransferNotAllowed,311 312 AccountTokenLimitExceeded,313 314 CollectionTokenLimitExceeded,315 316 MetadataFlagFrozen,317318 319 TokenNotFound,320 321 TokenValueTooLow,322 323 ApprovedValueTooLow,324 325 CantApproveMoreThanOwned,326327 328 AddressIsZero,329 330 UnsupportedOperation,331332 333 NotSufficientFounds,334 }335336 #[pallet::storage]337 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;338 #[pallet::storage]339 pub type DestroyedCollectionCount<T> =340 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;341342 343 #[pallet::storage]344 pub type CollectionById<T> = StorageMap<345 Hasher = Blake2_128Concat,346 Key = CollectionId,347 Value = Collection<<T as frame_system::Config>::AccountId>,348 QueryKind = OptionQuery,349 >;350351 #[pallet::storage]352 pub type AdminAmount<T> = StorageMap<353 Hasher = Blake2_128Concat,354 Key = CollectionId,355 Value = u32,356 QueryKind = ValueQuery,357 >;358359 360 #[pallet::storage]361 pub type IsAdmin<T: Config> = StorageNMap<362 Key = (363 Key<Blake2_128Concat, CollectionId>,364 Key<Blake2_128Concat, T::CrossAccountId>,365 ),366 Value = bool,367 QueryKind = ValueQuery,368 >;369370 371 #[pallet::storage]372 pub type Allowlist<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 DummyStorageValue<T> =384 StorageValue<Value = (CollectionStats, CollectionId, TokenId), QueryKind = OptionQuery>;385}386387impl<T: Config> Pallet<T> {388 389 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {390 ensure!(391 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,392 <Error<T>>::AddressIsZero393 );394 Ok(())395 }396 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {397 <IsAdmin<T>>::iter_prefix((collection,))398 .map(|(a, _)| a)399 .collect()400 }401 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {402 <Allowlist<T>>::iter_prefix((collection,))403 .map(|(a, _)| a)404 .collect()405 }406 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {407 <Allowlist<T>>::get((collection, user))408 }409 pub fn collection_stats() -> CollectionStats {410 let created = <CreatedCollectionCount<T>>::get();411 let destroyed = <DestroyedCollectionCount<T>>::get();412 CollectionStats {413 created: created.0,414 destroyed: destroyed.0,415 alive: created.0 - destroyed.0,416 }417 }418}419420impl<T: Config> Pallet<T> {421 pub fn init_collection(422 owner: T::AccountId,423 data: CreateCollectionData<T::AccountId>,424 ) -> Result<CollectionId, DispatchError> {425 {426 ensure!(427 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,428 Error::<T>::CollectionTokenPrefixLimitExceeded429 );430 }431432 let created_count = <CreatedCollectionCount<T>>::get()433 .0434 .checked_add(1)435 .ok_or(ArithmeticError::Overflow)?;436 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;437 let id = CollectionId(created_count);438439 440 ensure!(441 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,442 <Error<T>>::TotalCollectionsLimitExceeded443 );444445 446447 let collection = Collection {448 owner: owner.clone(),449 name: data.name,450 mode: data.mode.clone(),451 mint_mode: false,452 access: data.access.unwrap_or_default(),453 description: data.description,454 token_prefix: data.token_prefix,455 offchain_schema: data.offchain_schema,456 schema_version: data.schema_version.unwrap_or_default(),457 sponsorship: data458 .pending_sponsor459 .map(SponsorshipState::Unconfirmed)460 .unwrap_or_default(),461 variable_on_chain_schema: data.variable_on_chain_schema,462 const_on_chain_schema: data.const_on_chain_schema,463 limits: data464 .limits465 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))466 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,467 meta_update_permission: data.meta_update_permission.unwrap_or_default(),468 };469470 471 {472 let mut imbalance =473 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();474 imbalance.subsume(475 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(476 &T::TreasuryAccountId::get(),477 T::CollectionCreationPrice::get(),478 ),479 );480 <T as Config>::Currency::settle(481 &owner,482 imbalance,483 WithdrawReasons::TRANSFER,484 ExistenceRequirement::KeepAlive,485 )486 .map_err(|_| Error::<T>::NotSufficientFounds)?;487 }488489 <CreatedCollectionCount<T>>::put(created_count);490 <Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));491 <CollectionById<T>>::insert(id, collection);492 Ok(id)493 }494495 pub fn destroy_collection(496 collection: CollectionHandle<T>,497 sender: &T::CrossAccountId,498 ) -> DispatchResult {499 ensure!(500 collection.limits.owner_can_destroy(),501 <Error<T>>::NoPermission,502 );503 collection.check_is_owner(sender)?;504505 let destroyed_collections = <DestroyedCollectionCount<T>>::get()506 .0507 .checked_add(1)508 .ok_or(ArithmeticError::Overflow)?;509510 511512 <DestroyedCollectionCount<T>>::put(destroyed_collections);513 <CollectionById<T>>::remove(collection.id);514 <AdminAmount<T>>::remove(collection.id);515 <IsAdmin<T>>::remove_prefix((collection.id,), None);516 <Allowlist<T>>::remove_prefix((collection.id,), None);517518 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));519 Ok(())520 }521522 pub fn toggle_allowlist(523 collection: &CollectionHandle<T>,524 sender: &T::CrossAccountId,525 user: &T::CrossAccountId,526 allowed: bool,527 ) -> DispatchResult {528 collection.check_is_owner_or_admin(sender)?;529530 531532 if allowed {533 <Allowlist<T>>::insert((collection.id, user), true);534 } else {535 <Allowlist<T>>::remove((collection.id, user));536 }537538 Ok(())539 }540541 pub fn toggle_admin(542 collection: &CollectionHandle<T>,543 sender: &T::CrossAccountId,544 user: &T::CrossAccountId,545 admin: bool,546 ) -> DispatchResult {547 collection.check_is_owner_or_admin(sender)?;548549 let was_admin = <IsAdmin<T>>::get((collection.id, user));550 if was_admin == admin {551 return Ok(());552 }553 let amount = <AdminAmount<T>>::get(collection.id);554555 if admin {556 let amount = amount557 .checked_add(1)558 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;559 ensure!(560 amount <= Self::collection_admins_limit(),561 <Error<T>>::CollectionAdminCountExceeded,562 );563564 565566 <AdminAmount<T>>::insert(collection.id, amount);567 <IsAdmin<T>>::insert((collection.id, user), true);568 } else {569 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));570 <IsAdmin<T>>::remove((collection.id, user));571 }572573 Ok(())574 }575576 pub fn clamp_limits(577 mode: CollectionMode,578 old_limit: &CollectionLimits,579 mut new_limit: CollectionLimits,580 ) -> Result<CollectionLimits, DispatchError> {581 macro_rules! limit_default {582 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{583 $(584 if let Some($new) = $new.$field {585 let $old = $old.$field($($arg)?);586 let _ = $new;587 let _ = $old;588 $check589 } else {590 $new.$field = $old.$field591 }592 )*593 }};594 }595596 limit_default!(old_limit, new_limit,597 account_token_ownership_limit => ensure!(598 new_limit <= MAX_TOKEN_OWNERSHIP,599 <Error<T>>::CollectionLimitBoundsExceeded,600 ),601 sponsor_transfer_timeout(match mode {602 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,603 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,604 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,605 }) => ensure!(606 new_limit <= MAX_SPONSOR_TIMEOUT,607 <Error<T>>::CollectionLimitBoundsExceeded,608 ),609 sponsored_data_size => ensure!(610 new_limit <= CUSTOM_DATA_LIMIT,611 <Error<T>>::CollectionLimitBoundsExceeded,612 ),613 token_limit => ensure!(614 old_limit >= new_limit && new_limit > 0,615 <Error<T>>::CollectionTokenLimitExceeded616 ),617 owner_can_transfer => ensure!(618 old_limit || !new_limit,619 <Error<T>>::OwnerPermissionsCantBeReverted,620 ),621 owner_can_destroy => ensure!(622 old_limit || !new_limit,623 <Error<T>>::OwnerPermissionsCantBeReverted,624 ),625 sponsored_data_rate_limit => {},626 transfers_enabled => {},627 );628 Ok(new_limit)629 }630}631632#[macro_export]633macro_rules! unsupported {634 () => {635 Err(<Error<T>>::UnsupportedOperation.into())636 };637}638639640pub trait CommonWeightInfo<CrossAccountId> {641 fn create_item() -> Weight;642 fn create_multiple_items(amount: u32) -> Weight;643 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;644 fn burn_item() -> Weight;645 fn transfer() -> Weight;646 fn approve() -> Weight;647 fn transfer_from() -> Weight;648 fn burn_from() -> Weight;649 fn set_variable_metadata(bytes: u32) -> Weight;650}651652pub trait CommonCollectionOperations<T: Config> {653 fn create_item(654 &self,655 sender: T::CrossAccountId,656 to: T::CrossAccountId,657 data: CreateItemData,658 ) -> DispatchResultWithPostInfo;659 fn create_multiple_items(660 &self,661 sender: T::CrossAccountId,662 to: T::CrossAccountId,663 data: Vec<CreateItemData>,664 ) -> DispatchResultWithPostInfo;665 fn create_multiple_items_ex(666 &self,667 sender: T::CrossAccountId,668 data: CreateItemExData<T::CrossAccountId>,669 ) -> DispatchResultWithPostInfo;670 fn burn_item(671 &self,672 sender: T::CrossAccountId,673 token: TokenId,674 amount: u128,675 ) -> DispatchResultWithPostInfo;676677 fn transfer(678 &self,679 sender: T::CrossAccountId,680 to: T::CrossAccountId,681 token: TokenId,682 amount: u128,683 ) -> DispatchResultWithPostInfo;684 fn approve(685 &self,686 sender: T::CrossAccountId,687 spender: T::CrossAccountId,688 token: TokenId,689 amount: u128,690 ) -> DispatchResultWithPostInfo;691 fn transfer_from(692 &self,693 sender: T::CrossAccountId,694 from: T::CrossAccountId,695 to: T::CrossAccountId,696 token: TokenId,697 amount: u128,698 ) -> DispatchResultWithPostInfo;699 fn burn_from(700 &self,701 sender: T::CrossAccountId,702 from: T::CrossAccountId,703 token: TokenId,704 amount: u128,705 ) -> DispatchResultWithPostInfo;706707 fn set_variable_metadata(708 &self,709 sender: T::CrossAccountId,710 token: TokenId,711 data: BoundedVec<u8, CustomDataLimit>,712 ) -> DispatchResultWithPostInfo;713714 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;715 fn token_exists(&self, token: TokenId) -> bool;716 fn last_token_id(&self) -> TokenId;717718 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;719 fn const_metadata(&self, token: TokenId) -> Vec<u8>;720 fn variable_metadata(&self, token: TokenId) -> Vec<u8>;721722 723 fn collection_tokens(&self) -> u32;724 725 fn account_balance(&self, account: T::CrossAccountId) -> u32;726 727 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;728 fn allowance(729 &self,730 sender: T::CrossAccountId,731 spender: T::CrossAccountId,732 token: TokenId,733 ) -> u128;734}735736737pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {738 let post_info = PostDispatchInfo {739 actual_weight: Some(weight),740 pays_fee: Pays::Yes,741 };742 match res {743 Ok(()) => Ok(post_info),744 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),745 }746}