difftreelog
feat allow more fields to be set on collection creation
in: master
7 files changed
pallets/common/src/lib.rsdiffbeforeafterboth1#![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};12use pallet_evm::GasWeightMapping;13use up_data_structs::{14 COLLECTION_NUMBER_LIMIT, Collection, CollectionId, CreateItemData, ExistenceRequirement,15 MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_COLLECTION_NAME_LENGTH, MAX_TOKEN_PREFIX_LENGTH,16 COLLECTION_ADMINS_LIMIT, MetaUpdatePermission, Pays, PostDispatchInfo, TokenId, Weight,17 WithdrawReasons, CollectionStats,18};19pub use pallet::*;20use sp_core::H160;21use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};22pub mod account;23#[cfg(feature = "runtime-benchmarks")]24pub mod benchmarking;25pub mod erc;26pub mod eth;2728#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]29pub struct CollectionHandle<T: Config> {30 pub id: CollectionId,31 collection: Collection<T::AccountId>,32 pub recorder: SubstrateRecorder<T>,33}34impl<T: Config> WithRecorder<T> for CollectionHandle<T> {35 fn recorder(&self) -> &SubstrateRecorder<T> {36 &self.recorder37 }38 fn into_recorder(self) -> SubstrateRecorder<T> {39 self.recorder40 }41}42impl<T: Config> CollectionHandle<T> {43 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {44 <CollectionById<T>>::get(id).map(|collection| Self {45 id,46 collection,47 recorder: SubstrateRecorder::new(eth::collection_id_to_address(id), gas_limit),48 })49 }50 pub fn new(id: CollectionId) -> Option<Self> {51 Self::new_with_gas_limit(id, u64::MAX)52 }53 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {54 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)55 }56 pub fn log(&self, log: impl evm_coder::ToLog) {57 self.recorder.log(log)58 }59 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {60 self.recorder61 .consume_gas(T::GasWeightMapping::weight_to_gas(62 <T as frame_system::Config>::DbWeight::get()63 .read64 .saturating_mul(reads),65 ))66 }67 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {68 self.recorder69 .consume_gas(T::GasWeightMapping::weight_to_gas(70 <T as frame_system::Config>::DbWeight::get()71 .write72 .saturating_mul(writes),73 ))74 }75 pub fn submit_logs(self) {76 self.recorder.submit_logs()77 }78 pub fn save(self) -> DispatchResult {79 self.recorder.submit_logs();80 <CollectionById<T>>::insert(self.id, self.collection);81 Ok(())82 }83}84impl<T: Config> Deref for CollectionHandle<T> {85 type Target = Collection<T::AccountId>;8687 fn deref(&self) -> &Self::Target {88 &self.collection89 }90}9192impl<T: Config> DerefMut for CollectionHandle<T> {93 fn deref_mut(&mut self) -> &mut Self::Target {94 &mut self.collection95 }96}9798impl<T: Config> CollectionHandle<T> {99 pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {100 ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);101 Ok(())102 }103 pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {104 *subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))105 }106 pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {107 ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);108 Ok(())109 }110 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {111 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)112 }113 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {114 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)115 }116 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {117 ensure!(118 <Allowlist<T>>::get((self.id, user)),119 <Error<T>>::AddressNotInAllowlist120 );121 Ok(())122 }123124 pub fn check_can_update_meta(125 &self,126 subject: &T::CrossAccountId,127 item_owner: &T::CrossAccountId,128 ) -> DispatchResult {129 match self.meta_update_permission {130 MetaUpdatePermission::ItemOwner => {131 ensure!(subject == item_owner, <Error<T>>::NoPermission);132 Ok(())133 }134 MetaUpdatePermission::Admin => self.check_is_owner_or_admin(subject),135 MetaUpdatePermission::None => fail!(<Error<T>>::NoPermission),136 }137 }138}139140#[frame_support::pallet]141pub mod pallet {142 use super::*;143 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key};144 use account::CrossAccountId;145 use frame_support::traits::Currency;146 use up_data_structs::TokenId;147 use scale_info::TypeInfo;148149 #[pallet::config]150 pub trait Config: frame_system::Config + pallet_evm_coder_substrate::Config + TypeInfo {151 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;152153 type CrossAccountId: CrossAccountId<Self::AccountId>;154155 type EvmAddressMapping: pallet_evm::AddressMapping<Self::AccountId>;156 type EvmBackwardsAddressMapping: up_evm_mapping::EvmBackwardsAddressMapping<Self::AccountId>;157158 type Currency: Currency<Self::AccountId>;159 type CollectionCreationPrice: Get<160 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,161 >;162 type TreasuryAccountId: Get<Self::AccountId>;163 }164165 #[pallet::pallet]166 #[pallet::generate_store(pub(super) trait Store)]167 pub struct Pallet<T>(_);168169 #[pallet::extra_constants]170 impl<T: Config> Pallet<T> {171 pub fn collection_admins_limit() -> u32 {172 COLLECTION_ADMINS_LIMIT173 }174 }175176 #[pallet::event]177 #[pallet::generate_deposit(pub fn deposit_event)]178 pub enum Event<T: Config> {179 /// New collection was created180 ///181 /// # Arguments182 ///183 /// * collection_id: Globally unique identifier of newly created collection.184 ///185 /// * mode: [CollectionMode] converted into u8.186 ///187 /// * account_id: Collection owner.188 CollectionCreated(CollectionId, u8, T::AccountId),189190 /// New collection was destroyed191 ///192 /// # Arguments193 ///194 /// * collection_id: Globally unique identifier of collection.195 CollectionDestroyed(CollectionId),196197 /// New item was created.198 ///199 /// # Arguments200 ///201 /// * collection_id: Id of the collection where item was created.202 ///203 /// * item_id: Id of an item. Unique within the collection.204 ///205 /// * recipient: Owner of newly created item206 ///207 /// * amount: Always 1 for NFT208 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),209210 /// Collection item was burned.211 ///212 /// # Arguments213 ///214 /// * collection_id.215 ///216 /// * item_id: Identifier of burned NFT.217 ///218 /// * owner: which user has destroyed its tokens219 ///220 /// * amount: Always 1 for NFT221 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),222223 /// Item was transferred224 ///225 /// * collection_id: Id of collection to which item is belong226 ///227 /// * item_id: Id of an item228 ///229 /// * sender: Original owner of item230 ///231 /// * recipient: New owner of item232 ///233 /// * amount: Always 1 for NFT234 Transfer(235 CollectionId,236 TokenId,237 T::CrossAccountId,238 T::CrossAccountId,239 u128,240 ),241242 /// * collection_id243 ///244 /// * item_id245 ///246 /// * sender247 ///248 /// * spender249 ///250 /// * amount251 Approved(252 CollectionId,253 TokenId,254 T::CrossAccountId,255 T::CrossAccountId,256 u128,257 ),258 }259260 #[pallet::error]261 pub enum Error<T> {262 /// This collection does not exist.263 CollectionNotFound,264 /// Sender parameter and item owner must be equal.265 MustBeTokenOwner,266 /// No permission to perform action267 NoPermission,268 /// Collection is not in mint mode.269 PublicMintingNotAllowed,270 /// Address is not in allow list.271 AddressNotInAllowlist,272273 /// Collection name can not be longer than 63 char.274 CollectionNameLimitExceeded,275 /// Collection description can not be longer than 255 char.276 CollectionDescriptionLimitExceeded,277 /// Token prefix can not be longer than 15 char.278 CollectionTokenPrefixLimitExceeded,279 /// Total collections bound exceeded.280 TotalCollectionsLimitExceeded,281 /// variable_data exceeded data limit.282 TokenVariableDataLimitExceeded,283 /// Exceeded max admin count284 CollectionAdminCountExceeded,285286 /// Collection settings not allowing items transferring287 TransferNotAllowed,288 /// Account token limit exceeded per collection289 AccountTokenLimitExceeded,290 /// Collection token limit exceeded291 CollectionTokenLimitExceeded,292 /// Metadata flag frozen293 MetadataFlagFrozen,294295 /// Item not exists.296 TokenNotFound,297 /// Item balance not enough.298 TokenValueTooLow,299 /// Requested value more than approved.300 TokenValueNotEnough,301 /// Tried to approve more than owned302 CantApproveMoreThanOwned,303304 /// Can't transfer tokens to ethereum zero address305 AddressIsZero,306 /// Target collection doesn't supports this operation307 UnsupportedOperation,308 }309310 #[pallet::storage]311 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;312 #[pallet::storage]313 pub type DestroyedCollectionCount<T> =314 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;315316 /// Collection info317 #[pallet::storage]318 pub type CollectionById<T> = StorageMap<319 Hasher = Blake2_128Concat,320 Key = CollectionId,321 Value = Collection<<T as frame_system::Config>::AccountId>,322 QueryKind = OptionQuery,323 >;324325 #[pallet::storage]326 pub type AdminAmount<T> = StorageMap<327 Hasher = Blake2_128Concat,328 Key = CollectionId,329 Value = u32,330 QueryKind = ValueQuery,331 >;332333 /// List of collection admins334 #[pallet::storage]335 pub type IsAdmin<T: Config> = StorageNMap<336 Key = (337 Key<Blake2_128Concat, CollectionId>,338 Key<Blake2_128Concat, T::CrossAccountId>,339 ),340 Value = bool,341 QueryKind = ValueQuery,342 >;343344 /// Allowlisted collection users345 #[pallet::storage]346 pub type Allowlist<T: Config> = StorageNMap<347 Key = (348 Key<Blake2_128Concat, CollectionId>,349 Key<Blake2_128Concat, T::CrossAccountId>,350 ),351 Value = bool,352 QueryKind = ValueQuery,353 >;354355 /// Not used by code, exists only to provide some types to metadata356 #[pallet::storage]357 pub type DummyStorageValue<T> =358 StorageValue<Value = (CollectionStats, CollectionId, TokenId), QueryKind = OptionQuery>;359}360361impl<T: Config> Pallet<T> {362 /// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens363 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {364 ensure!(365 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,366 <Error<T>>::AddressIsZero367 );368 Ok(())369 }370 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {371 <IsAdmin<T>>::iter_prefix((collection,))372 .map(|(a, _)| a)373 .collect()374 }375 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {376 <Allowlist<T>>::iter_prefix((collection,))377 .map(|(a, _)| a)378 .collect()379 }380 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {381 <Allowlist<T>>::get((collection, user))382 }383 pub fn collection_stats() -> CollectionStats {384 let created = <CreatedCollectionCount<T>>::get();385 let destroyed = <DestroyedCollectionCount<T>>::get();386 CollectionStats {387 created: created.0,388 destroyed: destroyed.0,389 alive: created.0 - destroyed.0,390 }391 }392}393394impl<T: Config> Pallet<T> {395 pub fn init_collection(data: Collection<T::AccountId>) -> Result<CollectionId, DispatchError> {396 {397 ensure!(398 data.name.len() <= MAX_COLLECTION_NAME_LENGTH,399 Error::<T>::CollectionNameLimitExceeded400 );401 ensure!(402 data.description.len() <= MAX_COLLECTION_DESCRIPTION_LENGTH,403 Error::<T>::CollectionDescriptionLimitExceeded404 );405 ensure!(406 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH,407 Error::<T>::CollectionTokenPrefixLimitExceeded408 );409 }410411 let created_count = <CreatedCollectionCount<T>>::get()412 .0413 .checked_add(1)414 .ok_or(ArithmeticError::Overflow)?;415 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;416 let id = CollectionId(created_count);417418 // bound Total number of collections419 ensure!(420 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,421 <Error<T>>::TotalCollectionsLimitExceeded422 );423424 // =========425426 // Take a (non-refundable) deposit of collection creation427 {428 let mut imbalance =429 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();430 imbalance.subsume(431 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(432 &T::TreasuryAccountId::get(),433 T::CollectionCreationPrice::get(),434 ),435 );436 <T as Config>::Currency::settle(437 &data.owner,438 imbalance,439 WithdrawReasons::TRANSFER,440 ExistenceRequirement::KeepAlive,441 )442 .map_err(|_| Error::<T>::NoPermission)?;443 }444445 <CreatedCollectionCount<T>>::put(created_count);446 <Pallet<T>>::deposit_event(Event::CollectionCreated(447 id,448 data.mode.id(),449 data.owner.clone(),450 ));451 <CollectionById<T>>::insert(id, data);452 Ok(id)453 }454455 pub fn destroy_collection(456 collection: CollectionHandle<T>,457 sender: &T::CrossAccountId,458 ) -> DispatchResult {459 ensure!(460 collection.limits.owner_can_destroy(),461 <Error<T>>::NoPermission,462 );463 collection.check_is_owner(sender)?;464465 let destroyed_collections = <DestroyedCollectionCount<T>>::get()466 .0467 .checked_add(1)468 .ok_or(ArithmeticError::Overflow)?;469470 // =========471472 <DestroyedCollectionCount<T>>::put(destroyed_collections);473 <CollectionById<T>>::remove(collection.id);474 <AdminAmount<T>>::remove(collection.id);475 <IsAdmin<T>>::remove_prefix((collection.id,), None);476 <Allowlist<T>>::remove_prefix((collection.id,), None);477478 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));479 Ok(())480 }481482 pub fn toggle_allowlist(483 collection: &CollectionHandle<T>,484 sender: &T::CrossAccountId,485 user: &T::CrossAccountId,486 allowed: bool,487 ) -> DispatchResult {488 collection.check_is_owner_or_admin(sender)?;489490 // =========491492 if allowed {493 <Allowlist<T>>::insert((collection.id, user), true);494 } else {495 <Allowlist<T>>::remove((collection.id, user));496 }497498 Ok(())499 }500501 pub fn toggle_admin(502 collection: &CollectionHandle<T>,503 sender: &T::CrossAccountId,504 user: &T::CrossAccountId,505 admin: bool,506 ) -> DispatchResult {507 collection.check_is_owner_or_admin(sender)?;508509 let was_admin = <IsAdmin<T>>::get((collection.id, user));510 if was_admin == admin {511 return Ok(());512 }513 let amount = <AdminAmount<T>>::get(collection.id);514515 if admin {516 let amount = amount517 .checked_add(1)518 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;519 ensure!(520 amount <= Self::collection_admins_limit(),521 <Error<T>>::CollectionAdminCountExceeded,522 );523524 // =========525526 <AdminAmount<T>>::insert(collection.id, amount);527 <IsAdmin<T>>::insert((collection.id, user), true);528 } else {529 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));530 <IsAdmin<T>>::remove((collection.id, user));531 }532533 Ok(())534 }535}536537#[macro_export]538macro_rules! unsupported {539 () => {540 Err(<Error<T>>::UnsupportedOperation.into())541 };542}543544/// Worst cases545pub trait CommonWeightInfo {546 fn create_item() -> Weight;547 fn create_multiple_items(amount: u32) -> Weight;548 fn burn_item() -> Weight;549 fn transfer() -> Weight;550 fn approve() -> Weight;551 fn transfer_from() -> Weight;552 fn burn_from() -> Weight;553 fn set_variable_metadata(bytes: u32) -> Weight;554}555556pub trait CommonCollectionOperations<T: Config> {557 fn create_item(558 &self,559 sender: T::CrossAccountId,560 to: T::CrossAccountId,561 data: CreateItemData,562 ) -> DispatchResultWithPostInfo;563 fn create_multiple_items(564 &self,565 sender: T::CrossAccountId,566 to: T::CrossAccountId,567 data: Vec<CreateItemData>,568 ) -> DispatchResultWithPostInfo;569 fn burn_item(570 &self,571 sender: T::CrossAccountId,572 token: TokenId,573 amount: u128,574 ) -> DispatchResultWithPostInfo;575576 fn transfer(577 &self,578 sender: T::CrossAccountId,579 to: T::CrossAccountId,580 token: TokenId,581 amount: u128,582 ) -> DispatchResultWithPostInfo;583 fn approve(584 &self,585 sender: T::CrossAccountId,586 spender: T::CrossAccountId,587 token: TokenId,588 amount: u128,589 ) -> DispatchResultWithPostInfo;590 fn transfer_from(591 &self,592 sender: T::CrossAccountId,593 from: T::CrossAccountId,594 to: T::CrossAccountId,595 token: TokenId,596 amount: u128,597 ) -> DispatchResultWithPostInfo;598 fn burn_from(599 &self,600 sender: T::CrossAccountId,601 from: T::CrossAccountId,602 token: TokenId,603 amount: u128,604 ) -> DispatchResultWithPostInfo;605606 fn set_variable_metadata(607 &self,608 sender: T::CrossAccountId,609 token: TokenId,610 data: Vec<u8>,611 ) -> DispatchResultWithPostInfo;612613 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;614 fn token_exists(&self, token: TokenId) -> bool;615 fn last_token_id(&self) -> TokenId;616617 fn token_owner(&self, token: TokenId) -> T::CrossAccountId;618 fn const_metadata(&self, token: TokenId) -> Vec<u8>;619 fn variable_metadata(&self, token: TokenId) -> Vec<u8>;620621 /// How many tokens collection contains (Applicable to nonfungible/refungible)622 fn collection_tokens(&self) -> u32;623 /// Amount of different tokens account has (Applicable to nonfungible/refungible)624 fn account_balance(&self, account: T::CrossAccountId) -> u32;625 /// Amount of specific token account have (Applicable to fungible/refungible)626 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;627 fn allowance(628 &self,629 sender: T::CrossAccountId,630 spender: T::CrossAccountId,631 token: TokenId,632 ) -> u128;633}634635// Flexible enough for implementing CommonCollectionOperations636pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {637 let post_info = PostDispatchInfo {638 actual_weight: Some(weight),639 pays_fee: Pays::Yes,640 };641 match res {642 Ok(()) => Ok(post_info),643 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),644 }645}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};12use pallet_evm::GasWeightMapping;13use up_data_structs::{14 COLLECTION_NUMBER_LIMIT, Collection, CollectionId, CreateItemData, ExistenceRequirement,15 MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_COLLECTION_NAME_LENGTH, MAX_TOKEN_PREFIX_LENGTH,16 COLLECTION_ADMINS_LIMIT, MetaUpdatePermission, Pays, PostDispatchInfo, TokenId, Weight,17 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 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(&self, log: impl evm_coder::ToLog) {60 self.recorder.log(log)61 }62 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {63 self.recorder64 .consume_gas(T::GasWeightMapping::weight_to_gas(65 <T as frame_system::Config>::DbWeight::get()66 .read67 .saturating_mul(reads),68 ))69 }70 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {71 self.recorder72 .consume_gas(T::GasWeightMapping::weight_to_gas(73 <T as frame_system::Config>::DbWeight::get()74 .write75 .saturating_mul(writes),76 ))77 }78 pub fn submit_logs(self) {79 self.recorder.submit_logs()80 }81 pub fn save(self) -> DispatchResult {82 self.recorder.submit_logs();83 <CollectionById<T>>::insert(self.id, self.collection);84 Ok(())85 }86}87impl<T: Config> Deref for CollectionHandle<T> {88 type Target = Collection<T::AccountId>;8990 fn deref(&self) -> &Self::Target {91 &self.collection92 }93}9495impl<T: Config> DerefMut for CollectionHandle<T> {96 fn deref_mut(&mut self) -> &mut Self::Target {97 &mut self.collection98 }99}100101impl<T: Config> CollectionHandle<T> {102 pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {103 ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);104 Ok(())105 }106 pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {107 *subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))108 }109 pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {110 ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);111 Ok(())112 }113 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {114 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)115 }116 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {117 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)118 }119 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {120 ensure!(121 <Allowlist<T>>::get((self.id, user)),122 <Error<T>>::AddressNotInAllowlist123 );124 Ok(())125 }126127 pub fn check_can_update_meta(128 &self,129 subject: &T::CrossAccountId,130 item_owner: &T::CrossAccountId,131 ) -> DispatchResult {132 match self.meta_update_permission {133 MetaUpdatePermission::ItemOwner => {134 ensure!(subject == item_owner, <Error<T>>::NoPermission);135 Ok(())136 }137 MetaUpdatePermission::Admin => self.check_is_owner_or_admin(subject),138 MetaUpdatePermission::None => fail!(<Error<T>>::NoPermission),139 }140 }141}142143#[frame_support::pallet]144pub mod pallet {145 use super::*;146 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key};147 use account::CrossAccountId;148 use frame_support::traits::Currency;149 use up_data_structs::TokenId;150 use scale_info::TypeInfo;151152 #[pallet::config]153 pub trait Config: frame_system::Config + pallet_evm_coder_substrate::Config + TypeInfo {154 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;155156 type CrossAccountId: CrossAccountId<Self::AccountId>;157158 type EvmAddressMapping: pallet_evm::AddressMapping<Self::AccountId>;159 type EvmBackwardsAddressMapping: up_evm_mapping::EvmBackwardsAddressMapping<Self::AccountId>;160161 type Currency: Currency<Self::AccountId>;162 type CollectionCreationPrice: Get<163 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,164 >;165 type TreasuryAccountId: Get<Self::AccountId>;166 }167168 #[pallet::pallet]169 #[pallet::generate_store(pub(super) trait Store)]170 pub struct Pallet<T>(_);171172 #[pallet::extra_constants]173 impl<T: Config> Pallet<T> {174 pub fn collection_admins_limit() -> u32 {175 COLLECTION_ADMINS_LIMIT176 }177 }178179 #[pallet::event]180 #[pallet::generate_deposit(pub fn deposit_event)]181 pub enum Event<T: Config> {182 /// New collection was created183 ///184 /// # Arguments185 ///186 /// * collection_id: Globally unique identifier of newly created collection.187 ///188 /// * mode: [CollectionMode] converted into u8.189 ///190 /// * account_id: Collection owner.191 CollectionCreated(CollectionId, u8, T::AccountId),192193 /// New collection was destroyed194 ///195 /// # Arguments196 ///197 /// * collection_id: Globally unique identifier of collection.198 CollectionDestroyed(CollectionId),199200 /// New item was created.201 ///202 /// # Arguments203 ///204 /// * collection_id: Id of the collection where item was created.205 ///206 /// * item_id: Id of an item. Unique within the collection.207 ///208 /// * recipient: Owner of newly created item209 ///210 /// * amount: Always 1 for NFT211 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),212213 /// Collection item was burned.214 ///215 /// # Arguments216 ///217 /// * collection_id.218 ///219 /// * item_id: Identifier of burned NFT.220 ///221 /// * owner: which user has destroyed its tokens222 ///223 /// * amount: Always 1 for NFT224 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),225226 /// Item was transferred227 ///228 /// * collection_id: Id of collection to which item is belong229 ///230 /// * item_id: Id of an item231 ///232 /// * sender: Original owner of item233 ///234 /// * recipient: New owner of item235 ///236 /// * amount: Always 1 for NFT237 Transfer(238 CollectionId,239 TokenId,240 T::CrossAccountId,241 T::CrossAccountId,242 u128,243 ),244245 /// * collection_id246 ///247 /// * item_id248 ///249 /// * sender250 ///251 /// * spender252 ///253 /// * amount254 Approved(255 CollectionId,256 TokenId,257 T::CrossAccountId,258 T::CrossAccountId,259 u128,260 ),261 }262263 #[pallet::error]264 pub enum Error<T> {265 /// This collection does not exist.266 CollectionNotFound,267 /// Sender parameter and item owner must be equal.268 MustBeTokenOwner,269 /// No permission to perform action270 NoPermission,271 /// Collection is not in mint mode.272 PublicMintingNotAllowed,273 /// Address is not in allow list.274 AddressNotInAllowlist,275276 /// Collection name can not be longer than 63 char.277 CollectionNameLimitExceeded,278 /// Collection description can not be longer than 255 char.279 CollectionDescriptionLimitExceeded,280 /// Token prefix can not be longer than 15 char.281 CollectionTokenPrefixLimitExceeded,282 /// Total collections bound exceeded.283 TotalCollectionsLimitExceeded,284 /// variable_data exceeded data limit.285 TokenVariableDataLimitExceeded,286 /// Exceeded max admin count287 CollectionAdminCountExceeded,288 /// Collection limit bounds per collection exceeded289 CollectionLimitBoundsExceeded,290 /// Tried to enable permissions which are only permitted to be disabled291 OwnerPermissionsCantBeReverted,292293 /// Collection settings not allowing items transferring294 TransferNotAllowed,295 /// Account token limit exceeded per collection296 AccountTokenLimitExceeded,297 /// Collection token limit exceeded298 CollectionTokenLimitExceeded,299 /// Metadata flag frozen300 MetadataFlagFrozen,301302 /// Item not exists.303 TokenNotFound,304 /// Item balance not enough.305 TokenValueTooLow,306 /// Requested value more than approved.307 TokenValueNotEnough,308 /// Tried to approve more than owned309 CantApproveMoreThanOwned,310311 /// Can't transfer tokens to ethereum zero address312 AddressIsZero,313 /// Target collection doesn't supports this operation314 UnsupportedOperation,315 }316317 #[pallet::storage]318 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;319 #[pallet::storage]320 pub type DestroyedCollectionCount<T> =321 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;322323 /// Collection info324 #[pallet::storage]325 pub type CollectionById<T> = StorageMap<326 Hasher = Blake2_128Concat,327 Key = CollectionId,328 Value = Collection<<T as frame_system::Config>::AccountId>,329 QueryKind = OptionQuery,330 >;331332 #[pallet::storage]333 pub type AdminAmount<T> = StorageMap<334 Hasher = Blake2_128Concat,335 Key = CollectionId,336 Value = u32,337 QueryKind = ValueQuery,338 >;339340 /// List of collection admins341 #[pallet::storage]342 pub type IsAdmin<T: Config> = StorageNMap<343 Key = (344 Key<Blake2_128Concat, CollectionId>,345 Key<Blake2_128Concat, T::CrossAccountId>,346 ),347 Value = bool,348 QueryKind = ValueQuery,349 >;350351 /// Allowlisted collection users352 #[pallet::storage]353 pub type Allowlist<T: Config> = StorageNMap<354 Key = (355 Key<Blake2_128Concat, CollectionId>,356 Key<Blake2_128Concat, T::CrossAccountId>,357 ),358 Value = bool,359 QueryKind = ValueQuery,360 >;361362 /// Not used by code, exists only to provide some types to metadata363 #[pallet::storage]364 pub type DummyStorageValue<T> =365 StorageValue<Value = (CollectionStats, CollectionId, TokenId), QueryKind = OptionQuery>;366}367368impl<T: Config> Pallet<T> {369 /// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens370 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {371 ensure!(372 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,373 <Error<T>>::AddressIsZero374 );375 Ok(())376 }377 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {378 <IsAdmin<T>>::iter_prefix((collection,))379 .map(|(a, _)| a)380 .collect()381 }382 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {383 <Allowlist<T>>::iter_prefix((collection,))384 .map(|(a, _)| a)385 .collect()386 }387 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {388 <Allowlist<T>>::get((collection, user))389 }390 pub fn collection_stats() -> CollectionStats {391 let created = <CreatedCollectionCount<T>>::get();392 let destroyed = <DestroyedCollectionCount<T>>::get();393 CollectionStats {394 created: created.0,395 destroyed: destroyed.0,396 alive: created.0 - destroyed.0,397 }398 }399}400401impl<T: Config> Pallet<T> {402 pub fn init_collection(403 owner: T::AccountId,404 data: CreateCollectionData<T::AccountId>,405 ) -> Result<CollectionId, DispatchError> {406 {407 ensure!(408 data.name.len() <= MAX_COLLECTION_NAME_LENGTH,409 Error::<T>::CollectionNameLimitExceeded410 );411 ensure!(412 data.description.len() <= MAX_COLLECTION_DESCRIPTION_LENGTH,413 Error::<T>::CollectionDescriptionLimitExceeded414 );415 ensure!(416 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH,417 Error::<T>::CollectionTokenPrefixLimitExceeded418 );419 }420421 let created_count = <CreatedCollectionCount<T>>::get()422 .0423 .checked_add(1)424 .ok_or(ArithmeticError::Overflow)?;425 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;426 let id = CollectionId(created_count);427428 // bound Total number of collections429 ensure!(430 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,431 <Error<T>>::TotalCollectionsLimitExceeded432 );433434 // =========435436 let collection = Collection {437 owner: owner.clone(),438 name: data.name,439 mode: data.mode.clone(),440 mint_mode: false,441 access: data.access.unwrap_or_default(),442 description: data.description,443 token_prefix: data.token_prefix,444 offchain_schema: data.offchain_schema,445 schema_version: data.schema_version.unwrap_or_default(),446 sponsorship: data447 .pending_sponsor448 .map(SponsorshipState::Unconfirmed)449 .unwrap_or_default(),450 variable_on_chain_schema: data.variable_on_chain_schema,451 const_on_chain_schema: data.const_on_chain_schema,452 limits: data453 .limits454 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))455 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,456 meta_update_permission: data.meta_update_permission.unwrap_or_default(),457 };458459 // Take a (non-refundable) deposit of collection creation460 {461 let mut imbalance =462 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();463 imbalance.subsume(464 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(465 &T::TreasuryAccountId::get(),466 T::CollectionCreationPrice::get(),467 ),468 );469 <T as Config>::Currency::settle(470 &owner,471 imbalance,472 WithdrawReasons::TRANSFER,473 ExistenceRequirement::KeepAlive,474 )475 .map_err(|_| Error::<T>::NoPermission)?;476 }477478 <CreatedCollectionCount<T>>::put(created_count);479 <Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));480 <CollectionById<T>>::insert(id, collection);481 Ok(id)482 }483484 pub fn destroy_collection(485 collection: CollectionHandle<T>,486 sender: &T::CrossAccountId,487 ) -> DispatchResult {488 ensure!(489 collection.limits.owner_can_destroy(),490 <Error<T>>::NoPermission,491 );492 collection.check_is_owner(sender)?;493494 let destroyed_collections = <DestroyedCollectionCount<T>>::get()495 .0496 .checked_add(1)497 .ok_or(ArithmeticError::Overflow)?;498499 // =========500501 <DestroyedCollectionCount<T>>::put(destroyed_collections);502 <CollectionById<T>>::remove(collection.id);503 <AdminAmount<T>>::remove(collection.id);504 <IsAdmin<T>>::remove_prefix((collection.id,), None);505 <Allowlist<T>>::remove_prefix((collection.id,), None);506507 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));508 Ok(())509 }510511 pub fn toggle_allowlist(512 collection: &CollectionHandle<T>,513 sender: &T::CrossAccountId,514 user: &T::CrossAccountId,515 allowed: bool,516 ) -> DispatchResult {517 collection.check_is_owner_or_admin(sender)?;518519 // =========520521 if allowed {522 <Allowlist<T>>::insert((collection.id, user), true);523 } else {524 <Allowlist<T>>::remove((collection.id, user));525 }526527 Ok(())528 }529530 pub fn toggle_admin(531 collection: &CollectionHandle<T>,532 sender: &T::CrossAccountId,533 user: &T::CrossAccountId,534 admin: bool,535 ) -> DispatchResult {536 collection.check_is_owner_or_admin(sender)?;537538 let was_admin = <IsAdmin<T>>::get((collection.id, user));539 if was_admin == admin {540 return Ok(());541 }542 let amount = <AdminAmount<T>>::get(collection.id);543544 if admin {545 let amount = amount546 .checked_add(1)547 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;548 ensure!(549 amount <= Self::collection_admins_limit(),550 <Error<T>>::CollectionAdminCountExceeded,551 );552553 // =========554555 <AdminAmount<T>>::insert(collection.id, amount);556 <IsAdmin<T>>::insert((collection.id, user), true);557 } else {558 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));559 <IsAdmin<T>>::remove((collection.id, user));560 }561562 Ok(())563 }564565 pub fn clamp_limits(566 mode: CollectionMode,567 old_limit: &CollectionLimits,568 mut new_limit: CollectionLimits,569 ) -> Result<CollectionLimits, DispatchError> {570 macro_rules! limit_default {571 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{572 $(573 if let Some($new) = $new.$field {574 let $old = $old.$field($($arg)?);575 let _ = $new;576 let _ = $old;577 $check578 } else {579 $new.$field = $old.$field580 }581 )*582 }};583 }584585 limit_default!(old_limit, new_limit,586 account_token_ownership_limit => ensure!(587 new_limit <= MAX_TOKEN_OWNERSHIP,588 <Error<T>>::CollectionLimitBoundsExceeded,589 ),590 sponsor_transfer_timeout(match mode {591 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,592 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,593 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,594 }) => ensure!(595 new_limit <= MAX_SPONSOR_TIMEOUT,596 <Error<T>>::CollectionLimitBoundsExceeded,597 ),598 sponsored_data_size => ensure!(599 new_limit <= CUSTOM_DATA_LIMIT,600 <Error<T>>::CollectionLimitBoundsExceeded,601 ),602 token_limit => ensure!(603 old_limit >= new_limit && new_limit > 0,604 <Error<T>>::CollectionTokenLimitExceeded605 ),606 owner_can_transfer => ensure!(607 old_limit || !new_limit,608 <Error<T>>::OwnerPermissionsCantBeReverted,609 ),610 owner_can_destroy => ensure!(611 old_limit || !new_limit,612 <Error<T>>::OwnerPermissionsCantBeReverted,613 ),614 sponsored_data_rate_limit => {},615 transfers_enabled => {},616 );617 Ok(new_limit)618 }619}620621#[macro_export]622macro_rules! unsupported {623 () => {624 Err(<Error<T>>::UnsupportedOperation.into())625 };626}627628/// Worst cases629pub trait CommonWeightInfo {630 fn create_item() -> Weight;631 fn create_multiple_items(amount: u32) -> Weight;632 fn burn_item() -> Weight;633 fn transfer() -> Weight;634 fn approve() -> Weight;635 fn transfer_from() -> Weight;636 fn burn_from() -> Weight;637 fn set_variable_metadata(bytes: u32) -> Weight;638}639640pub trait CommonCollectionOperations<T: Config> {641 fn create_item(642 &self,643 sender: T::CrossAccountId,644 to: T::CrossAccountId,645 data: CreateItemData,646 ) -> DispatchResultWithPostInfo;647 fn create_multiple_items(648 &self,649 sender: T::CrossAccountId,650 to: T::CrossAccountId,651 data: Vec<CreateItemData>,652 ) -> DispatchResultWithPostInfo;653 fn burn_item(654 &self,655 sender: T::CrossAccountId,656 token: TokenId,657 amount: u128,658 ) -> DispatchResultWithPostInfo;659660 fn transfer(661 &self,662 sender: T::CrossAccountId,663 to: T::CrossAccountId,664 token: TokenId,665 amount: u128,666 ) -> DispatchResultWithPostInfo;667 fn approve(668 &self,669 sender: T::CrossAccountId,670 spender: T::CrossAccountId,671 token: TokenId,672 amount: u128,673 ) -> DispatchResultWithPostInfo;674 fn transfer_from(675 &self,676 sender: T::CrossAccountId,677 from: T::CrossAccountId,678 to: T::CrossAccountId,679 token: TokenId,680 amount: u128,681 ) -> DispatchResultWithPostInfo;682 fn burn_from(683 &self,684 sender: T::CrossAccountId,685 from: T::CrossAccountId,686 token: TokenId,687 amount: u128,688 ) -> DispatchResultWithPostInfo;689690 fn set_variable_metadata(691 &self,692 sender: T::CrossAccountId,693 token: TokenId,694 data: Vec<u8>,695 ) -> DispatchResultWithPostInfo;696697 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;698 fn token_exists(&self, token: TokenId) -> bool;699 fn last_token_id(&self) -> TokenId;700701 fn token_owner(&self, token: TokenId) -> T::CrossAccountId;702 fn const_metadata(&self, token: TokenId) -> Vec<u8>;703 fn variable_metadata(&self, token: TokenId) -> Vec<u8>;704705 /// How many tokens collection contains (Applicable to nonfungible/refungible)706 fn collection_tokens(&self) -> u32;707 /// Amount of different tokens account has (Applicable to nonfungible/refungible)708 fn account_balance(&self, account: T::CrossAccountId) -> u32;709 /// Amount of specific token account have (Applicable to fungible/refungible)710 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;711 fn allowance(712 &self,713 sender: T::CrossAccountId,714 spender: T::CrossAccountId,715 token: TokenId,716 ) -> u128;717}718719// Flexible enough for implementing CommonCollectionOperations720pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {721 let post_info = PostDispatchInfo {722 actual_weight: Some(weight),723 pays_fee: Pays::Yes,724 };725 match res {726 Ok(()) => Ok(post_info),727 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),728 }729}pallets/fungible/src/lib.rsdiffbeforeafterboth--- a/pallets/fungible/src/lib.rs
+++ b/pallets/fungible/src/lib.rs
@@ -2,7 +2,7 @@
use core::ops::Deref;
use frame_support::{ensure};
-use up_data_structs::{AccessMode, Collection, CollectionId, TokenId};
+use up_data_structs::{AccessMode, Collection, CollectionId, TokenId, CreateCollectionData};
use pallet_common::{
Error as CommonError, Event as CommonEvent, Pallet as PalletCommon, account::CrossAccountId,
};
@@ -100,8 +100,11 @@
}
impl<T: Config> Pallet<T> {
- pub fn init_collection(data: Collection<T::AccountId>) -> Result<CollectionId, DispatchError> {
- <PalletCommon<T>>::init_collection(data)
+ pub fn init_collection(
+ owner: T::AccountId,
+ data: CreateCollectionData<T::AccountId>,
+ ) -> Result<CollectionId, DispatchError> {
+ <PalletCommon<T>>::init_collection(owner, data)
}
pub fn destroy_collection(
collection: FungibleHandle<T>,
pallets/nonfungible/src/lib.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -4,6 +4,7 @@
use frame_support::{BoundedVec, ensure};
use up_data_structs::{
AccessMode, CUSTOM_DATA_LIMIT, Collection, CollectionId, CustomDataLimit, TokenId,
+ CreateCollectionData,
};
use pallet_common::{
Error as CommonError, Pallet as PalletCommon, Event as CommonEvent, account::CrossAccountId,
@@ -142,8 +143,11 @@
// unchecked calls skips any permission checks
impl<T: Config> Pallet<T> {
- pub fn init_collection(data: Collection<T::AccountId>) -> Result<CollectionId, DispatchError> {
- <PalletCommon<T>>::init_collection(data)
+ pub fn init_collection(
+ owner: T::AccountId,
+ data: CreateCollectionData<T::AccountId>,
+ ) -> Result<CollectionId, DispatchError> {
+ <PalletCommon<T>>::init_collection(owner, data)
}
pub fn destroy_collection(
collection: NonfungibleHandle<T>,
pallets/refungible/src/lib.rsdiffbeforeafterboth--- a/pallets/refungible/src/lib.rs
+++ b/pallets/refungible/src/lib.rs
@@ -3,7 +3,7 @@
use frame_support::{ensure, BoundedVec};
use up_data_structs::{
AccessMode, CUSTOM_DATA_LIMIT, Collection, CollectionId, CustomDataLimit,
- MAX_REFUNGIBLE_PIECES, TokenId,
+ MAX_REFUNGIBLE_PIECES, TokenId, CreateCollectionData,
};
use pallet_common::{
Error as CommonError, Event as CommonEvent, Pallet as PalletCommon, account::CrossAccountId,
@@ -156,8 +156,11 @@
// unchecked calls skips any permission checks
impl<T: Config> Pallet<T> {
- pub fn init_collection(data: Collection<T::AccountId>) -> Result<CollectionId, DispatchError> {
- <PalletCommon<T>>::init_collection(data)
+ pub fn init_collection(
+ owner: T::AccountId,
+ data: CreateCollectionData<T::AccountId>,
+ ) -> Result<CollectionId, DispatchError> {
+ <PalletCommon<T>>::init_collection(owner, data)
}
pub fn destroy_collection(
collection: RefungibleHandle<T>,
pallets/unique/src/lib.rsdiffbeforeafterboth--- a/pallets/unique/src/lib.rs
+++ b/pallets/unique/src/lib.rs
@@ -35,11 +35,10 @@
use frame_system::{self as system, ensure_signed};
use sp_runtime::{sp_std::prelude::Vec};
use up_data_structs::{
- MAX_DECIMAL_POINTS, MAX_SPONSOR_TIMEOUT, MAX_TOKEN_OWNERSHIP, CUSTOM_DATA_LIMIT,
- VARIABLE_ON_CHAIN_SCHEMA_LIMIT, CONST_ON_CHAIN_SCHEMA_LIMIT, OFFCHAIN_SCHEMA_LIMIT,
- FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
- NFT_SPONSOR_TRANSFER_TIMEOUT, AccessMode, Collection, CreateItemData, CollectionLimits,
- CollectionId, CollectionMode, TokenId, SchemaVersion, SponsorshipState, MetaUpdatePermission,
+ MAX_DECIMAL_POINTS, VARIABLE_ON_CHAIN_SCHEMA_LIMIT, CONST_ON_CHAIN_SCHEMA_LIMIT,
+ OFFCHAIN_SCHEMA_LIMIT, AccessMode, CreateItemData, CollectionLimits, CollectionId,
+ CollectionMode, TokenId, SchemaVersion, SponsorshipState, MetaUpdatePermission,
+ CreateCollectionData,
};
use pallet_common::{
account::CrossAccountId, CollectionHandle, Pallet as PalletCommon, Error as CommonError,
@@ -81,10 +80,6 @@
ConfirmUnsetSponsorFail,
/// Length of items properties must be greater than 0.
EmptyArgument,
- /// Collection limit bounds per collection exceeded
- CollectionLimitBoundsExceeded,
- /// Tried to enable permissions which are only permitted to be disabled
- OwnerPermissionsCantBeReverted,
}
}
@@ -318,42 +313,38 @@
// returns collection ID
#[weight = <SelfWeightOf<T>>::create_collection()]
#[transactional]
+ #[deprecated]
pub fn create_collection(origin,
collection_name: Vec<u16>,
collection_description: Vec<u16>,
token_prefix: Vec<u8>,
mode: CollectionMode) -> DispatchResult {
-
- // Anyone can create a collection
- let who = ensure_signed(origin)?;
-
- // Create new collection
- let new_collection = Collection {
- owner: who,
+ Self::create_collection_ex(origin, CreateCollectionData {
name: collection_name,
- mode: mode.clone(),
- mint_mode: false,
- access: AccessMode::Normal,
description: collection_description,
token_prefix,
- offchain_schema: Vec::new(),
- schema_version: SchemaVersion::ImageURL,
- sponsorship: SponsorshipState::Disabled,
- variable_on_chain_schema: Vec::new(),
- const_on_chain_schema: Vec::new(),
- limits: Default::default(),
- meta_update_permission: Default::default(),
- };
+ mode,
+ ..Default::default()
+ })
+ }
+
+ /// This method creates a collection
+ ///
+ /// Prefer it to deprecated [`created_collection`] method
+ #[weight = <SelfWeightOf<T>>::create_collection()]
+ #[transactional]
+ pub fn create_collection_ex(origin, data: CreateCollectionData<T::AccountId>) -> DispatchResult {
+ let owner = ensure_signed(origin)?;
- let _id = match mode {
- CollectionMode::NFT => {<PalletNonfungible<T>>::init_collection(new_collection)?},
+ let _id = match data.mode {
+ CollectionMode::NFT => {<PalletNonfungible<T>>::init_collection(owner, data)?},
CollectionMode::Fungible(decimal_points) => {
// check params
ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);
- <PalletFungible<T>>::init_collection(new_collection)?
+ <PalletFungible<T>>::init_collection(owner, data)?
}
CollectionMode::ReFungible => {
- <PalletRefungible<T>>::init_collection(new_collection)?
+ <PalletRefungible<T>>::init_collection(owner, data)?
}
};
@@ -1099,61 +1090,12 @@
collection_id: CollectionId,
new_limit: CollectionLimits,
) -> DispatchResult {
- let mut new_limit = new_limit;
let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;
target_collection.check_is_owner(&sender)?;
let old_limit = &target_collection.limits;
- macro_rules! limit_default {
- ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{
- $(
- if let Some($new) = $new.$field {
- let $old = $old.$field($($arg)?);
- let _ = $new;
- let _ = $old;
- $check
- } else {
- $new.$field = $old.$field
- }
- )*
- }};
- }
-
- limit_default!(old_limit, new_limit,
- account_token_ownership_limit => ensure!(
- new_limit <= MAX_TOKEN_OWNERSHIP,
- <Error<T>>::CollectionLimitBoundsExceeded,
- ),
- sponsor_transfer_timeout(match target_collection.mode {
- CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,
- CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
- CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
- }) => ensure!(
- new_limit <= MAX_SPONSOR_TIMEOUT,
- <Error<T>>::CollectionLimitBoundsExceeded,
- ),
- sponsored_data_size => ensure!(
- new_limit <= CUSTOM_DATA_LIMIT,
- <Error<T>>::CollectionLimitBoundsExceeded,
- ),
- token_limit => ensure!(
- old_limit >= new_limit && new_limit > 0,
- <CommonError<T>>::CollectionTokenLimitExceeded
- ),
- owner_can_transfer => ensure!(
- old_limit || !new_limit,
- <Error<T>>::OwnerPermissionsCantBeReverted,
- ),
- owner_can_destroy => ensure!(
- old_limit || !new_limit,
- <Error<T>>::OwnerPermissionsCantBeReverted,
- ),
- sponsored_data_rate_limit => {},
- transfers_enabled => {},
- );
-
- target_collection.limits = new_limit;
+ target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;
<Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(
collection_id
pallets/unique/src/tests.rsdiffbeforeafterboth--- a/pallets/unique/src/tests.rs
+++ b/pallets/unique/src/tests.rs
@@ -5,7 +5,7 @@
use up_data_structs::{
COLLECTION_NUMBER_LIMIT, CollectionId, CreateItemData, CreateFungibleData, CreateNftData,
CreateReFungibleData, MAX_DECIMAL_POINTS, COLLECTION_ADMINS_LIMIT, MetaUpdatePermission,
- TokenId,
+ TokenId, MAX_TOKEN_OWNERSHIP,
};
use frame_support::{assert_noop, assert_ok};
use sp_std::convert::TryInto;
primitives/data-structs/src/lib.rsdiffbeforeafterboth--- a/primitives/data-structs/src/lib.rs
+++ b/primitives/data-structs/src/lib.rs
@@ -230,6 +230,25 @@
pub meta_update_permission: MetaUpdatePermission,
}
+#[derive(Encode, Decode, Clone, PartialEq, TypeInfo, Debug, Derivative)]
+#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
+#[derivative(Default)]
+pub struct CreateCollectionData<AccountId> {
+ #[derivative(Default(value = "CollectionMode::NFT"))]
+ pub mode: CollectionMode,
+ pub access: Option<AccessMode>,
+ pub name: Vec<u16>,
+ pub description: Vec<u16>,
+ pub token_prefix: Vec<u8>,
+ pub offchain_schema: Vec<u8>,
+ pub schema_version: Option<SchemaVersion>,
+ pub pending_sponsor: Option<AccountId>,
+ pub limits: Option<CollectionLimits>,
+ pub variable_on_chain_schema: Vec<u8>,
+ pub const_on_chain_schema: Vec<u8>,
+ pub meta_update_permission: Option<MetaUpdatePermission>,
+}
+
#[derive(Encode, Decode, Debug, Clone, PartialEq, TypeInfo)]
#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
pub struct NftItemType<AccountId> {