difftreelog
refactor move properties around
in: master
18 files changed
client/rpc/src/lib.rsdiffbeforeafterboth--- a/client/rpc/src/lib.rs
+++ b/client/rpc/src/lib.rs
@@ -73,13 +73,6 @@
token: TokenId,
at: Option<BlockHash>,
) -> Result<Option<CrossAccountId>>;
- #[rpc(name = "unique_constMetadata")]
- fn const_metadata(
- &self,
- collection: CollectionId,
- token: TokenId,
- at: Option<BlockHash>,
- ) -> Result<Vec<u8>>;
#[rpc(name = "unique_collectionProperties")]
fn collection_properties(
@@ -418,9 +411,6 @@
);
pass_method!(
topmost_token_owner(collection: CollectionId, token: TokenId) -> Option<CrossAccountId>, unique_api
- );
- pass_method!(
- const_metadata(collection: CollectionId, token: TokenId) -> Vec<u8>, unique_api
);
pass_method!(total_supply(collection: CollectionId) -> u32, unique_api);
pass_method!(account_balance(collection: CollectionId, account: CrossAccountId) -> u32, unique_api);
pallets/common/src/benchmarking.rsdiffbeforeafterboth--- a/pallets/common/src/benchmarking.rs
+++ b/pallets/common/src/benchmarking.rs
@@ -86,8 +86,6 @@
let name = create_u16_data::<MAX_COLLECTION_NAME_LENGTH>();
let description = create_u16_data::<MAX_COLLECTION_DESCRIPTION_LENGTH>();
let token_prefix = create_data::<MAX_TOKEN_PREFIX_LENGTH>();
- let offchain_schema = create_data::<OFFCHAIN_SCHEMA_LIMIT>();
- let const_on_chain_schema = create_data::<CONST_ON_CHAIN_SCHEMA_LIMIT>();
handler(
owner,
CreateCollectionData {
@@ -95,8 +93,6 @@
name,
description,
token_prefix,
- offchain_schema,
- const_on_chain_schema,
..Default::default()
},
)
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)]1819extern crate alloc;2021use core::ops::{Deref, DerefMut};22use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};23use sp_std::vec::Vec;24use pallet_evm::account::CrossAccountId;25use frame_support::{26 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},27 ensure,28 traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},29 BoundedVec,30 weights::Pays,31 transactional,32};33use pallet_evm::GasWeightMapping;34use up_data_structs::{35 COLLECTION_NUMBER_LIMIT,36 Collection,37 RpcCollection,38 CollectionId,39 CreateItemData,40 MAX_TOKEN_PREFIX_LENGTH,41 COLLECTION_ADMINS_LIMIT,42 TokenId,43 CollectionStats,44 MAX_TOKEN_OWNERSHIP,45 CollectionMode,46 NFT_SPONSOR_TRANSFER_TIMEOUT,47 FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,48 REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,49 MAX_SPONSOR_TIMEOUT,50 CUSTOM_DATA_LIMIT,51 CollectionLimits,52 CreateCollectionData,53 SponsorshipState,54 CreateItemExData,55 SponsoringRateLimit,56 budget::Budget,57 COLLECTION_FIELD_LIMIT,58 CollectionField,59 PhantomType,60 Property,61 Properties,62 PropertiesPermissionMap,63 PropertyKey,64 PropertyValue,65 PropertyPermission,66 PropertiesError,67 PropertyKeyPermission,68 TokenData,69 TrySetProperty,70 PropertyScope,71 // RMRK72 RmrkCollectionInfo,73 RmrkInstanceInfo,74 RmrkResourceInfo,75 RmrkPropertyInfo,76 RmrkBaseInfo,77 RmrkPartType,78 RmrkTheme,79 RmrkNftChild,80};8182pub use pallet::*;83use sp_core::H160;84use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};85#[cfg(feature = "runtime-benchmarks")]86pub mod benchmarking;87pub mod dispatch;88pub mod erc;89pub mod eth;90pub mod weights;9192pub type SelfWeightOf<T> = <T as Config>::WeightInfo;9394#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]95pub struct CollectionHandle<T: Config> {96 pub id: CollectionId,97 collection: Collection<T::AccountId>,98 pub recorder: SubstrateRecorder<T>,99}100impl<T: Config> WithRecorder<T> for CollectionHandle<T> {101 fn recorder(&self) -> &SubstrateRecorder<T> {102 &self.recorder103 }104 fn into_recorder(self) -> SubstrateRecorder<T> {105 self.recorder106 }107}108impl<T: Config> CollectionHandle<T> {109 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {110 <CollectionById<T>>::get(id).map(|collection| Self {111 id,112 collection,113 recorder: SubstrateRecorder::new(gas_limit),114 })115 }116 pub fn new(id: CollectionId) -> Option<Self> {117 Self::new_with_gas_limit(id, u64::MAX)118 }119 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {120 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)121 }122 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {123 self.recorder124 .consume_gas(T::GasWeightMapping::weight_to_gas(125 <T as frame_system::Config>::DbWeight::get()126 .read127 .saturating_mul(reads),128 ))129 }130 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {131 self.recorder132 .consume_gas(T::GasWeightMapping::weight_to_gas(133 <T as frame_system::Config>::DbWeight::get()134 .write135 .saturating_mul(writes),136 ))137 }138 pub fn save(self) -> DispatchResult {139 <CollectionById<T>>::insert(self.id, self.collection);140 Ok(())141 }142}143impl<T: Config> Deref for CollectionHandle<T> {144 type Target = Collection<T::AccountId>;145146 fn deref(&self) -> &Self::Target {147 &self.collection148 }149}150151impl<T: Config> DerefMut for CollectionHandle<T> {152 fn deref_mut(&mut self) -> &mut Self::Target {153 &mut self.collection154 }155}156157impl<T: Config> CollectionHandle<T> {158 pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {159 ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);160 Ok(())161 }162 pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {163 *subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))164 }165 pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {166 ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);167 Ok(())168 }169 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {170 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)171 }172 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {173 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)174 }175 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {176 ensure!(177 <Allowlist<T>>::get((self.id, user)),178 <Error<T>>::AddressNotInAllowlist179 );180 Ok(())181 }182}183184#[frame_support::pallet]185pub mod pallet {186 use super::*;187 use pallet_evm::account;188 use dispatch::CollectionDispatch;189 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};190 use frame_system::pallet_prelude::*;191 use frame_support::traits::Currency;192 use up_data_structs::{TokenId, mapping::TokenAddressMapping};193 use scale_info::TypeInfo;194 use weights::WeightInfo;195196 #[pallet::config]197 pub trait Config:198 frame_system::Config + pallet_evm_coder_substrate::Config + TypeInfo + account::Config199 {200 type WeightInfo: WeightInfo;201 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;202203 type Currency: Currency<Self::AccountId>;204205 #[pallet::constant]206 type CollectionCreationPrice: Get<207 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,208 >;209 type CollectionDispatch: CollectionDispatch<Self>;210211 type TreasuryAccountId: Get<Self::AccountId>;212213 type EvmTokenAddressMapping: TokenAddressMapping<H160>;214 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;215 }216217 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);218219 #[pallet::pallet]220 #[pallet::storage_version(STORAGE_VERSION)]221 #[pallet::generate_store(pub(super) trait Store)]222 pub struct Pallet<T>(_);223224 #[pallet::extra_constants]225 impl<T: Config> Pallet<T> {226 pub fn collection_admins_limit() -> u32 {227 COLLECTION_ADMINS_LIMIT228 }229 }230231 #[pallet::event]232 #[pallet::generate_deposit(pub fn deposit_event)]233 pub enum Event<T: Config> {234 /// New collection was created235 ///236 /// # Arguments237 ///238 /// * collection_id: Globally unique identifier of newly created collection.239 ///240 /// * mode: [CollectionMode] converted into u8.241 ///242 /// * account_id: Collection owner.243 CollectionCreated(CollectionId, u8, T::AccountId),244245 /// New collection was destroyed246 ///247 /// # Arguments248 ///249 /// * collection_id: Globally unique identifier of collection.250 CollectionDestroyed(CollectionId),251252 /// New item was created.253 ///254 /// # Arguments255 ///256 /// * collection_id: Id of the collection where item was created.257 ///258 /// * item_id: Id of an item. Unique within the collection.259 ///260 /// * recipient: Owner of newly created item261 ///262 /// * amount: Always 1 for NFT263 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),264265 /// Collection item was burned.266 ///267 /// # Arguments268 ///269 /// * collection_id.270 ///271 /// * item_id: Identifier of burned NFT.272 ///273 /// * owner: which user has destroyed its tokens274 ///275 /// * amount: Always 1 for NFT276 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),277278 /// Item was transferred279 ///280 /// * collection_id: Id of collection to which item is belong281 ///282 /// * item_id: Id of an item283 ///284 /// * sender: Original owner of item285 ///286 /// * recipient: New owner of item287 ///288 /// * amount: Always 1 for NFT289 Transfer(290 CollectionId,291 TokenId,292 T::CrossAccountId,293 T::CrossAccountId,294 u128,295 ),296297 /// * collection_id298 ///299 /// * item_id300 ///301 /// * sender302 ///303 /// * spender304 ///305 /// * amount306 Approved(307 CollectionId,308 TokenId,309 T::CrossAccountId,310 T::CrossAccountId,311 u128,312 ),313314 CollectionPropertySet(CollectionId, PropertyKey),315316 CollectionPropertyDeleted(CollectionId, PropertyKey),317318 TokenPropertySet(CollectionId, TokenId, PropertyKey),319320 TokenPropertyDeleted(CollectionId, TokenId, PropertyKey),321322 PropertyPermissionSet(CollectionId, PropertyKey),323 }324325 #[pallet::error]326 pub enum Error<T> {327 /// This collection does not exist.328 CollectionNotFound,329 /// Sender parameter and item owner must be equal.330 MustBeTokenOwner,331 /// No permission to perform action332 NoPermission,333 /// Collection is not in mint mode.334 PublicMintingNotAllowed,335 /// Address is not in allow list.336 AddressNotInAllowlist,337338 /// Collection name can not be longer than 63 char.339 CollectionNameLimitExceeded,340 /// Collection description can not be longer than 255 char.341 CollectionDescriptionLimitExceeded,342 /// Token prefix can not be longer than 15 char.343 CollectionTokenPrefixLimitExceeded,344 /// Total collections bound exceeded.345 TotalCollectionsLimitExceeded,346 /// Exceeded max admin count347 CollectionAdminCountExceeded,348 /// Collection limit bounds per collection exceeded349 CollectionLimitBoundsExceeded,350 /// Tried to enable permissions which are only permitted to be disabled351 OwnerPermissionsCantBeReverted,352 /// Collection settings not allowing items transferring353 TransferNotAllowed,354 /// Account token limit exceeded per collection355 AccountTokenLimitExceeded,356 /// Collection token limit exceeded357 CollectionTokenLimitExceeded,358 /// Metadata flag frozen359 MetadataFlagFrozen,360361 /// Item not exists.362 TokenNotFound,363 /// Item balance not enough.364 TokenValueTooLow,365 /// Requested value more than approved.366 ApprovedValueTooLow,367 /// Tried to approve more than owned368 CantApproveMoreThanOwned,369370 /// Can't transfer tokens to ethereum zero address371 AddressIsZero,372 /// Target collection doesn't supports this operation373 UnsupportedOperation,374375 /// Not sufficient founds to perform action376 NotSufficientFounds,377378 /// Collection has nesting disabled379 NestingIsDisabled,380 /// Only owner may nest tokens under this collection381 OnlyOwnerAllowedToNest,382 /// Only tokens from specific collections may nest tokens under this383 SourceCollectionIsNotAllowedToNest,384385 /// Tried to store more data than allowed in collection field386 CollectionFieldSizeExceeded,387388 /// Tried to store more property data than allowed389 NoSpaceForProperty,390391 /// Tried to store more property keys than allowed392 PropertyLimitReached,393394 /// Property key is too long395 PropertyKeyIsTooLong,396397 /// Only ASCII letters, digits, and '_', '-' are allowed398 InvalidCharacterInPropertyKey,399400 /// Empty property keys are forbidden401 EmptyPropertyKey,402 }403404 #[pallet::storage]405 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;406 #[pallet::storage]407 pub type DestroyedCollectionCount<T> =408 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;409410 /// Collection info411 #[pallet::storage]412 pub type CollectionById<T> = StorageMap<413 Hasher = Blake2_128Concat,414 Key = CollectionId,415 Value = Collection<<T as frame_system::Config>::AccountId>,416 QueryKind = OptionQuery,417 >;418419 /// Collection properties420 #[pallet::storage]421 #[pallet::getter(fn collection_properties)]422 pub type CollectionProperties<T> = StorageMap<423 Hasher = Blake2_128Concat,424 Key = CollectionId,425 Value = Properties,426 QueryKind = ValueQuery,427 OnEmpty = up_data_structs::CollectionProperties,428 >;429430 #[pallet::storage]431 #[pallet::getter(fn property_permissions)]432 pub type CollectionPropertyPermissions<T> = StorageMap<433 Hasher = Blake2_128Concat,434 Key = CollectionId,435 Value = PropertiesPermissionMap,436 QueryKind = ValueQuery,437 >;438439 /// Large variable-size collection fields are extracted here440 #[pallet::storage]441 pub type CollectionData<T> = StorageNMap<442 Key = (443 Key<Twox64Concat, CollectionId>,444 Key<Twox64Concat, CollectionField>,445 ),446 Value = BoundedVec<u8, ConstU32<COLLECTION_FIELD_LIMIT>>,447 QueryKind = ValueQuery,448 >;449450 #[pallet::storage]451 pub type AdminAmount<T> = StorageMap<452 Hasher = Blake2_128Concat,453 Key = CollectionId,454 Value = u32,455 QueryKind = ValueQuery,456 >;457458 /// List of collection admins459 #[pallet::storage]460 pub type IsAdmin<T: Config> = StorageNMap<461 Key = (462 Key<Blake2_128Concat, CollectionId>,463 Key<Blake2_128Concat, T::CrossAccountId>,464 ),465 Value = bool,466 QueryKind = ValueQuery,467 >;468469 /// Allowlisted collection users470 #[pallet::storage]471 pub type Allowlist<T: Config> = StorageNMap<472 Key = (473 Key<Blake2_128Concat, CollectionId>,474 Key<Blake2_128Concat, T::CrossAccountId>,475 ),476 Value = bool,477 QueryKind = ValueQuery,478 >;479480 /// Not used by code, exists only to provide some types to metadata481 #[pallet::storage]482 pub type DummyStorageValue<T: Config> = StorageValue<483 Value = (484 CollectionStats,485 CollectionId,486 TokenId,487 PhantomType<TokenData<T::CrossAccountId>>,488 PhantomType<RpcCollection<T::AccountId>>,489 // RMRK490 PhantomType<RmrkCollectionInfo<T::AccountId>>,491 PhantomType<RmrkInstanceInfo<T::AccountId>>,492 PhantomType<RmrkResourceInfo>,493 PhantomType<RmrkPropertyInfo>,494 PhantomType<RmrkBaseInfo<T::AccountId>>,495 PhantomType<RmrkPartType>,496 PhantomType<RmrkTheme>,497 PhantomType<RmrkNftChild>,498 ),499 QueryKind = OptionQuery,500 >;501502 #[pallet::hooks]503 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {504 fn on_runtime_upgrade() -> Weight {505 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {506 use up_data_structs::{CollectionVersion1, CollectionVersion2};507 <CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {508 Self::set_field_raw(509 id,510 CollectionField::OffchainSchema,511 v.offchain_schema.clone().into_inner(),512 )513 .expect("data has lower bounds than field");514 Self::set_field_raw(515 id,516 CollectionField::ConstOnChainSchema,517 v.const_on_chain_schema.clone().into_inner(),518 )519 .expect("data has lower bounds than field");520521 Some(CollectionVersion2::from(v))522 });523 }524525 0526 }527 }528}529530impl<T: Config> Pallet<T> {531 /// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens532 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {533 ensure!(534 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,535 <Error<T>>::AddressIsZero536 );537 Ok(())538 }539 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {540 <IsAdmin<T>>::iter_prefix((collection,))541 .map(|(a, _)| a)542 .collect()543 }544 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {545 <Allowlist<T>>::iter_prefix((collection,))546 .map(|(a, _)| a)547 .collect()548 }549 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {550 <Allowlist<T>>::get((collection, user))551 }552 pub fn collection_stats() -> CollectionStats {553 let created = <CreatedCollectionCount<T>>::get();554 let destroyed = <DestroyedCollectionCount<T>>::get();555 CollectionStats {556 created: created.0,557 destroyed: destroyed.0,558 alive: created.0 - destroyed.0,559 }560 }561562 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {563 let collection = <CollectionById<T>>::get(collection);564 if collection.is_none() {565 return None;566 }567568 let collection = collection.unwrap();569 let limits = collection.limits;570 let effective_limits = CollectionLimits {571 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),572 sponsored_data_size: Some(limits.sponsored_data_size()),573 sponsored_data_rate_limit: Some(574 limits575 .sponsored_data_rate_limit576 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),577 ),578 token_limit: Some(limits.token_limit()),579 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(580 match collection.mode {581 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,582 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,583 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,584 },585 )),586 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),587 owner_can_transfer: Some(limits.owner_can_transfer()),588 owner_can_destroy: Some(limits.owner_can_destroy()),589 transfers_enabled: Some(limits.transfers_enabled()),590 nesting_rule: Some(limits.nesting_rule().clone()),591 };592593 Some(effective_limits)594 }595596 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {597 let Collection {598 name,599 description,600 owner,601 mode,602 access,603 token_prefix,604 mint_mode,605 schema_version,606 sponsorship,607 limits,608 } = <CollectionById<T>>::get(collection)?;609610 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)611 .into_iter()612 .map(|(key, permission)| PropertyKeyPermission {613 key,614 permission,615 })616 .collect();617618 let properties = <CollectionProperties<T>>::get(collection)619 .into_iter()620 .map(|(key, value)| Property {621 key,622 value,623 })624 .collect();625626 Some(RpcCollection {627 name: name.into_inner(),628 description: description.into_inner(),629 owner,630 mode,631 access,632 token_prefix: token_prefix.into_inner(),633 mint_mode,634 schema_version,635 sponsorship,636 limits,637 offchain_schema: <CollectionData<T>>::get((638 collection,639 CollectionField::OffchainSchema,640 ))641 .into_inner(),642 const_on_chain_schema: <CollectionData<T>>::get((643 collection,644 CollectionField::ConstOnChainSchema,645 ))646 .into_inner(),647 token_property_permissions,648 properties,649 })650 }651}652653impl<T: Config> Pallet<T> {654 pub fn init_collection(655 owner: T::AccountId,656 data: CreateCollectionData<T::AccountId>,657 ) -> Result<CollectionId, DispatchError> {658 {659 ensure!(660 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,661 Error::<T>::CollectionTokenPrefixLimitExceeded662 );663 }664665 let created_count = <CreatedCollectionCount<T>>::get()666 .0667 .checked_add(1)668 .ok_or(ArithmeticError::Overflow)?;669 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;670 let id = CollectionId(created_count);671672 // bound Total number of collections673 ensure!(674 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,675 <Error<T>>::TotalCollectionsLimitExceeded676 );677678 // =========679680 let collection = Collection {681 owner: owner.clone(),682 name: data.name,683 mode: data.mode.clone(),684 mint_mode: false,685 access: data.access.unwrap_or_default(),686 description: data.description,687 token_prefix: data.token_prefix,688 schema_version: data.schema_version.unwrap_or_default(),689 sponsorship: data690 .pending_sponsor691 .map(SponsorshipState::Unconfirmed)692 .unwrap_or_default(),693 limits: data694 .limits695 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))696 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,697 };698699 let mut collection_properties = up_data_structs::CollectionProperties::get();700 collection_properties701 .try_set_from_iter(data.properties.into_iter())702 .map_err(<Error<T>>::from)?;703704 CollectionProperties::<T>::insert(id, collection_properties);705706 let mut token_props_permissions = PropertiesPermissionMap::new();707 token_props_permissions708 .try_set_from_iter(data.token_property_permissions.into_iter())709 .map_err(<Error<T>>::from)?;710711 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);712713 // Take a (non-refundable) deposit of collection creation714 {715 let mut imbalance =716 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();717 imbalance.subsume(718 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(719 &T::TreasuryAccountId::get(),720 T::CollectionCreationPrice::get(),721 ),722 );723 <T as Config>::Currency::settle(724 &owner,725 imbalance,726 WithdrawReasons::TRANSFER,727 ExistenceRequirement::KeepAlive,728 )729 .map_err(|_| Error::<T>::NotSufficientFounds)?;730 }731732 <CreatedCollectionCount<T>>::put(created_count);733 <Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));734 <CollectionById<T>>::insert(id, collection);735 Self::set_field_raw(736 id,737 CollectionField::OffchainSchema,738 data.offchain_schema.into_inner(),739 )740 .expect("data has lower bounds than field");741 Self::set_field_raw(742 id,743 CollectionField::ConstOnChainSchema,744 data.const_on_chain_schema.into_inner(),745 )746 .expect("data has lower bounds than field");747 Ok(id)748 }749750 pub fn destroy_collection(751 collection: CollectionHandle<T>,752 sender: &T::CrossAccountId,753 ) -> DispatchResult {754 ensure!(755 collection.limits.owner_can_destroy(),756 <Error<T>>::NoPermission,757 );758 collection.check_is_owner(sender)?;759760 let destroyed_collections = <DestroyedCollectionCount<T>>::get()761 .0762 .checked_add(1)763 .ok_or(ArithmeticError::Overflow)?;764765 // =========766767 <DestroyedCollectionCount<T>>::put(destroyed_collections);768 <CollectionById<T>>::remove(collection.id);769 <CollectionData<T>>::remove_prefix((collection.id,), None);770 <AdminAmount<T>>::remove(collection.id);771 <IsAdmin<T>>::remove_prefix((collection.id,), None);772 <Allowlist<T>>::remove_prefix((collection.id,), None);773 <CollectionProperties<T>>::remove(collection.id);774775 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));776 Ok(())777 }778779 pub fn set_collection_property(780 collection: &CollectionHandle<T>,781 sender: &T::CrossAccountId,782 property: Property,783 ) -> DispatchResult {784 collection.check_is_owner_or_admin(sender)?;785786 CollectionProperties::<T>::try_mutate(collection.id, |properties| {787 let property = property.clone();788 properties.try_set(property.key, property.value)789 })790 .map_err(<Error<T>>::from)?;791792 Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));793794 Ok(())795 }796797 pub fn set_scoped_collection_property(798 collection_id: CollectionId,799 scope: PropertyScope,800 property: Property,801 ) -> DispatchResult {802 CollectionProperties::<T>::try_mutate(collection_id, |properties| {803 properties.try_scoped_set(scope, property.key, property.value)804 })805 .map_err(<Error<T>>::from)?;806807 Ok(())808 }809810 pub fn set_scoped_collection_properties(811 collection_id: CollectionId,812 scope: PropertyScope,813 properties: impl Iterator<Item = Property>,814 ) -> DispatchResult {815 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {816 stored_properties.try_scoped_set_from_iter(scope, properties)817 })818 .map_err(<Error<T>>::from)?;819820 Ok(())821 }822823 #[transactional]824 pub fn set_collection_properties(825 collection: &CollectionHandle<T>,826 sender: &T::CrossAccountId,827 properties: Vec<Property>,828 ) -> DispatchResult {829 for property in properties {830 Self::set_collection_property(collection, sender, property)?;831 }832833 Ok(())834 }835836 pub fn delete_collection_property(837 collection: &CollectionHandle<T>,838 sender: &T::CrossAccountId,839 property_key: PropertyKey,840 ) -> DispatchResult {841 collection.check_is_owner_or_admin(sender)?;842843 CollectionProperties::<T>::try_mutate(collection.id, |properties| {844 properties.remove(&property_key)845 })846 .map_err(<Error<T>>::from)?;847848 Self::deposit_event(Event::CollectionPropertyDeleted(849 collection.id,850 property_key,851 ));852853 Ok(())854 }855856 #[transactional]857 pub fn delete_collection_properties(858 collection: &CollectionHandle<T>,859 sender: &T::CrossAccountId,860 property_keys: Vec<PropertyKey>,861 ) -> DispatchResult {862 for key in property_keys {863 Self::delete_collection_property(collection, sender, key)?;864 }865866 Ok(())867 }868869 pub fn set_property_permission(870 collection: &CollectionHandle<T>,871 sender: &T::CrossAccountId,872 property_permission: PropertyKeyPermission,873 ) -> DispatchResult {874 collection.check_is_owner_or_admin(sender)?;875876 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);877 let current_permission = all_permissions.get(&property_permission.key);878 if matches![879 current_permission,880 Some(PropertyPermission { mutable: false, .. })881 ] {882 return Err(<Error<T>>::NoPermission.into());883 }884885 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {886 let property_permission = property_permission.clone();887 permissions.try_set(property_permission.key, property_permission.permission)888 })889 .map_err(<Error<T>>::from)?;890891 Self::deposit_event(Event::PropertyPermissionSet(892 collection.id,893 property_permission.key,894 ));895896 Ok(())897 }898899 #[transactional]900 pub fn set_property_permissions(901 collection: &CollectionHandle<T>,902 sender: &T::CrossAccountId,903 property_permissions: Vec<PropertyKeyPermission>,904 ) -> DispatchResult {905 for prop_pemission in property_permissions {906 Self::set_property_permission(collection, sender, prop_pemission)?;907 }908909 Ok(())910 }911912 pub fn get_collection_property(913 collection_id: CollectionId,914 key: &PropertyKey,915 ) -> Option<PropertyValue> {916 Self::collection_properties(collection_id).get(key).cloned()917 }918919 pub fn bytes_keys_to_property_keys(920 keys: Vec<Vec<u8>>,921 ) -> Result<Vec<PropertyKey>, DispatchError> {922 keys.into_iter()923 .map(|key| -> Result<PropertyKey, DispatchError> {924 key.try_into()925 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())926 })927 .collect::<Result<Vec<PropertyKey>, DispatchError>>()928 }929930 pub fn filter_collection_properties(931 collection_id: CollectionId,932 keys: Option<Vec<PropertyKey>>,933 ) -> Result<Vec<Property>, DispatchError> {934 let properties = Self::collection_properties(collection_id);935936 let properties = keys937 .map(|keys| {938 keys.into_iter()939 .filter_map(|key| {940 properties.get(&key).map(|value| Property {941 key,942 value: value.clone(),943 })944 })945 .collect()946 })947 .unwrap_or_else(|| {948 properties949 .into_iter()950 .map(|(key, value)| Property {951 key,952 value,953 })954 .collect()955 });956957 Ok(properties)958 }959960 pub fn filter_property_permissions(961 collection_id: CollectionId,962 keys: Option<Vec<PropertyKey>>,963 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {964 let permissions = Self::property_permissions(collection_id);965966 let key_permissions = keys967 .map(|keys| {968 keys.into_iter()969 .filter_map(|key| {970 permissions971 .get(&key)972 .map(|permission| PropertyKeyPermission {973 key,974 permission: permission.clone(),975 })976 })977 .collect()978 })979 .unwrap_or_else(|| {980 permissions981 .into_iter()982 .map(|(key, permission)| PropertyKeyPermission {983 key,984 permission,985 })986 .collect()987 });988989 Ok(key_permissions)990 }991992 fn set_field_raw(993 collection_id: CollectionId,994 field: CollectionField,995 value: Vec<u8>,996 ) -> DispatchResult {997 if !value.is_empty() {998 <CollectionData<T>>::insert(999 (collection_id, field),1000 BoundedVec::try_from(value).map_err(|_| <Error<T>>::CollectionFieldSizeExceeded)?,1001 )1002 } else {1003 <CollectionData<T>>::remove((collection_id, field));1004 }1005 Ok(())1006 }10071008 pub fn set_field(1009 collection: &CollectionHandle<T>,1010 sender: &T::CrossAccountId,1011 field: CollectionField,1012 value: Vec<u8>,1013 ) -> DispatchResult {1014 collection.check_is_owner_or_admin(sender)?;10151016 // =========10171018 Self::set_field_raw(collection.id, field, value)1019 }10201021 pub fn toggle_allowlist(1022 collection: &CollectionHandle<T>,1023 sender: &T::CrossAccountId,1024 user: &T::CrossAccountId,1025 allowed: bool,1026 ) -> DispatchResult {1027 collection.check_is_owner_or_admin(sender)?;10281029 // =========10301031 if allowed {1032 <Allowlist<T>>::insert((collection.id, user), true);1033 } else {1034 <Allowlist<T>>::remove((collection.id, user));1035 }10361037 Ok(())1038 }10391040 pub fn toggle_admin(1041 collection: &CollectionHandle<T>,1042 sender: &T::CrossAccountId,1043 user: &T::CrossAccountId,1044 admin: bool,1045 ) -> DispatchResult {1046 collection.check_is_owner_or_admin(sender)?;10471048 let was_admin = <IsAdmin<T>>::get((collection.id, user));1049 if was_admin == admin {1050 return Ok(());1051 }1052 let amount = <AdminAmount<T>>::get(collection.id);10531054 if admin {1055 let amount = amount1056 .checked_add(1)1057 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1058 ensure!(1059 amount <= Self::collection_admins_limit(),1060 <Error<T>>::CollectionAdminCountExceeded,1061 );10621063 // =========10641065 <AdminAmount<T>>::insert(collection.id, amount);1066 <IsAdmin<T>>::insert((collection.id, user), true);1067 } else {1068 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1069 <IsAdmin<T>>::remove((collection.id, user));1070 }10711072 Ok(())1073 }10741075 pub fn clamp_limits(1076 mode: CollectionMode,1077 old_limit: &CollectionLimits,1078 mut new_limit: CollectionLimits,1079 ) -> Result<CollectionLimits, DispatchError> {1080 macro_rules! limit_default {1081 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1082 $(1083 if let Some($new) = $new.$field {1084 let $old = $old.$field($($arg)?);1085 let _ = $new;1086 let _ = $old;1087 $check1088 } else {1089 $new.$field = $old.$field1090 }1091 )*1092 }};1093 }10941095 limit_default!(old_limit, new_limit,1096 account_token_ownership_limit => ensure!(1097 new_limit <= MAX_TOKEN_OWNERSHIP,1098 <Error<T>>::CollectionLimitBoundsExceeded,1099 ),1100 sponsor_transfer_timeout(match mode {1101 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1102 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1103 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1104 }) => ensure!(1105 new_limit <= MAX_SPONSOR_TIMEOUT,1106 <Error<T>>::CollectionLimitBoundsExceeded,1107 ),1108 sponsored_data_size => ensure!(1109 new_limit <= CUSTOM_DATA_LIMIT,1110 <Error<T>>::CollectionLimitBoundsExceeded,1111 ),1112 token_limit => ensure!(1113 old_limit >= new_limit && new_limit > 0,1114 <Error<T>>::CollectionTokenLimitExceeded1115 ),1116 owner_can_transfer => ensure!(1117 old_limit || !new_limit,1118 <Error<T>>::OwnerPermissionsCantBeReverted,1119 ),1120 owner_can_destroy => ensure!(1121 old_limit || !new_limit,1122 <Error<T>>::OwnerPermissionsCantBeReverted,1123 ),1124 sponsored_data_rate_limit => {},1125 transfers_enabled => {},1126 );1127 Ok(new_limit)1128 }1129}11301131#[macro_export]1132macro_rules! unsupported {1133 () => {1134 Err(<Error<T>>::UnsupportedOperation.into())1135 };1136}11371138/// Worst cases1139pub trait CommonWeightInfo<CrossAccountId> {1140 fn create_item() -> Weight;1141 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;1142 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1143 fn burn_item() -> Weight;1144 fn set_collection_properties(amount: u32) -> Weight;1145 fn delete_collection_properties(amount: u32) -> Weight;1146 fn set_token_properties(amount: u32) -> Weight;1147 fn delete_token_properties(amount: u32) -> Weight;1148 fn set_property_permissions(amount: u32) -> Weight;1149 fn transfer() -> Weight;1150 fn approve() -> Weight;1151 fn transfer_from() -> Weight;1152 fn burn_from() -> Weight;1153}11541155pub trait CommonCollectionOperations<T: Config> {1156 fn create_item(1157 &self,1158 sender: T::CrossAccountId,1159 to: T::CrossAccountId,1160 data: CreateItemData,1161 nesting_budget: &dyn Budget,1162 ) -> DispatchResultWithPostInfo;1163 fn create_multiple_items(1164 &self,1165 sender: T::CrossAccountId,1166 to: T::CrossAccountId,1167 data: Vec<CreateItemData>,1168 nesting_budget: &dyn Budget,1169 ) -> DispatchResultWithPostInfo;1170 fn create_multiple_items_ex(1171 &self,1172 sender: T::CrossAccountId,1173 data: CreateItemExData<T::CrossAccountId>,1174 nesting_budget: &dyn Budget,1175 ) -> DispatchResultWithPostInfo;1176 fn burn_item(1177 &self,1178 sender: T::CrossAccountId,1179 token: TokenId,1180 amount: u128,1181 ) -> DispatchResultWithPostInfo;1182 fn set_collection_properties(1183 &self,1184 sender: T::CrossAccountId,1185 properties: Vec<Property>,1186 ) -> DispatchResultWithPostInfo;1187 fn delete_collection_properties(1188 &self,1189 sender: &T::CrossAccountId,1190 property_keys: Vec<PropertyKey>,1191 ) -> DispatchResultWithPostInfo;1192 fn set_token_properties(1193 &self,1194 sender: T::CrossAccountId,1195 token_id: TokenId,1196 property: Vec<Property>,1197 ) -> DispatchResultWithPostInfo;1198 fn delete_token_properties(1199 &self,1200 sender: T::CrossAccountId,1201 token_id: TokenId,1202 property_keys: Vec<PropertyKey>,1203 ) -> DispatchResultWithPostInfo;1204 fn set_property_permissions(1205 &self,1206 sender: &T::CrossAccountId,1207 property_permissions: Vec<PropertyKeyPermission>,1208 ) -> DispatchResultWithPostInfo;1209 fn transfer(1210 &self,1211 sender: T::CrossAccountId,1212 to: T::CrossAccountId,1213 token: TokenId,1214 amount: u128,1215 nesting_budget: &dyn Budget,1216 ) -> DispatchResultWithPostInfo;1217 fn approve(1218 &self,1219 sender: T::CrossAccountId,1220 spender: T::CrossAccountId,1221 token: TokenId,1222 amount: u128,1223 ) -> DispatchResultWithPostInfo;1224 fn transfer_from(1225 &self,1226 sender: T::CrossAccountId,1227 from: T::CrossAccountId,1228 to: T::CrossAccountId,1229 token: TokenId,1230 amount: u128,1231 nesting_budget: &dyn Budget,1232 ) -> DispatchResultWithPostInfo;1233 fn burn_from(1234 &self,1235 sender: T::CrossAccountId,1236 from: T::CrossAccountId,1237 token: TokenId,1238 amount: u128,1239 nesting_budget: &dyn Budget,1240 ) -> DispatchResultWithPostInfo;12411242 fn check_nesting(1243 &self,1244 sender: T::CrossAccountId,1245 from: (CollectionId, TokenId),1246 under: TokenId,1247 budget: &dyn Budget,1248 ) -> DispatchResult;12491250 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1251 fn collection_tokens(&self) -> Vec<TokenId>;1252 fn token_exists(&self, token: TokenId) -> bool;1253 fn last_token_id(&self) -> TokenId;12541255 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1256 fn const_metadata(&self, token: TokenId) -> Vec<u8>;1257 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;1258 fn token_properties(&self, token_id: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;1259 /// Amount of unique collection tokens1260 fn total_supply(&self) -> u32;1261 /// Amount of different tokens account has (Applicable to nonfungible/refungible)1262 fn account_balance(&self, account: T::CrossAccountId) -> u32;1263 /// Amount of specific token account have (Applicable to fungible/refungible)1264 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1265 fn allowance(1266 &self,1267 sender: T::CrossAccountId,1268 spender: T::CrossAccountId,1269 token: TokenId,1270 ) -> u128;1271}12721273// Flexible enough for implementing CommonCollectionOperations1274pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1275 let post_info = PostDispatchInfo {1276 actual_weight: Some(weight),1277 pays_fee: Pays::Yes,1278 };1279 match res {1280 Ok(()) => Ok(post_info),1281 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1282 }1283}12841285impl<T: Config> From<PropertiesError> for Error<T> {1286 fn from(error: PropertiesError) -> Self {1287 match error {1288 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1289 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1290 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1291 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1292 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1293 }1294 }1295}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)]1819extern crate alloc;2021use core::ops::{Deref, DerefMut};22use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};23use sp_std::vec::Vec;24use pallet_evm::account::CrossAccountId;25use frame_support::{26 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},27 ensure,28 traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},29 BoundedVec,30 weights::Pays,31 transactional,32};33use pallet_evm::GasWeightMapping;34use up_data_structs::{35 COLLECTION_NUMBER_LIMIT,36 Collection,37 RpcCollection,38 CollectionId,39 CreateItemData,40 MAX_TOKEN_PREFIX_LENGTH,41 COLLECTION_ADMINS_LIMIT,42 TokenId,43 CollectionStats,44 MAX_TOKEN_OWNERSHIP,45 CollectionMode,46 NFT_SPONSOR_TRANSFER_TIMEOUT,47 FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,48 REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,49 MAX_SPONSOR_TIMEOUT,50 CUSTOM_DATA_LIMIT,51 CollectionLimits,52 CreateCollectionData,53 SponsorshipState,54 CreateItemExData,55 SponsoringRateLimit,56 budget::Budget,57 COLLECTION_FIELD_LIMIT,58 PhantomType,59 Property,60 Properties,61 PropertiesPermissionMap,62 PropertyKey,63 PropertyValue,64 PropertyPermission,65 PropertiesError,66 PropertyKeyPermission,67 TokenData,68 TrySetProperty,69 PropertyScope,70 // RMRK71 RmrkCollectionInfo,72 RmrkInstanceInfo,73 RmrkResourceInfo,74 RmrkPropertyInfo,75 RmrkBaseInfo,76 RmrkPartType,77 RmrkTheme,78 RmrkNftChild,79 CollectionPermissions,80 SchemaVersion,81};8283pub use pallet::*;84use sp_core::H160;85use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};86#[cfg(feature = "runtime-benchmarks")]87pub mod benchmarking;88pub mod dispatch;89pub mod erc;90pub mod eth;91pub mod weights;9293pub type SelfWeightOf<T> = <T as Config>::WeightInfo;9495#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]96pub struct CollectionHandle<T: Config> {97 pub id: CollectionId,98 collection: Collection<T::AccountId>,99 pub recorder: SubstrateRecorder<T>,100}101impl<T: Config> WithRecorder<T> for CollectionHandle<T> {102 fn recorder(&self) -> &SubstrateRecorder<T> {103 &self.recorder104 }105 fn into_recorder(self) -> SubstrateRecorder<T> {106 self.recorder107 }108}109impl<T: Config> CollectionHandle<T> {110 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {111 <CollectionById<T>>::get(id).map(|collection| Self {112 id,113 collection,114 recorder: SubstrateRecorder::new(gas_limit),115 })116 }117 pub fn new(id: CollectionId) -> Option<Self> {118 Self::new_with_gas_limit(id, u64::MAX)119 }120 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {121 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)122 }123 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {124 self.recorder125 .consume_gas(T::GasWeightMapping::weight_to_gas(126 <T as frame_system::Config>::DbWeight::get()127 .read128 .saturating_mul(reads),129 ))130 }131 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {132 self.recorder133 .consume_gas(T::GasWeightMapping::weight_to_gas(134 <T as frame_system::Config>::DbWeight::get()135 .write136 .saturating_mul(writes),137 ))138 }139 pub fn save(self) -> DispatchResult {140 <CollectionById<T>>::insert(self.id, self.collection);141 Ok(())142 }143}144impl<T: Config> Deref for CollectionHandle<T> {145 type Target = Collection<T::AccountId>;146147 fn deref(&self) -> &Self::Target {148 &self.collection149 }150}151152impl<T: Config> DerefMut for CollectionHandle<T> {153 fn deref_mut(&mut self) -> &mut Self::Target {154 &mut self.collection155 }156}157158impl<T: Config> CollectionHandle<T> {159 pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {160 ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);161 Ok(())162 }163 pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {164 *subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))165 }166 pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {167 ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);168 Ok(())169 }170 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {171 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)172 }173 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {174 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)175 }176 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {177 ensure!(178 <Allowlist<T>>::get((self.id, user)),179 <Error<T>>::AddressNotInAllowlist180 );181 Ok(())182 }183}184185#[frame_support::pallet]186pub mod pallet {187 use super::*;188 use pallet_evm::account;189 use dispatch::CollectionDispatch;190 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};191 use frame_system::pallet_prelude::*;192 use frame_support::traits::Currency;193 use up_data_structs::{TokenId, mapping::TokenAddressMapping};194 use scale_info::TypeInfo;195 use weights::WeightInfo;196197 #[pallet::config]198 pub trait Config:199 frame_system::Config + pallet_evm_coder_substrate::Config + TypeInfo + account::Config200 {201 type WeightInfo: WeightInfo;202 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;203204 type Currency: Currency<Self::AccountId>;205206 #[pallet::constant]207 type CollectionCreationPrice: Get<208 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,209 >;210 type CollectionDispatch: CollectionDispatch<Self>;211212 type TreasuryAccountId: Get<Self::AccountId>;213214 type EvmTokenAddressMapping: TokenAddressMapping<H160>;215 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;216 }217218 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);219220 #[pallet::pallet]221 #[pallet::storage_version(STORAGE_VERSION)]222 #[pallet::generate_store(pub(super) trait Store)]223 pub struct Pallet<T>(_);224225 #[pallet::extra_constants]226 impl<T: Config> Pallet<T> {227 pub fn collection_admins_limit() -> u32 {228 COLLECTION_ADMINS_LIMIT229 }230 }231232 #[pallet::event]233 #[pallet::generate_deposit(pub fn deposit_event)]234 pub enum Event<T: Config> {235 /// New collection was created236 ///237 /// # Arguments238 ///239 /// * collection_id: Globally unique identifier of newly created collection.240 ///241 /// * mode: [CollectionMode] converted into u8.242 ///243 /// * account_id: Collection owner.244 CollectionCreated(CollectionId, u8, T::AccountId),245246 /// New collection was destroyed247 ///248 /// # Arguments249 ///250 /// * collection_id: Globally unique identifier of collection.251 CollectionDestroyed(CollectionId),252253 /// New item was created.254 ///255 /// # Arguments256 ///257 /// * collection_id: Id of the collection where item was created.258 ///259 /// * item_id: Id of an item. Unique within the collection.260 ///261 /// * recipient: Owner of newly created item262 ///263 /// * amount: Always 1 for NFT264 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),265266 /// Collection item was burned.267 ///268 /// # Arguments269 ///270 /// * collection_id.271 ///272 /// * item_id: Identifier of burned NFT.273 ///274 /// * owner: which user has destroyed its tokens275 ///276 /// * amount: Always 1 for NFT277 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),278279 /// Item was transferred280 ///281 /// * collection_id: Id of collection to which item is belong282 ///283 /// * item_id: Id of an item284 ///285 /// * sender: Original owner of item286 ///287 /// * recipient: New owner of item288 ///289 /// * amount: Always 1 for NFT290 Transfer(291 CollectionId,292 TokenId,293 T::CrossAccountId,294 T::CrossAccountId,295 u128,296 ),297298 /// * collection_id299 ///300 /// * item_id301 ///302 /// * sender303 ///304 /// * spender305 ///306 /// * amount307 Approved(308 CollectionId,309 TokenId,310 T::CrossAccountId,311 T::CrossAccountId,312 u128,313 ),314315 CollectionPropertySet(CollectionId, PropertyKey),316317 CollectionPropertyDeleted(CollectionId, PropertyKey),318319 TokenPropertySet(CollectionId, TokenId, PropertyKey),320321 TokenPropertyDeleted(CollectionId, TokenId, PropertyKey),322323 PropertyPermissionSet(CollectionId, PropertyKey),324 }325326 #[pallet::error]327 pub enum Error<T> {328 /// This collection does not exist.329 CollectionNotFound,330 /// Sender parameter and item owner must be equal.331 MustBeTokenOwner,332 /// No permission to perform action333 NoPermission,334 /// Collection is not in mint mode.335 PublicMintingNotAllowed,336 /// Address is not in allow list.337 AddressNotInAllowlist,338339 /// Collection name can not be longer than 63 char.340 CollectionNameLimitExceeded,341 /// Collection description can not be longer than 255 char.342 CollectionDescriptionLimitExceeded,343 /// Token prefix can not be longer than 15 char.344 CollectionTokenPrefixLimitExceeded,345 /// Total collections bound exceeded.346 TotalCollectionsLimitExceeded,347 /// Exceeded max admin count348 CollectionAdminCountExceeded,349 /// Collection limit bounds per collection exceeded350 CollectionLimitBoundsExceeded,351 /// Tried to enable permissions which are only permitted to be disabled352 OwnerPermissionsCantBeReverted,353 /// Collection settings not allowing items transferring354 TransferNotAllowed,355 /// Account token limit exceeded per collection356 AccountTokenLimitExceeded,357 /// Collection token limit exceeded358 CollectionTokenLimitExceeded,359 /// Metadata flag frozen360 MetadataFlagFrozen,361362 /// Item not exists.363 TokenNotFound,364 /// Item balance not enough.365 TokenValueTooLow,366 /// Requested value more than approved.367 ApprovedValueTooLow,368 /// Tried to approve more than owned369 CantApproveMoreThanOwned,370371 /// Can't transfer tokens to ethereum zero address372 AddressIsZero,373 /// Target collection doesn't supports this operation374 UnsupportedOperation,375376 /// Not sufficient founds to perform action377 NotSufficientFounds,378379 /// Collection has nesting disabled380 NestingIsDisabled,381 /// Only owner may nest tokens under this collection382 OnlyOwnerAllowedToNest,383 /// Only tokens from specific collections may nest tokens under this384 SourceCollectionIsNotAllowedToNest,385386 /// Tried to store more data than allowed in collection field387 CollectionFieldSizeExceeded,388389 /// Tried to store more property data than allowed390 NoSpaceForProperty,391392 /// Tried to store more property keys than allowed393 PropertyLimitReached,394395 /// Property key is too long396 PropertyKeyIsTooLong,397398 /// Only ASCII letters, digits, and '_', '-' are allowed399 InvalidCharacterInPropertyKey,400401 /// Empty property keys are forbidden402 EmptyPropertyKey,403 }404405 #[pallet::storage]406 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;407 #[pallet::storage]408 pub type DestroyedCollectionCount<T> =409 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;410411 /// Collection info412 #[pallet::storage]413 pub type CollectionById<T> = StorageMap<414 Hasher = Blake2_128Concat,415 Key = CollectionId,416 Value = Collection<<T as frame_system::Config>::AccountId>,417 QueryKind = OptionQuery,418 >;419420 /// Collection properties421 #[pallet::storage]422 #[pallet::getter(fn collection_properties)]423 pub type CollectionProperties<T> = StorageMap<424 Hasher = Blake2_128Concat,425 Key = CollectionId,426 Value = Properties,427 QueryKind = ValueQuery,428 OnEmpty = up_data_structs::CollectionProperties,429 >;430431 #[pallet::storage]432 #[pallet::getter(fn property_permissions)]433 pub type CollectionPropertyPermissions<T> = StorageMap<434 Hasher = Blake2_128Concat,435 Key = CollectionId,436 Value = PropertiesPermissionMap,437 QueryKind = ValueQuery,438 >;439440 #[pallet::storage]441 pub type AdminAmount<T> = StorageMap<442 Hasher = Blake2_128Concat,443 Key = CollectionId,444 Value = u32,445 QueryKind = ValueQuery,446 >;447448 /// List of collection admins449 #[pallet::storage]450 pub type IsAdmin<T: Config> = StorageNMap<451 Key = (452 Key<Blake2_128Concat, CollectionId>,453 Key<Blake2_128Concat, T::CrossAccountId>,454 ),455 Value = bool,456 QueryKind = ValueQuery,457 >;458459 /// Allowlisted collection users460 #[pallet::storage]461 pub type Allowlist<T: Config> = StorageNMap<462 Key = (463 Key<Blake2_128Concat, CollectionId>,464 Key<Blake2_128Concat, T::CrossAccountId>,465 ),466 Value = bool,467 QueryKind = ValueQuery,468 >;469470 /// Not used by code, exists only to provide some types to metadata471 #[pallet::storage]472 pub type DummyStorageValue<T: Config> = StorageValue<473 Value = (474 CollectionStats,475 CollectionId,476 TokenId,477 PhantomType<TokenData<T::CrossAccountId>>,478 PhantomType<RpcCollection<T::AccountId>>,479 // RMRK480 PhantomType<RmrkCollectionInfo<T::AccountId>>,481 PhantomType<RmrkInstanceInfo<T::AccountId>>,482 PhantomType<RmrkResourceInfo>,483 PhantomType<RmrkPropertyInfo>,484 PhantomType<RmrkBaseInfo<T::AccountId>>,485 PhantomType<RmrkPartType>,486 PhantomType<RmrkTheme>,487 PhantomType<RmrkNftChild>,488 ),489 QueryKind = OptionQuery,490 >;491492 #[pallet::hooks]493 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {494 fn on_runtime_upgrade() -> Weight {495 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {496 use up_data_structs::{CollectionVersion1, CollectionVersion2};497 <CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {498 let mut props = Vec::new();499 if !v.offchain_schema.is_empty() {500 props.push(Property {501 key: b"_old_offchainSchema".to_vec().try_into().unwrap(),502 value: v.offchain_schema.clone().into_inner().try_into().expect("offchain schema too big"),503 });504 }505 if !v.variable_on_chain_schema.is_empty() {506 props.push(Property {507 key: b"_old_variableOnChainSchema".to_vec().try_into().unwrap(),508 value: v.variable_on_chain_schema.clone().into_inner().try_into().expect("offchain schema too big"),509 });510 }511 if !v.const_on_chain_schema.is_empty() {512 props.push(Property {513 key: b"_old_constOnChainSchema".to_vec().try_into().unwrap(),514 value: v.const_on_chain_schema.clone().into_inner().try_into().expect("offchain schema too big"),515 });516 }517 props.push(Property {518 key: b"_old_schemaVersion".to_vec().try_into().unwrap(),519 value: match v.schema_version {520 SchemaVersion::ImageURL => b"ImageUrl".as_slice(),521 SchemaVersion::Unique => b"Unique".as_slice(),522 }.to_vec().try_into().unwrap(),523 });524 Self::set_scoped_collection_properties(525 id,526 PropertyScope::None,527 props.into_iter(),528 ).expect("existing data larger than properties");529 Some(CollectionVersion2::from(v))530 });531 }532533 0534 }535 }536}537538impl<T: Config> Pallet<T> {539 /// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens540 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {541 ensure!(542 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,543 <Error<T>>::AddressIsZero544 );545 Ok(())546 }547 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {548 <IsAdmin<T>>::iter_prefix((collection,))549 .map(|(a, _)| a)550 .collect()551 }552 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {553 <Allowlist<T>>::iter_prefix((collection,))554 .map(|(a, _)| a)555 .collect()556 }557 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {558 <Allowlist<T>>::get((collection, user))559 }560 pub fn collection_stats() -> CollectionStats {561 let created = <CreatedCollectionCount<T>>::get();562 let destroyed = <DestroyedCollectionCount<T>>::get();563 CollectionStats {564 created: created.0,565 destroyed: destroyed.0,566 alive: created.0 - destroyed.0,567 }568 }569570 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {571 let collection = <CollectionById<T>>::get(collection);572 if collection.is_none() {573 return None;574 }575576 let collection = collection.unwrap();577 let limits = collection.limits;578 let effective_limits = CollectionLimits {579 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),580 sponsored_data_size: Some(limits.sponsored_data_size()),581 sponsored_data_rate_limit: Some(582 limits583 .sponsored_data_rate_limit584 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),585 ),586 token_limit: Some(limits.token_limit()),587 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(588 match collection.mode {589 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,590 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,591 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,592 },593 )),594 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),595 owner_can_transfer: Some(limits.owner_can_transfer()),596 owner_can_destroy: Some(limits.owner_can_destroy()),597 transfers_enabled: Some(limits.transfers_enabled()),598 };599600 Some(effective_limits)601 }602603 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {604 let Collection {605 name,606 description,607 owner,608 mode,609 token_prefix,610 sponsorship,611 limits,612 permissions,613 } = <CollectionById<T>>::get(collection)?;614615 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)616 .into_iter()617 .map(|(key, permission)| PropertyKeyPermission {618 key,619 permission,620 })621 .collect();622623 let properties = <CollectionProperties<T>>::get(collection)624 .into_iter()625 .map(|(key, value)| Property {626 key,627 value,628 })629 .collect();630631 Some(RpcCollection {632 name: name.into_inner(),633 description: description.into_inner(),634 owner,635 mode,636 token_prefix: token_prefix.into_inner(),637 sponsorship,638 limits,639 permissions,640 token_property_permissions,641 properties,642 })643 }644}645646macro_rules! limit_default {647 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{648 $(649 if let Some($new) = $new.$field {650 let $old = $old.$field($($arg)?);651 let _ = $new;652 let _ = $old;653 $check654 } else {655 $new.$field = $old.$field656 }657 )*658 }};659}660macro_rules! limit_default_clone {661 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{662 $(663 if let Some($new) = $new.$field.clone() {664 let $old = $old.$field($($arg)?);665 let _ = $new;666 let _ = $old;667 $check668 } else {669 $new.$field = $old.$field.clone()670 }671 )*672 }};673}674675impl<T: Config> Pallet<T> {676 pub fn init_collection(677 owner: T::AccountId,678 data: CreateCollectionData<T::AccountId>,679 ) -> Result<CollectionId, DispatchError> {680 {681 ensure!(682 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,683 Error::<T>::CollectionTokenPrefixLimitExceeded684 );685 }686687 let created_count = <CreatedCollectionCount<T>>::get()688 .0689 .checked_add(1)690 .ok_or(ArithmeticError::Overflow)?;691 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;692 let id = CollectionId(created_count);693694 // bound Total number of collections695 ensure!(696 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,697 <Error<T>>::TotalCollectionsLimitExceeded698 );699700 // =========701702 let collection = Collection {703 owner: owner.clone(),704 name: data.name,705 mode: data.mode.clone(),706 description: data.description,707 token_prefix: data.token_prefix,708 sponsorship: data709 .pending_sponsor710 .map(SponsorshipState::Unconfirmed)711 .unwrap_or_default(),712 limits: data713 .limits714 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))715 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,716 permissions: data717 .permissions718 .map(|permissions| Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions))719 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,720 };721722 let mut collection_properties = up_data_structs::CollectionProperties::get();723 collection_properties724 .try_set_from_iter(data.properties.into_iter())725 .map_err(<Error<T>>::from)?;726727 CollectionProperties::<T>::insert(id, collection_properties);728729 let mut token_props_permissions = PropertiesPermissionMap::new();730 token_props_permissions731 .try_set_from_iter(data.token_property_permissions.into_iter())732 .map_err(<Error<T>>::from)?;733734 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);735736 // Take a (non-refundable) deposit of collection creation737 {738 let mut imbalance =739 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();740 imbalance.subsume(741 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(742 &T::TreasuryAccountId::get(),743 T::CollectionCreationPrice::get(),744 ),745 );746 <T as Config>::Currency::settle(747 &owner,748 imbalance,749 WithdrawReasons::TRANSFER,750 ExistenceRequirement::KeepAlive,751 )752 .map_err(|_| Error::<T>::NotSufficientFounds)?;753 }754755 <CreatedCollectionCount<T>>::put(created_count);756 <Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));757 <CollectionById<T>>::insert(id, collection);758 Ok(id)759 }760761 pub fn destroy_collection(762 collection: CollectionHandle<T>,763 sender: &T::CrossAccountId,764 ) -> DispatchResult {765 ensure!(766 collection.limits.owner_can_destroy(),767 <Error<T>>::NoPermission,768 );769 collection.check_is_owner(sender)?;770771 let destroyed_collections = <DestroyedCollectionCount<T>>::get()772 .0773 .checked_add(1)774 .ok_or(ArithmeticError::Overflow)?;775776 // =========777778 <DestroyedCollectionCount<T>>::put(destroyed_collections);779 <CollectionById<T>>::remove(collection.id);780 <AdminAmount<T>>::remove(collection.id);781 <IsAdmin<T>>::remove_prefix((collection.id,), None);782 <Allowlist<T>>::remove_prefix((collection.id,), None);783 <CollectionProperties<T>>::remove(collection.id);784785 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));786 Ok(())787 }788789 pub fn set_collection_property(790 collection: &CollectionHandle<T>,791 sender: &T::CrossAccountId,792 property: Property,793 ) -> DispatchResult {794 collection.check_is_owner_or_admin(sender)?;795796 CollectionProperties::<T>::try_mutate(collection.id, |properties| {797 let property = property.clone();798 properties.try_set(property.key, property.value)799 })800 .map_err(<Error<T>>::from)?;801802 Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));803804 Ok(())805 }806807 pub fn set_scoped_collection_property(808 collection_id: CollectionId,809 scope: PropertyScope,810 property: Property,811 ) -> DispatchResult {812 CollectionProperties::<T>::try_mutate(collection_id, |properties| {813 properties.try_scoped_set(scope, property.key, property.value)814 })815 .map_err(<Error<T>>::from)?;816817 Ok(())818 }819820 pub fn set_scoped_collection_properties(821 collection_id: CollectionId,822 scope: PropertyScope,823 properties: impl Iterator<Item = Property>,824 ) -> DispatchResult {825 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {826 stored_properties.try_scoped_set_from_iter(scope, properties)827 })828 .map_err(<Error<T>>::from)?;829830 Ok(())831 }832833 #[transactional]834 pub fn set_collection_properties(835 collection: &CollectionHandle<T>,836 sender: &T::CrossAccountId,837 properties: Vec<Property>,838 ) -> DispatchResult {839 for property in properties {840 Self::set_collection_property(collection, sender, property)?;841 }842843 Ok(())844 }845846 pub fn delete_collection_property(847 collection: &CollectionHandle<T>,848 sender: &T::CrossAccountId,849 property_key: PropertyKey,850 ) -> DispatchResult {851 collection.check_is_owner_or_admin(sender)?;852853 CollectionProperties::<T>::try_mutate(collection.id, |properties| {854 properties.remove(&property_key)855 })856 .map_err(<Error<T>>::from)?;857858 Self::deposit_event(Event::CollectionPropertyDeleted(859 collection.id,860 property_key,861 ));862863 Ok(())864 }865866 #[transactional]867 pub fn delete_collection_properties(868 collection: &CollectionHandle<T>,869 sender: &T::CrossAccountId,870 property_keys: Vec<PropertyKey>,871 ) -> DispatchResult {872 for key in property_keys {873 Self::delete_collection_property(collection, sender, key)?;874 }875876 Ok(())877 }878879 // For migrations880 pub fn set_property_permission_unchecked(881 collection: CollectionId,882 property_permission: PropertyKeyPermission,883 ) -> DispatchResult {884 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {885 permissions.try_set(property_permission.key, property_permission.permission)886 })887 .map_err(<Error<T>>::from)?;888 Ok(())889 }890891 pub fn set_property_permission(892 collection: &CollectionHandle<T>,893 sender: &T::CrossAccountId,894 property_permission: PropertyKeyPermission,895 ) -> DispatchResult {896 collection.check_is_owner_or_admin(sender)?;897898 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);899 let current_permission = all_permissions.get(&property_permission.key);900 if matches![901 current_permission,902 Some(PropertyPermission { mutable: false, .. })903 ] {904 return Err(<Error<T>>::NoPermission.into());905 }906907 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {908 let property_permission = property_permission.clone();909 permissions.try_set(property_permission.key, property_permission.permission)910 })911 .map_err(<Error<T>>::from)?;912913 Self::deposit_event(Event::PropertyPermissionSet(914 collection.id,915 property_permission.key,916 ));917918 Ok(())919 }920921 #[transactional]922 pub fn set_property_permissions(923 collection: &CollectionHandle<T>,924 sender: &T::CrossAccountId,925 property_permissions: Vec<PropertyKeyPermission>,926 ) -> DispatchResult {927 for prop_pemission in property_permissions {928 Self::set_property_permission(collection, sender, prop_pemission)?;929 }930931 Ok(())932 }933934 pub fn get_collection_property(935 collection_id: CollectionId,936 key: &PropertyKey,937 ) -> Option<PropertyValue> {938 Self::collection_properties(collection_id).get(key).cloned()939 }940941 pub fn bytes_keys_to_property_keys(942 keys: Vec<Vec<u8>>,943 ) -> Result<Vec<PropertyKey>, DispatchError> {944 keys.into_iter()945 .map(|key| -> Result<PropertyKey, DispatchError> {946 key.try_into()947 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())948 })949 .collect::<Result<Vec<PropertyKey>, DispatchError>>()950 }951952 pub fn filter_collection_properties(953 collection_id: CollectionId,954 keys: Option<Vec<PropertyKey>>,955 ) -> Result<Vec<Property>, DispatchError> {956 let properties = Self::collection_properties(collection_id);957958 let properties = keys959 .map(|keys| {960 keys.into_iter()961 .filter_map(|key| {962 properties.get(&key).map(|value| Property {963 key,964 value: value.clone(),965 })966 })967 .collect()968 })969 .unwrap_or_else(|| {970 properties971 .into_iter()972 .map(|(key, value)| Property {973 key,974 value,975 })976 .collect()977 });978979 Ok(properties)980 }981982 pub fn filter_property_permissions(983 collection_id: CollectionId,984 keys: Option<Vec<PropertyKey>>,985 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {986 let permissions = Self::property_permissions(collection_id);987988 let key_permissions = keys989 .map(|keys| {990 keys.into_iter()991 .filter_map(|key| {992 permissions993 .get(&key)994 .map(|permission| PropertyKeyPermission {995 key,996 permission: permission.clone(),997 })998 })999 .collect()1000 })1001 .unwrap_or_else(|| {1002 permissions1003 .into_iter()1004 .map(|(key, permission)| PropertyKeyPermission {1005 key,1006 permission,1007 })1008 .collect()1009 });10101011 Ok(key_permissions)1012 }10131014 pub fn toggle_allowlist(1015 collection: &CollectionHandle<T>,1016 sender: &T::CrossAccountId,1017 user: &T::CrossAccountId,1018 allowed: bool,1019 ) -> DispatchResult {1020 collection.check_is_owner_or_admin(sender)?;10211022 // =========10231024 if allowed {1025 <Allowlist<T>>::insert((collection.id, user), true);1026 } else {1027 <Allowlist<T>>::remove((collection.id, user));1028 }10291030 Ok(())1031 }10321033 pub fn toggle_admin(1034 collection: &CollectionHandle<T>,1035 sender: &T::CrossAccountId,1036 user: &T::CrossAccountId,1037 admin: bool,1038 ) -> DispatchResult {1039 collection.check_is_owner_or_admin(sender)?;10401041 let was_admin = <IsAdmin<T>>::get((collection.id, user));1042 if was_admin == admin {1043 return Ok(());1044 }1045 let amount = <AdminAmount<T>>::get(collection.id);10461047 if admin {1048 let amount = amount1049 .checked_add(1)1050 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1051 ensure!(1052 amount <= Self::collection_admins_limit(),1053 <Error<T>>::CollectionAdminCountExceeded,1054 );10551056 // =========10571058 <AdminAmount<T>>::insert(collection.id, amount);1059 <IsAdmin<T>>::insert((collection.id, user), true);1060 } else {1061 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1062 <IsAdmin<T>>::remove((collection.id, user));1063 }10641065 Ok(())1066 }10671068 pub fn clamp_limits(1069 mode: CollectionMode,1070 old_limit: &CollectionLimits,1071 mut new_limit: CollectionLimits,1072 ) -> Result<CollectionLimits, DispatchError> {1073 limit_default!(old_limit, new_limit,1074 account_token_ownership_limit => ensure!(1075 new_limit <= MAX_TOKEN_OWNERSHIP,1076 <Error<T>>::CollectionLimitBoundsExceeded,1077 ),1078 sponsor_transfer_timeout(match mode {1079 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1080 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1081 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1082 }) => ensure!(1083 new_limit <= MAX_SPONSOR_TIMEOUT,1084 <Error<T>>::CollectionLimitBoundsExceeded,1085 ),1086 sponsored_data_size => ensure!(1087 new_limit <= CUSTOM_DATA_LIMIT,1088 <Error<T>>::CollectionLimitBoundsExceeded,1089 ),1090 token_limit => ensure!(1091 old_limit >= new_limit && new_limit > 0,1092 <Error<T>>::CollectionTokenLimitExceeded1093 ),1094 owner_can_transfer => ensure!(1095 old_limit || !new_limit,1096 <Error<T>>::OwnerPermissionsCantBeReverted,1097 ),1098 owner_can_destroy => ensure!(1099 old_limit || !new_limit,1100 <Error<T>>::OwnerPermissionsCantBeReverted,1101 ),1102 sponsored_data_rate_limit => {},1103 transfers_enabled => {},1104 );1105 Ok(new_limit)1106 }1107 pub fn clamp_permissions(1108 mode: CollectionMode,1109 old_limit: &CollectionPermissions,1110 mut new_limit: CollectionPermissions,1111 ) -> Result<CollectionPermissions, DispatchError> {1112 limit_default_clone!(old_limit, new_limit,1113 );1114 Ok(new_limit)1115 }1116}11171118#[macro_export]1119macro_rules! unsupported {1120 () => {1121 Err(<Error<T>>::UnsupportedOperation.into())1122 };1123}11241125/// Worst cases1126pub trait CommonWeightInfo<CrossAccountId> {1127 fn create_item() -> Weight;1128 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;1129 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1130 fn burn_item() -> Weight;1131 fn set_collection_properties(amount: u32) -> Weight;1132 fn delete_collection_properties(amount: u32) -> Weight;1133 fn set_token_properties(amount: u32) -> Weight;1134 fn delete_token_properties(amount: u32) -> Weight;1135 fn set_property_permissions(amount: u32) -> Weight;1136 fn transfer() -> Weight;1137 fn approve() -> Weight;1138 fn transfer_from() -> Weight;1139 fn burn_from() -> Weight;1140}11411142pub trait CommonCollectionOperations<T: Config> {1143 fn create_item(1144 &self,1145 sender: T::CrossAccountId,1146 to: T::CrossAccountId,1147 data: CreateItemData,1148 nesting_budget: &dyn Budget,1149 ) -> DispatchResultWithPostInfo;1150 fn create_multiple_items(1151 &self,1152 sender: T::CrossAccountId,1153 to: T::CrossAccountId,1154 data: Vec<CreateItemData>,1155 nesting_budget: &dyn Budget,1156 ) -> DispatchResultWithPostInfo;1157 fn create_multiple_items_ex(1158 &self,1159 sender: T::CrossAccountId,1160 data: CreateItemExData<T::CrossAccountId>,1161 nesting_budget: &dyn Budget,1162 ) -> DispatchResultWithPostInfo;1163 fn burn_item(1164 &self,1165 sender: T::CrossAccountId,1166 token: TokenId,1167 amount: u128,1168 ) -> DispatchResultWithPostInfo;1169 fn set_collection_properties(1170 &self,1171 sender: T::CrossAccountId,1172 properties: Vec<Property>,1173 ) -> DispatchResultWithPostInfo;1174 fn delete_collection_properties(1175 &self,1176 sender: &T::CrossAccountId,1177 property_keys: Vec<PropertyKey>,1178 ) -> DispatchResultWithPostInfo;1179 fn set_token_properties(1180 &self,1181 sender: T::CrossAccountId,1182 token_id: TokenId,1183 property: Vec<Property>,1184 ) -> DispatchResultWithPostInfo;1185 fn delete_token_properties(1186 &self,1187 sender: T::CrossAccountId,1188 token_id: TokenId,1189 property_keys: Vec<PropertyKey>,1190 ) -> DispatchResultWithPostInfo;1191 fn set_property_permissions(1192 &self,1193 sender: &T::CrossAccountId,1194 property_permissions: Vec<PropertyKeyPermission>,1195 ) -> DispatchResultWithPostInfo;1196 fn transfer(1197 &self,1198 sender: T::CrossAccountId,1199 to: T::CrossAccountId,1200 token: TokenId,1201 amount: u128,1202 nesting_budget: &dyn Budget,1203 ) -> DispatchResultWithPostInfo;1204 fn approve(1205 &self,1206 sender: T::CrossAccountId,1207 spender: T::CrossAccountId,1208 token: TokenId,1209 amount: u128,1210 ) -> DispatchResultWithPostInfo;1211 fn transfer_from(1212 &self,1213 sender: T::CrossAccountId,1214 from: T::CrossAccountId,1215 to: T::CrossAccountId,1216 token: TokenId,1217 amount: u128,1218 nesting_budget: &dyn Budget,1219 ) -> DispatchResultWithPostInfo;1220 fn burn_from(1221 &self,1222 sender: T::CrossAccountId,1223 from: T::CrossAccountId,1224 token: TokenId,1225 amount: u128,1226 nesting_budget: &dyn Budget,1227 ) -> DispatchResultWithPostInfo;12281229 fn check_nesting(1230 &self,1231 sender: T::CrossAccountId,1232 from: (CollectionId, TokenId),1233 under: TokenId,1234 budget: &dyn Budget,1235 ) -> DispatchResult;12361237 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1238 fn collection_tokens(&self) -> Vec<TokenId>;1239 fn token_exists(&self, token: TokenId) -> bool;1240 fn last_token_id(&self) -> TokenId;12411242 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1243 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;1244 fn token_properties(&self, token_id: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;1245 /// Amount of unique collection tokens1246 fn total_supply(&self) -> u32;1247 /// Amount of different tokens account has (Applicable to nonfungible/refungible)1248 fn account_balance(&self, account: T::CrossAccountId) -> u32;1249 /// Amount of specific token account have (Applicable to fungible/refungible)1250 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1251 fn allowance(1252 &self,1253 sender: T::CrossAccountId,1254 spender: T::CrossAccountId,1255 token: TokenId,1256 ) -> u128;1257}12581259// Flexible enough for implementing CommonCollectionOperations1260pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1261 let post_info = PostDispatchInfo {1262 actual_weight: Some(weight),1263 pays_fee: Pays::Yes,1264 };1265 match res {1266 Ok(()) => Ok(post_info),1267 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1268 }1269}12701271impl<T: Config> From<PropertiesError> for Error<T> {1272 fn from(error: PropertiesError) -> Self {1273 match error {1274 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1275 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1276 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1277 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1278 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1279 }1280 }1281}pallets/fungible/src/common.rsdiffbeforeafterboth--- a/pallets/fungible/src/common.rs
+++ b/pallets/fungible/src/common.rs
@@ -51,27 +51,27 @@
<SelfWeightOf<T>>::burn_item()
}
- fn set_collection_properties(amount: u32) -> Weight {
+ fn set_collection_properties(_amount: u32) -> Weight {
// Error
0
}
- fn delete_collection_properties(amount: u32) -> Weight {
+ fn delete_collection_properties(_amount: u32) -> Weight {
// Error
0
}
- fn set_token_properties(amount: u32) -> Weight {
+ fn set_token_properties(_amount: u32) -> Weight {
// Error
0
}
- fn delete_token_properties(amount: u32) -> Weight {
+ fn delete_token_properties(_amount: u32) -> Weight {
// Error
0
}
- fn set_property_permissions(amount: u32) -> Weight {
+ fn set_property_permissions(_amount: u32) -> Weight {
// Error
0
}
@@ -320,9 +320,6 @@
fn token_owner(&self, _token: TokenId) -> Option<T::CrossAccountId> {
None
- }
- fn const_metadata(&self, _token: TokenId) -> Vec<u8> {
- Vec::new()
}
fn token_property(&self, _token_id: TokenId, _key: &PropertyKey) -> Option<PropertyValue> {
pallets/fungible/src/lib.rsdiffbeforeafterboth--- a/pallets/fungible/src/lib.rs
+++ b/pallets/fungible/src/lib.rs
@@ -168,7 +168,7 @@
.checked_sub(amount)
.ok_or(<CommonError<T>>::TokenValueTooLow)?;
- if collection.access == AccessMode::AllowList {
+ if collection.permissions.access() == AccessMode::AllowList {
collection.check_allowlist(owner)?;
}
@@ -210,7 +210,7 @@
<CommonError<T>>::TransferNotAllowed,
);
- if collection.access == AccessMode::AllowList {
+ if collection.permissions.access() == AccessMode::AllowList {
collection.check_allowlist(from)?;
collection.check_allowlist(to)?;
}
@@ -280,7 +280,7 @@
) -> DispatchResult {
if !collection.is_owner_or_admin(sender) {
ensure!(
- collection.mint_mode,
+ collection.permissions.mint_mode(),
<CommonError<T>>::PublicMintingNotAllowed
);
collection.check_allowlist(sender)?;
@@ -380,7 +380,7 @@
spender: &T::CrossAccountId,
amount: u128,
) -> DispatchResult {
- if collection.access == AccessMode::AllowList {
+ if collection.permissions.access() == AccessMode::AllowList {
collection.check_allowlist(owner)?;
collection.check_allowlist(spender)?;
}
@@ -408,7 +408,7 @@
if spender.conv_eq(from) {
return Ok(None);
}
- if collection.access == AccessMode::AllowList {
+ if collection.permissions.access() == AccessMode::AllowList {
// `from`, `to` checked in [`transfer`]
collection.check_allowlist(spender)?;
}
pallets/nonfungible/src/benchmarking.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/benchmarking.rs
+++ b/pallets/nonfungible/src/benchmarking.rs
@@ -29,9 +29,7 @@
const SEED: u32 = 1;
fn create_max_item_data<T: Config>(owner: T::CrossAccountId) -> CreateItemData<T> {
- let const_data = create_data::<CUSTOM_DATA_LIMIT>();
CreateItemData::<T> {
- const_data,
owner,
properties: Default::default(),
}
pallets/nonfungible/src/common.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/common.rs
+++ b/pallets/nonfungible/src/common.rs
@@ -116,7 +116,6 @@
) -> Result<CreateItemData<T>, DispatchError> {
match data {
up_data_structs::CreateItemData::NFT(data) => Ok(CreateItemData::<T> {
- const_data: data.const_data,
properties: data.properties,
owner: to.clone(),
}),
@@ -376,12 +375,6 @@
fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId> {
<TokenData<T>>::get((self.id, token)).map(|t| t.owner)
- }
- fn const_metadata(&self, token: TokenId) -> Vec<u8> {
- <TokenData<T>>::get((self.id, token))
- .map(|t| t.const_data)
- .unwrap_or_default()
- .into_inner()
}
fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue> {
pallets/nonfungible/src/erc.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/erc.rs
+++ b/pallets/nonfungible/src/erc.rs
@@ -109,15 +109,6 @@
}
}
-fn error_unsupported_schema_version() -> Error {
- alloc::format!(
- "Unsupported schema version! Support only {:?}",
- SchemaVersion::ImageURL
- )
- .as_str()
- .into()
-}
-
#[derive(ToLog)]
pub enum ERC721Events {
Transfer {
@@ -167,16 +158,10 @@
/// Returns token's const_metadata
#[solidity(rename_selector = "tokenURI")]
fn token_uri(&self, token_id: uint256) -> Result<string> {
- if !matches!(self.schema_version, SchemaVersion::ImageURL) {
- return Err(error_unsupported_schema_version());
- }
-
self.consume_store_reads(1)?;
- let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;
+ let _token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;
Ok(string::from_utf8_lossy(
- &<TokenData<T>>::get((self.id, token_id))
- .ok_or("token not found")?
- .const_data,
+ todo!()
)
.into())
}
@@ -344,7 +329,6 @@
self,
&caller,
CreateItemData::<T> {
- const_data: BoundedVec::default(),
properties: BoundedVec::default(),
owner: to,
},
@@ -366,10 +350,6 @@
token_id: uint256,
token_uri: string,
) -> Result<bool> {
- if !matches!(self.schema_version, SchemaVersion::ImageURL) {
- return Err(error_unsupported_schema_version());
- }
-
let caller = T::CrossAccountId::from_eth(caller);
let to = T::CrossAccountId::from_eth(to);
let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;
@@ -385,13 +365,12 @@
return Err("item id should be next".into());
}
+ todo!("token uri");
+
<Pallet<T>>::create_item(
self,
&caller,
CreateItemData::<T> {
- const_data: Vec::<u8>::from(token_uri)
- .try_into()
- .map_err(|_| "token uri is too long")?,
properties: BoundedVec::default(),
owner: to,
},
@@ -477,7 +456,6 @@
}
let data = (0..total_tokens)
.map(|_| CreateItemData::<T> {
- const_data: BoundedVec::default(),
properties: BoundedVec::default(),
owner: to.clone(),
})
@@ -496,10 +474,6 @@
to: address,
tokens: Vec<(uint256, string)>,
) -> Result<bool> {
- if !matches!(self.schema_version, SchemaVersion::ImageURL) {
- return Err(error_unsupported_schema_version());
- }
-
let caller = T::CrossAccountId::from_eth(caller);
let to = T::CrossAccountId::from_eth(to);
let mut expected_index = <TokensMinted<T>>::get(self.id)
@@ -517,10 +491,8 @@
}
expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;
+ todo!("token uri");
data.push(CreateItemData::<T> {
- const_data: Vec::<u8>::from(token_uri)
- .try_into()
- .map_err(|_| "token uri is too long")?,
properties: BoundedVec::default(),
owner: to.clone(),
});
pallets/nonfungible/src/lib.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -33,7 +33,7 @@
use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};
use sp_core::H160;
use sp_runtime::{ArithmeticError, DispatchError, DispatchResult, TransactionOutcome};
-use sp_std::{vec::Vec, vec};
+use sp_std::{vec::Vec, vec, collections::btree_set::BTreeSet};
use core::ops::Deref;
use sp_std::collections::btree_map::BTreeMap;
use codec::{Encode, Decode, MaxEncodedLen};
@@ -52,6 +52,7 @@
#[struct_versioning::versioned(version = 2, upper)]
#[derive(Encode, Decode, TypeInfo, MaxEncodedLen)]
pub struct ItemData<CrossAccountId> {
+ #[version(..2)]
pub const_data: BoundedVec<u8, CustomDataLimit>,
#[version(..2)]
@@ -148,9 +149,45 @@
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn on_runtime_upgrade() -> Weight {
if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {
- <TokenData<T>>::translate_values::<ItemDataVersion1<T::CrossAccountId>, _>(|v| {
+ let mut had_consts = BTreeSet::new();
+ <TokenData<T>>::translate::<ItemDataVersion1<T::CrossAccountId>, _>(|(collection, token), v| {
+ let mut props = vec![];
+ if !v.const_data.is_empty() {
+ props.push(Property {
+ key: b"_old_constData".to_vec().try_into().unwrap(),
+ value: v.const_data.clone().into_inner().try_into().expect("const too long"),
+ });
+ had_consts.insert(collection);
+ }
+ if !v.variable_data.is_empty() {
+ props.push(Property {
+ key: b"_old_variableData".to_vec().try_into().unwrap(),
+ value: v.variable_data.clone().into_inner().try_into().expect("variable too long"),
+ })
+ }
+ if !props.is_empty() {
+ Self::set_scoped_token_properties(
+ collection,
+ token,
+ PropertyScope::None,
+ props.into_iter(),
+ ).expect("existing token data exceeds property storage");
+ }
Some(<ItemDataVersion2<T::CrossAccountId>>::from(v))
- })
+ });
+ for collection in had_consts {
+ <PalletCommon<T>>::set_property_permission_unchecked(
+ collection,
+ PropertyKeyPermission {
+ key: b"_old_constData".to_vec().try_into().unwrap(),
+ permission: PropertyPermission {
+ mutable: false,
+ collection_admin: true,
+ token_owner: false,
+ },
+ }
+ ).expect("failed to configure permission");
+ }
}
0
@@ -267,7 +304,7 @@
<CommonError<T>>::NoPermission
);
- if collection.access == AccessMode::AllowList {
+ if collection.permissions.access() == AccessMode::AllowList {
collection.check_allowlist(sender)?;
}
@@ -493,7 +530,7 @@
<CommonError<T>>::NoPermission
);
- if collection.access == AccessMode::AllowList {
+ if collection.permissions.access() == AccessMode::AllowList {
collection.check_allowlist(from)?;
collection.check_allowlist(to)?;
}
@@ -579,7 +616,7 @@
) -> DispatchResult {
if !collection.is_owner_or_admin(sender) {
ensure!(
- collection.mint_mode,
+ collection.permissions.mint_mode(),
<CommonError<T>>::PublicMintingNotAllowed
);
collection.check_allowlist(sender)?;
@@ -639,7 +676,7 @@
<TokenData<T>>::insert(
(collection.id, token),
ItemData {
- const_data: data.const_data.clone(),
+ // const_data: data.const_data.clone(),
owner: data.owner.clone(),
},
);
@@ -756,7 +793,7 @@
token: TokenId,
spender: Option<&T::CrossAccountId>,
) -> DispatchResult {
- if collection.access == AccessMode::AllowList {
+ if collection.permissions.access() == AccessMode::AllowList {
collection.check_allowlist(sender)?;
if let Some(spender) = spender {
collection.check_allowlist(spender)?;
@@ -791,7 +828,7 @@
if spender.conv_eq(from) {
return Ok(());
}
- if collection.access == AccessMode::AllowList {
+ if collection.permissions.access() == AccessMode::AllowList {
// `from`, `to` checked in [`transfer`]
collection.check_allowlist(spender)?;
}
@@ -875,7 +912,7 @@
);
Ok(())
}
- match handle.limits.nesting_rule() {
+ match handle.permissions.nesting() {
NestingRule::Disabled => fail!(<CommonError<T>>::NestingIsDisabled),
NestingRule::Owner => {
ensure_sender_allowed::<T>(handle.id, under, from, sender, nesting_budget)?
pallets/proxy-rmrk-core/src/lib.rsdiffbeforeafterboth--- a/pallets/proxy-rmrk-core/src/lib.rs
+++ b/pallets/proxy-rmrk-core/src/lib.rs
@@ -403,10 +403,8 @@
nft_type: NftType,
properties: impl Iterator<Item=Property>
) -> Result<TokenId, DispatchError> {
+ todo!("store nft type");
let data = CreateNftExData {
- const_data: nft_type.encode()
- .try_into()
- .map_err(|_| <Error<T>>::NftTypeEncodeError)?,
properties: BoundedVec::default(),
owner: owner.clone(),
};
@@ -528,13 +526,8 @@
Ok(nft_property)
}
- pub fn get_nft_type(collection_id: CollectionId, token_id: TokenId) -> Result<NftType, DispatchError> {
- let token_data = <TokenData<T>>::get((collection_id, token_id))
- .ok_or(<Error<T>>::NoAvailableNftId)?;
-
- let mut const_data = token_data.const_data.as_slice();
-
- NftType::decode(&mut const_data).map_err(|_| <Error<T>>::NoAvailableNftId.into())
+ pub fn get_nft_type(_collection_id: CollectionId, _token_id: TokenId) -> Result<NftType, DispatchError> {
+ todo!("should get it from properties?")
}
pub fn ensure_nft_type(collection_id: CollectionId, token_id: TokenId, nft_type: NftType) -> DispatchResult {
pallets/refungible/src/common.rsdiffbeforeafterboth--- a/pallets/refungible/src/common.rs
+++ b/pallets/refungible/src/common.rs
@@ -336,11 +336,6 @@
fn token_owner(&self, _token: TokenId) -> Option<T::CrossAccountId> {
None
}
- fn const_metadata(&self, token: TokenId) -> Vec<u8> {
- <TokenData<T>>::get((self.id, token))
- .const_data
- .into_inner()
- }
fn token_property(&self, _token_id: TokenId, _key: &PropertyKey) -> Option<PropertyValue> {
None
pallets/refungible/src/lib.rsdiffbeforeafterboth--- a/pallets/refungible/src/lib.rs
+++ b/pallets/refungible/src/lib.rs
@@ -321,7 +321,7 @@
<CommonError<T>>::TransferNotAllowed
);
- if collection.access == AccessMode::AllowList {
+ if collection.permissions.access() == AccessMode::AllowList {
collection.check_allowlist(from)?;
collection.check_allowlist(to)?;
}
@@ -424,7 +424,7 @@
) -> DispatchResult {
if !collection.is_owner_or_admin(sender) {
ensure!(
- collection.mint_mode,
+ collection.permissions.mint_mode(),
<CommonError<T>>::PublicMintingNotAllowed
);
collection.check_allowlist(sender)?;
@@ -566,7 +566,7 @@
token: TokenId,
amount: u128,
) -> DispatchResult {
- if collection.access == AccessMode::AllowList {
+ if collection.permissions.access() == AccessMode::AllowList {
collection.check_allowlist(sender)?;
collection.check_allowlist(spender)?;
}
@@ -598,7 +598,7 @@
if spender.conv_eq(from) {
return Ok(None);
}
- if collection.access == AccessMode::AllowList {
+ if collection.permissions.access() == AccessMode::AllowList {
// `from`, `to` checked in [`transfer`]
collection.check_allowlist(spender)?;
}
pallets/unique/src/benchmarking.rsdiffbeforeafterboth--- a/pallets/unique/src/benchmarking.rs
+++ b/pallets/unique/src/benchmarking.rs
@@ -130,26 +130,6 @@
let collection = create_nft_collection::<T>(caller.clone())?;
}: _(RawOrigin::Signed(caller.clone()), collection, false)
- set_offchain_schema {
- let b in 0..OFFCHAIN_SCHEMA_LIMIT;
-
- let caller: T::AccountId = account("caller", 0, SEED);
- let collection = create_nft_collection::<T>(caller.clone())?;
- let data = create_var_data(b);
- }: set_offchain_schema(RawOrigin::Signed(caller.clone()), collection, data)
-
- set_const_on_chain_schema {
- let b in 0..CONST_ON_CHAIN_SCHEMA_LIMIT;
-
- let caller: T::AccountId = account("caller", 0, SEED);
- let collection = create_nft_collection::<T>(caller.clone())?;
- let data = create_var_data(b);
- }: set_const_on_chain_schema(RawOrigin::Signed(caller.clone()), collection, data)
-
- set_schema_version {
- let caller: T::AccountId = account("caller", 0, SEED);
- let collection = create_nft_collection::<T>(caller.clone())?;
- }: set_schema_version(RawOrigin::Signed(caller.clone()), collection, SchemaVersion::Unique)
set_collection_limits{
let caller: T::AccountId = account("caller", 0, SEED);
pallets/unique/src/lib.rsdiffbeforeafterboth--- a/pallets/unique/src/lib.rs
+++ b/pallets/unique/src/lib.rs
@@ -35,10 +35,10 @@
use frame_system::{self as system, ensure_signed};
use sp_runtime::{sp_std::prelude::Vec};
use up_data_structs::{
- CONST_ON_CHAIN_SCHEMA_LIMIT, OFFCHAIN_SCHEMA_LIMIT, MAX_COLLECTION_NAME_LENGTH,
+ MAX_COLLECTION_NAME_LENGTH,
MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_TOKEN_PREFIX_LENGTH, AccessMode, CreateItemData,
- CollectionLimits, CollectionId, CollectionMode, TokenId, SchemaVersion, SponsorshipState,
- CreateCollectionData, CreateItemExData, budget, CollectionField, Property, PropertyKey,
+ CollectionLimits, CollectionPermissions, CollectionId, CollectionMode, TokenId, SponsorshipState,
+ CreateCollectionData, CreateItemExData, budget, Property, PropertyKey,
PropertyKeyPermission,
};
use pallet_evm::account::CrossAccountId;
@@ -162,6 +162,8 @@
/// * collection_id: Globally unique collection identifier.
CollectionLimitSet(CollectionId),
+ CollectionPermissionSet(CollectionId),
+
/// Mint permission was set
///
/// # Arguments
@@ -417,67 +419,6 @@
));
Ok(())
- }
-
- /// Toggle between normal and allow list access for the methods with access for `Anyone`.
- ///
- /// # Permissions
- ///
- /// * Collection Owner.
- ///
- /// # Arguments
- ///
- /// * collection_id.
- ///
- /// * mode: [AccessMode]
- #[weight = <SelfWeightOf<T>>::set_public_access_mode()]
- #[transactional]
- pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult
- {
- 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)?;
-
- target_collection.access = mode.clone();
-
- <Pallet<T>>::deposit_event(Event::<T>::PublicAccessModeSet(
- collection_id,
- mode
- ));
-
- target_collection.save()
- }
-
- /// Allows Anyone to create tokens if:
- /// * Allow List is enabled, and
- /// * Address is added to allow list, and
- /// * This method was called with True parameter
- ///
- /// # Permissions
- /// * Collection Owner
- ///
- /// # Arguments
- ///
- /// * collection_id.
- ///
- /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.
- #[weight = <SelfWeightOf<T>>::set_mint_permission()]
- #[transactional]
- pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult
- {
- 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)?;
-
- target_collection.mint_mode = mint_permission;
-
- <Pallet<T>>::deposit_event(Event::<T>::MintPermissionSet(
- collection_id
- ));
-
- target_collection.save()
}
/// Change the owner of the collection.
@@ -941,118 +882,42 @@
dispatch_call::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value, &budget))
}
- /// Set schema standard
- /// ImageURL
- /// Unique
- ///
- /// # Permissions
- ///
- /// * Collection Owner
- /// * Collection Admin
- ///
- /// # Arguments
- ///
- /// * collection_id.
- ///
- /// * schema: SchemaVersion: enum
- #[weight = <SelfWeightOf<T>>::set_schema_version()]
+ #[weight = <SelfWeightOf<T>>::set_collection_limits()]
#[transactional]
- pub fn set_schema_version(
+ pub fn set_collection_limits(
origin,
collection_id: CollectionId,
- version: SchemaVersion
+ new_limit: CollectionLimits,
) -> DispatchResult {
let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;
- target_collection.check_is_owner_or_admin(&sender)?;
- target_collection.schema_version = version;
-
- <Pallet<T>>::deposit_event(Event::<T>::SchemaVersionSet(
- collection_id
- ));
-
- target_collection.save()
- }
-
- /// Set off-chain data schema.
- ///
- /// # Permissions
- ///
- /// * Collection Owner
- /// * Collection Admin
- ///
- /// # Arguments
- ///
- /// * collection_id.
- ///
- /// * schema: String representing the offchain data schema.
- #[weight = <SelfWeightOf<T>>::set_offchain_schema(schema.len() as u32)]
- #[transactional]
- pub fn set_offchain_schema(
- origin,
- collection_id: CollectionId,
- schema: BoundedVec<u8, ConstU32<OFFCHAIN_SCHEMA_LIMIT>>,
- ) -> DispatchResult {
- let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
- let collection = <CollectionHandle<T>>::try_get(collection_id)?;
+ target_collection.check_is_owner(&sender)?;
+ let old_limit = &target_collection.limits;
- // =========
+ target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;
- <PalletCommon<T>>::set_field(&collection, &sender, CollectionField::OffchainSchema, schema.into_inner())?;
-
- <Pallet<T>>::deposit_event(Event::<T>::OffchainSchemaSet(
+ <Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(
collection_id
));
- Ok(())
- }
- /// Set const on-chain data schema.
- ///
- /// # Permissions
- ///
- /// * Collection Owner
- /// * Collection Admin
- ///
- /// # Arguments
- ///
- /// * collection_id.
- ///
- /// * schema: String representing the const on-chain data schema.
- #[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]
- #[transactional]
- pub fn set_const_on_chain_schema (
- origin,
- collection_id: CollectionId,
- schema: BoundedVec<u8, ConstU32<CONST_ON_CHAIN_SCHEMA_LIMIT>>
- ) -> DispatchResult {
- let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
- let collection = <CollectionHandle<T>>::try_get(collection_id)?;
-
- // =========
-
- <PalletCommon<T>>::set_field(&collection, &sender, CollectionField::ConstOnChainSchema, schema.into_inner())?;
-
- <Pallet<T>>::deposit_event(Event::<T>::ConstOnChainSchemaSet(
- collection_id
- ));
- Ok(())
+ target_collection.save()
}
#[weight = <SelfWeightOf<T>>::set_collection_limits()]
#[transactional]
- pub fn set_collection_limits(
+ pub fn set_collection_permissions(
origin,
collection_id: CollectionId,
- new_limit: CollectionLimits,
+ new_limit: CollectionPermissions,
) -> DispatchResult {
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;
+ let old_limit = &target_collection.permissions;
- target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;
+ target_collection.permissions = <PalletCommon<T>>::clamp_permissions(target_collection.mode.clone(), &old_limit, new_limit)?;
- <Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(
+ <Pallet<T>>::deposit_event(Event::<T>::CollectionPermissionSet(
collection_id
));
primitives/data-structs/src/lib.rsdiffbeforeafterboth--- a/primitives/data-structs/src/lib.rs
+++ b/primitives/data-structs/src/lib.rs
@@ -186,7 +186,6 @@
#[derive(Encode, Decode, Clone, PartialEq, TypeInfo)]
#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
pub struct TokenData<CrossAccountId> {
- pub const_data: Vec<u8>,
pub properties: Vec<Property>,
pub owner: Option<CrossAccountId>,
}
@@ -223,7 +222,7 @@
fn resolve(who: &AccountId, call: &Call) -> Option<AccountId>;
}
-#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq, TypeInfo, MaxEncodedLen)]
+#[derive(Encode, Decode, Eq, Debug, Clone, Copy, PartialEq, TypeInfo, MaxEncodedLen)]
#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
pub enum AccessMode {
Normal,
@@ -296,22 +295,26 @@
pub struct Collection<AccountId> {
pub owner: AccountId,
pub mode: CollectionMode,
+ #[version(..2)]
pub access: AccessMode,
pub name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,
pub description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,
pub token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,
+
+ #[version(..2)]
pub mint_mode: bool,
#[version(..2)]
pub offchain_schema: BoundedVec<u8, ConstU32<OFFCHAIN_SCHEMA_LIMIT>>,
+ #[version(..2)]
pub schema_version: SchemaVersion,
pub sponsorship: SponsorshipState<AccountId>,
- #[version(..2)]
- pub limits: CollectionLimitsVersion1, // Collection private restrictions
- #[version(2.., upper(limits.into()))]
- pub limits: CollectionLimitsVersion2,
+ pub limits: CollectionLimits,
+
+ #[version(2.., upper(Default::default()))]
+ pub permissions: CollectionPermissions,
#[version(..2)]
pub variable_on_chain_schema: BoundedVec<u8, ConstU32<VARIABLE_ON_CHAIN_SCHEMA_LIMIT>>,
@@ -329,27 +332,16 @@
pub struct RpcCollection<AccountId> {
pub owner: AccountId,
pub mode: CollectionMode,
- pub access: AccessMode,
pub name: Vec<u16>,
pub description: Vec<u16>,
pub token_prefix: Vec<u8>,
- pub mint_mode: bool,
- pub offchain_schema: Vec<u8>,
- pub schema_version: SchemaVersion,
pub sponsorship: SponsorshipState<AccountId>,
pub limits: CollectionLimits,
- pub const_on_chain_schema: Vec<u8>,
+ pub permissions: CollectionPermissions,
pub token_property_permissions: Vec<PropertyKeyPermission>,
pub properties: Vec<Property>,
}
-#[derive(Encode, Decode, Clone, PartialEq, TypeInfo, MaxEncodedLen)]
-#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
-pub enum CollectionField {
- ConstOnChainSchema,
- OffchainSchema,
-}
-
#[derive(Encode, Decode, Clone, PartialEq, TypeInfo, Derivative, MaxEncodedLen)]
#[derivative(Debug, Default(bound = ""))]
pub struct CreateCollectionData<AccountId> {
@@ -359,11 +351,9 @@
pub name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,
pub description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,
pub token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,
- pub offchain_schema: BoundedVec<u8, ConstU32<OFFCHAIN_SCHEMA_LIMIT>>,
- pub schema_version: Option<SchemaVersion>,
pub pending_sponsor: Option<AccountId>,
pub limits: Option<CollectionLimits>,
- pub const_on_chain_schema: BoundedVec<u8, ConstU32<CONST_ON_CHAIN_SCHEMA_LIMIT>>,
+ pub permissions: Option<CollectionPermissions>,
pub token_property_permissions: CollectionPropertiesPermissionsVec,
pub properties: CollectionPropertiesVec,
}
@@ -375,7 +365,6 @@
BoundedVec<Property, ConstU32<MAX_PROPERTIES_PER_ITEM>>;
/// 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 {
@@ -395,9 +384,6 @@
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 {
@@ -444,9 +430,26 @@
SponsoringRateLimit::Blocks(v) => Some(v.min(MAX_SPONSOR_TIMEOUT)),
}
}
- pub fn nesting_rule(&self) -> &NestingRule {
+}
+
+#[derive(Encode, Decode, Debug, Default, Clone, PartialEq, TypeInfo, MaxEncodedLen)]
+#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
+pub struct CollectionPermissions {
+ pub access: Option<AccessMode>,
+ pub mint_mode: Option<bool>,
+ pub nesting: Option<NestingRule>,
+}
+
+impl CollectionPermissions {
+ pub fn access(&self) -> AccessMode {
+ self.access.unwrap_or(AccessMode::Normal)
+ }
+ pub fn mint_mode(&self) -> bool {
+ self.mint_mode.unwrap_or(false)
+ }
+ pub fn nesting(&self) -> &NestingRule {
static DEFAULT: NestingRule = NestingRule::Disabled;
- self.nesting_rule.as_ref().unwrap_or(&DEFAULT)
+ self.nesting.as_ref().unwrap_or(&DEFAULT)
}
}
@@ -520,8 +523,6 @@
#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, TypeInfo, Derivative)]
#[derivative(Debug)]
pub struct CreateNftExData<CrossAccountId> {
- #[derivative(Debug(format_with = "bounded::vec_debug"))]
- pub const_data: BoundedVec<u8, CustomDataLimit>,
#[derivative(Debug(format_with = "bounded::vec_debug"))]
pub properties: CollectionPropertiesVec,
pub owner: CrossAccountId,
primitives/rpc/src/lib.rsdiffbeforeafterboth--- a/primitives/rpc/src/lib.rs
+++ b/primitives/rpc/src/lib.rs
@@ -41,7 +41,6 @@
fn token_owner(collection: CollectionId, token: TokenId) -> Result<Option<CrossAccountId>>;
fn topmost_token_owner(collection: CollectionId, token: TokenId) -> Result<Option<CrossAccountId>>;
- fn const_metadata(collection: CollectionId, token: TokenId) -> Result<Vec<u8>>;
fn collection_properties(collection: CollectionId, properties: Option<Vec<Vec<u8>>>) -> Result<Vec<Property>>;
runtime/common/src/runtime_apis.rsdiffbeforeafterboth--- a/runtime/common/src/runtime_apis.rs
+++ b/runtime/common/src/runtime_apis.rs
@@ -29,9 +29,6 @@
Ok(Some(<pallet_structure::Pallet<Runtime>>::find_topmost_owner(collection, token, &budget)?))
}
- fn const_metadata(collection: CollectionId, token: TokenId) -> Result<Vec<u8>, DispatchError> {
- dispatch_unique_runtime!(collection.const_metadata(token))
- }
fn collection_properties(
collection: CollectionId,
@@ -73,7 +70,6 @@
keys: Option<Vec<Vec<u8>>>
) -> Result<TokenData<CrossAccountId>, DispatchError> {
let token_data = TokenData {
- const_data: Self::const_metadata(collection, token_id)?,
properties: Self::token_properties(collection, token_id, keys)?,
owner: Self::token_owner(collection, token_id)?
};
runtime/tests/src/tests.rsdiffbeforeafterboth--- a/runtime/tests/src/tests.rs
+++ b/runtime/tests/src/tests.rs
@@ -2399,28 +2399,6 @@
// #endregion
#[test]
-fn set_const_on_chain_schema() {
- new_test_ext().execute_with(|| {
- let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));
-
- let origin1 = Origin::signed(1);
- assert_ok!(Unique::set_const_on_chain_schema(
- origin1,
- collection_id,
- b"test const on chain schema".to_vec().try_into().unwrap()
- ));
-
- assert_eq!(
- <pallet_common::CollectionData<Test>>::get((
- collection_id,
- CollectionField::ConstOnChainSchema
- )),
- b"test const on chain schema".to_vec()
- );
- });
-}
-
-#[test]
fn collection_transfer_flag_works() {
new_test_ext().execute_with(|| {
let origin1 = Origin::signed(1);