difftreelog
feat implement collection nesting rules field
in: master
3 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, Weight, PostDispatchInfo},25 ensure, fail,26 traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},27 BoundedVec,28 weights::Pays,29};30use pallet_evm::GasWeightMapping;31use up_data_structs::{32 COLLECTION_NUMBER_LIMIT, Collection, CollectionId, CreateItemData, MAX_TOKEN_PREFIX_LENGTH,33 COLLECTION_ADMINS_LIMIT, MetaUpdatePermission, TokenId, CollectionStats, MAX_TOKEN_OWNERSHIP,34 CollectionMode, 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, SponsoringRateLimit,37};38pub use pallet::*;39use sp_core::H160;40use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};41#[cfg(feature = "runtime-benchmarks")]42pub mod benchmarking;43pub mod dispatch;44pub mod erc;45pub mod eth;4647#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]48pub struct CollectionHandle<T: Config> {49 pub id: CollectionId,50 collection: Collection<T::AccountId>,51 pub recorder: SubstrateRecorder<T>,52}53impl<T: Config> WithRecorder<T> for CollectionHandle<T> {54 fn recorder(&self) -> &SubstrateRecorder<T> {55 &self.recorder56 }57 fn into_recorder(self) -> SubstrateRecorder<T> {58 self.recorder59 }60}61impl<T: Config> CollectionHandle<T> {62 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {63 <CollectionById<T>>::get(id).map(|collection| Self {64 id,65 collection,66 recorder: SubstrateRecorder::new(eth::collection_id_to_address(id), gas_limit),67 })68 }69 pub fn new(id: CollectionId) -> Option<Self> {70 Self::new_with_gas_limit(id, u64::MAX)71 }72 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {73 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)74 }75 pub fn log_mirrored(&self, log: impl evm_coder::ToLog) {76 self.recorder.log_mirrored(log)77 }78 pub fn log_direct(&self, log: impl evm_coder::ToLog) {79 self.recorder.log_direct(log)80 }81 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {82 self.recorder83 .consume_gas(T::GasWeightMapping::weight_to_gas(84 <T as frame_system::Config>::DbWeight::get()85 .read86 .saturating_mul(reads),87 ))88 }89 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {90 self.recorder91 .consume_gas(T::GasWeightMapping::weight_to_gas(92 <T as frame_system::Config>::DbWeight::get()93 .write94 .saturating_mul(writes),95 ))96 }97 pub fn submit_logs(self) {98 self.recorder.submit_logs()99 }100 pub fn save(self) -> DispatchResult {101 self.recorder.submit_logs();102 <CollectionById<T>>::insert(self.id, self.collection);103 Ok(())104 }105}106impl<T: Config> Deref for CollectionHandle<T> {107 type Target = Collection<T::AccountId>;108109 fn deref(&self) -> &Self::Target {110 &self.collection111 }112}113114impl<T: Config> DerefMut for CollectionHandle<T> {115 fn deref_mut(&mut self) -> &mut Self::Target {116 &mut self.collection117 }118}119120impl<T: Config> CollectionHandle<T> {121 pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {122 ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);123 Ok(())124 }125 pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {126 *subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))127 }128 pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {129 ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);130 Ok(())131 }132 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {133 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)134 }135 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {136 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)137 }138 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {139 ensure!(140 <Allowlist<T>>::get((self.id, user)),141 <Error<T>>::AddressNotInAllowlist142 );143 Ok(())144 }145146 pub fn check_can_update_meta(147 &self,148 subject: &T::CrossAccountId,149 item_owner: &T::CrossAccountId,150 ) -> DispatchResult {151 match self.meta_update_permission {152 MetaUpdatePermission::ItemOwner => {153 ensure!(subject == item_owner, <Error<T>>::NoPermission);154 Ok(())155 }156 MetaUpdatePermission::Admin => self.check_is_owner_or_admin(subject),157 MetaUpdatePermission::None => fail!(<Error<T>>::NoPermission),158 }159 }160}161162#[frame_support::pallet]163pub mod pallet {164 use super::*;165 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key};166 use pallet_evm::account;167 use dispatch::CollectionDispatch;168 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};169 use frame_system::pallet_prelude::*;170 use frame_support::traits::Currency;171 use up_data_structs::{TokenId, mapping::TokenAddressMapping};172 use scale_info::TypeInfo;173 use up_evm_mapping::CrossAccountId;174175 #[pallet::config]176 pub trait Config:177 frame_system::Config + pallet_evm_coder_substrate::Config + TypeInfo + account::Config178 {179 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;180181 type Currency: Currency<Self::AccountId>;182183 #[pallet::constant]184 type CollectionCreationPrice: Get<185 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,186 >;187 type CollectionDispatch: CollectionDispatch<Self>;188189 type TreasuryAccountId: Get<Self::AccountId>;190191 type EvmTokenAddressMapping: TokenAddressMapping<H160>;192 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;193 }194195 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);196197 #[pallet::pallet]198 #[pallet::storage_version(STORAGE_VERSION)]199 #[pallet::generate_store(pub(super) trait Store)]200 pub struct Pallet<T>(_);201202 #[pallet::extra_constants]203 impl<T: Config> Pallet<T> {204 pub fn collection_admins_limit() -> u32 {205 COLLECTION_ADMINS_LIMIT206 }207 }208209 #[pallet::event]210 #[pallet::generate_deposit(pub fn deposit_event)]211 pub enum Event<T: Config> {212 /// New collection was created213 ///214 /// # Arguments215 ///216 /// * collection_id: Globally unique identifier of newly created collection.217 ///218 /// * mode: [CollectionMode] converted into u8.219 ///220 /// * account_id: Collection owner.221 CollectionCreated(CollectionId, u8, T::AccountId),222223 /// New collection was destroyed224 ///225 /// # Arguments226 ///227 /// * collection_id: Globally unique identifier of collection.228 CollectionDestroyed(CollectionId),229230 /// New item was created.231 ///232 /// # Arguments233 ///234 /// * collection_id: Id of the collection where item was created.235 ///236 /// * item_id: Id of an item. Unique within the collection.237 ///238 /// * recipient: Owner of newly created item239 ///240 /// * amount: Always 1 for NFT241 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),242243 /// Collection item was burned.244 ///245 /// # Arguments246 ///247 /// * collection_id.248 ///249 /// * item_id: Identifier of burned NFT.250 ///251 /// * owner: which user has destroyed its tokens252 ///253 /// * amount: Always 1 for NFT254 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),255256 /// Item was transferred257 ///258 /// * collection_id: Id of collection to which item is belong259 ///260 /// * item_id: Id of an item261 ///262 /// * sender: Original owner of item263 ///264 /// * recipient: New owner of item265 ///266 /// * amount: Always 1 for NFT267 Transfer(268 CollectionId,269 TokenId,270 T::CrossAccountId,271 T::CrossAccountId,272 u128,273 ),274275 /// * collection_id276 ///277 /// * item_id278 ///279 /// * sender280 ///281 /// * spender282 ///283 /// * amount284 Approved(285 CollectionId,286 TokenId,287 T::CrossAccountId,288 T::CrossAccountId,289 u128,290 ),291 }292293 #[pallet::error]294 pub enum Error<T> {295 /// This collection does not exist.296 CollectionNotFound,297 /// Sender parameter and item owner must be equal.298 MustBeTokenOwner,299 /// No permission to perform action300 NoPermission,301 /// Collection is not in mint mode.302 PublicMintingNotAllowed,303 /// Address is not in allow list.304 AddressNotInAllowlist,305306 /// Collection name can not be longer than 63 char.307 CollectionNameLimitExceeded,308 /// Collection description can not be longer than 255 char.309 CollectionDescriptionLimitExceeded,310 /// Token prefix can not be longer than 15 char.311 CollectionTokenPrefixLimitExceeded,312 /// Total collections bound exceeded.313 TotalCollectionsLimitExceeded,314 /// variable_data exceeded data limit.315 TokenVariableDataLimitExceeded,316 /// Exceeded max admin count317 CollectionAdminCountExceeded,318 /// Collection limit bounds per collection exceeded319 CollectionLimitBoundsExceeded,320 /// Tried to enable permissions which are only permitted to be disabled321 OwnerPermissionsCantBeReverted,322323 /// Collection settings not allowing items transferring324 TransferNotAllowed,325 /// Account token limit exceeded per collection326 AccountTokenLimitExceeded,327 /// Collection token limit exceeded328 CollectionTokenLimitExceeded,329 /// Metadata flag frozen330 MetadataFlagFrozen,331332 /// Item not exists.333 TokenNotFound,334 /// Item balance not enough.335 TokenValueTooLow,336 /// Requested value more than approved.337 ApprovedValueTooLow,338 /// Tried to approve more than owned339 CantApproveMoreThanOwned,340341 /// Can't transfer tokens to ethereum zero address342 AddressIsZero,343 /// Target collection doesn't supports this operation344 UnsupportedOperation,345346 /// Not sufficient founds to perform action347 NotSufficientFounds,348 }349350 #[pallet::storage]351 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;352 #[pallet::storage]353 pub type DestroyedCollectionCount<T> =354 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;355356 /// Collection info357 #[pallet::storage]358 pub type CollectionById<T> = StorageMap<359 Hasher = Blake2_128Concat,360 Key = CollectionId,361 Value = Collection<<T as frame_system::Config>::AccountId>,362 QueryKind = OptionQuery,363 >;364365 #[pallet::storage]366 pub type AdminAmount<T> = StorageMap<367 Hasher = Blake2_128Concat,368 Key = CollectionId,369 Value = u32,370 QueryKind = ValueQuery,371 >;372373 /// List of collection admins374 #[pallet::storage]375 pub type IsAdmin<T: Config> = StorageNMap<376 Key = (377 Key<Blake2_128Concat, CollectionId>,378 Key<Blake2_128Concat, T::CrossAccountId>,379 ),380 Value = bool,381 QueryKind = ValueQuery,382 >;383384 /// Allowlisted collection users385 #[pallet::storage]386 pub type Allowlist<T: Config> = StorageNMap<387 Key = (388 Key<Blake2_128Concat, CollectionId>,389 Key<Blake2_128Concat, T::CrossAccountId>,390 ),391 Value = bool,392 QueryKind = ValueQuery,393 >;394395 /// Not used by code, exists only to provide some types to metadata396 #[pallet::storage]397 pub type DummyStorageValue<T> =398 StorageValue<Value = (CollectionStats, CollectionId, TokenId), QueryKind = OptionQuery>;399400 #[pallet::hooks]401 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {402 fn on_runtime_upgrade() -> Weight {403 let mut weight = 0;404405 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {406 use up_data_structs::{CollectionVersion1, CollectionVersion2};407 <CollectionById<T>>::translate_values::<CollectionVersion1<T::AccountId>, _>(|v| {408 Some(CollectionVersion2::from(v))409 });410 }411412 weight413 }414 }415}416417impl<T: Config> Pallet<T> {418 /// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens419 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {420 ensure!(421 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,422 <Error<T>>::AddressIsZero423 );424 Ok(())425 }426 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {427 <IsAdmin<T>>::iter_prefix((collection,))428 .map(|(a, _)| a)429 .collect()430 }431 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {432 <Allowlist<T>>::iter_prefix((collection,))433 .map(|(a, _)| a)434 .collect()435 }436 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {437 <Allowlist<T>>::get((collection, user))438 }439 pub fn collection_stats() -> CollectionStats {440 let created = <CreatedCollectionCount<T>>::get();441 let destroyed = <DestroyedCollectionCount<T>>::get();442 CollectionStats {443 created: created.0,444 destroyed: destroyed.0,445 alive: created.0 - destroyed.0,446 }447 }448449 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {450 let collection = <CollectionById<T>>::get(collection);451 if collection.is_none() {452 return None;453 }454455 let collection = collection.unwrap();456 let limits = collection.limits;457 let effective_limits = CollectionLimits {458 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),459 sponsored_data_size: Some(limits.sponsored_data_size()),460 sponsored_data_rate_limit: Some(461 limits462 .sponsored_data_rate_limit463 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),464 ),465 token_limit: Some(limits.token_limit()),466 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(467 match collection.mode {468 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,469 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,470 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,471 },472 )),473 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),474 owner_can_transfer: Some(limits.owner_can_transfer()),475 owner_can_destroy: Some(limits.owner_can_destroy()),476 transfers_enabled: Some(limits.transfers_enabled()),477 };478479 Some(effective_limits)480 }481}482483impl<T: Config> Pallet<T> {484 pub fn init_collection(485 owner: T::AccountId,486 data: CreateCollectionData<T::AccountId>,487 ) -> Result<CollectionId, DispatchError> {488 {489 ensure!(490 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,491 Error::<T>::CollectionTokenPrefixLimitExceeded492 );493 }494495 let created_count = <CreatedCollectionCount<T>>::get()496 .0497 .checked_add(1)498 .ok_or(ArithmeticError::Overflow)?;499 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;500 let id = CollectionId(created_count);501502 // bound Total number of collections503 ensure!(504 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,505 <Error<T>>::TotalCollectionsLimitExceeded506 );507508 // =========509510 let collection = Collection {511 owner: owner.clone(),512 name: data.name,513 mode: data.mode.clone(),514 mint_mode: false,515 access: data.access.unwrap_or_default(),516 description: data.description,517 token_prefix: data.token_prefix,518 offchain_schema: data.offchain_schema,519 schema_version: data.schema_version.unwrap_or_default(),520 sponsorship: data521 .pending_sponsor522 .map(SponsorshipState::Unconfirmed)523 .unwrap_or_default(),524 variable_on_chain_schema: data.variable_on_chain_schema,525 const_on_chain_schema: data.const_on_chain_schema,526 limits: data527 .limits528 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))529 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,530 meta_update_permission: data.meta_update_permission.unwrap_or_default(),531 };532533 // Take a (non-refundable) deposit of collection creation534 {535 let mut imbalance =536 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();537 imbalance.subsume(538 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(539 &T::TreasuryAccountId::get(),540 T::CollectionCreationPrice::get(),541 ),542 );543 <T as Config>::Currency::settle(544 &owner,545 imbalance,546 WithdrawReasons::TRANSFER,547 ExistenceRequirement::KeepAlive,548 )549 .map_err(|_| Error::<T>::NotSufficientFounds)?;550 }551552 <CreatedCollectionCount<T>>::put(created_count);553 <Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));554 <CollectionById<T>>::insert(id, collection);555 Ok(id)556 }557558 pub fn destroy_collection(559 collection: CollectionHandle<T>,560 sender: &T::CrossAccountId,561 ) -> DispatchResult {562 ensure!(563 collection.limits.owner_can_destroy(),564 <Error<T>>::NoPermission,565 );566 collection.check_is_owner(sender)?;567568 let destroyed_collections = <DestroyedCollectionCount<T>>::get()569 .0570 .checked_add(1)571 .ok_or(ArithmeticError::Overflow)?;572573 // =========574575 <DestroyedCollectionCount<T>>::put(destroyed_collections);576 <CollectionById<T>>::remove(collection.id);577 <AdminAmount<T>>::remove(collection.id);578 <IsAdmin<T>>::remove_prefix((collection.id,), None);579 <Allowlist<T>>::remove_prefix((collection.id,), None);580581 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));582 Ok(())583 }584585 pub fn toggle_allowlist(586 collection: &CollectionHandle<T>,587 sender: &T::CrossAccountId,588 user: &T::CrossAccountId,589 allowed: bool,590 ) -> DispatchResult {591 collection.check_is_owner_or_admin(sender)?;592593 // =========594595 if allowed {596 <Allowlist<T>>::insert((collection.id, user), true);597 } else {598 <Allowlist<T>>::remove((collection.id, user));599 }600601 Ok(())602 }603604 pub fn toggle_admin(605 collection: &CollectionHandle<T>,606 sender: &T::CrossAccountId,607 user: &T::CrossAccountId,608 admin: bool,609 ) -> DispatchResult {610 collection.check_is_owner_or_admin(sender)?;611612 let was_admin = <IsAdmin<T>>::get((collection.id, user));613 if was_admin == admin {614 return Ok(());615 }616 let amount = <AdminAmount<T>>::get(collection.id);617618 if admin {619 let amount = amount620 .checked_add(1)621 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;622 ensure!(623 amount <= Self::collection_admins_limit(),624 <Error<T>>::CollectionAdminCountExceeded,625 );626627 // =========628629 <AdminAmount<T>>::insert(collection.id, amount);630 <IsAdmin<T>>::insert((collection.id, user), true);631 } else {632 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));633 <IsAdmin<T>>::remove((collection.id, user));634 }635636 Ok(())637 }638639 pub fn clamp_limits(640 mode: CollectionMode,641 old_limit: &CollectionLimits,642 mut new_limit: CollectionLimits,643 ) -> Result<CollectionLimits, DispatchError> {644 macro_rules! limit_default {645 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{646 $(647 if let Some($new) = $new.$field {648 let $old = $old.$field($($arg)?);649 let _ = $new;650 let _ = $old;651 $check652 } else {653 $new.$field = $old.$field654 }655 )*656 }};657 }658659 limit_default!(old_limit, new_limit,660 account_token_ownership_limit => ensure!(661 new_limit <= MAX_TOKEN_OWNERSHIP,662 <Error<T>>::CollectionLimitBoundsExceeded,663 ),664 sponsor_transfer_timeout(match mode {665 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,666 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,667 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,668 }) => ensure!(669 new_limit <= MAX_SPONSOR_TIMEOUT,670 <Error<T>>::CollectionLimitBoundsExceeded,671 ),672 sponsored_data_size => ensure!(673 new_limit <= CUSTOM_DATA_LIMIT,674 <Error<T>>::CollectionLimitBoundsExceeded,675 ),676 token_limit => ensure!(677 old_limit >= new_limit && new_limit > 0,678 <Error<T>>::CollectionTokenLimitExceeded679 ),680 owner_can_transfer => ensure!(681 old_limit || !new_limit,682 <Error<T>>::OwnerPermissionsCantBeReverted,683 ),684 owner_can_destroy => ensure!(685 old_limit || !new_limit,686 <Error<T>>::OwnerPermissionsCantBeReverted,687 ),688 sponsored_data_rate_limit => {},689 transfers_enabled => {},690 );691 Ok(new_limit)692 }693}694695#[macro_export]696macro_rules! unsupported {697 () => {698 Err(<Error<T>>::UnsupportedOperation.into())699 };700}701702/// Worst cases703pub trait CommonWeightInfo<CrossAccountId> {704 fn create_item() -> Weight;705 fn create_multiple_items(amount: u32) -> Weight;706 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;707 fn burn_item() -> Weight;708 fn transfer() -> Weight;709 fn approve() -> Weight;710 fn transfer_from() -> Weight;711 fn burn_from() -> Weight;712 fn set_variable_metadata(bytes: u32) -> Weight;713}714715pub trait CommonCollectionOperations<T: Config> {716 fn create_item(717 &self,718 sender: T::CrossAccountId,719 to: T::CrossAccountId,720 data: CreateItemData,721 ) -> DispatchResultWithPostInfo;722 fn create_multiple_items(723 &self,724 sender: T::CrossAccountId,725 to: T::CrossAccountId,726 data: Vec<CreateItemData>,727 ) -> DispatchResultWithPostInfo;728 fn create_multiple_items_ex(729 &self,730 sender: T::CrossAccountId,731 data: CreateItemExData<T::CrossAccountId>,732 ) -> DispatchResultWithPostInfo;733 fn burn_item(734 &self,735 sender: T::CrossAccountId,736 token: TokenId,737 amount: u128,738 ) -> DispatchResultWithPostInfo;739740 fn transfer(741 &self,742 sender: T::CrossAccountId,743 to: T::CrossAccountId,744 token: TokenId,745 amount: u128,746 ) -> DispatchResultWithPostInfo;747 fn approve(748 &self,749 sender: T::CrossAccountId,750 spender: T::CrossAccountId,751 token: TokenId,752 amount: u128,753 ) -> DispatchResultWithPostInfo;754 fn transfer_from(755 &self,756 sender: T::CrossAccountId,757 from: T::CrossAccountId,758 to: T::CrossAccountId,759 token: TokenId,760 amount: u128,761 ) -> DispatchResultWithPostInfo;762 fn burn_from(763 &self,764 sender: T::CrossAccountId,765 from: T::CrossAccountId,766 token: TokenId,767 amount: u128,768 ) -> DispatchResultWithPostInfo;769770 fn set_variable_metadata(771 &self,772 sender: T::CrossAccountId,773 token: TokenId,774 data: BoundedVec<u8, CustomDataLimit>,775 ) -> DispatchResultWithPostInfo;776777 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;778 fn token_exists(&self, token: TokenId) -> bool;779 fn last_token_id(&self) -> TokenId;780781 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;782 fn const_metadata(&self, token: TokenId) -> Vec<u8>;783 fn variable_metadata(&self, token: TokenId) -> Vec<u8>;784785 /// How many tokens collection contains (Applicable to nonfungible/refungible)786 fn collection_tokens(&self) -> u32;787 /// Amount of different tokens account has (Applicable to nonfungible/refungible)788 fn account_balance(&self, account: T::CrossAccountId) -> u32;789 /// Amount of specific token account have (Applicable to fungible/refungible)790 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;791 fn allowance(792 &self,793 sender: T::CrossAccountId,794 spender: T::CrossAccountId,795 token: TokenId,796 ) -> u128;797}798799// Flexible enough for implementing CommonCollectionOperations800pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {801 let post_info = PostDispatchInfo {802 actual_weight: Some(weight),803 pays_fee: Pays::Yes,804 };805 match res {806 Ok(()) => Ok(post_info),807 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),808 }809}primitives/data-structs/Cargo.tomldiffbeforeafterboth--- a/primitives/data-structs/Cargo.toml
+++ b/primitives/data-structs/Cargo.toml
@@ -24,6 +24,7 @@
sp-std = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.20" }
sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.20" }
derivative = "2.2.0"
+struct-versioning = { path = "../../crates/struct-versioning" }
[features]
default = ["std"]
primitives/data-structs/src/lib.rsdiffbeforeafterboth--- a/primitives/data-structs/src/lib.rs
+++ b/primitives/data-structs/src/lib.rs
@@ -248,6 +248,7 @@
}
}
+#[struct_versioning::versioned(version = 2, upper)]
#[derive(Encode, Decode, Clone, PartialEq, TypeInfo, MaxEncodedLen)]
#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
pub struct Collection<AccountId> {
@@ -265,7 +266,12 @@
pub offchain_schema: BoundedVec<u8, ConstU32<OFFCHAIN_SCHEMA_LIMIT>>,
pub schema_version: SchemaVersion,
pub sponsorship: SponsorshipState<AccountId>,
- pub limits: CollectionLimits, // Collection private restrictions
+
+ #[version(..2)]
+ pub limits: CollectionLimitsVersion1, // Collection private restrictions
+ #[version(2.., upper(limits.into()))]
+ pub limits: CollectionLimitsVersion2,
+
#[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]
pub variable_on_chain_schema: BoundedVec<u8, ConstU32<VARIABLE_ON_CHAIN_SCHEMA_LIMIT>>,
#[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]
@@ -321,6 +327,7 @@
}
/// All fields are wrapped in `Option`s, where None means chain default
+#[struct_versioning::versioned(version = 2, upper)]
#[derive(Encode, Decode, Debug, Default, Clone, PartialEq, TypeInfo, MaxEncodedLen)]
#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
pub struct CollectionLimits {
@@ -338,6 +345,9 @@
pub owner_can_transfer: Option<bool>,
pub owner_can_destroy: Option<bool>,
pub transfers_enabled: Option<bool>,
+
+ #[version(2.., upper(None))]
+ pub nesting_rule: Option<NestingRule>,
}
impl CollectionLimits {
@@ -384,6 +394,26 @@
SponsoringRateLimit::Blocks(v) => Some(v.min(MAX_SPONSOR_TIMEOUT)),
}
}
+ pub fn nesting_rule(&self) -> &NestingRule {
+ static DEFAULT: NestingRule = NestingRule::Owner;
+ self.nesting_rule.as_ref().unwrap_or(&DEFAULT)
+ }
+}
+
+#[derive(Encode, Decode, Clone, PartialEq, TypeInfo, MaxEncodedLen, Derivative)]
+#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
+#[derivative(Debug)]
+pub enum NestingRule {
+ /// No one can nest tokens
+ Disabled,
+ /// Owner can nest any tokens
+ Owner,
+ /// Owner can nest tokens from specified collections
+ OwnerRestricted(
+ #[cfg_attr(feature = "serde1", serde(with = "bounded_set_serde"))]
+ #[derivative(Debug(format_with = "bounded_set_debug"))]
+ BoundedBTreeSet<CollectionId, ConstU32<16>>,
+ ),
}
#[derive(Encode, Decode, Debug, Clone, Copy, PartialEq, TypeInfo, MaxEncodedLen)]
@@ -482,6 +512,50 @@
(&v as &BTreeMap<K, V>).fmt(f)
}
+#[cfg(feature = "serde1")]
+#[allow(dead_code)]
+mod bounded_set_serde {
+ use core::convert::TryFrom;
+ use sp_std::collections::btree_set::BTreeSet;
+ use frame_support::{traits::Get, storage::bounded_btree_set::BoundedBTreeSet};
+ use serde::{
+ ser::{self, Serialize},
+ de::{self, Deserialize, Error},
+ };
+ pub fn serialize<D, K, S>(
+ value: &BoundedBTreeSet<K, S>,
+ serializer: D,
+ ) -> Result<D::Ok, D::Error>
+ where
+ D: ser::Serializer,
+ K: Serialize + Ord,
+ {
+ (value as &BTreeSet<_>).serialize(serializer)
+ }
+
+ pub fn deserialize<'de, D, K, S>(deserializer: D) -> Result<BoundedBTreeSet<K, S>, D::Error>
+ where
+ D: de::Deserializer<'de>,
+ K: de::Deserialize<'de> + Ord,
+ S: Get<u32>,
+ {
+ let map = <BTreeSet<K>>::deserialize(deserializer)?;
+ let len = map.len();
+ TryFrom::try_from(map).map_err(|_| D::Error::invalid_length(len, &"lesser size"))
+ }
+}
+
+fn bounded_set_debug<K, S>(
+ v: &BoundedBTreeSet<K, S>,
+ f: &mut fmt::Formatter,
+) -> Result<(), fmt::Error>
+where
+ K: fmt::Debug + Ord,
+{
+ use core::fmt::Debug;
+ (&v as &BTreeSet<K>).fmt(f)
+}
+
#[derive(Encode, Decode, MaxEncodedLen, Default, PartialEq, Clone, Derivative, TypeInfo)]
#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
#[derivative(Debug)]