difftreelog
CORE-238 add effective_collection_limits
in: master
13 files changed
Cargo.lockdiffbeforeafterboth--- a/Cargo.lock
+++ b/Cargo.lock
@@ -9360,6 +9360,42 @@
]
[[package]]
+name = "sc-consensus-manual-seal"
+version = "0.10.0-dev"
+source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.17#22d40c761a985482f93bbbea5ba4199bdba74f8e"
+dependencies = [
+ "assert_matches",
+ "async-trait",
+ "futures 0.3.21",
+ "jsonrpc-core",
+ "jsonrpc-core-client",
+ "jsonrpc-derive",
+ "log",
+ "parity-scale-codec",
+ "sc-client-api",
+ "sc-consensus",
+ "sc-consensus-aura",
+ "sc-consensus-babe",
+ "sc-consensus-epochs",
+ "sc-transaction-pool",
+ "sc-transaction-pool-api",
+ "serde",
+ "sp-api",
+ "sp-blockchain",
+ "sp-consensus",
+ "sp-consensus-aura",
+ "sp-consensus-babe",
+ "sp-consensus-slots",
+ "sp-core",
+ "sp-inherents",
+ "sp-keystore",
+ "sp-runtime",
+ "sp-timestamp",
+ "substrate-prometheus-endpoint",
+ "thiserror",
+]
+
+[[package]]
name = "sc-consensus-slots"
version = "0.10.0-dev"
source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.17#22d40c761a985482f93bbbea5ba4199bdba74f8e"
@@ -11697,7 +11733,7 @@
"chrono",
"lazy_static",
"matchers",
- "parking_lot 0.11.2",
+ "parking_lot 0.10.2",
"regex",
"serde",
"serde_json",
@@ -11957,6 +11993,7 @@
"sc-client-api",
"sc-consensus",
"sc-consensus-aura",
+ "sc-consensus-manual-seal",
"sc-executor",
"sc-finality-grandpa",
"sc-keystore",
client/rpc/src/lib.rsdiffbeforeafterboth--- a/client/rpc/src/lib.rs
+++ b/client/rpc/src/lib.rs
@@ -19,7 +19,7 @@
use codec::Decode;
use jsonrpc_core::{Error as RpcError, ErrorCode, Result};
use jsonrpc_derive::rpc;
-use up_data_structs::{Collection, CollectionId, CollectionStats, TokenId};
+use up_data_structs::{Collection, CollectionId, CollectionStats, CollectionLimits, TokenId};
use sp_api::{BlockId, BlockT, ProvideRuntimeApi, ApiExt};
use sp_blockchain::HeaderBackend;
use up_rpc::UniqueApi as UniqueRuntimeApi;
@@ -119,6 +119,12 @@
) -> Result<Option<Collection<AccountId>>>;
#[rpc(name = "unique_collectionStats")]
fn collection_stats(&self, at: Option<BlockHash>) -> Result<CollectionStats>;
+ #[rpc(name = "unique_effectiveCollectionLimits")]
+ fn effective_collection_limits(
+ &self,
+ collection_id: CollectionId,
+ at: Option<BlockHash>
+ ) -> Result<Option<CollectionLimits>>;
}
pub struct Unique<C, P> {
@@ -222,4 +228,5 @@
pass_method!(last_token_id(collection: CollectionId) -> TokenId);
pass_method!(collection_by_id(collection: CollectionId) -> Option<Collection<AccountId>>);
pass_method!(collection_stats() -> CollectionStats);
+ pass_method!(effective_collection_limits(collection_id: CollectionId) -> Option<CollectionLimits>);
}
pallets/common/src/lib.rsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![cfg_attr(not(feature = "std"), no_std)]1819use core::ops::{Deref, DerefMut};20use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};21use sp_std::vec::Vec;22use account::CrossAccountId;23use frame_support::{24 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo},25 ensure, fail,26 traits::{Imbalance, Get, Currency},27 BoundedVec,28};29use pallet_evm::GasWeightMapping;30use up_data_structs::{31 COLLECTION_NUMBER_LIMIT, Collection, CollectionId, CreateItemData, ExistenceRequirement,32 MAX_TOKEN_PREFIX_LENGTH, COLLECTION_ADMINS_LIMIT, MetaUpdatePermission, Pays, PostDispatchInfo,33 TokenId, Weight, WithdrawReasons, CollectionStats, MAX_TOKEN_OWNERSHIP, CollectionMode,34 NFT_SPONSOR_TRANSFER_TIMEOUT, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,35 REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, MAX_SPONSOR_TIMEOUT, CUSTOM_DATA_LIMIT, CollectionLimits,36 CustomDataLimit, CreateCollectionData, SponsorshipState, CreateItemExData,37};38pub use pallet::*;39use sp_core::H160;40use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};41pub mod account;42#[cfg(feature = "runtime-benchmarks")]43pub mod benchmarking;44pub mod erc;45pub mod eth;4647#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]48pub struct CollectionHandle<T: Config> {49 pub id: CollectionId,50 collection: Collection<T::AccountId>,51 pub recorder: SubstrateRecorder<T>,52}53impl<T: Config> WithRecorder<T> for CollectionHandle<T> {54 fn recorder(&self) -> &SubstrateRecorder<T> {55 &self.recorder56 }57 fn into_recorder(self) -> SubstrateRecorder<T> {58 self.recorder59 }60}61impl<T: Config> CollectionHandle<T> {62 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {63 <CollectionById<T>>::get(id).map(|collection| Self {64 id,65 collection,66 recorder: SubstrateRecorder::new(eth::collection_id_to_address(id), gas_limit),67 })68 }69 pub fn new(id: CollectionId) -> Option<Self> {70 Self::new_with_gas_limit(id, u64::MAX)71 }72 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {73 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)74 }75 pub fn log_mirrored(&self, log: impl evm_coder::ToLog) {76 self.recorder.log_mirrored(log)77 }78 pub fn log_direct(&self, log: impl evm_coder::ToLog) {79 self.recorder.log_direct(log)80 }81 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {82 self.recorder83 .consume_gas(T::GasWeightMapping::weight_to_gas(84 <T as frame_system::Config>::DbWeight::get()85 .read86 .saturating_mul(reads),87 ))88 }89 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {90 self.recorder91 .consume_gas(T::GasWeightMapping::weight_to_gas(92 <T as frame_system::Config>::DbWeight::get()93 .write94 .saturating_mul(writes),95 ))96 }97 pub fn submit_logs(self) {98 self.recorder.submit_logs()99 }100 pub fn save(self) -> DispatchResult {101 self.recorder.submit_logs();102 <CollectionById<T>>::insert(self.id, self.collection);103 Ok(())104 }105}106impl<T: Config> Deref for CollectionHandle<T> {107 type Target = Collection<T::AccountId>;108109 fn deref(&self) -> &Self::Target {110 &self.collection111 }112}113114impl<T: Config> DerefMut for CollectionHandle<T> {115 fn deref_mut(&mut self) -> &mut Self::Target {116 &mut self.collection117 }118}119120impl<T: Config> CollectionHandle<T> {121 pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {122 ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);123 Ok(())124 }125 pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {126 *subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))127 }128 pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {129 ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);130 Ok(())131 }132 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {133 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)134 }135 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {136 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)137 }138 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {139 ensure!(140 <Allowlist<T>>::get((self.id, user)),141 <Error<T>>::AddressNotInAllowlist142 );143 Ok(())144 }145146 pub fn check_can_update_meta(147 &self,148 subject: &T::CrossAccountId,149 item_owner: &T::CrossAccountId,150 ) -> DispatchResult {151 match self.meta_update_permission {152 MetaUpdatePermission::ItemOwner => {153 ensure!(subject == item_owner, <Error<T>>::NoPermission);154 Ok(())155 }156 MetaUpdatePermission::Admin => self.check_is_owner_or_admin(subject),157 MetaUpdatePermission::None => fail!(<Error<T>>::NoPermission),158 }159 }160}161162#[frame_support::pallet]163pub mod pallet {164 use super::*;165 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key};166 use account::CrossAccountId;167 use frame_support::traits::Currency;168 use up_data_structs::TokenId;169 use scale_info::TypeInfo;170171 #[pallet::config]172 pub trait Config: frame_system::Config + pallet_evm_coder_substrate::Config + TypeInfo {173 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;174175 type CrossAccountId: CrossAccountId<Self::AccountId>;176177 type EvmAddressMapping: pallet_evm::AddressMapping<Self::AccountId>;178 type EvmBackwardsAddressMapping: up_evm_mapping::EvmBackwardsAddressMapping<Self::AccountId>;179180 type Currency: Currency<Self::AccountId>;181182 #[pallet::constant]183 type CollectionCreationPrice: Get<184 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,185 >;186187 type TreasuryAccountId: Get<Self::AccountId>;188 }189190 #[pallet::pallet]191 #[pallet::generate_store(pub(super) trait Store)]192 pub struct Pallet<T>(_);193194 #[pallet::extra_constants]195 impl<T: Config> Pallet<T> {196 pub fn collection_admins_limit() -> u32 {197 COLLECTION_ADMINS_LIMIT198 }199 }200201 #[pallet::event]202 #[pallet::generate_deposit(pub fn deposit_event)]203 pub enum Event<T: Config> {204 /// New collection was created205 ///206 /// # Arguments207 ///208 /// * collection_id: Globally unique identifier of newly created collection.209 ///210 /// * mode: [CollectionMode] converted into u8.211 ///212 /// * account_id: Collection owner.213 CollectionCreated(CollectionId, u8, T::AccountId),214215 /// New collection was destroyed216 ///217 /// # Arguments218 ///219 /// * collection_id: Globally unique identifier of collection.220 CollectionDestroyed(CollectionId),221222 /// New item was created.223 ///224 /// # Arguments225 ///226 /// * collection_id: Id of the collection where item was created.227 ///228 /// * item_id: Id of an item. Unique within the collection.229 ///230 /// * recipient: Owner of newly created item231 ///232 /// * amount: Always 1 for NFT233 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),234235 /// Collection item was burned.236 ///237 /// # Arguments238 ///239 /// * collection_id.240 ///241 /// * item_id: Identifier of burned NFT.242 ///243 /// * owner: which user has destroyed its tokens244 ///245 /// * amount: Always 1 for NFT246 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),247248 /// Item was transferred249 ///250 /// * collection_id: Id of collection to which item is belong251 ///252 /// * item_id: Id of an item253 ///254 /// * sender: Original owner of item255 ///256 /// * recipient: New owner of item257 ///258 /// * amount: Always 1 for NFT259 Transfer(260 CollectionId,261 TokenId,262 T::CrossAccountId,263 T::CrossAccountId,264 u128,265 ),266267 /// * collection_id268 ///269 /// * item_id270 ///271 /// * sender272 ///273 /// * spender274 ///275 /// * amount276 Approved(277 CollectionId,278 TokenId,279 T::CrossAccountId,280 T::CrossAccountId,281 u128,282 ),283 }284285 #[pallet::error]286 pub enum Error<T> {287 /// This collection does not exist.288 CollectionNotFound,289 /// Sender parameter and item owner must be equal.290 MustBeTokenOwner,291 /// No permission to perform action292 NoPermission,293 /// Collection is not in mint mode.294 PublicMintingNotAllowed,295 /// Address is not in allow list.296 AddressNotInAllowlist,297298 /// Collection name can not be longer than 63 char.299 CollectionNameLimitExceeded,300 /// Collection description can not be longer than 255 char.301 CollectionDescriptionLimitExceeded,302 /// Token prefix can not be longer than 15 char.303 CollectionTokenPrefixLimitExceeded,304 /// Total collections bound exceeded.305 TotalCollectionsLimitExceeded,306 /// variable_data exceeded data limit.307 TokenVariableDataLimitExceeded,308 /// Exceeded max admin count309 CollectionAdminCountExceeded,310 /// Collection limit bounds per collection exceeded311 CollectionLimitBoundsExceeded,312 /// Tried to enable permissions which are only permitted to be disabled313 OwnerPermissionsCantBeReverted,314315 /// Collection settings not allowing items transferring316 TransferNotAllowed,317 /// Account token limit exceeded per collection318 AccountTokenLimitExceeded,319 /// Collection token limit exceeded320 CollectionTokenLimitExceeded,321 /// Metadata flag frozen322 MetadataFlagFrozen,323324 /// Item not exists.325 TokenNotFound,326 /// Item balance not enough.327 TokenValueTooLow,328 /// Requested value more than approved.329 ApprovedValueTooLow,330 /// Tried to approve more than owned331 CantApproveMoreThanOwned,332333 /// Can't transfer tokens to ethereum zero address334 AddressIsZero,335 /// Target collection doesn't supports this operation336 UnsupportedOperation,337338 /// Not sufficient founds to perform action339 NotSufficientFounds,340 }341342 #[pallet::storage]343 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;344 #[pallet::storage]345 pub type DestroyedCollectionCount<T> =346 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;347348 /// Collection info349 #[pallet::storage]350 pub type CollectionById<T> = StorageMap<351 Hasher = Blake2_128Concat,352 Key = CollectionId,353 Value = Collection<<T as frame_system::Config>::AccountId>,354 QueryKind = OptionQuery,355 >;356357 #[pallet::storage]358 pub type AdminAmount<T> = StorageMap<359 Hasher = Blake2_128Concat,360 Key = CollectionId,361 Value = u32,362 QueryKind = ValueQuery,363 >;364365 /// List of collection admins366 #[pallet::storage]367 pub type IsAdmin<T: Config> = StorageNMap<368 Key = (369 Key<Blake2_128Concat, CollectionId>,370 Key<Blake2_128Concat, T::CrossAccountId>,371 ),372 Value = bool,373 QueryKind = ValueQuery,374 >;375376 /// Allowlisted collection users377 #[pallet::storage]378 pub type Allowlist<T: Config> = StorageNMap<379 Key = (380 Key<Blake2_128Concat, CollectionId>,381 Key<Blake2_128Concat, T::CrossAccountId>,382 ),383 Value = bool,384 QueryKind = ValueQuery,385 >;386387 /// Not used by code, exists only to provide some types to metadata388 #[pallet::storage]389 pub type DummyStorageValue<T> =390 StorageValue<Value = (CollectionStats, CollectionId, TokenId), QueryKind = OptionQuery>;391}392393impl<T: Config> Pallet<T> {394 /// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens395 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {396 ensure!(397 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,398 <Error<T>>::AddressIsZero399 );400 Ok(())401 }402 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {403 <IsAdmin<T>>::iter_prefix((collection,))404 .map(|(a, _)| a)405 .collect()406 }407 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {408 <Allowlist<T>>::iter_prefix((collection,))409 .map(|(a, _)| a)410 .collect()411 }412 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {413 <Allowlist<T>>::get((collection, user))414 }415 pub fn collection_stats() -> CollectionStats {416 let created = <CreatedCollectionCount<T>>::get();417 let destroyed = <DestroyedCollectionCount<T>>::get();418 CollectionStats {419 created: created.0,420 destroyed: destroyed.0,421 alive: created.0 - destroyed.0,422 }423 }424}425426impl<T: Config> Pallet<T> {427 pub fn init_collection(428 owner: T::AccountId,429 data: CreateCollectionData<T::AccountId>,430 ) -> Result<CollectionId, DispatchError> {431 {432 ensure!(433 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,434 Error::<T>::CollectionTokenPrefixLimitExceeded435 );436 }437438 let created_count = <CreatedCollectionCount<T>>::get()439 .0440 .checked_add(1)441 .ok_or(ArithmeticError::Overflow)?;442 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;443 let id = CollectionId(created_count);444445 // bound Total number of collections446 ensure!(447 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,448 <Error<T>>::TotalCollectionsLimitExceeded449 );450451 // =========452453 let collection = Collection {454 owner: owner.clone(),455 name: data.name,456 mode: data.mode.clone(),457 mint_mode: false,458 access: data.access.unwrap_or_default(),459 description: data.description,460 token_prefix: data.token_prefix,461 offchain_schema: data.offchain_schema,462 schema_version: data.schema_version.unwrap_or_default(),463 sponsorship: data464 .pending_sponsor465 .map(SponsorshipState::Unconfirmed)466 .unwrap_or_default(),467 variable_on_chain_schema: data.variable_on_chain_schema,468 const_on_chain_schema: data.const_on_chain_schema,469 limits: data470 .limits471 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))472 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,473 meta_update_permission: data.meta_update_permission.unwrap_or_default(),474 };475476 // Take a (non-refundable) deposit of collection creation477 {478 let mut imbalance =479 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();480 imbalance.subsume(481 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(482 &T::TreasuryAccountId::get(),483 T::CollectionCreationPrice::get(),484 ),485 );486 <T as Config>::Currency::settle(487 &owner,488 imbalance,489 WithdrawReasons::TRANSFER,490 ExistenceRequirement::KeepAlive,491 )492 .map_err(|_| Error::<T>::NotSufficientFounds)?;493 }494495 <CreatedCollectionCount<T>>::put(created_count);496 <Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));497 <CollectionById<T>>::insert(id, collection);498 Ok(id)499 }500501 pub fn destroy_collection(502 collection: CollectionHandle<T>,503 sender: &T::CrossAccountId,504 ) -> DispatchResult {505 ensure!(506 collection.limits.owner_can_destroy(),507 <Error<T>>::NoPermission,508 );509 collection.check_is_owner(sender)?;510511 let destroyed_collections = <DestroyedCollectionCount<T>>::get()512 .0513 .checked_add(1)514 .ok_or(ArithmeticError::Overflow)?;515516 // =========517518 <DestroyedCollectionCount<T>>::put(destroyed_collections);519 <CollectionById<T>>::remove(collection.id);520 <AdminAmount<T>>::remove(collection.id);521 <IsAdmin<T>>::remove_prefix((collection.id,), None);522 <Allowlist<T>>::remove_prefix((collection.id,), None);523524 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));525 Ok(())526 }527528 pub fn toggle_allowlist(529 collection: &CollectionHandle<T>,530 sender: &T::CrossAccountId,531 user: &T::CrossAccountId,532 allowed: bool,533 ) -> DispatchResult {534 collection.check_is_owner_or_admin(sender)?;535536 // =========537538 if allowed {539 <Allowlist<T>>::insert((collection.id, user), true);540 } else {541 <Allowlist<T>>::remove((collection.id, user));542 }543544 Ok(())545 }546547 pub fn toggle_admin(548 collection: &CollectionHandle<T>,549 sender: &T::CrossAccountId,550 user: &T::CrossAccountId,551 admin: bool,552 ) -> DispatchResult {553 collection.check_is_owner_or_admin(sender)?;554555 let was_admin = <IsAdmin<T>>::get((collection.id, user));556 if was_admin == admin {557 return Ok(());558 }559 let amount = <AdminAmount<T>>::get(collection.id);560561 if admin {562 let amount = amount563 .checked_add(1)564 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;565 ensure!(566 amount <= Self::collection_admins_limit(),567 <Error<T>>::CollectionAdminCountExceeded,568 );569570 // =========571572 <AdminAmount<T>>::insert(collection.id, amount);573 <IsAdmin<T>>::insert((collection.id, user), true);574 } else {575 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));576 <IsAdmin<T>>::remove((collection.id, user));577 }578579 Ok(())580 }581582 pub fn clamp_limits(583 mode: CollectionMode,584 old_limit: &CollectionLimits,585 mut new_limit: CollectionLimits,586 ) -> Result<CollectionLimits, DispatchError> {587 macro_rules! limit_default {588 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{589 $(590 if let Some($new) = $new.$field {591 let $old = $old.$field($($arg)?);592 let _ = $new;593 let _ = $old;594 $check595 } else {596 $new.$field = $old.$field597 }598 )*599 }};600 }601602 limit_default!(old_limit, new_limit,603 account_token_ownership_limit => ensure!(604 new_limit <= MAX_TOKEN_OWNERSHIP,605 <Error<T>>::CollectionLimitBoundsExceeded,606 ),607 sponsor_transfer_timeout(match mode {608 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,609 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,610 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,611 }) => ensure!(612 new_limit <= MAX_SPONSOR_TIMEOUT,613 <Error<T>>::CollectionLimitBoundsExceeded,614 ),615 sponsored_data_size => ensure!(616 new_limit <= CUSTOM_DATA_LIMIT,617 <Error<T>>::CollectionLimitBoundsExceeded,618 ),619 token_limit => ensure!(620 old_limit >= new_limit && new_limit > 0,621 <Error<T>>::CollectionTokenLimitExceeded622 ),623 owner_can_transfer => ensure!(624 old_limit || !new_limit,625 <Error<T>>::OwnerPermissionsCantBeReverted,626 ),627 owner_can_destroy => ensure!(628 old_limit || !new_limit,629 <Error<T>>::OwnerPermissionsCantBeReverted,630 ),631 sponsored_data_rate_limit => {},632 transfers_enabled => {},633 );634 Ok(new_limit)635 }636}637638#[macro_export]639macro_rules! unsupported {640 () => {641 Err(<Error<T>>::UnsupportedOperation.into())642 };643}644645/// Worst cases646pub trait CommonWeightInfo<CrossAccountId> {647 fn create_item() -> Weight;648 fn create_multiple_items(amount: u32) -> Weight;649 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;650 fn burn_item() -> Weight;651 fn transfer() -> Weight;652 fn approve() -> Weight;653 fn transfer_from() -> Weight;654 fn burn_from() -> Weight;655 fn set_variable_metadata(bytes: u32) -> Weight;656}657658pub trait CommonCollectionOperations<T: Config> {659 fn create_item(660 &self,661 sender: T::CrossAccountId,662 to: T::CrossAccountId,663 data: CreateItemData,664 ) -> DispatchResultWithPostInfo;665 fn create_multiple_items(666 &self,667 sender: T::CrossAccountId,668 to: T::CrossAccountId,669 data: Vec<CreateItemData>,670 ) -> DispatchResultWithPostInfo;671 fn create_multiple_items_ex(672 &self,673 sender: T::CrossAccountId,674 data: CreateItemExData<T::CrossAccountId>,675 ) -> DispatchResultWithPostInfo;676 fn burn_item(677 &self,678 sender: T::CrossAccountId,679 token: TokenId,680 amount: u128,681 ) -> DispatchResultWithPostInfo;682683 fn transfer(684 &self,685 sender: T::CrossAccountId,686 to: T::CrossAccountId,687 token: TokenId,688 amount: u128,689 ) -> DispatchResultWithPostInfo;690 fn approve(691 &self,692 sender: T::CrossAccountId,693 spender: T::CrossAccountId,694 token: TokenId,695 amount: u128,696 ) -> DispatchResultWithPostInfo;697 fn transfer_from(698 &self,699 sender: T::CrossAccountId,700 from: T::CrossAccountId,701 to: T::CrossAccountId,702 token: TokenId,703 amount: u128,704 ) -> DispatchResultWithPostInfo;705 fn burn_from(706 &self,707 sender: T::CrossAccountId,708 from: T::CrossAccountId,709 token: TokenId,710 amount: u128,711 ) -> DispatchResultWithPostInfo;712713 fn set_variable_metadata(714 &self,715 sender: T::CrossAccountId,716 token: TokenId,717 data: BoundedVec<u8, CustomDataLimit>,718 ) -> DispatchResultWithPostInfo;719720 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;721 fn token_exists(&self, token: TokenId) -> bool;722 fn last_token_id(&self) -> TokenId;723724 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;725 fn const_metadata(&self, token: TokenId) -> Vec<u8>;726 fn variable_metadata(&self, token: TokenId) -> Vec<u8>;727728 /// How many tokens collection contains (Applicable to nonfungible/refungible)729 fn collection_tokens(&self) -> u32;730 /// Amount of different tokens account has (Applicable to nonfungible/refungible)731 fn account_balance(&self, account: T::CrossAccountId) -> u32;732 /// Amount of specific token account have (Applicable to fungible/refungible)733 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;734 fn allowance(735 &self,736 sender: T::CrossAccountId,737 spender: T::CrossAccountId,738 token: TokenId,739 ) -> u128;740}741742// Flexible enough for implementing CommonCollectionOperations743pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {744 let post_info = PostDispatchInfo {745 actual_weight: Some(weight),746 pays_fee: Pays::Yes,747 };748 match res {749 Ok(()) => Ok(post_info),750 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),751 }752}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)]1819use core::ops::{Deref, DerefMut};20use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};21use sp_std::vec::Vec;22use account::CrossAccountId;23use frame_support::{24 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo},25 ensure, fail,26 traits::{Imbalance, Get, Currency},27 BoundedVec,28};29use pallet_evm::GasWeightMapping;30use up_data_structs::{31 COLLECTION_NUMBER_LIMIT, Collection, CollectionId, CreateItemData, ExistenceRequirement,32 MAX_TOKEN_PREFIX_LENGTH, COLLECTION_ADMINS_LIMIT, MetaUpdatePermission, Pays, PostDispatchInfo,33 TokenId, Weight, WithdrawReasons, CollectionStats, MAX_TOKEN_OWNERSHIP, CollectionMode,34 NFT_SPONSOR_TRANSFER_TIMEOUT, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,35 REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, MAX_SPONSOR_TIMEOUT, CUSTOM_DATA_LIMIT, CollectionLimits,36 CustomDataLimit, CreateCollectionData, SponsorshipState, CreateItemExData, SponsoringRateLimit37};38pub use pallet::*;39use sp_core::H160;40use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};41pub mod account;42#[cfg(feature = "runtime-benchmarks")]43pub mod benchmarking;44pub mod erc;45pub mod eth;4647#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]48pub struct CollectionHandle<T: Config> {49 pub id: CollectionId,50 collection: Collection<T::AccountId>,51 pub recorder: SubstrateRecorder<T>,52}53impl<T: Config> WithRecorder<T> for CollectionHandle<T> {54 fn recorder(&self) -> &SubstrateRecorder<T> {55 &self.recorder56 }57 fn into_recorder(self) -> SubstrateRecorder<T> {58 self.recorder59 }60}61impl<T: Config> CollectionHandle<T> {62 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {63 <CollectionById<T>>::get(id).map(|collection| Self {64 id,65 collection,66 recorder: SubstrateRecorder::new(eth::collection_id_to_address(id), gas_limit),67 })68 }69 pub fn new(id: CollectionId) -> Option<Self> {70 Self::new_with_gas_limit(id, u64::MAX)71 }72 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {73 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)74 }75 pub fn log_mirrored(&self, log: impl evm_coder::ToLog) {76 self.recorder.log_mirrored(log)77 }78 pub fn log_direct(&self, log: impl evm_coder::ToLog) {79 self.recorder.log_direct(log)80 }81 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {82 self.recorder83 .consume_gas(T::GasWeightMapping::weight_to_gas(84 <T as frame_system::Config>::DbWeight::get()85 .read86 .saturating_mul(reads),87 ))88 }89 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {90 self.recorder91 .consume_gas(T::GasWeightMapping::weight_to_gas(92 <T as frame_system::Config>::DbWeight::get()93 .write94 .saturating_mul(writes),95 ))96 }97 pub fn submit_logs(self) {98 self.recorder.submit_logs()99 }100 pub fn save(self) -> DispatchResult {101 self.recorder.submit_logs();102 <CollectionById<T>>::insert(self.id, self.collection);103 Ok(())104 }105}106impl<T: Config> Deref for CollectionHandle<T> {107 type Target = Collection<T::AccountId>;108109 fn deref(&self) -> &Self::Target {110 &self.collection111 }112}113114impl<T: Config> DerefMut for CollectionHandle<T> {115 fn deref_mut(&mut self) -> &mut Self::Target {116 &mut self.collection117 }118}119120impl<T: Config> CollectionHandle<T> {121 pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {122 ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);123 Ok(())124 }125 pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {126 *subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))127 }128 pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {129 ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);130 Ok(())131 }132 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {133 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)134 }135 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {136 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)137 }138 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {139 ensure!(140 <Allowlist<T>>::get((self.id, user)),141 <Error<T>>::AddressNotInAllowlist142 );143 Ok(())144 }145146 pub fn check_can_update_meta(147 &self,148 subject: &T::CrossAccountId,149 item_owner: &T::CrossAccountId,150 ) -> DispatchResult {151 match self.meta_update_permission {152 MetaUpdatePermission::ItemOwner => {153 ensure!(subject == item_owner, <Error<T>>::NoPermission);154 Ok(())155 }156 MetaUpdatePermission::Admin => self.check_is_owner_or_admin(subject),157 MetaUpdatePermission::None => fail!(<Error<T>>::NoPermission),158 }159 }160}161162#[frame_support::pallet]163pub mod pallet {164 use super::*;165 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key};166 use account::CrossAccountId;167 use frame_support::traits::Currency;168 use up_data_structs::TokenId;169 use scale_info::TypeInfo;170171 #[pallet::config]172 pub trait Config: frame_system::Config + pallet_evm_coder_substrate::Config + TypeInfo {173 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;174175 type CrossAccountId: CrossAccountId<Self::AccountId>;176177 type EvmAddressMapping: pallet_evm::AddressMapping<Self::AccountId>;178 type EvmBackwardsAddressMapping: up_evm_mapping::EvmBackwardsAddressMapping<Self::AccountId>;179180 type Currency: Currency<Self::AccountId>;181182 #[pallet::constant]183 type CollectionCreationPrice: Get<184 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,185 >;186187 type TreasuryAccountId: Get<Self::AccountId>;188 }189190 #[pallet::pallet]191 #[pallet::generate_store(pub(super) trait Store)]192 pub struct Pallet<T>(_);193194 #[pallet::extra_constants]195 impl<T: Config> Pallet<T> {196 pub fn collection_admins_limit() -> u32 {197 COLLECTION_ADMINS_LIMIT198 }199 }200201 #[pallet::event]202 #[pallet::generate_deposit(pub fn deposit_event)]203 pub enum Event<T: Config> {204 /// New collection was created205 ///206 /// # Arguments207 ///208 /// * collection_id: Globally unique identifier of newly created collection.209 ///210 /// * mode: [CollectionMode] converted into u8.211 ///212 /// * account_id: Collection owner.213 CollectionCreated(CollectionId, u8, T::AccountId),214215 /// New collection was destroyed216 ///217 /// # Arguments218 ///219 /// * collection_id: Globally unique identifier of collection.220 CollectionDestroyed(CollectionId),221222 /// New item was created.223 ///224 /// # Arguments225 ///226 /// * collection_id: Id of the collection where item was created.227 ///228 /// * item_id: Id of an item. Unique within the collection.229 ///230 /// * recipient: Owner of newly created item231 ///232 /// * amount: Always 1 for NFT233 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),234235 /// Collection item was burned.236 ///237 /// # Arguments238 ///239 /// * collection_id.240 ///241 /// * item_id: Identifier of burned NFT.242 ///243 /// * owner: which user has destroyed its tokens244 ///245 /// * amount: Always 1 for NFT246 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),247248 /// Item was transferred249 ///250 /// * collection_id: Id of collection to which item is belong251 ///252 /// * item_id: Id of an item253 ///254 /// * sender: Original owner of item255 ///256 /// * recipient: New owner of item257 ///258 /// * amount: Always 1 for NFT259 Transfer(260 CollectionId,261 TokenId,262 T::CrossAccountId,263 T::CrossAccountId,264 u128,265 ),266267 /// * collection_id268 ///269 /// * item_id270 ///271 /// * sender272 ///273 /// * spender274 ///275 /// * amount276 Approved(277 CollectionId,278 TokenId,279 T::CrossAccountId,280 T::CrossAccountId,281 u128,282 ),283 }284285 #[pallet::error]286 pub enum Error<T> {287 /// This collection does not exist.288 CollectionNotFound,289 /// Sender parameter and item owner must be equal.290 MustBeTokenOwner,291 /// No permission to perform action292 NoPermission,293 /// Collection is not in mint mode.294 PublicMintingNotAllowed,295 /// Address is not in allow list.296 AddressNotInAllowlist,297298 /// Collection name can not be longer than 63 char.299 CollectionNameLimitExceeded,300 /// Collection description can not be longer than 255 char.301 CollectionDescriptionLimitExceeded,302 /// Token prefix can not be longer than 15 char.303 CollectionTokenPrefixLimitExceeded,304 /// Total collections bound exceeded.305 TotalCollectionsLimitExceeded,306 /// variable_data exceeded data limit.307 TokenVariableDataLimitExceeded,308 /// Exceeded max admin count309 CollectionAdminCountExceeded,310 /// Collection limit bounds per collection exceeded311 CollectionLimitBoundsExceeded,312 /// Tried to enable permissions which are only permitted to be disabled313 OwnerPermissionsCantBeReverted,314315 /// Collection settings not allowing items transferring316 TransferNotAllowed,317 /// Account token limit exceeded per collection318 AccountTokenLimitExceeded,319 /// Collection token limit exceeded320 CollectionTokenLimitExceeded,321 /// Metadata flag frozen322 MetadataFlagFrozen,323324 /// Item not exists.325 TokenNotFound,326 /// Item balance not enough.327 TokenValueTooLow,328 /// Requested value more than approved.329 ApprovedValueTooLow,330 /// Tried to approve more than owned331 CantApproveMoreThanOwned,332333 /// Can't transfer tokens to ethereum zero address334 AddressIsZero,335 /// Target collection doesn't supports this operation336 UnsupportedOperation,337338 /// Not sufficient founds to perform action339 NotSufficientFounds,340 }341342 #[pallet::storage]343 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;344 #[pallet::storage]345 pub type DestroyedCollectionCount<T> =346 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;347348 /// Collection info349 #[pallet::storage]350 pub type CollectionById<T> = StorageMap<351 Hasher = Blake2_128Concat,352 Key = CollectionId,353 Value = Collection<<T as frame_system::Config>::AccountId>,354 QueryKind = OptionQuery,355 >;356357 #[pallet::storage]358 pub type AdminAmount<T> = StorageMap<359 Hasher = Blake2_128Concat,360 Key = CollectionId,361 Value = u32,362 QueryKind = ValueQuery,363 >;364365 /// List of collection admins366 #[pallet::storage]367 pub type IsAdmin<T: Config> = StorageNMap<368 Key = (369 Key<Blake2_128Concat, CollectionId>,370 Key<Blake2_128Concat, T::CrossAccountId>,371 ),372 Value = bool,373 QueryKind = ValueQuery,374 >;375376 /// Allowlisted collection users377 #[pallet::storage]378 pub type Allowlist<T: Config> = StorageNMap<379 Key = (380 Key<Blake2_128Concat, CollectionId>,381 Key<Blake2_128Concat, T::CrossAccountId>,382 ),383 Value = bool,384 QueryKind = ValueQuery,385 >;386387 /// Not used by code, exists only to provide some types to metadata388 #[pallet::storage]389 pub type DummyStorageValue<T> =390 StorageValue<Value = (CollectionStats, CollectionId, TokenId), QueryKind = OptionQuery>;391}392393impl<T: Config> Pallet<T> {394 /// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens395 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {396 ensure!(397 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,398 <Error<T>>::AddressIsZero399 );400 Ok(())401 }402 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {403 <IsAdmin<T>>::iter_prefix((collection,))404 .map(|(a, _)| a)405 .collect()406 }407 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {408 <Allowlist<T>>::iter_prefix((collection,))409 .map(|(a, _)| a)410 .collect()411 }412 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {413 <Allowlist<T>>::get((collection, user))414 }415 pub fn collection_stats() -> CollectionStats {416 let created = <CreatedCollectionCount<T>>::get();417 let destroyed = <DestroyedCollectionCount<T>>::get();418 CollectionStats {419 created: created.0,420 destroyed: destroyed.0,421 alive: created.0 - destroyed.0,422 }423 }424425 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {426 let collection = <CollectionById<T>>::get(collection);427 if collection.is_none() {428 return None;429 }430 431 let limits = collection.unwrap().limits;432 let effective_limits = CollectionLimits {433 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),434 sponsored_data_size: Some(limits.sponsored_data_size()),435 sponsored_data_rate_limit: Some(436 limits.sponsored_data_rate_limit437 .unwrap_or(SponsoringRateLimit::SponsoringDisabled)),438 token_limit: Some(limits.token_limit()),439 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(MAX_SPONSOR_TIMEOUT)),440 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),441 owner_can_transfer: Some(limits.owner_can_transfer()),442 owner_can_destroy: Some(limits.owner_can_destroy()),443 transfers_enabled: Some(limits.transfers_enabled()),444 };445446 Some(effective_limits)447 }448}449450impl<T: Config> Pallet<T> {451 pub fn init_collection(452 owner: T::AccountId,453 data: CreateCollectionData<T::AccountId>,454 ) -> Result<CollectionId, DispatchError> {455 {456 ensure!(457 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,458 Error::<T>::CollectionTokenPrefixLimitExceeded459 );460 }461462 let created_count = <CreatedCollectionCount<T>>::get()463 .0464 .checked_add(1)465 .ok_or(ArithmeticError::Overflow)?;466 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;467 let id = CollectionId(created_count);468469 // bound Total number of collections470 ensure!(471 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,472 <Error<T>>::TotalCollectionsLimitExceeded473 );474475 // =========476477 let collection = Collection {478 owner: owner.clone(),479 name: data.name,480 mode: data.mode.clone(),481 mint_mode: false,482 access: data.access.unwrap_or_default(),483 description: data.description,484 token_prefix: data.token_prefix,485 offchain_schema: data.offchain_schema,486 schema_version: data.schema_version.unwrap_or_default(),487 sponsorship: data488 .pending_sponsor489 .map(SponsorshipState::Unconfirmed)490 .unwrap_or_default(),491 variable_on_chain_schema: data.variable_on_chain_schema,492 const_on_chain_schema: data.const_on_chain_schema,493 limits: data494 .limits495 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))496 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,497 meta_update_permission: data.meta_update_permission.unwrap_or_default(),498 };499500 // Take a (non-refundable) deposit of collection creation501 {502 let mut imbalance =503 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();504 imbalance.subsume(505 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(506 &T::TreasuryAccountId::get(),507 T::CollectionCreationPrice::get(),508 ),509 );510 <T as Config>::Currency::settle(511 &owner,512 imbalance,513 WithdrawReasons::TRANSFER,514 ExistenceRequirement::KeepAlive,515 )516 .map_err(|_| Error::<T>::NotSufficientFounds)?;517 }518519 <CreatedCollectionCount<T>>::put(created_count);520 <Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));521 <CollectionById<T>>::insert(id, collection);522 Ok(id)523 }524525 pub fn destroy_collection(526 collection: CollectionHandle<T>,527 sender: &T::CrossAccountId,528 ) -> DispatchResult {529 ensure!(530 collection.limits.owner_can_destroy(),531 <Error<T>>::NoPermission,532 );533 collection.check_is_owner(sender)?;534535 let destroyed_collections = <DestroyedCollectionCount<T>>::get()536 .0537 .checked_add(1)538 .ok_or(ArithmeticError::Overflow)?;539540 // =========541542 <DestroyedCollectionCount<T>>::put(destroyed_collections);543 <CollectionById<T>>::remove(collection.id);544 <AdminAmount<T>>::remove(collection.id);545 <IsAdmin<T>>::remove_prefix((collection.id,), None);546 <Allowlist<T>>::remove_prefix((collection.id,), None);547548 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));549 Ok(())550 }551552 pub fn toggle_allowlist(553 collection: &CollectionHandle<T>,554 sender: &T::CrossAccountId,555 user: &T::CrossAccountId,556 allowed: bool,557 ) -> DispatchResult {558 collection.check_is_owner_or_admin(sender)?;559560 // =========561562 if allowed {563 <Allowlist<T>>::insert((collection.id, user), true);564 } else {565 <Allowlist<T>>::remove((collection.id, user));566 }567568 Ok(())569 }570571 pub fn toggle_admin(572 collection: &CollectionHandle<T>,573 sender: &T::CrossAccountId,574 user: &T::CrossAccountId,575 admin: bool,576 ) -> DispatchResult {577 collection.check_is_owner_or_admin(sender)?;578579 let was_admin = <IsAdmin<T>>::get((collection.id, user));580 if was_admin == admin {581 return Ok(());582 }583 let amount = <AdminAmount<T>>::get(collection.id);584585 if admin {586 let amount = amount587 .checked_add(1)588 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;589 ensure!(590 amount <= Self::collection_admins_limit(),591 <Error<T>>::CollectionAdminCountExceeded,592 );593594 // =========595596 <AdminAmount<T>>::insert(collection.id, amount);597 <IsAdmin<T>>::insert((collection.id, user), true);598 } else {599 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));600 <IsAdmin<T>>::remove((collection.id, user));601 }602603 Ok(())604 }605606 pub fn clamp_limits(607 mode: CollectionMode,608 old_limit: &CollectionLimits,609 mut new_limit: CollectionLimits,610 ) -> Result<CollectionLimits, DispatchError> {611 macro_rules! limit_default {612 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{613 $(614 if let Some($new) = $new.$field {615 let $old = $old.$field($($arg)?);616 let _ = $new;617 let _ = $old;618 $check619 } else {620 $new.$field = $old.$field621 }622 )*623 }};624 }625626 limit_default!(old_limit, new_limit,627 account_token_ownership_limit => ensure!(628 new_limit <= MAX_TOKEN_OWNERSHIP,629 <Error<T>>::CollectionLimitBoundsExceeded,630 ),631 sponsor_transfer_timeout(match mode {632 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,633 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,634 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,635 }) => ensure!(636 new_limit <= MAX_SPONSOR_TIMEOUT,637 <Error<T>>::CollectionLimitBoundsExceeded,638 ),639 sponsored_data_size => ensure!(640 new_limit <= CUSTOM_DATA_LIMIT,641 <Error<T>>::CollectionLimitBoundsExceeded,642 ),643 token_limit => ensure!(644 old_limit >= new_limit && new_limit > 0,645 <Error<T>>::CollectionTokenLimitExceeded646 ),647 owner_can_transfer => ensure!(648 old_limit || !new_limit,649 <Error<T>>::OwnerPermissionsCantBeReverted,650 ),651 owner_can_destroy => ensure!(652 old_limit || !new_limit,653 <Error<T>>::OwnerPermissionsCantBeReverted,654 ),655 sponsored_data_rate_limit => {},656 transfers_enabled => {},657 );658 Ok(new_limit)659 }660}661662#[macro_export]663macro_rules! unsupported {664 () => {665 Err(<Error<T>>::UnsupportedOperation.into())666 };667}668669/// Worst cases670pub trait CommonWeightInfo<CrossAccountId> {671 fn create_item() -> Weight;672 fn create_multiple_items(amount: u32) -> Weight;673 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;674 fn burn_item() -> Weight;675 fn transfer() -> Weight;676 fn approve() -> Weight;677 fn transfer_from() -> Weight;678 fn burn_from() -> Weight;679 fn set_variable_metadata(bytes: u32) -> Weight;680}681682pub trait CommonCollectionOperations<T: Config> {683 fn create_item(684 &self,685 sender: T::CrossAccountId,686 to: T::CrossAccountId,687 data: CreateItemData,688 ) -> DispatchResultWithPostInfo;689 fn create_multiple_items(690 &self,691 sender: T::CrossAccountId,692 to: T::CrossAccountId,693 data: Vec<CreateItemData>,694 ) -> DispatchResultWithPostInfo;695 fn create_multiple_items_ex(696 &self,697 sender: T::CrossAccountId,698 data: CreateItemExData<T::CrossAccountId>,699 ) -> DispatchResultWithPostInfo;700 fn burn_item(701 &self,702 sender: T::CrossAccountId,703 token: TokenId,704 amount: u128,705 ) -> DispatchResultWithPostInfo;706707 fn transfer(708 &self,709 sender: T::CrossAccountId,710 to: T::CrossAccountId,711 token: TokenId,712 amount: u128,713 ) -> DispatchResultWithPostInfo;714 fn approve(715 &self,716 sender: T::CrossAccountId,717 spender: T::CrossAccountId,718 token: TokenId,719 amount: u128,720 ) -> DispatchResultWithPostInfo;721 fn transfer_from(722 &self,723 sender: T::CrossAccountId,724 from: T::CrossAccountId,725 to: T::CrossAccountId,726 token: TokenId,727 amount: u128,728 ) -> DispatchResultWithPostInfo;729 fn burn_from(730 &self,731 sender: T::CrossAccountId,732 from: T::CrossAccountId,733 token: TokenId,734 amount: u128,735 ) -> DispatchResultWithPostInfo;736737 fn set_variable_metadata(738 &self,739 sender: T::CrossAccountId,740 token: TokenId,741 data: BoundedVec<u8, CustomDataLimit>,742 ) -> DispatchResultWithPostInfo;743744 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;745 fn token_exists(&self, token: TokenId) -> bool;746 fn last_token_id(&self) -> TokenId;747748 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;749 fn const_metadata(&self, token: TokenId) -> Vec<u8>;750 fn variable_metadata(&self, token: TokenId) -> Vec<u8>;751752 /// How many tokens collection contains (Applicable to nonfungible/refungible)753 fn collection_tokens(&self) -> u32;754 /// Amount of different tokens account has (Applicable to nonfungible/refungible)755 fn account_balance(&self, account: T::CrossAccountId) -> u32;756 /// Amount of specific token account have (Applicable to fungible/refungible)757 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;758 fn allowance(759 &self,760 sender: T::CrossAccountId,761 spender: T::CrossAccountId,762 token: TokenId,763 ) -> u128;764}765766// Flexible enough for implementing CommonCollectionOperations767pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {768 let post_info = PostDispatchInfo {769 actual_weight: Some(weight),770 pays_fee: Pays::Yes,771 };772 match res {773 Ok(()) => Ok(post_info),774 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),775 }776}primitives/rpc/src/lib.rsdiffbeforeafterboth--- a/primitives/rpc/src/lib.rs
+++ b/primitives/rpc/src/lib.rs
@@ -16,7 +16,7 @@
#![cfg_attr(not(feature = "std"), no_std)]
-use up_data_structs::{CollectionId, TokenId, Collection, CollectionStats};
+use up_data_structs::{CollectionId, TokenId, Collection, CollectionStats, CollectionLimits};
use sp_std::vec::Vec;
use sp_core::H160;
use codec::Decode;
@@ -59,5 +59,6 @@
fn last_token_id(collection: CollectionId) -> Result<TokenId>;
fn collection_by_id(collection: CollectionId) -> Result<Option<Collection<AccountId>>>;
fn collection_stats() -> Result<CollectionStats>;
+ fn effective_collection_limits(collection_id: CollectionId) -> Result<Option<CollectionLimits>>;
}
}
runtime/common/src/runtime_apis.rsdiffbeforeafterboth--- a/runtime/common/src/runtime_apis.rs
+++ b/runtime/common/src/runtime_apis.rs
@@ -69,6 +69,10 @@
fn collection_stats() -> Result<CollectionStats, DispatchError> {
Ok(<pallet_common::Pallet<Runtime>>::collection_stats())
}
+
+ fn effective_collection_limits(collection: CollectionId) -> Result<Option<CollectionLimits>, DispatchError> {
+ Ok(<pallet_common::Pallet<Runtime>>::effective_collection_limits(collection))
+ }
}
impl sp_api::Core<Block> for Runtime {
tests/src/interfaces/augment-api-errors.tsdiffbeforeafterboth--- a/tests/src/interfaces/augment-api-errors.ts
+++ b/tests/src/interfaces/augment-api-errors.ts
@@ -105,6 +105,10 @@
**/
NoPermission: AugmentedError<ApiType>;
/**
+ * Not sufficient founds to perform action
+ **/
+ NotSufficientFounds: AugmentedError<ApiType>;
+ /**
* Tried to enable permissions which are only permitted to be disabled
**/
OwnerPermissionsCantBeReverted: AugmentedError<ApiType>;
tests/src/interfaces/augment-api-rpc.tsdiffbeforeafterboth--- a/tests/src/interfaces/augment-api-rpc.ts
+++ b/tests/src/interfaces/augment-api-rpc.ts
@@ -1,7 +1,7 @@
// Auto-generated via `yarn polkadot-types-from-chain`, do not edit
/* eslint-disable */
-import type { PalletCommonAccountBasicCrossAccountIdRepr, UpDataStructsCollection, UpDataStructsCollectionStats } from './unique';
+import type { PalletCommonAccountBasicCrossAccountIdRepr, UpDataStructsCollection, UpDataStructsCollectionLimits, UpDataStructsCollectionStats } from './unique';
import type { AugmentedRpc } from '@polkadot/rpc-core/types';
import type { Metadata, StorageKey } from '@polkadot/types';
import type { Bytes, HashMap, Json, Null, Option, Text, U256, U64, Vec, bool, u128, u32, u64 } from '@polkadot/types-codec';
@@ -604,6 +604,10 @@
**/
constMetadata: AugmentedRpc<(collection: u32 | AnyNumber | Uint8Array, tokenId: u32 | AnyNumber | Uint8Array, at?: Hash | string | Uint8Array) => Observable<Bytes>>;
/**
+ * Get effective collection limits
+ **/
+ effectiveCollectionLimits: AugmentedRpc<(collection: u32 | AnyNumber | Uint8Array, at?: Hash | string | Uint8Array) => Observable<Option<UpDataStructsCollectionLimits>>>;
+ /**
* Get last token id
**/
lastTokenId: AugmentedRpc<(collection: u32 | AnyNumber | Uint8Array, at?: Hash | string | Uint8Array) => Observable<u32>>;
tests/src/interfaces/augment-types.tsdiffbeforeafterboth--- a/tests/src/interfaces/augment-types.ts
+++ b/tests/src/interfaces/augment-types.ts
@@ -1,7 +1,7 @@
// Auto-generated via `yarn polkadot-types-from-defs`, do not edit
/* eslint-disable */
-import type { CumulusPalletDmpQueueCall, CumulusPalletDmpQueueConfigData, CumulusPalletDmpQueueError, CumulusPalletDmpQueueEvent, CumulusPalletDmpQueuePageIndexData, CumulusPalletParachainSystemCall, CumulusPalletParachainSystemError, CumulusPalletParachainSystemEvent, CumulusPalletParachainSystemRelayStateSnapshotMessagingStateSnapshot, CumulusPalletXcmCall, CumulusPalletXcmError, CumulusPalletXcmEvent, CumulusPalletXcmpQueueCall, CumulusPalletXcmpQueueError, CumulusPalletXcmpQueueEvent, CumulusPalletXcmpQueueInboundChannelDetails, CumulusPalletXcmpQueueInboundState, CumulusPalletXcmpQueueOutboundChannelDetails, CumulusPalletXcmpQueueOutboundState, CumulusPalletXcmpQueueQueueConfigData, CumulusPrimitivesParachainInherentParachainInherentData, EthbloomBloom, EthereumBlock, EthereumHeader, EthereumLog, EthereumReceiptEip658ReceiptData, EthereumReceiptReceiptV3, EthereumTransactionAccessListItem, EthereumTransactionEip1559Transaction, EthereumTransactionEip2930Transaction, EthereumTransactionLegacyTransaction, EthereumTransactionTransactionAction, EthereumTransactionTransactionSignature, EthereumTransactionTransactionV2, EthereumTypesHashH64, EvmCoreErrorExitError, EvmCoreErrorExitFatal, EvmCoreErrorExitReason, EvmCoreErrorExitRevert, EvmCoreErrorExitSucceed, FpRpcTransactionStatus, FrameSupportPalletId, FrameSupportTokensMiscBalanceStatus, FrameSupportWeightsDispatchClass, FrameSupportWeightsDispatchInfo, FrameSupportWeightsPays, FrameSupportWeightsPerDispatchClassU32, FrameSupportWeightsPerDispatchClassU64, FrameSupportWeightsPerDispatchClassWeightsPerClass, FrameSupportWeightsRuntimeDbWeight, FrameSupportWeightsWeightToFeeCoefficient, FrameSystemAccountInfo, FrameSystemCall, FrameSystemError, FrameSystemEvent, FrameSystemEventRecord, FrameSystemExtensionsCheckGenesis, FrameSystemExtensionsCheckNonce, FrameSystemExtensionsCheckSpecVersion, FrameSystemExtensionsCheckWeight, FrameSystemLastRuntimeUpgradeInfo, FrameSystemLimitsBlockLength, FrameSystemLimitsBlockWeights, FrameSystemLimitsWeightsPerClass, FrameSystemPhase, OrmlVestingModuleCall, OrmlVestingModuleError, OrmlVestingModuleEvent, OrmlVestingVestingSchedule, PalletBalancesAccountData, PalletBalancesBalanceLock, PalletBalancesCall, PalletBalancesError, PalletBalancesEvent, PalletBalancesReasons, PalletBalancesReleases, PalletBalancesReserveData, PalletCommonAccountBasicCrossAccountIdRepr, PalletCommonError, PalletCommonEvent, PalletEthereumCall, PalletEthereumError, PalletEthereumEvent, PalletEvmCall, PalletEvmCoderSubstrateError, PalletEvmContractHelpersError, PalletEvmContractHelpersSponsoringModeT, PalletEvmError, PalletEvmEvent, PalletEvmMigrationCall, PalletEvmMigrationError, PalletFungibleError, PalletInflationCall, PalletNonfungibleError, PalletNonfungibleItemData, PalletRefungibleError, PalletRefungibleItemData, PalletSudoCall, PalletSudoError, PalletSudoEvent, PalletTemplateTransactionPaymentCall, PalletTemplateTransactionPaymentChargeTransactionPayment, PalletTimestampCall, PalletTransactionPaymentReleases, PalletTreasuryCall, PalletTreasuryError, PalletTreasuryEvent, PalletTreasuryProposal, PalletUniqueCall, PalletUniqueError, PalletUniqueRawEvent, PalletXcmCall, PalletXcmError, PalletXcmEvent, PolkadotCorePrimitivesInboundDownwardMessage, PolkadotCorePrimitivesInboundHrmpMessage, PolkadotCorePrimitivesOutboundHrmpMessage, PolkadotParachainPrimitivesXcmpMessageFormat, PolkadotPrimitivesV1AbridgedHostConfiguration, PolkadotPrimitivesV1AbridgedHrmpChannel, PolkadotPrimitivesV1PersistedValidationData, PolkadotPrimitivesV1UpgradeRestriction, SpCoreEcdsaSignature, SpCoreEd25519Signature, SpCoreSr25519Signature, SpRuntimeArithmeticError, SpRuntimeDigest, SpRuntimeDigestDigestItem, SpRuntimeDispatchError, SpRuntimeModuleError, SpRuntimeMultiSignature, SpRuntimeTokenError, SpTrieStorageProof, SpVersionRuntimeVersion, UniqueRuntimeRuntime, UpDataStructsAccessMode, UpDataStructsCollection, UpDataStructsCollectionLimits, UpDataStructsCollectionMode, UpDataStructsCollectionStats, UpDataStructsCreateCollectionData, UpDataStructsCreateFungibleData, UpDataStructsCreateItemData, UpDataStructsCreateItemExData, UpDataStructsCreateNftData, UpDataStructsCreateNftExData, UpDataStructsCreateReFungibleData, UpDataStructsCreateRefungibleExData, UpDataStructsMetaUpdatePermission, UpDataStructsSchemaVersion, UpDataStructsSponsoringRateLimit, UpDataStructsSponsorshipState, XcmDoubleEncoded, XcmV0Junction, XcmV0JunctionBodyId, XcmV0JunctionBodyPart, XcmV0JunctionNetworkId, XcmV0MultiAsset, XcmV0MultiLocation, XcmV0Order, XcmV0OriginKind, XcmV0Response, XcmV0Xcm, XcmV1Junction, XcmV1MultiAsset, XcmV1MultiLocation, XcmV1MultiassetAssetId, XcmV1MultiassetAssetInstance, XcmV1MultiassetFungibility, XcmV1MultiassetMultiAssetFilter, XcmV1MultiassetMultiAssets, XcmV1MultiassetWildFungibility, XcmV1MultiassetWildMultiAsset, XcmV1MultilocationJunctions, XcmV1Order, XcmV1Response, XcmV1Xcm, XcmV2Instruction, XcmV2Response, XcmV2TraitsError, XcmV2TraitsOutcome, XcmV2WeightLimit, XcmV2Xcm, XcmVersionedMultiAssets, XcmVersionedMultiLocation, XcmVersionedXcm } from './unique';
+import type { CumulusPalletDmpQueueCall, CumulusPalletDmpQueueConfigData, CumulusPalletDmpQueueError, CumulusPalletDmpQueueEvent, CumulusPalletDmpQueuePageIndexData, CumulusPalletParachainSystemCall, CumulusPalletParachainSystemError, CumulusPalletParachainSystemEvent, CumulusPalletParachainSystemRelayStateSnapshotMessagingStateSnapshot, CumulusPalletXcmCall, CumulusPalletXcmError, CumulusPalletXcmEvent, CumulusPalletXcmpQueueCall, CumulusPalletXcmpQueueError, CumulusPalletXcmpQueueEvent, CumulusPalletXcmpQueueInboundChannelDetails, CumulusPalletXcmpQueueInboundState, CumulusPalletXcmpQueueOutboundChannelDetails, CumulusPalletXcmpQueueOutboundState, CumulusPalletXcmpQueueQueueConfigData, CumulusPrimitivesParachainInherentParachainInherentData, EthbloomBloom, EthereumBlock, EthereumHeader, EthereumLog, EthereumReceiptEip658ReceiptData, EthereumReceiptReceiptV3, EthereumTransactionAccessListItem, EthereumTransactionEip1559Transaction, EthereumTransactionEip2930Transaction, EthereumTransactionLegacyTransaction, EthereumTransactionTransactionAction, EthereumTransactionTransactionSignature, EthereumTransactionTransactionV2, EthereumTypesHashH64, EvmCoreErrorExitError, EvmCoreErrorExitFatal, EvmCoreErrorExitReason, EvmCoreErrorExitRevert, EvmCoreErrorExitSucceed, FpRpcTransactionStatus, FrameSupportPalletId, FrameSupportTokensMiscBalanceStatus, FrameSupportWeightsDispatchClass, FrameSupportWeightsDispatchInfo, FrameSupportWeightsPays, FrameSupportWeightsPerDispatchClassU32, FrameSupportWeightsPerDispatchClassU64, FrameSupportWeightsPerDispatchClassWeightsPerClass, FrameSupportWeightsRuntimeDbWeight, FrameSupportWeightsWeightToFeeCoefficient, FrameSystemAccountInfo, FrameSystemCall, FrameSystemError, FrameSystemEvent, FrameSystemEventRecord, FrameSystemExtensionsCheckGenesis, FrameSystemExtensionsCheckNonce, FrameSystemExtensionsCheckSpecVersion, FrameSystemExtensionsCheckWeight, FrameSystemLastRuntimeUpgradeInfo, FrameSystemLimitsBlockLength, FrameSystemLimitsBlockWeights, FrameSystemLimitsWeightsPerClass, FrameSystemPhase, OpalRuntimeRuntime, OrmlVestingModuleCall, OrmlVestingModuleError, OrmlVestingModuleEvent, OrmlVestingVestingSchedule, PalletBalancesAccountData, PalletBalancesBalanceLock, PalletBalancesCall, PalletBalancesError, PalletBalancesEvent, PalletBalancesReasons, PalletBalancesReleases, PalletBalancesReserveData, PalletCommonAccountBasicCrossAccountIdRepr, PalletCommonError, PalletCommonEvent, PalletEthereumCall, PalletEthereumError, PalletEthereumEvent, PalletEvmCall, PalletEvmCoderSubstrateError, PalletEvmContractHelpersError, PalletEvmContractHelpersSponsoringModeT, PalletEvmError, PalletEvmEvent, PalletEvmMigrationCall, PalletEvmMigrationError, PalletFungibleError, PalletInflationCall, PalletNonfungibleError, PalletNonfungibleItemData, PalletRefungibleError, PalletRefungibleItemData, PalletSudoCall, PalletSudoError, PalletSudoEvent, PalletTemplateTransactionPaymentCall, PalletTemplateTransactionPaymentChargeTransactionPayment, PalletTimestampCall, PalletTransactionPaymentReleases, PalletTreasuryCall, PalletTreasuryError, PalletTreasuryEvent, PalletTreasuryProposal, PalletUniqueCall, PalletUniqueError, PalletUniqueRawEvent, PalletXcmCall, PalletXcmError, PalletXcmEvent, PolkadotCorePrimitivesInboundDownwardMessage, PolkadotCorePrimitivesInboundHrmpMessage, PolkadotCorePrimitivesOutboundHrmpMessage, PolkadotParachainPrimitivesXcmpMessageFormat, PolkadotPrimitivesV1AbridgedHostConfiguration, PolkadotPrimitivesV1AbridgedHrmpChannel, PolkadotPrimitivesV1PersistedValidationData, PolkadotPrimitivesV1UpgradeRestriction, SpCoreEcdsaSignature, SpCoreEd25519Signature, SpCoreSr25519Signature, SpRuntimeArithmeticError, SpRuntimeDigest, SpRuntimeDigestDigestItem, SpRuntimeDispatchError, SpRuntimeModuleError, SpRuntimeMultiSignature, SpRuntimeTokenError, SpTrieStorageProof, SpVersionRuntimeVersion, UpDataStructsAccessMode, UpDataStructsCollection, UpDataStructsCollectionLimits, UpDataStructsCollectionMode, UpDataStructsCollectionStats, UpDataStructsCreateCollectionData, UpDataStructsCreateFungibleData, UpDataStructsCreateItemData, UpDataStructsCreateItemExData, UpDataStructsCreateNftData, UpDataStructsCreateNftExData, UpDataStructsCreateReFungibleData, UpDataStructsCreateRefungibleExData, UpDataStructsMetaUpdatePermission, UpDataStructsSchemaVersion, UpDataStructsSponsoringRateLimit, UpDataStructsSponsorshipState, XcmDoubleEncoded, XcmV0Junction, XcmV0JunctionBodyId, XcmV0JunctionBodyPart, XcmV0JunctionNetworkId, XcmV0MultiAsset, XcmV0MultiLocation, XcmV0Order, XcmV0OriginKind, XcmV0Response, XcmV0Xcm, XcmV1Junction, XcmV1MultiAsset, XcmV1MultiLocation, XcmV1MultiassetAssetId, XcmV1MultiassetAssetInstance, XcmV1MultiassetFungibility, XcmV1MultiassetMultiAssetFilter, XcmV1MultiassetMultiAssets, XcmV1MultiassetWildFungibility, XcmV1MultiassetWildMultiAsset, XcmV1MultilocationJunctions, XcmV1Order, XcmV1Response, XcmV1Xcm, XcmV2Instruction, XcmV2Response, XcmV2TraitsError, XcmV2TraitsOutcome, XcmV2WeightLimit, XcmV2Xcm, XcmVersionedMultiAssets, XcmVersionedMultiLocation, XcmVersionedXcm } from './unique';
import type { Data, StorageKey } from '@polkadot/types';
import type { BitVec, Bool, Bytes, I128, I16, I256, I32, I64, I8, Json, Null, Raw, Text, Type, U128, U16, U256, U32, U64, U8, USize, bool, i128, i16, i256, i32, i64, i8, u128, u16, u256, u32, u64, u8, usize } from '@polkadot/types-codec';
import type { AssetApproval, AssetApprovalKey, AssetBalance, AssetDestroyWitness, AssetDetails, AssetMetadata, TAssetBalance, TAssetDepositBalance } from '@polkadot/types/interfaces/assets';
@@ -18,7 +18,7 @@
import type { StatementKind } from '@polkadot/types/interfaces/claims';
import type { CollectiveOrigin, MemberCount, ProposalIndex, Votes, VotesTo230 } from '@polkadot/types/interfaces/collective';
import type { AuthorityId, RawVRFOutput } from '@polkadot/types/interfaces/consensus';
-import type { AliveContractInfo, CodeHash, CodeUploadRequest, CodeUploadResult, CodeUploadResultValue, ContractCallFlags, ContractCallRequest, ContractExecResult, ContractExecResultErr, ContractExecResultErrModule, ContractExecResultOk, ContractExecResultResult, ContractExecResultSuccessTo255, ContractExecResultSuccessTo260, ContractExecResultTo255, ContractExecResultTo260, ContractExecResultTo267, ContractInfo, ContractInstantiateResult, ContractInstantiateResultTo267, ContractReturnFlags, ContractStorageKey, DeletedContract, ExecReturnValue, Gas, HostFnWeights, HostFnWeightsTo264, InstantiateRequest, InstantiateReturnValue, InstantiateReturnValueTo267, InstructionWeights, Limits, LimitsTo264, PrefabWasmModule, RentProjection, Schedule, ScheduleTo212, ScheduleTo258, ScheduleTo264, SeedOf, StorageDeposit, TombstoneContractInfo, TrieId } from '@polkadot/types/interfaces/contracts';
+import type { AliveContractInfo, CodeHash, CodeSource, CodeUploadRequest, CodeUploadResult, CodeUploadResultValue, ContractCallFlags, ContractCallRequest, ContractExecResult, ContractExecResultErr, ContractExecResultErrModule, ContractExecResultOk, ContractExecResultResult, ContractExecResultSuccessTo255, ContractExecResultSuccessTo260, ContractExecResultTo255, ContractExecResultTo260, ContractExecResultTo267, ContractInfo, ContractInstantiateResult, ContractInstantiateResultTo267, ContractInstantiateResultTo299, ContractReturnFlags, ContractStorageKey, DeletedContract, ExecReturnValue, Gas, HostFnWeights, HostFnWeightsTo264, InstantiateRequest, InstantiateRequestV1, InstantiateRequestV2, InstantiateReturnValue, InstantiateReturnValueOk, InstantiateReturnValueTo267, InstructionWeights, Limits, LimitsTo264, PrefabWasmModule, RentProjection, Schedule, ScheduleTo212, ScheduleTo258, ScheduleTo264, SeedOf, StorageDeposit, TombstoneContractInfo, TrieId } from '@polkadot/types/interfaces/contracts';
import type { ContractConstructorSpecLatest, ContractConstructorSpecV0, ContractConstructorSpecV1, ContractConstructorSpecV2, ContractConstructorSpecV3, ContractContractSpecV0, ContractContractSpecV1, ContractContractSpecV2, ContractContractSpecV3, ContractCryptoHasher, ContractDiscriminant, ContractDisplayName, ContractEventParamSpecLatest, ContractEventParamSpecV0, ContractEventParamSpecV2, ContractEventSpecLatest, ContractEventSpecV0, ContractEventSpecV1, ContractEventSpecV2, ContractLayoutArray, ContractLayoutCell, ContractLayoutEnum, ContractLayoutHash, ContractLayoutHashingStrategy, ContractLayoutKey, ContractLayoutStruct, ContractLayoutStructField, ContractMessageParamSpecLatest, ContractMessageParamSpecV0, ContractMessageParamSpecV2, ContractMessageSpecLatest, ContractMessageSpecV0, ContractMessageSpecV1, ContractMessageSpecV2, ContractMetadata, ContractMetadataLatest, ContractMetadataV0, ContractMetadataV1, ContractMetadataV2, ContractMetadataV3, ContractProject, ContractProjectContract, ContractProjectInfo, ContractProjectSource, ContractProjectV0, ContractSelector, ContractStorageLayout, ContractTypeSpec } from '@polkadot/types/interfaces/contractsAbi';
import type { FundIndex, FundInfo, LastContribution, TrieIndex } from '@polkadot/types/interfaces/crowdloan';
import type { ConfigData, MessageId, OverweightIndex, PageCounter, PageIndexData } from '@polkadot/types/interfaces/cumulus';
@@ -198,6 +198,7 @@
ClassMetadata: ClassMetadata;
CodecHash: CodecHash;
CodeHash: CodeHash;
+ CodeSource: CodeSource;
CodeUploadRequest: CodeUploadRequest;
CodeUploadResult: CodeUploadResult;
CodeUploadResultValue: CodeUploadResultValue;
@@ -250,6 +251,7 @@
ContractInfo: ContractInfo;
ContractInstantiateResult: ContractInstantiateResult;
ContractInstantiateResultTo267: ContractInstantiateResultTo267;
+ ContractInstantiateResultTo299: ContractInstantiateResultTo299;
ContractLayoutArray: ContractLayoutArray;
ContractLayoutCell: ContractLayoutCell;
ContractLayoutEnum: ContractLayoutEnum;
@@ -591,7 +593,10 @@
InstanceId: InstanceId;
InstanceMetadata: InstanceMetadata;
InstantiateRequest: InstantiateRequest;
+ InstantiateRequestV1: InstantiateRequestV1;
+ InstantiateRequestV2: InstantiateRequestV2;
InstantiateReturnValue: InstantiateReturnValue;
+ InstantiateReturnValueOk: InstantiateReturnValueOk;
InstantiateReturnValueTo267: InstantiateReturnValueTo267;
InstructionV2: InstructionV2;
InstructionWeights: InstructionWeights;
@@ -707,6 +712,7 @@
OffchainAccuracyCompact: OffchainAccuracyCompact;
OffenceDetails: OffenceDetails;
Offender: Offender;
+ OpalRuntimeRuntime: OpalRuntimeRuntime;
OpaqueCall: OpaqueCall;
OpaqueMultiaddr: OpaqueMultiaddr;
OpaqueNetworkState: OpaqueNetworkState;
@@ -1146,7 +1152,6 @@
UnappliedSlash: UnappliedSlash;
UnappliedSlashOther: UnappliedSlashOther;
UncleEntryItem: UncleEntryItem;
- UniqueRuntimeRuntime: UniqueRuntimeRuntime;
UnknownTransaction: UnknownTransaction;
UnlockChunk: UnlockChunk;
UnrewardedRelayer: UnrewardedRelayer;
tests/src/interfaces/lookup.tsdiffbeforeafterboth--- a/tests/src/interfaces/lookup.ts
+++ b/tests/src/interfaces/lookup.ts
@@ -1756,7 +1756,7 @@
}
},
/**
- * Lookup225: frame_system::EventRecord<unique_runtime::Event, primitive_types::H256>
+ * Lookup225: frame_system::EventRecord<opal_runtime::Event, primitive_types::H256>
**/
FrameSystemEventRecord: {
phase: 'FrameSystemPhase',
@@ -2240,7 +2240,7 @@
* Lookup297: pallet_common::pallet::Error<T>
**/
PalletCommonError: {
- _enum: ['CollectionNotFound', 'MustBeTokenOwner', 'NoPermission', 'PublicMintingNotAllowed', 'AddressNotInAllowlist', 'CollectionNameLimitExceeded', 'CollectionDescriptionLimitExceeded', 'CollectionTokenPrefixLimitExceeded', 'TotalCollectionsLimitExceeded', 'TokenVariableDataLimitExceeded', 'CollectionAdminCountExceeded', 'CollectionLimitBoundsExceeded', 'OwnerPermissionsCantBeReverted', 'TransferNotAllowed', 'AccountTokenLimitExceeded', 'CollectionTokenLimitExceeded', 'MetadataFlagFrozen', 'TokenNotFound', 'TokenValueTooLow', 'ApprovedValueTooLow', 'CantApproveMoreThanOwned', 'AddressIsZero', 'UnsupportedOperation']
+ _enum: ['CollectionNotFound', 'MustBeTokenOwner', 'NoPermission', 'PublicMintingNotAllowed', 'AddressNotInAllowlist', 'CollectionNameLimitExceeded', 'CollectionDescriptionLimitExceeded', 'CollectionTokenPrefixLimitExceeded', 'TotalCollectionsLimitExceeded', 'TokenVariableDataLimitExceeded', 'CollectionAdminCountExceeded', 'CollectionLimitBoundsExceeded', 'OwnerPermissionsCantBeReverted', 'TransferNotAllowed', 'AccountTokenLimitExceeded', 'CollectionTokenLimitExceeded', 'MetadataFlagFrozen', 'TokenNotFound', 'TokenValueTooLow', 'ApprovedValueTooLow', 'CantApproveMoreThanOwned', 'AddressIsZero', 'UnsupportedOperation', 'NotSufficientFounds']
},
/**
* Lookup299: pallet_fungible::pallet::Error<T>
@@ -2417,11 +2417,11 @@
**/
FrameSystemExtensionsCheckWeight: 'Null',
/**
- * Lookup345: pallet_template_transaction_payment::ChargeTransactionPayment<unique_runtime::Runtime>
+ * Lookup345: pallet_template_transaction_payment::ChargeTransactionPayment<opal_runtime::Runtime>
**/
PalletTemplateTransactionPaymentChargeTransactionPayment: 'Compact<u128>',
/**
- * Lookup346: unique_runtime::Runtime
+ * Lookup346: opal_runtime::Runtime
**/
- UniqueRuntimeRuntime: 'Null'
+ OpalRuntimeRuntime: 'Null'
};
tests/src/interfaces/types-lookup.tsdiffbeforeafterboth--- a/tests/src/interfaces/types-lookup.ts
+++ b/tests/src/interfaces/types-lookup.ts
@@ -2462,7 +2462,8 @@
readonly isCantApproveMoreThanOwned: boolean;
readonly isAddressIsZero: boolean;
readonly isUnsupportedOperation: boolean;
- readonly type: 'CollectionNotFound' | 'MustBeTokenOwner' | 'NoPermission' | 'PublicMintingNotAllowed' | 'AddressNotInAllowlist' | 'CollectionNameLimitExceeded' | 'CollectionDescriptionLimitExceeded' | 'CollectionTokenPrefixLimitExceeded' | 'TotalCollectionsLimitExceeded' | 'TokenVariableDataLimitExceeded' | 'CollectionAdminCountExceeded' | 'CollectionLimitBoundsExceeded' | 'OwnerPermissionsCantBeReverted' | 'TransferNotAllowed' | 'AccountTokenLimitExceeded' | 'CollectionTokenLimitExceeded' | 'MetadataFlagFrozen' | 'TokenNotFound' | 'TokenValueTooLow' | 'ApprovedValueTooLow' | 'CantApproveMoreThanOwned' | 'AddressIsZero' | 'UnsupportedOperation';
+ readonly isNotSufficientFounds: boolean;
+ readonly type: 'CollectionNotFound' | 'MustBeTokenOwner' | 'NoPermission' | 'PublicMintingNotAllowed' | 'AddressNotInAllowlist' | 'CollectionNameLimitExceeded' | 'CollectionDescriptionLimitExceeded' | 'CollectionTokenPrefixLimitExceeded' | 'TotalCollectionsLimitExceeded' | 'TokenVariableDataLimitExceeded' | 'CollectionAdminCountExceeded' | 'CollectionLimitBoundsExceeded' | 'OwnerPermissionsCantBeReverted' | 'TransferNotAllowed' | 'AccountTokenLimitExceeded' | 'CollectionTokenLimitExceeded' | 'MetadataFlagFrozen' | 'TokenNotFound' | 'TokenValueTooLow' | 'ApprovedValueTooLow' | 'CantApproveMoreThanOwned' | 'AddressIsZero' | 'UnsupportedOperation' | 'NotSufficientFounds';
}
/** @name PalletFungibleError (299) */
@@ -2643,7 +2644,7 @@
/** @name PalletTemplateTransactionPaymentChargeTransactionPayment (345) */
export interface PalletTemplateTransactionPaymentChargeTransactionPayment extends Compact<u128> {}
- /** @name UniqueRuntimeRuntime (346) */
- export type UniqueRuntimeRuntime = Null;
+ /** @name OpalRuntimeRuntime (346) */
+ export type OpalRuntimeRuntime = Null;
} // declare module
tests/src/interfaces/unique/definitions.tsdiffbeforeafterboth--- a/tests/src/interfaces/unique/definitions.ts
+++ b/tests/src/interfaces/unique/definitions.ts
@@ -55,5 +55,6 @@
collectionById: fun('Get collection by specified id', [collectionParam], 'Option<UpDataStructsCollection>'),
collectionStats: fun('Get collection stats', [], 'UpDataStructsCollectionStats'),
allowed: fun('Check if user is allowed to use collection', [collectionParam, crossAccountParam()], 'bool'),
+ effectiveCollectionLimits: fun('Get effective collection limits', [collectionParam], 'Option<UpDataStructsCollectionLimits>'),
},
};
tests/src/interfaces/unique/types.tsdiffbeforeafterboth--- a/tests/src/interfaces/unique/types.ts
+++ b/tests/src/interfaces/unique/types.ts
@@ -654,6 +654,9 @@
readonly type: 'ApplyExtrinsic' | 'Finalization' | 'Initialization';
}
+/** @name OpalRuntimeRuntime */
+export interface OpalRuntimeRuntime extends Null {}
+
/** @name OrmlVestingModuleCall */
export interface OrmlVestingModuleCall extends Enum {
readonly isClaim: boolean;
@@ -892,7 +895,8 @@
readonly isCantApproveMoreThanOwned: boolean;
readonly isAddressIsZero: boolean;
readonly isUnsupportedOperation: boolean;
- readonly type: 'CollectionNotFound' | 'MustBeTokenOwner' | 'NoPermission' | 'PublicMintingNotAllowed' | 'AddressNotInAllowlist' | 'CollectionNameLimitExceeded' | 'CollectionDescriptionLimitExceeded' | 'CollectionTokenPrefixLimitExceeded' | 'TotalCollectionsLimitExceeded' | 'TokenVariableDataLimitExceeded' | 'CollectionAdminCountExceeded' | 'CollectionLimitBoundsExceeded' | 'OwnerPermissionsCantBeReverted' | 'TransferNotAllowed' | 'AccountTokenLimitExceeded' | 'CollectionTokenLimitExceeded' | 'MetadataFlagFrozen' | 'TokenNotFound' | 'TokenValueTooLow' | 'ApprovedValueTooLow' | 'CantApproveMoreThanOwned' | 'AddressIsZero' | 'UnsupportedOperation';
+ readonly isNotSufficientFounds: boolean;
+ readonly type: 'CollectionNotFound' | 'MustBeTokenOwner' | 'NoPermission' | 'PublicMintingNotAllowed' | 'AddressNotInAllowlist' | 'CollectionNameLimitExceeded' | 'CollectionDescriptionLimitExceeded' | 'CollectionTokenPrefixLimitExceeded' | 'TotalCollectionsLimitExceeded' | 'TokenVariableDataLimitExceeded' | 'CollectionAdminCountExceeded' | 'CollectionLimitBoundsExceeded' | 'OwnerPermissionsCantBeReverted' | 'TransferNotAllowed' | 'AccountTokenLimitExceeded' | 'CollectionTokenLimitExceeded' | 'MetadataFlagFrozen' | 'TokenNotFound' | 'TokenValueTooLow' | 'ApprovedValueTooLow' | 'CantApproveMoreThanOwned' | 'AddressIsZero' | 'UnsupportedOperation' | 'NotSufficientFounds';
}
/** @name PalletCommonEvent */
@@ -1721,9 +1725,6 @@
readonly transactionVersion: u32;
readonly stateVersion: u8;
}
-
-/** @name UniqueRuntimeRuntime */
-export interface UniqueRuntimeRuntime extends Null {}
/** @name UpDataStructsAccessMode */
export interface UpDataStructsAccessMode extends Enum {
tests/src/limits.test.tsdiffbeforeafterboth--- a/tests/src/limits.test.ts
+++ b/tests/src/limits.test.ts
@@ -396,3 +396,51 @@
//expect(aliceBalanceAfterSponsoredTransaction1).to.be.lessThan(aliceBalanceBefore);
});
});
+
+describe.only('Effective collection limits', () => {
+ it('Test1', async () => {
+ await usingApi(async (api) => {
+ const collectionId = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
+
+ {
+ const collection = await api.rpc.unique.collectionById(collectionId);
+ expect(collection.isSome).to.be.true;
+ const limits = collection.unwrap().limits;
+ expect(limits).to.be.any;
+
+ // Check that limits is undefined
+ expect(limits.accountTokenOwnershipLimit.isNone).to.be.true;
+ expect(limits.sponsoredDataSize.isNone).to.be.true;
+ expect(limits.sponsoredDataRateLimit.isNone).to.be.true;
+ expect(limits.tokenLimit.isNone).to.be.true;
+ expect(limits.sponsorTransferTimeout.isNone).to.be.true;
+ expect(limits.sponsorApproveTimeout.isNone).to.be.true;
+ expect(limits.ownerCanTransfer.isNone).to.be.true;
+ expect(limits.ownerCanDestroy.isNone).to.be.true;
+ expect(limits.transfersEnabled.isNone).to.be.true;
+ }
+
+ {
+ const limits = await api.rpc.unique.effectiveCollectionLimits(11111);
+ expect(limits.isNone).to.be.true;
+ }
+
+ {
+ const limitsOpt = await api.rpc.unique.effectiveCollectionLimits(collectionId);
+ expect(limitsOpt.isNone).to.be.false;
+ const limits = limitsOpt.unwrap();
+
+ console.log(limits);
+ expect(limits.accountTokenOwnershipLimit.isSome).to.be.true;
+ expect(limits.sponsoredDataSize.isSome).to.be.true;
+ expect(limits.sponsoredDataRateLimit.isSome).to.be.true;
+ expect(limits.tokenLimit.isSome).to.be.true;
+ expect(limits.sponsorTransferTimeout.isSome).to.be.true;
+ expect(limits.sponsorApproveTimeout.isSome).to.be.true;
+ expect(limits.ownerCanTransfer.isSome).to.be.true;
+ expect(limits.ownerCanDestroy.isSome).to.be.true;
+ expect(limits.transfersEnabled.isSome).to.be.true;
+ }
+ });
+ });
+});
\ No newline at end of file