difftreelog
cargo fmt
in: master
10 files changed
pallets/common/src/lib.rsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![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 /// New collection was created199 ///200 /// # Arguments201 ///202 /// * collection_id: Globally unique identifier of newly created collection.203 ///204 /// * mode: [CollectionMode] converted into u8.205 ///206 /// * account_id: Collection owner.207 CollectionCreated(CollectionId, u8, T::AccountId),208209 /// New collection was destroyed210 ///211 /// # Arguments212 ///213 /// * collection_id: Globally unique identifier of collection.214 CollectionDestroyed(CollectionId),215216 /// New item was created.217 ///218 /// # Arguments219 ///220 /// * collection_id: Id of the collection where item was created.221 ///222 /// * item_id: Id of an item. Unique within the collection.223 ///224 /// * recipient: Owner of newly created item225 ///226 /// * amount: Always 1 for NFT227 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),228229 /// Collection item was burned.230 ///231 /// # Arguments232 ///233 /// * collection_id.234 ///235 /// * item_id: Identifier of burned NFT.236 ///237 /// * owner: which user has destroyed its tokens238 ///239 /// * amount: Always 1 for NFT240 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),241242 /// Item was transferred243 ///244 /// * collection_id: Id of collection to which item is belong245 ///246 /// * item_id: Id of an item247 ///248 /// * sender: Original owner of item249 ///250 /// * recipient: New owner of item251 ///252 /// * amount: Always 1 for NFT253 Transfer(254 CollectionId,255 TokenId,256 T::CrossAccountId,257 T::CrossAccountId,258 u128,259 ),260261 /// * collection_id262 ///263 /// * item_id264 ///265 /// * sender266 ///267 /// * spender268 ///269 /// * amount270 Approved(271 CollectionId,272 TokenId,273 T::CrossAccountId,274 T::CrossAccountId,275 u128,276 ),277 }278279 #[pallet::error]280 pub enum Error<T> {281 /// This collection does not exist.282 CollectionNotFound,283 /// Sender parameter and item owner must be equal.284 MustBeTokenOwner,285 /// No permission to perform action286 NoPermission,287 /// Collection is not in mint mode.288 PublicMintingNotAllowed,289 /// Address is not in allow list.290 AddressNotInAllowlist,291292 /// Collection name can not be longer than 63 char.293 CollectionNameLimitExceeded,294 /// Collection description can not be longer than 255 char.295 CollectionDescriptionLimitExceeded,296 /// Token prefix can not be longer than 15 char.297 CollectionTokenPrefixLimitExceeded,298 /// Total collections bound exceeded.299 TotalCollectionsLimitExceeded,300 /// variable_data exceeded data limit.301 TokenVariableDataLimitExceeded,302 /// Exceeded max admin count303 CollectionAdminCountExceeded,304 /// Collection limit bounds per collection exceeded305 CollectionLimitBoundsExceeded,306 /// Tried to enable permissions which are only permitted to be disabled307 OwnerPermissionsCantBeReverted,308309 /// Collection settings not allowing items transferring310 TransferNotAllowed,311 /// Account token limit exceeded per collection312 AccountTokenLimitExceeded,313 /// Collection token limit exceeded314 CollectionTokenLimitExceeded,315 /// Metadata flag frozen316 MetadataFlagFrozen,317318 /// Item not exists.319 TokenNotFound,320 /// Item balance not enough.321 TokenValueTooLow,322 /// Requested value more than approved.323 ApprovedValueTooLow,324 /// Tried to approve more than owned325 CantApproveMoreThanOwned,326327 /// Can't transfer tokens to ethereum zero address328 AddressIsZero,329 /// Target collection doesn't supports this operation330 UnsupportedOperation,331332 /// Not sufficient founds to perform action333 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 /// Collection info343 #[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 /// List of collection admins360 #[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 /// Allowlisted collection users371 #[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 /// Not used by code, exists only to provide some types to metadata382 #[pallet::storage]383 pub type DummyStorageValue<T> =384 StorageValue<Value = (CollectionStats, CollectionId, TokenId), QueryKind = OptionQuery>;385}386387impl<T: Config> Pallet<T> {388 /// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens389 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 // bound Total number of collections440 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 // Take a (non-refundable) deposit of collection creation471 {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}638639/// Worst cases640pub 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 /// How many tokens collection contains (Applicable to nonfungible/refungible)723 fn collection_tokens(&self) -> u32;724 /// Amount of different tokens account has (Applicable to nonfungible/refungible)725 fn account_balance(&self, account: T::CrossAccountId) -> u32;726 /// Amount of specific token account have (Applicable to fungible/refungible)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}735736// Flexible enough for implementing CommonCollectionOperations737pub 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}1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![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:172 frame_system::Config + pallet_evm_coder_substrate::Config + TypeInfo + account::Config173 {174 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;175176 type Currency: Currency<Self::AccountId>;177178 #[pallet::constant]179 type CollectionCreationPrice: Get<180 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,181 >;182183 type TreasuryAccountId: Get<Self::AccountId>;184 }185186 #[pallet::pallet]187 #[pallet::generate_store(pub(super) trait Store)]188 pub struct Pallet<T>(_);189190 #[pallet::extra_constants]191 impl<T: Config> Pallet<T> {192 pub fn collection_admins_limit() -> u32 {193 COLLECTION_ADMINS_LIMIT194 }195 }196197 #[pallet::event]198 #[pallet::generate_deposit(pub fn deposit_event)]199 pub enum Event<T: Config> {200 /// New collection was created201 ///202 /// # Arguments203 ///204 /// * collection_id: Globally unique identifier of newly created collection.205 ///206 /// * mode: [CollectionMode] converted into u8.207 ///208 /// * account_id: Collection owner.209 CollectionCreated(CollectionId, u8, T::AccountId),210211 /// New collection was destroyed212 ///213 /// # Arguments214 ///215 /// * collection_id: Globally unique identifier of collection.216 CollectionDestroyed(CollectionId),217218 /// New item was created.219 ///220 /// # Arguments221 ///222 /// * collection_id: Id of the collection where item was created.223 ///224 /// * item_id: Id of an item. Unique within the collection.225 ///226 /// * recipient: Owner of newly created item227 ///228 /// * amount: Always 1 for NFT229 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),230231 /// Collection item was burned.232 ///233 /// # Arguments234 ///235 /// * collection_id.236 ///237 /// * item_id: Identifier of burned NFT.238 ///239 /// * owner: which user has destroyed its tokens240 ///241 /// * amount: Always 1 for NFT242 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),243244 /// Item was transferred245 ///246 /// * collection_id: Id of collection to which item is belong247 ///248 /// * item_id: Id of an item249 ///250 /// * sender: Original owner of item251 ///252 /// * recipient: New owner of item253 ///254 /// * amount: Always 1 for NFT255 Transfer(256 CollectionId,257 TokenId,258 T::CrossAccountId,259 T::CrossAccountId,260 u128,261 ),262263 /// * collection_id264 ///265 /// * item_id266 ///267 /// * sender268 ///269 /// * spender270 ///271 /// * amount272 Approved(273 CollectionId,274 TokenId,275 T::CrossAccountId,276 T::CrossAccountId,277 u128,278 ),279 }280281 #[pallet::error]282 pub enum Error<T> {283 /// This collection does not exist.284 CollectionNotFound,285 /// Sender parameter and item owner must be equal.286 MustBeTokenOwner,287 /// No permission to perform action288 NoPermission,289 /// Collection is not in mint mode.290 PublicMintingNotAllowed,291 /// Address is not in allow list.292 AddressNotInAllowlist,293294 /// Collection name can not be longer than 63 char.295 CollectionNameLimitExceeded,296 /// Collection description can not be longer than 255 char.297 CollectionDescriptionLimitExceeded,298 /// Token prefix can not be longer than 15 char.299 CollectionTokenPrefixLimitExceeded,300 /// Total collections bound exceeded.301 TotalCollectionsLimitExceeded,302 /// variable_data exceeded data limit.303 TokenVariableDataLimitExceeded,304 /// Exceeded max admin count305 CollectionAdminCountExceeded,306 /// Collection limit bounds per collection exceeded307 CollectionLimitBoundsExceeded,308 /// Tried to enable permissions which are only permitted to be disabled309 OwnerPermissionsCantBeReverted,310311 /// Collection settings not allowing items transferring312 TransferNotAllowed,313 /// Account token limit exceeded per collection314 AccountTokenLimitExceeded,315 /// Collection token limit exceeded316 CollectionTokenLimitExceeded,317 /// Metadata flag frozen318 MetadataFlagFrozen,319320 /// Item not exists.321 TokenNotFound,322 /// Item balance not enough.323 TokenValueTooLow,324 /// Requested value more than approved.325 ApprovedValueTooLow,326 /// Tried to approve more than owned327 CantApproveMoreThanOwned,328329 /// Can't transfer tokens to ethereum zero address330 AddressIsZero,331 /// Target collection doesn't supports this operation332 UnsupportedOperation,333334 /// Not sufficient founds to perform action335 NotSufficientFounds,336 }337338 #[pallet::storage]339 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;340 #[pallet::storage]341 pub type DestroyedCollectionCount<T> =342 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;343344 /// Collection info345 #[pallet::storage]346 pub type CollectionById<T> = StorageMap<347 Hasher = Blake2_128Concat,348 Key = CollectionId,349 Value = Collection<<T as frame_system::Config>::AccountId>,350 QueryKind = OptionQuery,351 >;352353 #[pallet::storage]354 pub type AdminAmount<T> = StorageMap<355 Hasher = Blake2_128Concat,356 Key = CollectionId,357 Value = u32,358 QueryKind = ValueQuery,359 >;360361 /// List of collection admins362 #[pallet::storage]363 pub type IsAdmin<T: Config> = StorageNMap<364 Key = (365 Key<Blake2_128Concat, CollectionId>,366 Key<Blake2_128Concat, T::CrossAccountId>,367 ),368 Value = bool,369 QueryKind = ValueQuery,370 >;371372 /// Allowlisted collection users373 #[pallet::storage]374 pub type Allowlist<T: Config> = StorageNMap<375 Key = (376 Key<Blake2_128Concat, CollectionId>,377 Key<Blake2_128Concat, T::CrossAccountId>,378 ),379 Value = bool,380 QueryKind = ValueQuery,381 >;382383 /// Not used by code, exists only to provide some types to metadata384 #[pallet::storage]385 pub type DummyStorageValue<T> =386 StorageValue<Value = (CollectionStats, CollectionId, TokenId), QueryKind = OptionQuery>;387}388389impl<T: Config> Pallet<T> {390 /// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens391 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {392 ensure!(393 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,394 <Error<T>>::AddressIsZero395 );396 Ok(())397 }398 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {399 <IsAdmin<T>>::iter_prefix((collection,))400 .map(|(a, _)| a)401 .collect()402 }403 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {404 <Allowlist<T>>::iter_prefix((collection,))405 .map(|(a, _)| a)406 .collect()407 }408 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {409 <Allowlist<T>>::get((collection, user))410 }411 pub fn collection_stats() -> CollectionStats {412 let created = <CreatedCollectionCount<T>>::get();413 let destroyed = <DestroyedCollectionCount<T>>::get();414 CollectionStats {415 created: created.0,416 destroyed: destroyed.0,417 alive: created.0 - destroyed.0,418 }419 }420}421422impl<T: Config> Pallet<T> {423 pub fn init_collection(424 owner: T::AccountId,425 data: CreateCollectionData<T::AccountId>,426 ) -> Result<CollectionId, DispatchError> {427 {428 ensure!(429 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,430 Error::<T>::CollectionTokenPrefixLimitExceeded431 );432 }433434 let created_count = <CreatedCollectionCount<T>>::get()435 .0436 .checked_add(1)437 .ok_or(ArithmeticError::Overflow)?;438 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;439 let id = CollectionId(created_count);440441 // bound Total number of collections442 ensure!(443 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,444 <Error<T>>::TotalCollectionsLimitExceeded445 );446447 // =========448449 let collection = Collection {450 owner: owner.clone(),451 name: data.name,452 mode: data.mode.clone(),453 mint_mode: false,454 access: data.access.unwrap_or_default(),455 description: data.description,456 token_prefix: data.token_prefix,457 offchain_schema: data.offchain_schema,458 schema_version: data.schema_version.unwrap_or_default(),459 sponsorship: data460 .pending_sponsor461 .map(SponsorshipState::Unconfirmed)462 .unwrap_or_default(),463 variable_on_chain_schema: data.variable_on_chain_schema,464 const_on_chain_schema: data.const_on_chain_schema,465 limits: data466 .limits467 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))468 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,469 meta_update_permission: data.meta_update_permission.unwrap_or_default(),470 };471472 // Take a (non-refundable) deposit of collection creation473 {474 let mut imbalance =475 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();476 imbalance.subsume(477 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(478 &T::TreasuryAccountId::get(),479 T::CollectionCreationPrice::get(),480 ),481 );482 <T as Config>::Currency::settle(483 &owner,484 imbalance,485 WithdrawReasons::TRANSFER,486 ExistenceRequirement::KeepAlive,487 )488 .map_err(|_| Error::<T>::NotSufficientFounds)?;489 }490491 <CreatedCollectionCount<T>>::put(created_count);492 <Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));493 <CollectionById<T>>::insert(id, collection);494 Ok(id)495 }496497 pub fn destroy_collection(498 collection: CollectionHandle<T>,499 sender: &T::CrossAccountId,500 ) -> DispatchResult {501 ensure!(502 collection.limits.owner_can_destroy(),503 <Error<T>>::NoPermission,504 );505 collection.check_is_owner(sender)?;506507 let destroyed_collections = <DestroyedCollectionCount<T>>::get()508 .0509 .checked_add(1)510 .ok_or(ArithmeticError::Overflow)?;511512 // =========513514 <DestroyedCollectionCount<T>>::put(destroyed_collections);515 <CollectionById<T>>::remove(collection.id);516 <AdminAmount<T>>::remove(collection.id);517 <IsAdmin<T>>::remove_prefix((collection.id,), None);518 <Allowlist<T>>::remove_prefix((collection.id,), None);519520 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));521 Ok(())522 }523524 pub fn toggle_allowlist(525 collection: &CollectionHandle<T>,526 sender: &T::CrossAccountId,527 user: &T::CrossAccountId,528 allowed: bool,529 ) -> DispatchResult {530 collection.check_is_owner_or_admin(sender)?;531532 // =========533534 if allowed {535 <Allowlist<T>>::insert((collection.id, user), true);536 } else {537 <Allowlist<T>>::remove((collection.id, user));538 }539540 Ok(())541 }542543 pub fn toggle_admin(544 collection: &CollectionHandle<T>,545 sender: &T::CrossAccountId,546 user: &T::CrossAccountId,547 admin: bool,548 ) -> DispatchResult {549 collection.check_is_owner_or_admin(sender)?;550551 let was_admin = <IsAdmin<T>>::get((collection.id, user));552 if was_admin == admin {553 return Ok(());554 }555 let amount = <AdminAmount<T>>::get(collection.id);556557 if admin {558 let amount = amount559 .checked_add(1)560 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;561 ensure!(562 amount <= Self::collection_admins_limit(),563 <Error<T>>::CollectionAdminCountExceeded,564 );565566 // =========567568 <AdminAmount<T>>::insert(collection.id, amount);569 <IsAdmin<T>>::insert((collection.id, user), true);570 } else {571 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));572 <IsAdmin<T>>::remove((collection.id, user));573 }574575 Ok(())576 }577578 pub fn clamp_limits(579 mode: CollectionMode,580 old_limit: &CollectionLimits,581 mut new_limit: CollectionLimits,582 ) -> Result<CollectionLimits, DispatchError> {583 macro_rules! limit_default {584 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{585 $(586 if let Some($new) = $new.$field {587 let $old = $old.$field($($arg)?);588 let _ = $new;589 let _ = $old;590 $check591 } else {592 $new.$field = $old.$field593 }594 )*595 }};596 }597598 limit_default!(old_limit, new_limit,599 account_token_ownership_limit => ensure!(600 new_limit <= MAX_TOKEN_OWNERSHIP,601 <Error<T>>::CollectionLimitBoundsExceeded,602 ),603 sponsor_transfer_timeout(match mode {604 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,605 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,606 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,607 }) => ensure!(608 new_limit <= MAX_SPONSOR_TIMEOUT,609 <Error<T>>::CollectionLimitBoundsExceeded,610 ),611 sponsored_data_size => ensure!(612 new_limit <= CUSTOM_DATA_LIMIT,613 <Error<T>>::CollectionLimitBoundsExceeded,614 ),615 token_limit => ensure!(616 old_limit >= new_limit && new_limit > 0,617 <Error<T>>::CollectionTokenLimitExceeded618 ),619 owner_can_transfer => ensure!(620 old_limit || !new_limit,621 <Error<T>>::OwnerPermissionsCantBeReverted,622 ),623 owner_can_destroy => ensure!(624 old_limit || !new_limit,625 <Error<T>>::OwnerPermissionsCantBeReverted,626 ),627 sponsored_data_rate_limit => {},628 transfers_enabled => {},629 );630 Ok(new_limit)631 }632}633634#[macro_export]635macro_rules! unsupported {636 () => {637 Err(<Error<T>>::UnsupportedOperation.into())638 };639}640641/// Worst cases642pub trait CommonWeightInfo<CrossAccountId> {643 fn create_item() -> Weight;644 fn create_multiple_items(amount: u32) -> Weight;645 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;646 fn burn_item() -> Weight;647 fn transfer() -> Weight;648 fn approve() -> Weight;649 fn transfer_from() -> Weight;650 fn burn_from() -> Weight;651 fn set_variable_metadata(bytes: u32) -> Weight;652}653654pub trait CommonCollectionOperations<T: Config> {655 fn create_item(656 &self,657 sender: T::CrossAccountId,658 to: T::CrossAccountId,659 data: CreateItemData,660 ) -> DispatchResultWithPostInfo;661 fn create_multiple_items(662 &self,663 sender: T::CrossAccountId,664 to: T::CrossAccountId,665 data: Vec<CreateItemData>,666 ) -> DispatchResultWithPostInfo;667 fn create_multiple_items_ex(668 &self,669 sender: T::CrossAccountId,670 data: CreateItemExData<T::CrossAccountId>,671 ) -> DispatchResultWithPostInfo;672 fn burn_item(673 &self,674 sender: T::CrossAccountId,675 token: TokenId,676 amount: u128,677 ) -> DispatchResultWithPostInfo;678679 fn transfer(680 &self,681 sender: T::CrossAccountId,682 to: T::CrossAccountId,683 token: TokenId,684 amount: u128,685 ) -> DispatchResultWithPostInfo;686 fn approve(687 &self,688 sender: T::CrossAccountId,689 spender: T::CrossAccountId,690 token: TokenId,691 amount: u128,692 ) -> DispatchResultWithPostInfo;693 fn transfer_from(694 &self,695 sender: T::CrossAccountId,696 from: T::CrossAccountId,697 to: T::CrossAccountId,698 token: TokenId,699 amount: u128,700 ) -> DispatchResultWithPostInfo;701 fn burn_from(702 &self,703 sender: T::CrossAccountId,704 from: T::CrossAccountId,705 token: TokenId,706 amount: u128,707 ) -> DispatchResultWithPostInfo;708709 fn set_variable_metadata(710 &self,711 sender: T::CrossAccountId,712 token: TokenId,713 data: BoundedVec<u8, CustomDataLimit>,714 ) -> DispatchResultWithPostInfo;715716 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;717 fn token_exists(&self, token: TokenId) -> bool;718 fn last_token_id(&self) -> TokenId;719720 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;721 fn const_metadata(&self, token: TokenId) -> Vec<u8>;722 fn variable_metadata(&self, token: TokenId) -> Vec<u8>;723724 /// How many tokens collection contains (Applicable to nonfungible/refungible)725 fn collection_tokens(&self) -> u32;726 /// Amount of different tokens account has (Applicable to nonfungible/refungible)727 fn account_balance(&self, account: T::CrossAccountId) -> u32;728 /// Amount of specific token account have (Applicable to fungible/refungible)729 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;730 fn allowance(731 &self,732 sender: T::CrossAccountId,733 spender: T::CrossAccountId,734 token: TokenId,735 ) -> u128;736}737738// Flexible enough for implementing CommonCollectionOperations739pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {740 let post_info = PostDispatchInfo {741 actual_weight: Some(weight),742 pays_fee: Pays::Yes,743 };744 match res {745 Ok(()) => Ok(post_info),746 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),747 }748}pallets/evm-contract-helpers/src/eth.rsdiffbeforeafterboth--- a/pallets/evm-contract-helpers/src/eth.rs
+++ b/pallets/evm-contract-helpers/src/eth.rs
@@ -18,7 +18,8 @@
use evm_coder::{abi::AbiWriter, execution::Result, generate_stubgen, solidity_interface, types::*};
use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};
use pallet_evm::{
- ExitRevert, OnCreate, OnMethodCall, PrecompileResult, PrecompileFailure, account::CrossAccountId
+ ExitRevert, OnCreate, OnMethodCall, PrecompileResult, PrecompileFailure,
+ account::CrossAccountId,
};
use sp_core::H160;
use crate::{
@@ -179,7 +180,9 @@
}
pub struct HelpersContractSponsoring<T: Config>(PhantomData<*const T>);
-impl<T: Config> SponsorshipHandler<T::CrossAccountId, (H160, Vec<u8>)> for HelpersContractSponsoring<T> {
+impl<T: Config> SponsorshipHandler<T::CrossAccountId, (H160, Vec<u8>)>
+ for HelpersContractSponsoring<T>
+{
fn get_sponsor(who: &T::CrossAccountId, call: &(H160, Vec<u8>)) -> Option<T::CrossAccountId> {
let mode = <Pallet<T>>::sponsoring_mode(call.0);
if mode == SponsoringModeT::Disabled {
pallets/evm-contract-helpers/src/lib.rsdiffbeforeafterboth--- a/pallets/evm-contract-helpers/src/lib.rs
+++ b/pallets/evm-contract-helpers/src/lib.rs
@@ -30,7 +30,9 @@
use sp_core::H160;
#[pallet::config]
- pub trait Config: frame_system::Config + pallet_evm_coder_substrate::Config + pallet_evm::account::Config {
+ pub trait Config:
+ frame_system::Config + pallet_evm_coder_substrate::Config + pallet_evm::account::Config
+ {
type ContractAddress: Get<H160>;
type DefaultSponsoringRateLimit: Get<Self::BlockNumber>;
}
pallets/evm-transaction-payment/src/lib.rsdiffbeforeafterboth--- a/pallets/evm-transaction-payment/src/lib.rs
+++ b/pallets/evm-transaction-payment/src/lib.rs
@@ -87,7 +87,8 @@
fee: U256,
) -> core::result::Result<Self::LiquidityInfo, pallet_evm::Error<T>> {
let who_pays_fee = if let WithdrawReason::Call { target, input } = &reason {
- T::EvmSponsorshipHandler::get_sponsor(who, &(*target, input.clone())).unwrap_or(who.clone())
+ T::EvmSponsorshipHandler::get_sponsor(who, &(*target, input.clone()))
+ .unwrap_or(who.clone())
} else {
who.clone()
};
pallets/fungible/src/lib.rsdiffbeforeafterboth--- a/pallets/fungible/src/lib.rs
+++ b/pallets/fungible/src/lib.rs
@@ -19,9 +19,7 @@
use core::ops::Deref;
use frame_support::{ensure};
use up_data_structs::{AccessMode, CollectionId, TokenId, CreateCollectionData};
-use pallet_common::{
- Error as CommonError, Event as CommonEvent, Pallet as PalletCommon,
-};
+use pallet_common::{Error as CommonError, Event as CommonEvent, Pallet as PalletCommon};
use pallet_evm::account::CrossAccountId;
use pallet_evm_coder_substrate::WithRecorder;
use sp_core::H160;
pallets/nonfungible/src/lib.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -21,9 +21,7 @@
use up_data_structs::{
AccessMode, CollectionId, CustomDataLimit, TokenId, CreateCollectionData, CreateNftExData,
};
-use pallet_common::{
- Error as CommonError, Pallet as PalletCommon, Event as CommonEvent,
-};
+use pallet_common::{Error as CommonError, Pallet as PalletCommon, Event as CommonEvent};
use pallet_evm::account::CrossAccountId;
use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};
use sp_core::H160;
pallets/refungible/src/lib.rsdiffbeforeafterboth--- a/pallets/refungible/src/lib.rs
+++ b/pallets/refungible/src/lib.rs
@@ -21,9 +21,7 @@
AccessMode, CollectionId, CustomDataLimit, MAX_REFUNGIBLE_PIECES, TokenId,
CreateCollectionData, CreateRefungibleExData,
};
-use pallet_common::{
- Error as CommonError, Event as CommonEvent, Pallet as PalletCommon,
-};
+use pallet_common::{Error as CommonError, Event as CommonEvent, Pallet as PalletCommon};
use pallet_evm::account::CrossAccountId;
use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};
use sp_std::{vec::Vec, vec, collections::btree_map::BTreeMap};
pallets/unique/src/eth/sponsoring.rsdiffbeforeafterboth--- a/pallets/unique/src/eth/sponsoring.rs
+++ b/pallets/unique/src/eth/sponsoring.rs
@@ -32,7 +32,9 @@
use up_data_structs::{CreateItemData, CreateNftData};
pub struct UniqueEthSponsorshipHandler<T: Config>(PhantomData<*const T>);
-impl<T: Config> SponsorshipHandler<T::CrossAccountId, (H160, Vec<u8>)> for UniqueEthSponsorshipHandler<T> {
+impl<T: Config> SponsorshipHandler<T::CrossAccountId, (H160, Vec<u8>)>
+ for UniqueEthSponsorshipHandler<T>
+{
fn get_sponsor(who: &T::CrossAccountId, call: &(H160, Vec<u8>)) -> Option<T::CrossAccountId> {
let collection_id = map_eth_to_id(&call.0)?;
let collection = <CollectionHandle<T>>::new(collection_id)?;
@@ -59,16 +61,17 @@
.map(|()| sponsor)
}
UniqueNFTCall::ERC721Mintable(call) => match call {
- pallet_nonfungible::erc::ERC721MintableCall::Mint { .. } |
- pallet_nonfungible::erc::ERC721MintableCall::MintWithTokenUri { .. } => {
- withdraw_create_item(
- &collection,
- who.as_sub(),
- &CreateItemData::NFT(CreateNftData::default()))
- .map(|()| sponsor)
- }
+ pallet_nonfungible::erc::ERC721MintableCall::Mint { .. }
+ | pallet_nonfungible::erc::ERC721MintableCall::MintWithTokenUri {
+ ..
+ } => withdraw_create_item(
+ &collection,
+ who.as_sub(),
+ &CreateItemData::NFT(CreateNftData::default()),
+ )
+ .map(|()| sponsor),
_ => None,
- }
+ },
_ => None,
}
}
pallets/unique/src/lib.rsdiffbeforeafterboth--- a/pallets/unique/src/lib.rs
+++ b/pallets/unique/src/lib.rs
@@ -53,10 +53,7 @@
CollectionMode, TokenId, SchemaVersion, SponsorshipState, MetaUpdatePermission,
CreateCollectionData, CustomDataLimit, CreateItemExData,
};
-use pallet_common::{
- CollectionHandle, Pallet as PalletCommon, Error as CommonError,
- CommonWeightInfo,
-};
+use pallet_common::{CollectionHandle, Pallet as PalletCommon, Error as CommonError, CommonWeightInfo};
use pallet_evm::account::CrossAccountId;
use pallet_refungible::{Pallet as PalletRefungible, RefungibleHandle};
use pallet_fungible::{Pallet as PalletFungible, FungibleHandle};
pallets/unique/src/tests.rsdiffbeforeafterboth--- a/pallets/unique/src/tests.rs
+++ b/pallets/unique/src/tests.rs
@@ -2865,18 +2865,49 @@
let origin1 = Origin::signed(user1);
let origin2 = Origin::signed(user2);
let account2 = account(user2);
-
- let collection_id = create_test_collection_for_owner(&CollectionMode::NFT, user1, CollectionId(1));
- assert_ok!(TemplateModule::set_collection_sponsor(origin1.clone(), collection_id, user1));
- assert_ok!(TemplateModule::confirm_sponsorship(origin1.clone(), collection_id));
+ let collection_id =
+ create_test_collection_for_owner(&CollectionMode::NFT, user1, CollectionId(1));
+ assert_ok!(TemplateModule::set_collection_sponsor(
+ origin1.clone(),
+ collection_id,
+ user1
+ ));
+ assert_ok!(TemplateModule::confirm_sponsorship(
+ origin1.clone(),
+ collection_id
+ ));
+
// Expect error while have no permissions
- assert!(TemplateModule::create_item(origin2.clone(), collection_id, account2.clone(), default_nft_data().into()).is_err());
+ assert!(TemplateModule::create_item(
+ origin2.clone(),
+ collection_id,
+ account2.clone(),
+ default_nft_data().into()
+ )
+ .is_err());
- assert_ok!(TemplateModule::set_public_access_mode(origin1.clone(), collection_id, AccessMode::AllowList));
- assert_ok!(TemplateModule::add_to_allow_list(origin1.clone(), collection_id, account2.clone()));
- assert_ok!(TemplateModule::set_mint_permission(origin1.clone(), collection_id, true));
-
- assert_ok!(TemplateModule::create_item(origin2, collection_id, account2, default_nft_data().into()));
+ assert_ok!(TemplateModule::set_public_access_mode(
+ origin1.clone(),
+ collection_id,
+ AccessMode::AllowList
+ ));
+ assert_ok!(TemplateModule::add_to_allow_list(
+ origin1.clone(),
+ collection_id,
+ account2.clone()
+ ));
+ assert_ok!(TemplateModule::set_mint_permission(
+ origin1.clone(),
+ collection_id,
+ true
+ ));
+
+ assert_ok!(TemplateModule::create_item(
+ origin2,
+ collection_id,
+ account2,
+ default_nft_data().into()
+ ));
});
-}
\ No newline at end of file
+}