difftreelog
feat allow more fields to be set on collection creation
in: master
7 files changed
pallets/common/src/lib.rsdiffbeforeafterboth--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -14,7 +14,10 @@
COLLECTION_NUMBER_LIMIT, Collection, CollectionId, CreateItemData, ExistenceRequirement,
MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_COLLECTION_NAME_LENGTH, MAX_TOKEN_PREFIX_LENGTH,
COLLECTION_ADMINS_LIMIT, MetaUpdatePermission, Pays, PostDispatchInfo, TokenId, Weight,
- WithdrawReasons, CollectionStats,
+ WithdrawReasons, CollectionStats, MAX_TOKEN_OWNERSHIP, CollectionMode,
+ NFT_SPONSOR_TRANSFER_TIMEOUT, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
+ REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, MAX_SPONSOR_TIMEOUT, CUSTOM_DATA_LIMIT, CollectionLimits,
+ CreateCollectionData, SponsorshipState,
};
pub use pallet::*;
use sp_core::H160;
@@ -282,6 +285,10 @@
TokenVariableDataLimitExceeded,
/// Exceeded max admin count
CollectionAdminCountExceeded,
+ /// Collection limit bounds per collection exceeded
+ CollectionLimitBoundsExceeded,
+ /// Tried to enable permissions which are only permitted to be disabled
+ OwnerPermissionsCantBeReverted,
/// Collection settings not allowing items transferring
TransferNotAllowed,
@@ -392,7 +399,10 @@
}
impl<T: Config> Pallet<T> {
- pub fn init_collection(data: Collection<T::AccountId>) -> Result<CollectionId, DispatchError> {
+ pub fn init_collection(
+ owner: T::AccountId,
+ data: CreateCollectionData<T::AccountId>,
+ ) -> Result<CollectionId, DispatchError> {
{
ensure!(
data.name.len() <= MAX_COLLECTION_NAME_LENGTH,
@@ -423,6 +433,29 @@
// =========
+ let collection = Collection {
+ owner: owner.clone(),
+ name: data.name,
+ mode: data.mode.clone(),
+ mint_mode: false,
+ access: data.access.unwrap_or_default(),
+ description: data.description,
+ token_prefix: data.token_prefix,
+ offchain_schema: data.offchain_schema,
+ schema_version: data.schema_version.unwrap_or_default(),
+ sponsorship: data
+ .pending_sponsor
+ .map(SponsorshipState::Unconfirmed)
+ .unwrap_or_default(),
+ variable_on_chain_schema: data.variable_on_chain_schema,
+ const_on_chain_schema: data.const_on_chain_schema,
+ limits: data
+ .limits
+ .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))
+ .unwrap_or_else(|| Ok(CollectionLimits::default()))?,
+ meta_update_permission: data.meta_update_permission.unwrap_or_default(),
+ };
+
// Take a (non-refundable) deposit of collection creation
{
let mut imbalance =
@@ -434,7 +467,7 @@
),
);
<T as Config>::Currency::settle(
- &data.owner,
+ &owner,
imbalance,
WithdrawReasons::TRANSFER,
ExistenceRequirement::KeepAlive,
@@ -443,12 +476,8 @@
}
<CreatedCollectionCount<T>>::put(created_count);
- <Pallet<T>>::deposit_event(Event::CollectionCreated(
- id,
- data.mode.id(),
- data.owner.clone(),
- ));
- <CollectionById<T>>::insert(id, data);
+ <Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));
+ <CollectionById<T>>::insert(id, collection);
Ok(id)
}
@@ -532,6 +561,61 @@
Ok(())
}
+
+ pub fn clamp_limits(
+ mode: CollectionMode,
+ old_limit: &CollectionLimits,
+ mut new_limit: CollectionLimits,
+ ) -> Result<CollectionLimits, DispatchError> {
+ macro_rules! limit_default {
+ ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{
+ $(
+ if let Some($new) = $new.$field {
+ let $old = $old.$field($($arg)?);
+ let _ = $new;
+ let _ = $old;
+ $check
+ } else {
+ $new.$field = $old.$field
+ }
+ )*
+ }};
+ }
+
+ limit_default!(old_limit, new_limit,
+ account_token_ownership_limit => ensure!(
+ new_limit <= MAX_TOKEN_OWNERSHIP,
+ <Error<T>>::CollectionLimitBoundsExceeded,
+ ),
+ sponsor_transfer_timeout(match mode {
+ CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,
+ CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
+ CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
+ }) => ensure!(
+ new_limit <= MAX_SPONSOR_TIMEOUT,
+ <Error<T>>::CollectionLimitBoundsExceeded,
+ ),
+ sponsored_data_size => ensure!(
+ new_limit <= CUSTOM_DATA_LIMIT,
+ <Error<T>>::CollectionLimitBoundsExceeded,
+ ),
+ token_limit => ensure!(
+ old_limit >= new_limit && new_limit > 0,
+ <Error<T>>::CollectionTokenLimitExceeded
+ ),
+ owner_can_transfer => ensure!(
+ old_limit || !new_limit,
+ <Error<T>>::OwnerPermissionsCantBeReverted,
+ ),
+ owner_can_destroy => ensure!(
+ old_limit || !new_limit,
+ <Error<T>>::OwnerPermissionsCantBeReverted,
+ ),
+ sponsored_data_rate_limit => {},
+ transfers_enabled => {},
+ );
+ Ok(new_limit)
+ }
}
#[macro_export]
pallets/fungible/src/lib.rsdiffbeforeafterboth--- a/pallets/fungible/src/lib.rs
+++ b/pallets/fungible/src/lib.rs
@@ -2,7 +2,7 @@
use core::ops::Deref;
use frame_support::{ensure};
-use up_data_structs::{AccessMode, Collection, CollectionId, TokenId};
+use up_data_structs::{AccessMode, Collection, CollectionId, TokenId, CreateCollectionData};
use pallet_common::{
Error as CommonError, Event as CommonEvent, Pallet as PalletCommon, account::CrossAccountId,
};
@@ -100,8 +100,11 @@
}
impl<T: Config> Pallet<T> {
- pub fn init_collection(data: Collection<T::AccountId>) -> Result<CollectionId, DispatchError> {
- <PalletCommon<T>>::init_collection(data)
+ pub fn init_collection(
+ owner: T::AccountId,
+ data: CreateCollectionData<T::AccountId>,
+ ) -> Result<CollectionId, DispatchError> {
+ <PalletCommon<T>>::init_collection(owner, data)
}
pub fn destroy_collection(
collection: FungibleHandle<T>,
pallets/nonfungible/src/lib.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -4,6 +4,7 @@
use frame_support::{BoundedVec, ensure};
use up_data_structs::{
AccessMode, CUSTOM_DATA_LIMIT, Collection, CollectionId, CustomDataLimit, TokenId,
+ CreateCollectionData,
};
use pallet_common::{
Error as CommonError, Pallet as PalletCommon, Event as CommonEvent, account::CrossAccountId,
@@ -142,8 +143,11 @@
// unchecked calls skips any permission checks
impl<T: Config> Pallet<T> {
- pub fn init_collection(data: Collection<T::AccountId>) -> Result<CollectionId, DispatchError> {
- <PalletCommon<T>>::init_collection(data)
+ pub fn init_collection(
+ owner: T::AccountId,
+ data: CreateCollectionData<T::AccountId>,
+ ) -> Result<CollectionId, DispatchError> {
+ <PalletCommon<T>>::init_collection(owner, data)
}
pub fn destroy_collection(
collection: NonfungibleHandle<T>,
pallets/refungible/src/lib.rsdiffbeforeafterboth--- a/pallets/refungible/src/lib.rs
+++ b/pallets/refungible/src/lib.rs
@@ -3,7 +3,7 @@
use frame_support::{ensure, BoundedVec};
use up_data_structs::{
AccessMode, CUSTOM_DATA_LIMIT, Collection, CollectionId, CustomDataLimit,
- MAX_REFUNGIBLE_PIECES, TokenId,
+ MAX_REFUNGIBLE_PIECES, TokenId, CreateCollectionData,
};
use pallet_common::{
Error as CommonError, Event as CommonEvent, Pallet as PalletCommon, account::CrossAccountId,
@@ -156,8 +156,11 @@
// unchecked calls skips any permission checks
impl<T: Config> Pallet<T> {
- pub fn init_collection(data: Collection<T::AccountId>) -> Result<CollectionId, DispatchError> {
- <PalletCommon<T>>::init_collection(data)
+ pub fn init_collection(
+ owner: T::AccountId,
+ data: CreateCollectionData<T::AccountId>,
+ ) -> Result<CollectionId, DispatchError> {
+ <PalletCommon<T>>::init_collection(owner, data)
}
pub fn destroy_collection(
collection: RefungibleHandle<T>,
pallets/unique/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, decl_event,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 up_data_structs::{38 MAX_DECIMAL_POINTS, VARIABLE_ON_CHAIN_SCHEMA_LIMIT, CONST_ON_CHAIN_SCHEMA_LIMIT,39 OFFCHAIN_SCHEMA_LIMIT, AccessMode, CreateItemData, CollectionLimits, CollectionId,40 CollectionMode, TokenId, SchemaVersion, SponsorshipState, MetaUpdatePermission,41 CreateCollectionData,42};43use pallet_common::{44 account::CrossAccountId, CollectionHandle, Pallet as PalletCommon, Error as CommonError,45 CommonWeightInfo,46};47use pallet_refungible::{Pallet as PalletRefungible, RefungibleHandle};48use pallet_fungible::{Pallet as PalletFungible, FungibleHandle};49use pallet_nonfungible::{Pallet as PalletNonfungible, NonfungibleHandle};5051#[cfg(test)]52mod mock;5354#[cfg(test)]55mod tests;5657mod eth;58mod sponsorship;59pub use sponsorship::UniqueSponsorshipHandler;60pub use eth::sponsoring::UniqueEthSponsorshipHandler;6162pub use eth::UniqueErcSupport;6364pub mod common;65use common::CommonWeights;66pub mod dispatch;67use dispatch::dispatch_call;6869#[cfg(feature = "runtime-benchmarks")]70mod benchmarking;71pub mod weights;72use weights::WeightInfo;7374decl_error! {75 /// Error for non-fungible-token module.76 pub enum Error for Module<T: Config> {77 /// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.78 CollectionDecimalPointLimitExceeded,79 /// This address is not set as sponsor, use setCollectionSponsor first.80 ConfirmUnsetSponsorFail,81 /// Length of items properties must be greater than 0.82 EmptyArgument,83 }84}8586pub trait Config:87 system::Config88 + pallet_evm_coder_substrate::Config89 + pallet_common::Config90 + pallet_nonfungible::Config91 + pallet_refungible::Config92 + pallet_fungible::Config93 + Sized94 + TypeInfo95{96 type Event: From<Event<Self>> + Into<<Self as frame_system::Config>::Event>;9798 /// Weight information for extrinsics in this pallet.99 type WeightInfo: WeightInfo;100}101102decl_event! {103 pub enum Event<T>104 where105 <T as frame_system::Config>::AccountId,106 <T as pallet_common::Config>::CrossAccountId,107 {108 /// Collection sponsor was removed109 ///110 /// # Arguments111 ///112 /// * collection_id: Globally unique collection identifier.113 CollectionSponsorRemoved(CollectionId),114115 /// Collection admin was added116 ///117 /// # Arguments118 ///119 /// * collection_id: Globally unique collection identifier.120 ///121 /// * admin: Admin address.122 CollectionAdminAdded(CollectionId, CrossAccountId),123124 /// Collection owned was change125 ///126 /// # Arguments127 ///128 /// * collection_id: Globally unique collection identifier.129 ///130 /// * owner: New owner address.131 CollectionOwnedChanged(CollectionId, AccountId),132133 /// Collection sponsor was set134 ///135 /// # Arguments136 ///137 /// * collection_id: Globally unique collection identifier.138 ///139 /// * owner: New sponsor address.140 CollectionSponsorSet(CollectionId, AccountId),141142 /// const on chain schema was set143 ///144 /// # Arguments145 ///146 /// * collection_id: Globally unique collection identifier.147 ConstOnChainSchemaSet(CollectionId),148149 /// New sponsor was confirm150 ///151 /// # Arguments152 ///153 /// * collection_id: Globally unique collection identifier.154 ///155 /// * sponsor: New sponsor address.156 SponsorshipConfirmed(CollectionId, AccountId),157158 /// Collection admin was removed159 ///160 /// # Arguments161 ///162 /// * collection_id: Globally unique collection identifier.163 ///164 /// * admin: Admin address.165 CollectionAdminRemoved(CollectionId, CrossAccountId),166167 /// Address was remove from allow list168 ///169 /// # Arguments170 ///171 /// * collection_id: Globally unique collection identifier.172 ///173 /// * user: Address.174 AllowListAddressRemoved(CollectionId, CrossAccountId),175176 /// Address was add to allow list177 ///178 /// # Arguments179 ///180 /// * collection_id: Globally unique collection identifier.181 ///182 /// * user: Address.183 AllowListAddressAdded(CollectionId, CrossAccountId),184185 /// Collection limits was set186 ///187 /// # Arguments188 ///189 /// * collection_id: Globally unique collection identifier.190 CollectionLimitSet(CollectionId),191192 /// Mint permission was set193 ///194 /// # Arguments195 ///196 /// * collection_id: Globally unique collection identifier.197 MintPermissionSet(CollectionId),198199 /// Offchain schema was set200 ///201 /// # Arguments202 ///203 /// * collection_id: Globally unique collection identifier.204 OffchainSchemaSet(CollectionId),205206 /// Public access mode was set207 ///208 /// # Arguments209 ///210 /// * collection_id: Globally unique collection identifier.211 ///212 /// * mode: New access state.213 PublicAccessModeSet(CollectionId, AccessMode),214215 /// Schema version was set216 ///217 /// # Arguments218 ///219 /// * collection_id: Globally unique collection identifier.220 SchemaVersionSet(CollectionId),221222 /// Variable on chain schema was set223 ///224 /// # Arguments225 ///226 /// * collection_id: Globally unique collection identifier.227 VariableOnChainSchemaSet(CollectionId),228 }229}230231type SelfWeightOf<T> = <T as Config>::WeightInfo;232233// # Used definitions234//235// ## User control levels236//237// chain-controlled - key is uncontrolled by user238// i.e autoincrementing index239// can use non-cryptographic hash240// real - key is controlled by user241// but it is hard to generate enough colliding values, i.e owner of signed txs242// can use non-cryptographic hash243// controlled - key is completly controlled by users244// i.e maps with mutable keys245// should use cryptographic hash246//247// ## User control level downgrade reasons248//249// ?1 - chain-controlled -> controlled250// collections/tokens can be destroyed, resulting in massive holes251// ?2 - chain-controlled -> controlled252// same as ?1, but can be only added, resulting in easier exploitation253// ?3 - real -> controlled254// no confirmation required, so addresses can be easily generated255decl_storage! {256 trait Store for Module<T: Config> as Unique {257258 //#region Private members259 /// Used for migrations260 ChainVersion: u64;261 //#endregion262263 //#region Tokens transfer rate limit baskets264 /// (Collection id (controlled?2), who created (real))265 /// TODO: Off chain worker should remove from this map when collection gets removed266 pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => Option<T::BlockNumber>;267 /// Collection id (controlled?2), token id (controlled?2)268 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;269 /// Collection id (controlled?2), owning user (real)270 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;271 /// Collection id (controlled?2), token id (controlled?2)272 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): nmap hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;273 //#endregion274275 /// Variable metadata sponsoring276 /// Collection id (controlled?2), token id (controlled?2)277 pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;278 /// Approval sponsoring279 pub NftApproveBasket get(fn nft_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;280 pub FungibleApproveBasket get(fn fungible_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;281 pub RefungibleApproveBasket get(fn refungible_approve_basket): nmap hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;282 }283}284285decl_module! {286 pub struct Module<T: Config> for enum Call287 where288 origin: T::Origin289 {290 type Error = Error<T>;291292 fn deposit_event() = default;293294 fn on_initialize(_now: T::BlockNumber) -> Weight {295 0296 }297298 /// 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 of the collection is set to the address that signed the transaction and can be changed later.299 ///300 /// # Permissions301 ///302 /// * Anyone.303 ///304 /// # Arguments305 ///306 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.307 ///308 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.309 ///310 /// * token_prefix: UTF-8 string with token prefix.311 ///312 /// * mode: [CollectionMode] collection type and type dependent data.313 // returns collection ID314 #[weight = <SelfWeightOf<T>>::create_collection()]315 #[transactional]316 #[deprecated]317 pub fn create_collection(origin,318 collection_name: Vec<u16>,319 collection_description: Vec<u16>,320 token_prefix: Vec<u8>,321 mode: CollectionMode) -> DispatchResult {322 Self::create_collection_ex(origin, CreateCollectionData {323 name: collection_name,324 description: collection_description,325 token_prefix,326 mode,327 ..Default::default()328 })329 }330331 /// This method creates a collection332 ///333 /// Prefer it to deprecated [`created_collection`] method334 #[weight = <SelfWeightOf<T>>::create_collection()]335 #[transactional]336 pub fn create_collection_ex(origin, data: CreateCollectionData<T::AccountId>) -> DispatchResult {337 let owner = ensure_signed(origin)?;338339 let _id = match data.mode {340 CollectionMode::NFT => {<PalletNonfungible<T>>::init_collection(owner, data)?},341 CollectionMode::Fungible(decimal_points) => {342 // check params343 ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);344 <PalletFungible<T>>::init_collection(owner, data)?345 }346 CollectionMode::ReFungible => {347 <PalletRefungible<T>>::init_collection(owner, data)?348 }349 };350351 Ok(())352 }353354 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.355 ///356 /// # Permissions357 ///358 /// * Collection Owner.359 ///360 /// # Arguments361 ///362 /// * collection_id: collection to destroy.363 #[weight = <SelfWeightOf<T>>::destroy_collection()]364 #[transactional]365 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {366 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);367368 let collection = <CollectionHandle<T>>::try_get(collection_id)?;369 collection.check_is_owner(&sender)?;370371 // =========372373 match collection.mode {374 CollectionMode::ReFungible => PalletRefungible::destroy_collection(RefungibleHandle::cast(collection), &sender)?,375 CollectionMode::Fungible(_) => PalletFungible::destroy_collection(FungibleHandle::cast(collection), &sender)?,376 CollectionMode::NFT => PalletNonfungible::destroy_collection(NonfungibleHandle::cast(collection), &sender)?,377 }378379 <NftTransferBasket<T>>::remove_prefix(collection_id, None);380 <FungibleTransferBasket<T>>::remove_prefix(collection_id, None);381 <ReFungibleTransferBasket<T>>::remove_prefix((collection_id,), None);382383 <VariableMetaDataBasket<T>>::remove_prefix(collection_id, None);384 <NftApproveBasket<T>>::remove_prefix(collection_id, None);385 <FungibleApproveBasket<T>>::remove_prefix(collection_id, None);386 <RefungibleApproveBasket<T>>::remove_prefix((collection_id,), None);387388 Ok(())389 }390391 /// Add an address to allow list.392 ///393 /// # Permissions394 ///395 /// * Collection Owner396 /// * Collection Admin397 ///398 /// # Arguments399 ///400 /// * collection_id.401 ///402 /// * address.403 #[weight = <SelfWeightOf<T>>::add_to_allow_list()]404 #[transactional]405 pub fn add_to_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{406407 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);408 let collection = <CollectionHandle<T>>::try_get(collection_id)?;409410 <PalletCommon<T>>::toggle_allowlist(411 &collection,412 &sender,413 &address,414 true,415 )?;416417 Self::deposit_event(Event::<T>::AllowListAddressAdded(418 collection_id,419 address420 ));421422 Ok(())423 }424425 /// Remove an address from allow list.426 ///427 /// # Permissions428 ///429 /// * Collection Owner430 /// * Collection Admin431 ///432 /// # Arguments433 ///434 /// * collection_id.435 ///436 /// * address.437 #[weight = <SelfWeightOf<T>>::remove_from_allow_list()]438 #[transactional]439 pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{440441 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);442 let collection = <CollectionHandle<T>>::try_get(collection_id)?;443444 <PalletCommon<T>>::toggle_allowlist(445 &collection,446 &sender,447 &address,448 false,449 )?;450451 <Pallet<T>>::deposit_event(Event::<T>::AllowListAddressRemoved(452 collection_id,453 address454 ));455456 Ok(())457 }458459 /// Toggle between normal and allow list access for the methods with access for `Anyone`.460 ///461 /// # Permissions462 ///463 /// * Collection Owner.464 ///465 /// # Arguments466 ///467 /// * collection_id.468 ///469 /// * mode: [AccessMode]470 #[weight = <SelfWeightOf<T>>::set_public_access_mode()]471 #[transactional]472 pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult473 {474 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);475476 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;477 target_collection.check_is_owner(&sender)?;478479 target_collection.access = mode.clone();480481 <Pallet<T>>::deposit_event(Event::<T>::PublicAccessModeSet(482 collection_id,483 mode484 ));485486 target_collection.save()487 }488489 /// Allows Anyone to create tokens if:490 /// * Allow List is enabled, and491 /// * Address is added to allow list, and492 /// * This method was called with True parameter493 ///494 /// # Permissions495 /// * Collection Owner496 ///497 /// # Arguments498 ///499 /// * collection_id.500 ///501 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.502 #[weight = <SelfWeightOf<T>>::set_mint_permission()]503 #[transactional]504 pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult505 {506 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);507508 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;509 target_collection.check_is_owner(&sender)?;510511 target_collection.mint_mode = mint_permission;512513 <Pallet<T>>::deposit_event(Event::<T>::MintPermissionSet(514 collection_id515 ));516517 target_collection.save()518 }519520 /// Change the owner of the collection.521 ///522 /// # Permissions523 ///524 /// * Collection Owner.525 ///526 /// # Arguments527 ///528 /// * collection_id.529 ///530 /// * new_owner.531 #[weight = <SelfWeightOf<T>>::change_collection_owner()]532 #[transactional]533 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {534535 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);536537 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;538 target_collection.check_is_owner(&sender)?;539540 target_collection.owner = new_owner.clone();541 <Pallet<T>>::deposit_event(Event::<T>::CollectionOwnedChanged(542 collection_id,543 new_owner544 ));545546 target_collection.save()547 }548549 /// Adds an admin of the Collection.550 /// 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.551 ///552 /// # Permissions553 ///554 /// * Collection Owner.555 /// * Collection Admin.556 ///557 /// # Arguments558 ///559 /// * collection_id: ID of the Collection to add admin for.560 ///561 /// * new_admin_id: Address of new admin to add.562 #[weight = <SelfWeightOf<T>>::add_collection_admin()]563 #[transactional]564 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {565 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);566 let collection = <CollectionHandle<T>>::try_get(collection_id)?;567568 <Pallet<T>>::deposit_event(Event::<T>::CollectionAdminAdded(569 collection_id,570 new_admin_id.clone()571 ));572573 <PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)574 }575576 /// 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.577 ///578 /// # Permissions579 ///580 /// * Collection Owner.581 /// * Collection Admin.582 ///583 /// # Arguments584 ///585 /// * collection_id: ID of the Collection to remove admin for.586 ///587 /// * account_id: Address of admin to remove.588 #[weight = <SelfWeightOf<T>>::remove_collection_admin()]589 #[transactional]590 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {591 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);592 let collection = <CollectionHandle<T>>::try_get(collection_id)?;593594 <Pallet<T>>::deposit_event(Event::<T>::CollectionAdminRemoved(595 collection_id,596 account_id.clone()597 ));598599 <PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)600 }601602 /// # Permissions603 ///604 /// * Collection Owner605 ///606 /// # Arguments607 ///608 /// * collection_id.609 ///610 /// * new_sponsor.611 #[weight = <SelfWeightOf<T>>::set_collection_sponsor()]612 #[transactional]613 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {614 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);615616 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;617 target_collection.check_is_owner(&sender)?;618619 target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor.clone());620621 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(622 collection_id,623 new_sponsor624 ));625626 target_collection.save()627 }628629 /// # Permissions630 ///631 /// * Sponsor.632 ///633 /// # Arguments634 ///635 /// * collection_id.636 #[weight = <SelfWeightOf<T>>::confirm_sponsorship()]637 #[transactional]638 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {639 let sender = ensure_signed(origin)?;640641 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;642 ensure!(643 target_collection.sponsorship.pending_sponsor() == Some(&sender),644 Error::<T>::ConfirmUnsetSponsorFail645 );646647 target_collection.sponsorship = SponsorshipState::Confirmed(sender.clone());648649 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(650 collection_id,651 sender652 ));653654 target_collection.save()655 }656657 /// Switch back to pay-per-own-transaction model.658 ///659 /// # Permissions660 ///661 /// * Collection owner.662 ///663 /// # Arguments664 ///665 /// * collection_id.666 #[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]667 #[transactional]668 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {669 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);670671 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;672 target_collection.check_is_owner(&sender)?;673674 target_collection.sponsorship = SponsorshipState::Disabled;675676 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(677 collection_id678 ));679 target_collection.save()680 }681682 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.683 ///684 /// # Permissions685 ///686 /// * Collection Owner.687 /// * Collection Admin.688 /// * Anyone if689 /// * Allow List is enabled, and690 /// * Address is added to allow list, and691 /// * MintPermission is enabled (see SetMintPermission method)692 ///693 /// # Arguments694 ///695 /// * collection_id: ID of the collection.696 ///697 /// * owner: Address, initial owner of the NFT.698 ///699 /// * data: Token data to store on chain.700 #[weight = <CommonWeights<T>>::create_item()]701 #[transactional]702 pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {703 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);704705 dispatch_call::<T, _>(collection_id, |d| d.create_item(sender, owner, data))706 }707708 /// This method creates multiple items in a collection created with CreateCollection method.709 ///710 /// # Permissions711 ///712 /// * Collection Owner.713 /// * Collection Admin.714 /// * Anyone if715 /// * Allow List is enabled, and716 /// * Address is added to allow list, and717 /// * MintPermission is enabled (see SetMintPermission method)718 ///719 /// # Arguments720 ///721 /// * collection_id: ID of the collection.722 ///723 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].724 ///725 /// * owner: Address, initial owner of the NFT.726 #[weight = <CommonWeights<T>>::create_multiple_items(items_data.len() as u32)]727 #[transactional]728 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {729 ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);730 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);731732 dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data))733 }734735 // TODO! transaction weight736737 /// Set transfers_enabled value for particular collection738 ///739 /// # Permissions740 ///741 /// * Collection Owner.742 ///743 /// # Arguments744 ///745 /// * collection_id: ID of the collection.746 ///747 /// * value: New flag value.748 #[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]749 #[transactional]750 pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {751 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);752 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;753 target_collection.check_is_owner(&sender)?;754755 // =========756757 target_collection.limits.transfers_enabled = Some(value);758 target_collection.save()759 }760761 /// Destroys a concrete instance of NFT.762 ///763 /// # Permissions764 ///765 /// * Collection Owner.766 /// * Collection Admin.767 /// * Current NFT Owner.768 ///769 /// # Arguments770 ///771 /// * collection_id: ID of the collection.772 ///773 /// * item_id: ID of NFT to burn.774 #[weight = <CommonWeights<T>>::burn_item()]775 #[transactional]776 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {777 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);778779 let post_info = dispatch_call::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;780 if value == 1 {781 <NftTransferBasket<T>>::remove(collection_id, item_id);782 <NftApproveBasket<T>>::remove(collection_id, item_id);783 }784 // Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?785 // <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());786 // <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));787 Ok(post_info)788 }789790 /// Destroys a concrete instance of NFT on behalf of the owner791 /// See also: [`approve`]792 ///793 /// # Permissions794 ///795 /// * Collection Owner.796 /// * Collection Admin.797 /// * Current NFT Owner.798 ///799 /// # Arguments800 ///801 /// * collection_id: ID of the collection.802 ///803 /// * item_id: ID of NFT to burn.804 ///805 /// * from: owner of item806 #[weight = <CommonWeights<T>>::burn_from()]807 #[transactional]808 pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {809 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);810811 dispatch_call::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value))812 }813814 /// Change ownership of the token.815 ///816 /// # Permissions817 ///818 /// * Collection Owner819 /// * Collection Admin820 /// * Current NFT owner821 ///822 /// # Arguments823 ///824 /// * recipient: Address of token recipient.825 ///826 /// * collection_id.827 ///828 /// * item_id: ID of the item829 /// * Non-Fungible Mode: Required.830 /// * Fungible Mode: Ignored.831 /// * Re-Fungible Mode: Required.832 ///833 /// * value: Amount to transfer.834 /// * Non-Fungible Mode: Ignored835 /// * Fungible Mode: Must specify transferred amount836 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)837 #[weight = <CommonWeights<T>>::transfer()]838 #[transactional]839 pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {840 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);841842 dispatch_call::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value))843 }844845 /// Set, change, or remove approved address to transfer the ownership of the NFT.846 ///847 /// # Permissions848 ///849 /// * Collection Owner850 /// * Collection Admin851 /// * Current NFT owner852 ///853 /// # Arguments854 ///855 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).856 ///857 /// * collection_id.858 ///859 /// * item_id: ID of the item.860 #[weight = <CommonWeights<T>>::approve()]861 #[transactional]862 pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {863 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);864865 dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))866 }867868 /// 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.869 ///870 /// # Permissions871 /// * Collection Owner872 /// * Collection Admin873 /// * Current NFT owner874 /// * Address approved by current NFT owner875 ///876 /// # Arguments877 ///878 /// * from: Address that owns token.879 ///880 /// * recipient: Address of token recipient.881 ///882 /// * collection_id.883 ///884 /// * item_id: ID of the item.885 ///886 /// * value: Amount to transfer.887 #[weight = <CommonWeights<T>>::transfer_from()]888 #[transactional]889 pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {890 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);891892 dispatch_call::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value))893 }894895 /// Set off-chain data schema.896 ///897 /// # Permissions898 ///899 /// * Collection Owner900 /// * Collection Admin901 ///902 /// # Arguments903 ///904 /// * collection_id.905 ///906 /// * schema: String representing the offchain data schema.907 #[weight = <CommonWeights<T>>::set_variable_metadata(data.len() as u32)]908 #[transactional]909 pub fn set_variable_meta_data (910 origin,911 collection_id: CollectionId,912 item_id: TokenId,913 data: Vec<u8>914 ) -> DispatchResultWithPostInfo {915 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);916917 dispatch_call::<T, _>(collection_id, |d| d.set_variable_metadata(sender, item_id, data))918 }919920 /// Set meta_update_permission value for particular collection921 ///922 /// # Permissions923 ///924 /// * Collection Owner.925 ///926 /// # Arguments927 ///928 /// * collection_id: ID of the collection.929 ///930 /// * value: New flag value.931 #[weight = <SelfWeightOf<T>>::set_meta_update_permission_flag()]932 #[transactional]933 pub fn set_meta_update_permission_flag(origin, collection_id: CollectionId, value: MetaUpdatePermission) -> DispatchResult {934 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);935 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;936937 ensure!(938 target_collection.meta_update_permission != MetaUpdatePermission::None,939 <CommonError<T>>::MetadataFlagFrozen,940 );941 target_collection.check_is_owner(&sender)?;942943 target_collection.meta_update_permission = value;944945 target_collection.save()946 }947948 /// Set schema standard949 /// ImageURL950 /// Unique951 ///952 /// # Permissions953 ///954 /// * Collection Owner955 /// * Collection Admin956 ///957 /// # Arguments958 ///959 /// * collection_id.960 ///961 /// * schema: SchemaVersion: enum962 #[weight = <SelfWeightOf<T>>::set_schema_version()]963 #[transactional]964 pub fn set_schema_version(965 origin,966 collection_id: CollectionId,967 version: SchemaVersion968 ) -> DispatchResult {969 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);970 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;971 target_collection.check_is_owner_or_admin(&sender)?;972 target_collection.schema_version = version;973974 <Pallet<T>>::deposit_event(Event::<T>::SchemaVersionSet(975 collection_id976 ));977978 target_collection.save()979 }980981 /// Set off-chain data schema.982 ///983 /// # Permissions984 ///985 /// * Collection Owner986 /// * Collection Admin987 ///988 /// # Arguments989 ///990 /// * collection_id.991 ///992 /// * schema: String representing the offchain data schema.993 #[weight = <SelfWeightOf<T>>::set_offchain_schema(schema.len() as u32)]994 #[transactional]995 pub fn set_offchain_schema(996 origin,997 collection_id: CollectionId,998 schema: Vec<u8>999 ) -> DispatchResult {1000 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1001 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1002 target_collection.check_is_owner_or_admin(&sender)?;10031004 // check schema limit1005 ensure!(schema.len() as u32 <= OFFCHAIN_SCHEMA_LIMIT, "");10061007 target_collection.offchain_schema = schema;10081009 <Pallet<T>>::deposit_event(Event::<T>::OffchainSchemaSet(1010 collection_id1011 ));10121013 target_collection.save()1014 }10151016 /// Set const on-chain data schema.1017 ///1018 /// # Permissions1019 ///1020 /// * Collection Owner1021 /// * Collection Admin1022 ///1023 /// # Arguments1024 ///1025 /// * collection_id.1026 ///1027 /// * schema: String representing the const on-chain data schema.1028 #[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]1029 #[transactional]1030 pub fn set_const_on_chain_schema (1031 origin,1032 collection_id: CollectionId,1033 schema: Vec<u8>1034 ) -> DispatchResult {1035 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1036 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1037 target_collection.check_is_owner_or_admin(&sender)?;10381039 // check schema limit1040 ensure!(schema.len() as u32 <= CONST_ON_CHAIN_SCHEMA_LIMIT, "");10411042 target_collection.const_on_chain_schema = schema;10431044 <Pallet<T>>::deposit_event(Event::<T>::ConstOnChainSchemaSet(1045 collection_id1046 ));10471048 target_collection.save()1049 }10501051 /// Set variable on-chain data schema.1052 ///1053 /// # Permissions1054 ///1055 /// * Collection Owner1056 /// * Collection Admin1057 ///1058 /// # Arguments1059 ///1060 /// * collection_id.1061 ///1062 /// * schema: String representing the variable on-chain data schema.1063 #[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]1064 #[transactional]1065 pub fn set_variable_on_chain_schema (1066 origin,1067 collection_id: CollectionId,1068 schema: Vec<u8>1069 ) -> DispatchResult {1070 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1071 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1072 target_collection.check_is_owner_or_admin(&sender)?;10731074 // check schema limit1075 ensure!(schema.len() as u32 <= VARIABLE_ON_CHAIN_SCHEMA_LIMIT, "");10761077 target_collection.variable_on_chain_schema = schema;10781079 <Pallet<T>>::deposit_event(Event::<T>::VariableOnChainSchemaSet(1080 collection_id1081 ));10821083 target_collection.save()1084 }10851086 #[weight = <SelfWeightOf<T>>::set_collection_limits()]1087 #[transactional]1088 pub fn set_collection_limits(1089 origin,1090 collection_id: CollectionId,1091 new_limit: CollectionLimits,1092 ) -> DispatchResult {1093 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1094 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1095 target_collection.check_is_owner(&sender)?;1096 let old_limit = &target_collection.limits;10971098 target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;10991100 <Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(1101 collection_id1102 ));11031104 target_collection.save()1105 }1106 }1107}pallets/unique/src/tests.rsdiffbeforeafterboth--- a/pallets/unique/src/tests.rs
+++ b/pallets/unique/src/tests.rs
@@ -5,7 +5,7 @@
use up_data_structs::{
COLLECTION_NUMBER_LIMIT, CollectionId, CreateItemData, CreateFungibleData, CreateNftData,
CreateReFungibleData, MAX_DECIMAL_POINTS, COLLECTION_ADMINS_LIMIT, MetaUpdatePermission,
- TokenId,
+ TokenId, MAX_TOKEN_OWNERSHIP,
};
use frame_support::{assert_noop, assert_ok};
use sp_std::convert::TryInto;
primitives/data-structs/src/lib.rsdiffbeforeafterboth--- a/primitives/data-structs/src/lib.rs
+++ b/primitives/data-structs/src/lib.rs
@@ -230,6 +230,25 @@
pub meta_update_permission: MetaUpdatePermission,
}
+#[derive(Encode, Decode, Clone, PartialEq, TypeInfo, Debug, Derivative)]
+#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
+#[derivative(Default)]
+pub struct CreateCollectionData<AccountId> {
+ #[derivative(Default(value = "CollectionMode::NFT"))]
+ pub mode: CollectionMode,
+ pub access: Option<AccessMode>,
+ pub name: Vec<u16>,
+ pub description: Vec<u16>,
+ pub token_prefix: Vec<u8>,
+ pub offchain_schema: Vec<u8>,
+ pub schema_version: Option<SchemaVersion>,
+ pub pending_sponsor: Option<AccountId>,
+ pub limits: Option<CollectionLimits>,
+ pub variable_on_chain_schema: Vec<u8>,
+ pub const_on_chain_schema: Vec<u8>,
+ pub meta_update_permission: Option<MetaUpdatePermission>,
+}
+
#[derive(Encode, Decode, Debug, Clone, PartialEq, TypeInfo)]
#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
pub struct NftItemType<AccountId> {