mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-30 16:37:57 +00:00
[NFTs] Rework permissions model (#13482)
* Disallow admin to transfer or burn items he doesn't own * lock_collection should be accessible by collection's owner only * Allow admin to access lock_item_properties() * Fix do_lock_item_properties * Move update_mint_settings() to Issuer * Rename check_owner to check_origin * Typo * Make admin to be in charge of managing the metadata * Make admin the main attributes manager * offchain mint should be signed by Issuer * Remove the special case when the Issuer calls the mint() function * Rework burn and destroy methods * Return back item_metadatas * Don't repatriate the deposit on transfer * A bit more tests * One more test * Add migration * Chore * Clippy * Rename to owned_item * Address comments * Replace .filter_map with .find_map * Improve version validation in pre_upgrade() * ".git/.scripts/commands/bench/bench.sh" pallet dev pallet_nfts --------- Co-authored-by: parity-processbot <>
This commit is contained in:
@@ -30,6 +30,7 @@
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
mod benchmarking;
|
||||
pub mod migration;
|
||||
#[cfg(test)]
|
||||
pub mod mock;
|
||||
#[cfg(test)]
|
||||
@@ -58,6 +59,9 @@ pub use pallet::*;
|
||||
pub use types::*;
|
||||
pub use weights::WeightInfo;
|
||||
|
||||
/// The log target of this pallet.
|
||||
pub const LOG_TARGET: &'static str = "runtime::nfts";
|
||||
|
||||
type AccountIdLookupOf<T> = <<T as SystemConfig>::Lookup as StaticLookup>::Source;
|
||||
|
||||
#[frame_support::pallet]
|
||||
@@ -67,9 +71,13 @@ pub mod pallet {
|
||||
use frame_system::pallet_prelude::*;
|
||||
use sp_runtime::traits::{IdentifyAccount, Verify};
|
||||
|
||||
/// The current storage version.
|
||||
const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
|
||||
|
||||
#[pallet::pallet]
|
||||
#[pallet::generate_store(pub(super) trait Store)]
|
||||
pub struct Pallet<T, I = ()>(_);
|
||||
#[pallet::storage_version(STORAGE_VERSION)]
|
||||
pub struct Pallet<T, I = ()>(PhantomData<(T, I)>);
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub trait BenchmarkHelper<CollectionId, ItemId> {
|
||||
@@ -628,6 +636,8 @@ pub mod pallet {
|
||||
MaxAttributesLimitReached,
|
||||
/// The provided namespace isn't supported in this call.
|
||||
WrongNamespace,
|
||||
/// Can't delete non-empty collections.
|
||||
CollectionNotEmpty,
|
||||
}
|
||||
|
||||
#[pallet::call]
|
||||
@@ -719,20 +729,22 @@ pub mod pallet {
|
||||
/// The origin must conform to `ForceOrigin` or must be `Signed` and the sender must be the
|
||||
/// owner of the `collection`.
|
||||
///
|
||||
/// NOTE: The collection must have 0 items to be destroyed.
|
||||
///
|
||||
/// - `collection`: The identifier of the collection to be destroyed.
|
||||
/// - `witness`: Information on the items minted in the collection. This must be
|
||||
/// correct.
|
||||
///
|
||||
/// Emits `Destroyed` event when successful.
|
||||
///
|
||||
/// Weight: `O(n + m)` where:
|
||||
/// - `n = witness.items`
|
||||
/// Weight: `O(m + c + a)` where:
|
||||
/// - `m = witness.item_metadatas`
|
||||
/// - `c = witness.item_configs`
|
||||
/// - `a = witness.attributes`
|
||||
#[pallet::call_index(2)]
|
||||
#[pallet::weight(T::WeightInfo::destroy(
|
||||
witness.items,
|
||||
witness.item_metadatas,
|
||||
witness.item_metadatas,
|
||||
witness.item_configs,
|
||||
witness.attributes,
|
||||
))]
|
||||
pub fn destroy(
|
||||
@@ -746,8 +758,8 @@ pub mod pallet {
|
||||
let details = Self::do_destroy_collection(collection, witness, maybe_check_owner)?;
|
||||
|
||||
Ok(Some(T::WeightInfo::destroy(
|
||||
details.items,
|
||||
details.item_metadatas,
|
||||
details.item_configs,
|
||||
details.attributes,
|
||||
))
|
||||
.into())
|
||||
@@ -755,7 +767,7 @@ pub mod pallet {
|
||||
|
||||
/// Mint an item of a particular collection.
|
||||
///
|
||||
/// The origin must be Signed and the sender must be the Issuer of the `collection`.
|
||||
/// The origin must be Signed and the sender must comply with the `mint_settings` rules.
|
||||
///
|
||||
/// - `collection`: The collection of the item to be minted.
|
||||
/// - `item`: An identifier of the new item.
|
||||
@@ -789,11 +801,6 @@ pub mod pallet {
|
||||
mint_to.clone(),
|
||||
item_config,
|
||||
|collection_details, collection_config| {
|
||||
// Issuer can mint regardless of mint settings
|
||||
if Self::has_role(&collection, &caller, CollectionRole::Issuer) {
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
let mint_settings = collection_config.mint_settings;
|
||||
let now = frame_system::Pallet::<T>::block_number();
|
||||
|
||||
@@ -805,7 +812,12 @@ pub mod pallet {
|
||||
}
|
||||
|
||||
match mint_settings.mint_type {
|
||||
MintType::Issuer => return Err(Error::<T, I>::NoPermission.into()),
|
||||
MintType::Issuer => {
|
||||
ensure!(
|
||||
Self::has_role(&collection, &caller, CollectionRole::Issuer),
|
||||
Error::<T, I>::NoPermission
|
||||
);
|
||||
},
|
||||
MintType::HolderOf(collection_id) => {
|
||||
let MintWitness { owned_item } =
|
||||
witness_data.ok_or(Error::<T, I>::BadWitness)?;
|
||||
@@ -899,38 +911,30 @@ pub mod pallet {
|
||||
|
||||
/// Destroy a single item.
|
||||
///
|
||||
/// Origin must be Signed and the signing account must be either:
|
||||
/// - the Admin of the `collection`;
|
||||
/// - the Owner of the `item`;
|
||||
/// The origin must conform to `ForceOrigin` or must be Signed and the signing account must
|
||||
/// be the owner of the `item`.
|
||||
///
|
||||
/// - `collection`: The collection of the item to be burned.
|
||||
/// - `item`: The item to be burned.
|
||||
/// - `check_owner`: If `Some` then the operation will fail with `WrongOwner` unless the
|
||||
/// item is owned by this value.
|
||||
///
|
||||
/// Emits `Burned` with the actual amount burned.
|
||||
/// Emits `Burned`.
|
||||
///
|
||||
/// Weight: `O(1)`
|
||||
/// Modes: `check_owner.is_some()`.
|
||||
#[pallet::call_index(5)]
|
||||
#[pallet::weight(T::WeightInfo::burn())]
|
||||
pub fn burn(
|
||||
origin: OriginFor<T>,
|
||||
collection: T::CollectionId,
|
||||
item: T::ItemId,
|
||||
check_owner: Option<AccountIdLookupOf<T>>,
|
||||
) -> DispatchResult {
|
||||
let origin = ensure_signed(origin)?;
|
||||
let check_owner = check_owner.map(T::Lookup::lookup).transpose()?;
|
||||
let maybe_check_origin = T::ForceOrigin::try_origin(origin)
|
||||
.map(|_| None)
|
||||
.or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?;
|
||||
|
||||
Self::do_burn(collection, item, |details| {
|
||||
let is_admin = Self::has_role(&collection, &origin, CollectionRole::Admin);
|
||||
let is_permitted = is_admin || details.owner == origin;
|
||||
ensure!(is_permitted, Error::<T, I>::NoPermission);
|
||||
ensure!(
|
||||
check_owner.map_or(true, |o| o == details.owner),
|
||||
Error::<T, I>::WrongOwner
|
||||
);
|
||||
if let Some(check_origin) = maybe_check_origin {
|
||||
ensure!(details.owner == check_origin, Error::<T, I>::NoPermission);
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
@@ -938,7 +942,6 @@ pub mod pallet {
|
||||
/// Move an item from the sender account to another.
|
||||
///
|
||||
/// Origin must be Signed and the signing account must be either:
|
||||
/// - the Admin of the `collection`;
|
||||
/// - the Owner of the `item`;
|
||||
/// - the approved delegate for the `item` (in this case, the approval is reset).
|
||||
///
|
||||
@@ -962,8 +965,7 @@ pub mod pallet {
|
||||
let dest = T::Lookup::lookup(dest)?;
|
||||
|
||||
Self::do_transfer(collection, item, dest, |_, details| {
|
||||
let is_admin = Self::has_role(&collection, &origin, CollectionRole::Admin);
|
||||
if details.owner != origin && !is_admin {
|
||||
if details.owner != origin {
|
||||
let deadline =
|
||||
details.approvals.get(&origin).ok_or(Error::<T, I>::NoPermission)?;
|
||||
if let Some(d) = deadline {
|
||||
@@ -1086,12 +1088,13 @@ pub mod pallet {
|
||||
|
||||
/// Disallows specified settings for the whole collection.
|
||||
///
|
||||
/// Origin must be Signed and the sender should be the Freezer of the `collection`.
|
||||
/// Origin must be Signed and the sender should be the Owner of the `collection`.
|
||||
///
|
||||
/// - `collection`: The collection to be locked.
|
||||
/// - `lock_settings`: The settings to be locked.
|
||||
///
|
||||
/// Note: it's possible to only lock(set) the setting, but not to unset it.
|
||||
///
|
||||
/// Emits `CollectionLocked`.
|
||||
///
|
||||
/// Weight: `O(1)`
|
||||
@@ -1243,7 +1246,6 @@ pub mod pallet {
|
||||
///
|
||||
/// Origin must be either:
|
||||
/// - the `Force` origin;
|
||||
/// - `Signed` with the signer being the Admin of the `collection`;
|
||||
/// - `Signed` with the signer being the Owner of the `item`;
|
||||
///
|
||||
/// Arguments:
|
||||
@@ -1273,7 +1275,6 @@ pub mod pallet {
|
||||
///
|
||||
/// Origin must be either:
|
||||
/// - the `Force` origin;
|
||||
/// - `Signed` with the signer being the Admin of the `collection`;
|
||||
/// - `Signed` with the signer being the Owner of the `item`;
|
||||
///
|
||||
/// Arguments:
|
||||
@@ -1298,8 +1299,8 @@ pub mod pallet {
|
||||
|
||||
/// Disallows changing the metadata or attributes of the item.
|
||||
///
|
||||
/// Origin must be either `ForceOrigin` or Signed and the sender should be the Owner of the
|
||||
/// `collection`.
|
||||
/// Origin must be either `ForceOrigin` or Signed and the sender should be the Admin
|
||||
/// of the `collection`.
|
||||
///
|
||||
/// - `collection`: The collection if the `item`.
|
||||
/// - `item`: An item to be locked.
|
||||
@@ -1307,8 +1308,8 @@ pub mod pallet {
|
||||
/// - `lock_attributes`: Specifies whether the attributes in the `CollectionOwner` namespace
|
||||
/// should be locked.
|
||||
///
|
||||
/// Note: `lock_attributes` affects the attributes in the `CollectionOwner` namespace
|
||||
/// only. When the metadata or attributes are locked, it won't be possible the unlock them.
|
||||
/// Note: `lock_attributes` affects the attributes in the `CollectionOwner` namespace only.
|
||||
/// When the metadata or attributes are locked, it won't be possible the unlock them.
|
||||
///
|
||||
/// Emits `ItemPropertiesLocked`.
|
||||
///
|
||||
@@ -1322,11 +1323,11 @@ pub mod pallet {
|
||||
lock_metadata: bool,
|
||||
lock_attributes: bool,
|
||||
) -> DispatchResult {
|
||||
let maybe_check_owner = T::ForceOrigin::try_origin(origin)
|
||||
let maybe_check_origin = T::ForceOrigin::try_origin(origin)
|
||||
.map(|_| None)
|
||||
.or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?;
|
||||
Self::do_lock_item_properties(
|
||||
maybe_check_owner,
|
||||
maybe_check_origin,
|
||||
collection,
|
||||
item,
|
||||
lock_metadata,
|
||||
@@ -1337,7 +1338,7 @@ pub mod pallet {
|
||||
/// Set an attribute for a collection or item.
|
||||
///
|
||||
/// Origin must be Signed and must conform to the namespace ruleset:
|
||||
/// - `CollectionOwner` namespace could be modified by the `collection` owner only;
|
||||
/// - `CollectionOwner` namespace could be modified by the `collection` Admin only;
|
||||
/// - `ItemOwner` namespace could be modified by the `maybe_item` owner only. `maybe_item`
|
||||
/// should be set in that case;
|
||||
/// - `Account(AccountId)` namespace could be modified only when the `origin` was given a
|
||||
@@ -1367,15 +1368,12 @@ pub mod pallet {
|
||||
value: BoundedVec<u8, T::ValueLimit>,
|
||||
) -> DispatchResult {
|
||||
let origin = ensure_signed(origin)?;
|
||||
Self::do_set_attribute(
|
||||
origin.clone(),
|
||||
collection,
|
||||
maybe_item,
|
||||
namespace,
|
||||
key,
|
||||
value,
|
||||
origin,
|
||||
)
|
||||
let depositor = match namespace {
|
||||
AttributeNamespace::CollectionOwner =>
|
||||
Self::collection_owner(collection).ok_or(Error::<T, I>::UnknownCollection)?,
|
||||
_ => origin.clone(),
|
||||
};
|
||||
Self::do_set_attribute(origin, collection, maybe_item, namespace, key, value, depositor)
|
||||
}
|
||||
|
||||
/// Force-set an attribute for a collection or item.
|
||||
@@ -1490,7 +1488,7 @@ pub mod pallet {
|
||||
|
||||
/// Set the metadata for an item.
|
||||
///
|
||||
/// Origin must be either `ForceOrigin` or Signed and the sender should be the Owner of the
|
||||
/// Origin must be either `ForceOrigin` or Signed and the sender should be the Admin of the
|
||||
/// `collection`.
|
||||
///
|
||||
/// If the origin is Signed, then funds of signer are reserved according to the formula:
|
||||
@@ -1512,15 +1510,15 @@ pub mod pallet {
|
||||
item: T::ItemId,
|
||||
data: BoundedVec<u8, T::StringLimit>,
|
||||
) -> DispatchResult {
|
||||
let maybe_check_owner = T::ForceOrigin::try_origin(origin)
|
||||
let maybe_check_origin = T::ForceOrigin::try_origin(origin)
|
||||
.map(|_| None)
|
||||
.or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?;
|
||||
Self::do_set_item_metadata(maybe_check_owner, collection, item, data, None)
|
||||
Self::do_set_item_metadata(maybe_check_origin, collection, item, data, None)
|
||||
}
|
||||
|
||||
/// Clear the metadata for an item.
|
||||
///
|
||||
/// Origin must be either `ForceOrigin` or Signed and the sender should be the Owner of the
|
||||
/// Origin must be either `ForceOrigin` or Signed and the sender should be the Admin of the
|
||||
/// `collection`.
|
||||
///
|
||||
/// Any deposit is freed for the collection's owner.
|
||||
@@ -1538,15 +1536,15 @@ pub mod pallet {
|
||||
collection: T::CollectionId,
|
||||
item: T::ItemId,
|
||||
) -> DispatchResult {
|
||||
let maybe_check_owner = T::ForceOrigin::try_origin(origin)
|
||||
let maybe_check_origin = T::ForceOrigin::try_origin(origin)
|
||||
.map(|_| None)
|
||||
.or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?;
|
||||
Self::do_clear_item_metadata(maybe_check_owner, collection, item)
|
||||
Self::do_clear_item_metadata(maybe_check_origin, collection, item)
|
||||
}
|
||||
|
||||
/// Set the metadata for a collection.
|
||||
///
|
||||
/// Origin must be either `ForceOrigin` or `Signed` and the sender should be the Owner of
|
||||
/// Origin must be either `ForceOrigin` or `Signed` and the sender should be the Admin of
|
||||
/// the `collection`.
|
||||
///
|
||||
/// If the origin is `Signed`, then funds of signer are reserved according to the formula:
|
||||
@@ -1566,15 +1564,15 @@ pub mod pallet {
|
||||
collection: T::CollectionId,
|
||||
data: BoundedVec<u8, T::StringLimit>,
|
||||
) -> DispatchResult {
|
||||
let maybe_check_owner = T::ForceOrigin::try_origin(origin)
|
||||
let maybe_check_origin = T::ForceOrigin::try_origin(origin)
|
||||
.map(|_| None)
|
||||
.or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?;
|
||||
Self::do_set_collection_metadata(maybe_check_owner, collection, data)
|
||||
Self::do_set_collection_metadata(maybe_check_origin, collection, data)
|
||||
}
|
||||
|
||||
/// Clear the metadata for a collection.
|
||||
///
|
||||
/// Origin must be either `ForceOrigin` or `Signed` and the sender should be the Owner of
|
||||
/// Origin must be either `ForceOrigin` or `Signed` and the sender should be the Admin of
|
||||
/// the `collection`.
|
||||
///
|
||||
/// Any deposit is freed for the collection's owner.
|
||||
@@ -1590,10 +1588,10 @@ pub mod pallet {
|
||||
origin: OriginFor<T>,
|
||||
collection: T::CollectionId,
|
||||
) -> DispatchResult {
|
||||
let maybe_check_owner = T::ForceOrigin::try_origin(origin)
|
||||
let maybe_check_origin = T::ForceOrigin::try_origin(origin)
|
||||
.map(|_| None)
|
||||
.or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?;
|
||||
Self::do_clear_collection_metadata(maybe_check_owner, collection)
|
||||
Self::do_clear_collection_metadata(maybe_check_origin, collection)
|
||||
}
|
||||
|
||||
/// Set (or reset) the acceptance of ownership for a particular account.
|
||||
@@ -1640,8 +1638,8 @@ pub mod pallet {
|
||||
|
||||
/// Update mint settings.
|
||||
///
|
||||
/// Origin must be either `ForceOrigin` or `Signed` and the sender should be the Owner of
|
||||
/// the `collection`.
|
||||
/// Origin must be either `ForceOrigin` or `Signed` and the sender should be the Issuer
|
||||
/// of the `collection`.
|
||||
///
|
||||
/// - `collection`: The identifier of the collection to change.
|
||||
/// - `mint_settings`: The new mint settings.
|
||||
@@ -1658,15 +1656,15 @@ pub mod pallet {
|
||||
T::CollectionId,
|
||||
>,
|
||||
) -> DispatchResult {
|
||||
let maybe_check_owner = T::ForceOrigin::try_origin(origin)
|
||||
let maybe_check_origin = T::ForceOrigin::try_origin(origin)
|
||||
.map(|_| None)
|
||||
.or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?;
|
||||
Self::do_update_mint_settings(maybe_check_owner, collection, mint_settings)
|
||||
Self::do_update_mint_settings(maybe_check_origin, collection, mint_settings)
|
||||
}
|
||||
|
||||
/// Set (or reset) the price for an item.
|
||||
///
|
||||
/// Origin must be Signed and must be the owner of the asset `item`.
|
||||
/// Origin must be Signed and must be the owner of the `item`.
|
||||
///
|
||||
/// - `collection`: The collection of the item.
|
||||
/// - `item`: The item to set the price for.
|
||||
@@ -1827,7 +1825,7 @@ pub mod pallet {
|
||||
/// its metadata, attributes, who can mint it (`None` for anyone) and until what block
|
||||
/// number.
|
||||
/// - `signature`: The signature of the `data` object.
|
||||
/// - `signer`: The `data` object's signer. Should be an owner of the collection.
|
||||
/// - `signer`: The `data` object's signer. Should be an Issuer of the collection.
|
||||
///
|
||||
/// Emits `Issued` on success.
|
||||
/// Emits `AttributeSet` if the attributes were provided.
|
||||
@@ -1853,7 +1851,7 @@ pub mod pallet {
|
||||
/// - `data`: The pre-signed approval that consists of the information about the item,
|
||||
/// attributes to update and until what block number.
|
||||
/// - `signature`: The signature of the `data` object.
|
||||
/// - `signer`: The `data` object's signer. Should be an owner of the collection for the
|
||||
/// - `signer`: The `data` object's signer. Should be an Admin of the collection for the
|
||||
/// `CollectionOwner` namespace.
|
||||
///
|
||||
/// Emits `AttributeSet` for each provided attribute.
|
||||
|
||||
Reference in New Issue
Block a user