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, CreateItemExData,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,321322 323 NotSufficientFounds,324 }325326 #[pallet::storage]327 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;328 #[pallet::storage]329 pub type DestroyedCollectionCount<T> =330 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;331332 333 #[pallet::storage]334 pub type CollectionById<T> = StorageMap<335 Hasher = Blake2_128Concat,336 Key = CollectionId,337 Value = Collection<<T as frame_system::Config>::AccountId>,338 QueryKind = OptionQuery,339 >;340341 #[pallet::storage]342 pub type AdminAmount<T> = StorageMap<343 Hasher = Blake2_128Concat,344 Key = CollectionId,345 Value = u32,346 QueryKind = ValueQuery,347 >;348349 350 #[pallet::storage]351 pub type IsAdmin<T: Config> = StorageNMap<352 Key = (353 Key<Blake2_128Concat, CollectionId>,354 Key<Blake2_128Concat, T::CrossAccountId>,355 ),356 Value = bool,357 QueryKind = ValueQuery,358 >;359360 361 #[pallet::storage]362 pub type Allowlist<T: Config> = StorageNMap<363 Key = (364 Key<Blake2_128Concat, CollectionId>,365 Key<Blake2_128Concat, T::CrossAccountId>,366 ),367 Value = bool,368 QueryKind = ValueQuery,369 >;370371 372 #[pallet::storage]373 pub type DummyStorageValue<T> =374 StorageValue<Value = (CollectionStats, CollectionId, TokenId), QueryKind = OptionQuery>;375}376377impl<T: Config> Pallet<T> {378 379 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {380 ensure!(381 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,382 <Error<T>>::AddressIsZero383 );384 Ok(())385 }386 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {387 <IsAdmin<T>>::iter_prefix((collection,))388 .map(|(a, _)| a)389 .collect()390 }391 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {392 <Allowlist<T>>::iter_prefix((collection,))393 .map(|(a, _)| a)394 .collect()395 }396 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {397 <Allowlist<T>>::get((collection, user))398 }399 pub fn collection_stats() -> CollectionStats {400 let created = <CreatedCollectionCount<T>>::get();401 let destroyed = <DestroyedCollectionCount<T>>::get();402 CollectionStats {403 created: created.0,404 destroyed: destroyed.0,405 alive: created.0 - destroyed.0,406 }407 }408}409410impl<T: Config> Pallet<T> {411 pub fn init_collection(412 owner: T::AccountId,413 data: CreateCollectionData<T::AccountId>,414 ) -> Result<CollectionId, DispatchError> {415 {416 ensure!(417 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,418 Error::<T>::CollectionTokenPrefixLimitExceeded419 );420 }421422 let created_count = <CreatedCollectionCount<T>>::get()423 .0424 .checked_add(1)425 .ok_or(ArithmeticError::Overflow)?;426 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;427 let id = CollectionId(created_count);428429 430 ensure!(431 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,432 <Error<T>>::TotalCollectionsLimitExceeded433 );434435 436437 let collection = Collection {438 owner: owner.clone(),439 name: data.name,440 mode: data.mode.clone(),441 mint_mode: false,442 access: data.access.unwrap_or_default(),443 description: data.description,444 token_prefix: data.token_prefix,445 offchain_schema: data.offchain_schema,446 schema_version: data.schema_version.unwrap_or_default(),447 sponsorship: data448 .pending_sponsor449 .map(SponsorshipState::Unconfirmed)450 .unwrap_or_default(),451 variable_on_chain_schema: data.variable_on_chain_schema,452 const_on_chain_schema: data.const_on_chain_schema,453 limits: data454 .limits455 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))456 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,457 meta_update_permission: data.meta_update_permission.unwrap_or_default(),458 };459460 461 {462 let mut imbalance =463 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();464 imbalance.subsume(465 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(466 &T::TreasuryAccountId::get(),467 T::CollectionCreationPrice::get(),468 ),469 );470 <T as Config>::Currency::settle(471 &owner,472 imbalance,473 WithdrawReasons::TRANSFER,474 ExistenceRequirement::KeepAlive,475 )476 .map_err(|_| Error::<T>::NotSufficientFounds)?;477 }478479 <CreatedCollectionCount<T>>::put(created_count);480 <Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));481 <CollectionById<T>>::insert(id, collection);482 Ok(id)483 }484485 pub fn destroy_collection(486 collection: CollectionHandle<T>,487 sender: &T::CrossAccountId,488 ) -> DispatchResult {489 ensure!(490 collection.limits.owner_can_destroy(),491 <Error<T>>::NoPermission,492 );493 collection.check_is_owner(sender)?;494495 let destroyed_collections = <DestroyedCollectionCount<T>>::get()496 .0497 .checked_add(1)498 .ok_or(ArithmeticError::Overflow)?;499500 501502 <DestroyedCollectionCount<T>>::put(destroyed_collections);503 <CollectionById<T>>::remove(collection.id);504 <AdminAmount<T>>::remove(collection.id);505 <IsAdmin<T>>::remove_prefix((collection.id,), None);506 <Allowlist<T>>::remove_prefix((collection.id,), None);507508 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));509 Ok(())510 }511512 pub fn toggle_allowlist(513 collection: &CollectionHandle<T>,514 sender: &T::CrossAccountId,515 user: &T::CrossAccountId,516 allowed: bool,517 ) -> DispatchResult {518 collection.check_is_owner_or_admin(sender)?;519520 521522 if allowed {523 <Allowlist<T>>::insert((collection.id, user), true);524 } else {525 <Allowlist<T>>::remove((collection.id, user));526 }527528 Ok(())529 }530531 pub fn toggle_admin(532 collection: &CollectionHandle<T>,533 sender: &T::CrossAccountId,534 user: &T::CrossAccountId,535 admin: bool,536 ) -> DispatchResult {537 collection.check_is_owner_or_admin(sender)?;538539 let was_admin = <IsAdmin<T>>::get((collection.id, user));540 if was_admin == admin {541 return Ok(());542 }543 let amount = <AdminAmount<T>>::get(collection.id);544545 if admin {546 let amount = amount547 .checked_add(1)548 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;549 ensure!(550 amount <= Self::collection_admins_limit(),551 <Error<T>>::CollectionAdminCountExceeded,552 );553554 555556 <AdminAmount<T>>::insert(collection.id, amount);557 <IsAdmin<T>>::insert((collection.id, user), true);558 } else {559 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));560 <IsAdmin<T>>::remove((collection.id, user));561 }562563 Ok(())564 }565566 pub fn clamp_limits(567 mode: CollectionMode,568 old_limit: &CollectionLimits,569 mut new_limit: CollectionLimits,570 ) -> Result<CollectionLimits, DispatchError> {571 macro_rules! limit_default {572 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{573 $(574 if let Some($new) = $new.$field {575 let $old = $old.$field($($arg)?);576 let _ = $new;577 let _ = $old;578 $check579 } else {580 $new.$field = $old.$field581 }582 )*583 }};584 }585586 limit_default!(old_limit, new_limit,587 account_token_ownership_limit => ensure!(588 new_limit <= MAX_TOKEN_OWNERSHIP,589 <Error<T>>::CollectionLimitBoundsExceeded,590 ),591 sponsor_transfer_timeout(match mode {592 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,593 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,594 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,595 }) => ensure!(596 new_limit <= MAX_SPONSOR_TIMEOUT,597 <Error<T>>::CollectionLimitBoundsExceeded,598 ),599 sponsored_data_size => ensure!(600 new_limit <= CUSTOM_DATA_LIMIT,601 <Error<T>>::CollectionLimitBoundsExceeded,602 ),603 token_limit => ensure!(604 old_limit >= new_limit && new_limit > 0,605 <Error<T>>::CollectionTokenLimitExceeded606 ),607 owner_can_transfer => ensure!(608 old_limit || !new_limit,609 <Error<T>>::OwnerPermissionsCantBeReverted,610 ),611 owner_can_destroy => ensure!(612 old_limit || !new_limit,613 <Error<T>>::OwnerPermissionsCantBeReverted,614 ),615 sponsored_data_rate_limit => {},616 transfers_enabled => {},617 );618 Ok(new_limit)619 }620}621622#[macro_export]623macro_rules! unsupported {624 () => {625 Err(<Error<T>>::UnsupportedOperation.into())626 };627}628629630pub trait CommonWeightInfo<CrossAccountId> {631 fn create_item() -> Weight;632 fn create_multiple_items(amount: u32) -> Weight;633 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;634 fn burn_item() -> Weight;635 fn transfer() -> Weight;636 fn approve() -> Weight;637 fn transfer_from() -> Weight;638 fn burn_from() -> Weight;639 fn set_variable_metadata(bytes: u32) -> Weight;640}641642pub trait CommonCollectionOperations<T: Config> {643 fn create_item(644 &self,645 sender: T::CrossAccountId,646 to: T::CrossAccountId,647 data: CreateItemData,648 ) -> DispatchResultWithPostInfo;649 fn create_multiple_items(650 &self,651 sender: T::CrossAccountId,652 to: T::CrossAccountId,653 data: Vec<CreateItemData>,654 ) -> DispatchResultWithPostInfo;655 fn create_multiple_items_ex(656 &self,657 sender: T::CrossAccountId,658 data: CreateItemExData<T::CrossAccountId>,659 ) -> DispatchResultWithPostInfo;660 fn burn_item(661 &self,662 sender: T::CrossAccountId,663 token: TokenId,664 amount: u128,665 ) -> DispatchResultWithPostInfo;666667 fn transfer(668 &self,669 sender: T::CrossAccountId,670 to: T::CrossAccountId,671 token: TokenId,672 amount: u128,673 ) -> DispatchResultWithPostInfo;674 fn approve(675 &self,676 sender: T::CrossAccountId,677 spender: T::CrossAccountId,678 token: TokenId,679 amount: u128,680 ) -> DispatchResultWithPostInfo;681 fn transfer_from(682 &self,683 sender: T::CrossAccountId,684 from: T::CrossAccountId,685 to: T::CrossAccountId,686 token: TokenId,687 amount: u128,688 ) -> DispatchResultWithPostInfo;689 fn burn_from(690 &self,691 sender: T::CrossAccountId,692 from: T::CrossAccountId,693 token: TokenId,694 amount: u128,695 ) -> DispatchResultWithPostInfo;696697 fn set_variable_metadata(698 &self,699 sender: T::CrossAccountId,700 token: TokenId,701 data: BoundedVec<u8, CustomDataLimit>,702 ) -> DispatchResultWithPostInfo;703704 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;705 fn token_exists(&self, token: TokenId) -> bool;706 fn last_token_id(&self) -> TokenId;707708 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;709 fn const_metadata(&self, token: TokenId) -> Vec<u8>;710 fn variable_metadata(&self, token: TokenId) -> Vec<u8>;711712 713 fn collection_tokens(&self) -> u32;714 715 fn account_balance(&self, account: T::CrossAccountId) -> u32;716 717 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;718 fn allowance(719 &self,720 sender: T::CrossAccountId,721 spender: T::CrossAccountId,722 token: TokenId,723 ) -> u128;724}725726727pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {728 let post_info = PostDispatchInfo {729 actual_weight: Some(weight),730 pays_fee: Pays::Yes,731 };732 match res {733 Ok(()) => Ok(post_info),734 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),735 }736}