difftreelog
refactor rmrk proxy, add add_theme rmrk proxy
in: master
8 files changed
client/rpc/src/lib.rsdiffbeforeafterboth--- a/client/rpc/src/lib.rs
+++ b/client/rpc/src/lib.rs
@@ -30,7 +30,7 @@
// RMRK
use rmrk_rpc::RmrkApi as RmrkRuntimeApi;
use up_data_structs::{
- RmrkCollectionId, RmrkNftId, RmrkBaseId, RmrkNftChild, RmrkThemeName, RmrkPropertyKey,
+ RmrkCollectionId, RmrkNftId, RmrkBaseId, RmrkNftChild, RmrkThemeName,
RmrkResourceId,
};
@@ -248,7 +248,7 @@
fn collection_properties(
&self,
collection_id: RmrkCollectionId,
- filter_keys: Option<Vec<RmrkPropertyKey>>, //String
+ filter_keys: Option<Vec<String>>,
at: Option<BlockHash>,
) -> Result<Vec<PropertyInfo>>;
@@ -258,7 +258,7 @@
&self,
collection_id: RmrkCollectionId,
nft_id: RmrkNftId,
- filter_keys: Option<Vec<RmrkPropertyKey>>,
+ filter_keys: Option<Vec<String>>,
at: Option<BlockHash>,
) -> Result<Vec<PropertyInfo>>;
@@ -299,8 +299,8 @@
fn theme(
&self,
base_id: RmrkBaseId,
- theme_name: RmrkThemeName, // String
- filter_keys: Option<Vec<RmrkPropertyKey>>,
+ theme_name: String,
+ filter_keys: Option<Vec<String>>,
at: Option<BlockHash>,
) -> Result<Option<Theme>>;
}
@@ -523,11 +523,22 @@
pass_method!(account_tokens(account_id: AccountId, collection_id: RmrkCollectionId) -> Vec<RmrkNftId>, rmrk_api);
pass_method!(nft_children(collection_id: RmrkCollectionId, nft_id: RmrkNftId) -> Vec<RmrkNftChild>, rmrk_api);
pass_method!(
- collection_properties(collection_id: RmrkCollectionId, filter_keys: Option<Vec<RmrkPropertyKey>>) -> Vec<PropertyInfo>,
+ collection_properties(
+ collection_id: RmrkCollectionId,
+
+ #[map(|keys| string_keys_to_bytes_keys(keys))]
+ filter_keys: Option<Vec<String>>
+ ) -> Vec<PropertyInfo>,
rmrk_api
);
pass_method!(
- nft_properties(collection_id: RmrkCollectionId, nft_id: RmrkNftId, filter_keys: Option<Vec<RmrkPropertyKey>>) -> Vec<PropertyInfo>,
+ nft_properties(
+ collection_id: RmrkCollectionId,
+ nft_id: RmrkNftId,
+
+ #[map(|keys| string_keys_to_bytes_keys(keys))]
+ filter_keys: Option<Vec<String>>
+ ) -> Vec<PropertyInfo>,
rmrk_api
);
pass_method!(nft_resources(collection_id: RmrkCollectionId, nft_id: RmrkNftId) -> Vec<ResourceInfo>, rmrk_api);
@@ -535,7 +546,16 @@
pass_method!(base(base_id: RmrkBaseId) -> Option<BaseInfo>, rmrk_api);
pass_method!(base_parts(base_id: RmrkBaseId) -> Vec<PartType>, rmrk_api);
pass_method!(theme_names(base_id: RmrkBaseId) -> Vec<RmrkThemeName>, rmrk_api);
- pass_method!(theme(base_id: RmrkBaseId, theme_name: RmrkThemeName, filter_keys: Option<Vec<RmrkPropertyKey>>) -> Option<Theme>, rmrk_api);
+ pass_method!(
+ theme(
+ base_id: RmrkBaseId,
+
+ #[map(|n| n.into_bytes())]
+ theme_name: String,
+
+ #[map(|keys| string_keys_to_bytes_keys(keys))]
+ filter_keys: Option<Vec<String>>
+ ) -> Option<Theme>, rmrk_api);
}
fn string_keys_to_bytes_keys(keys: Option<Vec<String>>) -> Option<Vec<Vec<u8>>> {
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: &CollectionHandle<T>,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 #[transactional]811 pub fn set_scoped_collection_properties(812 collection: &CollectionHandle<T>,813 scope: PropertyScope,814 properties: impl Iterator<Item = Property>,815 ) -> DispatchResult {816 CollectionProperties::<T>::try_mutate(collection.id, |stored_properties| {817 stored_properties.try_scoped_set_from_iter(scope, properties)818 })819 .map_err(<Error<T>>::from)?;820821 Ok(())822 }823824 #[transactional]825 pub fn set_collection_properties(826 collection: &CollectionHandle<T>,827 sender: &T::CrossAccountId,828 properties: Vec<Property>,829 ) -> DispatchResult {830 for property in properties {831 Self::set_collection_property(collection, sender, property)?;832 }833834 Ok(())835 }836837 pub fn delete_collection_property(838 collection: &CollectionHandle<T>,839 sender: &T::CrossAccountId,840 property_key: PropertyKey,841 ) -> DispatchResult {842 collection.check_is_owner_or_admin(sender)?;843844 CollectionProperties::<T>::try_mutate(collection.id, |properties| {845 properties.remove(&property_key)846 })847 .map_err(<Error<T>>::from)?;848849 Self::deposit_event(Event::CollectionPropertyDeleted(850 collection.id,851 property_key,852 ));853854 Ok(())855 }856857 #[transactional]858 pub fn delete_collection_properties(859 collection: &CollectionHandle<T>,860 sender: &T::CrossAccountId,861 property_keys: Vec<PropertyKey>,862 ) -> DispatchResult {863 for key in property_keys {864 Self::delete_collection_property(collection, sender, key)?;865 }866867 Ok(())868 }869870 pub fn set_property_permission(871 collection: &CollectionHandle<T>,872 sender: &T::CrossAccountId,873 property_permission: PropertyKeyPermission,874 ) -> DispatchResult {875 collection.check_is_owner_or_admin(sender)?;876877 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);878 let current_permission = all_permissions.get(&property_permission.key);879 if matches![880 current_permission,881 Some(PropertyPermission { mutable: false, .. })882 ] {883 return Err(<Error<T>>::NoPermission.into());884 }885886 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {887 let property_permission = property_permission.clone();888 permissions.try_set(property_permission.key, property_permission.permission)889 })890 .map_err(<Error<T>>::from)?;891892 Self::deposit_event(Event::PropertyPermissionSet(893 collection.id,894 property_permission.key,895 ));896897 Ok(())898 }899900 #[transactional]901 pub fn set_property_permissions(902 collection: &CollectionHandle<T>,903 sender: &T::CrossAccountId,904 property_permissions: Vec<PropertyKeyPermission>,905 ) -> DispatchResult {906 for prop_pemission in property_permissions {907 Self::set_property_permission(collection, sender, prop_pemission)?;908 }909910 Ok(())911 }912913 pub fn get_collection_property(914 collection_id: CollectionId,915 key: &PropertyKey,916 ) -> Option<PropertyValue> {917 Self::collection_properties(collection_id).get(key).cloned()918 }919920 pub fn bytes_keys_to_property_keys(921 keys: Vec<Vec<u8>>,922 ) -> Result<Vec<PropertyKey>, DispatchError> {923 keys.into_iter()924 .map(|key| -> Result<PropertyKey, DispatchError> {925 key.try_into()926 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())927 })928 .collect::<Result<Vec<PropertyKey>, DispatchError>>()929 }930931 pub fn filter_collection_properties(932 collection_id: CollectionId,933 keys: Option<Vec<PropertyKey>>,934 ) -> Result<Vec<Property>, DispatchError> {935 let properties = Self::collection_properties(collection_id);936937 let properties = keys938 .map(|keys| {939 keys.into_iter()940 .filter_map(|key| {941 properties.get(&key).map(|value| Property {942 key,943 value: value.clone(),944 })945 })946 .collect()947 })948 .unwrap_or_else(|| {949 properties950 .into_iter()951 .map(|(key, value)| Property {952 key,953 value,954 })955 .collect()956 });957958 Ok(properties)959 }960961 pub fn filter_property_permissions(962 collection_id: CollectionId,963 keys: Option<Vec<PropertyKey>>,964 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {965 let permissions = Self::property_permissions(collection_id);966967 let key_permissions = keys968 .map(|keys| {969 keys.into_iter()970 .filter_map(|key| {971 permissions972 .get(&key)973 .map(|permission| PropertyKeyPermission {974 key,975 permission: permission.clone(),976 })977 })978 .collect()979 })980 .unwrap_or_else(|| {981 permissions982 .into_iter()983 .map(|(key, permission)| PropertyKeyPermission {984 key,985 permission,986 })987 .collect()988 });989990 Ok(key_permissions)991 }992993 fn set_field_raw(994 collection_id: CollectionId,995 field: CollectionField,996 value: Vec<u8>,997 ) -> DispatchResult {998 if !value.is_empty() {999 <CollectionData<T>>::insert(1000 (collection_id, field),1001 BoundedVec::try_from(value).map_err(|_| <Error<T>>::CollectionFieldSizeExceeded)?,1002 )1003 } else {1004 <CollectionData<T>>::remove((collection_id, field));1005 }1006 Ok(())1007 }10081009 pub fn set_field(1010 collection: &CollectionHandle<T>,1011 sender: &T::CrossAccountId,1012 field: CollectionField,1013 value: Vec<u8>,1014 ) -> DispatchResult {1015 collection.check_is_owner_or_admin(sender)?;10161017 // =========10181019 Self::set_field_raw(collection.id, field, value)1020 }10211022 pub fn toggle_allowlist(1023 collection: &CollectionHandle<T>,1024 sender: &T::CrossAccountId,1025 user: &T::CrossAccountId,1026 allowed: bool,1027 ) -> DispatchResult {1028 collection.check_is_owner_or_admin(sender)?;10291030 // =========10311032 if allowed {1033 <Allowlist<T>>::insert((collection.id, user), true);1034 } else {1035 <Allowlist<T>>::remove((collection.id, user));1036 }10371038 Ok(())1039 }10401041 pub fn toggle_admin(1042 collection: &CollectionHandle<T>,1043 sender: &T::CrossAccountId,1044 user: &T::CrossAccountId,1045 admin: bool,1046 ) -> DispatchResult {1047 collection.check_is_owner_or_admin(sender)?;10481049 let was_admin = <IsAdmin<T>>::get((collection.id, user));1050 if was_admin == admin {1051 return Ok(());1052 }1053 let amount = <AdminAmount<T>>::get(collection.id);10541055 if admin {1056 let amount = amount1057 .checked_add(1)1058 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1059 ensure!(1060 amount <= Self::collection_admins_limit(),1061 <Error<T>>::CollectionAdminCountExceeded,1062 );10631064 // =========10651066 <AdminAmount<T>>::insert(collection.id, amount);1067 <IsAdmin<T>>::insert((collection.id, user), true);1068 } else {1069 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1070 <IsAdmin<T>>::remove((collection.id, user));1071 }10721073 Ok(())1074 }10751076 pub fn clamp_limits(1077 mode: CollectionMode,1078 old_limit: &CollectionLimits,1079 mut new_limit: CollectionLimits,1080 ) -> Result<CollectionLimits, DispatchError> {1081 macro_rules! limit_default {1082 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1083 $(1084 if let Some($new) = $new.$field {1085 let $old = $old.$field($($arg)?);1086 let _ = $new;1087 let _ = $old;1088 $check1089 } else {1090 $new.$field = $old.$field1091 }1092 )*1093 }};1094 }10951096 limit_default!(old_limit, new_limit,1097 account_token_ownership_limit => ensure!(1098 new_limit <= MAX_TOKEN_OWNERSHIP,1099 <Error<T>>::CollectionLimitBoundsExceeded,1100 ),1101 sponsor_transfer_timeout(match mode {1102 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1103 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1104 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1105 }) => ensure!(1106 new_limit <= MAX_SPONSOR_TIMEOUT,1107 <Error<T>>::CollectionLimitBoundsExceeded,1108 ),1109 sponsored_data_size => ensure!(1110 new_limit <= CUSTOM_DATA_LIMIT,1111 <Error<T>>::CollectionLimitBoundsExceeded,1112 ),1113 token_limit => ensure!(1114 old_limit >= new_limit && new_limit > 0,1115 <Error<T>>::CollectionTokenLimitExceeded1116 ),1117 owner_can_transfer => ensure!(1118 old_limit || !new_limit,1119 <Error<T>>::OwnerPermissionsCantBeReverted,1120 ),1121 owner_can_destroy => ensure!(1122 old_limit || !new_limit,1123 <Error<T>>::OwnerPermissionsCantBeReverted,1124 ),1125 sponsored_data_rate_limit => {},1126 transfers_enabled => {},1127 );1128 Ok(new_limit)1129 }1130}11311132#[macro_export]1133macro_rules! unsupported {1134 () => {1135 Err(<Error<T>>::UnsupportedOperation.into())1136 };1137}11381139/// Worst cases1140pub trait CommonWeightInfo<CrossAccountId> {1141 fn create_item() -> Weight;1142 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;1143 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1144 fn burn_item() -> Weight;1145 fn set_collection_properties(amount: u32) -> Weight;1146 fn delete_collection_properties(amount: u32) -> Weight;1147 fn set_token_properties(amount: u32) -> Weight;1148 fn delete_token_properties(amount: u32) -> Weight;1149 fn set_property_permissions(amount: u32) -> Weight;1150 fn transfer() -> Weight;1151 fn approve() -> Weight;1152 fn transfer_from() -> Weight;1153 fn burn_from() -> Weight;1154}11551156pub trait CommonCollectionOperations<T: Config> {1157 fn create_item(1158 &self,1159 sender: T::CrossAccountId,1160 to: T::CrossAccountId,1161 data: CreateItemData,1162 nesting_budget: &dyn Budget,1163 ) -> DispatchResultWithPostInfo;1164 fn create_multiple_items(1165 &self,1166 sender: T::CrossAccountId,1167 to: T::CrossAccountId,1168 data: Vec<CreateItemData>,1169 nesting_budget: &dyn Budget,1170 ) -> DispatchResultWithPostInfo;1171 fn create_multiple_items_ex(1172 &self,1173 sender: T::CrossAccountId,1174 data: CreateItemExData<T::CrossAccountId>,1175 nesting_budget: &dyn Budget,1176 ) -> DispatchResultWithPostInfo;1177 fn burn_item(1178 &self,1179 sender: T::CrossAccountId,1180 token: TokenId,1181 amount: u128,1182 ) -> DispatchResultWithPostInfo;1183 fn set_collection_properties(1184 &self,1185 sender: T::CrossAccountId,1186 properties: Vec<Property>,1187 ) -> DispatchResultWithPostInfo;1188 fn delete_collection_properties(1189 &self,1190 sender: &T::CrossAccountId,1191 property_keys: Vec<PropertyKey>,1192 ) -> DispatchResultWithPostInfo;1193 fn set_token_properties(1194 &self,1195 sender: T::CrossAccountId,1196 token_id: TokenId,1197 property: Vec<Property>,1198 ) -> DispatchResultWithPostInfo;1199 fn delete_token_properties(1200 &self,1201 sender: T::CrossAccountId,1202 token_id: TokenId,1203 property_keys: Vec<PropertyKey>,1204 ) -> DispatchResultWithPostInfo;1205 fn set_property_permissions(1206 &self,1207 sender: &T::CrossAccountId,1208 property_permissions: Vec<PropertyKeyPermission>,1209 ) -> DispatchResultWithPostInfo;1210 fn transfer(1211 &self,1212 sender: T::CrossAccountId,1213 to: T::CrossAccountId,1214 token: TokenId,1215 amount: u128,1216 nesting_budget: &dyn Budget,1217 ) -> DispatchResultWithPostInfo;1218 fn approve(1219 &self,1220 sender: T::CrossAccountId,1221 spender: T::CrossAccountId,1222 token: TokenId,1223 amount: u128,1224 ) -> DispatchResultWithPostInfo;1225 fn transfer_from(1226 &self,1227 sender: T::CrossAccountId,1228 from: T::CrossAccountId,1229 to: T::CrossAccountId,1230 token: TokenId,1231 amount: u128,1232 nesting_budget: &dyn Budget,1233 ) -> DispatchResultWithPostInfo;1234 fn burn_from(1235 &self,1236 sender: T::CrossAccountId,1237 from: T::CrossAccountId,1238 token: TokenId,1239 amount: u128,1240 nesting_budget: &dyn Budget,1241 ) -> DispatchResultWithPostInfo;12421243 fn check_nesting(1244 &self,1245 sender: T::CrossAccountId,1246 from: (CollectionId, TokenId),1247 under: TokenId,1248 budget: &dyn Budget,1249 ) -> DispatchResult;12501251 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1252 fn collection_tokens(&self) -> Vec<TokenId>;1253 fn token_exists(&self, token: TokenId) -> bool;1254 fn last_token_id(&self) -> TokenId;12551256 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1257 fn const_metadata(&self, token: TokenId) -> Vec<u8>;1258 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;1259 fn token_properties(&self, token_id: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;1260 /// Amount of unique collection tokens1261 fn total_supply(&self) -> u32;1262 /// Amount of different tokens account has (Applicable to nonfungible/refungible)1263 fn account_balance(&self, account: T::CrossAccountId) -> u32;1264 /// Amount of specific token account have (Applicable to fungible/refungible)1265 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1266 fn allowance(1267 &self,1268 sender: T::CrossAccountId,1269 spender: T::CrossAccountId,1270 token: TokenId,1271 ) -> u128;1272}12731274// Flexible enough for implementing CommonCollectionOperations1275pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1276 let post_info = PostDispatchInfo {1277 actual_weight: Some(weight),1278 pays_fee: Pays::Yes,1279 };1280 match res {1281 Ok(()) => Ok(post_info),1282 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1283 }1284}12851286impl<T: Config> From<PropertiesError> for Error<T> {1287 fn from(error: PropertiesError) -> Self {1288 match error {1289 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1290 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1291 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1292 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1293 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1294 }1295 }1296}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 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}pallets/nonfungible/src/lib.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -195,12 +195,12 @@
}
pub fn set_scoped_token_property(
- collection: &CollectionHandle<T>,
+ collection_id: CollectionId,
token_id: TokenId,
scope: PropertyScope,
property: Property,
) -> DispatchResult {
- TokenProperties::<T>::try_mutate((collection.id, token_id), |properties| {
+ TokenProperties::<T>::try_mutate((collection_id, token_id), |properties| {
properties.try_scoped_set(scope, property.key, property.value)
})
.map_err(<CommonError<T>>::from)?;
@@ -209,12 +209,12 @@
}
pub fn set_scoped_token_properties(
- collection: &CollectionHandle<T>,
+ collection_id: CollectionId,
token_id: TokenId,
scope: PropertyScope,
properties: impl Iterator<Item=Property>,
) -> DispatchResult {
- TokenProperties::<T>::try_mutate((collection.id, token_id), |stored_properties| {
+ TokenProperties::<T>::try_mutate((collection_id, token_id), |stored_properties| {
stored_properties.try_scoped_set_from_iter(scope, properties)
})
.map_err(<CommonError<T>>::from)?;
@@ -222,8 +222,8 @@
Ok(())
}
- pub fn current_token_id(collection: &CollectionHandle<T>) -> TokenId {
- TokenId(<TokensMinted<T>>::get(collection.id))
+ pub fn current_token_id(collection_id: CollectionId) -> TokenId {
+ TokenId(<TokensMinted<T>>::get(collection_id))
}
}
pallets/proxy-rmrk-core/src/lib.rsdiffbeforeafterboth--- a/pallets/proxy-rmrk-core/src/lib.rs
+++ b/pallets/proxy-rmrk-core/src/lib.rs
@@ -33,6 +33,8 @@
use misc::*;
pub use property::*;
+use RmrkProperty::*;
+
#[frame_support::pallet]
pub mod pallet {
use super::*;
@@ -135,15 +137,13 @@
}
let collection_id = collection_id_res?;
-
- let collection = Self::get_nft_collection(collection_id)?.into_inner();
<PalletCommon<T>>::set_scoped_collection_properties(
- &collection,
+ collection_id,
PropertyScope::Rmrk,
[
- rmrk_property!(Config=T, Metadata: metadata)?,
- rmrk_property!(Config=T, CollectionType: CollectionType::Regular)?,
+ Self::rmrk_property(Metadata, &metadata)?,
+ Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,
].into_iter()
)?;
@@ -168,7 +168,7 @@
let unique_collection_id = collection_id.into();
- let collection = Self::get_typed_nft_collection(unique_collection_id, CollectionType::Regular)?;
+ let collection = Self::get_typed_nft_collection(unique_collection_id, misc::CollectionType::Regular)?;
ensure!(collection.total_supply() == 0, <Error<T>>::CollectionNotEmpty);
@@ -193,7 +193,7 @@
Self::change_collection_owner(
collection_id.into(),
- CollectionType::Regular,
+ misc::CollectionType::Regular,
sender.clone(),
new_issuer.clone()
)?;
@@ -218,7 +218,7 @@
let collection = Self::get_typed_nft_collection(
collection_id.into(),
- CollectionType::Regular
+ misc::CollectionType::Regular
)?;
Self::check_collection_owner(&collection, &cross_sender)?;
@@ -253,20 +253,27 @@
amount
});
+ let collection = Self::get_typed_nft_collection(
+ collection_id.into(),
+ misc::CollectionType::Regular,
+ )?;
+
let nft_id = Self::create_nft(
&sender,
&cross_owner,
- collection_id.into(),
- CollectionType::Regular,
+ &collection,
NftType::Regular,
[
- rmrk_property!(Config=T, RoyaltyInfo: royalty_info)?,
- rmrk_property!(Config=T, Metadata: metadata)?,
- rmrk_property!(Config=T, Equipped: false)?,
- rmrk_property!(Config=T, ResourceCollection: None::<CollectionId>)?,
- rmrk_property!(Config=T, ResourcePriorities: <Vec<u8>>::new())?,
+ Self::rmrk_property(RoyaltyInfo, &royalty_info)?,
+ Self::rmrk_property(Metadata, &metadata)?,
+ Self::rmrk_property(Equipped, &false)?,
+ Self::rmrk_property(ResourceCollection, &None::<CollectionId>)?,
+ Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,
].into_iter()
- )?;
+ ).map_err(|err| match err {
+ DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),
+ err => Self::map_common_err_to_proxy(err)
+ })?;
Self::deposit_event(Event::NftMinted {
owner,
@@ -290,7 +297,7 @@
Self::destroy_nft(
cross_sender,
collection_id.into(),
- CollectionType::Regular,
+ misc::CollectionType::Regular,
nft_id.into()
)?;
@@ -302,19 +309,37 @@
}
impl<T: Config> Pallet<T> {
+ pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {
+ let key = rmrk_key.to_key::<T>()?;
+
+ let scoped_key = PropertyScope::Rmrk.apply(key)
+ .map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;
+
+ Ok(scoped_key)
+ }
+
+ pub fn rmrk_property<E: Encode>(rmrk_key: RmrkProperty, value: &E) -> Result<Property, DispatchError> {
+ let key = rmrk_key.to_key::<T>()?;
+
+ let value = value.encode()
+ .try_into()
+ .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;
+
+ let property = Property {
+ key,
+ value,
+ };
+
+ Ok(property)
+ }
+
pub fn create_nft(
sender: &T::CrossAccountId,
owner: &T::CrossAccountId,
- collection_id: CollectionId,
- collection_type: CollectionType,
+ collection: &NonfungibleHandle<T>,
nft_type: NftType,
properties: impl Iterator<Item=Property>
) -> Result<TokenId, DispatchError> {
- let collection = Self::get_typed_nft_collection(
- collection_id,
- collection_type
- )?;
-
let data = CreateNftExData {
const_data: nft_type.encode()
.try_into()
@@ -326,16 +351,16 @@
let budget = budget::Value::new(2);
<PalletNft<T>>::create_item(
- &collection,
+ collection,
sender,
data,
&budget,
- ).map_err(Self::map_common_err_to_proxy)?;
+ )?;
- let nft_id = <PalletNft<T>>::current_token_id(&collection);
+ let nft_id = <PalletNft<T>>::current_token_id(collection.id);
<PalletNft<T>>::set_scoped_token_properties(
- &collection,
+ collection.id,
nft_id,
PropertyScope::Rmrk,
properties
@@ -347,7 +372,7 @@
fn destroy_nft(
sender: T::CrossAccountId,
collection_id: CollectionId,
- collection_type: CollectionType,
+ collection_type: misc::CollectionType,
token_id: TokenId
) -> DispatchResult {
let collection = Self::get_typed_nft_collection(
@@ -363,7 +388,7 @@
fn change_collection_owner(
collection_id: CollectionId,
- collection_type: CollectionType,
+ collection_type: misc::CollectionType,
sender: T::AccountId,
new_owner: T::AccountId,
) -> DispatchResult {
@@ -390,10 +415,12 @@
pub fn get_nft_collection(collection_id: CollectionId) -> Result<NonfungibleHandle<T>, DispatchError> {
let collection = <CollectionHandle<T>>::try_get(collection_id)
- .map_err(|_| <Error<T>>::CollectionUnknown)?
- .into_nft_collection()?;
+ .map_err(|_| <Error<T>>::CollectionUnknown)?;
- Ok(collection)
+ match collection.mode {
+ CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),
+ _ => Err(<Error<T>>::CollectionUnknown.into())
+ }
}
// should this even be here, might displace it to common/nonfungible -- but they did not need it, only rmrk does
@@ -407,23 +434,23 @@
pub fn get_collection_property(collection_id: CollectionId, key: RmrkProperty) -> Result<PropertyValue, DispatchError> {
let collection_property = <PalletCommon<T>>::collection_properties(collection_id)
- .get(&rmrk_property!(Config=T, key)?)
+ .get(&Self::rmrk_property_key(key)?)
.ok_or(<Error<T>>::CollectionUnknown)?
.clone();
Ok(collection_property)
}
- pub fn get_collection_type(collection_id: CollectionId) -> Result<CollectionType, DispatchError> {
- let value = Self::get_collection_property(collection_id, RmrkProperty::CollectionType)?;
- let collection_type: CollectionType = (&value)
- .try_into()
- .map_err(<Error<T>>::from)?;
+ pub fn get_collection_type(collection_id: CollectionId) -> Result<misc::CollectionType, DispatchError> {
+ let value = Self::get_collection_property(collection_id, CollectionType)?;
+
+ let mut value = value.as_slice();
- Ok(collection_type)
+ misc::CollectionType::decode(&mut value)
+ .map_err(|_| <Error<T>>::CorruptedCollectionType.into())
}
- pub fn ensure_collection_type(collection_id: CollectionId, collection_type: CollectionType) -> DispatchResult {
+ pub fn ensure_collection_type(collection_id: CollectionId, collection_type: misc::CollectionType) -> DispatchResult {
let actual_type = Self::get_collection_type(collection_id)?;
ensure!(actual_type == collection_type, <CommonError<T>>::NoPermission);
@@ -432,7 +459,7 @@
pub fn get_nft_property(collection_id: CollectionId, nft_id: TokenId, key: RmrkProperty) -> Result<PropertyValue, DispatchError> {
let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))
- .get(&rmrk_property!(Config=T, key)?)
+ .get(&Self::rmrk_property_key(key)?)
.ok_or(<Error<T>>::NoAvailableNftId)?
.clone();
@@ -440,10 +467,12 @@
}
pub fn get_nft_type(collection_id: CollectionId, token_id: TokenId) -> Result<NftType, DispatchError> {
- <TokenData<T>>::get((collection_id, token_id))
- .unwrap()
- .rmrk_nft_type()
- .ok_or_else(|| <Error<T>>::NoAvailableNftId.into())
+ 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 ensure_nft_type(collection_id: CollectionId, token_id: TokenId, nft_type: NftType) -> DispatchResult {
@@ -466,7 +495,7 @@
let value = Self::get_nft_property(
collection_id,
token_id,
- RmrkProperty::ThemeProperty(&key)
+ ThemeProperty(&key)
).ok()?.decode_or_default();
let property = RmrkThemeProperty {
@@ -491,7 +520,7 @@
collection_id: CollectionId,
token_id: TokenId
) -> Result<impl Iterator<Item=RmrkThemeProperty>, DispatchError> {
- let key_prefix = rmrk_property!(Config=T, key: ThemeProperty(&RmrkString::default()))?;
+ let key_prefix = Self::rmrk_property_key(ThemeProperty(&RmrkString::default()))?;
let properties = <PalletNft<T>>::token_properties((collection_id, token_id))
.into_iter()
@@ -514,7 +543,7 @@
pub fn get_typed_nft_collection(
collection_id: CollectionId,
- collection_type: CollectionType
+ collection_type: misc::CollectionType
) -> Result<NonfungibleHandle<T>, DispatchError> {
Self::ensure_collection_type(collection_id, collection_type)?;
pallets/proxy-rmrk-core/src/misc.rsdiffbeforeafterboth--- a/pallets/proxy-rmrk-core/src/misc.rs
+++ b/pallets/proxy-rmrk-core/src/misc.rs
@@ -1,23 +1,6 @@
use super::*;
use codec::{Encode, Decode};
-use pallet_nonfungible::{NonfungibleHandle, ItemData};
-
-macro_rules! impl_rmrk_value {
- ($enum_name:path, decode_error: $error:ident) => {
- impl TryFrom<&PropertyValue> for $enum_name {
- type Error = MiscError;
-
- fn try_from(value: &PropertyValue) -> Result<Self, Self::Error> {
- let mut value = value.as_slice();
- <$enum_name>::decode(&mut value)
- .map_err(|_| MiscError::$error)
- }
- }
-
- };
-}
-
#[macro_export]
macro_rules! map_common_err_to_proxy {
(match $err:ident { $($common_err:ident => $proxy_err:ident),+ }) => {
@@ -29,59 +12,8 @@
$err
}
};
-}
-
-pub enum MiscError {
- RmrkPropertyValueIsTooLong,
- CorruptedCollectionType,
-}
-
-impl<T: Config> From<MiscError> for Error<T> {
- fn from(error: MiscError) -> Self {
- match error {
- MiscError::RmrkPropertyValueIsTooLong => Self::RmrkPropertyValueIsTooLong,
- MiscError::CorruptedCollectionType => Self::CorruptedCollectionType,
- }
- }
-}
-
-pub trait IntoNftCollection<T: Config> {
- fn into_nft_collection(self) -> Result<NonfungibleHandle<T>, Error<T>>;
}
-impl<T: Config> IntoNftCollection<T> for CollectionHandle<T> {
- fn into_nft_collection(self) -> Result<NonfungibleHandle<T>, Error<T>> {
- match self.mode {
- CollectionMode::NFT => Ok(NonfungibleHandle::cast(self)),
- _ => Err(<Error<T>>::CollectionUnknown)
- }
- }
-}
-
-pub trait IntoPropertyValue {
- fn into_property_value(self) -> Result<PropertyValue, MiscError>;
-}
-
-impl<T: Encode> IntoPropertyValue for T {
- fn into_property_value(self) -> Result<PropertyValue, MiscError> {
- self.encode()
- .try_into()
- .map_err(|_| MiscError::RmrkPropertyValueIsTooLong)
- }
-}
-
-pub trait RmrkNft {
- fn rmrk_nft_type(&self) -> Option<NftType>;
-}
-
-impl<CrossAccountId> RmrkNft for ItemData<CrossAccountId> {
- fn rmrk_nft_type(&self) -> Option<NftType> {
- let mut value = self.const_data.as_slice();
-
- NftType::decode(&mut value).ok()
- }
-}
-
pub trait RmrkDecode<T: Decode + Default, S> {
fn decode_or_default(&self) -> T;
}
@@ -121,5 +53,3 @@
SlotPart,
Theme
}
-
-impl_rmrk_value!(CollectionType, decode_error: CorruptedCollectionType);
pallets/proxy-rmrk-core/src/property.rsdiffbeforeafterboth--- a/pallets/proxy-rmrk-core/src/property.rs
+++ b/pallets/proxy-rmrk-core/src/property.rs
@@ -71,31 +71,3 @@
}
}
}
-
-#[macro_export]
-macro_rules! rmrk_property {
- (Config=$cfg:ty, key: $key:ident $(($key_ext:expr))?) => {
- rmrk_property!(Config=$cfg, $crate::RmrkProperty::$key $(($key_ext))?)
- };
-
- (Config=$cfg:ty, $key:ident $(($key_ext:expr))?: $value:expr) => {{
- let key = rmrk_property!(@$cfg, $crate::RmrkProperty::$key $(($key_ext))?)?;
-
- let value = $value.into_property_value()
- .map_err(<$crate::Error<$cfg>>::from)?;
-
- Ok::<_, $crate::Error<$cfg>>(Property {
- key,
- value,
- })
- }};
-
- (@$cfg:ty, $key_enum:expr) => {
- $key_enum.to_key::<$cfg>()
- };
-
- (Config=$cfg:ty, $key_enum:expr) => {
- PropertyScope::Rmrk.apply(rmrk_property!(@$cfg, $key_enum)?)
- .map_err(|_| <$crate::Error<$cfg>>::RmrkPropertyKeyIsTooLong)
- };
-}
pallets/proxy-rmrk-equip/src/lib.rsdiffbeforeafterboth--- a/pallets/proxy-rmrk-equip/src/lib.rs
+++ b/pallets/proxy-rmrk-equip/src/lib.rs
@@ -20,9 +20,9 @@
use frame_system::{pallet_prelude::*, ensure_signed};
use sp_runtime::DispatchError;
use up_data_structs::*;
-use pallet_common::{Pallet as PalletCommon, Error as CommonError, CollectionHandle};
-use pallet_rmrk_core::{Pallet as PalletCore, rmrk_property, misc::*};
-use pallet_nonfungible::{Pallet as PalletNft};
+use pallet_common::{Pallet as PalletCommon, Error as CommonError};
+use pallet_rmrk_core::{Pallet as PalletCore, misc::{self, *}, property::RmrkProperty::*};
+use pallet_nonfungible::{Pallet as PalletNft, NonfungibleHandle};
use pallet_evm::account::CrossAccountId;
pub use pallet::*;
@@ -48,6 +48,16 @@
TokenId
>;
+ #[pallet::storage]
+ #[pallet::getter(fn base_has_default_theme)]
+ pub type BaseHasDefaultTheme<T: Config> = StorageMap<
+ _,
+ Twox64Concat,
+ CollectionId,
+ bool,
+ ValueQuery
+ >;
+
#[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
pub struct Pallet<T>(_);
@@ -63,7 +73,11 @@
#[pallet::error]
pub enum Error<T> {
+ PermissionError,
NoAvailableBaseId,
+ NoAvailablePartId,
+ BaseDoesntExist,
+ NeedsDefaultThemeFirst,
}
#[pallet::call]
@@ -95,17 +109,17 @@
let collection_id = collection_id_res?;
- let collection = <PalletCore<T>>::get_nft_collection(collection_id)?.into_inner();
-
<PalletCommon<T>>::set_scoped_collection_properties(
- &collection,
+ collection_id,
PropertyScope::Rmrk,
[
- rmrk_property!(Config=T, CollectionType: CollectionType::Base)?,
- rmrk_property!(Config=T, BaseType: base_type)?,
+ <PalletCore<T>>::rmrk_property(CollectionType, &misc::CollectionType::Base)?,
+ <PalletCore<T>>::rmrk_property(BaseType, &base_type)?,
].into_iter()
)?;
+ let collection = <PalletCore<T>>::get_nft_collection(collection_id)?;
+
for part in parts {
let part_id = part.id();
let part_token_id = Self::create_part(
@@ -117,10 +131,10 @@
<InernalPartId<T>>::insert(collection_id, part_id, part_token_id);
<PalletNft<T>>::set_scoped_token_property(
- &collection,
+ collection_id,
part_token_id,
PropertyScope::Rmrk,
- rmrk_property!(Config=T, ExternalPartId: part_id)?
+ <PalletCore<T>>::rmrk_property(ExternalPartId, &part_id)?
)?;
}
@@ -128,13 +142,64 @@
Ok(())
}
+
+ #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]
+ #[transactional]
+ pub fn theme_add(
+ origin: OriginFor<T>,
+ base_id: RmrkBaseId,
+ theme: RmrkTheme,
+ ) -> DispatchResult {
+ let sender = ensure_signed(origin)?;
+
+ let sender = T::CrossAccountId::from_sub(sender);
+ let owner = &sender;
+
+ let collection_id: CollectionId = base_id.into();
+
+ let collection = <PalletCore<T>>::get_typed_nft_collection(
+ collection_id,
+ misc::CollectionType::Base
+ ).map_err(|_| <Error<T>>::BaseDoesntExist)?;
+
+ if theme.name.as_slice() == b"default" {
+ <BaseHasDefaultTheme<T>>::insert(collection_id, true);
+ } else if !Self::base_has_default_theme(collection_id) {
+ return Err(<Error<T>>::NeedsDefaultThemeFirst.into());
+ }
+
+ let token_id = <PalletCore<T>>::create_nft(
+ &sender,
+ owner,
+ &collection,
+ NftType::Theme,
+ [
+ <PalletCore<T>>::rmrk_property(ThemeName, &theme.name)?,
+ <PalletCore<T>>::rmrk_property(ThemeInherit, &theme.inherit)?
+ ].into_iter()
+ ).map_err(|_| <Error<T>>::PermissionError)?;
+
+ for property in theme.properties {
+ <PalletNft<T>>::set_scoped_token_property(
+ collection_id,
+ token_id,
+ PropertyScope::Rmrk,
+ <PalletCore<T>>::rmrk_property(
+ ThemeProperty(&property.key),
+ &property.value
+ )?
+ )?;
+ }
+
+ Ok(())
+ }
}
}
impl<T: Config> Pallet<T> {
fn create_part(
sender: &T::CrossAccountId,
- collection: &CollectionHandle<T>,
+ collection: &NonfungibleHandle<T>,
part: RmrkPartType
) -> Result<TokenId, DispatchError> {
let owner = sender;
@@ -150,21 +215,23 @@
let token_id = <PalletCore<T>>::create_nft(
sender,
owner,
- collection.id,
- CollectionType::Base,
+ collection,
nft_type,
[
- rmrk_property!(Config=T, Src: src)?,
- rmrk_property!(Config=T, ZIndex: z_index)?
+ <PalletCore<T>>::rmrk_property(Src, &src)?,
+ <PalletCore<T>>::rmrk_property(ZIndex, &z_index)?
].into_iter()
- )?;
+ ).map_err(|err| match err {
+ DispatchError::Arithmetic(_) => <Error<T>>::NoAvailablePartId.into(),
+ err => err
+ })?;
if let RmrkPartType::SlotPart(part) = part {
<PalletNft<T>>::set_scoped_token_property(
- collection,
+ collection.id,
token_id,
PropertyScope::Rmrk,
- rmrk_property!(Config=T, EquippableList: part.equippable)?
+ <PalletCore<T>>::rmrk_property(EquippableList, &part.equippable)?
)?;
}
runtime/common/src/runtime_apis.rsdiffbeforeafterboth--- a/runtime/common/src/runtime_apis.rs
+++ b/runtime/common/src/runtime_apis.rs
@@ -347,7 +347,7 @@
fn base_parts(base_id: RmrkBaseId) -> Result<Vec<RmrkPartType>, DispatchError> {
use frame_support::BoundedVec;
- use pallet_proxy_rmrk_core::{RmrkProperty, misc::{CollectionType, NftType, RmrkNft, RmrkDecode}};
+ use pallet_proxy_rmrk_core::{RmrkProperty, misc::{CollectionType, NftType, RmrkDecode}};
let collection_id = CollectionId(base_id);
if RmrkCore::ensure_collection_type(collection_id, CollectionType::Base).is_err() { return Ok(Vec::new()); }
@@ -379,7 +379,7 @@
fn theme_names(base_id: RmrkBaseId) -> Result<Vec<RmrkThemeName>, DispatchError> {
use frame_support::BoundedVec;
- use pallet_proxy_rmrk_core::{RmrkProperty, misc::{CollectionType, RmrkNft, RmrkDecode}};
+ use pallet_proxy_rmrk_core::{RmrkProperty, misc::{CollectionType, RmrkDecode}};
let collection_id = CollectionId(base_id);
if RmrkCore::ensure_collection_type(collection_id, CollectionType::Base).is_err() {
@@ -407,7 +407,7 @@
use frame_support::BoundedVec;
use pallet_proxy_rmrk_core::{
RmrkProperty,
- misc::{CollectionType, NftType, RmrkNft, RmrkDecode}
+ misc::{CollectionType, NftType, RmrkDecode}
};
let collection_id = CollectionId(base_id);