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>;165 type CollectionCreationPrice: Get<166 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,167 >;168 type TreasuryAccountId: Get<Self::AccountId>;169 }170171 #[pallet::pallet]172 #[pallet::generate_store(pub(super) trait Store)]173 pub struct Pallet<T>(_);174175 #[pallet::extra_constants]176 impl<T: Config> Pallet<T> {177 pub fn collection_admins_limit() -> u32 {178 COLLECTION_ADMINS_LIMIT179 }180 }181182 #[pallet::event]183 #[pallet::generate_deposit(pub fn deposit_event)]184 pub enum Event<T: Config> {185 186 187 188 189 190 191 192 193 194 CollectionCreated(CollectionId, u8, T::AccountId),195196 197 198 199 200 201 CollectionDestroyed(CollectionId),202203 204 205 206 207 208 209 210 211 212 213 214 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),215216 217 218 219 220 221 222 223 224 225 226 227 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),228229 230 231 232 233 234 235 236 237 238 239 240 Transfer(241 CollectionId,242 TokenId,243 T::CrossAccountId,244 T::CrossAccountId,245 u128,246 ),247248 249 250 251 252 253 254 255 256 257 Approved(258 CollectionId,259 TokenId,260 T::CrossAccountId,261 T::CrossAccountId,262 u128,263 ),264 }265266 #[pallet::error]267 pub enum Error<T> {268 269 CollectionNotFound,270 271 MustBeTokenOwner,272 273 NoPermission,274 275 PublicMintingNotAllowed,276 277 AddressNotInAllowlist,278279 280 CollectionNameLimitExceeded,281 282 CollectionDescriptionLimitExceeded,283 284 CollectionTokenPrefixLimitExceeded,285 286 TotalCollectionsLimitExceeded,287 288 TokenVariableDataLimitExceeded,289 290 CollectionAdminCountExceeded,291 292 CollectionLimitBoundsExceeded,293 294 OwnerPermissionsCantBeReverted,295296 297 TransferNotAllowed,298 299 AccountTokenLimitExceeded,300 301 CollectionTokenLimitExceeded,302 303 MetadataFlagFrozen,304305 306 TokenNotFound,307 308 TokenValueTooLow,309 310 ApprovedValueTooLow,311 312 CantApproveMoreThanOwned,313314 315 AddressIsZero,316 317 UnsupportedOperation,318 }319320 #[pallet::storage]321 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;322 #[pallet::storage]323 pub type DestroyedCollectionCount<T> =324 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;325326 327 #[pallet::storage]328 pub type CollectionById<T> = StorageMap<329 Hasher = Blake2_128Concat,330 Key = CollectionId,331 Value = Collection<<T as frame_system::Config>::AccountId>,332 QueryKind = OptionQuery,333 >;334335 #[pallet::storage]336 pub type AdminAmount<T> = StorageMap<337 Hasher = Blake2_128Concat,338 Key = CollectionId,339 Value = u32,340 QueryKind = ValueQuery,341 >;342343 344 #[pallet::storage]345 pub type IsAdmin<T: Config> = StorageNMap<346 Key = (347 Key<Blake2_128Concat, CollectionId>,348 Key<Blake2_128Concat, T::CrossAccountId>,349 ),350 Value = bool,351 QueryKind = ValueQuery,352 >;353354 355 #[pallet::storage]356 pub type Allowlist<T: Config> = StorageNMap<357 Key = (358 Key<Blake2_128Concat, CollectionId>,359 Key<Blake2_128Concat, T::CrossAccountId>,360 ),361 Value = bool,362 QueryKind = ValueQuery,363 >;364365 366 #[pallet::storage]367 pub type DummyStorageValue<T> =368 StorageValue<Value = (CollectionStats, CollectionId, TokenId), QueryKind = OptionQuery>;369}370371impl<T: Config> Pallet<T> {372 373 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {374 ensure!(375 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,376 <Error<T>>::AddressIsZero377 );378 Ok(())379 }380 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {381 <IsAdmin<T>>::iter_prefix((collection,))382 .map(|(a, _)| a)383 .collect()384 }385 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {386 <Allowlist<T>>::iter_prefix((collection,))387 .map(|(a, _)| a)388 .collect()389 }390 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {391 <Allowlist<T>>::get((collection, user))392 }393 pub fn collection_stats() -> CollectionStats {394 let created = <CreatedCollectionCount<T>>::get();395 let destroyed = <DestroyedCollectionCount<T>>::get();396 CollectionStats {397 created: created.0,398 destroyed: destroyed.0,399 alive: created.0 - destroyed.0,400 }401 }402}403404impl<T: Config> Pallet<T> {405 pub fn init_collection(406 owner: T::AccountId,407 data: CreateCollectionData<T::AccountId>,408 ) -> Result<CollectionId, DispatchError> {409 {410 ensure!(411 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,412 Error::<T>::CollectionTokenPrefixLimitExceeded413 );414 }415416 let created_count = <CreatedCollectionCount<T>>::get()417 .0418 .checked_add(1)419 .ok_or(ArithmeticError::Overflow)?;420 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;421 let id = CollectionId(created_count);422423 424 ensure!(425 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,426 <Error<T>>::TotalCollectionsLimitExceeded427 );428429 430431 let collection = Collection {432 owner: owner.clone(),433 name: data.name,434 mode: data.mode.clone(),435 mint_mode: false,436 access: data.access.unwrap_or_default(),437 description: data.description,438 token_prefix: data.token_prefix,439 offchain_schema: data.offchain_schema,440 schema_version: data.schema_version.unwrap_or_default(),441 sponsorship: data442 .pending_sponsor443 .map(SponsorshipState::Unconfirmed)444 .unwrap_or_default(),445 variable_on_chain_schema: data.variable_on_chain_schema,446 const_on_chain_schema: data.const_on_chain_schema,447 limits: data448 .limits449 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))450 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,451 meta_update_permission: data.meta_update_permission.unwrap_or_default(),452 };453454 455 {456 let mut imbalance =457 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();458 imbalance.subsume(459 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(460 &T::TreasuryAccountId::get(),461 T::CollectionCreationPrice::get(),462 ),463 );464 <T as Config>::Currency::settle(465 &owner,466 imbalance,467 WithdrawReasons::TRANSFER,468 ExistenceRequirement::KeepAlive,469 )470 .map_err(|_| Error::<T>::NoPermission)?;471 }472473 <CreatedCollectionCount<T>>::put(created_count);474 <Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));475 <CollectionById<T>>::insert(id, collection);476 Ok(id)477 }478479 pub fn destroy_collection(480 collection: CollectionHandle<T>,481 sender: &T::CrossAccountId,482 ) -> DispatchResult {483 ensure!(484 collection.limits.owner_can_destroy(),485 <Error<T>>::NoPermission,486 );487 collection.check_is_owner(sender)?;488489 let destroyed_collections = <DestroyedCollectionCount<T>>::get()490 .0491 .checked_add(1)492 .ok_or(ArithmeticError::Overflow)?;493494 495496 <DestroyedCollectionCount<T>>::put(destroyed_collections);497 <CollectionById<T>>::remove(collection.id);498 <AdminAmount<T>>::remove(collection.id);499 <IsAdmin<T>>::remove_prefix((collection.id,), None);500 <Allowlist<T>>::remove_prefix((collection.id,), None);501502 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));503 Ok(())504 }505506 pub fn toggle_allowlist(507 collection: &CollectionHandle<T>,508 sender: &T::CrossAccountId,509 user: &T::CrossAccountId,510 allowed: bool,511 ) -> DispatchResult {512 collection.check_is_owner_or_admin(sender)?;513514 515516 if allowed {517 <Allowlist<T>>::insert((collection.id, user), true);518 } else {519 <Allowlist<T>>::remove((collection.id, user));520 }521522 Ok(())523 }524525 pub fn toggle_admin(526 collection: &CollectionHandle<T>,527 sender: &T::CrossAccountId,528 user: &T::CrossAccountId,529 admin: bool,530 ) -> DispatchResult {531 collection.check_is_owner_or_admin(sender)?;532533 let was_admin = <IsAdmin<T>>::get((collection.id, user));534 if was_admin == admin {535 return Ok(());536 }537 let amount = <AdminAmount<T>>::get(collection.id);538539 if admin {540 let amount = amount541 .checked_add(1)542 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;543 ensure!(544 amount <= Self::collection_admins_limit(),545 <Error<T>>::CollectionAdminCountExceeded,546 );547548 549550 <AdminAmount<T>>::insert(collection.id, amount);551 <IsAdmin<T>>::insert((collection.id, user), true);552 } else {553 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));554 <IsAdmin<T>>::remove((collection.id, user));555 }556557 Ok(())558 }559560 pub fn clamp_limits(561 mode: CollectionMode,562 old_limit: &CollectionLimits,563 mut new_limit: CollectionLimits,564 ) -> Result<CollectionLimits, DispatchError> {565 macro_rules! limit_default {566 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{567 $(568 if let Some($new) = $new.$field {569 let $old = $old.$field($($arg)?);570 let _ = $new;571 let _ = $old;572 $check573 } else {574 $new.$field = $old.$field575 }576 )*577 }};578 }579580 limit_default!(old_limit, new_limit,581 account_token_ownership_limit => ensure!(582 new_limit <= MAX_TOKEN_OWNERSHIP,583 <Error<T>>::CollectionLimitBoundsExceeded,584 ),585 sponsor_transfer_timeout(match mode {586 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,587 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,588 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,589 }) => ensure!(590 new_limit <= MAX_SPONSOR_TIMEOUT,591 <Error<T>>::CollectionLimitBoundsExceeded,592 ),593 sponsored_data_size => ensure!(594 new_limit <= CUSTOM_DATA_LIMIT,595 <Error<T>>::CollectionLimitBoundsExceeded,596 ),597 token_limit => ensure!(598 old_limit >= new_limit && new_limit > 0,599 <Error<T>>::CollectionTokenLimitExceeded600 ),601 owner_can_transfer => ensure!(602 old_limit || !new_limit,603 <Error<T>>::OwnerPermissionsCantBeReverted,604 ),605 owner_can_destroy => ensure!(606 old_limit || !new_limit,607 <Error<T>>::OwnerPermissionsCantBeReverted,608 ),609 sponsored_data_rate_limit => {},610 transfers_enabled => {},611 );612 Ok(new_limit)613 }614}615616#[macro_export]617macro_rules! unsupported {618 () => {619 Err(<Error<T>>::UnsupportedOperation.into())620 };621}622623624pub trait CommonWeightInfo {625 fn create_item() -> Weight;626 fn create_multiple_items(amount: u32) -> Weight;627 fn burn_item() -> Weight;628 fn transfer() -> Weight;629 fn approve() -> Weight;630 fn transfer_from() -> Weight;631 fn burn_from() -> Weight;632 fn set_variable_metadata(bytes: u32) -> Weight;633}634635pub trait CommonCollectionOperations<T: Config> {636 fn create_item(637 &self,638 sender: T::CrossAccountId,639 to: T::CrossAccountId,640 data: CreateItemData,641 ) -> DispatchResultWithPostInfo;642 fn create_multiple_items(643 &self,644 sender: T::CrossAccountId,645 to: T::CrossAccountId,646 data: Vec<CreateItemData>,647 ) -> DispatchResultWithPostInfo;648 fn burn_item(649 &self,650 sender: T::CrossAccountId,651 token: TokenId,652 amount: u128,653 ) -> DispatchResultWithPostInfo;654655 fn transfer(656 &self,657 sender: T::CrossAccountId,658 to: T::CrossAccountId,659 token: TokenId,660 amount: u128,661 ) -> DispatchResultWithPostInfo;662 fn approve(663 &self,664 sender: T::CrossAccountId,665 spender: T::CrossAccountId,666 token: TokenId,667 amount: u128,668 ) -> DispatchResultWithPostInfo;669 fn transfer_from(670 &self,671 sender: T::CrossAccountId,672 from: T::CrossAccountId,673 to: T::CrossAccountId,674 token: TokenId,675 amount: u128,676 ) -> DispatchResultWithPostInfo;677 fn burn_from(678 &self,679 sender: T::CrossAccountId,680 from: T::CrossAccountId,681 token: TokenId,682 amount: u128,683 ) -> DispatchResultWithPostInfo;684685 fn set_variable_metadata(686 &self,687 sender: T::CrossAccountId,688 token: TokenId,689 data: BoundedVec<u8, CustomDataLimit>,690 ) -> DispatchResultWithPostInfo;691692 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;693 fn token_exists(&self, token: TokenId) -> bool;694 fn last_token_id(&self) -> TokenId;695696 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;697 fn const_metadata(&self, token: TokenId) -> Vec<u8>;698 fn variable_metadata(&self, token: TokenId) -> Vec<u8>;699700 701 fn collection_tokens(&self) -> u32;702 703 fn account_balance(&self, account: T::CrossAccountId) -> u32;704 705 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;706 fn allowance(707 &self,708 sender: T::CrossAccountId,709 spender: T::CrossAccountId,710 token: TokenId,711 ) -> u128;712}713714715pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {716 let post_info = PostDispatchInfo {717 actual_weight: Some(weight),718 pays_fee: Pays::Yes,719 };720 match res {721 Ok(()) => Ok(post_info),722 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),723 }724}