1#![cfg_attr(not(feature = "std"), no_std)]23use core::ops::{Deref, DerefMut};4use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};5use sp_std::vec::Vec;6use account::CrossAccountId;7use frame_support::{8 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo},9 ensure, fail,10 traits::{Imbalance, Get, Currency},11 BoundedVec,12};13use pallet_evm::GasWeightMapping;14use up_data_structs::{15 COLLECTION_NUMBER_LIMIT, Collection, CollectionId, CreateItemData, ExistenceRequirement,16 MAX_TOKEN_PREFIX_LENGTH, COLLECTION_ADMINS_LIMIT, MetaUpdatePermission, Pays, PostDispatchInfo,17 TokenId, Weight, WithdrawReasons, CollectionStats, MAX_TOKEN_OWNERSHIP, CollectionMode,18 NFT_SPONSOR_TRANSFER_TIMEOUT, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,19 REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, MAX_SPONSOR_TIMEOUT, CUSTOM_DATA_LIMIT, CollectionLimits,20 CustomDataLimit, CreateCollectionData, SponsorshipState,21};22pub use pallet::*;23use sp_core::H160;24use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};25pub mod account;26#[cfg(feature = "runtime-benchmarks")]27pub mod benchmarking;28pub mod erc;29pub mod eth;3031#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]32pub struct CollectionHandle<T: Config> {33 pub id: CollectionId,34 collection: Collection<T::AccountId>,35 pub recorder: SubstrateRecorder<T>,36}37impl<T: Config> WithRecorder<T> for CollectionHandle<T> {38 fn recorder(&self) -> &SubstrateRecorder<T> {39 &self.recorder40 }41 fn into_recorder(self) -> SubstrateRecorder<T> {42 self.recorder43 }44}45impl<T: Config> CollectionHandle<T> {46 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {47 <CollectionById<T>>::get(id).map(|collection| Self {48 id,49 collection,50 recorder: SubstrateRecorder::new(eth::collection_id_to_address(id), gas_limit),51 })52 }53 pub fn new(id: CollectionId) -> Option<Self> {54 Self::new_with_gas_limit(id, u64::MAX)55 }56 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {57 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)58 }59 pub fn log_mirrored(&self, log: impl evm_coder::ToLog) {60 self.recorder.log_mirrored(log)61 }62 pub fn log_direct(&self, log: impl evm_coder::ToLog) {63 self.recorder.log_direct(log)64 }65 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {66 self.recorder67 .consume_gas(T::GasWeightMapping::weight_to_gas(68 <T as frame_system::Config>::DbWeight::get()69 .read70 .saturating_mul(reads),71 ))72 }73 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {74 self.recorder75 .consume_gas(T::GasWeightMapping::weight_to_gas(76 <T as frame_system::Config>::DbWeight::get()77 .write78 .saturating_mul(writes),79 ))80 }81 pub fn submit_logs(self) {82 self.recorder.submit_logs()83 }84 pub fn save(self) -> DispatchResult {85 self.recorder.submit_logs();86 <CollectionById<T>>::insert(self.id, self.collection);87 Ok(())88 }89}90impl<T: Config> Deref for CollectionHandle<T> {91 type Target = Collection<T::AccountId>;9293 fn deref(&self) -> &Self::Target {94 &self.collection95 }96}9798impl<T: Config> DerefMut for CollectionHandle<T> {99 fn deref_mut(&mut self) -> &mut Self::Target {100 &mut self.collection101 }102}103104impl<T: Config> CollectionHandle<T> {105 pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {106 ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);107 Ok(())108 }109 pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {110 *subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))111 }112 pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {113 ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);114 Ok(())115 }116 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {117 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)118 }119 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {120 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)121 }122 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {123 ensure!(124 <Allowlist<T>>::get((self.id, user)),125 <Error<T>>::AddressNotInAllowlist126 );127 Ok(())128 }129130 pub fn check_can_update_meta(131 &self,132 subject: &T::CrossAccountId,133 item_owner: &T::CrossAccountId,134 ) -> DispatchResult {135 match self.meta_update_permission {136 MetaUpdatePermission::ItemOwner => {137 ensure!(subject == item_owner, <Error<T>>::NoPermission);138 Ok(())139 }140 MetaUpdatePermission::Admin => self.check_is_owner_or_admin(subject),141 MetaUpdatePermission::None => fail!(<Error<T>>::NoPermission),142 }143 }144}145146#[frame_support::pallet]147pub mod pallet {148 use super::*;149 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key};150 use account::CrossAccountId;151 use frame_support::traits::Currency;152 use up_data_structs::TokenId;153 use scale_info::TypeInfo;154155 #[pallet::config]156 pub trait Config: frame_system::Config + pallet_evm_coder_substrate::Config + TypeInfo {157 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;158159 type CrossAccountId: CrossAccountId<Self::AccountId>;160161 type EvmAddressMapping: pallet_evm::AddressMapping<Self::AccountId>;162 type EvmBackwardsAddressMapping: up_evm_mapping::EvmBackwardsAddressMapping<Self::AccountId>;163164 type Currency: Currency<Self::AccountId>;165166 #[pallet::constant]167 type CollectionCreationPrice: Get<168 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,169 >;170171 type TreasuryAccountId: Get<Self::AccountId>;172 }173174 #[pallet::pallet]175 #[pallet::generate_store(pub(super) trait Store)]176 pub struct Pallet<T>(_);177178 #[pallet::extra_constants]179 impl<T: Config> Pallet<T> {180 pub fn collection_admins_limit() -> u32 {181 COLLECTION_ADMINS_LIMIT182 }183 }184185 #[pallet::event]186 #[pallet::generate_deposit(pub fn deposit_event)]187 pub enum Event<T: Config> {188 189 190 191 192 193 194 195 196 197 CollectionCreated(CollectionId, u8, T::AccountId),198199 200 201 202 203 204 CollectionDestroyed(CollectionId),205206 207 208 209 210 211 212 213 214 215 216 217 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),218219 220 221 222 223 224 225 226 227 228 229 230 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),231232 233 234 235 236 237 238 239 240 241 242 243 Transfer(244 CollectionId,245 TokenId,246 T::CrossAccountId,247 T::CrossAccountId,248 u128,249 ),250251 252 253 254 255 256 257 258 259 260 Approved(261 CollectionId,262 TokenId,263 T::CrossAccountId,264 T::CrossAccountId,265 u128,266 ),267 }268269 #[pallet::error]270 pub enum Error<T> {271 272 CollectionNotFound,273 274 MustBeTokenOwner,275 276 NoPermission,277 278 PublicMintingNotAllowed,279 280 AddressNotInAllowlist,281282 283 CollectionNameLimitExceeded,284 285 CollectionDescriptionLimitExceeded,286 287 CollectionTokenPrefixLimitExceeded,288 289 TotalCollectionsLimitExceeded,290 291 TokenVariableDataLimitExceeded,292 293 CollectionAdminCountExceeded,294 295 CollectionLimitBoundsExceeded,296 297 OwnerPermissionsCantBeReverted,298299 300 TransferNotAllowed,301 302 AccountTokenLimitExceeded,303 304 CollectionTokenLimitExceeded,305 306 MetadataFlagFrozen,307308 309 TokenNotFound,310 311 TokenValueTooLow,312 313 ApprovedValueTooLow,314 315 CantApproveMoreThanOwned,316317 318 AddressIsZero,319 320 UnsupportedOperation,321 }322323 #[pallet::storage]324 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;325 #[pallet::storage]326 pub type DestroyedCollectionCount<T> =327 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;328329 330 #[pallet::storage]331 pub type CollectionById<T> = StorageMap<332 Hasher = Blake2_128Concat,333 Key = CollectionId,334 Value = Collection<<T as frame_system::Config>::AccountId>,335 QueryKind = OptionQuery,336 >;337338 #[pallet::storage]339 pub type AdminAmount<T> = StorageMap<340 Hasher = Blake2_128Concat,341 Key = CollectionId,342 Value = u32,343 QueryKind = ValueQuery,344 >;345346 347 #[pallet::storage]348 pub type IsAdmin<T: Config> = StorageNMap<349 Key = (350 Key<Blake2_128Concat, CollectionId>,351 Key<Blake2_128Concat, T::CrossAccountId>,352 ),353 Value = bool,354 QueryKind = ValueQuery,355 >;356357 358 #[pallet::storage]359 pub type Allowlist<T: Config> = StorageNMap<360 Key = (361 Key<Blake2_128Concat, CollectionId>,362 Key<Blake2_128Concat, T::CrossAccountId>,363 ),364 Value = bool,365 QueryKind = ValueQuery,366 >;367368 369 #[pallet::storage]370 pub type DummyStorageValue<T> =371 StorageValue<Value = (CollectionStats, CollectionId, TokenId), QueryKind = OptionQuery>;372}373374impl<T: Config> Pallet<T> {375 376 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {377 ensure!(378 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,379 <Error<T>>::AddressIsZero380 );381 Ok(())382 }383 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {384 <IsAdmin<T>>::iter_prefix((collection,))385 .map(|(a, _)| a)386 .collect()387 }388 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {389 <Allowlist<T>>::iter_prefix((collection,))390 .map(|(a, _)| a)391 .collect()392 }393 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {394 <Allowlist<T>>::get((collection, user))395 }396 pub fn collection_stats() -> CollectionStats {397 let created = <CreatedCollectionCount<T>>::get();398 let destroyed = <DestroyedCollectionCount<T>>::get();399 CollectionStats {400 created: created.0,401 destroyed: destroyed.0,402 alive: created.0 - destroyed.0,403 }404 }405}406407impl<T: Config> Pallet<T> {408 pub fn init_collection(409 owner: T::AccountId,410 data: CreateCollectionData<T::AccountId>,411 ) -> Result<CollectionId, DispatchError> {412 {413 ensure!(414 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,415 Error::<T>::CollectionTokenPrefixLimitExceeded416 );417 }418419 let created_count = <CreatedCollectionCount<T>>::get()420 .0421 .checked_add(1)422 .ok_or(ArithmeticError::Overflow)?;423 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;424 let id = CollectionId(created_count);425426 427 ensure!(428 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,429 <Error<T>>::TotalCollectionsLimitExceeded430 );431432 433434 let collection = Collection {435 owner: owner.clone(),436 name: data.name,437 mode: data.mode.clone(),438 mint_mode: false,439 access: data.access.unwrap_or_default(),440 description: data.description,441 token_prefix: data.token_prefix,442 offchain_schema: data.offchain_schema,443 schema_version: data.schema_version.unwrap_or_default(),444 sponsorship: data445 .pending_sponsor446 .map(SponsorshipState::Unconfirmed)447 .unwrap_or_default(),448 variable_on_chain_schema: data.variable_on_chain_schema,449 const_on_chain_schema: data.const_on_chain_schema,450 limits: data451 .limits452 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))453 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,454 meta_update_permission: data.meta_update_permission.unwrap_or_default(),455 };456457 458 {459 let mut imbalance =460 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();461 imbalance.subsume(462 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(463 &T::TreasuryAccountId::get(),464 T::CollectionCreationPrice::get(),465 ),466 );467 <T as Config>::Currency::settle(468 &owner,469 imbalance,470 WithdrawReasons::TRANSFER,471 ExistenceRequirement::KeepAlive,472 )473 .map_err(|_| Error::<T>::NoPermission)?;474 }475476 <CreatedCollectionCount<T>>::put(created_count);477 <Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));478 <CollectionById<T>>::insert(id, collection);479 Ok(id)480 }481482 pub fn destroy_collection(483 collection: CollectionHandle<T>,484 sender: &T::CrossAccountId,485 ) -> DispatchResult {486 ensure!(487 collection.limits.owner_can_destroy(),488 <Error<T>>::NoPermission,489 );490 collection.check_is_owner(sender)?;491492 let destroyed_collections = <DestroyedCollectionCount<T>>::get()493 .0494 .checked_add(1)495 .ok_or(ArithmeticError::Overflow)?;496497 498499 <DestroyedCollectionCount<T>>::put(destroyed_collections);500 <CollectionById<T>>::remove(collection.id);501 <AdminAmount<T>>::remove(collection.id);502 <IsAdmin<T>>::remove_prefix((collection.id,), None);503 <Allowlist<T>>::remove_prefix((collection.id,), None);504505 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));506 Ok(())507 }508509 pub fn toggle_allowlist(510 collection: &CollectionHandle<T>,511 sender: &T::CrossAccountId,512 user: &T::CrossAccountId,513 allowed: bool,514 ) -> DispatchResult {515 collection.check_is_owner_or_admin(sender)?;516517 518519 if allowed {520 <Allowlist<T>>::insert((collection.id, user), true);521 } else {522 <Allowlist<T>>::remove((collection.id, user));523 }524525 Ok(())526 }527528 pub fn toggle_admin(529 collection: &CollectionHandle<T>,530 sender: &T::CrossAccountId,531 user: &T::CrossAccountId,532 admin: bool,533 ) -> DispatchResult {534 collection.check_is_owner_or_admin(sender)?;535536 let was_admin = <IsAdmin<T>>::get((collection.id, user));537 if was_admin == admin {538 return Ok(());539 }540 let amount = <AdminAmount<T>>::get(collection.id);541542 if admin {543 let amount = amount544 .checked_add(1)545 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;546 ensure!(547 amount <= Self::collection_admins_limit(),548 <Error<T>>::CollectionAdminCountExceeded,549 );550551 552553 <AdminAmount<T>>::insert(collection.id, amount);554 <IsAdmin<T>>::insert((collection.id, user), true);555 } else {556 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));557 <IsAdmin<T>>::remove((collection.id, user));558 }559560 Ok(())561 }562563 pub fn clamp_limits(564 mode: CollectionMode,565 old_limit: &CollectionLimits,566 mut new_limit: CollectionLimits,567 ) -> Result<CollectionLimits, DispatchError> {568 macro_rules! limit_default {569 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{570 $(571 if let Some($new) = $new.$field {572 let $old = $old.$field($($arg)?);573 let _ = $new;574 let _ = $old;575 $check576 } else {577 $new.$field = $old.$field578 }579 )*580 }};581 }582583 limit_default!(old_limit, new_limit,584 account_token_ownership_limit => ensure!(585 new_limit <= MAX_TOKEN_OWNERSHIP,586 <Error<T>>::CollectionLimitBoundsExceeded,587 ),588 sponsor_transfer_timeout(match mode {589 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,590 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,591 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,592 }) => ensure!(593 new_limit <= MAX_SPONSOR_TIMEOUT,594 <Error<T>>::CollectionLimitBoundsExceeded,595 ),596 sponsored_data_size => ensure!(597 new_limit <= CUSTOM_DATA_LIMIT,598 <Error<T>>::CollectionLimitBoundsExceeded,599 ),600 token_limit => ensure!(601 old_limit >= new_limit && new_limit > 0,602 <Error<T>>::CollectionTokenLimitExceeded603 ),604 owner_can_transfer => ensure!(605 old_limit || !new_limit,606 <Error<T>>::OwnerPermissionsCantBeReverted,607 ),608 owner_can_destroy => ensure!(609 old_limit || !new_limit,610 <Error<T>>::OwnerPermissionsCantBeReverted,611 ),612 sponsored_data_rate_limit => {},613 transfers_enabled => {},614 );615 Ok(new_limit)616 }617}618619#[macro_export]620macro_rules! unsupported {621 () => {622 Err(<Error<T>>::UnsupportedOperation.into())623 };624}625626627pub trait CommonWeightInfo {628 fn create_item() -> Weight;629 fn create_multiple_items(amount: u32) -> Weight;630 fn burn_item() -> Weight;631 fn transfer() -> Weight;632 fn approve() -> Weight;633 fn transfer_from() -> Weight;634 fn burn_from() -> Weight;635 fn set_variable_metadata(bytes: u32) -> Weight;636}637638pub trait CommonCollectionOperations<T: Config> {639 fn create_item(640 &self,641 sender: T::CrossAccountId,642 to: T::CrossAccountId,643 data: CreateItemData,644 ) -> DispatchResultWithPostInfo;645 fn create_multiple_items(646 &self,647 sender: T::CrossAccountId,648 to: T::CrossAccountId,649 data: Vec<CreateItemData>,650 ) -> DispatchResultWithPostInfo;651 fn burn_item(652 &self,653 sender: T::CrossAccountId,654 token: TokenId,655 amount: u128,656 ) -> DispatchResultWithPostInfo;657658 fn transfer(659 &self,660 sender: T::CrossAccountId,661 to: T::CrossAccountId,662 token: TokenId,663 amount: u128,664 ) -> DispatchResultWithPostInfo;665 fn approve(666 &self,667 sender: T::CrossAccountId,668 spender: T::CrossAccountId,669 token: TokenId,670 amount: u128,671 ) -> DispatchResultWithPostInfo;672 fn transfer_from(673 &self,674 sender: T::CrossAccountId,675 from: T::CrossAccountId,676 to: T::CrossAccountId,677 token: TokenId,678 amount: u128,679 ) -> DispatchResultWithPostInfo;680 fn burn_from(681 &self,682 sender: T::CrossAccountId,683 from: T::CrossAccountId,684 token: TokenId,685 amount: u128,686 ) -> DispatchResultWithPostInfo;687688 fn set_variable_metadata(689 &self,690 sender: T::CrossAccountId,691 token: TokenId,692 data: BoundedVec<u8, CustomDataLimit>,693 ) -> DispatchResultWithPostInfo;694695 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;696 fn token_exists(&self, token: TokenId) -> bool;697 fn last_token_id(&self) -> TokenId;698699 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;700 fn const_metadata(&self, token: TokenId) -> Vec<u8>;701 fn variable_metadata(&self, token: TokenId) -> Vec<u8>;702703 704 fn collection_tokens(&self) -> u32;705 706 fn account_balance(&self, account: T::CrossAccountId) -> u32;707 708 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;709 fn allowance(710 &self,711 sender: T::CrossAccountId,712 spender: T::CrossAccountId,713 token: TokenId,714 ) -> u128;715}716717718pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {719 let post_info = PostDispatchInfo {720 actual_weight: Some(weight),721 pays_fee: Pays::Yes,722 };723 match res {724 Ok(()) => Ok(post_info),725 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),726 }727}