difftreelog
Merge pull request #848 from UniqueNetwork/fix/find-parent
in: master
15 files changed
pallets/common/src/erc.rsdiffbeforeafterboth--- a/pallets/common/src/erc.rs
+++ b/pallets/common/src/erc.rs
@@ -28,8 +28,8 @@
use sp_std::{vec, vec::Vec};
use sp_core::U256;
use up_data_structs::{
- AccessMode, CollectionMode, CollectionPermissions, OwnerRestrictedSet, Property,
- SponsoringRateLimit, SponsorshipState,
+ CollectionMode, CollectionPermissions, OwnerRestrictedSet, Property, SponsoringRateLimit,
+ SponsorshipState,
};
use crate::{
pallets/common/src/eth.rsdiffbeforeafterboth--- a/pallets/common/src/eth.rs
+++ b/pallets/common/src/eth.rs
@@ -80,10 +80,7 @@
if cross_account_id.is_canonical_substrate() {
Self::from_sub::<T>(cross_account_id.as_sub())
} else {
- Self {
- eth: *cross_account_id.as_eth(),
- sub: Default::default(),
- }
+ Self::from_eth(*cross_account_id.as_eth())
}
}
/// Creates [`CrossAddress`] from Substrate account.
@@ -97,6 +94,13 @@
sub: U256::from_big_endian(account_id.as_ref()),
}
}
+ /// Creates [`CrossAddress`] from Ethereum account.
+ pub fn from_eth(address: Address) -> Self {
+ Self {
+ eth: address,
+ sub: Default::default(),
+ }
+ }
/// Converts [`CrossAddress`] to `CrossAccountId`.
pub fn into_sub_cross_account<T>(&self) -> evm_coder::execution::Result<T::CrossAccountId>
where
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//! # Common pallet18//!19//! The Common pallet provides functionality for handling collections.20//!21//! ## Overview22//!23//! The Common pallet provides an interface for common collection operations for different collection types24//! (see [CommonCollectionOperations]), as well as a generic dispatcher for these, see [dispatch] module.25//! It also provides this functionality to EVM, see [erc] and [eth] modules.26//!27//! The Common pallet provides functions for:28//!29//! - Setting and approving collection sponsor.30//! - Get\set\delete allow list.31//! - Get\set\delete collection properties.32//! - Get\set\delete collection property permissions.33//! - Get\set\delete token property permissions.34//! - Get\set\delete collection administrators.35//! - Checking access permissions.36//!37//! ### Terminology38//! **Collection sponsor** - For the collection, you can set a sponsor, at whose expense it will39//! be possible to mint tokens.40//!41//! **Allow list** - List of users who have the right to minting tokens.42//!43//! **Collection properties** - Collection properties are simply key-value stores where various44//! metadata can be placed.45//!46//! **Permissions on token properties** - For each property in the token can be set permission47//! to change, see [`PropertyPermission`].48//!49//! **Collection administrator** - For a collection, you can set administrators who have the right50//! to most actions on the collection.5152#![warn(missing_docs)]53#![cfg_attr(not(feature = "std"), no_std)]54extern crate alloc;5556use core::ops::{Deref, DerefMut};57use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};58use sp_std::vec::Vec;59use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};60use evm_coder::ToLog;61use frame_support::{62 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},63 ensure,64 traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},65 dispatch::Pays,66 transactional,67};68use pallet_evm::GasWeightMapping;69use up_data_structs::{70 COLLECTION_NUMBER_LIMIT,71 Collection,72 RpcCollection,73 CollectionFlags,74 RpcCollectionFlags,75 CollectionId,76 CreateItemData,77 MAX_TOKEN_PREFIX_LENGTH,78 COLLECTION_ADMINS_LIMIT,79 TokenId,80 TokenChild,81 CollectionStats,82 MAX_TOKEN_OWNERSHIP,83 CollectionMode,84 NFT_SPONSOR_TRANSFER_TIMEOUT,85 FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,86 REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,87 MAX_SPONSOR_TIMEOUT,88 CUSTOM_DATA_LIMIT,89 CollectionLimits,90 CreateCollectionData,91 SponsorshipState,92 CreateItemExData,93 SponsoringRateLimit,94 budget::Budget,95 PhantomType,96 Property,97 Properties,98 PropertiesPermissionMap,99 PropertyKey,100 PropertyValue,101 PropertyPermission,102 PropertiesError,103 TokenOwnerError,104 PropertyKeyPermission,105 TokenData,106 TrySetProperty,107 PropertyScope,108 // RMRK109 RmrkCollectionInfo,110 RmrkInstanceInfo,111 RmrkResourceInfo,112 RmrkPropertyInfo,113 RmrkBaseInfo,114 RmrkPartType,115 RmrkBoundedTheme,116 RmrkNftChild,117 CollectionPermissions,118};119use up_pov_estimate_rpc::PovInfo;120121pub use pallet::*;122use sp_core::H160;123use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};124#[cfg(feature = "runtime-benchmarks")]125pub mod benchmarking;126pub mod dispatch;127pub mod erc;128pub mod eth;129pub mod weights;130131/// Weight info.132pub type SelfWeightOf<T> = <T as Config>::WeightInfo;133134/// Collection handle contains information about collection data and id.135/// Also provides functionality to count consumed gas.136///137/// CollectionHandle is used as a generic wrapper for collections of all types.138/// It allows to perform common operations and queries on any collection type,139/// both completely general for all, as well as their respective implementations of [`CommonCollectionOperations`].140#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]141pub struct CollectionHandle<T: Config> {142 /// Collection id143 pub id: CollectionId,144 collection: Collection<T::AccountId>,145 /// Substrate recorder for counting consumed gas146 pub recorder: SubstrateRecorder<T>,147}148149impl<T: Config> WithRecorder<T> for CollectionHandle<T> {150 fn recorder(&self) -> &SubstrateRecorder<T> {151 &self.recorder152 }153 fn into_recorder(self) -> SubstrateRecorder<T> {154 self.recorder155 }156}157158impl<T: Config> CollectionHandle<T> {159 /// Same as [CollectionHandle::new] but with an explicit gas limit.160 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {161 <CollectionById<T>>::get(id).map(|collection| Self {162 id,163 collection,164 recorder: SubstrateRecorder::new(gas_limit),165 })166 }167168 /// Same as [CollectionHandle::new] but with an existed [`SubstrateRecorder`].169 pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder<T>) -> Option<Self> {170 <CollectionById<T>>::get(id).map(|collection| Self {171 id,172 collection,173 recorder,174 })175 }176177 /// Retrives collection data from storage and creates collection handle with default parameters.178 /// If collection not found return `None`179 pub fn new(id: CollectionId) -> Option<Self> {180 Self::new_with_gas_limit(id, u64::MAX)181 }182183 /// Same as [`CollectionHandle::new`] but if collection not found [CollectionNotFound](Error::CollectionNotFound) returned.184 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {185 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)186 }187188 /// Consume gas for reading.189 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {190 self.recorder191 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_ref_time(192 <T as frame_system::Config>::DbWeight::get()193 .read194 .saturating_mul(reads),195 )))196 }197198 /// Consume gas for writing.199 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {200 self.recorder201 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_ref_time(202 <T as frame_system::Config>::DbWeight::get()203 .write204 .saturating_mul(writes),205 )))206 }207208 /// Consume gas for reading and writing.209 pub fn consume_store_reads_and_writes(210 &self,211 reads: u64,212 writes: u64,213 ) -> evm_coder::execution::Result<()> {214 let weight = <T as frame_system::Config>::DbWeight::get();215 let reads = weight.read.saturating_mul(reads);216 let writes = weight.read.saturating_mul(writes);217 self.recorder218 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_ref_time(219 reads.saturating_add(writes),220 )))221 }222223 /// Save collection to storage.224 pub fn save(&self) -> DispatchResult {225 <CollectionById<T>>::insert(self.id, &self.collection);226 Ok(())227 }228229 /// Set collection sponsor.230 ///231 /// Unique collections allows sponsoring for certain actions.232 /// This method allows you to set the sponsor of the collection.233 /// In order for sponsorship to become active, it must be confirmed through [`Self::confirm_sponsorship`].234 pub fn set_sponsor(235 &mut self,236 sender: &T::CrossAccountId,237 sponsor: T::AccountId,238 ) -> DispatchResult {239 self.check_is_internal()?;240 self.check_is_owner_or_admin(sender)?;241242 self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor.clone());243244 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(self.id, sponsor));245 <PalletEvm<T>>::deposit_log(246 erc::CollectionHelpersEvents::CollectionChanged {247 collection_id: eth::collection_id_to_address(self.id),248 }249 .to_log(T::ContractAddress::get()),250 );251252 self.save()253 }254255 /// Force set `sponsor`.256 ///257 /// Differs from [`set_sponsor`][`Self::set_sponsor`] in that confirmation258 /// from the `sponsor` is not required.259 ///260 /// # Arguments261 ///262 /// * `sender`: Caller's account.263 /// * `sponsor`: ID of the account of the sponsor-to-be.264 pub fn force_set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {265 self.check_is_internal()?;266267 self.collection.sponsorship = SponsorshipState::Confirmed(sponsor.clone());268269 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(self.id, sponsor.clone()));270 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(self.id, sponsor));271 <PalletEvm<T>>::deposit_log(272 erc::CollectionHelpersEvents::CollectionChanged {273 collection_id: eth::collection_id_to_address(self.id),274 }275 .to_log(T::ContractAddress::get()),276 );277278 self.save()279 }280281 /// Confirm sponsorship282 ///283 /// In order for the sponsorship to become active, the user set as the sponsor must confirm their participation.284 /// Before confirming sponsorship, the user must be specified as the sponsor of the collection via [`Self::set_sponsor`].285 pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> DispatchResult {286 self.check_is_internal()?;287 ensure!(288 self.collection.sponsorship.pending_sponsor() == Some(sender),289 Error::<T>::ConfirmSponsorshipFail290 );291292 self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());293294 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(self.id, sender.clone()));295 <PalletEvm<T>>::deposit_log(296 erc::CollectionHelpersEvents::CollectionChanged {297 collection_id: eth::collection_id_to_address(self.id),298 }299 .to_log(T::ContractAddress::get()),300 );301302 self.save()303 }304305 /// Remove collection sponsor.306 pub fn remove_sponsor(&mut self, sender: &T::CrossAccountId) -> DispatchResult {307 self.check_is_internal()?;308 self.check_is_owner_or_admin(sender)?;309310 self.collection.sponsorship = SponsorshipState::Disabled;311312 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(self.id));313 <PalletEvm<T>>::deposit_log(314 erc::CollectionHelpersEvents::CollectionChanged {315 collection_id: eth::collection_id_to_address(self.id),316 }317 .to_log(T::ContractAddress::get()),318 );319 self.save()320 }321322 /// Force remove `sponsor`.323 ///324 /// Differs from `remove_sponsor` in that325 /// it doesn't require consent from the `owner` of the collection.326 pub fn force_remove_sponsor(&mut self) -> DispatchResult {327 self.check_is_internal()?;328329 self.collection.sponsorship = SponsorshipState::Disabled;330331 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(self.id));332 <PalletEvm<T>>::deposit_log(333 erc::CollectionHelpersEvents::CollectionChanged {334 collection_id: eth::collection_id_to_address(self.id),335 }336 .to_log(T::ContractAddress::get()),337 );338 self.save()339 }340341 /// Checks that the collection was created with, and must be operated upon through **Unique API**.342 /// Now check only the `external` flag and if it's **true**, then return [`Error::CollectionIsExternal`] error.343 pub fn check_is_internal(&self) -> DispatchResult {344 if self.flags.external {345 return Err(<Error<T>>::CollectionIsExternal)?;346 }347348 Ok(())349 }350351 /// Checks that the collection was created with, and must be operated upon through an **assimilated API**.352 /// Now check only the `external` flag and if it's **false**, then return [`Error::CollectionIsInternal`] error.353 pub fn check_is_external(&self) -> DispatchResult {354 if !self.flags.external {355 return Err(<Error<T>>::CollectionIsInternal)?;356 }357358 Ok(())359 }360}361362impl<T: Config> Deref for CollectionHandle<T> {363 type Target = Collection<T::AccountId>;364365 fn deref(&self) -> &Self::Target {366 &self.collection367 }368}369370impl<T: Config> DerefMut for CollectionHandle<T> {371 fn deref_mut(&mut self) -> &mut Self::Target {372 &mut self.collection373 }374}375376impl<T: Config> CollectionHandle<T> {377 /// Checks if the `user` is the owner of the collection.378 pub fn check_is_owner(&self, user: &T::CrossAccountId) -> DispatchResult {379 ensure!(*user.as_sub() == self.owner, <Error<T>>::NoPermission);380 Ok(())381 }382383 /// Returns **true** if the `user` is the owner or administrator of the collection.384 pub fn is_owner_or_admin(&self, user: &T::CrossAccountId) -> bool {385 *user.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, user))386 }387388 /// Checks if the `user` is the owner or administrator of the collection.389 pub fn check_is_owner_or_admin(&self, user: &T::CrossAccountId) -> DispatchResult {390 ensure!(self.is_owner_or_admin(user), <Error<T>>::NoPermission);391 Ok(())392 }393394 /// Returns **true** if395 /// * the `user`is a collection owner or admin396 /// * the collection limits allow the owner/admins to transfer/burn any collection token397 pub fn ignores_token_restrictions(&self, user: &T::CrossAccountId) -> bool {398 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)399 }400401 /// Return **true** if `user` does not have enough token parts, and he can ignore such restrictions.402 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {403 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)404 }405406 /// Checks if the user is in the allow list. If not [Error::AddressNotInAllowlist] returns.407 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {408 ensure!(409 <Allowlist<T>>::get((self.id, user)),410 <Error<T>>::AddressNotInAllowlist411 );412 Ok(())413 }414415 /// Changes collection owner to another account416 /// #### Store read/writes417 /// 1 writes418 pub fn change_owner(419 &mut self,420 caller: T::CrossAccountId,421 new_owner: T::CrossAccountId,422 ) -> DispatchResult {423 self.check_is_internal()?;424 self.check_is_owner(&caller)?;425 self.collection.owner = new_owner.as_sub().clone();426427 <Pallet<T>>::deposit_event(Event::<T>::CollectionOwnerChanged(428 self.id,429 new_owner.as_sub().clone(),430 ));431 <PalletEvm<T>>::deposit_log(432 erc::CollectionHelpersEvents::CollectionChanged {433 collection_id: eth::collection_id_to_address(self.id),434 }435 .to_log(T::ContractAddress::get()),436 );437438 self.save()439 }440}441442#[frame_support::pallet]443pub mod pallet {444 use super::*;445 use dispatch::CollectionDispatch;446 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};447 use frame_system::pallet_prelude::*;448 use frame_support::traits::Currency;449 use up_data_structs::{TokenId, mapping::TokenAddressMapping};450 use scale_info::TypeInfo;451 use weights::WeightInfo;452453 #[pallet::config]454 pub trait Config:455 frame_system::Config + pallet_evm_coder_substrate::Config + pallet_evm::Config + TypeInfo456 {457 /// Weight information for functions of this pallet.458 type WeightInfo: WeightInfo;459460 /// Events compatible with [`frame_system::Config::Event`].461 type RuntimeEvent: IsType<<Self as frame_system::Config>::RuntimeEvent> + From<Event<Self>>;462463 /// Handler of accounts and payment.464 type Currency: Currency<Self::AccountId>;465466 /// Set price to create a collection.467 #[pallet::constant]468 type CollectionCreationPrice: Get<469 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,470 >;471472 /// Dispatcher of operations on collections.473 type CollectionDispatch: CollectionDispatch<Self>;474475 /// Account which holds the chain's treasury.476 type TreasuryAccountId: Get<Self::AccountId>;477478 /// Address under which the CollectionHelper contract would be available.479 #[pallet::constant]480 type ContractAddress: Get<H160>;481482 /// Mapper for token addresses to Ethereum addresses.483 type EvmTokenAddressMapping: TokenAddressMapping<H160>;484485 /// Mapper for token addresses to [`CrossAccountId`].486 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;487 }488489 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);490491 #[pallet::pallet]492 #[pallet::storage_version(STORAGE_VERSION)]493 #[pallet::generate_store(pub(super) trait Store)]494 pub struct Pallet<T>(_);495496 #[pallet::extra_constants]497 impl<T: Config> Pallet<T> {498 /// Maximum admins per collection.499 pub fn collection_admins_limit() -> u32 {500 COLLECTION_ADMINS_LIMIT501 }502 }503504 impl<T: Config> Pallet<T> {505 /// Helper function that handles deposit events506 pub fn deposit_event(event: Event<T>) {507 let event = <T as Config>::RuntimeEvent::from(event);508 let event = event.into();509 <frame_system::Pallet<T>>::deposit_event(event)510 }511 }512513 #[pallet::event]514 pub enum Event<T: Config> {515 /// New collection was created516 CollectionCreated(517 /// Globally unique identifier of newly created collection.518 CollectionId,519 /// [`CollectionMode`] converted into _u8_.520 u8,521 /// Collection owner.522 T::AccountId,523 ),524525 /// New collection was destroyed526 CollectionDestroyed(527 /// Globally unique identifier of collection.528 CollectionId,529 ),530531 /// New item was created.532 ItemCreated(533 /// Id of the collection where item was created.534 CollectionId,535 /// Id of an item. Unique within the collection.536 TokenId,537 /// Owner of newly created item538 T::CrossAccountId,539 /// Always 1 for NFT540 u128,541 ),542543 /// Collection item was burned.544 ItemDestroyed(545 /// Id of the collection where item was destroyed.546 CollectionId,547 /// Identifier of burned NFT.548 TokenId,549 /// Which user has destroyed its tokens.550 T::CrossAccountId,551 /// Amount of token pieces destroed. Always 1 for NFT.552 u128,553 ),554555 /// Item was transferred556 Transfer(557 /// Id of collection to which item is belong.558 CollectionId,559 /// Id of an item.560 TokenId,561 /// Original owner of item.562 T::CrossAccountId,563 /// New owner of item.564 T::CrossAccountId,565 /// Amount of token pieces transfered. Always 1 for NFT.566 u128,567 ),568569 /// Amount pieces of token owned by `sender` was approved for `spender`.570 Approved(571 /// Id of collection to which item is belong.572 CollectionId,573 /// Id of an item.574 TokenId,575 /// Original owner of item.576 T::CrossAccountId,577 /// Id for which the approval was granted.578 T::CrossAccountId,579 /// Amount of token pieces transfered. Always 1 for NFT.580 u128,581 ),582583 /// A `sender` approves operations on all owned tokens for `spender`.584 ApprovedForAll(585 /// Id of collection to which item is belong.586 CollectionId,587 /// Owner of a wallet.588 T::CrossAccountId,589 /// Id for which operator status was granted or rewoked.590 T::CrossAccountId,591 /// Is operator status granted or revoked?592 bool,593 ),594595 /// The colletion property has been added or edited.596 CollectionPropertySet(597 /// Id of collection to which property has been set.598 CollectionId,599 /// The property that was set.600 PropertyKey,601 ),602603 /// The property has been deleted.604 CollectionPropertyDeleted(605 /// Id of collection to which property has been deleted.606 CollectionId,607 /// The property that was deleted.608 PropertyKey,609 ),610611 /// The token property has been added or edited.612 TokenPropertySet(613 /// Identifier of the collection whose token has the property set.614 CollectionId,615 /// The token for which the property was set.616 TokenId,617 /// The property that was set.618 PropertyKey,619 ),620621 /// The token property has been deleted.622 TokenPropertyDeleted(623 /// Identifier of the collection whose token has the property deleted.624 CollectionId,625 /// The token for which the property was deleted.626 TokenId,627 /// The property that was deleted.628 PropertyKey,629 ),630631 /// The token property permission of a collection has been set.632 PropertyPermissionSet(633 /// ID of collection to which property permission has been set.634 CollectionId,635 /// The property permission that was set.636 PropertyKey,637 ),638639 /// Address was added to the allow list.640 AllowListAddressAdded(641 /// ID of the affected collection.642 CollectionId,643 /// Address of the added account.644 T::CrossAccountId,645 ),646647 /// Address was removed from the allow list.648 AllowListAddressRemoved(649 /// ID of the affected collection.650 CollectionId,651 /// Address of the removed account.652 T::CrossAccountId,653 ),654655 /// Collection admin was added.656 CollectionAdminAdded(657 /// ID of the affected collection.658 CollectionId,659 /// Admin address.660 T::CrossAccountId,661 ),662663 /// Collection admin was removed.664 CollectionAdminRemoved(665 /// ID of the affected collection.666 CollectionId,667 /// Removed admin address.668 T::CrossAccountId,669 ),670671 /// Collection limits were set.672 CollectionLimitSet(673 /// ID of the affected collection.674 CollectionId,675 ),676677 /// Collection owned was changed.678 CollectionOwnerChanged(679 /// ID of the affected collection.680 CollectionId,681 /// New owner address.682 T::AccountId,683 ),684685 /// Collection permissions were set.686 CollectionPermissionSet(687 /// ID of the affected collection.688 CollectionId,689 ),690691 /// Collection sponsor was set.692 CollectionSponsorSet(693 /// ID of the affected collection.694 CollectionId,695 /// New sponsor address.696 T::AccountId,697 ),698699 /// New sponsor was confirm.700 SponsorshipConfirmed(701 /// ID of the affected collection.702 CollectionId,703 /// New sponsor address.704 T::AccountId,705 ),706707 /// Collection sponsor was removed.708 CollectionSponsorRemoved(709 /// ID of the affected collection.710 CollectionId,711 ),712 }713714 #[pallet::error]715 pub enum Error<T> {716 /// This collection does not exist.717 CollectionNotFound,718 /// Sender parameter and item owner must be equal.719 MustBeTokenOwner,720 /// No permission to perform action721 NoPermission,722 /// Destroying only empty collections is allowed723 CantDestroyNotEmptyCollection,724 /// Collection is not in mint mode.725 PublicMintingNotAllowed,726 /// Address is not in allow list.727 AddressNotInAllowlist,728729 /// Collection name can not be longer than 63 char.730 CollectionNameLimitExceeded,731 /// Collection description can not be longer than 255 char.732 CollectionDescriptionLimitExceeded,733 /// Token prefix can not be longer than 15 char.734 CollectionTokenPrefixLimitExceeded,735 /// Total collections bound exceeded.736 TotalCollectionsLimitExceeded,737 /// Exceeded max admin count738 CollectionAdminCountExceeded,739 /// Collection limit bounds per collection exceeded740 CollectionLimitBoundsExceeded,741 /// Tried to enable permissions which are only permitted to be disabled742 OwnerPermissionsCantBeReverted,743 /// Collection settings not allowing items transferring744 TransferNotAllowed,745 /// Account token limit exceeded per collection746 AccountTokenLimitExceeded,747 /// Collection token limit exceeded748 CollectionTokenLimitExceeded,749 /// Metadata flag frozen750 MetadataFlagFrozen,751752 /// Item does not exist753 TokenNotFound,754 /// Item is balance not enough755 TokenValueTooLow,756 /// Requested value is more than the approved757 ApprovedValueTooLow,758 /// Tried to approve more than owned759 CantApproveMoreThanOwned,760 /// Only spending from eth mirror could be approved761 AddressIsNotEthMirror,762763 /// Can't transfer tokens to ethereum zero address764 AddressIsZero,765766 /// The operation is not supported767 UnsupportedOperation,768769 /// Insufficient funds to perform an action770 NotSufficientFounds,771772 /// User does not satisfy the nesting rule773 UserIsNotAllowedToNest,774 /// Only tokens from specific collections may nest tokens under this one775 SourceCollectionIsNotAllowedToNest,776777 /// Tried to store more data than allowed in collection field778 CollectionFieldSizeExceeded,779780 /// Tried to store more property data than allowed781 NoSpaceForProperty,782783 /// Tried to store more property keys than allowed784 PropertyLimitReached,785786 /// Property key is too long787 PropertyKeyIsTooLong,788789 /// Only ASCII letters, digits, and symbols `_`, `-`, and `.` are allowed790 InvalidCharacterInPropertyKey,791792 /// Empty property keys are forbidden793 EmptyPropertyKey,794795 /// Tried to access an external collection with an internal API796 CollectionIsExternal,797798 /// Tried to access an internal collection with an external API799 CollectionIsInternal,800801 /// This address is not set as sponsor, use setCollectionSponsor first.802 ConfirmSponsorshipFail,803804 /// The user is not an administrator.805 UserIsNotCollectionAdmin,806 }807808 /// Storage of the count of created collections. Essentially contains the last collection ID.809 #[pallet::storage]810 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;811812 /// Storage of the count of deleted collections.813 #[pallet::storage]814 pub type DestroyedCollectionCount<T> =815 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;816817 /// Storage of collection info.818 #[pallet::storage]819 pub type CollectionById<T> = StorageMap<820 Hasher = Blake2_128Concat,821 Key = CollectionId,822 Value = Collection<<T as frame_system::Config>::AccountId>,823 QueryKind = OptionQuery,824 >;825826 /// Storage of collection properties.827 #[pallet::storage]828 #[pallet::getter(fn collection_properties)]829 pub type CollectionProperties<T> = StorageMap<830 Hasher = Blake2_128Concat,831 Key = CollectionId,832 Value = Properties,833 QueryKind = ValueQuery,834 OnEmpty = up_data_structs::CollectionProperties,835 >;836837 /// Storage of token property permissions of a collection.838 #[pallet::storage]839 #[pallet::getter(fn property_permissions)]840 pub type CollectionPropertyPermissions<T> = StorageMap<841 Hasher = Blake2_128Concat,842 Key = CollectionId,843 Value = PropertiesPermissionMap,844 QueryKind = ValueQuery,845 >;846847 /// Storage of the amount of collection admins.848 #[pallet::storage]849 pub type AdminAmount<T> = StorageMap<850 Hasher = Blake2_128Concat,851 Key = CollectionId,852 Value = u32,853 QueryKind = ValueQuery,854 >;855856 /// List of collection admins.857 #[pallet::storage]858 pub type IsAdmin<T: Config> = StorageNMap<859 Key = (860 Key<Blake2_128Concat, CollectionId>,861 Key<Blake2_128Concat, T::CrossAccountId>,862 ),863 Value = bool,864 QueryKind = ValueQuery,865 >;866867 /// Allowlisted collection users.868 #[pallet::storage]869 pub type Allowlist<T: Config> = StorageNMap<870 Key = (871 Key<Blake2_128Concat, CollectionId>,872 Key<Blake2_128Concat, T::CrossAccountId>,873 ),874 Value = bool,875 QueryKind = ValueQuery,876 >;877878 /// Not used by code, exists only to provide some types to metadata.879 #[pallet::storage]880 pub type DummyStorageValue<T: Config> = StorageValue<881 Value = (882 CollectionStats,883 CollectionId,884 TokenId,885 TokenChild,886 PhantomType<(887 TokenData<T::CrossAccountId>,888 RpcCollection<T::AccountId>,889 // RMRK890 RmrkCollectionInfo<T::AccountId>,891 RmrkInstanceInfo<T::AccountId>,892 RmrkResourceInfo,893 RmrkPropertyInfo,894 RmrkBaseInfo<T::AccountId>,895 RmrkPartType,896 RmrkBoundedTheme,897 RmrkNftChild,898 // PoV Estimate Info899 PovInfo,900 )>,901 ),902 QueryKind = OptionQuery,903 >;904905 #[pallet::hooks]906 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {907 fn on_runtime_upgrade() -> Weight {908 StorageVersion::new(1).put::<Pallet<T>>();909910 Weight::zero()911 }912 }913}914915impl<T: Config> Pallet<T> {916 /// Enshure that receiver address is correct.917 ///918 /// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens.919 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {920 ensure!(921 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,922 <Error<T>>::AddressIsZero923 );924 Ok(())925 }926927 /// Get a vector of collection admins.928 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {929 <IsAdmin<T>>::iter_prefix((collection,))930 .map(|(a, _)| a)931 .collect()932 }933934 /// Get a vector of users allowed to mint tokens.935 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {936 <Allowlist<T>>::iter_prefix((collection,))937 .map(|(a, _)| a)938 .collect()939 }940941 /// Is `user` allowed to mint token in `collection`.942 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {943 <Allowlist<T>>::get((collection, user))944 }945946 /// Get statistics of collections.947 pub fn collection_stats() -> CollectionStats {948 let created = <CreatedCollectionCount<T>>::get();949 let destroyed = <DestroyedCollectionCount<T>>::get();950 CollectionStats {951 created: created.0,952 destroyed: destroyed.0,953 alive: created.0 - destroyed.0,954 }955 }956957 /// Get the effective limits for the collection.958 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {959 let collection = <CollectionById<T>>::get(collection)?;960 let limits = collection.limits;961 let effective_limits = CollectionLimits {962 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),963 sponsored_data_size: Some(limits.sponsored_data_size()),964 sponsored_data_rate_limit: Some(965 limits966 .sponsored_data_rate_limit967 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),968 ),969 token_limit: Some(limits.token_limit()),970 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(971 match collection.mode {972 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,973 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,974 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,975 },976 )),977 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),978 owner_can_transfer: Some(limits.owner_can_transfer()),979 owner_can_destroy: Some(limits.owner_can_destroy()),980 transfers_enabled: Some(limits.transfers_enabled()),981 };982983 Some(effective_limits)984 }985986 /// Returns information about the `collection` adapted for rpc.987 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {988 let Collection {989 name,990 description,991 owner,992 mode,993 token_prefix,994 sponsorship,995 limits,996 permissions,997 flags,998 } = <CollectionById<T>>::get(collection)?;9991000 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)1001 .into_iter()1002 .map(|(key, permission)| PropertyKeyPermission { key, permission })1003 .collect();10041005 let properties = <CollectionProperties<T>>::get(collection)1006 .into_iter()1007 .map(|(key, value)| Property { key, value })1008 .collect();10091010 let permissions = CollectionPermissions {1011 access: Some(permissions.access()),1012 mint_mode: Some(permissions.mint_mode()),1013 nesting: Some(permissions.nesting().clone()),1014 };10151016 Some(RpcCollection {1017 name: name.into_inner(),1018 description: description.into_inner(),1019 owner,1020 mode,1021 token_prefix: token_prefix.into_inner(),1022 sponsorship,1023 limits,1024 permissions,1025 token_property_permissions,1026 properties,1027 read_only: flags.external,10281029 flags: RpcCollectionFlags {1030 foreign: flags.foreign,1031 erc721metadata: flags.erc721metadata,1032 },1033 })1034 }1035}10361037macro_rules! limit_default {1038 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1039 $(1040 if let Some($new) = $new.$field {1041 let $old = $old.$field($($arg)?);1042 let _ = $new;1043 let _ = $old;1044 $check1045 } else {1046 $new.$field = $old.$field1047 }1048 )*1049 }};1050}1051macro_rules! limit_default_clone {1052 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1053 $(1054 if let Some($new) = $new.$field.clone() {1055 let $old = $old.$field($($arg)?);1056 let _ = $new;1057 let _ = $old;1058 $check1059 } else {1060 $new.$field = $old.$field.clone()1061 }1062 )*1063 }};1064}10651066impl<T: Config> Pallet<T> {1067 /// Create new collection.1068 ///1069 /// * `owner` - The owner of the collection.1070 /// * `data` - Description of the created collection.1071 /// * `flags` - Extra flags to store.1072 pub fn init_collection(1073 owner: T::CrossAccountId,1074 payer: T::CrossAccountId,1075 data: CreateCollectionData<T::AccountId>,1076 flags: CollectionFlags,1077 ) -> Result<CollectionId, DispatchError> {1078 {1079 ensure!(1080 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,1081 Error::<T>::CollectionTokenPrefixLimitExceeded1082 );1083 }10841085 let created_count = <CreatedCollectionCount<T>>::get()1086 .01087 .checked_add(1)1088 .ok_or(ArithmeticError::Overflow)?;1089 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;1090 let id = CollectionId(created_count);10911092 // bound Total number of collections1093 ensure!(1094 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,1095 <Error<T>>::TotalCollectionsLimitExceeded1096 );10971098 // =========10991100 let collection = Collection {1101 owner: owner.as_sub().clone(),1102 name: data.name,1103 mode: data.mode.clone(),1104 description: data.description,1105 token_prefix: data.token_prefix,1106 sponsorship: data1107 .pending_sponsor1108 .map(SponsorshipState::Unconfirmed)1109 .unwrap_or_default(),1110 limits: data1111 .limits1112 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))1113 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,1114 permissions: data1115 .permissions1116 .map(|permissions| {1117 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)1118 })1119 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,1120 flags,1121 };11221123 let mut collection_properties = up_data_structs::CollectionProperties::get();1124 collection_properties1125 .try_set_from_iter(data.properties.into_iter())1126 .map_err(<Error<T>>::from)?;11271128 CollectionProperties::<T>::insert(id, collection_properties);11291130 let mut token_props_permissions = PropertiesPermissionMap::new();1131 token_props_permissions1132 .try_set_from_iter(data.token_property_permissions.into_iter())1133 .map_err(<Error<T>>::from)?;11341135 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);11361137 // Take a (non-refundable) deposit of collection creation1138 {1139 let mut imbalance =1140 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();1141 imbalance.subsume(1142 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(1143 &T::TreasuryAccountId::get(),1144 T::CollectionCreationPrice::get(),1145 ),1146 );1147 <T as Config>::Currency::settle(1148 payer.as_sub(),1149 imbalance,1150 WithdrawReasons::TRANSFER,1151 ExistenceRequirement::KeepAlive,1152 )1153 .map_err(|_| Error::<T>::NotSufficientFounds)?;1154 }11551156 <CreatedCollectionCount<T>>::put(created_count);1157 <Pallet<T>>::deposit_event(Event::CollectionCreated(1158 id,1159 data.mode.id(),1160 owner.as_sub().clone(),1161 ));1162 <PalletEvm<T>>::deposit_log(1163 erc::CollectionHelpersEvents::CollectionCreated {1164 owner: *owner.as_eth(),1165 collection_id: eth::collection_id_to_address(id),1166 }1167 .to_log(T::ContractAddress::get()),1168 );1169 <CollectionById<T>>::insert(id, collection);1170 Ok(id)1171 }11721173 /// Destroy collection.1174 ///1175 /// * `collection` - Collection handler.1176 /// * `sender` - The owner or administrator of the collection.1177 pub fn destroy_collection(1178 collection: CollectionHandle<T>,1179 sender: &T::CrossAccountId,1180 ) -> DispatchResult {1181 ensure!(1182 collection.limits.owner_can_destroy(),1183 <Error<T>>::NoPermission,1184 );1185 collection.check_is_owner(sender)?;11861187 let destroyed_collections = <DestroyedCollectionCount<T>>::get()1188 .01189 .checked_add(1)1190 .ok_or(ArithmeticError::Overflow)?;11911192 // =========11931194 <DestroyedCollectionCount<T>>::put(destroyed_collections);1195 <CollectionById<T>>::remove(collection.id);1196 <AdminAmount<T>>::remove(collection.id);1197 let _ = <IsAdmin<T>>::clear_prefix((collection.id,), u32::MAX, None);1198 let _ = <Allowlist<T>>::clear_prefix((collection.id,), u32::MAX, None);1199 <CollectionProperties<T>>::remove(collection.id);12001201 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));12021203 <PalletEvm<T>>::deposit_log(1204 erc::CollectionHelpersEvents::CollectionDestroyed {1205 collection_id: eth::collection_id_to_address(collection.id),1206 }1207 .to_log(T::ContractAddress::get()),1208 );1209 Ok(())1210 }12111212 /// This function sets or removes a collection properties according to1213 /// `properties_updates` contents:1214 /// * sets a property under the <key> with the value provided `(<key>, Some(<value>))`1215 /// * removes a property under the <key> if the value is `None` `(<key>, None)`.1216 ///1217 /// This function fires an event for each property change.1218 /// In case of an error, all the changes (including the events) will be reverted1219 /// since the function is transactional.1220 #[transactional]1221 fn modify_collection_properties(1222 collection: &CollectionHandle<T>,1223 sender: &T::CrossAccountId,1224 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,1225 ) -> DispatchResult {1226 collection.check_is_owner_or_admin(sender)?;12271228 let mut stored_properties = <CollectionProperties<T>>::get(collection.id);12291230 for (key, value) in properties_updates {1231 match value {1232 Some(value) => {1233 stored_properties1234 .try_set(key.clone(), value)1235 .map_err(<Error<T>>::from)?;12361237 Self::deposit_event(Event::CollectionPropertySet(collection.id, key));1238 <PalletEvm<T>>::deposit_log(1239 erc::CollectionHelpersEvents::CollectionChanged {1240 collection_id: eth::collection_id_to_address(collection.id),1241 }1242 .to_log(T::ContractAddress::get()),1243 );1244 }1245 None => {1246 stored_properties.remove(&key).map_err(<Error<T>>::from)?;12471248 Self::deposit_event(Event::CollectionPropertyDeleted(collection.id, key));1249 <PalletEvm<T>>::deposit_log(1250 erc::CollectionHelpersEvents::CollectionChanged {1251 collection_id: eth::collection_id_to_address(collection.id),1252 }1253 .to_log(T::ContractAddress::get()),1254 );1255 }1256 }1257 }12581259 <CollectionProperties<T>>::set(collection.id, stored_properties);12601261 Ok(())1262 }12631264 /// Set collection property.1265 ///1266 /// * `collection` - Collection handler.1267 /// * `sender` - The owner or administrator of the collection.1268 /// * `property` - The property to set.1269 pub fn set_collection_property(1270 collection: &CollectionHandle<T>,1271 sender: &T::CrossAccountId,1272 property: Property,1273 ) -> DispatchResult {1274 Self::set_collection_properties(collection, sender, [property].into_iter())1275 }12761277 /// Set a scoped collection property, where the scope is a special prefix1278 /// prohibiting a user access to change the property directly.1279 ///1280 /// * `collection_id` - ID of the collection for which the property is being set.1281 /// * `scope` - Property scope.1282 /// * `property` - The property to set.1283 pub fn set_scoped_collection_property(1284 collection_id: CollectionId,1285 scope: PropertyScope,1286 property: Property,1287 ) -> DispatchResult {1288 CollectionProperties::<T>::try_mutate(collection_id, |properties| {1289 properties.try_scoped_set(scope, property.key, property.value)1290 })1291 .map_err(<Error<T>>::from)?;12921293 Ok(())1294 }12951296 /// Set scoped collection properties, where the scope is a special prefix1297 /// prohibiting a user access to change the properties directly.1298 ///1299 /// * `collection_id` - ID of the collection for which the properties is being set.1300 /// * `scope` - Property scope.1301 /// * `properties` - The properties to set.1302 pub fn set_scoped_collection_properties(1303 collection_id: CollectionId,1304 scope: PropertyScope,1305 properties: impl Iterator<Item = Property>,1306 ) -> DispatchResult {1307 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {1308 stored_properties.try_scoped_set_from_iter(scope, properties)1309 })1310 .map_err(<Error<T>>::from)?;13111312 Ok(())1313 }13141315 /// Set collection properties.1316 ///1317 /// * `collection` - Collection handler.1318 /// * `sender` - The owner or administrator of the collection.1319 /// * `properties` - The properties to set.1320 pub fn set_collection_properties(1321 collection: &CollectionHandle<T>,1322 sender: &T::CrossAccountId,1323 properties: impl Iterator<Item = Property>,1324 ) -> DispatchResult {1325 Self::modify_collection_properties(1326 collection,1327 sender,1328 properties.map(|property| (property.key, Some(property.value))),1329 )1330 }13311332 /// Delete collection property.1333 ///1334 /// * `collection` - Collection handler.1335 /// * `sender` - The owner or administrator of the collection.1336 /// * `property` - The property to delete.1337 pub fn delete_collection_property(1338 collection: &CollectionHandle<T>,1339 sender: &T::CrossAccountId,1340 property_key: PropertyKey,1341 ) -> DispatchResult {1342 Self::delete_collection_properties(collection, sender, [property_key].into_iter())1343 }13441345 /// Delete collection properties.1346 ///1347 /// * `collection` - Collection handler.1348 /// * `sender` - The owner or administrator of the collection.1349 /// * `properties` - The properties to delete.1350 pub fn delete_collection_properties(1351 collection: &CollectionHandle<T>,1352 sender: &T::CrossAccountId,1353 property_keys: impl Iterator<Item = PropertyKey>,1354 ) -> DispatchResult {1355 Self::modify_collection_properties(collection, sender, property_keys.map(|key| (key, None)))1356 }13571358 /// Set collection propetry permission without any checks.1359 ///1360 /// Used for migrations.1361 ///1362 /// * `collection` - Collection handler.1363 /// * `property_permissions` - Property permissions.1364 pub fn set_property_permission_unchecked(1365 collection: CollectionId,1366 property_permission: PropertyKeyPermission,1367 ) -> DispatchResult {1368 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1369 permissions.try_set(property_permission.key, property_permission.permission)1370 })1371 .map_err(<Error<T>>::from)?;1372 Ok(())1373 }13741375 /// Set collection property permission.1376 ///1377 /// * `collection` - Collection handler.1378 /// * `sender` - The owner or administrator of the collection.1379 /// * `property_permission` - Property permission.1380 pub fn set_property_permission(1381 collection: &CollectionHandle<T>,1382 sender: &T::CrossAccountId,1383 property_permission: PropertyKeyPermission,1384 ) -> DispatchResult {1385 Self::set_scoped_property_permission(1386 collection,1387 sender,1388 PropertyScope::None,1389 property_permission,1390 )1391 }13921393 /// Set collection property permission with scope.1394 ///1395 /// * `collection` - Collection handler.1396 /// * `sender` - The owner or administrator of the collection.1397 /// * `scope` - Property scope.1398 /// * `property_permission` - Property permission.1399 pub fn set_scoped_property_permission(1400 collection: &CollectionHandle<T>,1401 sender: &T::CrossAccountId,1402 scope: PropertyScope,1403 property_permission: PropertyKeyPermission,1404 ) -> DispatchResult {1405 collection.check_is_owner_or_admin(sender)?;14061407 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1408 let current_permission = all_permissions.get(&property_permission.key);1409 if matches![1410 current_permission,1411 Some(PropertyPermission { mutable: false, .. })1412 ] {1413 return Err(<Error<T>>::NoPermission.into());1414 }14151416 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1417 let property_permission = property_permission.clone();1418 permissions.try_scoped_set(1419 scope,1420 property_permission.key,1421 property_permission.permission,1422 )1423 })1424 .map_err(<Error<T>>::from)?;14251426 Self::deposit_event(Event::PropertyPermissionSet(1427 collection.id,1428 property_permission.key,1429 ));1430 <PalletEvm<T>>::deposit_log(1431 erc::CollectionHelpersEvents::CollectionChanged {1432 collection_id: eth::collection_id_to_address(collection.id),1433 }1434 .to_log(T::ContractAddress::get()),1435 );14361437 Ok(())1438 }14391440 /// Set token property permission.1441 ///1442 /// * `collection` - Collection handler.1443 /// * `sender` - The owner or administrator of the collection.1444 /// * `property_permissions` - Property permissions.1445 #[transactional]1446 pub fn set_token_property_permissions(1447 collection: &CollectionHandle<T>,1448 sender: &T::CrossAccountId,1449 property_permissions: Vec<PropertyKeyPermission>,1450 ) -> DispatchResult {1451 Self::set_scoped_token_property_permissions(1452 collection,1453 sender,1454 PropertyScope::None,1455 property_permissions,1456 )1457 }14581459 /// Set token property permission with scope.1460 ///1461 /// * `collection` - Collection handler.1462 /// * `sender` - The owner or administrator of the collection.1463 /// * `scope` - Property scope.1464 /// * `property_permissions` - Property permissions.1465 #[transactional]1466 pub fn set_scoped_token_property_permissions(1467 collection: &CollectionHandle<T>,1468 sender: &T::CrossAccountId,1469 scope: PropertyScope,1470 property_permissions: Vec<PropertyKeyPermission>,1471 ) -> DispatchResult {1472 for prop_pemission in property_permissions {1473 Self::set_scoped_property_permission(collection, sender, scope, prop_pemission)?;1474 }14751476 Ok(())1477 }14781479 /// Get collection property.1480 pub fn get_collection_property(1481 collection_id: CollectionId,1482 key: &PropertyKey,1483 ) -> Option<PropertyValue> {1484 Self::collection_properties(collection_id).get(key).cloned()1485 }14861487 /// Convert byte vector to property key vector.1488 pub fn bytes_keys_to_property_keys(1489 keys: Vec<Vec<u8>>,1490 ) -> Result<Vec<PropertyKey>, DispatchError> {1491 keys.into_iter()1492 .map(|key| -> Result<PropertyKey, DispatchError> {1493 key.try_into()1494 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1495 })1496 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1497 }14981499 /// Get properties according to given keys.1500 pub fn filter_collection_properties(1501 collection_id: CollectionId,1502 keys: Option<Vec<PropertyKey>>,1503 ) -> Result<Vec<Property>, DispatchError> {1504 let properties = Self::collection_properties(collection_id);15051506 let properties = keys1507 .map(|keys| {1508 keys.into_iter()1509 .filter_map(|key| {1510 properties.get(&key).map(|value| Property {1511 key,1512 value: value.clone(),1513 })1514 })1515 .collect()1516 })1517 .unwrap_or_else(|| {1518 properties1519 .into_iter()1520 .map(|(key, value)| Property { key, value })1521 .collect()1522 });15231524 Ok(properties)1525 }15261527 /// Get property permissions according to given keys.1528 pub fn filter_property_permissions(1529 collection_id: CollectionId,1530 keys: Option<Vec<PropertyKey>>,1531 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1532 let permissions = Self::property_permissions(collection_id);15331534 let key_permissions = keys1535 .map(|keys| {1536 keys.into_iter()1537 .filter_map(|key| {1538 permissions1539 .get(&key)1540 .map(|permission| PropertyKeyPermission {1541 key,1542 permission: permission.clone(),1543 })1544 })1545 .collect()1546 })1547 .unwrap_or_else(|| {1548 permissions1549 .into_iter()1550 .map(|(key, permission)| PropertyKeyPermission { key, permission })1551 .collect()1552 });15531554 Ok(key_permissions)1555 }15561557 /// Toggle `user` participation in the `collection`'s allow list.1558 /// #### Store read/writes1559 /// 1 writes1560 pub fn toggle_allowlist(1561 collection: &CollectionHandle<T>,1562 sender: &T::CrossAccountId,1563 user: &T::CrossAccountId,1564 allowed: bool,1565 ) -> DispatchResult {1566 collection.check_is_owner_or_admin(sender)?;15671568 // =========15691570 if allowed {1571 <Allowlist<T>>::insert((collection.id, user), true);1572 Self::deposit_event(Event::<T>::AllowListAddressAdded(1573 collection.id,1574 user.clone(),1575 ));1576 } else {1577 <Allowlist<T>>::remove((collection.id, user));1578 Self::deposit_event(Event::<T>::AllowListAddressRemoved(1579 collection.id,1580 user.clone(),1581 ));1582 }15831584 <PalletEvm<T>>::deposit_log(1585 erc::CollectionHelpersEvents::CollectionChanged {1586 collection_id: eth::collection_id_to_address(collection.id),1587 }1588 .to_log(T::ContractAddress::get()),1589 );15901591 Ok(())1592 }15931594 /// Toggle `user` participation in the `collection`'s admin list.1595 /// #### Store read/writes1596 /// 2 reads, 2 writes1597 pub fn toggle_admin(1598 collection: &CollectionHandle<T>,1599 sender: &T::CrossAccountId,1600 user: &T::CrossAccountId,1601 admin: bool,1602 ) -> DispatchResult {1603 collection.check_is_internal()?;1604 collection.check_is_owner(sender)?;16051606 let is_admin = <IsAdmin<T>>::get((collection.id, user));1607 if is_admin == admin {1608 if admin {1609 return Ok(());1610 } else {1611 return Err(Error::<T>::UserIsNotCollectionAdmin.into());1612 }1613 }1614 let amount = <AdminAmount<T>>::get(collection.id);16151616 // =========16171618 if admin {1619 let amount = amount1620 .checked_add(1)1621 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1622 ensure!(1623 amount <= Self::collection_admins_limit(),1624 <Error<T>>::CollectionAdminCountExceeded,1625 );16261627 <AdminAmount<T>>::insert(collection.id, amount);1628 <IsAdmin<T>>::insert((collection.id, user), true);16291630 Self::deposit_event(Event::<T>::CollectionAdminAdded(1631 collection.id,1632 user.clone(),1633 ));1634 } else {1635 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1636 <IsAdmin<T>>::remove((collection.id, user));16371638 Self::deposit_event(Event::<T>::CollectionAdminRemoved(1639 collection.id,1640 user.clone(),1641 ));1642 }16431644 <PalletEvm<T>>::deposit_log(1645 erc::CollectionHelpersEvents::CollectionChanged {1646 collection_id: eth::collection_id_to_address(collection.id),1647 }1648 .to_log(T::ContractAddress::get()),1649 );16501651 Ok(())1652 }16531654 /// Update collection limits.1655 pub fn update_limits(1656 user: &T::CrossAccountId,1657 collection: &mut CollectionHandle<T>,1658 new_limit: CollectionLimits,1659 ) -> DispatchResult {1660 collection.check_is_internal()?;1661 collection.check_is_owner_or_admin(user)?;16621663 collection.limits =1664 Self::clamp_limits(collection.mode.clone(), &collection.limits, new_limit)?;16651666 Self::deposit_event(Event::<T>::CollectionLimitSet(collection.id));1667 <PalletEvm<T>>::deposit_log(1668 erc::CollectionHelpersEvents::CollectionChanged {1669 collection_id: eth::collection_id_to_address(collection.id),1670 }1671 .to_log(T::ContractAddress::get()),1672 );16731674 collection.save()1675 }16761677 /// Merge set fields from `new_limit` to `old_limit`.1678 fn clamp_limits(1679 mode: CollectionMode,1680 old_limit: &CollectionLimits,1681 mut new_limit: CollectionLimits,1682 ) -> Result<CollectionLimits, DispatchError> {1683 let limits = old_limit;1684 limit_default!(old_limit, new_limit,1685 account_token_ownership_limit => ensure!(1686 new_limit <= MAX_TOKEN_OWNERSHIP,1687 <Error<T>>::CollectionLimitBoundsExceeded,1688 ),1689 sponsored_data_size => ensure!(1690 new_limit <= CUSTOM_DATA_LIMIT,1691 <Error<T>>::CollectionLimitBoundsExceeded,1692 ),16931694 sponsored_data_rate_limit => {},1695 token_limit => ensure!(1696 old_limit >= new_limit && new_limit > 0,1697 <Error<T>>::CollectionTokenLimitExceeded1698 ),16991700 sponsor_transfer_timeout(match mode {1701 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1702 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1703 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1704 }) => ensure!(1705 new_limit <= MAX_SPONSOR_TIMEOUT,1706 <Error<T>>::CollectionLimitBoundsExceeded,1707 ),1708 sponsor_approve_timeout => {},1709 owner_can_transfer => ensure!(1710 !limits.owner_can_transfer_instaled() ||1711 old_limit || !new_limit,1712 <Error<T>>::OwnerPermissionsCantBeReverted,1713 ),1714 owner_can_destroy => ensure!(1715 old_limit || !new_limit,1716 <Error<T>>::OwnerPermissionsCantBeReverted,1717 ),1718 transfers_enabled => {},1719 );1720 Ok(new_limit)1721 }17221723 /// Update collection permissions.1724 pub fn update_permissions(1725 user: &T::CrossAccountId,1726 collection: &mut CollectionHandle<T>,1727 new_permission: CollectionPermissions,1728 ) -> DispatchResult {1729 collection.check_is_internal()?;1730 collection.check_is_owner_or_admin(user)?;1731 collection.permissions = Self::clamp_permissions(1732 collection.mode.clone(),1733 &collection.permissions,1734 new_permission,1735 )?;17361737 Self::deposit_event(Event::<T>::CollectionPermissionSet(collection.id));1738 <PalletEvm<T>>::deposit_log(1739 erc::CollectionHelpersEvents::CollectionChanged {1740 collection_id: eth::collection_id_to_address(collection.id),1741 }1742 .to_log(T::ContractAddress::get()),1743 );17441745 collection.save()1746 }17471748 /// Merge set fields from `new_permission` to `old_permission`.1749 fn clamp_permissions(1750 _mode: CollectionMode,1751 old_permission: &CollectionPermissions,1752 mut new_permission: CollectionPermissions,1753 ) -> Result<CollectionPermissions, DispatchError> {1754 limit_default_clone!(old_permission, new_permission,1755 access => {},1756 mint_mode => {},1757 nesting => { /* todo check for permissive, if only it gets out of benchmarks */ },1758 );1759 Ok(new_permission)1760 }17611762 /// Repair possibly broken properties of a collection.1763 pub fn repair_collection(collection_id: CollectionId) -> DispatchResult {1764 CollectionProperties::<T>::mutate(collection_id, |properties| {1765 properties.recompute_consumed_space();1766 });17671768 Ok(())1769 }1770}17711772/// Indicates unsupported methods by returning [Error::UnsupportedOperation].1773#[macro_export]1774macro_rules! unsupported {1775 ($runtime:path) => {1776 Err($crate::Error::<$runtime>::UnsupportedOperation.into())1777 };1778}17791780/// Return weights for various worst-case operations.1781pub trait CommonWeightInfo<CrossAccountId> {1782 /// Weight of item creation.1783 fn create_item() -> Weight;17841785 /// Weight of items creation.1786 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;17871788 /// Weight of items creation.1789 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;17901791 /// The weight of the burning item.1792 fn burn_item() -> Weight;17931794 /// Property setting weight.1795 ///1796 /// * `amount`- The number of properties to set.1797 fn set_collection_properties(amount: u32) -> Weight;17981799 /// Collection property deletion weight.1800 ///1801 /// * `amount`- The number of properties to set.1802 fn delete_collection_properties(amount: u32) -> Weight;18031804 /// Token property setting weight.1805 ///1806 /// * `amount`- The number of properties to set.1807 fn set_token_properties(amount: u32) -> Weight;18081809 /// Token property deletion weight.1810 ///1811 /// * `amount`- The number of properties to delete.1812 fn delete_token_properties(amount: u32) -> Weight;18131814 /// Token property permissions set weight.1815 ///1816 /// * `amount`- The number of property permissions to set.1817 fn set_token_property_permissions(amount: u32) -> Weight;18181819 /// Transfer price of the token or its parts.1820 fn transfer() -> Weight;18211822 /// The price of setting the permission of the operation from another user.1823 fn approve() -> Weight;18241825 /// The price of setting the permission of the operation from another user for eth mirror.1826 fn approve_from() -> Weight;18271828 /// Transfer price from another user.1829 fn transfer_from() -> Weight;18301831 /// The price of burning a token from another user.1832 fn burn_from() -> Weight;18331834 /// Differs from burn_item in case of Fungible and Refungible, as it should burn1835 /// whole users's balance.1836 ///1837 /// This method shouldn't be used directly, as it doesn't count breadth price, use [burn_recursively](CommonWeightInfo::burn_recursively) instead1838 fn burn_recursively_self_raw() -> Weight;18391840 /// Cost of iterating over `amount` children while burning, without counting child burning itself.1841 ///1842 /// This method shouldn't be used directly, as it doesn't count depth price, use [burn_recursively](CommonWeightInfo::burn_recursively) instead1843 fn burn_recursively_breadth_raw(amount: u32) -> Weight;18441845 /// The price of recursive burning a token.1846 ///1847 /// `max_selfs` - The maximum burning weight of the token itself.1848 /// `max_breadth` - The maximum number of nested tokens to burn.1849 fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1850 Self::burn_recursively_self_raw()1851 .saturating_mul(max_selfs.max(1) as u64)1852 .saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1853 }18541855 /// The price of retrieving token owner1856 fn token_owner() -> Weight;18571858 /// The price of setting approval for all1859 fn set_allowance_for_all() -> Weight;18601861 /// The price of repairing an item.1862 fn force_repair_item() -> Weight;1863}18641865/// Weight info extension trait for refungible pallet.1866pub trait RefungibleExtensionsWeightInfo {1867 /// Weight of token repartition.1868 fn repartition() -> Weight;1869}18701871/// Common collection operations.1872///1873/// It wraps methods in Fungible, Nonfungible and Refungible pallets1874/// and adds weight info.1875pub trait CommonCollectionOperations<T: Config> {1876 /// Create token.1877 ///1878 /// * `sender` - The user who mint the token and pays for the transaction.1879 /// * `to` - The user who will own the token.1880 /// * `data` - Token data.1881 /// * `nesting_budget` - A budget that can be spent on nesting tokens.1882 fn create_item(1883 &self,1884 sender: T::CrossAccountId,1885 to: T::CrossAccountId,1886 data: CreateItemData,1887 nesting_budget: &dyn Budget,1888 ) -> DispatchResultWithPostInfo;18891890 /// Create multiple tokens.1891 ///1892 /// * `sender` - The user who mint the token and pays for the transaction.1893 /// * `to` - The user who will own the token.1894 /// * `data` - Token data.1895 /// * `nesting_budget` - A budget that can be spent on nesting tokens.1896 fn create_multiple_items(1897 &self,1898 sender: T::CrossAccountId,1899 to: T::CrossAccountId,1900 data: Vec<CreateItemData>,1901 nesting_budget: &dyn Budget,1902 ) -> DispatchResultWithPostInfo;19031904 /// Create multiple tokens.1905 ///1906 /// * `sender` - The user who mint the token and pays for the transaction.1907 /// * `to` - The user who will own the token.1908 /// * `data` - Token data.1909 /// * `nesting_budget` - A budget that can be spent on nesting tokens.1910 fn create_multiple_items_ex(1911 &self,1912 sender: T::CrossAccountId,1913 data: CreateItemExData<T::CrossAccountId>,1914 nesting_budget: &dyn Budget,1915 ) -> DispatchResultWithPostInfo;19161917 /// Burn token.1918 ///1919 /// * `sender` - The user who owns the token.1920 /// * `token` - Token id that will burned.1921 /// * `amount` - The number of parts of the token that will be burned.1922 fn burn_item(1923 &self,1924 sender: T::CrossAccountId,1925 token: TokenId,1926 amount: u128,1927 ) -> DispatchResultWithPostInfo;19281929 /// Burn token and all nested tokens recursievly.1930 ///1931 /// * `sender` - The user who owns the token.1932 /// * `token` - Token id that will burned.1933 /// * `self_budget` - The budget that can be spent on burning tokens.1934 /// * `breadth_budget` - The budget that can be spent on burning nested tokens.1935 fn burn_item_recursively(1936 &self,1937 sender: T::CrossAccountId,1938 token: TokenId,1939 self_budget: &dyn Budget,1940 breadth_budget: &dyn Budget,1941 ) -> DispatchResultWithPostInfo;19421943 /// Set collection properties.1944 ///1945 /// * `sender` - Must be either the owner of the collection or its admin.1946 /// * `properties` - Properties to be set.1947 fn set_collection_properties(1948 &self,1949 sender: T::CrossAccountId,1950 properties: Vec<Property>,1951 ) -> DispatchResultWithPostInfo;19521953 /// Delete collection properties.1954 ///1955 /// * `sender` - Must be either the owner of the collection or its admin.1956 /// * `properties` - The properties to be removed.1957 fn delete_collection_properties(1958 &self,1959 sender: &T::CrossAccountId,1960 property_keys: Vec<PropertyKey>,1961 ) -> DispatchResultWithPostInfo;19621963 /// Set token properties.1964 ///1965 /// The appropriate [`PropertyPermission`] for the token property1966 /// must be set with [`Self::set_token_property_permissions`].1967 ///1968 /// * `sender` - Must be either the owner of the token or its admin.1969 /// * `token_id` - The token for which the properties are being set.1970 /// * `properties` - Properties to be set.1971 /// * `budget` - Budget for setting properties.1972 fn set_token_properties(1973 &self,1974 sender: T::CrossAccountId,1975 token_id: TokenId,1976 properties: Vec<Property>,1977 budget: &dyn Budget,1978 ) -> DispatchResultWithPostInfo;19791980 /// Remove token properties.1981 ///1982 /// The appropriate [`PropertyPermission`] for the token property1983 /// must be set with [`Self::set_token_property_permissions`].1984 ///1985 /// * `sender` - Must be either the owner of the token or its admin.1986 /// * `token_id` - The token for which the properties are being remove.1987 /// * `property_keys` - Keys to remove corresponding properties.1988 /// * `budget` - Budget for removing properties.1989 fn delete_token_properties(1990 &self,1991 sender: T::CrossAccountId,1992 token_id: TokenId,1993 property_keys: Vec<PropertyKey>,1994 budget: &dyn Budget,1995 ) -> DispatchResultWithPostInfo;19961997 /// Set token property permissions.1998 ///1999 /// * `sender` - Must be either the owner of the token or its admin.2000 /// * `token_id` - The token for which the properties are being set.2001 /// * `property_permissions` - Property permissions to be set.2002 /// * `budget` - Budget for setting properties.2003 fn set_token_property_permissions(2004 &self,2005 sender: &T::CrossAccountId,2006 property_permissions: Vec<PropertyKeyPermission>,2007 ) -> DispatchResultWithPostInfo;20082009 /// Transfer amount of token pieces.2010 ///2011 /// * `sender` - Donor user.2012 /// * `to` - Recepient user.2013 /// * `token` - The token of which parts are being sent.2014 /// * `amount` - The number of parts of the token that will be transferred.2015 /// * `budget` - The maximum budget that can be spent on the transfer.2016 fn transfer(2017 &self,2018 sender: T::CrossAccountId,2019 to: T::CrossAccountId,2020 token: TokenId,2021 amount: u128,2022 budget: &dyn Budget,2023 ) -> DispatchResultWithPostInfo;20242025 /// Grant access to another account to transfer parts of the token owned by the calling user via [Self::transfer_from].2026 ///2027 /// * `sender` - The user who grants access to the token.2028 /// * `spender` - The user to whom the rights are granted.2029 /// * `token` - The token to which access is granted.2030 /// * `amount` - The amount of pieces that another user can dispose of.2031 fn approve(2032 &self,2033 sender: T::CrossAccountId,2034 spender: T::CrossAccountId,2035 token: TokenId,2036 amount: u128,2037 ) -> DispatchResultWithPostInfo;20382039 /// Grant access to another account to transfer parts of the token owned by the calling user's eth mirror via [Self::transfer_from].2040 ///2041 /// * `sender` - The user who grants access to the token.2042 /// * `from` - Spender's eth mirror.2043 /// * `to` - The user to whom the rights are granted.2044 /// * `token` - The token to which access is granted.2045 /// * `amount` - The amount of pieces that another user can dispose of.2046 fn approve_from(2047 &self,2048 sender: T::CrossAccountId,2049 from: T::CrossAccountId,2050 to: T::CrossAccountId,2051 token: TokenId,2052 amount: u128,2053 ) -> DispatchResultWithPostInfo;20542055 /// Send parts of a token owned by another user.2056 ///2057 /// Before calling this method, you must grant rights to the calling user via [`Self::approve`].2058 ///2059 /// * `sender` - The user who must have access to the token (see [`Self::approve`]).2060 /// * `from` - The user who owns the token.2061 /// * `to` - Recepient user.2062 /// * `token` - The token of which parts are being sent.2063 /// * `amount` - The number of parts of the token that will be transferred.2064 /// * `budget` - The maximum budget that can be spent on the transfer.2065 fn transfer_from(2066 &self,2067 sender: T::CrossAccountId,2068 from: T::CrossAccountId,2069 to: T::CrossAccountId,2070 token: TokenId,2071 amount: u128,2072 budget: &dyn Budget,2073 ) -> DispatchResultWithPostInfo;20742075 /// Burn parts of a token owned by another user.2076 ///2077 /// Before calling this method, you must grant rights to the calling user via [`Self::approve`].2078 ///2079 /// * `sender` - The user who must have access to the token (see [`Self::approve`]).2080 /// * `from` - The user who owns the token.2081 /// * `token` - The token of which parts are being sent.2082 /// * `amount` - The number of parts of the token that will be transferred.2083 /// * `budget` - The maximum budget that can be spent on the burn.2084 fn burn_from(2085 &self,2086 sender: T::CrossAccountId,2087 from: T::CrossAccountId,2088 token: TokenId,2089 amount: u128,2090 budget: &dyn Budget,2091 ) -> DispatchResultWithPostInfo;20922093 /// Check permission to nest token.2094 ///2095 /// * `sender` - The user who initiated the check.2096 /// * `from` - The token that is checked for embedding.2097 /// * `under` - Token under which to check.2098 /// * `budget` - The maximum budget that can be spent on the check.2099 fn check_nesting(2100 &self,2101 sender: T::CrossAccountId,2102 from: (CollectionId, TokenId),2103 under: TokenId,2104 budget: &dyn Budget,2105 ) -> DispatchResult;21062107 /// Nest one token into another.2108 ///2109 /// * `under` - Token holder.2110 /// * `to_nest` - Nested token.2111 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));21122113 /// Unnest token.2114 ///2115 /// * `under` - Token holder.2116 /// * `to_nest` - Token to unnest.2117 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));21182119 /// Get all user tokens.2120 ///2121 /// * `account` - Account for which you need to get tokens.2122 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;21232124 /// Get all the tokens in the collection.2125 fn collection_tokens(&self) -> Vec<TokenId>;21262127 /// Check if the token exists.2128 ///2129 /// * `token` - Id token to check.2130 fn token_exists(&self, token: TokenId) -> bool;21312132 /// Get the id of the last minted token.2133 fn last_token_id(&self) -> TokenId;21342135 /// Get the owner of the token.2136 ///2137 /// * `token` - The token for which you need to find out the owner.2138 fn token_owner(&self, token: TokenId) -> Result<T::CrossAccountId, TokenOwnerError>;21392140 /// Returns 10 tokens owners in no particular order.2141 ///2142 /// * `token` - The token for which you need to find out the owners.2143 fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;21442145 /// Get the value of the token property by key.2146 ///2147 /// * `token` - Token with the property to get.2148 /// * `key` - Property name.2149 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;21502151 /// Get a set of token properties by key vector.2152 ///2153 /// * `token` - Token with the property to get.2154 /// * `keys` - Vector of property keys. If this parameter is [None](sp_std::result::Result),2155 /// then all properties are returned.2156 fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;21572158 /// Amount of unique collection tokens2159 fn total_supply(&self) -> u32;21602161 /// Amount of different tokens account has.2162 ///2163 /// * `account` - The account for which need to get the balance.2164 fn account_balance(&self, account: T::CrossAccountId) -> u32;21652166 /// Amount of specific token account have.2167 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;21682169 /// Amount of token pieces2170 fn total_pieces(&self, token: TokenId) -> Option<u128>;21712172 /// Get the number of parts of the token that a trusted user can manage.2173 ///2174 /// * `sender` - Trusted user.2175 /// * `spender` - Owner of the token.2176 /// * `token` - The token for which to get the value.2177 fn allowance(2178 &self,2179 sender: T::CrossAccountId,2180 spender: T::CrossAccountId,2181 token: TokenId,2182 ) -> u128;21832184 /// Get extension for RFT collection.2185 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;21862187 /// The `operator` is allowed to transfer all tokens of the `owner` on their behalf.2188 /// * `owner` - Token owner2189 /// * `operator` - Operator2190 /// * `approve` - Should operator status be granted or revoked?2191 fn set_allowance_for_all(2192 &self,2193 owner: T::CrossAccountId,2194 operator: T::CrossAccountId,2195 approve: bool,2196 ) -> DispatchResultWithPostInfo;21972198 /// Tells whether the given `owner` approves the `operator`.2199 fn allowance_for_all(&self, owner: T::CrossAccountId, operator: T::CrossAccountId) -> bool;22002201 /// Repairs a possibly broken item.2202 fn repair_item(&self, token: TokenId) -> DispatchResultWithPostInfo;2203}22042205/// Extension for RFT collection.2206pub trait RefungibleExtensions<T>2207where2208 T: Config,2209{2210 /// Change the number of parts of the token.2211 ///2212 /// When the value changes down, this function is equivalent to burning parts of the token.2213 ///2214 /// * `sender` - The user calling the repartition operation. Must be the owner of the token.2215 /// * `token` - The token for which you want to change the number of parts.2216 /// * `amount` - The new value of the parts of the token.2217 fn repartition(2218 &self,2219 sender: &T::CrossAccountId,2220 token: TokenId,2221 amount: u128,2222 ) -> DispatchResultWithPostInfo;2223}22242225/// Merge [`DispatchResult`] with [`Weight`] into [`DispatchResultWithPostInfo`].2226///2227/// Used for [`CommonCollectionOperations`] implementations and flexible enough to do so.2228pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {2229 let post_info = PostDispatchInfo {2230 actual_weight: Some(weight),2231 pays_fee: Pays::Yes,2232 };2233 match res {2234 Ok(()) => Ok(post_info),2235 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),2236 }2237}22382239impl<T: Config> From<PropertiesError> for Error<T> {2240 fn from(error: PropertiesError) -> Self {2241 match error {2242 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,2243 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,2244 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,2245 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,2246 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,2247 }2248 }2249}pallets/fungible/src/common.rsdiffbeforeafterboth--- a/pallets/fungible/src/common.rs
+++ b/pallets/fungible/src/common.rs
@@ -17,7 +17,9 @@
use core::marker::PhantomData;
use frame_support::{dispatch::DispatchResultWithPostInfo, ensure, fail, weights::Weight, traits::Get};
-use up_data_structs::{TokenId, CollectionId, CreateItemExData, budget::Budget, CreateItemData};
+use up_data_structs::{
+ TokenId, CollectionId, CreateItemExData, budget::Budget, CreateItemData, TokenOwnerError,
+};
use pallet_common::{
CommonCollectionOperations, CommonWeightInfo, RefungibleExtensions, with_weight,
weights::WeightInfo as _,
@@ -404,8 +406,8 @@
TokenId::default()
}
- fn token_owner(&self, _token: TokenId) -> Option<T::CrossAccountId> {
- None
+ fn token_owner(&self, _token: TokenId) -> Result<T::CrossAccountId, TokenOwnerError> {
+ Err(TokenOwnerError::MultipleOwners)
}
/// Returns 10 tokens owners in no particular order.
pallets/nonfungible/src/common.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/common.rs
+++ b/pallets/nonfungible/src/common.rs
@@ -19,7 +19,7 @@
use frame_support::{dispatch::DispatchResultWithPostInfo, ensure, fail, weights::Weight};
use up_data_structs::{
TokenId, CreateItemExData, CollectionId, budget::Budget, Property, PropertyKey,
- PropertyKeyPermission, PropertyValue,
+ PropertyKeyPermission, PropertyValue, TokenOwnerError,
};
use pallet_common::{
CommonCollectionOperations, CommonWeightInfo, RefungibleExtensions, with_weight,
@@ -460,13 +460,15 @@
TokenId(<TokensMinted<T>>::get(self.id))
}
- fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId> {
- <TokenData<T>>::get((self.id, token)).map(|t| t.owner)
+ fn token_owner(&self, token: TokenId) -> Result<T::CrossAccountId, TokenOwnerError> {
+ <TokenData<T>>::get((self.id, token))
+ .map(|t| t.owner)
+ .ok_or(TokenOwnerError::NotFound)
}
/// Returns token owners.
fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId> {
- self.token_owner(token).map_or_else(|| vec![], |t| vec![t])
+ self.token_owner(token).map_or_else(|_| vec![], |t| vec![t])
}
fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue> {
pallets/nonfungible/src/erc.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/erc.rs
+++ b/pallets/nonfungible/src/erc.rs
@@ -728,7 +728,7 @@
fn cross_owner_of(&self, token_id: U256) -> Result<eth::CrossAddress> {
Self::token_owner(&self, token_id.try_into()?)
.map(|o| eth::CrossAddress::from_sub_cross_account::<T>(&o))
- .ok_or(Error::Revert("key too large".into()))
+ .map_err(|_| Error::Revert("token not found".into()))
}
/// Returns the token properties.
pallets/proxy-rmrk-core/src/lib.rsdiffbeforeafterboth--- a/pallets/proxy-rmrk-core/src/lib.rs
+++ b/pallets/proxy-rmrk-core/src/lib.rs
@@ -741,7 +741,8 @@
Some((collection_id, nft_id)),
&target_nft_budget,
)
- .map_err(Self::map_unique_err_to_proxy)?;
+ .map_err(Self::map_unique_err_to_proxy)?
+ .ok_or::<DispatchError>(<Error<T>>::NoPermission.into())?;
approval_required = cross_sender != target_nft_owner;
@@ -989,7 +990,8 @@
let nft_owner =
<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)
- .map_err(|_| <Error<T>>::ResourceDoesntExist)?;
+ .map_err(|_| <Error<T>>::ResourceDoesntExist)?
+ .ok_or::<DispatchError>(<Error<T>>::NoPermission.into())?;
Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {
ensure!(res.pending, <Error<T>>::ResourceNotPending);
@@ -1044,7 +1046,8 @@
let nft_owner =
<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)
- .map_err(|_| <Error<T>>::ResourceDoesntExist)?;
+ .map_err(|_| <Error<T>>::ResourceDoesntExist)?
+ .ok_or::<DispatchError>(<Error<T>>::NoPermission.into())?;
ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);
@@ -1666,7 +1669,8 @@
let budget = budget::Value::new(NESTING_BUDGET);
let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)
- .map_err(Self::map_unique_err_to_proxy)?;
+ .map_err(Self::map_unique_err_to_proxy)?
+ .ok_or::<DispatchError>(<Error<T>>::NoPermission.into())?;
let pending = sender != nft_owner;
@@ -1720,7 +1724,8 @@
let budget = up_data_structs::budget::Value::new(NESTING_BUDGET);
let topmost_owner =
- <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;
+ <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?
+ .ok_or::<DispatchError>(<Error<T>>::NoPermission.into())?;
let sender = T::CrossAccountId::from_sub(sender);
if topmost_owner == sender {
pallets/proxy-rmrk-core/src/rpc.rsdiffbeforeafterboth--- a/pallets/proxy-rmrk-core/src/rpc.rs
+++ b/pallets/proxy-rmrk-core/src/rpc.rs
@@ -68,7 +68,7 @@
}
let owner = match collection.token_owner(nft_id) {
- Some(owner) => match T::CrossTokenAddressMapping::address_to_token(&owner) {
+ Ok(owner) => match T::CrossTokenAddressMapping::address_to_token(&owner) {
Some((col, tok)) => {
let rmrk_collection = <Pallet<T>>::rmrk_collection_id(col)?;
@@ -76,7 +76,7 @@
}
None => RmrkAccountIdOrCollectionNftTuple::AccountId(owner.as_sub().clone()),
},
- None => return Ok(None),
+ _ => return Ok(None),
};
Ok(Some(RmrkInstanceInfo {
pallets/refungible/src/common.rsdiffbeforeafterboth--- a/pallets/refungible/src/common.rs
+++ b/pallets/refungible/src/common.rs
@@ -21,7 +21,7 @@
use up_data_structs::{
CollectionId, TokenId, CreateItemExData, budget::Budget, Property, PropertyKey, PropertyValue,
PropertyKeyPermission, CollectionPropertiesVec, CreateRefungibleExMultipleOwners,
- CreateRefungibleExSingleOwner,
+ CreateRefungibleExSingleOwner, TokenOwnerError,
};
use pallet_common::{
CommonCollectionOperations, CommonWeightInfo, RefungibleExtensions, with_weight,
@@ -478,7 +478,7 @@
TokenId(<TokensMinted<T>>::get(self.id))
}
- fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId> {
+ fn token_owner(&self, token: TokenId) -> Result<T::CrossAccountId, TokenOwnerError> {
<Pallet<T>>::token_owner(self.id, token)
}
pallets/refungible/src/erc.rsdiffbeforeafterboth--- a/pallets/refungible/src/erc.rs
+++ b/pallets/refungible/src/erc.rs
@@ -43,7 +43,7 @@
use sp_std::{collections::btree_map::BTreeMap, vec::Vec, vec};
use up_data_structs::{
CollectionId, CollectionPropertiesVec, mapping::TokenAddressMapping, Property, PropertyKey,
- PropertyKeyPermission, PropertyPermission, TokenId,
+ PropertyKeyPermission, PropertyPermission, TokenId, TokenOwnerError,
};
use crate::{
@@ -411,9 +411,12 @@
self.consume_store_reads(2)?;
let token = token_id.try_into()?;
let owner = <Pallet<T>>::token_owner(self.id, token);
- Ok(owner
+ owner
.map(|address| *address.as_eth())
- .unwrap_or_else(|| ADDRESS_FOR_PARTIALLY_OWNED_TOKENS))
+ .or_else(|err| match err {
+ TokenOwnerError::NotFound => Err(Error::Revert("token not found".into())),
+ TokenOwnerError::MultipleOwners => Ok(ADDRESS_FOR_PARTIALLY_OWNED_TOKENS),
+ })
}
/// @dev Not implemented
@@ -766,7 +769,12 @@
fn cross_owner_of(&self, token_id: U256) -> Result<eth::CrossAddress> {
Self::token_owner(&self, token_id.try_into()?)
.map(|o| eth::CrossAddress::from_sub_cross_account::<T>(&o))
- .ok_or(Error::Revert("key too large".into()))
+ .or_else(|err| match err {
+ TokenOwnerError::NotFound => Err(Error::Revert("token not found".into())),
+ TokenOwnerError::MultipleOwners => Ok(eth::CrossAddress::from_eth(
+ ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,
+ )),
+ })
}
/// Returns the token properties.
pallets/refungible/src/lib.rsdiffbeforeafterboth--- a/pallets/refungible/src/lib.rs
+++ b/pallets/refungible/src/lib.rs
@@ -107,7 +107,7 @@
AccessMode, budget::Budget, CollectionId, CollectionFlags, CreateCollectionData,
mapping::TokenAddressMapping, MAX_REFUNGIBLE_PIECES, Property, PropertyKey,
PropertyKeyPermission, PropertyPermission, PropertyScope, PropertyValue, TokenId,
- TrySetProperty, PropertiesPermissionMap, CreateRefungibleExMultipleOwners,
+ TrySetProperty, PropertiesPermissionMap, CreateRefungibleExMultipleOwners, TokenOwnerError,
};
pub use pallet::*;
@@ -480,7 +480,7 @@
<Balance<T>>::remove((collection.id, token, owner));
<AccountBalance<T>>::insert((collection.id, owner), account_balance);
- if let Some(user) = Self::token_owner(collection.id, token) {
+ if let Ok(user) = Self::token_owner(collection.id, token) {
<PalletEvm<T>>::deposit_log(
ERC721Events::Transfer {
from: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,
@@ -1365,17 +1365,20 @@
Ok(())
}
- fn token_owner(collection_id: CollectionId, token_id: TokenId) -> Option<T::CrossAccountId> {
+ fn token_owner(
+ collection_id: CollectionId,
+ token_id: TokenId,
+ ) -> Result<T::CrossAccountId, TokenOwnerError> {
let mut owner = None;
let mut count = 0;
for key in Balance::<T>::iter_key_prefix((collection_id, token_id)) {
count += 1;
if count > 1 {
- return None;
+ return Err(TokenOwnerError::MultipleOwners);
}
owner = Some(key);
}
- owner
+ owner.ok_or(TokenOwnerError::NotFound)
}
fn total_pieces(collection_id: CollectionId, token_id: TokenId) -> Option<u128> {
pallets/structure/src/lib.rsdiffbeforeafterboth--- a/pallets/structure/src/lib.rs
+++ b/pallets/structure/src/lib.rs
@@ -61,7 +61,9 @@
use frame_support::fail;
pub use pallet::*;
use pallet_common::{dispatch::CollectionDispatch, CollectionHandle};
-use up_data_structs::{CollectionId, TokenId, mapping::TokenAddressMapping, budget::Budget};
+use up_data_structs::{
+ CollectionId, TokenId, mapping::TokenAddressMapping, budget::Budget, TokenOwnerError,
+};
#[cfg(feature = "runtime-benchmarks")]
pub mod benchmarking;
@@ -135,6 +137,8 @@
User(CrossAccountId),
/// Could not find the token provided as the owner.
TokenNotFound,
+ /// Nested token has multiple owners.
+ MultipleOwners,
/// Token owner is another token (still, the target token may not exist).
Token(CollectionId, TokenId),
}
@@ -159,11 +163,12 @@
let handle = handle.as_dyn();
Ok(match handle.token_owner(token) {
- Some(owner) => match T::CrossTokenAddressMapping::address_to_token(&owner) {
+ Ok(owner) => match T::CrossTokenAddressMapping::address_to_token(&owner) {
Some((collection, token)) => Parent::Token(collection, token),
None => Parent::User(owner),
},
- None => Parent::TokenNotFound,
+ Err(TokenOwnerError::MultipleOwners) => Parent::MultipleOwners,
+ Err(TokenOwnerError::NotFound) => Parent::TokenNotFound,
})
}
@@ -203,19 +208,27 @@
///
/// May return token address if parent token not yet exists
///
+ /// Returns `None` if the token has multiple owners.
+ ///
/// - `budget`: Limit for searching parents in depth.
pub fn find_topmost_owner(
collection: CollectionId,
token: TokenId,
budget: &dyn Budget,
- ) -> Result<T::CrossAccountId, DispatchError> {
+ ) -> Result<Option<T::CrossAccountId>, DispatchError> {
let owner = Self::parent_chain(collection, token)
.take_while(|_| budget.consume())
- .find(|p| matches!(p, Ok(Parent::User(_) | Parent::TokenNotFound)))
+ .find(|p| {
+ matches!(
+ p,
+ Ok(Parent::User(_) | Parent::TokenNotFound | Parent::MultipleOwners)
+ )
+ })
.ok_or(<Error<T>>::DepthLimit)??;
Ok(match owner {
- Parent::User(v) => v,
+ Parent::User(v) => Some(v),
+ Parent::MultipleOwners => None,
_ => fail!(<Error<T>>::TokenNotFound),
})
}
@@ -223,13 +236,15 @@
/// Find the topmost parent and check that assigning `for_nest` token as a child for
/// `token` wouldn't create a cycle.
///
+ /// Returns `None` if the token has multiple owners.
+ ///
/// - `budget`: Limit for searching parents in depth.
pub fn get_checked_topmost_owner(
collection: CollectionId,
token: TokenId,
for_nest: Option<(CollectionId, TokenId)>,
budget: &dyn Budget,
- ) -> Result<T::CrossAccountId, DispatchError> {
+ ) -> Result<Option<T::CrossAccountId>, DispatchError> {
// Tried to nest token in itself
if Some((collection, token)) == for_nest {
return Err(<Error<T>>::OuroborosDetected.into());
@@ -242,8 +257,9 @@
return Err(<Error<T>>::OuroborosDetected.into())
}
// Token is owned by other user
- Parent::User(user) => return Ok(user),
+ Parent::User(user) => return Ok(Some(user)),
Parent::TokenNotFound => return Err(<Error<T>>::TokenNotFound.into()),
+ Parent::MultipleOwners => return Ok(None),
// Continue parent chain
Parent::Token(_, _) => {}
}
@@ -284,12 +300,17 @@
budget: &dyn Budget,
) -> Result<bool, DispatchError> {
let target_parent = match T::CrossTokenAddressMapping::address_to_token(&user) {
- Some((collection, token)) => Self::find_topmost_owner(collection, token, budget)?,
+ Some((collection, token)) => match Self::find_topmost_owner(collection, token, budget)?
+ {
+ Some(topmost_owner) => topmost_owner,
+ None => return Ok(false),
+ },
None => user,
};
- Self::get_checked_topmost_owner(collection, token, for_nest, budget)
- .map(|indirect_owner| indirect_owner == target_parent)
+ Self::get_checked_topmost_owner(collection, token, for_nest, budget).map(|indirect_owner| {
+ indirect_owner.map_or(false, |indirect_owner| indirect_owner == target_parent)
+ })
}
/// Checks that `under` is valid token and that `token_id` could be nested under it
primitives/data-structs/src/lib.rsdiffbeforeafterboth--- a/primitives/data-structs/src/lib.rs
+++ b/primitives/data-structs/src/lib.rs
@@ -1099,6 +1099,13 @@
EmptyPropertyKey,
}
+/// Token owner error: it could be either `NotFound` ot `MultipleOwners`.
+#[derive(Debug)]
+pub enum TokenOwnerError {
+ NotFound,
+ MultipleOwners,
+}
+
/// Marker for scope of property.
///
/// Scoped property can't be changed by user. Used for external collections.
runtime/common/runtime_apis.rsdiffbeforeafterboth--- a/runtime/common/runtime_apis.rs
+++ b/runtime/common/runtime_apis.rs
@@ -16,11 +16,11 @@
#[macro_export]
macro_rules! dispatch_unique_runtime {
- ($collection:ident.$method:ident($($name:ident),*)) => {{
+ ($collection:ident.$method:ident($($name:ident),*) $($rest:tt)*) => {{
let collection = <Runtime as pallet_common::Config>::CollectionDispatch::dispatch(<pallet_common::CollectionHandle<Runtime>>::try_get($collection)?);
let dispatch = collection.as_dyn();
- Ok::<_, DispatchError>(dispatch.$method($($name),*))
+ Ok::<_, DispatchError>(dispatch.$method($($name),*) $($rest)*)
}};
}
@@ -73,7 +73,7 @@
}
fn token_owner(collection: CollectionId, token: TokenId) -> Result<Option<CrossAccountId>, DispatchError> {
- dispatch_unique_runtime!(collection.token_owner(token))
+ dispatch_unique_runtime!(collection.token_owner(token).ok())
}
fn token_owners(collection: CollectionId, token: TokenId) -> Result<Vec::<CrossAccountId>, DispatchError> {
@@ -83,7 +83,7 @@
fn topmost_token_owner(collection: CollectionId, token: TokenId) -> Result<Option<CrossAccountId>, DispatchError> {
let budget = up_data_structs::budget::Value::new(10);
- Ok(Some(<pallet_structure::Pallet<Runtime>>::find_topmost_owner(collection, token, &budget)?))
+ Ok(<pallet_structure::Pallet<Runtime>>::find_topmost_owner(collection, token, &budget)?)
}
fn token_children(collection: CollectionId, token: TokenId) -> Result<Vec<TokenChild>, DispatchError> {
Ok(<pallet_nonfungible::Pallet<Runtime>>::token_children_ids(collection, token))
tests/src/nesting/nest.test.tsdiffbeforeafterboth--- a/tests/src/nesting/nest.test.ts
+++ b/tests/src/nesting/nest.test.ts
@@ -15,7 +15,7 @@
// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
import {IKeyringPair} from '@polkadot/types/types';
-import {expect, itSub, usingPlaygrounds} from '../util';
+import {expect, itSub, Pallets, usingPlaygrounds} from '../util';
describe('Integration Test: Composite nesting tests', () => {
let alice: IKeyringPair;
@@ -138,7 +138,7 @@
before(async () => {
await usingPlaygrounds(async (helper, privateKey) => {
const donor = await privateKey({filename: __filename});
- [alice, bob, charlie] = await helper.arrange.createAccounts([50n, 10n, 10n], donor);
+ [alice, bob, charlie] = await helper.arrange.createAccounts([200n, 10n, 10n], donor);
});
});
@@ -288,6 +288,38 @@
await collectionFT.transfer(charlie, targetToken.nestingAccount(), 2n);
expect(await collectionFT.getBalance(targetToken.nestingAccount())).to.be.equal(7n);
});
+
+ itSub.ifWithPallets('ReFungible: getTopmostOwner works correctly with Nesting', [Pallets.ReFungible], async({helper}) => {
+ const collectionNFT = await helper.nft.mintCollection(alice, {
+ permissions: {
+ nesting: {
+ tokenOwner: true,
+ },
+ },
+ });
+ const collectionRFT = await helper.rft.mintCollection(alice);
+
+ const nft = await collectionNFT.mintToken(alice, {Substrate: alice.address});
+ const rft = await collectionRFT.mintToken(alice, 100n, {Substrate: alice.address});
+
+ expect(await rft.getTopmostOwner()).deep.equal({Substrate: alice.address});
+
+ await rft.transfer(alice, nft.nestingAccount(), 40n);
+
+ expect(await rft.getTopmostOwner()).deep.equal(null);
+
+ await rft.transfer(alice, nft.nestingAccount(), 60n);
+
+ expect(await rft.getTopmostOwner()).deep.equal({Substrate: alice.address});
+
+ await rft.transferFrom(alice, nft.nestingAccount(), {Substrate: alice.address}, 30n);
+
+ expect(await rft.getTopmostOwner()).deep.equal(null);
+
+ await rft.transferFrom(alice, nft.nestingAccount(), {Substrate: alice.address}, 70n);
+
+ expect(await rft.getTopmostOwner()).deep.equal({Substrate: alice.address});
+ });
});
describe('Negative Test: Nesting', () => {