difftreelog
refactor make collection limits fields optional
in: master
8 files changed
pallets/common/src/lib.rsdiffbeforeafterboth--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -105,10 +105,10 @@
Ok(())
}
pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> Result<bool, DispatchError> {
- Ok(self.limits.owner_can_transfer && self.is_owner_or_admin(user)?)
+ Ok(self.limits.owner_can_transfer() && self.is_owner_or_admin(user)?)
}
pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> Result<bool, DispatchError> {
- Ok(self.limits.owner_can_transfer && self.is_owner_or_admin(user)?)
+ Ok(self.limits.owner_can_transfer() && self.is_owner_or_admin(user)?)
}
pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {
self.consume_sload()?;
@@ -405,9 +405,10 @@
collection: CollectionHandle<T>,
sender: &T::CrossAccountId,
) -> DispatchResult {
- if !collection.limits.owner_can_destroy {
- fail!(Error::<T>::NoPermission);
- }
+ ensure!(
+ collection.limits.owner_can_destroy(),
+ <Error<T>>::NoPermission,
+ );
collection.check_is_owner(&sender)?;
let destroyed_collections = <DestroyedCollectionCount<T>>::get()
pallets/fungible/src/lib.rsdiffbeforeafterboth--- a/pallets/fungible/src/lib.rs
+++ b/pallets/fungible/src/lib.rs
@@ -157,8 +157,8 @@
amount: u128,
) -> DispatchResult {
ensure!(
- collection.transfers_enabled,
- <CommonError<T>>::TransferNotAllowed
+ collection.limits.transfers_enabled(),
+ <CommonError<T>>::TransferNotAllowed,
);
if collection.access == AccessMode::WhiteList {
pallets/nft/src/eth/sponsoring.rsdiffbeforeafterboth--- a/pallets/nft/src/eth/sponsoring.rs
+++ b/pallets/nft/src/eth/sponsoring.rs
@@ -43,11 +43,8 @@
let token_id: u32 = token_id.try_into().map_err(|_| AnyError)?;
let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
let collection_limits = &collection.limits;
- let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {
- collection_limits.sponsor_transfer_timeout
- } else {
- NFT_SPONSOR_TRANSFER_TIMEOUT
- };
+ let limit =
+ collection_limits.sponsor_transfer_timeout(NFT_SPONSOR_TRANSFER_TIMEOUT);
let mut sponsor = true;
if <NftTransferBasket<T>>::contains_key(collection_id, token_id) {
@@ -74,11 +71,8 @@
UniqueFungibleCall::ERC20(ERC20Call::Transfer { .. }) => {
let who = T::CrossAccountId::from_eth(*caller);
let collection_limits = &collection.limits;
- let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {
- collection_limits.sponsor_transfer_timeout
- } else {
- FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT
- };
+ let limit = collection_limits
+ .sponsor_transfer_timeout(FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT);
let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
let mut sponsored = true;
pallets/nft/src/lib.rsdiffbeforeafterboth1//2// This file is subject to the terms and conditions defined in3// file 'LICENSE', which is part of this source code package.4//56#![recursion_limit = "1024"]7#![cfg_attr(not(feature = "std"), no_std)]8#![allow(9 clippy::too_many_arguments,10 clippy::unnecessary_mut_passed,11 clippy::unused_unit12)]1314extern crate alloc;1516pub use serde::{Serialize, Deserialize};1718pub use frame_support::{19 construct_runtime, decl_module, decl_storage, decl_error,20 dispatch::DispatchResult,21 ensure, fail, parameter_types,22 traits::{23 ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced, Randomness,24 IsSubType, WithdrawReasons,25 },26 weights::{27 constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},28 DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,29 WeightToFeePolynomial, DispatchClass,30 },31 StorageValue, transactional,32 pallet_prelude::DispatchResultWithPostInfo,33};34use scale_info::TypeInfo;35use frame_system::{self as system, ensure_signed};36use sp_runtime::{sp_std::prelude::Vec};37use nft_data_structs::{38 MAX_DECIMAL_POINTS, MAX_SPONSOR_TIMEOUT, MAX_TOKEN_OWNERSHIP, CUSTOM_DATA_LIMIT,39 VARIABLE_ON_CHAIN_SCHEMA_LIMIT, CONST_ON_CHAIN_SCHEMA_LIMIT, COLLECTION_ADMINS_LIMIT,40 OFFCHAIN_SCHEMA_LIMIT, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,41 NFT_SPONSOR_TRANSFER_TIMEOUT, AccessMode, Collection, CreateItemData, CollectionLimits,42 CollectionId, CollectionMode, TokenId, SchemaVersion, SponsorshipState, MetaUpdatePermission,43};44use pallet_common::{45 account::CrossAccountId, CollectionHandle, IsAdmin, Pallet as PalletCommon,46 Error as CommonError, CommonWeightInfo, Allowlist,47};48use pallet_refungible::{Pallet as PalletRefungible, RefungibleHandle};49use pallet_fungible::{Pallet as PalletFungible, FungibleHandle};50use pallet_nonfungible::{Pallet as PalletNonfungible, NonfungibleHandle};5152#[cfg(test)]53mod mock;5455#[cfg(test)]56mod tests;5758mod eth;59mod sponsorship;60pub use sponsorship::NftSponsorshipHandler;61pub use eth::sponsoring::NftEthSponsorshipHandler;6263pub use eth::NftErcSupport;6465pub mod common;66use common::CommonWeights;67pub mod dispatch;68use dispatch::dispatch_call;6970#[cfg(feature = "runtime-benchmarks")]71mod benchmarking;72pub mod weights;73use weights::WeightInfo;7475decl_error! {76 /// Error for non-fungible-token module.77 pub enum Error for Module<T: Config> {78 /// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.79 CollectionDecimalPointLimitExceeded,80 /// This address is not set as sponsor, use setCollectionSponsor first.81 ConfirmUnsetSponsorFail,82 /// Length of items properties must be greater than 0.83 EmptyArgument,84 /// Collection limit bounds per collection exceeded85 CollectionLimitBoundsExceeded,86 /// Tried to enable permissions which are only permitted to be disabled87 OwnerPermissionsCantBeReverted,88 }89}90pub trait Config:91 system::Config92 + pallet_evm_coder_substrate::Config93 + pallet_common::Config94 + pallet_nonfungible::Config95 + pallet_refungible::Config96 + pallet_fungible::Config97 + Sized98 + TypeInfo99{100 /// Weight information for extrinsics in this pallet.101 type WeightInfo: WeightInfo;102}103104type SelfWeightOf<T> = <T as Config>::WeightInfo;105106// # Used definitions107//108// ## User control levels109//110// chain-controlled - key is uncontrolled by user111// i.e autoincrementing index112// can use non-cryptographic hash113// real - key is controlled by user114// but it is hard to generate enough colliding values, i.e owner of signed txs115// can use non-cryptographic hash116// controlled - key is completly controlled by users117// i.e maps with mutable keys118// should use cryptographic hash119//120// ## User control level downgrade reasons121//122// ?1 - chain-controlled -> controlled123// collections/tokens can be destroyed, resulting in massive holes124// ?2 - chain-controlled -> controlled125// same as ?1, but can be only added, resulting in easier exploitation126// ?3 - real -> controlled127// no confirmation required, so addresses can be easily generated128decl_storage! {129 trait Store for Module<T: Config> as Nft {130131 //#region Private members132 /// Used for migrations133 ChainVersion: u64;134 //#endregion135136 //#region Tokens transfer rate limit baskets137 /// (Collection id (controlled?2), who created (real))138 /// TODO: Off chain worker should remove from this map when collection gets removed139 pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => T::BlockNumber;140 /// Collection id (controlled?2), token id (controlled?2)141 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;142 /// Collection id (controlled?2), owning user (real)143 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;144 /// Collection id (controlled?2), token id (controlled?2)145 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;146 //#endregion147148 /// Variable metadata sponsoring149 /// Collection id (controlled?2), token id (controlled?2)150 pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber> = None;151 }152}153154decl_module! {155 pub struct Module<T: Config> for enum Call156 where157 origin: T::Origin158 {159 const CollectionAdminsLimit: u64 = COLLECTION_ADMINS_LIMIT;160 type Error = Error<T>;161162 fn on_initialize(_now: T::BlockNumber) -> Weight {163 0164 }165166 /// This method creates a Collection of NFTs. Each Token may have multiple properties encoded as an array of bytes of certain length. The initial owner and admin of the collection are set to the address that signed the transaction. Both addresses can be changed later.167 ///168 /// # Permissions169 ///170 /// * Anyone.171 ///172 /// # Arguments173 ///174 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.175 ///176 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.177 ///178 /// * token_prefix: UTF-8 string with token prefix.179 ///180 /// * mode: [CollectionMode] collection type and type dependent data.181 // returns collection ID182 #[weight = <SelfWeightOf<T>>::create_collection()]183 #[transactional]184 pub fn create_collection(origin,185 collection_name: Vec<u16>,186 collection_description: Vec<u16>,187 token_prefix: Vec<u8>,188 mode: CollectionMode) -> DispatchResult {189190 // Anyone can create a collection191 let who = ensure_signed(origin)?;192193 // Create new collection194 let new_collection = Collection::<T> {195 owner: who.clone(),196 name: collection_name,197 mode: mode.clone(),198 mint_mode: false,199 access: AccessMode::Normal,200 description: collection_description,201 token_prefix,202 offchain_schema: Vec::new(),203 schema_version: SchemaVersion::ImageURL,204 sponsorship: SponsorshipState::Disabled,205 variable_on_chain_schema: Vec::new(),206 const_on_chain_schema: Vec::new(),207 limits: Default::default(),208 meta_update_permission: Default::default(),209 };210211 let _id = match mode {212 CollectionMode::NFT => {PalletNonfungible::init_collection(new_collection)?},213 CollectionMode::Fungible(decimal_points) => {214 // check params215 ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);216 PalletFungible::init_collection(new_collection)?217 }218 CollectionMode::ReFungible => {219 PalletRefungible::init_collection(new_collection)?220 }221 };222223 Ok(())224 }225226 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.227 ///228 /// # Permissions229 ///230 /// * Collection Owner.231 ///232 /// # Arguments233 ///234 /// * collection_id: collection to destroy.235 #[weight = <SelfWeightOf<T>>::destroy_collection()]236 #[transactional]237 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {238 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);239240 let collection = <CollectionHandle<T>>::try_get(collection_id)?;241 collection.check_is_owner(&sender)?;242243 // =========244245 match collection.mode {246 CollectionMode::ReFungible => PalletRefungible::destroy_collection(RefungibleHandle::cast(collection), &sender)?,247 CollectionMode::Fungible(_) => PalletFungible::destroy_collection(FungibleHandle::cast(collection), &sender)?,248 CollectionMode::NFT => PalletNonfungible::destroy_collection(NonfungibleHandle::cast(collection), &sender)?,249 }250251 <NftTransferBasket<T>>::remove_prefix(collection_id, None);252 <FungibleTransferBasket<T>>::remove_prefix(collection_id, None);253 <ReFungibleTransferBasket<T>>::remove_prefix(collection_id, None);254255 <VariableMetaDataBasket<T>>::remove_prefix(collection_id, None);256257 Ok(())258 }259260 /// Add an address to white list.261 ///262 /// # Permissions263 ///264 /// * Collection Owner265 /// * Collection Admin266 ///267 /// # Arguments268 ///269 /// * collection_id.270 ///271 /// * address.272 #[weight = <SelfWeightOf<T>>::add_to_white_list()]273 #[transactional]274 pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{275276 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);277 let collection = <CollectionHandle<T>>::try_get(collection_id)?;278279 <PalletCommon<T>>::toggle_allowlist(280 &collection,281 &sender,282 &address,283 true,284 )?;285286 Ok(())287 }288289 /// Remove an address from white list.290 ///291 /// # Permissions292 ///293 /// * Collection Owner294 /// * Collection Admin295 ///296 /// # Arguments297 ///298 /// * collection_id.299 ///300 /// * address.301 #[weight = <SelfWeightOf<T>>::remove_from_white_list()]302 #[transactional]303 pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{304305 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);306 let collection = <CollectionHandle<T>>::try_get(collection_id)?;307308 <PalletCommon<T>>::toggle_allowlist(309 &collection,310 &sender,311 &address,312 false,313 )?;314315 Ok(())316 }317318 /// Toggle between normal and white list access for the methods with access for `Anyone`.319 ///320 /// # Permissions321 ///322 /// * Collection Owner.323 ///324 /// # Arguments325 ///326 /// * collection_id.327 ///328 /// * mode: [AccessMode]329 #[weight = <SelfWeightOf<T>>::set_public_access_mode()]330 #[transactional]331 pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult332 {333 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);334335 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;336 target_collection.check_is_owner(&sender)?;337338 target_collection.access = mode;339 target_collection.save()340 }341342 /// Allows Anyone to create tokens if:343 /// * White List is enabled, and344 /// * Address is added to white list, and345 /// * This method was called with True parameter346 ///347 /// # Permissions348 /// * Collection Owner349 ///350 /// # Arguments351 ///352 /// * collection_id.353 ///354 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.355 #[weight = <SelfWeightOf<T>>::set_mint_permission()]356 #[transactional]357 pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult358 {359 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);360361 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;362 target_collection.check_is_owner(&sender)?;363364 target_collection.mint_mode = mint_permission;365 target_collection.save()366 }367368 /// Change the owner of the collection.369 ///370 /// # Permissions371 ///372 /// * Collection Owner.373 ///374 /// # Arguments375 ///376 /// * collection_id.377 ///378 /// * new_owner.379 #[weight = <SelfWeightOf<T>>::change_collection_owner()]380 #[transactional]381 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {382383 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);384385 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;386 target_collection.check_is_owner(&sender)?;387388 target_collection.owner = new_owner;389 target_collection.save()390 }391392 /// Adds an admin of the Collection.393 /// 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.394 ///395 /// # Permissions396 ///397 /// * Collection Owner.398 /// * Collection Admin.399 ///400 /// # Arguments401 ///402 /// * collection_id: ID of the Collection to add admin for.403 ///404 /// * new_admin_id: Address of new admin to add.405 #[weight = <SelfWeightOf<T>>::add_collection_admin()]406 #[transactional]407 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {408 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);409410 let collection = <CollectionHandle<T>>::try_get(collection_id)?;411 collection.check_is_owner_or_admin(&sender)?;412413 <IsAdmin<T>>::insert((collection_id, new_admin_id.as_sub()), true);414 Ok(())415 }416417 /// 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.418 ///419 /// # Permissions420 ///421 /// * Collection Owner.422 /// * Collection Admin.423 ///424 /// # Arguments425 ///426 /// * collection_id: ID of the Collection to remove admin for.427 ///428 /// * account_id: Address of admin to remove.429 #[weight = <SelfWeightOf<T>>::remove_collection_admin()]430 #[transactional]431 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {432 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);433434 let collection = <CollectionHandle<T>>::try_get(collection_id)?;435 collection.check_is_owner_or_admin(&sender)?;436437 <IsAdmin<T>>::remove((collection_id, account_id.as_sub()));438 Ok(())439 }440441 /// # Permissions442 ///443 /// * Collection Owner444 ///445 /// # Arguments446 ///447 /// * collection_id.448 ///449 /// * new_sponsor.450 #[weight = <SelfWeightOf<T>>::set_collection_sponsor()]451 #[transactional]452 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {453 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);454455 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;456 target_collection.check_is_owner_or_admin(&sender)?;457458 target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor);459 target_collection.save()460 }461462 /// # Permissions463 ///464 /// * Sponsor.465 ///466 /// # Arguments467 ///468 /// * collection_id.469 #[weight = <SelfWeightOf<T>>::confirm_sponsorship()]470 #[transactional]471 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {472 let sender = ensure_signed(origin)?;473474 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;475 ensure!(476 target_collection.sponsorship.pending_sponsor() == Some(&sender),477 Error::<T>::ConfirmUnsetSponsorFail478 );479480 target_collection.sponsorship = SponsorshipState::Confirmed(sender);481 target_collection.save()482 }483484 /// Switch back to pay-per-own-transaction model.485 ///486 /// # Permissions487 ///488 /// * Collection owner.489 ///490 /// # Arguments491 ///492 /// * collection_id.493 #[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]494 #[transactional]495 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {496 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);497498 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;499 target_collection.check_is_owner(&sender)?;500501 target_collection.sponsorship = SponsorshipState::Disabled;502 target_collection.save()503 }504505 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.506 ///507 /// # Permissions508 ///509 /// * Collection Owner.510 /// * Collection Admin.511 /// * Anyone if512 /// * White List is enabled, and513 /// * Address is added to white list, and514 /// * MintPermission is enabled (see SetMintPermission method)515 ///516 /// # Arguments517 ///518 /// * collection_id: ID of the collection.519 ///520 /// * owner: Address, initial owner of the NFT.521 ///522 /// * data: Token data to store on chain.523 #[weight = <CommonWeights<T>>::create_item()]524 #[transactional]525 pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {526 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);527528 dispatch_call::<T, _>(collection_id, |d| d.create_item(sender, owner, data))529 }530531 /// This method creates multiple items in a collection created with CreateCollection method.532 ///533 /// # Permissions534 ///535 /// * Collection Owner.536 /// * Collection Admin.537 /// * Anyone if538 /// * White List is enabled, and539 /// * Address is added to white list, and540 /// * MintPermission is enabled (see SetMintPermission method)541 ///542 /// # Arguments543 ///544 /// * collection_id: ID of the collection.545 ///546 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].547 ///548 /// * owner: Address, initial owner of the NFT.549 #[weight = <CommonWeights<T>>::create_multiple_items(items_data.len() as u32)]550 #[transactional]551 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {552 ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);553 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);554555 dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data))556 }557558 // TODO! transaction weight559560 /// Set transfers_enabled value for particular collection561 ///562 /// # Permissions563 ///564 /// * Collection Owner.565 ///566 /// # Arguments567 ///568 /// * collection_id: ID of the collection.569 ///570 /// * value: New flag value.571 #[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]572 #[transactional]573 pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {574 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);575 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;576 target_collection.check_is_owner(&sender)?;577578 // =========579580 target_collection.limits.transfers_enabled = Some(value);581 target_collection.save()582 }583584 /// Destroys a concrete instance of NFT.585 ///586 /// # Permissions587 ///588 /// * Collection Owner.589 /// * Collection Admin.590 /// * Current NFT Owner.591 ///592 /// # Arguments593 ///594 /// * collection_id: ID of the collection.595 ///596 /// * item_id: ID of NFT to burn.597 #[weight = <CommonWeights<T>>::burn_item()]598 #[transactional]599 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {600 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);601602 dispatch_call::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))603 }604605 /// Destroys a concrete instance of NFT on behalf of the owner606 /// See also: [`approve`]607 ///608 /// # Permissions609 ///610 /// * Collection Owner.611 /// * Collection Admin.612 /// * Current NFT Owner.613 ///614 /// # Arguments615 ///616 /// * collection_id: ID of the collection.617 ///618 /// * item_id: ID of NFT to burn.619 ///620 /// * from: owner of item621 #[weight = <CommonWeights<T>>::burn_from()]622 #[transactional]623 pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {624 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);625626 dispatch_call::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value))627 }628629 /// Change ownership of the token.630 ///631 /// # Permissions632 ///633 /// * Collection Owner634 /// * Collection Admin635 /// * Current NFT owner636 ///637 /// # Arguments638 ///639 /// * recipient: Address of token recipient.640 ///641 /// * collection_id.642 ///643 /// * item_id: ID of the item644 /// * Non-Fungible Mode: Required.645 /// * Fungible Mode: Ignored.646 /// * Re-Fungible Mode: Required.647 ///648 /// * value: Amount to transfer.649 /// * Non-Fungible Mode: Ignored650 /// * Fungible Mode: Must specify transferred amount651 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)652 #[weight = <CommonWeights<T>>::transfer()]653 #[transactional]654 pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {655 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);656657 dispatch_call::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value))658 }659660 /// Set, change, or remove approved address to transfer the ownership of the NFT.661 ///662 /// # Permissions663 ///664 /// * Collection Owner665 /// * Collection Admin666 /// * Current NFT owner667 ///668 /// # Arguments669 ///670 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).671 ///672 /// * collection_id.673 ///674 /// * item_id: ID of the item.675 #[weight = <CommonWeights<T>>::approve()]676 #[transactional]677 pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {678 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);679680 dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))681 }682683 /// 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.684 ///685 /// # Permissions686 /// * Collection Owner687 /// * Collection Admin688 /// * Current NFT owner689 /// * Address approved by current NFT owner690 ///691 /// # Arguments692 ///693 /// * from: Address that owns token.694 ///695 /// * recipient: Address of token recipient.696 ///697 /// * collection_id.698 ///699 /// * item_id: ID of the item.700 ///701 /// * value: Amount to transfer.702 #[weight = <CommonWeights<T>>::transfer_from()]703 #[transactional]704 pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {705 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);706707 dispatch_call::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value))708 }709710 /// Set off-chain data schema.711 ///712 /// # Permissions713 ///714 /// * Collection Owner715 /// * Collection Admin716 ///717 /// # Arguments718 ///719 /// * collection_id.720 ///721 /// * schema: String representing the offchain data schema.722 #[weight = <CommonWeights<T>>::set_variable_metadata(data.len() as u32)]723 #[transactional]724 pub fn set_variable_meta_data (725 origin,726 collection_id: CollectionId,727 item_id: TokenId,728 data: Vec<u8>729 ) -> DispatchResultWithPostInfo {730 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);731732 dispatch_call::<T, _>(collection_id, |d| d.set_variable_metadata(sender, item_id, data))733 }734735 /// Set meta_update_permission value for particular collection736 ///737 /// # Permissions738 ///739 /// * Collection Owner.740 ///741 /// # Arguments742 ///743 /// * collection_id: ID of the collection.744 ///745 /// * value: New flag value.746 #[weight = <SelfWeightOf<T>>::set_meta_update_permission_flag()]747 #[transactional]748 pub fn set_meta_update_permission_flag(origin, collection_id: CollectionId, value: MetaUpdatePermission) -> DispatchResult {749 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);750 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;751752 ensure!(753 target_collection.meta_update_permission != MetaUpdatePermission::None,754 <CommonError<T>>::MetadataFlagFrozen,755 );756 target_collection.check_is_owner(&sender)?;757758 target_collection.meta_update_permission = value;759760 target_collection.save()761 }762763 /// Set schema standard764 /// ImageURL765 /// Unique766 ///767 /// # Permissions768 ///769 /// * Collection Owner770 /// * Collection Admin771 ///772 /// # Arguments773 ///774 /// * collection_id.775 ///776 /// * schema: SchemaVersion: enum777 #[weight = <SelfWeightOf<T>>::set_schema_version()]778 #[transactional]779 pub fn set_schema_version(780 origin,781 collection_id: CollectionId,782 version: SchemaVersion783 ) -> DispatchResult {784 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);785 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;786 target_collection.check_is_owner_or_admin(&sender)?;787 target_collection.schema_version = version;788 target_collection.save()789 }790791 /// Set off-chain data schema.792 ///793 /// # Permissions794 ///795 /// * Collection Owner796 /// * Collection Admin797 ///798 /// # Arguments799 ///800 /// * collection_id.801 ///802 /// * schema: String representing the offchain data schema.803 #[weight = <SelfWeightOf<T>>::set_offchain_schema(schema.len() as u32)]804 #[transactional]805 pub fn set_offchain_schema(806 origin,807 collection_id: CollectionId,808 schema: Vec<u8>809 ) -> DispatchResult {810 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);811 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;812 target_collection.check_is_owner_or_admin(&sender)?;813814 // check schema limit815 ensure!(schema.len() as u32 <= OFFCHAIN_SCHEMA_LIMIT, "");816817 target_collection.offchain_schema = schema;818 target_collection.save()819 }820821 /// Set const on-chain data schema.822 ///823 /// # Permissions824 ///825 /// * Collection Owner826 /// * Collection Admin827 ///828 /// # Arguments829 ///830 /// * collection_id.831 ///832 /// * schema: String representing the const on-chain data schema.833 #[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]834 #[transactional]835 pub fn set_const_on_chain_schema (836 origin,837 collection_id: CollectionId,838 schema: Vec<u8>839 ) -> DispatchResult {840 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);841 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;842 target_collection.check_is_owner_or_admin(&sender)?;843844 // check schema limit845 ensure!(schema.len() as u32 <= CONST_ON_CHAIN_SCHEMA_LIMIT, "");846847 target_collection.const_on_chain_schema = schema;848 target_collection.save()849 }850851 /// Set variable on-chain data schema.852 ///853 /// # Permissions854 ///855 /// * Collection Owner856 /// * Collection Admin857 ///858 /// # Arguments859 ///860 /// * collection_id.861 ///862 /// * schema: String representing the variable on-chain data schema.863 #[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]864 #[transactional]865 pub fn set_variable_on_chain_schema (866 origin,867 collection_id: CollectionId,868 schema: Vec<u8>869 ) -> DispatchResult {870 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);871 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;872 target_collection.check_is_owner_or_admin(&sender)?;873874 // check schema limit875 ensure!(schema.len() as u32 <= VARIABLE_ON_CHAIN_SCHEMA_LIMIT, "");876877 target_collection.variable_on_chain_schema = schema;878 target_collection.save()879 }880881 #[weight = <SelfWeightOf<T>>::set_collection_limits()]882 #[transactional]883 pub fn set_collection_limits(884 origin,885 collection_id: CollectionId,886 new_limit: CollectionLimits,887 ) -> DispatchResult {888 let mut new_limit = new_limit;889 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);890 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;891 target_collection.check_is_owner(&sender)?;892 let old_limit = &target_collection.limits;893894 macro_rules! limit_default {895 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{896 $(897 if let Some($new) = $new.$field {898 let $old = $old.$field($($arg)?);899 let _ = $new;900 let _ = $old;901 $check902 } else {903 $new.$field = $old.$field904 }905 )*906 }};907 }908909 limit_default!(old_limit, new_limit,910 account_token_ownership_limit => ensure!(911 new_limit <= MAX_TOKEN_OWNERSHIP,912 <Error<T>>::CollectionLimitBoundsExceeded,913 ),914 sponsor_transfer_timeout(match target_collection.mode {915 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,916 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,917 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,918 }) => ensure!(919 new_limit <= MAX_SPONSOR_TIMEOUT,920 <Error<T>>::CollectionLimitBoundsExceeded,921 ),922 sponsored_data_size => ensure!(923 new_limit <= CUSTOM_DATA_LIMIT,924 <Error<T>>::CollectionLimitBoundsExceeded,925 ),926 token_limit => ensure!(927 old_limit >= new_limit && new_limit > 0,928 <CommonError<T>>::CollectionTokenLimitExceeded929 ),930 owner_can_transfer => ensure!(931 old_limit || !new_limit,932 <Error<T>>::OwnerPermissionsCantBeReverted,933 ),934 owner_can_destroy => ensure!(935 old_limit || !new_limit,936 <Error<T>>::OwnerPermissionsCantBeReverted,937 ),938 sponsored_data_rate_limit => {},939 transfers_enabled => {},940 );941942 target_collection.limits = new_limit;943944 target_collection.save()945 }946 }947}948949// TODO: limit returned entries?950impl<T: Config> Pallet<T> {951 pub fn adminlist(collection: CollectionId) -> Vec<T::AccountId> {952 <IsAdmin<T>>::iter_prefix((collection,))953 .map(|(a, _)| a)954 .collect()955 }956 pub fn allowlist(collection: CollectionId) -> Vec<T::AccountId> {957 <Allowlist<T>>::iter_prefix((collection,))958 .map(|(a, _)| a)959 .collect()960 }961}pallets/nft/src/sponsorship.rsdiffbeforeafterboth--- a/pallets/nft/src/sponsorship.rs
+++ b/pallets/nft/src/sponsorship.rs
@@ -26,7 +26,13 @@
// sponsor timeout
let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
- let limit = collection.limits.sponsor_transfer_timeout;
+ let limit = collection
+ .limits
+ .sponsor_transfer_timeout(match _properties {
+ CreateItemData::NFT(_) => NFT_SPONSOR_TRANSFER_TIMEOUT,
+ CreateItemData::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
+ CreateItemData::ReFungible(_) => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
+ });
if CreateItemBasket::<T>::contains_key((collection_id, &who)) {
let last_tx_block = CreateItemBasket::<T>::get((collection_id, &who));
let limit_time = last_tx_block + limit.into();
@@ -37,7 +43,7 @@
CreateItemBasket::<T>::insert((collection_id, who.clone()), block_number);
// check free create limit
- if collection.limits.sponsored_data_size >= (_properties.data_size() as u32) {
+ if collection.limits.sponsored_data_size() >= (_properties.data_size() as u32) {
collection.sponsorship.sponsor().cloned()
} else {
None
@@ -61,11 +67,8 @@
sponsor_transfer = match collection_mode {
CollectionMode::NFT => {
// get correct limit
- let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {
- collection_limits.sponsor_transfer_timeout
- } else {
- NFT_SPONSOR_TRANSFER_TIMEOUT
- };
+ let limit =
+ collection_limits.sponsor_transfer_timeout(NFT_SPONSOR_TRANSFER_TIMEOUT);
let mut sponsored = true;
if NftTransferBasket::<T>::contains_key(collection_id, item_id) {
@@ -83,11 +86,8 @@
}
CollectionMode::Fungible(_) => {
// get correct limit
- let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {
- collection_limits.sponsor_transfer_timeout
- } else {
- FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT
- };
+ let limit = collection_limits
+ .sponsor_transfer_timeout(FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT);
let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
let mut sponsored = true;
@@ -106,11 +106,8 @@
}
CollectionMode::ReFungible => {
// get correct limit
- let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {
- collection_limits.sponsor_transfer_timeout
- } else {
- REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT
- };
+ let limit = collection_limits
+ .sponsor_transfer_timeout(REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT);
let mut sponsored = true;
if ReFungibleTransferBasket::<T>::contains_key(collection_id, item_id) {
@@ -150,13 +147,13 @@
// Can't sponsor fungible collection, this tx will be rejected
// as invalid
!matches!(collection.mode, CollectionMode::Fungible(_)) &&
- data.len() <= collection.limits.sponsored_data_size as usize
+ data.len() <= collection.limits.sponsored_data_size() as usize
{
- if let Some(rate_limit) = collection.limits.sponsored_data_rate_limit {
+ if let Some(rate_limit) = collection.limits.sponsored_data_rate_limit() {
let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
if VariableMetaDataBasket::<T>::get(collection_id, item_id)
- .map(|last_block| block_number - last_block > rate_limit)
+ .map(|last_block| block_number - last_block > rate_limit.into())
.unwrap_or(true)
{
sponsor_metadata_changes = true;
pallets/nonfungible/src/lib.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -164,7 +164,7 @@
.ok_or_else(|| <CommonError<T>>::TokenNotFound)?;
ensure!(
&token_data.owner == sender
- || (collection.limits.owner_can_transfer
+ || (collection.limits.owner_can_transfer()
&& collection.is_owner_or_admin(sender)?),
<CommonError<T>>::NoPermission
);
@@ -215,7 +215,7 @@
token: TokenId,
) -> DispatchResult {
ensure!(
- collection.transfers_enabled,
+ collection.limits.transfers_enabled(),
<CommonError<T>>::TransferNotAllowed
);
@@ -223,7 +223,8 @@
.ok_or_else(|| <CommonError<T>>::TokenNotFound)?;
ensure!(
&token_data.owner == from
- || (collection.limits.owner_can_transfer && collection.is_owner_or_admin(from)?),
+ || (collection.limits.owner_can_transfer()
+ && collection.is_owner_or_admin(from)?),
<CommonError<T>>::NoPermission
);
@@ -327,7 +328,7 @@
.checked_add(data.len() as u32)
.ok_or(ArithmeticError::Overflow)?;
ensure!(
- tokens_minted < collection.limits.token_limit,
+ tokens_minted < collection.limits.token_limit(),
<CommonError<T>>::CollectionTokenLimitExceeded
);
collection.consume_sstore()?;
pallets/refungible/src/lib.rsdiffbeforeafterboth--- a/pallets/refungible/src/lib.rs
+++ b/pallets/refungible/src/lib.rs
@@ -268,7 +268,7 @@
amount: u128,
) -> DispatchResult {
ensure!(
- collection.transfers_enabled,
+ collection.limits.transfers_enabled(),
<CommonError<T>>::TransferNotAllowed
);
@@ -404,7 +404,7 @@
.checked_add(data.len() as u32)
.ok_or(ArithmeticError::Overflow)?;
ensure!(
- tokens_minted < collection.limits.token_limit,
+ tokens_minted < collection.limits.token_limit(),
<CommonError<T>>::CollectionTokenLimitExceeded
);
primitives/nft/src/lib.rsdiffbeforeafterboth--- a/primitives/nft/src/lib.rs
+++ b/primitives/nft/src/lib.rs
@@ -42,6 +42,7 @@
10
};
pub const COLLECTION_ADMINS_LIMIT: u64 = 5;
+pub const COLLECTION_TOKEN_LIMIT: u32 = u32::MAX;
pub const ACCOUNT_TOKEN_OWNERSHIP_LIMIT: u32 = if cfg!(not(feature = "limit-testing")) {
1000000
} else {
@@ -217,11 +218,10 @@
pub offchain_schema: Vec<u8>,
pub schema_version: SchemaVersion,
pub sponsorship: SponsorshipState<T::AccountId>,
- pub limits: CollectionLimits<T::BlockNumber>, // Collection private restrictions
- pub variable_on_chain_schema: Vec<u8>, //
- pub const_on_chain_schema: Vec<u8>, //
+ pub limits: CollectionLimits, // Collection private restrictions
+ pub variable_on_chain_schema: Vec<u8>, //
+ pub const_on_chain_schema: Vec<u8>, //
pub meta_update_permission: MetaUpdatePermission,
- pub transfers_enabled: bool,
}
#[derive(Encode, Decode, Debug, Clone, PartialEq, TypeInfo)]
@@ -246,42 +246,57 @@
pub variable_data: Vec<u8>,
}
-#[derive(Encode, Decode, Debug, Clone, PartialEq, TypeInfo)]
+#[derive(Encode, Decode, Debug, Default, Clone, PartialEq, TypeInfo)]
#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
-pub struct CollectionLimits<BlockNumber: Encode + Decode> {
+pub struct CollectionLimits {
pub account_token_ownership_limit: Option<u32>,
- pub sponsored_data_size: u32,
+ pub sponsored_data_size: Option<u32>,
/// None - setVariableMetadata is not sponsored
/// Some(v) - setVariableMetadata is sponsored
/// if there is v block between txs
- pub sponsored_data_rate_limit: Option<BlockNumber>,
- pub token_limit: u32,
+ pub sponsored_data_rate_limit: Option<u32>,
+ pub token_limit: Option<u32>,
// Timeouts for item types in passed blocks
- pub sponsor_transfer_timeout: u32,
- pub owner_can_transfer: bool,
- pub owner_can_destroy: bool,
+ pub sponsor_transfer_timeout: Option<u32>,
+ pub owner_can_transfer: Option<bool>,
+ pub owner_can_destroy: Option<bool>,
+ pub transfers_enabled: Option<bool>,
}
-impl<BlockNumber: Encode + Decode> CollectionLimits<BlockNumber> {
+impl CollectionLimits {
pub fn account_token_ownership_limit(&self) -> u32 {
self.account_token_ownership_limit
.unwrap_or(ACCOUNT_TOKEN_OWNERSHIP_LIMIT)
- .min(ACCOUNT_TOKEN_OWNERSHIP_LIMIT)
+ .min(MAX_TOKEN_OWNERSHIP)
}
-}
-
-impl<BlockNumber: Encode + Decode> Default for CollectionLimits<BlockNumber> {
- fn default() -> Self {
- Self {
- account_token_ownership_limit: Some(10_000_000),
- token_limit: u32::max_value(),
- sponsored_data_size: u32::MAX,
- sponsored_data_rate_limit: None,
- sponsor_transfer_timeout: 14400,
- owner_can_transfer: true,
- owner_can_destroy: true,
- }
+ pub fn sponsored_data_size(&self) -> u32 {
+ self.sponsored_data_size
+ .unwrap_or(CUSTOM_DATA_LIMIT)
+ .min(CUSTOM_DATA_LIMIT)
+ }
+ pub fn token_limit(&self) -> u32 {
+ self.token_limit
+ .unwrap_or(COLLECTION_TOKEN_LIMIT)
+ .min(COLLECTION_TOKEN_LIMIT)
+ }
+ pub fn sponsor_transfer_timeout(&self, default: u32) -> u32 {
+ self.sponsor_transfer_timeout
+ .unwrap_or(default)
+ .min(MAX_SPONSOR_TIMEOUT)
+ }
+ pub fn owner_can_transfer(&self) -> bool {
+ self.owner_can_transfer.unwrap_or(true)
+ }
+ pub fn owner_can_destroy(&self) -> bool {
+ self.owner_can_destroy.unwrap_or(true)
+ }
+ pub fn transfers_enabled(&self) -> bool {
+ self.transfers_enabled.unwrap_or(true)
+ }
+ pub fn sponsored_data_rate_limit(&self) -> Option<u32> {
+ self.sponsored_data_rate_limit
+ .map(|v| v.min(MAX_SPONSOR_TIMEOUT))
}
}