difftreelog
doc: architectural changes
in: master
10 files changed
client/rpc/src/lib.rsdiffbeforeafterboth--- a/client/rpc/src/lib.rs
+++ b/client/rpc/src/lib.rs
@@ -42,6 +42,7 @@
#[rpc(server)]
#[async_trait]
pub trait UniqueApi<BlockHash, CrossAccountId, AccountId> {
+ /// Get tokens owned by account
#[method(name = "unique_accountTokens")]
fn account_tokens(
&self,
@@ -49,12 +50,14 @@
account: CrossAccountId,
at: Option<BlockHash>,
) -> Result<Vec<TokenId>>;
+ /// Get tokens contained in collection
#[method(name = "unique_collectionTokens")]
fn collection_tokens(
&self,
collection: CollectionId,
at: Option<BlockHash>,
) -> Result<Vec<TokenId>>;
+ /// Check if token exists
#[method(name = "unique_tokenExists")]
fn token_exists(
&self,
@@ -62,7 +65,7 @@
token: TokenId,
at: Option<BlockHash>,
) -> Result<bool>;
-
+ /// Get token owner
#[method(name = "unique_tokenOwner")]
fn token_owner(
&self,
@@ -70,6 +73,7 @@
token: TokenId,
at: Option<BlockHash>,
) -> Result<Option<CrossAccountId>>;
+ /// Get token owner, in case of nested token - find the parent recursively
#[method(name = "unique_topmostTokenOwner")]
fn topmost_token_owner(
&self,
@@ -77,6 +81,7 @@
token: TokenId,
at: Option<BlockHash>,
) -> Result<Option<CrossAccountId>>;
+ /// Get tokens nested directly into the token
#[method(name = "unique_tokenChildren")]
fn token_children(
&self,
@@ -84,7 +89,7 @@
token: TokenId,
at: Option<BlockHash>,
) -> Result<Vec<TokenChild>>;
-
+ /// Get collection properties
#[method(name = "unique_collectionProperties")]
fn collection_properties(
&self,
@@ -92,7 +97,7 @@
keys: Option<Vec<String>>,
at: Option<BlockHash>,
) -> Result<Vec<Property>>;
-
+ /// Get token properties
#[method(name = "unique_tokenProperties")]
fn token_properties(
&self,
@@ -101,7 +106,7 @@
keys: Option<Vec<String>>,
at: Option<BlockHash>,
) -> Result<Vec<Property>>;
-
+ /// Get property permissions
#[method(name = "unique_propertyPermissions")]
fn property_permissions(
&self,
@@ -109,7 +114,7 @@
keys: Option<Vec<String>>,
at: Option<BlockHash>,
) -> Result<Vec<PropertyKeyPermission>>;
-
+ /// Get token data
#[method(name = "unique_tokenData")]
fn token_data(
&self,
@@ -118,9 +123,10 @@
keys: Option<Vec<String>>,
at: Option<BlockHash>,
) -> Result<TokenData<CrossAccountId>>;
-
+ /// Get amount of unique collection tokens
#[method(name = "unique_totalSupply")]
fn total_supply(&self, collection: CollectionId, at: Option<BlockHash>) -> Result<u32>;
+ /// Get owned amount of any user tokens
#[method(name = "unique_accountBalance")]
fn account_balance(
&self,
@@ -128,6 +134,7 @@
account: CrossAccountId,
at: Option<BlockHash>,
) -> Result<u32>;
+ /// Get owned amount of specific account token
#[method(name = "unique_balance")]
fn balance(
&self,
@@ -136,6 +143,7 @@
token: TokenId,
at: Option<BlockHash>,
) -> Result<String>;
+ /// Get allowed amount
#[method(name = "unique_allowance")]
fn allowance(
&self,
@@ -145,19 +153,21 @@
token: TokenId,
at: Option<BlockHash>,
) -> Result<String>;
-
+ /// Get admin list
#[method(name = "unique_adminlist")]
fn adminlist(
&self,
collection: CollectionId,
at: Option<BlockHash>,
) -> Result<Vec<CrossAccountId>>;
+ /// Get allowlist
#[method(name = "unique_allowlist")]
fn allowlist(
&self,
collection: CollectionId,
at: Option<BlockHash>,
) -> Result<Vec<CrossAccountId>>;
+ /// Check if user is allowed to use collection
#[method(name = "unique_allowed")]
fn allowed(
&self,
@@ -165,17 +175,20 @@
user: CrossAccountId,
at: Option<BlockHash>,
) -> Result<bool>;
+ /// Get last token ID created in a collection
#[method(name = "unique_lastTokenId")]
fn last_token_id(&self, collection: CollectionId, at: Option<BlockHash>) -> Result<TokenId>;
+ /// Get collection by specified ID
#[method(name = "unique_collectionById")]
fn collection_by_id(
&self,
collection: CollectionId,
at: Option<BlockHash>,
) -> Result<Option<RpcCollection<AccountId>>>;
+ /// Get collection stats
#[method(name = "unique_collectionStats")]
fn collection_stats(&self, at: Option<BlockHash>) -> Result<CollectionStats>;
-
+ /// Get number of blocks when sponsored transaction is available
#[method(name = "unique_nextSponsored")]
fn next_sponsored(
&self,
@@ -184,14 +197,14 @@
token: TokenId,
at: Option<BlockHash>,
) -> Result<Option<u64>>;
-
+ /// Get effective collection limits
#[method(name = "unique_effectiveCollectionLimits")]
fn effective_collection_limits(
&self,
collection_id: CollectionId,
at: Option<BlockHash>,
) -> Result<Option<CollectionLimits>>;
-
+ /// Get total pieces of token
#[method(name = "unique_totalPieces")]
fn total_pieces(
&self,
@@ -304,6 +317,7 @@
fn base_parts(&self, base_id: RmrkBaseId, at: Option<BlockHash>) -> Result<Vec<PartType>>;
#[method(name = "rmrk_themeNames")]
+ /// Get Base's theme names
fn theme_names(
&self,
base_id: RmrkBaseId,
@@ -311,6 +325,7 @@
) -> Result<Vec<RmrkThemeName>>;
#[method(name = "rmrk_themes")]
+ /// Get Theme info -- name, properties, and inherit flag
fn theme(
&self,
base_id: RmrkBaseId,
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, Pallet as PalletEvm};25use evm_coder::ToLog;26use frame_support::{27 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},28 ensure,29 traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},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 TokenChild,44 CollectionStats,45 MAX_TOKEN_OWNERSHIP,46 CollectionMode,47 NFT_SPONSOR_TRANSFER_TIMEOUT,48 FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,49 REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,50 MAX_SPONSOR_TIMEOUT,51 CUSTOM_DATA_LIMIT,52 CollectionLimits,53 CreateCollectionData,54 SponsorshipState,55 CreateItemExData,56 SponsoringRateLimit,57 budget::Budget,58 PhantomType,59 Property,60 Properties,61 PropertiesPermissionMap,62 PropertyKey,63 PropertyValue,64 PropertyPermission,65 PropertiesError,66 PropertyKeyPermission,67 TokenData,68 TrySetProperty,69 PropertyScope,70 // RMRK71 RmrkCollectionInfo,72 RmrkInstanceInfo,73 RmrkResourceInfo,74 RmrkPropertyInfo,75 RmrkBaseInfo,76 RmrkPartType,77 RmrkBoundedTheme,78 RmrkNftChild,79 CollectionPermissions,80 SchemaVersion,81};8283pub use pallet::*;84use sp_core::H160;85use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};86#[cfg(feature = "runtime-benchmarks")]87pub mod benchmarking;88pub mod dispatch;89pub mod erc;90pub mod eth;91pub mod weights;9293pub type SelfWeightOf<T> = <T as Config>::WeightInfo;9495#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]96pub struct CollectionHandle<T: Config> {97 pub id: CollectionId,98 collection: Collection<T::AccountId>,99 pub recorder: SubstrateRecorder<T>,100}101impl<T: Config> WithRecorder<T> for CollectionHandle<T> {102 fn recorder(&self) -> &SubstrateRecorder<T> {103 &self.recorder104 }105 fn into_recorder(self) -> SubstrateRecorder<T> {106 self.recorder107 }108}109impl<T: Config> CollectionHandle<T> {110 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {111 <CollectionById<T>>::get(id).map(|collection| Self {112 id,113 collection,114 recorder: SubstrateRecorder::new(gas_limit),115 })116 }117118 pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder<T>) -> Option<Self> {119 <CollectionById<T>>::get(id).map(|collection| Self {120 id,121 collection,122 recorder,123 })124 }125126 pub fn new(id: CollectionId) -> Option<Self> {127 Self::new_with_gas_limit(id, u64::MAX)128 }129130 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {131 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)132 }133134 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {135 self.recorder136 .consume_gas(T::GasWeightMapping::weight_to_gas(137 <T as frame_system::Config>::DbWeight::get()138 .read139 .saturating_mul(reads),140 ))141 }142143 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {144 self.recorder145 .consume_gas(T::GasWeightMapping::weight_to_gas(146 <T as frame_system::Config>::DbWeight::get()147 .write148 .saturating_mul(writes),149 ))150 }151 pub fn save(self) -> DispatchResult {152 <CollectionById<T>>::insert(self.id, self.collection);153 Ok(())154 }155156 pub fn set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {157 self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor);158 Ok(())159 }160161 pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> Result<bool, DispatchError> {162 if self.collection.sponsorship.pending_sponsor() != Some(sender) {163 return Ok(false);164 }165166 self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());167 Ok(true)168 }169170 /// Checks that the collection was created with, and must be operated upon through **Unique API**.171 /// Now check only the `external_collection` flag and if it's **true**, then return `CollectionIsExternal` error.172 pub fn check_is_internal(&self) -> DispatchResult {173 if self.external_collection {174 return Err(<Error<T>>::CollectionIsExternal)?;175 }176177 Ok(())178 }179180 /// Checks that the collection was created with, and must be operated upon through an **assimilated API**.181 /// Now check only the `external_collection` flag and if it's **false**, then return `CollectionIsInternal` error.182 pub fn check_is_external(&self) -> DispatchResult {183 if !self.external_collection {184 return Err(<Error<T>>::CollectionIsInternal)?;185 }186187 Ok(())188 }189}190191impl<T: Config> Deref for CollectionHandle<T> {192 type Target = Collection<T::AccountId>;193194 fn deref(&self) -> &Self::Target {195 &self.collection196 }197}198199impl<T: Config> DerefMut for CollectionHandle<T> {200 fn deref_mut(&mut self) -> &mut Self::Target {201 &mut self.collection202 }203}204205impl<T: Config> CollectionHandle<T> {206 pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {207 ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);208 Ok(())209 }210 pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {211 *subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))212 }213 pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {214 ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);215 Ok(())216 }217 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {218 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)219 }220 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {221 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)222 }223 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {224 ensure!(225 <Allowlist<T>>::get((self.id, user)),226 <Error<T>>::AddressNotInAllowlist227 );228 Ok(())229 }230}231232#[frame_support::pallet]233pub mod pallet {234 use super::*;235 use pallet_evm::account;236 use dispatch::CollectionDispatch;237 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};238 use frame_system::pallet_prelude::*;239 use frame_support::traits::Currency;240 use up_data_structs::{TokenId, mapping::TokenAddressMapping};241 use scale_info::TypeInfo;242 use weights::WeightInfo;243244 #[pallet::config]245 pub trait Config:246 frame_system::Config247 + pallet_evm_coder_substrate::Config248 + pallet_evm::Config249 + TypeInfo250 + account::Config251 {252 type WeightInfo: WeightInfo;253 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;254255 type Currency: Currency<Self::AccountId>;256257 #[pallet::constant]258 type CollectionCreationPrice: Get<259 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,260 >;261 type CollectionDispatch: CollectionDispatch<Self>;262263 type TreasuryAccountId: Get<Self::AccountId>;264 type ContractAddress: Get<H160>;265266 type EvmTokenAddressMapping: TokenAddressMapping<H160>;267 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;268 }269270 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);271272 #[pallet::pallet]273 #[pallet::storage_version(STORAGE_VERSION)]274 #[pallet::generate_store(pub(super) trait Store)]275 pub struct Pallet<T>(_);276277 #[pallet::extra_constants]278 impl<T: Config> Pallet<T> {279 pub fn collection_admins_limit() -> u32 {280 COLLECTION_ADMINS_LIMIT281 }282 }283284 #[pallet::event]285 #[pallet::generate_deposit(pub fn deposit_event)]286 pub enum Event<T: Config> {287 /// New collection was created288 ///289 /// # Arguments290 ///291 /// * collection_id: Globally unique identifier of newly created collection.292 ///293 /// * mode: [CollectionMode] converted into u8.294 ///295 /// * account_id: Collection owner.296 CollectionCreated(CollectionId, u8, T::AccountId),297298 /// New collection was destroyed299 ///300 /// # Arguments301 ///302 /// * collection_id: Globally unique identifier of collection.303 CollectionDestroyed(CollectionId),304305 /// New item was created.306 ///307 /// # Arguments308 ///309 /// * collection_id: Id of the collection where item was created.310 ///311 /// * item_id: Id of an item. Unique within the collection.312 ///313 /// * recipient: Owner of newly created item314 ///315 /// * amount: Always 1 for NFT316 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),317318 /// Collection item was burned.319 ///320 /// # Arguments321 ///322 /// * collection_id.323 ///324 /// * item_id: Identifier of burned NFT.325 ///326 /// * owner: which user has destroyed its tokens327 ///328 /// * amount: Always 1 for NFT329 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),330331 /// Item was transferred332 ///333 /// * collection_id: Id of collection to which item is belong334 ///335 /// * item_id: Id of an item336 ///337 /// * sender: Original owner of item338 ///339 /// * recipient: New owner of item340 ///341 /// * amount: Always 1 for NFT342 Transfer(343 CollectionId,344 TokenId,345 T::CrossAccountId,346 T::CrossAccountId,347 u128,348 ),349350 /// * collection_id351 ///352 /// * item_id353 ///354 /// * sender355 ///356 /// * spender357 ///358 /// * amount359 Approved(360 CollectionId,361 TokenId,362 T::CrossAccountId,363 T::CrossAccountId,364 u128,365 ),366367 CollectionPropertySet(CollectionId, PropertyKey),368369 CollectionPropertyDeleted(CollectionId, PropertyKey),370371 TokenPropertySet(CollectionId, TokenId, PropertyKey),372373 TokenPropertyDeleted(CollectionId, TokenId, PropertyKey),374375 PropertyPermissionSet(CollectionId, PropertyKey),376 }377378 #[pallet::error]379 pub enum Error<T> {380 /// This collection does not exist.381 CollectionNotFound,382 /// Sender parameter and item owner must be equal.383 MustBeTokenOwner,384 /// No permission to perform action385 NoPermission,386 /// Destroying only empty collections is allowed387 CantDestroyNotEmptyCollection,388 /// Collection is not in mint mode.389 PublicMintingNotAllowed,390 /// Address is not in allow list.391 AddressNotInAllowlist,392393 /// Collection name can not be longer than 63 char.394 CollectionNameLimitExceeded,395 /// Collection description can not be longer than 255 char.396 CollectionDescriptionLimitExceeded,397 /// Token prefix can not be longer than 15 char.398 CollectionTokenPrefixLimitExceeded,399 /// Total collections bound exceeded.400 TotalCollectionsLimitExceeded,401 /// Exceeded max admin count402 CollectionAdminCountExceeded,403 /// Collection limit bounds per collection exceeded404 CollectionLimitBoundsExceeded,405 /// Tried to enable permissions which are only permitted to be disabled406 OwnerPermissionsCantBeReverted,407 /// Collection settings not allowing items transferring408 TransferNotAllowed,409 /// Account token limit exceeded per collection410 AccountTokenLimitExceeded,411 /// Collection token limit exceeded412 CollectionTokenLimitExceeded,413 /// Metadata flag frozen414 MetadataFlagFrozen,415416 /// Item not exists.417 TokenNotFound,418 /// Item balance not enough.419 TokenValueTooLow,420 /// Requested value more than approved.421 ApprovedValueTooLow,422 /// Tried to approve more than owned423 CantApproveMoreThanOwned,424425 /// Can't transfer tokens to ethereum zero address426 AddressIsZero,427 /// Target collection doesn't supports this operation428 UnsupportedOperation,429430 /// Not sufficient funds to perform action431 NotSufficientFounds,432433 /// User not passed nesting rule434 UserIsNotAllowedToNest,435 /// Only tokens from specific collections may nest tokens under this436 SourceCollectionIsNotAllowedToNest,437438 /// Tried to store more data than allowed in collection field439 CollectionFieldSizeExceeded,440441 /// Tried to store more property data than allowed442 NoSpaceForProperty,443444 /// Tried to store more property keys than allowed445 PropertyLimitReached,446447 /// Property key is too long448 PropertyKeyIsTooLong,449450 /// Only ASCII letters, digits, and '_', '-' are allowed451 InvalidCharacterInPropertyKey,452453 /// Empty property keys are forbidden454 EmptyPropertyKey,455456 /// Tried to access an external collection with an internal API457 CollectionIsExternal,458459 /// Tried to access an internal collection with an external API460 CollectionIsInternal,461 }462463 #[pallet::storage]464 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;465 #[pallet::storage]466 pub type DestroyedCollectionCount<T> =467 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;468469 /// Collection info470 #[pallet::storage]471 pub type CollectionById<T> = StorageMap<472 Hasher = Blake2_128Concat,473 Key = CollectionId,474 Value = Collection<<T as frame_system::Config>::AccountId>,475 QueryKind = OptionQuery,476 >;477478 /// Collection properties479 #[pallet::storage]480 #[pallet::getter(fn collection_properties)]481 pub type CollectionProperties<T> = StorageMap<482 Hasher = Blake2_128Concat,483 Key = CollectionId,484 Value = Properties,485 QueryKind = ValueQuery,486 OnEmpty = up_data_structs::CollectionProperties,487 >;488489 #[pallet::storage]490 #[pallet::getter(fn property_permissions)]491 pub type CollectionPropertyPermissions<T> = StorageMap<492 Hasher = Blake2_128Concat,493 Key = CollectionId,494 Value = PropertiesPermissionMap,495 QueryKind = ValueQuery,496 >;497498 #[pallet::storage]499 pub type AdminAmount<T> = StorageMap<500 Hasher = Blake2_128Concat,501 Key = CollectionId,502 Value = u32,503 QueryKind = ValueQuery,504 >;505506 /// List of collection admins507 #[pallet::storage]508 pub type IsAdmin<T: Config> = StorageNMap<509 Key = (510 Key<Blake2_128Concat, CollectionId>,511 Key<Blake2_128Concat, T::CrossAccountId>,512 ),513 Value = bool,514 QueryKind = ValueQuery,515 >;516517 /// Allowlisted collection users518 #[pallet::storage]519 pub type Allowlist<T: Config> = StorageNMap<520 Key = (521 Key<Blake2_128Concat, CollectionId>,522 Key<Blake2_128Concat, T::CrossAccountId>,523 ),524 Value = bool,525 QueryKind = ValueQuery,526 >;527528 /// Not used by code, exists only to provide some types to metadata529 #[pallet::storage]530 pub type DummyStorageValue<T: Config> = StorageValue<531 Value = (532 CollectionStats,533 CollectionId,534 TokenId,535 TokenChild,536 PhantomType<(537 TokenData<T::CrossAccountId>,538 RpcCollection<T::AccountId>,539 // RMRK540 RmrkCollectionInfo<T::AccountId>,541 RmrkInstanceInfo<T::AccountId>,542 RmrkResourceInfo,543 RmrkPropertyInfo,544 RmrkBaseInfo<T::AccountId>,545 RmrkPartType,546 RmrkBoundedTheme,547 RmrkNftChild,548 )>,549 ),550 QueryKind = OptionQuery,551 >;552553 #[pallet::hooks]554 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {555 fn on_runtime_upgrade() -> Weight {556 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {557 use up_data_structs::{CollectionVersion1, CollectionVersion2};558 <CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {559 let mut props = Vec::new();560 if !v.offchain_schema.is_empty() {561 props.push(Property {562 key: b"_old_offchainSchema".to_vec().try_into().unwrap(),563 value: v564 .offchain_schema565 .clone()566 .into_inner()567 .try_into()568 .expect("offchain schema too big"),569 });570 }571 if !v.variable_on_chain_schema.is_empty() {572 props.push(Property {573 key: b"_old_variableOnChainSchema".to_vec().try_into().unwrap(),574 value: v575 .variable_on_chain_schema576 .clone()577 .into_inner()578 .try_into()579 .expect("offchain schema too big"),580 });581 }582 if !v.const_on_chain_schema.is_empty() {583 props.push(Property {584 key: b"_old_constOnChainSchema".to_vec().try_into().unwrap(),585 value: v586 .const_on_chain_schema587 .clone()588 .into_inner()589 .try_into()590 .expect("offchain schema too big"),591 });592 }593 props.push(Property {594 key: b"_old_schemaVersion".to_vec().try_into().unwrap(),595 value: match v.schema_version {596 SchemaVersion::ImageURL => b"ImageUrl".as_slice(),597 SchemaVersion::Unique => b"Unique".as_slice(),598 }599 .to_vec()600 .try_into()601 .unwrap(),602 });603 Self::set_scoped_collection_properties(604 id,605 PropertyScope::None,606 props.into_iter(),607 )608 .expect("existing data larger than properties");609 let mut new = CollectionVersion2::from(v.clone());610 new.permissions.access = Some(v.access);611 new.permissions.mint_mode = Some(v.mint_mode);612 Some(new)613 });614 }615616 0617 }618 }619}620621impl<T: Config> Pallet<T> {622 /// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens623 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {624 ensure!(625 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,626 <Error<T>>::AddressIsZero627 );628 Ok(())629 }630 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {631 <IsAdmin<T>>::iter_prefix((collection,))632 .map(|(a, _)| a)633 .collect()634 }635 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {636 <Allowlist<T>>::iter_prefix((collection,))637 .map(|(a, _)| a)638 .collect()639 }640 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {641 <Allowlist<T>>::get((collection, user))642 }643 pub fn collection_stats() -> CollectionStats {644 let created = <CreatedCollectionCount<T>>::get();645 let destroyed = <DestroyedCollectionCount<T>>::get();646 CollectionStats {647 created: created.0,648 destroyed: destroyed.0,649 alive: created.0 - destroyed.0,650 }651 }652653 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {654 let collection = <CollectionById<T>>::get(collection);655 if collection.is_none() {656 return None;657 }658659 let collection = collection.unwrap();660 let limits = collection.limits;661 let effective_limits = CollectionLimits {662 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),663 sponsored_data_size: Some(limits.sponsored_data_size()),664 sponsored_data_rate_limit: Some(665 limits666 .sponsored_data_rate_limit667 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),668 ),669 token_limit: Some(limits.token_limit()),670 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(671 match collection.mode {672 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,673 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,674 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,675 },676 )),677 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),678 owner_can_transfer: Some(limits.owner_can_transfer()),679 owner_can_destroy: Some(limits.owner_can_destroy()),680 transfers_enabled: Some(limits.transfers_enabled()),681 };682683 Some(effective_limits)684 }685686 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {687 let Collection {688 name,689 description,690 owner,691 mode,692 token_prefix,693 sponsorship,694 limits,695 permissions,696 external_collection,697 } = <CollectionById<T>>::get(collection)?;698699 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)700 .into_iter()701 .map(|(key, permission)| PropertyKeyPermission { key, permission })702 .collect();703704 let properties = <CollectionProperties<T>>::get(collection)705 .into_iter()706 .map(|(key, value)| Property { key, value })707 .collect();708709 let permissions = CollectionPermissions {710 access: Some(permissions.access()),711 mint_mode: Some(permissions.mint_mode()),712 nesting: Some(permissions.nesting().clone()),713 };714715 Some(RpcCollection {716 name: name.into_inner(),717 description: description.into_inner(),718 owner,719 mode,720 token_prefix: token_prefix.into_inner(),721 sponsorship,722 limits,723 permissions,724 token_property_permissions,725 properties,726 read_only: external_collection,727 })728 }729}730731macro_rules! limit_default {732 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{733 $(734 if let Some($new) = $new.$field {735 let $old = $old.$field($($arg)?);736 let _ = $new;737 let _ = $old;738 $check739 } else {740 $new.$field = $old.$field741 }742 )*743 }};744}745macro_rules! limit_default_clone {746 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{747 $(748 if let Some($new) = $new.$field.clone() {749 let $old = $old.$field($($arg)?);750 let _ = $new;751 let _ = $old;752 $check753 } else {754 $new.$field = $old.$field.clone()755 }756 )*757 }};758}759760impl<T: Config> Pallet<T> {761 pub fn init_collection(762 owner: T::CrossAccountId,763 data: CreateCollectionData<T::AccountId>,764 is_external: bool,765 ) -> Result<CollectionId, DispatchError> {766 {767 ensure!(768 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,769 Error::<T>::CollectionTokenPrefixLimitExceeded770 );771 }772773 let created_count = <CreatedCollectionCount<T>>::get()774 .0775 .checked_add(1)776 .ok_or(ArithmeticError::Overflow)?;777 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;778 let id = CollectionId(created_count);779780 // bound Total number of collections781 ensure!(782 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,783 <Error<T>>::TotalCollectionsLimitExceeded784 );785786 // =========787788 let collection = Collection {789 owner: owner.as_sub().clone(),790 name: data.name,791 mode: data.mode.clone(),792 description: data.description,793 token_prefix: data.token_prefix,794 sponsorship: data795 .pending_sponsor796 .map(SponsorshipState::Unconfirmed)797 .unwrap_or_default(),798 limits: data799 .limits800 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))801 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,802 permissions: data803 .permissions804 .map(|permissions| {805 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)806 })807 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,808 external_collection: is_external,809 };810811 let mut collection_properties = up_data_structs::CollectionProperties::get();812 collection_properties813 .try_set_from_iter(data.properties.into_iter())814 .map_err(<Error<T>>::from)?;815816 CollectionProperties::<T>::insert(id, collection_properties);817818 let mut token_props_permissions = PropertiesPermissionMap::new();819 token_props_permissions820 .try_set_from_iter(data.token_property_permissions.into_iter())821 .map_err(<Error<T>>::from)?;822823 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);824825 // Take a (non-refundable) deposit of collection creation826 {827 let mut imbalance =828 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();829 imbalance.subsume(830 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(831 &T::TreasuryAccountId::get(),832 T::CollectionCreationPrice::get(),833 ),834 );835 <T as Config>::Currency::settle(836 &owner.as_sub(),837 imbalance,838 WithdrawReasons::TRANSFER,839 ExistenceRequirement::KeepAlive,840 )841 .map_err(|_| Error::<T>::NotSufficientFounds)?;842 }843844 <CreatedCollectionCount<T>>::put(created_count);845 <Pallet<T>>::deposit_event(Event::CollectionCreated(846 id,847 data.mode.id(),848 owner.as_sub().clone(),849 ));850 <PalletEvm<T>>::deposit_log(851 erc::CollectionHelpersEvents::CollectionCreated {852 owner: *owner.as_eth(),853 collection_id: eth::collection_id_to_address(id),854 }855 .to_log(T::ContractAddress::get()),856 );857 <CollectionById<T>>::insert(id, collection);858 Ok(id)859 }860861 pub fn destroy_collection(862 collection: CollectionHandle<T>,863 sender: &T::CrossAccountId,864 ) -> DispatchResult {865 ensure!(866 collection.limits.owner_can_destroy(),867 <Error<T>>::NoPermission,868 );869 collection.check_is_owner(sender)?;870871 let destroyed_collections = <DestroyedCollectionCount<T>>::get()872 .0873 .checked_add(1)874 .ok_or(ArithmeticError::Overflow)?;875876 // =========877878 <DestroyedCollectionCount<T>>::put(destroyed_collections);879 <CollectionById<T>>::remove(collection.id);880 <AdminAmount<T>>::remove(collection.id);881 <IsAdmin<T>>::remove_prefix((collection.id,), None);882 <Allowlist<T>>::remove_prefix((collection.id,), None);883 <CollectionProperties<T>>::remove(collection.id);884885 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));886 Ok(())887 }888889 pub fn set_collection_property(890 collection: &CollectionHandle<T>,891 sender: &T::CrossAccountId,892 property: Property,893 ) -> DispatchResult {894 collection.check_is_owner_or_admin(sender)?;895896 CollectionProperties::<T>::try_mutate(collection.id, |properties| {897 let property = property.clone();898 properties.try_set(property.key, property.value)899 })900 .map_err(<Error<T>>::from)?;901902 Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));903904 Ok(())905 }906907 pub fn set_scoped_collection_property(908 collection_id: CollectionId,909 scope: PropertyScope,910 property: Property,911 ) -> DispatchResult {912 CollectionProperties::<T>::try_mutate(collection_id, |properties| {913 properties.try_scoped_set(scope, property.key, property.value)914 })915 .map_err(<Error<T>>::from)?;916917 Ok(())918 }919920 pub fn set_scoped_collection_properties(921 collection_id: CollectionId,922 scope: PropertyScope,923 properties: impl Iterator<Item = Property>,924 ) -> DispatchResult {925 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {926 stored_properties.try_scoped_set_from_iter(scope, properties)927 })928 .map_err(<Error<T>>::from)?;929930 Ok(())931 }932933 #[transactional]934 pub fn set_collection_properties(935 collection: &CollectionHandle<T>,936 sender: &T::CrossAccountId,937 properties: Vec<Property>,938 ) -> DispatchResult {939 for property in properties {940 Self::set_collection_property(collection, sender, property)?;941 }942943 Ok(())944 }945946 pub fn delete_collection_property(947 collection: &CollectionHandle<T>,948 sender: &T::CrossAccountId,949 property_key: PropertyKey,950 ) -> DispatchResult {951 collection.check_is_owner_or_admin(sender)?;952953 CollectionProperties::<T>::try_mutate(collection.id, |properties| {954 properties.remove(&property_key)955 })956 .map_err(<Error<T>>::from)?;957958 Self::deposit_event(Event::CollectionPropertyDeleted(959 collection.id,960 property_key,961 ));962963 Ok(())964 }965966 #[transactional]967 pub fn delete_collection_properties(968 collection: &CollectionHandle<T>,969 sender: &T::CrossAccountId,970 property_keys: Vec<PropertyKey>,971 ) -> DispatchResult {972 for key in property_keys {973 Self::delete_collection_property(collection, sender, key)?;974 }975976 Ok(())977 }978979 // For migrations980 pub fn set_property_permission_unchecked(981 collection: CollectionId,982 property_permission: PropertyKeyPermission,983 ) -> DispatchResult {984 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {985 permissions.try_set(property_permission.key, property_permission.permission)986 })987 .map_err(<Error<T>>::from)?;988 Ok(())989 }990991 pub fn set_property_permission(992 collection: &CollectionHandle<T>,993 sender: &T::CrossAccountId,994 property_permission: PropertyKeyPermission,995 ) -> DispatchResult {996 collection.check_is_owner_or_admin(sender)?;997998 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);999 let current_permission = all_permissions.get(&property_permission.key);1000 if matches![1001 current_permission,1002 Some(PropertyPermission { mutable: false, .. })1003 ] {1004 return Err(<Error<T>>::NoPermission.into());1005 }10061007 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1008 let property_permission = property_permission.clone();1009 permissions.try_set(property_permission.key, property_permission.permission)1010 })1011 .map_err(<Error<T>>::from)?;10121013 Self::deposit_event(Event::PropertyPermissionSet(1014 collection.id,1015 property_permission.key,1016 ));10171018 Ok(())1019 }10201021 #[transactional]1022 pub fn set_token_property_permissions(1023 collection: &CollectionHandle<T>,1024 sender: &T::CrossAccountId,1025 property_permissions: Vec<PropertyKeyPermission>,1026 ) -> DispatchResult {1027 for prop_pemission in property_permissions {1028 Self::set_property_permission(collection, sender, prop_pemission)?;1029 }10301031 Ok(())1032 }10331034 pub fn get_collection_property(1035 collection_id: CollectionId,1036 key: &PropertyKey,1037 ) -> Option<PropertyValue> {1038 Self::collection_properties(collection_id).get(key).cloned()1039 }10401041 pub fn bytes_keys_to_property_keys(1042 keys: Vec<Vec<u8>>,1043 ) -> Result<Vec<PropertyKey>, DispatchError> {1044 keys.into_iter()1045 .map(|key| -> Result<PropertyKey, DispatchError> {1046 key.try_into()1047 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1048 })1049 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1050 }10511052 pub fn filter_collection_properties(1053 collection_id: CollectionId,1054 keys: Option<Vec<PropertyKey>>,1055 ) -> Result<Vec<Property>, DispatchError> {1056 let properties = Self::collection_properties(collection_id);10571058 let properties = keys1059 .map(|keys| {1060 keys.into_iter()1061 .filter_map(|key| {1062 properties.get(&key).map(|value| Property {1063 key,1064 value: value.clone(),1065 })1066 })1067 .collect()1068 })1069 .unwrap_or_else(|| {1070 properties1071 .into_iter()1072 .map(|(key, value)| Property { key, value })1073 .collect()1074 });10751076 Ok(properties)1077 }10781079 pub fn filter_property_permissions(1080 collection_id: CollectionId,1081 keys: Option<Vec<PropertyKey>>,1082 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1083 let permissions = Self::property_permissions(collection_id);10841085 let key_permissions = keys1086 .map(|keys| {1087 keys.into_iter()1088 .filter_map(|key| {1089 permissions1090 .get(&key)1091 .map(|permission| PropertyKeyPermission {1092 key,1093 permission: permission.clone(),1094 })1095 })1096 .collect()1097 })1098 .unwrap_or_else(|| {1099 permissions1100 .into_iter()1101 .map(|(key, permission)| PropertyKeyPermission { key, permission })1102 .collect()1103 });11041105 Ok(key_permissions)1106 }11071108 pub fn toggle_allowlist(1109 collection: &CollectionHandle<T>,1110 sender: &T::CrossAccountId,1111 user: &T::CrossAccountId,1112 allowed: bool,1113 ) -> DispatchResult {1114 collection.check_is_owner_or_admin(sender)?;11151116 // =========11171118 if allowed {1119 <Allowlist<T>>::insert((collection.id, user), true);1120 } else {1121 <Allowlist<T>>::remove((collection.id, user));1122 }11231124 Ok(())1125 }11261127 pub fn toggle_admin(1128 collection: &CollectionHandle<T>,1129 sender: &T::CrossAccountId,1130 user: &T::CrossAccountId,1131 admin: bool,1132 ) -> DispatchResult {1133 collection.check_is_owner(sender)?;11341135 let was_admin = <IsAdmin<T>>::get((collection.id, user));1136 if was_admin == admin {1137 return Ok(());1138 }1139 let amount = <AdminAmount<T>>::get(collection.id);11401141 if admin {1142 let amount = amount1143 .checked_add(1)1144 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1145 ensure!(1146 amount <= Self::collection_admins_limit(),1147 <Error<T>>::CollectionAdminCountExceeded,1148 );11491150 // =========11511152 <AdminAmount<T>>::insert(collection.id, amount);1153 <IsAdmin<T>>::insert((collection.id, user), true);1154 } else {1155 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1156 <IsAdmin<T>>::remove((collection.id, user));1157 }11581159 Ok(())1160 }11611162 pub fn clamp_limits(1163 mode: CollectionMode,1164 old_limit: &CollectionLimits,1165 mut new_limit: CollectionLimits,1166 ) -> Result<CollectionLimits, DispatchError> {1167 let limits = old_limit;1168 limit_default!(old_limit, new_limit,1169 account_token_ownership_limit => ensure!(1170 new_limit <= MAX_TOKEN_OWNERSHIP,1171 <Error<T>>::CollectionLimitBoundsExceeded,1172 ),1173 sponsored_data_size => ensure!(1174 new_limit <= CUSTOM_DATA_LIMIT,1175 <Error<T>>::CollectionLimitBoundsExceeded,1176 ),11771178 sponsored_data_rate_limit => {},1179 token_limit => ensure!(1180 old_limit >= new_limit && new_limit > 0,1181 <Error<T>>::CollectionTokenLimitExceeded1182 ),11831184 sponsor_transfer_timeout(match mode {1185 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1186 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1187 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1188 }) => ensure!(1189 new_limit <= MAX_SPONSOR_TIMEOUT,1190 <Error<T>>::CollectionLimitBoundsExceeded,1191 ),1192 sponsor_approve_timeout => {},1193 owner_can_transfer => ensure!(1194 !limits.owner_can_transfer_instaled() ||1195 old_limit || !new_limit,1196 <Error<T>>::OwnerPermissionsCantBeReverted,1197 ),1198 owner_can_destroy => ensure!(1199 old_limit || !new_limit,1200 <Error<T>>::OwnerPermissionsCantBeReverted,1201 ),1202 transfers_enabled => {},1203 );1204 Ok(new_limit)1205 }12061207 pub fn clamp_permissions(1208 _mode: CollectionMode,1209 old_limit: &CollectionPermissions,1210 mut new_limit: CollectionPermissions,1211 ) -> Result<CollectionPermissions, DispatchError> {1212 limit_default_clone!(old_limit, new_limit,1213 access => {},1214 mint_mode => {},1215 nesting => { /* todo check for permissive, if only it gets out of benchmarks */ },1216 );1217 Ok(new_limit)1218 }1219}12201221#[macro_export]1222macro_rules! unsupported {1223 () => {1224 Err(<Error<T>>::UnsupportedOperation.into())1225 };1226}12271228/// Worst cases1229pub trait CommonWeightInfo<CrossAccountId> {1230 fn create_item() -> Weight;1231 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;1232 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1233 fn burn_item() -> Weight;1234 fn set_collection_properties(amount: u32) -> Weight;1235 fn delete_collection_properties(amount: u32) -> Weight;1236 fn set_token_properties(amount: u32) -> Weight;1237 fn delete_token_properties(amount: u32) -> Weight;1238 fn set_token_property_permissions(amount: u32) -> Weight;1239 fn transfer() -> Weight;1240 fn approve() -> Weight;1241 fn transfer_from() -> Weight;1242 fn burn_from() -> Weight;12431244 /// Differs from burn_item in case of Fungible and Refungible, as it should burn1245 /// whole users's balance1246 ///1247 /// This method shouldn't be used directly, as it doesn't count breadth price, use `burn_recursively` instead1248 fn burn_recursively_self_raw() -> Weight;1249 /// Cost of iterating over `amount` children while burning, without counting child burning itself1250 ///1251 /// This method shouldn't be used directly, as it doesn't count depth price, use `burn_recursively` instead1252 fn burn_recursively_breadth_raw(amount: u32) -> Weight;12531254 fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1255 Self::burn_recursively_self_raw()1256 .saturating_mul(max_selfs.max(1) as u64)1257 .saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1258 }1259}12601261pub trait RefungibleExtensionsWeightInfo {1262 fn repartition() -> Weight;1263}12641265pub trait CommonCollectionOperations<T: Config> {1266 fn create_item(1267 &self,1268 sender: T::CrossAccountId,1269 to: T::CrossAccountId,1270 data: CreateItemData,1271 nesting_budget: &dyn Budget,1272 ) -> DispatchResultWithPostInfo;1273 fn create_multiple_items(1274 &self,1275 sender: T::CrossAccountId,1276 to: T::CrossAccountId,1277 data: Vec<CreateItemData>,1278 nesting_budget: &dyn Budget,1279 ) -> DispatchResultWithPostInfo;1280 fn create_multiple_items_ex(1281 &self,1282 sender: T::CrossAccountId,1283 data: CreateItemExData<T::CrossAccountId>,1284 nesting_budget: &dyn Budget,1285 ) -> DispatchResultWithPostInfo;1286 fn burn_item(1287 &self,1288 sender: T::CrossAccountId,1289 token: TokenId,1290 amount: u128,1291 ) -> DispatchResultWithPostInfo;1292 fn burn_item_recursively(1293 &self,1294 sender: T::CrossAccountId,1295 token: TokenId,1296 self_budget: &dyn Budget,1297 breadth_budget: &dyn Budget,1298 ) -> DispatchResultWithPostInfo;1299 fn set_collection_properties(1300 &self,1301 sender: T::CrossAccountId,1302 properties: Vec<Property>,1303 ) -> DispatchResultWithPostInfo;1304 fn delete_collection_properties(1305 &self,1306 sender: &T::CrossAccountId,1307 property_keys: Vec<PropertyKey>,1308 ) -> DispatchResultWithPostInfo;1309 fn set_token_properties(1310 &self,1311 sender: T::CrossAccountId,1312 token_id: TokenId,1313 property: Vec<Property>,1314 nesting_budget: &dyn Budget,1315 ) -> DispatchResultWithPostInfo;1316 fn delete_token_properties(1317 &self,1318 sender: T::CrossAccountId,1319 token_id: TokenId,1320 property_keys: Vec<PropertyKey>,1321 nesting_budget: &dyn Budget,1322 ) -> DispatchResultWithPostInfo;1323 fn set_token_property_permissions(1324 &self,1325 sender: &T::CrossAccountId,1326 property_permissions: Vec<PropertyKeyPermission>,1327 ) -> DispatchResultWithPostInfo;1328 fn transfer(1329 &self,1330 sender: T::CrossAccountId,1331 to: T::CrossAccountId,1332 token: TokenId,1333 amount: u128,1334 nesting_budget: &dyn Budget,1335 ) -> DispatchResultWithPostInfo;1336 fn approve(1337 &self,1338 sender: T::CrossAccountId,1339 spender: T::CrossAccountId,1340 token: TokenId,1341 amount: u128,1342 ) -> DispatchResultWithPostInfo;1343 fn transfer_from(1344 &self,1345 sender: T::CrossAccountId,1346 from: T::CrossAccountId,1347 to: T::CrossAccountId,1348 token: TokenId,1349 amount: u128,1350 nesting_budget: &dyn Budget,1351 ) -> DispatchResultWithPostInfo;1352 fn burn_from(1353 &self,1354 sender: T::CrossAccountId,1355 from: T::CrossAccountId,1356 token: TokenId,1357 amount: u128,1358 nesting_budget: &dyn Budget,1359 ) -> DispatchResultWithPostInfo;13601361 fn check_nesting(1362 &self,1363 sender: T::CrossAccountId,1364 from: (CollectionId, TokenId),1365 under: TokenId,1366 nesting_budget: &dyn Budget,1367 ) -> DispatchResult;13681369 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));13701371 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));13721373 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1374 fn collection_tokens(&self) -> Vec<TokenId>;1375 fn token_exists(&self, token: TokenId) -> bool;1376 fn last_token_id(&self) -> TokenId;13771378 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1379 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;1380 fn token_properties(&self, token_id: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;1381 /// Amount of unique collection tokens1382 fn total_supply(&self) -> u32;1383 /// Amount of different tokens account has (Applicable to nonfungible/refungible)1384 fn account_balance(&self, account: T::CrossAccountId) -> u32;1385 /// Amount of specific token account have (Applicable to fungible/refungible)1386 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1387 /// Amount of token pieces1388 fn total_pieces(&self, token: TokenId) -> Option<u128>;1389 fn allowance(1390 &self,1391 sender: T::CrossAccountId,1392 spender: T::CrossAccountId,1393 token: TokenId,1394 ) -> u128;1395 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;1396}13971398pub trait RefungibleExtensions<T>1399where1400 T: Config,1401{1402 fn repartition(1403 &self,1404 owner: &T::CrossAccountId,1405 token: TokenId,1406 amount: u128,1407 ) -> DispatchResultWithPostInfo;1408}14091410// Flexible enough for implementing CommonCollectionOperations1411pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1412 let post_info = PostDispatchInfo {1413 actual_weight: Some(weight),1414 pays_fee: Pays::Yes,1415 };1416 match res {1417 Ok(()) => Ok(post_info),1418 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1419 }1420}14211422impl<T: Config> From<PropertiesError> for Error<T> {1423 fn from(error: PropertiesError) -> Self {1424 match error {1425 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1426 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1427 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1428 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1429 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1430 }1431 }1432}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, Pallet as PalletEvm};25use evm_coder::ToLog;26use frame_support::{27 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},28 ensure,29 traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},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 TokenChild,44 CollectionStats,45 MAX_TOKEN_OWNERSHIP,46 CollectionMode,47 NFT_SPONSOR_TRANSFER_TIMEOUT,48 FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,49 REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,50 MAX_SPONSOR_TIMEOUT,51 CUSTOM_DATA_LIMIT,52 CollectionLimits,53 CreateCollectionData,54 SponsorshipState,55 CreateItemExData,56 SponsoringRateLimit,57 budget::Budget,58 PhantomType,59 Property,60 Properties,61 PropertiesPermissionMap,62 PropertyKey,63 PropertyValue,64 PropertyPermission,65 PropertiesError,66 PropertyKeyPermission,67 TokenData,68 TrySetProperty,69 PropertyScope,70 // RMRK71 RmrkCollectionInfo,72 RmrkInstanceInfo,73 RmrkResourceInfo,74 RmrkPropertyInfo,75 RmrkBaseInfo,76 RmrkPartType,77 RmrkBoundedTheme,78 RmrkNftChild,79 CollectionPermissions,80 SchemaVersion,81};8283pub use pallet::*;84use sp_core::H160;85use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};86#[cfg(feature = "runtime-benchmarks")]87pub mod benchmarking;88pub mod dispatch;89pub mod erc;90pub mod eth;91pub mod weights;9293pub type SelfWeightOf<T> = <T as Config>::WeightInfo;9495#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]96pub struct CollectionHandle<T: Config> {97 pub id: CollectionId,98 collection: Collection<T::AccountId>,99 pub recorder: SubstrateRecorder<T>,100}101impl<T: Config> WithRecorder<T> for CollectionHandle<T> {102 fn recorder(&self) -> &SubstrateRecorder<T> {103 &self.recorder104 }105 fn into_recorder(self) -> SubstrateRecorder<T> {106 self.recorder107 }108}109impl<T: Config> CollectionHandle<T> {110 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {111 <CollectionById<T>>::get(id).map(|collection| Self {112 id,113 collection,114 recorder: SubstrateRecorder::new(gas_limit),115 })116 }117118 pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder<T>) -> Option<Self> {119 <CollectionById<T>>::get(id).map(|collection| Self {120 id,121 collection,122 recorder,123 })124 }125126 pub fn new(id: CollectionId) -> Option<Self> {127 Self::new_with_gas_limit(id, u64::MAX)128 }129130 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {131 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)132 }133134 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {135 self.recorder136 .consume_gas(T::GasWeightMapping::weight_to_gas(137 <T as frame_system::Config>::DbWeight::get()138 .read139 .saturating_mul(reads),140 ))141 }142143 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {144 self.recorder145 .consume_gas(T::GasWeightMapping::weight_to_gas(146 <T as frame_system::Config>::DbWeight::get()147 .write148 .saturating_mul(writes),149 ))150 }151 pub fn save(self) -> DispatchResult {152 <CollectionById<T>>::insert(self.id, self.collection);153 Ok(())154 }155156 pub fn set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {157 self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor);158 Ok(())159 }160161 pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> Result<bool, DispatchError> {162 if self.collection.sponsorship.pending_sponsor() != Some(sender) {163 return Ok(false);164 }165166 self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());167 Ok(true)168 }169170 /// Checks that the collection was created with, and must be operated upon through **Unique API**.171 /// Now check only the `external_collection` flag and if it's **true**, then return `CollectionIsExternal` error.172 pub fn check_is_internal(&self) -> DispatchResult {173 if self.external_collection {174 return Err(<Error<T>>::CollectionIsExternal)?;175 }176177 Ok(())178 }179180 /// Checks that the collection was created with, and must be operated upon through an **assimilated API**.181 /// Now check only the `external_collection` flag and if it's **false**, then return `CollectionIsInternal` error.182 pub fn check_is_external(&self) -> DispatchResult {183 if !self.external_collection {184 return Err(<Error<T>>::CollectionIsInternal)?;185 }186187 Ok(())188 }189}190191impl<T: Config> Deref for CollectionHandle<T> {192 type Target = Collection<T::AccountId>;193194 fn deref(&self) -> &Self::Target {195 &self.collection196 }197}198199impl<T: Config> DerefMut for CollectionHandle<T> {200 fn deref_mut(&mut self) -> &mut Self::Target {201 &mut self.collection202 }203}204205impl<T: Config> CollectionHandle<T> {206 pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {207 ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);208 Ok(())209 }210 pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {211 *subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))212 }213 pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {214 ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);215 Ok(())216 }217 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {218 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)219 }220 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {221 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)222 }223 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {224 ensure!(225 <Allowlist<T>>::get((self.id, user)),226 <Error<T>>::AddressNotInAllowlist227 );228 Ok(())229 }230}231232#[frame_support::pallet]233pub mod pallet {234 use super::*;235 use pallet_evm::account;236 use dispatch::CollectionDispatch;237 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};238 use frame_system::pallet_prelude::*;239 use frame_support::traits::Currency;240 use up_data_structs::{TokenId, mapping::TokenAddressMapping};241 use scale_info::TypeInfo;242 use weights::WeightInfo;243244 #[pallet::config]245 pub trait Config:246 frame_system::Config247 + pallet_evm_coder_substrate::Config248 + pallet_evm::Config249 + TypeInfo250 + account::Config251 {252 type WeightInfo: WeightInfo;253 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;254255 type Currency: Currency<Self::AccountId>;256257 #[pallet::constant]258 type CollectionCreationPrice: Get<259 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,260 >;261 type CollectionDispatch: CollectionDispatch<Self>;262263 type TreasuryAccountId: Get<Self::AccountId>;264 type ContractAddress: Get<H160>;265266 type EvmTokenAddressMapping: TokenAddressMapping<H160>;267 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;268 }269270 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);271272 #[pallet::pallet]273 #[pallet::storage_version(STORAGE_VERSION)]274 #[pallet::generate_store(pub(super) trait Store)]275 pub struct Pallet<T>(_);276277 #[pallet::extra_constants]278 impl<T: Config> Pallet<T> {279 pub fn collection_admins_limit() -> u32 {280 COLLECTION_ADMINS_LIMIT281 }282 }283284 #[pallet::event]285 #[pallet::generate_deposit(pub fn deposit_event)]286 pub enum Event<T: Config> {287 /// New collection was created288 ///289 /// # Arguments290 ///291 /// * collection_id: Globally unique identifier of newly created collection.292 ///293 /// * mode: [CollectionMode] converted into u8.294 ///295 /// * account_id: Collection owner.296 CollectionCreated(CollectionId, u8, T::AccountId),297298 /// New collection was destroyed299 ///300 /// # Arguments301 ///302 /// * collection_id: Globally unique identifier of collection that has been destroyed.303 CollectionDestroyed(CollectionId),304305 /// New item was created.306 ///307 /// # Arguments308 ///309 /// * collection_id: ID of the collection where the item was created.310 ///311 /// * item_id: ID of the item. Unique within the collection.312 ///313 /// * recipient: Owner of the newly created item.314 ///315 /// * amount: The amount of tokens that were created (always 1 for NFT).316 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),317318 /// Collection item was burned.319 ///320 /// # Arguments321 ///322 /// * collection_id: Identifier of the collection to which the burned NFT belonged.323 ///324 /// * item_id: Identifier of burned NFT.325 ///326 /// * owner: Which user has destroyed their tokens.327 ///328 /// * amount: The amount of tokens that were destroyed (always 1 for NFT).329 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),330331 /// Item was transferred.332 /// 333 /// # Arguments334 ///335 /// * collection_id: ID of the collection to which the item belongs.336 ///337 /// * item_id: ID of the item trasnferred.338 ///339 /// * sender: Original owner of the item.340 ///341 /// * recipient: New owner of the item.342 ///343 /// * amount: The amount of tokens that were transferred (always 1 for NFT).344 Transfer(345 CollectionId,346 TokenId,347 T::CrossAccountId,348 T::CrossAccountId,349 u128,350 ),351352 /// Sponsoring allowance was approved.353 /// 354 /// # Arguments355 /// 356 /// * collection_id357 ///358 /// * item_id359 ///360 /// * sender361 ///362 /// * spender363 ///364 /// * amount365 Approved(366 CollectionId,367 TokenId,368 T::CrossAccountId,369 T::CrossAccountId,370 u128,371 ),372373 /// Collection property was added or edited.374 /// 375 /// # Arguments376 /// 377 /// * collection_id: ID of the collection, whose property was just set.378 /// 379 /// * property_key: Key of the property that was just set.380 CollectionPropertySet(CollectionId, PropertyKey),381382 /// Collection property was deleted.383 /// 384 /// # Arguments385 /// 386 /// * collection_id: ID of the collection, whose property was just deleted.387 /// 388 /// * property_key: Key of the property that was just deleted.389 CollectionPropertyDeleted(CollectionId, PropertyKey),390391 /// Item property was added or edited.392 /// 393 /// # Arguments394 /// 395 /// * collection_id: ID of the collection, whose token's property was just set.396 /// 397 /// * item_id: ID of the item, whose property was just set.398 /// 399 /// * property_key: Key of the property that was just set.400 TokenPropertySet(CollectionId, TokenId, PropertyKey),401402 /// Item property was deleted.403 /// 404 /// # Arguments405 /// 406 /// * collection_id: ID of the collection, whose token's property was just deleted.407 /// 408 /// * item_id: ID of the item, whose property was just deleted.409 /// 410 /// * property_key: Key of the property that was just deleted.411 TokenPropertyDeleted(CollectionId, TokenId, PropertyKey),412413 /// Token property permission was added or updated for a collection.414 /// 415 /// # Arguments416 /// 417 /// * collection_id: ID of the collection, whose permissions were just set/updated.418 /// 419 /// * property_key: Key of the property of the set/updated permission.420 PropertyPermissionSet(CollectionId, PropertyKey),421 }422423 #[pallet::error]424 pub enum Error<T> {425 /// This collection does not exist.426 CollectionNotFound,427 /// Sender parameter and item owner must be equal.428 MustBeTokenOwner,429 /// No permission to perform action430 NoPermission,431 /// Destroying only empty collections is allowed432 CantDestroyNotEmptyCollection,433 /// Collection is not in mint mode.434 PublicMintingNotAllowed,435 /// Address is not in allow list.436 AddressNotInAllowlist,437438 /// Collection name can not be longer than 63 char.439 CollectionNameLimitExceeded,440 /// Collection description can not be longer than 255 char.441 CollectionDescriptionLimitExceeded,442 /// Token prefix can not be longer than 15 char.443 CollectionTokenPrefixLimitExceeded,444 /// Total collections bound exceeded.445 TotalCollectionsLimitExceeded,446 /// Exceeded max admin count447 CollectionAdminCountExceeded,448 /// Collection limit bounds per collection exceeded449 CollectionLimitBoundsExceeded,450 /// Tried to enable permissions which are only permitted to be disabled451 OwnerPermissionsCantBeReverted,452 /// Collection settings not allowing items transferring453 TransferNotAllowed,454 /// Account token limit exceeded per collection455 AccountTokenLimitExceeded,456 /// Collection token limit exceeded457 CollectionTokenLimitExceeded,458 /// Metadata flag frozen459 MetadataFlagFrozen,460461 /// Item does not exist462 TokenNotFound,463 /// Item is balance not enough464 TokenValueTooLow,465 /// Requested value is more than the approved466 ApprovedValueTooLow,467 /// Tried to approve more than owned468 CantApproveMoreThanOwned,469470 /// Can't transfer tokens to ethereum zero address471 AddressIsZero,472 /// Target collection doesn't support this operation473 UnsupportedOperation,474475 /// Insufficient funds to perform an action476 NotSufficientFounds,477478 /// User does not satisfy the nesting rule479 UserIsNotAllowedToNest,480 /// Only tokens from specific collections may nest tokens under this one481 SourceCollectionIsNotAllowedToNest,482483 /// Tried to store more data than allowed in collection field484 CollectionFieldSizeExceeded,485486 /// Tried to store more property data than allowed487 NoSpaceForProperty,488489 /// Tried to store more property keys than allowed490 PropertyLimitReached,491492 /// Property key is too long493 PropertyKeyIsTooLong,494495 /// Only ASCII letters, digits, and symbols '_', '-', and '.' are allowed496 InvalidCharacterInPropertyKey,497498 /// Empty property keys are forbidden499 EmptyPropertyKey,500501 /// Tried to access an external collection with an internal API502 CollectionIsExternal,503504 /// Tried to access an internal collection with an external API505 CollectionIsInternal,506 }507508 /// The number of created collections. Essentially contains the last collection ID.509 #[pallet::storage]510 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;511512 /// The number of destroyed collections513 #[pallet::storage]514 pub type DestroyedCollectionCount<T> =515 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;516517 /// Collection info518 #[pallet::storage]519 pub type CollectionById<T> = StorageMap<520 Hasher = Blake2_128Concat,521 Key = CollectionId,522 Value = Collection<<T as frame_system::Config>::AccountId>,523 QueryKind = OptionQuery,524 >;525526 /// Collection properties527 #[pallet::storage]528 #[pallet::getter(fn collection_properties)]529 pub type CollectionProperties<T> = StorageMap<530 Hasher = Blake2_128Concat,531 Key = CollectionId,532 Value = Properties,533 QueryKind = ValueQuery,534 OnEmpty = up_data_structs::CollectionProperties,535 >;536537 /// Token permissions of a collection538 #[pallet::storage]539 #[pallet::getter(fn property_permissions)]540 pub type CollectionPropertyPermissions<T> = StorageMap<541 Hasher = Blake2_128Concat,542 Key = CollectionId,543 Value = PropertiesPermissionMap,544 QueryKind = ValueQuery,545 >;546547 /// Amount of collection admins548 #[pallet::storage]549 pub type AdminAmount<T> = StorageMap<550 Hasher = Blake2_128Concat,551 Key = CollectionId,552 Value = u32,553 QueryKind = ValueQuery,554 >;555556 /// List of collection admins557 #[pallet::storage]558 pub type IsAdmin<T: Config> = StorageNMap<559 Key = (560 Key<Blake2_128Concat, CollectionId>,561 Key<Blake2_128Concat, T::CrossAccountId>,562 ),563 Value = bool,564 QueryKind = ValueQuery,565 >;566567 /// Allowlisted collection users568 #[pallet::storage]569 pub type Allowlist<T: Config> = StorageNMap<570 Key = (571 Key<Blake2_128Concat, CollectionId>,572 Key<Blake2_128Concat, T::CrossAccountId>,573 ),574 Value = bool,575 QueryKind = ValueQuery,576 >;577578 /// Not used by code, exists only to provide some types to metadata579 #[pallet::storage]580 pub type DummyStorageValue<T: Config> = StorageValue<581 Value = (582 CollectionStats,583 CollectionId,584 TokenId,585 TokenChild,586 PhantomType<(587 TokenData<T::CrossAccountId>,588 RpcCollection<T::AccountId>,589 // RMRK590 RmrkCollectionInfo<T::AccountId>,591 RmrkInstanceInfo<T::AccountId>,592 RmrkResourceInfo,593 RmrkPropertyInfo,594 RmrkBaseInfo<T::AccountId>,595 RmrkPartType,596 RmrkBoundedTheme,597 RmrkNftChild,598 )>,599 ),600 QueryKind = OptionQuery,601 >;602603 #[pallet::hooks]604 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {605 fn on_runtime_upgrade() -> Weight {606 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {607 use up_data_structs::{CollectionVersion1, CollectionVersion2};608 <CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {609 let mut props = Vec::new();610 if !v.offchain_schema.is_empty() {611 props.push(Property {612 key: b"_old_offchainSchema".to_vec().try_into().unwrap(),613 value: v614 .offchain_schema615 .clone()616 .into_inner()617 .try_into()618 .expect("offchain schema too big"),619 });620 }621 if !v.variable_on_chain_schema.is_empty() {622 props.push(Property {623 key: b"_old_variableOnChainSchema".to_vec().try_into().unwrap(),624 value: v625 .variable_on_chain_schema626 .clone()627 .into_inner()628 .try_into()629 .expect("offchain schema too big"),630 });631 }632 if !v.const_on_chain_schema.is_empty() {633 props.push(Property {634 key: b"_old_constOnChainSchema".to_vec().try_into().unwrap(),635 value: v636 .const_on_chain_schema637 .clone()638 .into_inner()639 .try_into()640 .expect("offchain schema too big"),641 });642 }643 props.push(Property {644 key: b"_old_schemaVersion".to_vec().try_into().unwrap(),645 value: match v.schema_version {646 SchemaVersion::ImageURL => b"ImageUrl".as_slice(),647 SchemaVersion::Unique => b"Unique".as_slice(),648 }649 .to_vec()650 .try_into()651 .unwrap(),652 });653 Self::set_scoped_collection_properties(654 id,655 PropertyScope::None,656 props.into_iter(),657 )658 .expect("existing data larger than properties");659 let mut new = CollectionVersion2::from(v.clone());660 new.permissions.access = Some(v.access);661 new.permissions.mint_mode = Some(v.mint_mode);662 Some(new)663 });664 }665666 0667 }668 }669}670671impl<T: Config> Pallet<T> {672 /// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens673 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {674 ensure!(675 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,676 <Error<T>>::AddressIsZero677 );678 Ok(())679 }680 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {681 <IsAdmin<T>>::iter_prefix((collection,))682 .map(|(a, _)| a)683 .collect()684 }685 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {686 <Allowlist<T>>::iter_prefix((collection,))687 .map(|(a, _)| a)688 .collect()689 }690 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {691 <Allowlist<T>>::get((collection, user))692 }693 pub fn collection_stats() -> CollectionStats {694 let created = <CreatedCollectionCount<T>>::get();695 let destroyed = <DestroyedCollectionCount<T>>::get();696 CollectionStats {697 created: created.0,698 destroyed: destroyed.0,699 alive: created.0 - destroyed.0,700 }701 }702703 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {704 let collection = <CollectionById<T>>::get(collection);705 if collection.is_none() {706 return None;707 }708709 let collection = collection.unwrap();710 let limits = collection.limits;711 let effective_limits = CollectionLimits {712 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),713 sponsored_data_size: Some(limits.sponsored_data_size()),714 sponsored_data_rate_limit: Some(715 limits716 .sponsored_data_rate_limit717 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),718 ),719 token_limit: Some(limits.token_limit()),720 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(721 match collection.mode {722 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,723 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,724 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,725 },726 )),727 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),728 owner_can_transfer: Some(limits.owner_can_transfer()),729 owner_can_destroy: Some(limits.owner_can_destroy()),730 transfers_enabled: Some(limits.transfers_enabled()),731 };732733 Some(effective_limits)734 }735736 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {737 let Collection {738 name,739 description,740 owner,741 mode,742 token_prefix,743 sponsorship,744 limits,745 permissions,746 external_collection,747 } = <CollectionById<T>>::get(collection)?;748749 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)750 .into_iter()751 .map(|(key, permission)| PropertyKeyPermission { key, permission })752 .collect();753754 let properties = <CollectionProperties<T>>::get(collection)755 .into_iter()756 .map(|(key, value)| Property { key, value })757 .collect();758759 let permissions = CollectionPermissions {760 access: Some(permissions.access()),761 mint_mode: Some(permissions.mint_mode()),762 nesting: Some(permissions.nesting().clone()),763 };764765 Some(RpcCollection {766 name: name.into_inner(),767 description: description.into_inner(),768 owner,769 mode,770 token_prefix: token_prefix.into_inner(),771 sponsorship,772 limits,773 permissions,774 token_property_permissions,775 properties,776 read_only: external_collection,777 })778 }779}780781macro_rules! limit_default {782 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{783 $(784 if let Some($new) = $new.$field {785 let $old = $old.$field($($arg)?);786 let _ = $new;787 let _ = $old;788 $check789 } else {790 $new.$field = $old.$field791 }792 )*793 }};794}795macro_rules! limit_default_clone {796 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{797 $(798 if let Some($new) = $new.$field.clone() {799 let $old = $old.$field($($arg)?);800 let _ = $new;801 let _ = $old;802 $check803 } else {804 $new.$field = $old.$field.clone()805 }806 )*807 }};808}809810impl<T: Config> Pallet<T> {811 pub fn init_collection(812 owner: T::CrossAccountId,813 data: CreateCollectionData<T::AccountId>,814 is_external: bool,815 ) -> Result<CollectionId, DispatchError> {816 {817 ensure!(818 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,819 Error::<T>::CollectionTokenPrefixLimitExceeded820 );821 }822823 let created_count = <CreatedCollectionCount<T>>::get()824 .0825 .checked_add(1)826 .ok_or(ArithmeticError::Overflow)?;827 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;828 let id = CollectionId(created_count);829830 // bound Total number of collections831 ensure!(832 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,833 <Error<T>>::TotalCollectionsLimitExceeded834 );835836 // =========837838 let collection = Collection {839 owner: owner.as_sub().clone(),840 name: data.name,841 mode: data.mode.clone(),842 description: data.description,843 token_prefix: data.token_prefix,844 sponsorship: data845 .pending_sponsor846 .map(SponsorshipState::Unconfirmed)847 .unwrap_or_default(),848 limits: data849 .limits850 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))851 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,852 permissions: data853 .permissions854 .map(|permissions| {855 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)856 })857 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,858 external_collection: is_external,859 };860861 let mut collection_properties = up_data_structs::CollectionProperties::get();862 collection_properties863 .try_set_from_iter(data.properties.into_iter())864 .map_err(<Error<T>>::from)?;865866 CollectionProperties::<T>::insert(id, collection_properties);867868 let mut token_props_permissions = PropertiesPermissionMap::new();869 token_props_permissions870 .try_set_from_iter(data.token_property_permissions.into_iter())871 .map_err(<Error<T>>::from)?;872873 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);874875 // Take a (non-refundable) deposit of collection creation876 {877 let mut imbalance =878 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();879 imbalance.subsume(880 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(881 &T::TreasuryAccountId::get(),882 T::CollectionCreationPrice::get(),883 ),884 );885 <T as Config>::Currency::settle(886 &owner.as_sub(),887 imbalance,888 WithdrawReasons::TRANSFER,889 ExistenceRequirement::KeepAlive,890 )891 .map_err(|_| Error::<T>::NotSufficientFounds)?;892 }893894 <CreatedCollectionCount<T>>::put(created_count);895 <Pallet<T>>::deposit_event(Event::CollectionCreated(896 id,897 data.mode.id(),898 owner.as_sub().clone(),899 ));900 <PalletEvm<T>>::deposit_log(901 erc::CollectionHelpersEvents::CollectionCreated {902 owner: *owner.as_eth(),903 collection_id: eth::collection_id_to_address(id),904 }905 .to_log(T::ContractAddress::get()),906 );907 <CollectionById<T>>::insert(id, collection);908 Ok(id)909 }910911 pub fn destroy_collection(912 collection: CollectionHandle<T>,913 sender: &T::CrossAccountId,914 ) -> DispatchResult {915 ensure!(916 collection.limits.owner_can_destroy(),917 <Error<T>>::NoPermission,918 );919 collection.check_is_owner(sender)?;920921 let destroyed_collections = <DestroyedCollectionCount<T>>::get()922 .0923 .checked_add(1)924 .ok_or(ArithmeticError::Overflow)?;925926 // =========927928 <DestroyedCollectionCount<T>>::put(destroyed_collections);929 <CollectionById<T>>::remove(collection.id);930 <AdminAmount<T>>::remove(collection.id);931 <IsAdmin<T>>::remove_prefix((collection.id,), None);932 <Allowlist<T>>::remove_prefix((collection.id,), None);933 <CollectionProperties<T>>::remove(collection.id);934935 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));936 Ok(())937 }938939 pub fn set_collection_property(940 collection: &CollectionHandle<T>,941 sender: &T::CrossAccountId,942 property: Property,943 ) -> DispatchResult {944 collection.check_is_owner_or_admin(sender)?;945946 CollectionProperties::<T>::try_mutate(collection.id, |properties| {947 let property = property.clone();948 properties.try_set(property.key, property.value)949 })950 .map_err(<Error<T>>::from)?;951952 Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));953954 Ok(())955 }956957 pub fn set_scoped_collection_property(958 collection_id: CollectionId,959 scope: PropertyScope,960 property: Property,961 ) -> DispatchResult {962 CollectionProperties::<T>::try_mutate(collection_id, |properties| {963 properties.try_scoped_set(scope, property.key, property.value)964 })965 .map_err(<Error<T>>::from)?;966967 Ok(())968 }969970 pub fn set_scoped_collection_properties(971 collection_id: CollectionId,972 scope: PropertyScope,973 properties: impl Iterator<Item = Property>,974 ) -> DispatchResult {975 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {976 stored_properties.try_scoped_set_from_iter(scope, properties)977 })978 .map_err(<Error<T>>::from)?;979980 Ok(())981 }982983 #[transactional]984 pub fn set_collection_properties(985 collection: &CollectionHandle<T>,986 sender: &T::CrossAccountId,987 properties: Vec<Property>,988 ) -> DispatchResult {989 for property in properties {990 Self::set_collection_property(collection, sender, property)?;991 }992993 Ok(())994 }995996 pub fn delete_collection_property(997 collection: &CollectionHandle<T>,998 sender: &T::CrossAccountId,999 property_key: PropertyKey,1000 ) -> DispatchResult {1001 collection.check_is_owner_or_admin(sender)?;10021003 CollectionProperties::<T>::try_mutate(collection.id, |properties| {1004 properties.remove(&property_key)1005 })1006 .map_err(<Error<T>>::from)?;10071008 Self::deposit_event(Event::CollectionPropertyDeleted(1009 collection.id,1010 property_key,1011 ));10121013 Ok(())1014 }10151016 #[transactional]1017 pub fn delete_collection_properties(1018 collection: &CollectionHandle<T>,1019 sender: &T::CrossAccountId,1020 property_keys: Vec<PropertyKey>,1021 ) -> DispatchResult {1022 for key in property_keys {1023 Self::delete_collection_property(collection, sender, key)?;1024 }10251026 Ok(())1027 }10281029 // For migrations1030 pub fn set_property_permission_unchecked(1031 collection: CollectionId,1032 property_permission: PropertyKeyPermission,1033 ) -> DispatchResult {1034 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1035 permissions.try_set(property_permission.key, property_permission.permission)1036 })1037 .map_err(<Error<T>>::from)?;1038 Ok(())1039 }10401041 pub fn set_property_permission(1042 collection: &CollectionHandle<T>,1043 sender: &T::CrossAccountId,1044 property_permission: PropertyKeyPermission,1045 ) -> DispatchResult {1046 collection.check_is_owner_or_admin(sender)?;10471048 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1049 let current_permission = all_permissions.get(&property_permission.key);1050 if matches![1051 current_permission,1052 Some(PropertyPermission { mutable: false, .. })1053 ] {1054 return Err(<Error<T>>::NoPermission.into());1055 }10561057 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1058 let property_permission = property_permission.clone();1059 permissions.try_set(property_permission.key, property_permission.permission)1060 })1061 .map_err(<Error<T>>::from)?;10621063 Self::deposit_event(Event::PropertyPermissionSet(1064 collection.id,1065 property_permission.key,1066 ));10671068 Ok(())1069 }10701071 #[transactional]1072 pub fn set_token_property_permissions(1073 collection: &CollectionHandle<T>,1074 sender: &T::CrossAccountId,1075 property_permissions: Vec<PropertyKeyPermission>,1076 ) -> DispatchResult {1077 for prop_pemission in property_permissions {1078 Self::set_property_permission(collection, sender, prop_pemission)?;1079 }10801081 Ok(())1082 }10831084 pub fn get_collection_property(1085 collection_id: CollectionId,1086 key: &PropertyKey,1087 ) -> Option<PropertyValue> {1088 Self::collection_properties(collection_id).get(key).cloned()1089 }10901091 pub fn bytes_keys_to_property_keys(1092 keys: Vec<Vec<u8>>,1093 ) -> Result<Vec<PropertyKey>, DispatchError> {1094 keys.into_iter()1095 .map(|key| -> Result<PropertyKey, DispatchError> {1096 key.try_into()1097 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1098 })1099 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1100 }11011102 pub fn filter_collection_properties(1103 collection_id: CollectionId,1104 keys: Option<Vec<PropertyKey>>,1105 ) -> Result<Vec<Property>, DispatchError> {1106 let properties = Self::collection_properties(collection_id);11071108 let properties = keys1109 .map(|keys| {1110 keys.into_iter()1111 .filter_map(|key| {1112 properties.get(&key).map(|value| Property {1113 key,1114 value: value.clone(),1115 })1116 })1117 .collect()1118 })1119 .unwrap_or_else(|| {1120 properties1121 .into_iter()1122 .map(|(key, value)| Property { key, value })1123 .collect()1124 });11251126 Ok(properties)1127 }11281129 pub fn filter_property_permissions(1130 collection_id: CollectionId,1131 keys: Option<Vec<PropertyKey>>,1132 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1133 let permissions = Self::property_permissions(collection_id);11341135 let key_permissions = keys1136 .map(|keys| {1137 keys.into_iter()1138 .filter_map(|key| {1139 permissions1140 .get(&key)1141 .map(|permission| PropertyKeyPermission {1142 key,1143 permission: permission.clone(),1144 })1145 })1146 .collect()1147 })1148 .unwrap_or_else(|| {1149 permissions1150 .into_iter()1151 .map(|(key, permission)| PropertyKeyPermission { key, permission })1152 .collect()1153 });11541155 Ok(key_permissions)1156 }11571158 pub fn toggle_allowlist(1159 collection: &CollectionHandle<T>,1160 sender: &T::CrossAccountId,1161 user: &T::CrossAccountId,1162 allowed: bool,1163 ) -> DispatchResult {1164 collection.check_is_owner_or_admin(sender)?;11651166 // =========11671168 if allowed {1169 <Allowlist<T>>::insert((collection.id, user), true);1170 } else {1171 <Allowlist<T>>::remove((collection.id, user));1172 }11731174 Ok(())1175 }11761177 pub fn toggle_admin(1178 collection: &CollectionHandle<T>,1179 sender: &T::CrossAccountId,1180 user: &T::CrossAccountId,1181 admin: bool,1182 ) -> DispatchResult {1183 collection.check_is_owner(sender)?;11841185 let was_admin = <IsAdmin<T>>::get((collection.id, user));1186 if was_admin == admin {1187 return Ok(());1188 }1189 let amount = <AdminAmount<T>>::get(collection.id);11901191 if admin {1192 let amount = amount1193 .checked_add(1)1194 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1195 ensure!(1196 amount <= Self::collection_admins_limit(),1197 <Error<T>>::CollectionAdminCountExceeded,1198 );11991200 // =========12011202 <AdminAmount<T>>::insert(collection.id, amount);1203 <IsAdmin<T>>::insert((collection.id, user), true);1204 } else {1205 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1206 <IsAdmin<T>>::remove((collection.id, user));1207 }12081209 Ok(())1210 }12111212 pub fn clamp_limits(1213 mode: CollectionMode,1214 old_limit: &CollectionLimits,1215 mut new_limit: CollectionLimits,1216 ) -> Result<CollectionLimits, DispatchError> {1217 let limits = old_limit;1218 limit_default!(old_limit, new_limit,1219 account_token_ownership_limit => ensure!(1220 new_limit <= MAX_TOKEN_OWNERSHIP,1221 <Error<T>>::CollectionLimitBoundsExceeded,1222 ),1223 sponsored_data_size => ensure!(1224 new_limit <= CUSTOM_DATA_LIMIT,1225 <Error<T>>::CollectionLimitBoundsExceeded,1226 ),12271228 sponsored_data_rate_limit => {},1229 token_limit => ensure!(1230 old_limit >= new_limit && new_limit > 0,1231 <Error<T>>::CollectionTokenLimitExceeded1232 ),12331234 sponsor_transfer_timeout(match mode {1235 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1236 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1237 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1238 }) => ensure!(1239 new_limit <= MAX_SPONSOR_TIMEOUT,1240 <Error<T>>::CollectionLimitBoundsExceeded,1241 ),1242 sponsor_approve_timeout => {},1243 owner_can_transfer => ensure!(1244 !limits.owner_can_transfer_instaled() ||1245 old_limit || !new_limit,1246 <Error<T>>::OwnerPermissionsCantBeReverted,1247 ),1248 owner_can_destroy => ensure!(1249 old_limit || !new_limit,1250 <Error<T>>::OwnerPermissionsCantBeReverted,1251 ),1252 transfers_enabled => {},1253 );1254 Ok(new_limit)1255 }12561257 pub fn clamp_permissions(1258 _mode: CollectionMode,1259 old_limit: &CollectionPermissions,1260 mut new_limit: CollectionPermissions,1261 ) -> Result<CollectionPermissions, DispatchError> {1262 limit_default_clone!(old_limit, new_limit,1263 access => {},1264 mint_mode => {},1265 nesting => { /* todo check for permissive, if only it gets out of benchmarks */ },1266 );1267 Ok(new_limit)1268 }1269}12701271#[macro_export]1272macro_rules! unsupported {1273 () => {1274 Err(<Error<T>>::UnsupportedOperation.into())1275 };1276}12771278/// Worst cases1279pub trait CommonWeightInfo<CrossAccountId> {1280 fn create_item() -> Weight;1281 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;1282 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1283 fn burn_item() -> Weight;1284 fn set_collection_properties(amount: u32) -> Weight;1285 fn delete_collection_properties(amount: u32) -> Weight;1286 fn set_token_properties(amount: u32) -> Weight;1287 fn delete_token_properties(amount: u32) -> Weight;1288 fn set_token_property_permissions(amount: u32) -> Weight;1289 fn transfer() -> Weight;1290 fn approve() -> Weight;1291 fn transfer_from() -> Weight;1292 fn burn_from() -> Weight;12931294 /// Differs from burn_item in case of Fungible and Refungible, as it should burn1295 /// whole users's balance1296 ///1297 /// This method shouldn't be used directly, as it doesn't count breadth price, use `burn_recursively` instead1298 fn burn_recursively_self_raw() -> Weight;1299 /// Cost of iterating over `amount` children while burning, without counting child burning itself1300 ///1301 /// This method shouldn't be used directly, as it doesn't count depth price, use `burn_recursively` instead1302 fn burn_recursively_breadth_raw(amount: u32) -> Weight;13031304 fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1305 Self::burn_recursively_self_raw()1306 .saturating_mul(max_selfs.max(1) as u64)1307 .saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1308 }1309}13101311pub trait RefungibleExtensionsWeightInfo {1312 fn repartition() -> Weight;1313}13141315pub trait CommonCollectionOperations<T: Config> {1316 fn create_item(1317 &self,1318 sender: T::CrossAccountId,1319 to: T::CrossAccountId,1320 data: CreateItemData,1321 nesting_budget: &dyn Budget,1322 ) -> DispatchResultWithPostInfo;1323 fn create_multiple_items(1324 &self,1325 sender: T::CrossAccountId,1326 to: T::CrossAccountId,1327 data: Vec<CreateItemData>,1328 nesting_budget: &dyn Budget,1329 ) -> DispatchResultWithPostInfo;1330 fn create_multiple_items_ex(1331 &self,1332 sender: T::CrossAccountId,1333 data: CreateItemExData<T::CrossAccountId>,1334 nesting_budget: &dyn Budget,1335 ) -> DispatchResultWithPostInfo;1336 fn burn_item(1337 &self,1338 sender: T::CrossAccountId,1339 token: TokenId,1340 amount: u128,1341 ) -> DispatchResultWithPostInfo;1342 fn burn_item_recursively(1343 &self,1344 sender: T::CrossAccountId,1345 token: TokenId,1346 self_budget: &dyn Budget,1347 breadth_budget: &dyn Budget,1348 ) -> DispatchResultWithPostInfo;1349 fn set_collection_properties(1350 &self,1351 sender: T::CrossAccountId,1352 properties: Vec<Property>,1353 ) -> DispatchResultWithPostInfo;1354 fn delete_collection_properties(1355 &self,1356 sender: &T::CrossAccountId,1357 property_keys: Vec<PropertyKey>,1358 ) -> DispatchResultWithPostInfo;1359 fn set_token_properties(1360 &self,1361 sender: T::CrossAccountId,1362 token_id: TokenId,1363 property: Vec<Property>,1364 nesting_budget: &dyn Budget,1365 ) -> DispatchResultWithPostInfo;1366 fn delete_token_properties(1367 &self,1368 sender: T::CrossAccountId,1369 token_id: TokenId,1370 property_keys: Vec<PropertyKey>,1371 nesting_budget: &dyn Budget,1372 ) -> DispatchResultWithPostInfo;1373 fn set_token_property_permissions(1374 &self,1375 sender: &T::CrossAccountId,1376 property_permissions: Vec<PropertyKeyPermission>,1377 ) -> DispatchResultWithPostInfo;1378 fn transfer(1379 &self,1380 sender: T::CrossAccountId,1381 to: T::CrossAccountId,1382 token: TokenId,1383 amount: u128,1384 nesting_budget: &dyn Budget,1385 ) -> DispatchResultWithPostInfo;1386 fn approve(1387 &self,1388 sender: T::CrossAccountId,1389 spender: T::CrossAccountId,1390 token: TokenId,1391 amount: u128,1392 ) -> DispatchResultWithPostInfo;1393 fn transfer_from(1394 &self,1395 sender: T::CrossAccountId,1396 from: T::CrossAccountId,1397 to: T::CrossAccountId,1398 token: TokenId,1399 amount: u128,1400 nesting_budget: &dyn Budget,1401 ) -> DispatchResultWithPostInfo;1402 fn burn_from(1403 &self,1404 sender: T::CrossAccountId,1405 from: T::CrossAccountId,1406 token: TokenId,1407 amount: u128,1408 nesting_budget: &dyn Budget,1409 ) -> DispatchResultWithPostInfo;14101411 fn check_nesting(1412 &self,1413 sender: T::CrossAccountId,1414 from: (CollectionId, TokenId),1415 under: TokenId,1416 nesting_budget: &dyn Budget,1417 ) -> DispatchResult;14181419 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));14201421 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));14221423 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1424 fn collection_tokens(&self) -> Vec<TokenId>;1425 fn token_exists(&self, token: TokenId) -> bool;1426 fn last_token_id(&self) -> TokenId;14271428 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1429 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;1430 fn token_properties(&self, token_id: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;1431 /// Amount of unique collection tokens1432 fn total_supply(&self) -> u32;1433 /// Amount of different tokens account has (Applicable to nonfungible/refungible)1434 fn account_balance(&self, account: T::CrossAccountId) -> u32;1435 /// Amount of specific token account have (Applicable to fungible/refungible)1436 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1437 /// Amount of token pieces1438 fn total_pieces(&self, token: TokenId) -> Option<u128>;1439 fn allowance(1440 &self,1441 sender: T::CrossAccountId,1442 spender: T::CrossAccountId,1443 token: TokenId,1444 ) -> u128;1445 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;1446}14471448pub trait RefungibleExtensions<T>1449where1450 T: Config,1451{1452 fn repartition(1453 &self,1454 owner: &T::CrossAccountId,1455 token: TokenId,1456 amount: u128,1457 ) -> DispatchResultWithPostInfo;1458}14591460// Flexible enough for implementing CommonCollectionOperations1461pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1462 let post_info = PostDispatchInfo {1463 actual_weight: Some(weight),1464 pays_fee: Pays::Yes,1465 };1466 match res {1467 Ok(()) => Ok(post_info),1468 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1469 }1470}14711472impl<T: Config> From<PropertiesError> for Error<T> {1473 fn from(error: PropertiesError) -> Self {1474 match error {1475 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1476 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1477 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1478 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1479 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1480 }1481 }1482}pallets/fungible/src/lib.rsdiffbeforeafterboth--- a/pallets/fungible/src/lib.rs
+++ b/pallets/fungible/src/lib.rs
@@ -44,6 +44,7 @@
pub mod erc;
pub mod weights;
+/// todo:doc?
pub type CreateItemData<T> = (<T as pallet_evm::account::Config>::CrossAccountId, u128);
pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;
@@ -78,10 +79,12 @@
#[pallet::generate_store(pub(super) trait Store)]
pub struct Pallet<T>(_);
+ /// Total amount of fungible tokens inside a collection.
#[pallet::storage]
pub type TotalSupply<T: Config> =
StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u128, QueryKind = ValueQuery>;
+ /// Amount of tokens owned by an account inside a collection.
#[pallet::storage]
pub type Balance<T: Config> = StorageNMap<
Key = (
@@ -92,6 +95,7 @@
QueryKind = ValueQuery,
>;
+ /// todo:doc
#[pallet::storage]
pub type Allowance<T: Config> = StorageNMap<
Key = (
pallets/nonfungible/src/lib.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -56,6 +56,8 @@
pub type CreateItemData<T> = CreateNftExData<<T as pallet_evm::account::Config>::CrossAccountId>;
pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;
+/// Token data, stored independently from other data used to describe it.
+/// Notably contains the owner account address.
#[struct_versioning::versioned(version = 2, upper)]
#[derive(Encode, Decode, TypeInfo, MaxEncodedLen)]
pub struct ItemData<CrossAccountId> {
@@ -102,13 +104,17 @@
#[pallet::generate_store(pub(super) trait Store)]
pub struct Pallet<T>(_);
+ /// Total amount of minted tokens in a collection.
#[pallet::storage]
pub type TokensMinted<T: Config> =
StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;
+
+ /// Amount of burnt tokens in a collection.
#[pallet::storage]
pub type TokensBurnt<T: Config> =
StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;
+ /// Token data, used to partially describe a token.
#[pallet::storage]
pub type TokenData<T: Config> = StorageNMap<
Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),
@@ -116,6 +122,7 @@
QueryKind = OptionQuery,
>;
+ /// Key-value pairs, describing the metadata of a token.
#[pallet::storage]
#[pallet::getter(fn token_properties)]
pub type TokenProperties<T: Config> = StorageNMap<
@@ -125,6 +132,7 @@
OnEmpty = up_data_structs::TokenProperties,
>;
+ /// Scoped, auxiliary properties of a token, primarily used for on-chain operations.
#[pallet::storage]
#[pallet::getter(fn token_aux_property)]
pub type TokenAuxProperties<T: Config> = StorageNMap<
@@ -138,7 +146,7 @@
QueryKind = OptionQuery,
>;
- /// Used to enumerate tokens owned by account
+ /// Used to enumerate tokens owned by account.
#[pallet::storage]
pub type Owned<T: Config> = StorageNMap<
Key = (
@@ -150,7 +158,7 @@
QueryKind = ValueQuery,
>;
- /// Used to enumerate token's children
+ /// Used to enumerate token's children.
#[pallet::storage]
#[pallet::getter(fn token_children)]
pub type TokenChildren<T: Config> = StorageNMap<
@@ -163,6 +171,7 @@
QueryKind = ValueQuery,
>;
+ /// Amount of tokens owned in a collection.s
#[pallet::storage]
pub type AccountBalance<T: Config> = StorageNMap<
Key = (
@@ -173,6 +182,7 @@
QueryKind = ValueQuery,
>;
+ /// todo doc
#[pallet::storage]
pub type Allowance<T: Config> = StorageNMap<
Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),
@@ -180,6 +190,7 @@
QueryKind = OptionQuery,
>;
+ /// Upgrade from the old schema to properties.
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn on_runtime_upgrade() -> Weight {
pallets/refungible/src/lib.rsdiffbeforeafterboth--- a/pallets/refungible/src/lib.rs
+++ b/pallets/refungible/src/lib.rs
@@ -38,6 +38,8 @@
pub mod weights;
pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;
+/// Token data, stored independently from other data used to describe it.
+/// Notably contains the token metadata.
#[struct_versioning::versioned(version = 2, upper)]
#[derive(Encode, Decode, Default, TypeInfo, MaxEncodedLen)]
pub struct ItemData {
@@ -86,13 +88,17 @@
#[pallet::generate_store(pub(super) trait Store)]
pub struct Pallet<T>(_);
+ /// Total amount of minted tokens in a collection.
#[pallet::storage]
pub type TokensMinted<T: Config> =
StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;
+
+ /// Amount of tokens burnt in a collection.
#[pallet::storage]
pub type TokensBurnt<T: Config> =
StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;
+ /// Token data, used to partially describe a token.
#[pallet::storage]
pub type TokenData<T: Config> = StorageNMap<
Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),
@@ -100,6 +106,7 @@
QueryKind = ValueQuery,
>;
+ /// Amount of pieces a refungible token is split into.
#[pallet::storage]
pub type TotalSupply<T: Config> = StorageNMap<
Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),
@@ -107,7 +114,7 @@
QueryKind = ValueQuery,
>;
- /// Used to enumerate tokens owned by account
+ /// Used to enumerate tokens owned by account.
#[pallet::storage]
pub type Owned<T: Config> = StorageNMap<
Key = (
@@ -119,6 +126,7 @@
QueryKind = ValueQuery,
>;
+ /// Amount of tokens (not pieces) partially owned by an account within a collection.
#[pallet::storage]
pub type AccountBalance<T: Config> = StorageNMap<
Key = (
@@ -130,6 +138,7 @@
QueryKind = ValueQuery,
>;
+ /// Amount of pieces of a token owned by an account.
#[pallet::storage]
pub type Balance<T: Config> = StorageNMap<
Key = (
@@ -142,6 +151,7 @@
QueryKind = ValueQuery,
>;
+ /// todo:doc
#[pallet::storage]
pub type Allowance<T: Config> = StorageNMap<
Key = (
@@ -248,7 +258,7 @@
// TODO: ERC721 transfer event
Ok(())
}
-
+
pub fn burn(
collection: &RefungibleHandle<T>,
owner: &T::CrossAccountId,
@@ -595,6 +605,7 @@
Ok(())
}
+ /// todo:doc oh look, a precedent. not pub, too. but it has an unclear use-case.
/// Returns allowance, which should be set after transaction
fn check_allowed(
collection: &RefungibleHandle<T>,
pallets/scheduler/src/lib.rsdiffbeforeafterboth--- a/pallets/scheduler/src/lib.rs
+++ b/pallets/scheduler/src/lib.rs
@@ -258,6 +258,7 @@
/// A Scheduler-Runtime interface for finer payment handling.
pub trait DispatchCall<T: frame_system::Config + Config, SelfContainedSignedInfo> {
+ /// Reserve the maximum spendings on a call.
fn reserve_balance(
id: ScheduledId,
sponsor: <T as frame_system::Config>::AccountId,
@@ -265,6 +266,7 @@
count: u32,
) -> Result<(), DispatchError>;
+ /// Pay for call dispatch (un-reserve) from the reserved funds, returning the change.
fn pay_for_call(
id: ScheduledId,
sponsor: <T as frame_system::Config>::AccountId,
@@ -280,6 +282,7 @@
TransactionValidityError,
>;
+ /// Release reserved funds.
fn cancel_reserve(
id: ScheduledId,
sponsor: <T as frame_system::Config>::AccountId,
pallets/structure/src/lib.rsdiffbeforeafterboth--- a/pallets/structure/src/lib.rs
+++ b/pallets/structure/src/lib.rs
@@ -25,19 +25,19 @@
#[pallet::error]
pub enum Error<T> {
- /// While searched for owner, got already checked account
+ /// While searching for the owner, encountered an already checked account, detecting a loop.
OuroborosDetected,
- /// While searched for owner, encountered depth limit
+ /// While searching for the owner, reached the depth limit.
DepthLimit,
- /// While iterating over children, encountered breadth limit
+ /// While iterating over children, reached the breadth limit.
BreadthLimit,
- /// While searched for owner, found token owner by not-yet-existing token
+ /// Couldn't find the token owner that is a token. Perhaps, it does not yet exist. todo:doc? rephrase?
TokenNotFound,
}
#[pallet::event]
pub enum Event<T> {
- /// Executed call on behalf of token
+ /// Executed call on behalf of the token.
Executed(DispatchResult),
}
@@ -73,11 +73,11 @@
#[derive(PartialEq)]
pub enum Parent<CrossAccountId> {
- /// Token owned by normal account
+ /// Token owned by a normal account.
User(CrossAccountId),
- /// Passed token not found
+ /// Could not find the token provided as the owner.
TokenNotFound,
- /// Token owner is another token (target token still may not exist)
+ /// Token owner is another token (still, the target token may not exist).
Token(CollectionId, TokenId),
}
pallets/unique/src/lib.rsdiffbeforeafterboth--- a/pallets/unique/src/lib.rs
+++ b/pallets/unique/src/lib.rs
@@ -101,7 +101,7 @@
/// * admin: Admin address.
CollectionAdminAdded(CollectionId, CrossAccountId),
- /// Collection owned was change
+ /// Collection owned was changed
///
/// # Arguments
///
@@ -137,7 +137,7 @@
/// * admin: Admin address.
CollectionAdminRemoved(CollectionId, CrossAccountId),
- /// Address was remove from allow list
+ /// Address was removed from the allow list
///
/// # Arguments
///
@@ -146,7 +146,7 @@
/// * user: Address.
AllowListAddressRemoved(CollectionId, CrossAccountId),
- /// Address was add to allow list
+ /// Address was added to the allow list
///
/// # Arguments
///
@@ -155,13 +155,18 @@
/// * user: Address.
AllowListAddressAdded(CollectionId, CrossAccountId),
- /// Collection limits was set
+ /// Collection limits were set
///
/// # Arguments
///
/// * collection_id: Globally unique collection identifier.
CollectionLimitSet(CollectionId),
+ /// Collection permissions were set
+ ///
+ /// # Arguments
+ ///
+ /// * collection_id: Globally unique collection identifier.
CollectionPermissionSet(CollectionId),
}
}
@@ -198,7 +203,7 @@
ChainVersion: u64;
//#endregion
- //#region Tokens transfer rate limit baskets
+ //#region Tokens transfer sponosoring rate limit baskets
/// (Collection id (controlled?2), who created (real))
/// TODO: Off chain worker should remove from this map when collection gets removed
pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => Option<T::BlockNumber>;
@@ -214,11 +219,14 @@
/// Collection id (controlled?2), token id (controlled?2)
#[deprecated]
pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;
+ /// Last sponsoring of token property setting // todo:doc rephrase this and the following
pub TokenPropertyBasket get(fn token_property_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;
- /// Approval sponsoring
+ /// Last sponsoring of NFT approval in a collection
pub NftApproveBasket get(fn nft_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;
+ /// Last sponsoring of fungible tokens approval in a collection
pub FungibleApproveBasket get(fn fungible_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;
+ /// Last sponsoring of RFT approval in a collection
pub RefungibleApproveBasket get(fn refungible_approve_basket): nmap hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;
}
}
@@ -278,9 +286,16 @@
Self::create_collection_ex(origin, data)
}
- /// This method creates a collection
+ /// Create a collection with explicit parameters.
+ /// Prefer it to the deprecated [`created_collection`] method.
+ ///
+ /// # Permissions
+ ///
+ /// * Anyone.
///
- /// Prefer it to deprecated [`created_collection`] method
+ /// # Arguments
+ ///
+ /// * data: explicit create-collection data.
#[weight = <SelfWeightOf<T>>::create_collection()]
#[transactional]
pub fn create_collection_ex(origin, data: CreateCollectionData<T::AccountId>) -> DispatchResult {
@@ -293,11 +308,11 @@
Ok(())
}
- /// Destroys collection if no tokens within this collection
+ /// Destroy the collection if no tokens exist within.
///
/// # Permissions
///
- /// * Collection Owner.
+ /// * Collection Owner
///
/// # Arguments
///
@@ -398,7 +413,7 @@
///
/// # Permissions
///
- /// * Collection Owner.
+ /// * Collection Owner
///
/// # Arguments
///
@@ -424,40 +439,40 @@
target_collection.save()
}
- /// Adds an admin of the Collection.
+ /// Adds an admin of the collection.
/// NFT Collection can be controlled by multiple admin addresses (some which can also be servers, for example). Admins can issue and burn NFTs, as well as add and remove other admins, but cannot change NFT or Collection ownership.
///
/// # Permissions
///
- /// * Collection Owner.
- /// * Collection Admin.
+ /// * Collection Owner
+ /// * Collection Admin
///
/// # Arguments
///
/// * collection_id: ID of the Collection to add admin for.
///
- /// * new_admin_id: Address of new admin to add.
+ /// * new_admin: Address of new admin to add.
#[weight = <SelfWeightOf<T>>::add_collection_admin()]
#[transactional]
- pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {
+ pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin: T::CrossAccountId) -> DispatchResult {
let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
let collection = <CollectionHandle<T>>::try_get(collection_id)?;
collection.check_is_internal()?;
<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminAdded(
collection_id,
- new_admin_id.clone()
+ new_admin.clone()
));
- <PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)
+ <PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin, true)
}
/// Remove admin address of the Collection. An admin address can remove itself. List of admins may become empty, in which case only Collection Owner will be able to add an Admin.
///
/// # Permissions
///
- /// * Collection Owner.
- /// * Collection Admin.
+ /// * Collection Owner
+ /// * Collection Admin
///
/// # Arguments
///
@@ -479,9 +494,12 @@
<PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)
}
+ /// Set (invite) a new collection sponsor. If successful, confirmation from the sponsor-to-be will be pending.
+ ///
/// # Permissions
///
/// * Collection Owner
+ /// * Collection Admin
///
/// # Arguments
///
@@ -507,9 +525,11 @@
target_collection.save()
}
+ /// Confirm own sponsorship of a collection.
+ ///
/// # Permissions
///
- /// * Sponsor.
+ /// * The sponsor to-be
///
/// # Arguments
///
@@ -538,7 +558,7 @@
///
/// # Permissions
///
- /// * Collection owner.
+ /// * Collection Owner
///
/// # Arguments
///
@@ -560,12 +580,12 @@
target_collection.save()
}
- /// This method creates a concrete instance of NFT Collection created with CreateCollection method.
+ /// Create a concrete instance of NFT Collection created with CreateCollection method.
///
/// # Permissions
///
- /// * Collection Owner.
- /// * Collection Admin.
+ /// * Collection Owner
+ /// * Collection Admin
/// * Anyone if
/// * Allow List is enabled, and
/// * Address is added to allow list, and
@@ -587,12 +607,12 @@
dispatch_tx::<T, _>(collection_id, |d| d.create_item(sender, owner, data, &budget))
}
- /// This method creates multiple items in a collection created with CreateCollection method.
+ /// Create multiple items in a collection created with CreateCollection method.
///
/// # Permissions
///
- /// * Collection Owner.
- /// * Collection Admin.
+ /// * Collection Owner
+ /// * Collection Admin
/// * Anyone if
/// * Allow List is enabled, and
/// * Address is added to allow list, and
@@ -615,6 +635,18 @@
dispatch_tx::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data, &budget))
}
+ /// Add or change collection properties.
+ ///
+ /// # Permissions
+ ///
+ /// * Collection Owner
+ /// * Collection Admin
+ ///
+ /// # Arguments
+ ///
+ /// * collection_id.
+ ///
+ /// * properties: a vector of key-value pairs stored as the collection's metadata. Keys support Latin letters, '-', '_', and '.' as symbols.
#[weight = T::CommonWeightInfo::set_collection_properties(properties.len() as u32)]
#[transactional]
pub fn set_collection_properties(
@@ -629,6 +661,18 @@
dispatch_tx::<T, _>(collection_id, |d| d.set_collection_properties(sender, properties))
}
+ /// Delete specified collection properties.
+ ///
+ /// # Permissions
+ ///
+ /// * Collection Owner
+ /// * Collection Admin
+ ///
+ /// # Arguments
+ ///
+ /// * collection_id.
+ ///
+ /// * property_keys: a vector of keys of the properties to be deleted.
#[weight = T::CommonWeightInfo::delete_collection_properties(property_keys.len() as u32)]
#[transactional]
pub fn delete_collection_properties(
@@ -643,6 +687,22 @@
dispatch_tx::<T, _>(collection_id, |d| d.delete_collection_properties(&sender, property_keys))
}
+ /// Add or change token properties according to collection's permissions.
+ ///
+ /// # Permissions
+ ///
+ /// * Depends on collection's token property permissions and specified property mutability:
+ /// * Collection Owner
+ /// * Collection Admin
+ /// * Token Owner
+ ///
+ /// # Arguments
+ ///
+ /// * collection_id.
+ ///
+ /// * token_id.
+ ///
+ /// * properties: a vector of key-value pairs stored as the token's metadata. Keys support Latin letters, '-', '_', and '.' as symbols.
#[weight = T::CommonWeightInfo::set_token_properties(properties.len() as u32)]
#[transactional]
pub fn set_token_properties(
@@ -659,6 +719,22 @@
dispatch_tx::<T, _>(collection_id, |d| d.set_token_properties(sender, token_id, properties, &budget))
}
+ /// Delete specified token properties.
+ ///
+ /// # Permissions
+ ///
+ /// * Depends on collection's token property permissions and specified property mutability:
+ /// * Collection Owner
+ /// * Collection Admin
+ /// * Token Owner
+ ///
+ /// # Arguments
+ ///
+ /// * collection_id.
+ ///
+ /// * token_id.
+ ///
+ /// * property_keys: a vector of keys of the properties to be deleted.
#[weight = T::CommonWeightInfo::delete_token_properties(property_keys.len() as u32)]
#[transactional]
pub fn delete_token_properties(
@@ -675,6 +751,18 @@
dispatch_tx::<T, _>(collection_id, |d| d.delete_token_properties(sender, token_id, property_keys, &budget))
}
+ /// Add or change token property permissions of a collection.
+ ///
+ /// # Permissions
+ ///
+ /// * Collection Owner
+ /// * Collection Admin
+ ///
+ /// # Arguments
+ ///
+ /// * collection_id.
+ ///
+ /// * property_permissions: a vector of permissions for property keys. Keys support Latin letters, '-', '_', and '.' as symbols.
#[weight = T::CommonWeightInfo::set_token_property_permissions(property_permissions.len() as u32)]
#[transactional]
pub fn set_token_property_permissions(
@@ -689,6 +777,22 @@
dispatch_tx::<T, _>(collection_id, |d| d.set_token_property_permissions(&sender, property_permissions))
}
+ /// Create multiple items inside a collection with explicitly specified initial parameters.
+ ///
+ /// # Permissions
+ ///
+ /// * Collection Owner
+ /// * Collection Admin
+ /// * Anyone if
+ /// * Allow List is enabled, and
+ /// * Address is added to allow list, and
+ /// * MintPermission is enabled (see SetMintPermission method)
+ ///
+ /// # Arguments
+ ///
+ /// * collection_id: ID of the collection.
+ ///
+ /// * data: explicit item creation data.
#[weight = T::CommonWeightInfo::create_multiple_items_ex(&data)]
#[transactional]
pub fn create_multiple_items_ex(origin, collection_id: CollectionId, data: CreateItemExData<T::CrossAccountId>) -> DispatchResultWithPostInfo {
@@ -698,11 +802,11 @@
dispatch_tx::<T, _>(collection_id, |d| d.create_multiple_items_ex(sender, data, &budget))
}
- /// Set transfers_enabled value for particular collection
+ /// Set transfers_enabled value for particular collection.
///
/// # Permissions
///
- /// * Collection Owner.
+ /// * Collection Owner
///
/// # Arguments
///
@@ -723,13 +827,13 @@
target_collection.save()
}
- /// Destroys a concrete instance of NFT.
+ /// Destroy a concrete instance of NFT.
///
/// # Permissions
///
- /// * Collection Owner.
- /// * Collection Admin.
- /// * Current NFT Owner.
+ /// * Collection Owner
+ /// * Collection Admin
+ /// * Current NFT Owner
///
/// # Arguments
///
@@ -752,7 +856,7 @@
Ok(post_info)
}
- /// Destroys a concrete instance of NFT on behalf of the owner
+ /// Destroy a concrete instance of NFT on behalf of the owner.
/// See also: [`approve`]
///
/// # Permissions
@@ -835,6 +939,7 @@
/// Change ownership of a NFT on behalf of the owner. See Approve method for additional information. After this method executes, the approval is removed so that the approved address will not be able to transfer this NFT again from this owner.
///
/// # Permissions
+ ///
/// * Collection Owner
/// * Collection Admin
/// * Current NFT owner
@@ -860,6 +965,18 @@
dispatch_tx::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value, &budget))
}
+ /// Set specific limits of a collection. Empty, or None fields mean chain default.
+ ///.
+ /// # Permissions
+ ///
+ /// * Collection Owner
+ /// * Collection Admin
+ ///
+ /// # Arguments
+ ///
+ /// * collection_id.
+ ///
+ /// * new_limit: The new limits of the collection. They will overwrite the current ones.
#[weight = <SelfWeightOf<T>>::set_collection_limits()]
#[transactional]
pub fn set_collection_limits(
@@ -882,12 +999,24 @@
target_collection.save()
}
+ /// Set specific permissions of a collection. Empty, or None fields mean chain default.
+ ///
+ /// # Permissions
+ ///
+ /// * Collection Owner
+ /// * Collection Admin
+ ///
+ /// # Arguments
+ ///
+ /// * collection_id.
+ ///
+ /// * new_permission: The new permissions of the collection. They will overwrite the current ones.
#[weight = <SelfWeightOf<T>>::set_collection_limits()]
#[transactional]
pub fn set_collection_permissions(
origin,
collection_id: CollectionId,
- new_limit: CollectionPermissions,
+ new_permission: CollectionPermissions,
) -> DispatchResult {
let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;
@@ -895,7 +1024,7 @@
target_collection.check_is_owner_or_admin(&sender)?;
let old_limit = &target_collection.permissions;
- target_collection.permissions = <PalletCommon<T>>::clamp_permissions(target_collection.mode.clone(), &old_limit, new_limit)?;
+ target_collection.permissions = <PalletCommon<T>>::clamp_permissions(target_collection.mode.clone(), &old_limit, new_permission)?;
<Pallet<T>>::deposit_event(Event::<T>::CollectionPermissionSet(
collection_id
@@ -904,6 +1033,19 @@
target_collection.save()
}
+ /// Re-partition a refungible token, while owning all of its parts.
+ ///
+ /// # Permissions
+ ///
+ /// * Token Owner (must own every part)
+ ///
+ /// # Arguments
+ ///
+ /// * collection_id.
+ ///
+ /// * token: the ID of the RFT.
+ ///
+ /// * amount: The new number of parts into which the token shall be partitioned.
#[weight = T::RefungibleExtensionsWeightInfo::repartition()]
#[transactional]
pub fn repartition(
primitives/data-structs/src/lib.rsdiffbeforeafterboth--- a/primitives/data-structs/src/lib.rs
+++ b/primitives/data-structs/src/lib.rs
@@ -197,7 +197,6 @@
#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
pub enum CollectionMode {
NFT,
- // decimal points
Fungible(DecimalPoints),
ReFungible,
}
@@ -252,12 +251,14 @@
pub enum SponsorshipState<AccountId> {
/// The fees are applied to the transaction sender
Disabled,
+ /// Pending confirmation from a sponsor-to-be
Unconfirmed(AccountId),
/// Transactions are sponsored by specified account
Confirmed(AccountId),
}
impl<AccountId> SponsorshipState<AccountId> {
+ /// Get the acting sponsor account, if present
pub fn sponsor(&self) -> Option<&AccountId> {
match self {
Self::Confirmed(sponsor) => Some(sponsor),
@@ -265,6 +266,7 @@
}
}
+ /// Get the sponsor account currently pending confirmation, if present
pub fn pending_sponsor(&self) -> Option<&AccountId> {
match self {
Self::Unconfirmed(sponsor) | Self::Confirmed(sponsor) => Some(sponsor),
@@ -272,6 +274,7 @@
}
}
+ /// Is sponsorship set and acting
pub fn confirmed(&self) -> bool {
matches!(self, Self::Confirmed(_))
}
@@ -283,7 +286,7 @@
}
}
-/// Used in storage
+/// Collection parameters, used in storage (see [`RpcCollection`] for the RPC version)
#[struct_versioning::versioned(version = 2, upper)]
#[derive(Encode, Decode, Clone, PartialEq, TypeInfo, MaxEncodedLen)]
pub struct Collection<AccountId> {
@@ -324,7 +327,7 @@
pub meta_update_permission: MetaUpdatePermission,
}
-/// Used in RPC calls
+/// Collection parameters, used in RPC calls (see [`Collection`] for the storage version)
#[derive(Encode, Decode, Clone, PartialEq, TypeInfo)]
#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
pub struct RpcCollection<AccountId> {
@@ -362,12 +365,15 @@
pub type CollectionPropertiesVec = BoundedVec<Property, ConstU32<MAX_PROPERTIES_PER_ITEM>>;
-/// All fields are wrapped in `Option`s, where None means chain default
+/// Limits and restrictions of a collection.
+/// All fields are wrapped in `Option`s, where None means chain default.
// When adding/removing fields from this struct - don't forget to also update clamp_limits
#[derive(Encode, Decode, Debug, Default, Clone, PartialEq, TypeInfo, MaxEncodedLen)]
#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
pub struct CollectionLimits {
+ /// Maximum number of owned tokens per account
pub account_token_ownership_limit: Option<u32>,
+ /// Maximum size of data of a sponsored transaction
pub sponsored_data_size: Option<u32>,
/// FIXME should we delete this or repurpose it?
@@ -375,13 +381,18 @@
/// Some(v) - setVariableMetadata is sponsored
/// if there is v block between txs
pub sponsored_data_rate_limit: Option<SponsoringRateLimit>,
+ /// Maximum amount of tokens inside the collection
pub token_limit: Option<u32>,
- // Timeouts for item types in passed blocks
+ /// Timeout for sponsoring a token transfer in passed blocks
pub sponsor_transfer_timeout: Option<u32>,
+ /// Timeout for sponsoring an approval in passed blocks
pub sponsor_approve_timeout: Option<u32>,
+ /// Can a token be transferred by the owner
pub owner_can_transfer: Option<bool>,
+ /// Can a token be burned by the owner
pub owner_can_destroy: Option<bool>,
+ /// Can a token be transferred at all
pub transfers_enabled: Option<bool>,
}
@@ -509,6 +520,7 @@
#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
pub enum SponsoringRateLimit {
SponsoringDisabled,
+ /// Once per how many blocks can sponsorship of a transaction type occur
Blocks(u32),
}
@@ -516,6 +528,7 @@
#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
#[derivative(Debug)]
pub struct CreateNftData {
+ /// Key-value pairs used to describe the token as metadata
#[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]
#[derivative(Debug(format_with = "bounded::vec_debug"))]
pub properties: CollectionPropertiesVec,
@@ -524,6 +537,7 @@
#[derive(Encode, Decode, MaxEncodedLen, Default, Debug, Clone, PartialEq, TypeInfo)]
#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
pub struct CreateFungibleData {
+ /// Number of fungible tokens minted
pub value: u128,
}
@@ -534,6 +548,7 @@
#[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]
#[derivative(Debug(format_with = "bounded::vec_debug"))]
pub const_data: BoundedVec<u8, CustomDataLimit>,
+ /// Number of pieces the RFT is split into
pub pieces: u128,
}
@@ -553,6 +568,7 @@
ReFungible(CreateReFungibleData),
}
+/// Explicit NFT creation data with meta parameters
#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, TypeInfo, Derivative)]
#[derivative(Debug)]
pub struct CreateNftExData<CrossAccountId> {
@@ -561,6 +577,7 @@
pub owner: CrossAccountId,
}
+/// Explicit RFT creation data with meta parameters
#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, TypeInfo, Derivative)]
#[derivative(Debug(bound = "CrossAccountId: fmt::Debug + Ord"))]
pub struct CreateRefungibleExData<CrossAccountId> {
@@ -570,6 +587,7 @@
pub users: BoundedBTreeMap<CrossAccountId, u128, ConstU32<MAX_ITEMS_PER_BATCH>>,
}
+/// Explicit item creation data with meta parameters, namely the owner
#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, TypeInfo, Derivative)]
#[derivative(Debug(bound = "CrossAccountId: fmt::Debug + Ord"))]
pub enum CreateItemExData<CrossAccountId> {
@@ -617,6 +635,7 @@
}
}
+/// Token's address, dictated by its collection and token IDs
#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, Debug, TypeInfo)]
#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
// todo possibly rename to be used generally as an address pair
tests/src/interfaces/unique/definitions.tsdiffbeforeafterboth--- a/tests/src/interfaces/unique/definitions.ts
+++ b/tests/src/interfaces/unique/definitions.ts
@@ -43,13 +43,13 @@
accountTokens: fun('Get tokens owned by account', [collectionParam, crossAccountParam()], 'Vec<u32>'),
collectionTokens: fun('Get tokens contained in collection', [collectionParam], 'Vec<u32>'),
- lastTokenId: fun('Get last token id', [collectionParam], 'u32'),
+ lastTokenId: fun('Get last token ID created in a collection', [collectionParam], 'u32'),
totalSupply: fun('Get amount of unique collection tokens', [collectionParam], 'u32'),
- accountBalance: fun('Get amount of different user tokens', [collectionParam, crossAccountParam()], 'u32'),
- balance: fun('Get amount of specific account token', [collectionParam, crossAccountParam(), tokenParam], 'u128'),
+ accountBalance: fun('Get owned amount of any user tokens', [collectionParam, crossAccountParam()], 'u32'),
+ balance: fun('Get owned amount of specific account token', [collectionParam, crossAccountParam(), tokenParam], 'u128'),
allowance: fun('Get allowed amount', [collectionParam, crossAccountParam('sender'), crossAccountParam('spender'), tokenParam], 'u128'),
tokenOwner: fun('Get token owner', [collectionParam, tokenParam], `Option<${CROSS_ACCOUNT_ID_TYPE}>`),
- topmostTokenOwner: fun('Get token owner, in case of nested token - find parent recursive', [collectionParam, tokenParam], `Option<${CROSS_ACCOUNT_ID_TYPE}>`),
+ topmostTokenOwner: fun('Get token owner, in case of nested token - find the parent recursively', [collectionParam, tokenParam], `Option<${CROSS_ACCOUNT_ID_TYPE}>`),
tokenChildren: fun('Get tokens nested directly into the token', [collectionParam, tokenParam], 'Vec<UpDataStructsTokenChild>'),
constMetadata: fun('Get token constant metadata', [collectionParam, tokenParam], 'Vec<u8>'),
variableMetadata: fun('Get token variable metadata', [collectionParam, tokenParam], 'Vec<u8>'),
@@ -74,7 +74,7 @@
'UpDataStructsTokenData',
),
tokenExists: fun('Check if token exists', [collectionParam, tokenParam], 'bool'),
- collectionById: fun('Get collection by specified id', [collectionParam], 'Option<UpDataStructsRpcCollection>'),
+ collectionById: fun('Get collection by specified ID', [collectionParam], 'Option<UpDataStructsRpcCollection>'),
collectionStats: fun('Get collection stats', [], 'UpDataStructsCollectionStats'),
allowed: fun('Check if user is allowed to use collection', [collectionParam, crossAccountParam()], 'bool'),
nextSponsored: fun('Get number of blocks when sponsored transaction is available', [collectionParam, crossAccountParam(), tokenParam], 'Option<u64>'),