mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-07-06 23:07:22 +00:00
1227 lines
43 KiB
Rust
1227 lines
43 KiB
Rust
// This file is part of Substrate.
|
|
|
|
// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
//! # Unique (Assets) Module
|
|
//!
|
|
//! A simple, secure module for dealing with non-fungible assets.
|
|
//!
|
|
//! ## Related Modules
|
|
//!
|
|
//! * [`System`](../frame_system/index.html)
|
|
//! * [`Support`](../frame_support/index.html)
|
|
|
|
// Ensure we're `no_std` when compiling for Wasm.
|
|
#![cfg_attr(not(feature = "std"), no_std)]
|
|
|
|
#[cfg(feature = "runtime-benchmarks")]
|
|
mod benchmarking;
|
|
#[cfg(test)]
|
|
pub mod mock;
|
|
#[cfg(test)]
|
|
mod tests;
|
|
pub mod weights;
|
|
|
|
mod functions;
|
|
mod impl_nonfungibles;
|
|
mod types;
|
|
pub use types::*;
|
|
|
|
use codec::{Decode, Encode, HasCompact};
|
|
use frame_support::traits::{BalanceStatus::Reserved, Currency, ReservableCurrency};
|
|
use frame_system::Config as SystemConfig;
|
|
use sp_runtime::{
|
|
traits::{Saturating, StaticLookup, Zero},
|
|
ArithmeticError, RuntimeDebug,
|
|
};
|
|
use sp_std::prelude::*;
|
|
|
|
pub use pallet::*;
|
|
pub use weights::WeightInfo;
|
|
|
|
#[frame_support::pallet]
|
|
pub mod pallet {
|
|
use super::*;
|
|
use frame_support::pallet_prelude::*;
|
|
use frame_system::pallet_prelude::*;
|
|
|
|
#[pallet::pallet]
|
|
#[pallet::generate_store(pub(super) trait Store)]
|
|
pub struct Pallet<T, I = ()>(_);
|
|
|
|
#[pallet::config]
|
|
/// The module configuration trait.
|
|
pub trait Config<I: 'static = ()>: frame_system::Config {
|
|
/// The overarching event type.
|
|
type Event: From<Event<Self, I>> + IsType<<Self as frame_system::Config>::Event>;
|
|
|
|
/// Identifier for the class of asset.
|
|
type ClassId: Member + Parameter + Default + Copy + HasCompact;
|
|
|
|
/// The type used to identify a unique asset within an asset class.
|
|
type InstanceId: Member + Parameter + Default + Copy + HasCompact + From<u16>;
|
|
|
|
/// The currency mechanism, used for paying for reserves.
|
|
type Currency: ReservableCurrency<Self::AccountId>;
|
|
|
|
/// The origin which may forcibly create or destroy an asset or otherwise alter privileged
|
|
/// attributes.
|
|
type ForceOrigin: EnsureOrigin<Self::Origin>;
|
|
|
|
/// The basic amount of funds that must be reserved for an asset class.
|
|
#[pallet::constant]
|
|
type ClassDeposit: Get<DepositBalanceOf<Self, I>>;
|
|
|
|
/// The basic amount of funds that must be reserved for an asset instance.
|
|
#[pallet::constant]
|
|
type InstanceDeposit: Get<DepositBalanceOf<Self, I>>;
|
|
|
|
/// The basic amount of funds that must be reserved when adding metadata to your asset.
|
|
#[pallet::constant]
|
|
type MetadataDepositBase: Get<DepositBalanceOf<Self, I>>;
|
|
|
|
/// The basic amount of funds that must be reserved when adding an attribute to an asset.
|
|
#[pallet::constant]
|
|
type AttributeDepositBase: Get<DepositBalanceOf<Self, I>>;
|
|
|
|
/// The additional funds that must be reserved for the number of bytes store in metadata,
|
|
/// either "normal" metadata or attribute metadata.
|
|
#[pallet::constant]
|
|
type DepositPerByte: Get<DepositBalanceOf<Self, I>>;
|
|
|
|
/// The maximum length of data stored on-chain.
|
|
#[pallet::constant]
|
|
type StringLimit: Get<u32>;
|
|
|
|
/// The maximum length of an attribute key.
|
|
#[pallet::constant]
|
|
type KeyLimit: Get<u32>;
|
|
|
|
/// The maximum length of an attribute value.
|
|
#[pallet::constant]
|
|
type ValueLimit: Get<u32>;
|
|
|
|
/// Weight information for extrinsics in this pallet.
|
|
type WeightInfo: WeightInfo;
|
|
}
|
|
|
|
#[pallet::storage]
|
|
/// Details of an asset class.
|
|
pub(super) type Class<T: Config<I>, I: 'static = ()> = StorageMap<
|
|
_,
|
|
Blake2_128Concat,
|
|
T::ClassId,
|
|
ClassDetails<T::AccountId, DepositBalanceOf<T, I>>,
|
|
>;
|
|
|
|
#[pallet::storage]
|
|
/// The assets held by any given account; set out this way so that assets owned by a single
|
|
/// account can be enumerated.
|
|
pub(super) type Account<T: Config<I>, I: 'static = ()> = StorageNMap<
|
|
_,
|
|
(
|
|
NMapKey<Blake2_128Concat, T::AccountId>, // owner
|
|
NMapKey<Blake2_128Concat, T::ClassId>,
|
|
NMapKey<Blake2_128Concat, T::InstanceId>,
|
|
),
|
|
(),
|
|
OptionQuery,
|
|
>;
|
|
|
|
#[pallet::storage]
|
|
/// The assets in existence and their ownership details.
|
|
pub(super) type Asset<T: Config<I>, I: 'static = ()> = StorageDoubleMap<
|
|
_,
|
|
Blake2_128Concat,
|
|
T::ClassId,
|
|
Blake2_128Concat,
|
|
T::InstanceId,
|
|
InstanceDetails<T::AccountId, DepositBalanceOf<T, I>>,
|
|
OptionQuery,
|
|
>;
|
|
|
|
#[pallet::storage]
|
|
/// Metadata of an asset class.
|
|
pub(super) type ClassMetadataOf<T: Config<I>, I: 'static = ()> = StorageMap<
|
|
_,
|
|
Blake2_128Concat,
|
|
T::ClassId,
|
|
ClassMetadata<DepositBalanceOf<T, I>, T::StringLimit>,
|
|
OptionQuery,
|
|
>;
|
|
|
|
#[pallet::storage]
|
|
/// Metadata of an asset instance.
|
|
pub(super) type InstanceMetadataOf<T: Config<I>, I: 'static = ()> = StorageDoubleMap<
|
|
_,
|
|
Blake2_128Concat,
|
|
T::ClassId,
|
|
Blake2_128Concat,
|
|
T::InstanceId,
|
|
InstanceMetadata<DepositBalanceOf<T, I>, T::StringLimit>,
|
|
OptionQuery,
|
|
>;
|
|
|
|
#[pallet::storage]
|
|
/// Metadata of an asset class.
|
|
pub(super) type Attribute<T: Config<I>, I: 'static = ()> = StorageNMap<
|
|
_,
|
|
(
|
|
NMapKey<Blake2_128Concat, T::ClassId>,
|
|
NMapKey<Blake2_128Concat, Option<T::InstanceId>>,
|
|
NMapKey<Blake2_128Concat, BoundedVec<u8, T::KeyLimit>>,
|
|
),
|
|
(BoundedVec<u8, T::ValueLimit>, DepositBalanceOf<T, I>),
|
|
OptionQuery,
|
|
>;
|
|
|
|
#[pallet::event]
|
|
#[pallet::generate_deposit(pub(super) fn deposit_event)]
|
|
#[pallet::metadata(
|
|
T::AccountId = "AccountId",
|
|
T::ClassId = "ClassId",
|
|
T::InstanceId = "InstanceId"
|
|
)]
|
|
pub enum Event<T: Config<I>, I: 'static = ()> {
|
|
/// An asset class was created. \[ class, creator, owner \]
|
|
Created(T::ClassId, T::AccountId, T::AccountId),
|
|
/// An asset class was force-created. \[ class, owner \]
|
|
ForceCreated(T::ClassId, T::AccountId),
|
|
/// An asset `class` was destroyed. \[ class \]
|
|
Destroyed(T::ClassId),
|
|
/// An asset `instance` was issued. \[ class, instance, owner \]
|
|
Issued(T::ClassId, T::InstanceId, T::AccountId),
|
|
/// An asset `instance` was transferred. \[ class, instance, from, to \]
|
|
Transferred(T::ClassId, T::InstanceId, T::AccountId, T::AccountId),
|
|
/// An asset `instance` was destroyed. \[ class, instance, owner \]
|
|
Burned(T::ClassId, T::InstanceId, T::AccountId),
|
|
/// Some asset `instance` was frozen. \[ class, instance \]
|
|
Frozen(T::ClassId, T::InstanceId),
|
|
/// Some asset `instance` was thawed. \[ class, instance \]
|
|
Thawed(T::ClassId, T::InstanceId),
|
|
/// Some asset `class` was frozen. \[ class \]
|
|
ClassFrozen(T::ClassId),
|
|
/// Some asset `class` was thawed. \[ class \]
|
|
ClassThawed(T::ClassId),
|
|
/// The owner changed \[ class, new_owner \]
|
|
OwnerChanged(T::ClassId, T::AccountId),
|
|
/// The management team changed \[ class, issuer, admin, freezer \]
|
|
TeamChanged(T::ClassId, T::AccountId, T::AccountId, T::AccountId),
|
|
/// An `instance` of an asset `class` has been approved by the `owner` for transfer by a
|
|
/// `delegate`.
|
|
/// \[ class, instance, owner, delegate \]
|
|
ApprovedTransfer(T::ClassId, T::InstanceId, T::AccountId, T::AccountId),
|
|
/// An approval for a `delegate` account to transfer the `instance` of an asset `class` was
|
|
/// cancelled by its `owner`.
|
|
/// \[ class, instance, owner, delegate \]
|
|
ApprovalCancelled(T::ClassId, T::InstanceId, T::AccountId, T::AccountId),
|
|
/// An asset `class` has had its attributes changed by the `Force` origin.
|
|
/// \[ class \]
|
|
AssetStatusChanged(T::ClassId),
|
|
/// New metadata has been set for an asset class. \[ class, data, is_frozen \]
|
|
ClassMetadataSet(T::ClassId, BoundedVec<u8, T::StringLimit>, bool),
|
|
/// Metadata has been cleared for an asset class. \[ class \]
|
|
ClassMetadataCleared(T::ClassId),
|
|
/// New metadata has been set for an asset instance.
|
|
/// \[ class, instance, data, is_frozen \]
|
|
MetadataSet(T::ClassId, T::InstanceId, BoundedVec<u8, T::StringLimit>, bool),
|
|
/// Metadata has been cleared for an asset instance. \[ class, instance \]
|
|
MetadataCleared(T::ClassId, T::InstanceId),
|
|
/// Metadata has been cleared for an asset instance. \[ class, successful_instances \]
|
|
Redeposited(T::ClassId, Vec<T::InstanceId>),
|
|
/// New attribute metadata has been set for an asset class or instance.
|
|
/// \[ class, maybe_instance, key, value \]
|
|
AttributeSet(
|
|
T::ClassId,
|
|
Option<T::InstanceId>,
|
|
BoundedVec<u8, T::KeyLimit>,
|
|
BoundedVec<u8, T::ValueLimit>,
|
|
),
|
|
/// Attribute metadata has been cleared for an asset class or instance.
|
|
/// \[ class, maybe_instance, key, maybe_value \]
|
|
AttributeCleared(T::ClassId, Option<T::InstanceId>, BoundedVec<u8, T::KeyLimit>),
|
|
}
|
|
|
|
#[pallet::error]
|
|
pub enum Error<T, I = ()> {
|
|
/// The signing account has no permission to do the operation.
|
|
NoPermission,
|
|
/// The given asset ID is unknown.
|
|
Unknown,
|
|
/// The asset instance ID has already been used for an asset.
|
|
AlreadyExists,
|
|
/// The owner turned out to be different to what was expected.
|
|
WrongOwner,
|
|
/// Invalid witness data given.
|
|
BadWitness,
|
|
/// The asset ID is already taken.
|
|
InUse,
|
|
/// The asset instance or class is frozen.
|
|
Frozen,
|
|
/// The delegate turned out to be different to what was expected.
|
|
WrongDelegate,
|
|
/// There is no delegate approved.
|
|
NoDelegate,
|
|
/// No approval exists that would allow the transfer.
|
|
Unapproved,
|
|
}
|
|
|
|
#[pallet::hooks]
|
|
impl<T: Config<I>, I: 'static> Hooks<BlockNumberFor<T>> for Pallet<T, I> {}
|
|
|
|
impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
|
/// Get the owner of the asset instance, if the asset exists.
|
|
pub fn owner(class: T::ClassId, instance: T::InstanceId) -> Option<T::AccountId> {
|
|
Asset::<T, I>::get(class, instance).map(|i| i.owner)
|
|
}
|
|
}
|
|
|
|
#[pallet::call]
|
|
impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
|
/// Issue a new class of non-fungible assets from a public origin.
|
|
///
|
|
/// This new asset class has no assets initially and its owner is the origin.
|
|
///
|
|
/// The origin must be Signed and the sender must have sufficient funds free.
|
|
///
|
|
/// `AssetDeposit` funds of sender are reserved.
|
|
///
|
|
/// Parameters:
|
|
/// - `class`: The identifier of the new asset class. This must not be currently in use.
|
|
/// - `admin`: The admin of this class of assets. The admin is the initial address of each
|
|
/// member of the asset class's admin team.
|
|
///
|
|
/// Emits `Created` event when successful.
|
|
///
|
|
/// Weight: `O(1)`
|
|
#[pallet::weight(T::WeightInfo::create())]
|
|
pub fn create(
|
|
origin: OriginFor<T>,
|
|
#[pallet::compact] class: T::ClassId,
|
|
admin: <T::Lookup as StaticLookup>::Source,
|
|
) -> DispatchResult {
|
|
let owner = ensure_signed(origin)?;
|
|
let admin = T::Lookup::lookup(admin)?;
|
|
|
|
Self::do_create_class(
|
|
class,
|
|
owner.clone(),
|
|
admin.clone(),
|
|
T::ClassDeposit::get(),
|
|
false,
|
|
Event::Created(class, owner, admin),
|
|
)
|
|
}
|
|
|
|
/// Issue a new class of non-fungible assets from a privileged origin.
|
|
///
|
|
/// This new asset class has no assets initially.
|
|
///
|
|
/// The origin must conform to `ForceOrigin`.
|
|
///
|
|
/// Unlike `create`, no funds are reserved.
|
|
///
|
|
/// - `class`: The identifier of the new asset. This must not be currently in use.
|
|
/// - `owner`: The owner of this class of assets. The owner has full superuser permissions
|
|
/// over this asset, but may later change and configure the permissions using
|
|
/// `transfer_ownership` and `set_team`.
|
|
///
|
|
/// Emits `ForceCreated` event when successful.
|
|
///
|
|
/// Weight: `O(1)`
|
|
#[pallet::weight(T::WeightInfo::force_create())]
|
|
pub fn force_create(
|
|
origin: OriginFor<T>,
|
|
#[pallet::compact] class: T::ClassId,
|
|
owner: <T::Lookup as StaticLookup>::Source,
|
|
free_holding: bool,
|
|
) -> DispatchResult {
|
|
T::ForceOrigin::ensure_origin(origin)?;
|
|
let owner = T::Lookup::lookup(owner)?;
|
|
|
|
Self::do_create_class(
|
|
class,
|
|
owner.clone(),
|
|
owner.clone(),
|
|
Zero::zero(),
|
|
free_holding,
|
|
Event::ForceCreated(class, owner),
|
|
)
|
|
}
|
|
|
|
/// Destroy a class of fungible assets.
|
|
///
|
|
/// The origin must conform to `ForceOrigin` or must be `Signed` and the sender must be the
|
|
/// owner of the asset `class`.
|
|
///
|
|
/// - `class`: The identifier of the asset class to be destroyed.
|
|
/// - `witness`: Information on the instances minted in the asset class. This must be
|
|
/// correct.
|
|
///
|
|
/// Emits `Destroyed` event when successful.
|
|
///
|
|
/// Weight: `O(n + m)` where:
|
|
/// - `n = witness.instances`
|
|
/// - `m = witness.instance_metadatas`
|
|
/// - `a = witness.attributes`
|
|
#[pallet::weight(T::WeightInfo::destroy(
|
|
witness.instances,
|
|
witness.instance_metadatas,
|
|
witness.attributes,
|
|
))]
|
|
pub fn destroy(
|
|
origin: OriginFor<T>,
|
|
#[pallet::compact] class: T::ClassId,
|
|
witness: DestroyWitness,
|
|
) -> DispatchResult {
|
|
let maybe_check_owner = match T::ForceOrigin::try_origin(origin) {
|
|
Ok(_) => None,
|
|
Err(origin) => Some(ensure_signed(origin)?),
|
|
};
|
|
Class::<T, I>::try_mutate_exists(class, |maybe_details| {
|
|
let class_details = maybe_details.take().ok_or(Error::<T, I>::Unknown)?;
|
|
if let Some(check_owner) = maybe_check_owner {
|
|
ensure!(class_details.owner == check_owner, Error::<T, I>::NoPermission);
|
|
}
|
|
ensure!(class_details.instances == witness.instances, Error::<T, I>::BadWitness);
|
|
ensure!(
|
|
class_details.instance_metadatas == witness.instance_metadatas,
|
|
Error::<T, I>::BadWitness
|
|
);
|
|
ensure!(class_details.attributes == witness.attributes, Error::<T, I>::BadWitness);
|
|
|
|
for (instance, details) in Asset::<T, I>::drain_prefix(&class) {
|
|
Account::<T, I>::remove((&details.owner, &class, &instance));
|
|
}
|
|
InstanceMetadataOf::<T, I>::remove_prefix(&class, None);
|
|
ClassMetadataOf::<T, I>::remove(&class);
|
|
Attribute::<T, I>::remove_prefix((&class,), None);
|
|
T::Currency::unreserve(&class_details.owner, class_details.total_deposit);
|
|
|
|
Self::deposit_event(Event::Destroyed(class));
|
|
|
|
// NOTE: could use postinfo to reflect the actual number of
|
|
// accounts/sufficient/approvals
|
|
Ok(())
|
|
})
|
|
}
|
|
|
|
/// Mint an asset instance of a particular class.
|
|
///
|
|
/// The origin must be Signed and the sender must be the Issuer of the asset `class`.
|
|
///
|
|
/// - `class`: The class of the asset to be minted.
|
|
/// - `instance`: The instance value of the asset to be minted.
|
|
/// - `beneficiary`: The initial owner of the minted asset.
|
|
///
|
|
/// Emits `Issued` event when successful.
|
|
///
|
|
/// Weight: `O(1)`
|
|
#[pallet::weight(T::WeightInfo::mint())]
|
|
pub fn mint(
|
|
origin: OriginFor<T>,
|
|
#[pallet::compact] class: T::ClassId,
|
|
#[pallet::compact] instance: T::InstanceId,
|
|
owner: <T::Lookup as StaticLookup>::Source,
|
|
) -> DispatchResult {
|
|
let origin = ensure_signed(origin)?;
|
|
let owner = T::Lookup::lookup(owner)?;
|
|
|
|
Self::do_mint(class, instance, owner, |class_details| {
|
|
ensure!(class_details.issuer == origin, Error::<T, I>::NoPermission);
|
|
Ok(())
|
|
})
|
|
}
|
|
|
|
/// Destroy a single asset instance.
|
|
///
|
|
/// Origin must be Signed and the sender should be the Admin of the asset `class`.
|
|
///
|
|
/// - `class`: The class of the asset to be burned.
|
|
/// - `instance`: The instance of the asset to be burned.
|
|
/// - `check_owner`: If `Some` then the operation will fail with `WrongOwner` unless the
|
|
/// asset is owned by this value.
|
|
///
|
|
/// Emits `Burned` with the actual amount burned.
|
|
///
|
|
/// Weight: `O(1)`
|
|
/// Modes: `check_owner.is_some()`.
|
|
#[pallet::weight(T::WeightInfo::burn())]
|
|
pub fn burn(
|
|
origin: OriginFor<T>,
|
|
#[pallet::compact] class: T::ClassId,
|
|
#[pallet::compact] instance: T::InstanceId,
|
|
check_owner: Option<<T::Lookup as StaticLookup>::Source>,
|
|
) -> DispatchResult {
|
|
let origin = ensure_signed(origin)?;
|
|
let check_owner = check_owner.map(T::Lookup::lookup).transpose()?;
|
|
|
|
Self::do_burn(class, instance, |class_details, details| {
|
|
let is_permitted = class_details.admin == origin || details.owner == origin;
|
|
ensure!(is_permitted, Error::<T, I>::NoPermission);
|
|
ensure!(
|
|
check_owner.map_or(true, |o| o == details.owner),
|
|
Error::<T, I>::WrongOwner
|
|
);
|
|
Ok(())
|
|
})
|
|
}
|
|
|
|
/// Move an asset from the sender account to another.
|
|
///
|
|
/// Origin must be Signed and the signing account must be either:
|
|
/// - the Admin of the asset `class`;
|
|
/// - the Owner of the asset `instance`;
|
|
/// - the approved delegate for the asset `instance` (in this case, the approval is reset).
|
|
///
|
|
/// Arguments:
|
|
/// - `class`: The class of the asset to be transferred.
|
|
/// - `instance`: The instance of the asset to be transferred.
|
|
/// - `dest`: The account to receive ownership of the asset.
|
|
///
|
|
/// Emits `Transferred`.
|
|
///
|
|
/// Weight: `O(1)`
|
|
#[pallet::weight(T::WeightInfo::transfer())]
|
|
pub fn transfer(
|
|
origin: OriginFor<T>,
|
|
#[pallet::compact] class: T::ClassId,
|
|
#[pallet::compact] instance: T::InstanceId,
|
|
dest: <T::Lookup as StaticLookup>::Source,
|
|
) -> DispatchResult {
|
|
let origin = ensure_signed(origin)?;
|
|
let dest = T::Lookup::lookup(dest)?;
|
|
|
|
Self::do_transfer(class, instance, dest, |class_details, details| {
|
|
if details.owner != origin && class_details.admin != origin {
|
|
let approved = details.approved.take().map_or(false, |i| i == origin);
|
|
ensure!(approved, Error::<T, I>::NoPermission);
|
|
}
|
|
Ok(())
|
|
})
|
|
}
|
|
|
|
/// Reevaluate the deposits on some assets.
|
|
///
|
|
/// Origin must be Signed and the sender should be the Owner of the asset `class`.
|
|
///
|
|
/// - `class`: The class of the asset to be frozen.
|
|
/// - `instances`: The instances of the asset class whose deposits will be reevaluated.
|
|
///
|
|
/// NOTE: This exists as a best-effort function. Any asset instances which are unknown or
|
|
/// in the case that the owner account does not have reservable funds to pay for a
|
|
/// deposit increase are ignored. Generally the owner isn't going to call this on instances
|
|
/// whose existing deposit is less than the refreshed deposit as it would only cost them,
|
|
/// so it's of little consequence.
|
|
///
|
|
/// It will still return an error in the case that the class is unknown of the signer is
|
|
/// not permitted to call it.
|
|
///
|
|
/// Weight: `O(instances.len())`
|
|
#[pallet::weight(T::WeightInfo::redeposit(instances.len() as u32))]
|
|
pub fn redeposit(
|
|
origin: OriginFor<T>,
|
|
#[pallet::compact] class: T::ClassId,
|
|
instances: Vec<T::InstanceId>,
|
|
) -> DispatchResult {
|
|
let origin = ensure_signed(origin)?;
|
|
|
|
let mut class_details = Class::<T, I>::get(&class).ok_or(Error::<T, I>::Unknown)?;
|
|
ensure!(class_details.owner == origin, Error::<T, I>::NoPermission);
|
|
let deposit = match class_details.free_holding {
|
|
true => Zero::zero(),
|
|
false => T::InstanceDeposit::get(),
|
|
};
|
|
|
|
let mut successful = Vec::with_capacity(instances.len());
|
|
for instance in instances.into_iter() {
|
|
let mut details = match Asset::<T, I>::get(&class, &instance) {
|
|
Some(x) => x,
|
|
None => continue,
|
|
};
|
|
let old = details.deposit;
|
|
if old > deposit {
|
|
T::Currency::unreserve(&class_details.owner, old - deposit);
|
|
} else if deposit > old {
|
|
if T::Currency::reserve(&class_details.owner, deposit - old).is_err() {
|
|
// NOTE: No alterations made to class_details in this iteration so far, so
|
|
// this is OK to do.
|
|
continue
|
|
}
|
|
} else {
|
|
continue
|
|
}
|
|
class_details.total_deposit.saturating_accrue(deposit);
|
|
class_details.total_deposit.saturating_reduce(old);
|
|
details.deposit = deposit;
|
|
Asset::<T, I>::insert(&class, &instance, &details);
|
|
successful.push(instance);
|
|
}
|
|
Class::<T, I>::insert(&class, &class_details);
|
|
|
|
Self::deposit_event(Event::<T, I>::Redeposited(class, successful));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Disallow further unprivileged transfer of an asset instance.
|
|
///
|
|
/// Origin must be Signed and the sender should be the Freezer of the asset `class`.
|
|
///
|
|
/// - `class`: The class of the asset to be frozen.
|
|
/// - `instance`: The instance of the asset to be frozen.
|
|
///
|
|
/// Emits `Frozen`.
|
|
///
|
|
/// Weight: `O(1)`
|
|
#[pallet::weight(T::WeightInfo::freeze())]
|
|
pub fn freeze(
|
|
origin: OriginFor<T>,
|
|
#[pallet::compact] class: T::ClassId,
|
|
#[pallet::compact] instance: T::InstanceId,
|
|
) -> DispatchResult {
|
|
let origin = ensure_signed(origin)?;
|
|
|
|
let mut details =
|
|
Asset::<T, I>::get(&class, &instance).ok_or(Error::<T, I>::Unknown)?;
|
|
let class_details = Class::<T, I>::get(&class).ok_or(Error::<T, I>::Unknown)?;
|
|
ensure!(class_details.freezer == origin, Error::<T, I>::NoPermission);
|
|
|
|
details.is_frozen = true;
|
|
Asset::<T, I>::insert(&class, &instance, &details);
|
|
|
|
Self::deposit_event(Event::<T, I>::Frozen(class, instance));
|
|
Ok(())
|
|
}
|
|
|
|
/// Re-allow unprivileged transfer of an asset instance.
|
|
///
|
|
/// Origin must be Signed and the sender should be the Freezer of the asset `class`.
|
|
///
|
|
/// - `class`: The class of the asset to be thawed.
|
|
/// - `instance`: The instance of the asset to be thawed.
|
|
///
|
|
/// Emits `Thawed`.
|
|
///
|
|
/// Weight: `O(1)`
|
|
#[pallet::weight(T::WeightInfo::thaw())]
|
|
pub fn thaw(
|
|
origin: OriginFor<T>,
|
|
#[pallet::compact] class: T::ClassId,
|
|
#[pallet::compact] instance: T::InstanceId,
|
|
) -> DispatchResult {
|
|
let origin = ensure_signed(origin)?;
|
|
|
|
let mut details =
|
|
Asset::<T, I>::get(&class, &instance).ok_or(Error::<T, I>::Unknown)?;
|
|
let class_details = Class::<T, I>::get(&class).ok_or(Error::<T, I>::Unknown)?;
|
|
ensure!(class_details.admin == origin, Error::<T, I>::NoPermission);
|
|
|
|
details.is_frozen = false;
|
|
Asset::<T, I>::insert(&class, &instance, &details);
|
|
|
|
Self::deposit_event(Event::<T, I>::Thawed(class, instance));
|
|
Ok(())
|
|
}
|
|
|
|
/// Disallow further unprivileged transfers for a whole asset class.
|
|
///
|
|
/// Origin must be Signed and the sender should be the Freezer of the asset `class`.
|
|
///
|
|
/// - `class`: The asset class to be frozen.
|
|
///
|
|
/// Emits `ClassFrozen`.
|
|
///
|
|
/// Weight: `O(1)`
|
|
#[pallet::weight(T::WeightInfo::freeze_class())]
|
|
pub fn freeze_class(
|
|
origin: OriginFor<T>,
|
|
#[pallet::compact] class: T::ClassId,
|
|
) -> DispatchResult {
|
|
let origin = ensure_signed(origin)?;
|
|
|
|
Class::<T, I>::try_mutate(class, |maybe_details| {
|
|
let details = maybe_details.as_mut().ok_or(Error::<T, I>::Unknown)?;
|
|
ensure!(&origin == &details.freezer, Error::<T, I>::NoPermission);
|
|
|
|
details.is_frozen = true;
|
|
|
|
Self::deposit_event(Event::<T, I>::ClassFrozen(class));
|
|
Ok(())
|
|
})
|
|
}
|
|
|
|
/// Re-allow unprivileged transfers for a whole asset class.
|
|
///
|
|
/// Origin must be Signed and the sender should be the Admin of the asset `class`.
|
|
///
|
|
/// - `class`: The class to be thawed.
|
|
///
|
|
/// Emits `ClassThawed`.
|
|
///
|
|
/// Weight: `O(1)`
|
|
#[pallet::weight(T::WeightInfo::thaw_class())]
|
|
pub fn thaw_class(
|
|
origin: OriginFor<T>,
|
|
#[pallet::compact] class: T::ClassId,
|
|
) -> DispatchResult {
|
|
let origin = ensure_signed(origin)?;
|
|
|
|
Class::<T, I>::try_mutate(class, |maybe_details| {
|
|
let details = maybe_details.as_mut().ok_or(Error::<T, I>::Unknown)?;
|
|
ensure!(&origin == &details.admin, Error::<T, I>::NoPermission);
|
|
|
|
details.is_frozen = false;
|
|
|
|
Self::deposit_event(Event::<T, I>::ClassThawed(class));
|
|
Ok(())
|
|
})
|
|
}
|
|
|
|
/// Change the Owner of an asset class.
|
|
///
|
|
/// Origin must be Signed and the sender should be the Owner of the asset `class`.
|
|
///
|
|
/// - `class`: The asset class whose owner should be changed.
|
|
/// - `owner`: The new Owner of this asset class.
|
|
///
|
|
/// Emits `OwnerChanged`.
|
|
///
|
|
/// Weight: `O(1)`
|
|
#[pallet::weight(T::WeightInfo::transfer_ownership())]
|
|
pub fn transfer_ownership(
|
|
origin: OriginFor<T>,
|
|
#[pallet::compact] class: T::ClassId,
|
|
owner: <T::Lookup as StaticLookup>::Source,
|
|
) -> DispatchResult {
|
|
let origin = ensure_signed(origin)?;
|
|
let owner = T::Lookup::lookup(owner)?;
|
|
|
|
Class::<T, I>::try_mutate(class, |maybe_details| {
|
|
let details = maybe_details.as_mut().ok_or(Error::<T, I>::Unknown)?;
|
|
ensure!(&origin == &details.owner, Error::<T, I>::NoPermission);
|
|
if details.owner == owner {
|
|
return Ok(())
|
|
}
|
|
|
|
// Move the deposit to the new owner.
|
|
T::Currency::repatriate_reserved(
|
|
&details.owner,
|
|
&owner,
|
|
details.total_deposit,
|
|
Reserved,
|
|
)?;
|
|
details.owner = owner.clone();
|
|
|
|
Self::deposit_event(Event::OwnerChanged(class, owner));
|
|
Ok(())
|
|
})
|
|
}
|
|
|
|
/// Change the Issuer, Admin and Freezer of an asset class.
|
|
///
|
|
/// Origin must be Signed and the sender should be the Owner of the asset `class`.
|
|
///
|
|
/// - `class`: The asset class whose team should be changed.
|
|
/// - `issuer`: The new Issuer of this asset class.
|
|
/// - `admin`: The new Admin of this asset class.
|
|
/// - `freezer`: The new Freezer of this asset class.
|
|
///
|
|
/// Emits `TeamChanged`.
|
|
///
|
|
/// Weight: `O(1)`
|
|
#[pallet::weight(T::WeightInfo::set_team())]
|
|
pub fn set_team(
|
|
origin: OriginFor<T>,
|
|
#[pallet::compact] class: T::ClassId,
|
|
issuer: <T::Lookup as StaticLookup>::Source,
|
|
admin: <T::Lookup as StaticLookup>::Source,
|
|
freezer: <T::Lookup as StaticLookup>::Source,
|
|
) -> DispatchResult {
|
|
let origin = ensure_signed(origin)?;
|
|
let issuer = T::Lookup::lookup(issuer)?;
|
|
let admin = T::Lookup::lookup(admin)?;
|
|
let freezer = T::Lookup::lookup(freezer)?;
|
|
|
|
Class::<T, I>::try_mutate(class, |maybe_details| {
|
|
let details = maybe_details.as_mut().ok_or(Error::<T, I>::Unknown)?;
|
|
ensure!(&origin == &details.owner, Error::<T, I>::NoPermission);
|
|
|
|
details.issuer = issuer.clone();
|
|
details.admin = admin.clone();
|
|
details.freezer = freezer.clone();
|
|
|
|
Self::deposit_event(Event::TeamChanged(class, issuer, admin, freezer));
|
|
Ok(())
|
|
})
|
|
}
|
|
|
|
/// Approve an instance to be transferred by a delegated third-party account.
|
|
///
|
|
/// Origin must be Signed and must be the owner of the asset `instance`.
|
|
///
|
|
/// - `class`: The class of the asset to be approved for delegated transfer.
|
|
/// - `instance`: The instance of the asset to be approved for delegated transfer.
|
|
/// - `delegate`: The account to delegate permission to transfer the asset.
|
|
///
|
|
/// Emits `ApprovedTransfer` on success.
|
|
///
|
|
/// Weight: `O(1)`
|
|
#[pallet::weight(T::WeightInfo::approve_transfer())]
|
|
pub fn approve_transfer(
|
|
origin: OriginFor<T>,
|
|
#[pallet::compact] class: T::ClassId,
|
|
#[pallet::compact] instance: T::InstanceId,
|
|
delegate: <T::Lookup as StaticLookup>::Source,
|
|
) -> DispatchResult {
|
|
let maybe_check: Option<T::AccountId> = T::ForceOrigin::try_origin(origin)
|
|
.map(|_| None)
|
|
.or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?;
|
|
|
|
let delegate = T::Lookup::lookup(delegate)?;
|
|
|
|
let class_details = Class::<T, I>::get(&class).ok_or(Error::<T, I>::Unknown)?;
|
|
let mut details =
|
|
Asset::<T, I>::get(&class, &instance).ok_or(Error::<T, I>::Unknown)?;
|
|
|
|
if let Some(check) = maybe_check {
|
|
let permitted = &check == &class_details.admin || &check == &details.owner;
|
|
ensure!(permitted, Error::<T, I>::NoPermission);
|
|
}
|
|
|
|
details.approved = Some(delegate);
|
|
Asset::<T, I>::insert(&class, &instance, &details);
|
|
|
|
let delegate = details.approved.expect("set as Some above; qed");
|
|
Self::deposit_event(Event::ApprovedTransfer(class, instance, details.owner, delegate));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Cancel the prior approval for the transfer of an asset by a delegate.
|
|
///
|
|
/// Origin must be either:
|
|
/// - the `Force` origin;
|
|
/// - `Signed` with the signer being the Admin of the asset `class`;
|
|
/// - `Signed` with the signer being the Owner of the asset `instance`;
|
|
///
|
|
/// Arguments:
|
|
/// - `class`: The class of the asset of whose approval will be cancelled.
|
|
/// - `instance`: The instance of the asset of whose approval will be cancelled.
|
|
/// - `maybe_check_delegate`: If `Some` will ensure that the given account is the one to
|
|
/// which permission of transfer is delegated.
|
|
///
|
|
/// Emits `ApprovalCancelled` on success.
|
|
///
|
|
/// Weight: `O(1)`
|
|
#[pallet::weight(T::WeightInfo::cancel_approval())]
|
|
pub fn cancel_approval(
|
|
origin: OriginFor<T>,
|
|
#[pallet::compact] class: T::ClassId,
|
|
#[pallet::compact] instance: T::InstanceId,
|
|
maybe_check_delegate: Option<<T::Lookup as StaticLookup>::Source>,
|
|
) -> DispatchResult {
|
|
let maybe_check: Option<T::AccountId> = T::ForceOrigin::try_origin(origin)
|
|
.map(|_| None)
|
|
.or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?;
|
|
|
|
let class_details = Class::<T, I>::get(&class).ok_or(Error::<T, I>::Unknown)?;
|
|
let mut details =
|
|
Asset::<T, I>::get(&class, &instance).ok_or(Error::<T, I>::Unknown)?;
|
|
if let Some(check) = maybe_check {
|
|
let permitted = &check == &class_details.admin || &check == &details.owner;
|
|
ensure!(permitted, Error::<T, I>::NoPermission);
|
|
}
|
|
let maybe_check_delegate = maybe_check_delegate.map(T::Lookup::lookup).transpose()?;
|
|
let old = details.approved.take().ok_or(Error::<T, I>::NoDelegate)?;
|
|
if let Some(check_delegate) = maybe_check_delegate {
|
|
ensure!(check_delegate == old, Error::<T, I>::WrongDelegate);
|
|
}
|
|
|
|
Asset::<T, I>::insert(&class, &instance, &details);
|
|
Self::deposit_event(Event::ApprovalCancelled(class, instance, details.owner, old));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Alter the attributes of a given asset.
|
|
///
|
|
/// Origin must be `ForceOrigin`.
|
|
///
|
|
/// - `class`: The identifier of the asset.
|
|
/// - `owner`: The new Owner of this asset.
|
|
/// - `issuer`: The new Issuer of this asset.
|
|
/// - `admin`: The new Admin of this asset.
|
|
/// - `freezer`: The new Freezer of this asset.
|
|
/// - `free_holding`: Whether a deposit is taken for holding an instance of this asset
|
|
/// class.
|
|
/// - `is_frozen`: Whether this asset class is frozen except for permissioned/admin
|
|
/// instructions.
|
|
///
|
|
/// Emits `AssetStatusChanged` with the identity of the asset.
|
|
///
|
|
/// Weight: `O(1)`
|
|
#[pallet::weight(T::WeightInfo::force_asset_status())]
|
|
pub fn force_asset_status(
|
|
origin: OriginFor<T>,
|
|
#[pallet::compact] class: T::ClassId,
|
|
owner: <T::Lookup as StaticLookup>::Source,
|
|
issuer: <T::Lookup as StaticLookup>::Source,
|
|
admin: <T::Lookup as StaticLookup>::Source,
|
|
freezer: <T::Lookup as StaticLookup>::Source,
|
|
free_holding: bool,
|
|
is_frozen: bool,
|
|
) -> DispatchResult {
|
|
T::ForceOrigin::ensure_origin(origin)?;
|
|
|
|
Class::<T, I>::try_mutate(class, |maybe_asset| {
|
|
let mut asset = maybe_asset.take().ok_or(Error::<T, I>::Unknown)?;
|
|
asset.owner = T::Lookup::lookup(owner)?;
|
|
asset.issuer = T::Lookup::lookup(issuer)?;
|
|
asset.admin = T::Lookup::lookup(admin)?;
|
|
asset.freezer = T::Lookup::lookup(freezer)?;
|
|
asset.free_holding = free_holding;
|
|
asset.is_frozen = is_frozen;
|
|
*maybe_asset = Some(asset);
|
|
|
|
Self::deposit_event(Event::AssetStatusChanged(class));
|
|
Ok(())
|
|
})
|
|
}
|
|
|
|
/// Set an attribute for an asset class or instance.
|
|
///
|
|
/// Origin must be either `ForceOrigin` or Signed and the sender should be the Owner of the
|
|
/// asset `class`.
|
|
///
|
|
/// If the origin is Signed, then funds of signer are reserved according to the formula:
|
|
/// `MetadataDepositBase + DepositPerByte * (key.len + value.len)` taking into
|
|
/// account any already reserved funds.
|
|
///
|
|
/// - `class`: The identifier of the asset class whose instance's metadata to set.
|
|
/// - `maybe_instance`: The identifier of the asset instance whose metadata to set.
|
|
/// - `key`: The key of the attribute.
|
|
/// - `value`: The value to which to set the attribute.
|
|
///
|
|
/// Emits `AttributeSet`.
|
|
///
|
|
/// Weight: `O(1)`
|
|
#[pallet::weight(T::WeightInfo::set_attribute())]
|
|
pub fn set_attribute(
|
|
origin: OriginFor<T>,
|
|
#[pallet::compact] class: T::ClassId,
|
|
maybe_instance: Option<T::InstanceId>,
|
|
key: BoundedVec<u8, T::KeyLimit>,
|
|
value: BoundedVec<u8, T::ValueLimit>,
|
|
) -> DispatchResult {
|
|
let maybe_check_owner = T::ForceOrigin::try_origin(origin)
|
|
.map(|_| None)
|
|
.or_else(|origin| ensure_signed(origin).map(Some))?;
|
|
|
|
let mut class_details = Class::<T, I>::get(&class).ok_or(Error::<T, I>::Unknown)?;
|
|
if let Some(check_owner) = &maybe_check_owner {
|
|
ensure!(check_owner == &class_details.owner, Error::<T, I>::NoPermission);
|
|
}
|
|
let maybe_is_frozen = match maybe_instance {
|
|
None => ClassMetadataOf::<T, I>::get(class).map(|v| v.is_frozen),
|
|
Some(instance) =>
|
|
InstanceMetadataOf::<T, I>::get(class, instance).map(|v| v.is_frozen),
|
|
};
|
|
ensure!(!maybe_is_frozen.unwrap_or(false), Error::<T, I>::Frozen);
|
|
|
|
let attribute = Attribute::<T, I>::get((class, maybe_instance, &key));
|
|
if attribute.is_none() {
|
|
class_details.attributes.saturating_inc();
|
|
}
|
|
let old_deposit = attribute.map_or(Zero::zero(), |m| m.1);
|
|
class_details.total_deposit.saturating_reduce(old_deposit);
|
|
let mut deposit = Zero::zero();
|
|
if !class_details.free_holding && maybe_check_owner.is_some() {
|
|
deposit = T::DepositPerByte::get()
|
|
.saturating_mul(((key.len() + value.len()) as u32).into())
|
|
.saturating_add(T::AttributeDepositBase::get());
|
|
}
|
|
class_details.total_deposit.saturating_accrue(deposit);
|
|
if deposit > old_deposit {
|
|
T::Currency::reserve(&class_details.owner, deposit - old_deposit)?;
|
|
} else if deposit < old_deposit {
|
|
T::Currency::unreserve(&class_details.owner, old_deposit - deposit);
|
|
}
|
|
|
|
Attribute::<T, I>::insert((&class, maybe_instance, &key), (&value, deposit));
|
|
Class::<T, I>::insert(class, &class_details);
|
|
Self::deposit_event(Event::AttributeSet(class, maybe_instance, key, value));
|
|
Ok(())
|
|
}
|
|
|
|
/// Set an attribute for an asset class or instance.
|
|
///
|
|
/// Origin must be either `ForceOrigin` or Signed and the sender should be the Owner of the
|
|
/// asset `class`.
|
|
///
|
|
/// If the origin is Signed, then funds of signer are reserved according to the formula:
|
|
/// `MetadataDepositBase + DepositPerByte * (key.len + value.len)` taking into
|
|
/// account any already reserved funds.
|
|
///
|
|
/// - `class`: The identifier of the asset class whose instance's metadata to set.
|
|
/// - `instance`: The identifier of the asset instance whose metadata to set.
|
|
/// - `key`: The key of the attribute.
|
|
/// - `value`: The value to which to set the attribute.
|
|
///
|
|
/// Emits `AttributeSet`.
|
|
///
|
|
/// Weight: `O(1)`
|
|
#[pallet::weight(T::WeightInfo::clear_attribute())]
|
|
pub fn clear_attribute(
|
|
origin: OriginFor<T>,
|
|
#[pallet::compact] class: T::ClassId,
|
|
maybe_instance: Option<T::InstanceId>,
|
|
key: BoundedVec<u8, T::KeyLimit>,
|
|
) -> DispatchResult {
|
|
let maybe_check_owner = T::ForceOrigin::try_origin(origin)
|
|
.map(|_| None)
|
|
.or_else(|origin| ensure_signed(origin).map(Some))?;
|
|
|
|
let mut class_details = Class::<T, I>::get(&class).ok_or(Error::<T, I>::Unknown)?;
|
|
if let Some(check_owner) = &maybe_check_owner {
|
|
ensure!(check_owner == &class_details.owner, Error::<T, I>::NoPermission);
|
|
}
|
|
let maybe_is_frozen = match maybe_instance {
|
|
None => ClassMetadataOf::<T, I>::get(class).map(|v| v.is_frozen),
|
|
Some(instance) =>
|
|
InstanceMetadataOf::<T, I>::get(class, instance).map(|v| v.is_frozen),
|
|
};
|
|
ensure!(!maybe_is_frozen.unwrap_or(false), Error::<T, I>::Frozen);
|
|
|
|
if let Some((_, deposit)) = Attribute::<T, I>::take((class, maybe_instance, &key)) {
|
|
class_details.attributes.saturating_dec();
|
|
class_details.total_deposit.saturating_reduce(deposit);
|
|
T::Currency::unreserve(&class_details.owner, deposit);
|
|
Class::<T, I>::insert(class, &class_details);
|
|
Self::deposit_event(Event::AttributeCleared(class, maybe_instance, key));
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Set the metadata for an asset instance.
|
|
///
|
|
/// Origin must be either `ForceOrigin` or Signed and the sender should be the Owner of the
|
|
/// asset `class`.
|
|
///
|
|
/// If the origin is Signed, then funds of signer are reserved according to the formula:
|
|
/// `MetadataDepositBase + DepositPerByte * data.len` taking into
|
|
/// account any already reserved funds.
|
|
///
|
|
/// - `class`: The identifier of the asset class whose instance's metadata to set.
|
|
/// - `instance`: The identifier of the asset instance whose metadata to set.
|
|
/// - `data`: The general information of this asset. Limited in length by `StringLimit`.
|
|
/// - `is_frozen`: Whether the metadata should be frozen against further changes.
|
|
///
|
|
/// Emits `MetadataSet`.
|
|
///
|
|
/// Weight: `O(1)`
|
|
#[pallet::weight(T::WeightInfo::set_metadata())]
|
|
pub fn set_metadata(
|
|
origin: OriginFor<T>,
|
|
#[pallet::compact] class: T::ClassId,
|
|
#[pallet::compact] instance: T::InstanceId,
|
|
data: BoundedVec<u8, T::StringLimit>,
|
|
is_frozen: bool,
|
|
) -> DispatchResult {
|
|
let maybe_check_owner = T::ForceOrigin::try_origin(origin)
|
|
.map(|_| None)
|
|
.or_else(|origin| ensure_signed(origin).map(Some))?;
|
|
|
|
let mut class_details = Class::<T, I>::get(&class).ok_or(Error::<T, I>::Unknown)?;
|
|
|
|
if let Some(check_owner) = &maybe_check_owner {
|
|
ensure!(check_owner == &class_details.owner, Error::<T, I>::NoPermission);
|
|
}
|
|
|
|
InstanceMetadataOf::<T, I>::try_mutate_exists(class, instance, |metadata| {
|
|
let was_frozen = metadata.as_ref().map_or(false, |m| m.is_frozen);
|
|
ensure!(maybe_check_owner.is_none() || !was_frozen, Error::<T, I>::Frozen);
|
|
|
|
if metadata.is_none() {
|
|
class_details.instance_metadatas.saturating_inc();
|
|
}
|
|
let old_deposit = metadata.take().map_or(Zero::zero(), |m| m.deposit);
|
|
class_details.total_deposit.saturating_reduce(old_deposit);
|
|
let mut deposit = Zero::zero();
|
|
if !class_details.free_holding && maybe_check_owner.is_some() {
|
|
deposit = T::DepositPerByte::get()
|
|
.saturating_mul(((data.len()) as u32).into())
|
|
.saturating_add(T::MetadataDepositBase::get());
|
|
}
|
|
if deposit > old_deposit {
|
|
T::Currency::reserve(&class_details.owner, deposit - old_deposit)?;
|
|
} else if deposit < old_deposit {
|
|
T::Currency::unreserve(&class_details.owner, old_deposit - deposit);
|
|
}
|
|
class_details.total_deposit.saturating_accrue(deposit);
|
|
|
|
*metadata = Some(InstanceMetadata { deposit, data: data.clone(), is_frozen });
|
|
|
|
Class::<T, I>::insert(&class, &class_details);
|
|
Self::deposit_event(Event::MetadataSet(class, instance, data, is_frozen));
|
|
Ok(())
|
|
})
|
|
}
|
|
|
|
/// Clear the metadata for an asset instance.
|
|
///
|
|
/// Origin must be either `ForceOrigin` or Signed and the sender should be the Owner of the
|
|
/// asset `instance`.
|
|
///
|
|
/// Any deposit is freed for the asset class owner.
|
|
///
|
|
/// - `class`: The identifier of the asset class whose instance's metadata to clear.
|
|
/// - `instance`: The identifier of the asset instance whose metadata to clear.
|
|
///
|
|
/// Emits `MetadataCleared`.
|
|
///
|
|
/// Weight: `O(1)`
|
|
#[pallet::weight(T::WeightInfo::clear_metadata())]
|
|
pub fn clear_metadata(
|
|
origin: OriginFor<T>,
|
|
#[pallet::compact] class: T::ClassId,
|
|
#[pallet::compact] instance: T::InstanceId,
|
|
) -> DispatchResult {
|
|
let maybe_check_owner = T::ForceOrigin::try_origin(origin)
|
|
.map(|_| None)
|
|
.or_else(|origin| ensure_signed(origin).map(Some))?;
|
|
|
|
let mut class_details = Class::<T, I>::get(&class).ok_or(Error::<T, I>::Unknown)?;
|
|
if let Some(check_owner) = &maybe_check_owner {
|
|
ensure!(check_owner == &class_details.owner, Error::<T, I>::NoPermission);
|
|
}
|
|
|
|
InstanceMetadataOf::<T, I>::try_mutate_exists(class, instance, |metadata| {
|
|
let was_frozen = metadata.as_ref().map_or(false, |m| m.is_frozen);
|
|
ensure!(maybe_check_owner.is_none() || !was_frozen, Error::<T, I>::Frozen);
|
|
|
|
if metadata.is_some() {
|
|
class_details.instance_metadatas.saturating_dec();
|
|
}
|
|
let deposit = metadata.take().ok_or(Error::<T, I>::Unknown)?.deposit;
|
|
T::Currency::unreserve(&class_details.owner, deposit);
|
|
class_details.total_deposit.saturating_reduce(deposit);
|
|
|
|
Class::<T, I>::insert(&class, &class_details);
|
|
Self::deposit_event(Event::MetadataCleared(class, instance));
|
|
Ok(())
|
|
})
|
|
}
|
|
|
|
/// Set the metadata for an asset class.
|
|
///
|
|
/// Origin must be either `ForceOrigin` or `Signed` and the sender should be the Owner of
|
|
/// the asset `class`.
|
|
///
|
|
/// If the origin is `Signed`, then funds of signer are reserved according to the formula:
|
|
/// `MetadataDepositBase + DepositPerByte * data.len` taking into
|
|
/// account any already reserved funds.
|
|
///
|
|
/// - `class`: The identifier of the asset whose metadata to update.
|
|
/// - `data`: The general information of this asset. Limited in length by `StringLimit`.
|
|
/// - `is_frozen`: Whether the metadata should be frozen against further changes.
|
|
///
|
|
/// Emits `ClassMetadataSet`.
|
|
///
|
|
/// Weight: `O(1)`
|
|
#[pallet::weight(T::WeightInfo::set_class_metadata())]
|
|
pub fn set_class_metadata(
|
|
origin: OriginFor<T>,
|
|
#[pallet::compact] class: T::ClassId,
|
|
data: BoundedVec<u8, T::StringLimit>,
|
|
is_frozen: bool,
|
|
) -> DispatchResult {
|
|
let maybe_check_owner = T::ForceOrigin::try_origin(origin)
|
|
.map(|_| None)
|
|
.or_else(|origin| ensure_signed(origin).map(Some))?;
|
|
|
|
let mut details = Class::<T, I>::get(&class).ok_or(Error::<T, I>::Unknown)?;
|
|
if let Some(check_owner) = &maybe_check_owner {
|
|
ensure!(check_owner == &details.owner, Error::<T, I>::NoPermission);
|
|
}
|
|
|
|
ClassMetadataOf::<T, I>::try_mutate_exists(class, |metadata| {
|
|
let was_frozen = metadata.as_ref().map_or(false, |m| m.is_frozen);
|
|
ensure!(maybe_check_owner.is_none() || !was_frozen, Error::<T, I>::Frozen);
|
|
|
|
let old_deposit = metadata.take().map_or(Zero::zero(), |m| m.deposit);
|
|
details.total_deposit.saturating_reduce(old_deposit);
|
|
let mut deposit = Zero::zero();
|
|
if maybe_check_owner.is_some() && !details.free_holding {
|
|
deposit = T::DepositPerByte::get()
|
|
.saturating_mul(((data.len()) as u32).into())
|
|
.saturating_add(T::MetadataDepositBase::get());
|
|
}
|
|
if deposit > old_deposit {
|
|
T::Currency::reserve(&details.owner, deposit - old_deposit)?;
|
|
} else if deposit < old_deposit {
|
|
T::Currency::unreserve(&details.owner, old_deposit - deposit);
|
|
}
|
|
details.total_deposit.saturating_accrue(deposit);
|
|
|
|
Class::<T, I>::insert(&class, details);
|
|
|
|
*metadata = Some(ClassMetadata { deposit, data: data.clone(), is_frozen });
|
|
|
|
Self::deposit_event(Event::ClassMetadataSet(class, data, is_frozen));
|
|
Ok(())
|
|
})
|
|
}
|
|
|
|
/// Clear the metadata for an asset class.
|
|
///
|
|
/// Origin must be either `ForceOrigin` or `Signed` and the sender should be the Owner of
|
|
/// the asset `class`.
|
|
///
|
|
/// Any deposit is freed for the asset class owner.
|
|
///
|
|
/// - `class`: The identifier of the asset class whose metadata to clear.
|
|
///
|
|
/// Emits `ClassMetadataCleared`.
|
|
///
|
|
/// Weight: `O(1)`
|
|
#[pallet::weight(T::WeightInfo::clear_class_metadata())]
|
|
pub fn clear_class_metadata(
|
|
origin: OriginFor<T>,
|
|
#[pallet::compact] class: T::ClassId,
|
|
) -> DispatchResult {
|
|
let maybe_check_owner = T::ForceOrigin::try_origin(origin)
|
|
.map(|_| None)
|
|
.or_else(|origin| ensure_signed(origin).map(Some))?;
|
|
|
|
let details = Class::<T, I>::get(&class).ok_or(Error::<T, I>::Unknown)?;
|
|
if let Some(check_owner) = &maybe_check_owner {
|
|
ensure!(check_owner == &details.owner, Error::<T, I>::NoPermission);
|
|
}
|
|
|
|
ClassMetadataOf::<T, I>::try_mutate_exists(class, |metadata| {
|
|
let was_frozen = metadata.as_ref().map_or(false, |m| m.is_frozen);
|
|
ensure!(maybe_check_owner.is_none() || !was_frozen, Error::<T, I>::Frozen);
|
|
|
|
let deposit = metadata.take().ok_or(Error::<T, I>::Unknown)?.deposit;
|
|
T::Currency::unreserve(&details.owner, deposit);
|
|
Self::deposit_event(Event::ClassMetadataCleared(class));
|
|
Ok(())
|
|
})
|
|
}
|
|
}
|
|
}
|