mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-26 14:37:57 +00:00
Asset Pallet: Support repeated destroys to safely destroy large assets (#12310)
* Support repeated destroys to safely destroy large assets * require freezing accounts before destroying * support only deleting asset as final stage when there's no assets left * pre: introduce the RemoveKeyLimit config parameter * debug_ensure empty account in the right if block * update to having separate max values for accounts and approvals * add tests and use RemoveKeyLimit constant * add useful comments to the extrinsics, and calculate returned weight * add benchmarking for start_destroy and finish destroy * push failing benchmark logic * add benchmark tests for new functions * update weights via local benchmarks * remove extra weight file * Update frame/assets/src/lib.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> * Update frame/assets/src/types.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> * Update frame/assets/src/lib.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> * effect some changes from codereview * use NotFrozen error * remove origin checks, as anyone can complete destruction after owner has begun the process; Add live check for other extrinsics * fix comments about Origin behaviour * add AssetStatus docs * modularize logic to allow calling logic in on_idle and on_initialize hooks * introduce simple migration for assets details * reintroduce logging in the migrations * move deposit_Event out of the mutate block * Update frame/assets/src/functions.rs Co-authored-by: Muharem Ismailov <ismailov.m.h@gmail.com> * Update frame/assets/src/migration.rs Co-authored-by: Muharem Ismailov <ismailov.m.h@gmail.com> * move AssetNotLive checkout out of the mutate blocks * rename RemoveKeysLimit to RemoveItemsLimit * update docs * fix event name in benchmark * fix cargo fmt. * fix lint in benchmarking * Empty commit to trigger CI * Update frame/assets/src/lib.rs Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Update frame/assets/src/lib.rs Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Update frame/assets/src/functions.rs Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Update frame/assets/src/functions.rs Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Update frame/assets/src/functions.rs Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Update frame/assets/src/lib.rs Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Update frame/assets/src/functions.rs Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * effect change suggested during code review * move limit to a single location * Update frame/assets/src/functions.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> * rename events * fix weight typo, using rocksdb instead of T::DbWeight. Pending generating weights * switch to using dead_account.len() * rename event in the benchmarks * empty to retrigger CI * trigger CI to check cumulus dependency * trigger CI for dependent cumulus * Update frame/assets/src/migration.rs Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * move is-frozen to the assetStatus enum (#12547) * add pre and post migration hooks * update do_transfer logic to add new assert for more correct error messages * trigger CI * switch checking AssetStatus from checking Destroying state to checking live state * fix error type in tests from Frozen to AssetNotLive * trigger CI * change ensure check for fn reducible_balance() * change the error type to Error:<T,I>::IncorrectStatus to be clearer * Trigger CI Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> Co-authored-by: parity-processbot <> Co-authored-by: Muharem Ismailov <ismailov.m.h@gmail.com> Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>
This commit is contained in:
@@ -78,25 +78,6 @@ fn swap_is_sufficient<T: Config<I>, I: 'static>(s: &mut bool) {
|
||||
});
|
||||
}
|
||||
|
||||
fn add_consumers<T: Config<I>, I: 'static>(minter: T::AccountId, n: u32) {
|
||||
let origin = SystemOrigin::Signed(minter);
|
||||
let mut s = false;
|
||||
swap_is_sufficient::<T, I>(&mut s);
|
||||
for i in 0..n {
|
||||
let target = account("consumer", i, SEED);
|
||||
T::Currency::make_free_balance_be(&target, T::Currency::minimum_balance());
|
||||
let target_lookup = T::Lookup::unlookup(target);
|
||||
assert!(Assets::<T, I>::mint(
|
||||
origin.clone().into(),
|
||||
Default::default(),
|
||||
target_lookup,
|
||||
100u32.into()
|
||||
)
|
||||
.is_ok());
|
||||
}
|
||||
swap_is_sufficient::<T, I>(&mut s);
|
||||
}
|
||||
|
||||
fn add_sufficients<T: Config<I>, I: 'static>(minter: T::AccountId, n: u32) {
|
||||
let origin = SystemOrigin::Signed(minter);
|
||||
let mut s = true;
|
||||
@@ -168,18 +149,66 @@ benchmarks_instance_pallet! {
|
||||
assert_last_event::<T, I>(Event::ForceCreated { asset_id: Default::default(), owner: caller }.into());
|
||||
}
|
||||
|
||||
destroy {
|
||||
let c in 0 .. 5_000;
|
||||
let s in 0 .. 5_000;
|
||||
let a in 0 .. 5_00;
|
||||
let (caller, _) = create_default_asset::<T, I>(true);
|
||||
add_consumers::<T, I>(caller.clone(), c);
|
||||
add_sufficients::<T, I>(caller.clone(), s);
|
||||
add_approvals::<T, I>(caller.clone(), a);
|
||||
let witness = Asset::<T, I>::get(T::AssetId::default()).unwrap().destroy_witness();
|
||||
}: _(SystemOrigin::Signed(caller), Default::default(), witness)
|
||||
start_destroy {
|
||||
let (caller, caller_lookup) = create_default_minted_asset::<T, I>(true, 100u32.into());
|
||||
Assets::<T, I>::freeze_asset(
|
||||
SystemOrigin::Signed(caller.clone()).into(),
|
||||
Default::default(),
|
||||
)?;
|
||||
}:_(SystemOrigin::Signed(caller), Default::default())
|
||||
verify {
|
||||
assert_last_event::<T, I>(Event::Destroyed { asset_id: Default::default() }.into());
|
||||
assert_last_event::<T, I>(Event::DestructionStarted { asset_id: Default::default() }.into());
|
||||
}
|
||||
|
||||
destroy_accounts {
|
||||
let c in 0 .. T::RemoveItemsLimit::get();
|
||||
let (caller, _) = create_default_asset::<T, I>(true);
|
||||
add_sufficients::<T, I>(caller.clone(), c);
|
||||
Assets::<T, I>::freeze_asset(
|
||||
SystemOrigin::Signed(caller.clone()).into(),
|
||||
Default::default(),
|
||||
)?;
|
||||
Assets::<T,I>::start_destroy(SystemOrigin::Signed(caller.clone()).into(), Default::default())?;
|
||||
}:_(SystemOrigin::Signed(caller), Default::default())
|
||||
verify {
|
||||
assert_last_event::<T, I>(Event::AccountsDestroyed {
|
||||
asset_id: Default::default() ,
|
||||
accounts_destroyed: c,
|
||||
accounts_remaining: 0,
|
||||
}.into());
|
||||
}
|
||||
|
||||
destroy_approvals {
|
||||
let a in 0 .. T::RemoveItemsLimit::get();
|
||||
let (caller, _) = create_default_minted_asset::<T, I>(true, 100u32.into());
|
||||
add_approvals::<T, I>(caller.clone(), a);
|
||||
Assets::<T, I>::freeze_asset(
|
||||
SystemOrigin::Signed(caller.clone()).into(),
|
||||
Default::default(),
|
||||
)?;
|
||||
Assets::<T,I>::start_destroy(SystemOrigin::Signed(caller.clone()).into(), Default::default())?;
|
||||
}:_(SystemOrigin::Signed(caller), Default::default())
|
||||
verify {
|
||||
assert_last_event::<T, I>(Event::ApprovalsDestroyed {
|
||||
asset_id: Default::default() ,
|
||||
approvals_destroyed: a,
|
||||
approvals_remaining: 0,
|
||||
}.into());
|
||||
}
|
||||
|
||||
finish_destroy {
|
||||
let (caller, caller_lookup) = create_default_asset::<T, I>(true);
|
||||
Assets::<T, I>::freeze_asset(
|
||||
SystemOrigin::Signed(caller.clone()).into(),
|
||||
Default::default(),
|
||||
)?;
|
||||
Assets::<T,I>::start_destroy(SystemOrigin::Signed(caller.clone()).into(), Default::default())?;
|
||||
}:_(SystemOrigin::Signed(caller), Default::default())
|
||||
verify {
|
||||
assert_last_event::<T, I>(Event::Destroyed {
|
||||
asset_id: Default::default() ,
|
||||
}.into()
|
||||
);
|
||||
}
|
||||
|
||||
mint {
|
||||
|
||||
@@ -157,7 +157,7 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
if details.supply.checked_sub(&amount).is_none() {
|
||||
return Underflow
|
||||
}
|
||||
if details.is_frozen {
|
||||
if details.status == AssetStatus::Frozen {
|
||||
return Frozen
|
||||
}
|
||||
if amount.is_zero() {
|
||||
@@ -205,7 +205,7 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
keep_alive: bool,
|
||||
) -> Result<T::Balance, DispatchError> {
|
||||
let details = Asset::<T, I>::get(id).ok_or(Error::<T, I>::Unknown)?;
|
||||
ensure!(!details.is_frozen, Error::<T, I>::Frozen);
|
||||
ensure!(details.status == AssetStatus::Live, Error::<T, I>::AssetNotLive);
|
||||
|
||||
let account = Account::<T, I>::get(id, who).ok_or(Error::<T, I>::NoAccount)?;
|
||||
ensure!(!account.is_frozen, Error::<T, I>::Frozen);
|
||||
@@ -300,6 +300,7 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
ensure!(!Account::<T, I>::contains_key(id, &who), Error::<T, I>::AlreadyExists);
|
||||
let deposit = T::AssetAccountDeposit::get();
|
||||
let mut details = Asset::<T, I>::get(&id).ok_or(Error::<T, I>::Unknown)?;
|
||||
ensure!(details.status == AssetStatus::Live, Error::<T, I>::AssetNotLive);
|
||||
let reason = Self::new_account(&who, &mut details, Some(deposit))?;
|
||||
T::Currency::reserve(&who, deposit)?;
|
||||
Asset::<T, I>::insert(&id, details);
|
||||
@@ -321,9 +322,8 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
let mut account = Account::<T, I>::get(id, &who).ok_or(Error::<T, I>::NoDeposit)?;
|
||||
let deposit = account.reason.take_deposit().ok_or(Error::<T, I>::NoDeposit)?;
|
||||
let mut details = Asset::<T, I>::get(&id).ok_or(Error::<T, I>::Unknown)?;
|
||||
|
||||
ensure!(details.status == AssetStatus::Live, Error::<T, I>::AssetNotLive);
|
||||
ensure!(account.balance.is_zero() || allow_burn, Error::<T, I>::WouldBurn);
|
||||
ensure!(!details.is_frozen, Error::<T, I>::Frozen);
|
||||
ensure!(!account.is_frozen, Error::<T, I>::Frozen);
|
||||
|
||||
T::Currency::unreserve(&who, deposit);
|
||||
@@ -390,7 +390,7 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
Self::can_increase(id, beneficiary, amount, true).into_result()?;
|
||||
Asset::<T, I>::try_mutate(id, |maybe_details| -> DispatchResult {
|
||||
let details = maybe_details.as_mut().ok_or(Error::<T, I>::Unknown)?;
|
||||
|
||||
ensure!(details.status == AssetStatus::Live, Error::<T, I>::AssetNotLive);
|
||||
check(details)?;
|
||||
|
||||
Account::<T, I>::try_mutate(id, beneficiary, |maybe_account| -> DispatchResult {
|
||||
@@ -430,6 +430,12 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
maybe_check_admin: Option<T::AccountId>,
|
||||
f: DebitFlags,
|
||||
) -> Result<T::Balance, DispatchError> {
|
||||
let d = Asset::<T, I>::get(id).ok_or(Error::<T, I>::Unknown)?;
|
||||
ensure!(
|
||||
d.status == AssetStatus::Live || d.status == AssetStatus::Frozen,
|
||||
Error::<T, I>::AssetNotLive
|
||||
);
|
||||
|
||||
let actual = Self::decrease_balance(id, target, amount, f, |actual, details| {
|
||||
// Check admin rights.
|
||||
if let Some(check_admin) = maybe_check_admin {
|
||||
@@ -467,12 +473,14 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
return Ok(amount)
|
||||
}
|
||||
|
||||
let details = Asset::<T, I>::get(id).ok_or(Error::<T, I>::Unknown)?;
|
||||
ensure!(details.status == AssetStatus::Live, Error::<T, I>::AssetNotLive);
|
||||
|
||||
let actual = Self::prep_debit(id, target, amount, f)?;
|
||||
let mut target_died: Option<DeadConsequence> = None;
|
||||
|
||||
Asset::<T, I>::try_mutate(id, |maybe_details| -> DispatchResult {
|
||||
let details = maybe_details.as_mut().ok_or(Error::<T, I>::Unknown)?;
|
||||
|
||||
check(actual, details)?;
|
||||
|
||||
Account::<T, I>::try_mutate(id, target, |maybe_account| -> DispatchResult {
|
||||
@@ -540,6 +548,8 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
if amount.is_zero() {
|
||||
return Ok((amount, None))
|
||||
}
|
||||
let details = Asset::<T, I>::get(id).ok_or(Error::<T, I>::Unknown)?;
|
||||
ensure!(details.status == AssetStatus::Live, Error::<T, I>::AssetNotLive);
|
||||
|
||||
// Figure out the debit and credit, together with side-effects.
|
||||
let debit = Self::prep_debit(id, source, amount, f.into())?;
|
||||
@@ -651,72 +661,123 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
accounts: 0,
|
||||
sufficients: 0,
|
||||
approvals: 0,
|
||||
is_frozen: false,
|
||||
status: AssetStatus::Live,
|
||||
},
|
||||
);
|
||||
Self::deposit_event(Event::ForceCreated { asset_id: id, owner });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Destroy an existing asset.
|
||||
///
|
||||
/// * `id`: The asset you want to destroy.
|
||||
/// * `witness`: Witness data needed about the current state of the asset, used to confirm
|
||||
/// complexity of the operation.
|
||||
/// * `maybe_check_owner`: An optional check before destroying the asset, if the provided
|
||||
/// account is the owner of that asset. Can be used for authorization checks.
|
||||
pub(super) fn do_destroy(
|
||||
/// Start the process of destroying an asset, by setting the asset status to `Destroying`, and
|
||||
/// emitting the `DestructionStarted` event.
|
||||
pub(super) fn do_start_destroy(
|
||||
id: T::AssetId,
|
||||
witness: DestroyWitness,
|
||||
maybe_check_owner: Option<T::AccountId>,
|
||||
) -> Result<DestroyWitness, DispatchError> {
|
||||
let mut dead_accounts: Vec<T::AccountId> = vec![];
|
||||
) -> DispatchResult {
|
||||
Asset::<T, I>::try_mutate_exists(id, |maybe_details| -> Result<(), DispatchError> {
|
||||
let mut details = maybe_details.as_mut().ok_or(Error::<T, I>::Unknown)?;
|
||||
if let Some(check_owner) = maybe_check_owner {
|
||||
ensure!(details.owner == check_owner, Error::<T, I>::NoPermission);
|
||||
}
|
||||
details.status = AssetStatus::Destroying;
|
||||
|
||||
let result_witness: DestroyWitness = Asset::<T, I>::try_mutate_exists(
|
||||
id,
|
||||
|maybe_details| -> Result<DestroyWitness, DispatchError> {
|
||||
let mut details = maybe_details.take().ok_or(Error::<T, I>::Unknown)?;
|
||||
if let Some(check_owner) = maybe_check_owner {
|
||||
ensure!(details.owner == check_owner, Error::<T, I>::NoPermission);
|
||||
}
|
||||
ensure!(details.accounts <= witness.accounts, Error::<T, I>::BadWitness);
|
||||
ensure!(details.sufficients <= witness.sufficients, Error::<T, I>::BadWitness);
|
||||
ensure!(details.approvals <= witness.approvals, Error::<T, I>::BadWitness);
|
||||
Self::deposit_event(Event::DestructionStarted { asset_id: id });
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
/// Destroy accounts associated with a given asset up to the max (T::RemoveItemsLimit).
|
||||
///
|
||||
/// Each call emits the `Event::DestroyedAccounts` event.
|
||||
/// Returns the number of destroyed accounts.
|
||||
pub(super) fn do_destroy_accounts(
|
||||
id: T::AssetId,
|
||||
max_items: u32,
|
||||
) -> Result<u32, DispatchError> {
|
||||
let mut dead_accounts: Vec<T::AccountId> = vec![];
|
||||
let mut remaining_accounts = 0;
|
||||
let _ =
|
||||
Asset::<T, I>::try_mutate_exists(id, |maybe_details| -> Result<(), DispatchError> {
|
||||
let mut details = maybe_details.as_mut().ok_or(Error::<T, I>::Unknown)?;
|
||||
// Should only destroy accounts while the asset is in a destroying state
|
||||
ensure!(details.status == AssetStatus::Destroying, Error::<T, I>::IncorrectStatus);
|
||||
|
||||
for (who, v) in Account::<T, I>::drain_prefix(id) {
|
||||
// We have to force this as it's destroying the entire asset class.
|
||||
// This could mean that some accounts now have irreversibly reserved
|
||||
// funds.
|
||||
let _ = Self::dead_account(&who, &mut details, &v.reason, true);
|
||||
dead_accounts.push(who);
|
||||
if dead_accounts.len() >= (max_items as usize) {
|
||||
break
|
||||
}
|
||||
}
|
||||
debug_assert_eq!(details.accounts, 0);
|
||||
debug_assert_eq!(details.sufficients, 0);
|
||||
remaining_accounts = details.accounts;
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
let metadata = Metadata::<T, I>::take(&id);
|
||||
T::Currency::unreserve(
|
||||
&details.owner,
|
||||
details.deposit.saturating_add(metadata.deposit),
|
||||
);
|
||||
|
||||
for ((owner, _), approval) in Approvals::<T, I>::drain_prefix((&id,)) {
|
||||
T::Currency::unreserve(&owner, approval.deposit);
|
||||
}
|
||||
Self::deposit_event(Event::Destroyed { asset_id: id });
|
||||
|
||||
Ok(DestroyWitness {
|
||||
accounts: details.accounts,
|
||||
sufficients: details.sufficients,
|
||||
approvals: details.approvals,
|
||||
})
|
||||
},
|
||||
)?;
|
||||
|
||||
// Execute hooks outside of `mutate`.
|
||||
for who in dead_accounts {
|
||||
for who in &dead_accounts {
|
||||
T::Freezer::died(id, &who);
|
||||
}
|
||||
Ok(result_witness)
|
||||
|
||||
Self::deposit_event(Event::AccountsDestroyed {
|
||||
asset_id: id,
|
||||
accounts_destroyed: dead_accounts.len() as u32,
|
||||
accounts_remaining: remaining_accounts as u32,
|
||||
});
|
||||
Ok(dead_accounts.len() as u32)
|
||||
}
|
||||
|
||||
/// Destroy approvals associated with a given asset up to the max (T::RemoveItemsLimit).
|
||||
///
|
||||
/// Each call emits the `Event::DestroyedApprovals` event
|
||||
/// Returns the number of destroyed approvals.
|
||||
pub(super) fn do_destroy_approvals(
|
||||
id: T::AssetId,
|
||||
max_items: u32,
|
||||
) -> Result<u32, DispatchError> {
|
||||
let mut removed_approvals = 0;
|
||||
let _ =
|
||||
Asset::<T, I>::try_mutate_exists(id, |maybe_details| -> Result<(), DispatchError> {
|
||||
let mut details = maybe_details.as_mut().ok_or(Error::<T, I>::Unknown)?;
|
||||
|
||||
// Should only destroy accounts while the asset is in a destroying state.
|
||||
ensure!(details.status == AssetStatus::Destroying, Error::<T, I>::IncorrectStatus);
|
||||
|
||||
for ((owner, _), approval) in Approvals::<T, I>::drain_prefix((id,)) {
|
||||
T::Currency::unreserve(&owner, approval.deposit);
|
||||
removed_approvals = removed_approvals.saturating_add(1);
|
||||
details.approvals = details.approvals.saturating_sub(1);
|
||||
if removed_approvals >= max_items {
|
||||
break
|
||||
}
|
||||
}
|
||||
Self::deposit_event(Event::ApprovalsDestroyed {
|
||||
asset_id: id,
|
||||
approvals_destroyed: removed_approvals as u32,
|
||||
approvals_remaining: details.approvals as u32,
|
||||
});
|
||||
Ok(())
|
||||
})?;
|
||||
Ok(removed_approvals)
|
||||
}
|
||||
|
||||
/// Complete destroying an asset and unreserve the deposit.
|
||||
///
|
||||
/// On success, the `Event::Destroyed` event is emitted.
|
||||
pub(super) fn do_finish_destroy(id: T::AssetId) -> DispatchResult {
|
||||
Asset::<T, I>::try_mutate_exists(id, |maybe_details| -> Result<(), DispatchError> {
|
||||
let details = maybe_details.take().ok_or(Error::<T, I>::Unknown)?;
|
||||
ensure!(details.status == AssetStatus::Destroying, Error::<T, I>::IncorrectStatus);
|
||||
ensure!(details.accounts == 0, Error::<T, I>::InUse);
|
||||
ensure!(details.approvals == 0, Error::<T, I>::InUse);
|
||||
|
||||
let metadata = Metadata::<T, I>::take(&id);
|
||||
T::Currency::unreserve(
|
||||
&details.owner,
|
||||
details.deposit.saturating_add(metadata.deposit),
|
||||
);
|
||||
Self::deposit_event(Event::Destroyed { asset_id: id });
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates an approval from `owner` to spend `amount` of asset `id` tokens by 'delegate'
|
||||
@@ -730,7 +791,7 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
amount: T::Balance,
|
||||
) -> DispatchResult {
|
||||
let mut d = Asset::<T, I>::get(id).ok_or(Error::<T, I>::Unknown)?;
|
||||
ensure!(!d.is_frozen, Error::<T, I>::Frozen);
|
||||
ensure!(d.status == AssetStatus::Live, Error::<T, I>::AssetNotLive);
|
||||
Approvals::<T, I>::try_mutate(
|
||||
(id, &owner, &delegate),
|
||||
|maybe_approved| -> DispatchResult {
|
||||
@@ -780,6 +841,9 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
) -> DispatchResult {
|
||||
let mut owner_died: Option<DeadConsequence> = None;
|
||||
|
||||
let d = Asset::<T, I>::get(id).ok_or(Error::<T, I>::Unknown)?;
|
||||
ensure!(d.status == AssetStatus::Live, Error::<T, I>::AssetNotLive);
|
||||
|
||||
Approvals::<T, I>::try_mutate_exists(
|
||||
(id, &owner, delegate),
|
||||
|maybe_approved| -> DispatchResult {
|
||||
@@ -826,6 +890,7 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
symbol.clone().try_into().map_err(|_| Error::<T, I>::BadMetadata)?;
|
||||
|
||||
let d = Asset::<T, I>::get(id).ok_or(Error::<T, I>::Unknown)?;
|
||||
ensure!(d.status == AssetStatus::Live, Error::<T, I>::AssetNotLive);
|
||||
ensure!(from == &d.owner, Error::<T, I>::NoPermission);
|
||||
|
||||
Metadata::<T, I>::try_mutate_exists(id, |metadata| {
|
||||
|
||||
@@ -179,22 +179,6 @@ impl<T: Config<I>, I: 'static> fungibles::Create<T::AccountId> for Pallet<T, I>
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> fungibles::Destroy<T::AccountId> for Pallet<T, I> {
|
||||
type DestroyWitness = DestroyWitness;
|
||||
|
||||
fn get_destroy_witness(asset: &T::AssetId) -> Option<Self::DestroyWitness> {
|
||||
Asset::<T, I>::get(asset).map(|asset_details| asset_details.destroy_witness())
|
||||
}
|
||||
|
||||
fn destroy(
|
||||
id: T::AssetId,
|
||||
witness: Self::DestroyWitness,
|
||||
maybe_check_owner: Option<T::AccountId>,
|
||||
) -> Result<Self::DestroyWitness, DispatchError> {
|
||||
Self::do_destroy(id, witness, maybe_check_owner)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> fungibles::metadata::Inspect<<T as SystemConfig>::AccountId>
|
||||
for Pallet<T, I>
|
||||
{
|
||||
|
||||
@@ -121,11 +121,15 @@
|
||||
//! * [`System`](../frame_system/index.html)
|
||||
//! * [`Support`](../frame_support/index.html)
|
||||
|
||||
// This recursion limit is needed because we have too many benchmarks and benchmarking will fail if
|
||||
// we add more without this limit.
|
||||
#![recursion_limit = "1024"]
|
||||
// Ensure we're `no_std` when compiling for Wasm.
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
mod benchmarking;
|
||||
pub mod migration;
|
||||
#[cfg(test)]
|
||||
pub mod mock;
|
||||
#[cfg(test)]
|
||||
@@ -174,8 +178,12 @@ pub mod pallet {
|
||||
use frame_support::pallet_prelude::*;
|
||||
use frame_system::pallet_prelude::*;
|
||||
|
||||
/// The current storage version.
|
||||
const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
|
||||
|
||||
#[pallet::pallet]
|
||||
#[pallet::generate_store(pub(super) trait Store)]
|
||||
#[pallet::storage_version(STORAGE_VERSION)]
|
||||
pub struct Pallet<T, I = ()>(_);
|
||||
|
||||
#[pallet::config]
|
||||
@@ -195,6 +203,12 @@ pub mod pallet {
|
||||
+ MaxEncodedLen
|
||||
+ TypeInfo;
|
||||
|
||||
/// Max number of items to destroy per `destroy_accounts` and `destroy_approvals` call.
|
||||
///
|
||||
/// Must be configured to result in a weight that makes each call fit in a block.
|
||||
#[pallet::constant]
|
||||
type RemoveItemsLimit: Get<u32>;
|
||||
|
||||
/// Identifier for the class of asset.
|
||||
type AssetId: Member
|
||||
+ Parameter
|
||||
@@ -342,7 +356,7 @@ pub mod pallet {
|
||||
accounts: 0,
|
||||
sufficients: 0,
|
||||
approvals: 0,
|
||||
is_frozen: false,
|
||||
status: AssetStatus::Live,
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -417,6 +431,16 @@ pub mod pallet {
|
||||
AssetFrozen { asset_id: T::AssetId },
|
||||
/// Some asset `asset_id` was thawed.
|
||||
AssetThawed { asset_id: T::AssetId },
|
||||
/// Accounts were destroyed for given asset.
|
||||
AccountsDestroyed { asset_id: T::AssetId, accounts_destroyed: u32, accounts_remaining: u32 },
|
||||
/// Approvals were destroyed for given asset.
|
||||
ApprovalsDestroyed {
|
||||
asset_id: T::AssetId,
|
||||
approvals_destroyed: u32,
|
||||
approvals_remaining: u32,
|
||||
},
|
||||
/// An asset class is in the process of being destroyed.
|
||||
DestructionStarted { asset_id: T::AssetId },
|
||||
/// An asset class was destroyed.
|
||||
Destroyed { asset_id: T::AssetId },
|
||||
/// Some asset class was force-created.
|
||||
@@ -487,6 +511,15 @@ pub mod pallet {
|
||||
NoDeposit,
|
||||
/// The operation would result in funds being burned.
|
||||
WouldBurn,
|
||||
/// The asset is a live asset and is actively being used. Usually emit for operations such
|
||||
/// as `start_destroy` which require the asset to be in a destroying state.
|
||||
LiveAsset,
|
||||
/// The asset is not live, and likely being destroyed.
|
||||
AssetNotLive,
|
||||
/// The asset status is not the expected status.
|
||||
IncorrectStatus,
|
||||
/// The asset should be frozen before the given operation.
|
||||
NotFrozen,
|
||||
}
|
||||
|
||||
#[pallet::call]
|
||||
@@ -540,7 +573,7 @@ pub mod pallet {
|
||||
accounts: 0,
|
||||
sufficients: 0,
|
||||
approvals: 0,
|
||||
is_frozen: false,
|
||||
status: AssetStatus::Live,
|
||||
},
|
||||
);
|
||||
Self::deposit_event(Event::Created { asset_id: id, creator: owner, owner: admin });
|
||||
@@ -579,45 +612,89 @@ pub mod pallet {
|
||||
Self::do_force_create(id, owner, is_sufficient, min_balance)
|
||||
}
|
||||
|
||||
/// Destroy a class of fungible assets.
|
||||
/// Start the process of destroying a class of fungible asset
|
||||
/// start_destroy is the first in a series of extrinsics that should be called, to allow
|
||||
/// destroying an asset.
|
||||
///
|
||||
/// The origin must conform to `ForceOrigin` or must be Signed and the sender must be the
|
||||
/// owner of the asset `id`.
|
||||
///
|
||||
/// - `id`: The identifier of the asset to be destroyed. This must identify an existing
|
||||
/// asset.
|
||||
/// asset.
|
||||
///
|
||||
/// Emits `Destroyed` event when successful.
|
||||
///
|
||||
/// NOTE: It can be helpful to first freeze an asset before destroying it so that you
|
||||
/// can provide accurate witness information and prevent users from manipulating state
|
||||
/// in a way that can make it harder to destroy.
|
||||
///
|
||||
/// Weight: `O(c + p + a)` where:
|
||||
/// - `c = (witness.accounts - witness.sufficients)`
|
||||
/// - `s = witness.sufficients`
|
||||
/// - `a = witness.approvals`
|
||||
#[pallet::weight(T::WeightInfo::destroy(
|
||||
witness.accounts.saturating_sub(witness.sufficients),
|
||||
witness.sufficients,
|
||||
witness.approvals,
|
||||
))]
|
||||
pub fn destroy(
|
||||
/// Assets must be freezed before calling start_destroy.
|
||||
#[pallet::weight(T::WeightInfo::start_destroy())]
|
||||
pub fn start_destroy(
|
||||
origin: OriginFor<T>,
|
||||
#[pallet::compact] id: T::AssetId,
|
||||
witness: DestroyWitness,
|
||||
) -> DispatchResultWithPostInfo {
|
||||
) -> DispatchResult {
|
||||
let maybe_check_owner = match T::ForceOrigin::try_origin(origin) {
|
||||
Ok(_) => None,
|
||||
Err(origin) => Some(ensure_signed(origin)?),
|
||||
};
|
||||
let details = Self::do_destroy(id, witness, maybe_check_owner)?;
|
||||
Ok(Some(T::WeightInfo::destroy(
|
||||
details.accounts.saturating_sub(details.sufficients),
|
||||
details.sufficients,
|
||||
details.approvals,
|
||||
))
|
||||
.into())
|
||||
Self::do_start_destroy(id, maybe_check_owner)
|
||||
}
|
||||
|
||||
/// Destroy all accounts associated with a given asset.
|
||||
/// `destroy_accounts` should only be called after `start_destroy` has been called, and the
|
||||
/// asset is in a `Destroying` state
|
||||
///
|
||||
/// Due to weight restrictions, this function may need to be called multiple
|
||||
/// times to fully destroy all accounts. It will destroy `RemoveItemsLimit` accounts at a
|
||||
/// time.
|
||||
///
|
||||
/// - `id`: The identifier of the asset to be destroyed. This must identify an existing
|
||||
/// asset.
|
||||
///
|
||||
/// Each call Emits the `Event::DestroyedAccounts` event.
|
||||
#[pallet::weight(T::WeightInfo::destroy_accounts(T::RemoveItemsLimit::get()))]
|
||||
pub fn destroy_accounts(
|
||||
origin: OriginFor<T>,
|
||||
#[pallet::compact] id: T::AssetId,
|
||||
) -> DispatchResultWithPostInfo {
|
||||
let _ = ensure_signed(origin)?;
|
||||
let removed_accounts = Self::do_destroy_accounts(id, T::RemoveItemsLimit::get())?;
|
||||
Ok(Some(T::WeightInfo::destroy_accounts(removed_accounts)).into())
|
||||
}
|
||||
|
||||
/// Destroy all approvals associated with a given asset up to the max (T::RemoveItemsLimit),
|
||||
/// `destroy_approvals` should only be called after `start_destroy` has been called, and the
|
||||
/// asset is in a `Destroying` state
|
||||
///
|
||||
/// Due to weight restrictions, this function may need to be called multiple
|
||||
/// times to fully destroy all approvals. It will destroy `RemoveItemsLimit` approvals at a
|
||||
/// time.
|
||||
///
|
||||
/// - `id`: The identifier of the asset to be destroyed. This must identify an existing
|
||||
/// asset.
|
||||
///
|
||||
/// Each call Emits the `Event::DestroyedApprovals` event.
|
||||
#[pallet::weight(T::WeightInfo::destroy_approvals(T::RemoveItemsLimit::get()))]
|
||||
pub fn destroy_approvals(
|
||||
origin: OriginFor<T>,
|
||||
#[pallet::compact] id: T::AssetId,
|
||||
) -> DispatchResultWithPostInfo {
|
||||
let _ = ensure_signed(origin)?;
|
||||
let removed_approvals = Self::do_destroy_approvals(id, T::RemoveItemsLimit::get())?;
|
||||
Ok(Some(T::WeightInfo::destroy_approvals(removed_approvals)).into())
|
||||
}
|
||||
|
||||
/// Complete destroying asset and unreserve currency.
|
||||
/// `finish_destroy` should only be called after `start_destroy` has been called, and the
|
||||
/// asset is in a `Destroying` state. All accounts or approvals should be destroyed before
|
||||
/// hand.
|
||||
///
|
||||
/// - `id`: The identifier of the asset to be destroyed. This must identify an existing
|
||||
/// asset.
|
||||
///
|
||||
/// Each successful call Emits the `Event::Destroyed` event.
|
||||
#[pallet::weight(T::WeightInfo::finish_destroy())]
|
||||
pub fn finish_destroy(
|
||||
origin: OriginFor<T>,
|
||||
#[pallet::compact] id: T::AssetId,
|
||||
) -> DispatchResult {
|
||||
let _ = ensure_signed(origin)?;
|
||||
Self::do_finish_destroy(id)
|
||||
}
|
||||
|
||||
/// Mint assets of a particular class.
|
||||
@@ -793,6 +870,10 @@ pub mod pallet {
|
||||
let origin = ensure_signed(origin)?;
|
||||
|
||||
let d = Asset::<T, I>::get(id).ok_or(Error::<T, I>::Unknown)?;
|
||||
ensure!(
|
||||
d.status == AssetStatus::Live || d.status == AssetStatus::Frozen,
|
||||
Error::<T, I>::AssetNotLive
|
||||
);
|
||||
ensure!(origin == d.freezer, Error::<T, I>::NoPermission);
|
||||
let who = T::Lookup::lookup(who)?;
|
||||
|
||||
@@ -824,6 +905,10 @@ pub mod pallet {
|
||||
let origin = ensure_signed(origin)?;
|
||||
|
||||
let details = Asset::<T, I>::get(id).ok_or(Error::<T, I>::Unknown)?;
|
||||
ensure!(
|
||||
details.status == AssetStatus::Live || details.status == AssetStatus::Frozen,
|
||||
Error::<T, I>::AssetNotLive
|
||||
);
|
||||
ensure!(origin == details.admin, Error::<T, I>::NoPermission);
|
||||
let who = T::Lookup::lookup(who)?;
|
||||
|
||||
@@ -854,9 +939,10 @@ pub mod pallet {
|
||||
|
||||
Asset::<T, I>::try_mutate(id, |maybe_details| {
|
||||
let d = maybe_details.as_mut().ok_or(Error::<T, I>::Unknown)?;
|
||||
ensure!(d.status == AssetStatus::Live, Error::<T, I>::AssetNotLive);
|
||||
ensure!(origin == d.freezer, Error::<T, I>::NoPermission);
|
||||
|
||||
d.is_frozen = true;
|
||||
d.status = AssetStatus::Frozen;
|
||||
|
||||
Self::deposit_event(Event::<T, I>::AssetFrozen { asset_id: id });
|
||||
Ok(())
|
||||
@@ -882,8 +968,9 @@ pub mod pallet {
|
||||
Asset::<T, I>::try_mutate(id, |maybe_details| {
|
||||
let d = maybe_details.as_mut().ok_or(Error::<T, I>::Unknown)?;
|
||||
ensure!(origin == d.admin, Error::<T, I>::NoPermission);
|
||||
ensure!(d.status == AssetStatus::Frozen, Error::<T, I>::NotFrozen);
|
||||
|
||||
d.is_frozen = false;
|
||||
d.status = AssetStatus::Live;
|
||||
|
||||
Self::deposit_event(Event::<T, I>::AssetThawed { asset_id: id });
|
||||
Ok(())
|
||||
@@ -911,6 +998,7 @@ pub mod pallet {
|
||||
|
||||
Asset::<T, I>::try_mutate(id, |maybe_details| {
|
||||
let details = maybe_details.as_mut().ok_or(Error::<T, I>::Unknown)?;
|
||||
ensure!(details.status == AssetStatus::Live, Error::<T, I>::LiveAsset);
|
||||
ensure!(origin == details.owner, Error::<T, I>::NoPermission);
|
||||
if details.owner == owner {
|
||||
return Ok(())
|
||||
@@ -956,6 +1044,7 @@ pub mod pallet {
|
||||
|
||||
Asset::<T, I>::try_mutate(id, |maybe_details| {
|
||||
let details = maybe_details.as_mut().ok_or(Error::<T, I>::Unknown)?;
|
||||
ensure!(details.status == AssetStatus::Live, Error::<T, I>::AssetNotLive);
|
||||
ensure!(origin == details.owner, Error::<T, I>::NoPermission);
|
||||
|
||||
details.issuer = issuer.clone();
|
||||
@@ -1014,6 +1103,7 @@ pub mod pallet {
|
||||
let origin = ensure_signed(origin)?;
|
||||
|
||||
let d = Asset::<T, I>::get(id).ok_or(Error::<T, I>::Unknown)?;
|
||||
ensure!(d.status == AssetStatus::Live, Error::<T, I>::AssetNotLive);
|
||||
ensure!(origin == d.owner, Error::<T, I>::NoPermission);
|
||||
|
||||
Metadata::<T, I>::try_mutate_exists(id, |metadata| {
|
||||
@@ -1142,13 +1232,18 @@ pub mod pallet {
|
||||
|
||||
Asset::<T, I>::try_mutate(id, |maybe_asset| {
|
||||
let mut asset = maybe_asset.take().ok_or(Error::<T, I>::Unknown)?;
|
||||
ensure!(asset.status != AssetStatus::Destroying, Error::<T, I>::AssetNotLive);
|
||||
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.min_balance = min_balance;
|
||||
asset.is_sufficient = is_sufficient;
|
||||
asset.is_frozen = is_frozen;
|
||||
if is_frozen {
|
||||
asset.status = AssetStatus::Frozen;
|
||||
} else {
|
||||
asset.status = AssetStatus::Live;
|
||||
}
|
||||
*maybe_asset = Some(asset);
|
||||
|
||||
Self::deposit_event(Event::AssetStatusChanged { asset_id: id });
|
||||
@@ -1210,6 +1305,7 @@ pub mod pallet {
|
||||
let owner = ensure_signed(origin)?;
|
||||
let delegate = T::Lookup::lookup(delegate)?;
|
||||
let mut d = Asset::<T, I>::get(id).ok_or(Error::<T, I>::Unknown)?;
|
||||
ensure!(d.status == AssetStatus::Live, Error::<T, I>::AssetNotLive);
|
||||
let approval =
|
||||
Approvals::<T, I>::take((id, &owner, &delegate)).ok_or(Error::<T, I>::Unknown)?;
|
||||
T::Currency::unreserve(&owner, approval.deposit);
|
||||
@@ -1242,6 +1338,7 @@ pub mod pallet {
|
||||
delegate: AccountIdLookupOf<T>,
|
||||
) -> DispatchResult {
|
||||
let mut d = Asset::<T, I>::get(id).ok_or(Error::<T, I>::Unknown)?;
|
||||
ensure!(d.status == AssetStatus::Live, Error::<T, I>::AssetNotLive);
|
||||
T::ForceOrigin::try_origin(origin)
|
||||
.map(|_| ())
|
||||
.or_else(|origin| -> DispatchResult {
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2017-2022 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.
|
||||
|
||||
use super::*;
|
||||
use frame_support::{log, traits::OnRuntimeUpgrade};
|
||||
|
||||
pub mod v1 {
|
||||
use frame_support::{pallet_prelude::*, weights::Weight};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Decode)]
|
||||
pub struct OldAssetDetails<Balance, AccountId, DepositBalance> {
|
||||
pub owner: AccountId,
|
||||
pub issuer: AccountId,
|
||||
pub admin: AccountId,
|
||||
pub freezer: AccountId,
|
||||
pub supply: Balance,
|
||||
pub deposit: DepositBalance,
|
||||
pub min_balance: Balance,
|
||||
pub is_sufficient: bool,
|
||||
pub accounts: u32,
|
||||
pub sufficients: u32,
|
||||
pub approvals: u32,
|
||||
pub is_frozen: bool,
|
||||
}
|
||||
|
||||
impl<Balance, AccountId, DepositBalance> OldAssetDetails<Balance, AccountId, DepositBalance> {
|
||||
fn migrate_to_v1(self) -> AssetDetails<Balance, AccountId, DepositBalance> {
|
||||
let status = if self.is_frozen { AssetStatus::Frozen } else { AssetStatus::Live };
|
||||
|
||||
AssetDetails {
|
||||
owner: self.owner,
|
||||
issuer: self.issuer,
|
||||
admin: self.admin,
|
||||
freezer: self.freezer,
|
||||
supply: self.supply,
|
||||
deposit: self.deposit,
|
||||
min_balance: self.min_balance,
|
||||
is_sufficient: self.is_sufficient,
|
||||
accounts: self.accounts,
|
||||
sufficients: self.sufficients,
|
||||
approvals: self.approvals,
|
||||
status,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MigrateToV1<T>(sp_std::marker::PhantomData<T>);
|
||||
impl<T: Config> OnRuntimeUpgrade for MigrateToV1<T> {
|
||||
fn on_runtime_upgrade() -> Weight {
|
||||
let current_version = Pallet::<T>::current_storage_version();
|
||||
let onchain_version = Pallet::<T>::on_chain_storage_version();
|
||||
if onchain_version == 0 && current_version == 1 {
|
||||
let mut translated = 0u64;
|
||||
Asset::<T>::translate::<
|
||||
OldAssetDetails<T::Balance, T::AccountId, DepositBalanceOf<T>>,
|
||||
_,
|
||||
>(|_key, old_value| {
|
||||
translated.saturating_inc();
|
||||
Some(old_value.migrate_to_v1())
|
||||
});
|
||||
current_version.put::<Pallet<T>>();
|
||||
log::info!(target: "runtime::assets", "Upgraded {} pools, storage to version {:?}", translated, current_version);
|
||||
T::DbWeight::get().reads_writes(translated + 1, translated + 1)
|
||||
} else {
|
||||
log::info!(target: "runtime::assets", "Migration did not execute. This probably should be removed");
|
||||
T::DbWeight::get().reads(1)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn pre_upgrade() -> Result<Vec<u8>, &'static str> {
|
||||
frame_support::ensure!(
|
||||
Pallet::<T>::on_chain_storage_version() == 0,
|
||||
"must upgrade linearly"
|
||||
);
|
||||
let prev_count = Asset::<T>::iter().count();
|
||||
Ok((prev_count as u32).encode())
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn post_upgrade(prev_count: Vec<u8>) -> Result<(), &'static str> {
|
||||
let prev_count: u32 = Decode::decode(&mut prev_count.as_slice()).expect(
|
||||
"the state parameter should be something that was generated by pre_upgrade",
|
||||
);
|
||||
let post_count = Asset::<T>::iter().count() as u32;
|
||||
assert_eq!(
|
||||
prev_count, post_count,
|
||||
"the asset count before and after the migration should be the same"
|
||||
);
|
||||
|
||||
let current_version = Pallet::<T>::current_storage_version();
|
||||
let onchain_version = Pallet::<T>::on_chain_storage_version();
|
||||
|
||||
frame_support::ensure!(current_version == 1, "must_upgrade");
|
||||
assert_eq!(
|
||||
current_version, onchain_version,
|
||||
"after migration, the current_version and onchain_version should be the same"
|
||||
);
|
||||
|
||||
Asset::<T>::iter().for_each(|(_id, asset)| {
|
||||
assert!(asset.status == AssetStatus::Live || asset.status == AssetStatus::Frozen, "assets should only be live or frozen. None should be in destroying status, or undefined state")
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -100,6 +100,7 @@ impl Config for Test {
|
||||
type Freezer = TestFreezer;
|
||||
type WeightInfo = ();
|
||||
type Extra = ();
|
||||
type RemoveItemsLimit = ConstU32<5>;
|
||||
}
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
@@ -328,8 +328,12 @@ fn lifecycle_should_work() {
|
||||
assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 20, 100));
|
||||
assert_eq!(Account::<Test>::iter_prefix(0).count(), 2);
|
||||
|
||||
let w = Asset::<Test>::get(0).unwrap().destroy_witness();
|
||||
assert_ok!(Assets::destroy(RuntimeOrigin::signed(1), 0, w));
|
||||
assert_ok!(Assets::freeze_asset(RuntimeOrigin::signed(1), 0));
|
||||
assert_ok!(Assets::start_destroy(RuntimeOrigin::signed(1), 0));
|
||||
assert_ok!(Assets::destroy_accounts(RuntimeOrigin::signed(1), 0));
|
||||
assert_ok!(Assets::destroy_approvals(RuntimeOrigin::signed(1), 0));
|
||||
assert_ok!(Assets::finish_destroy(RuntimeOrigin::signed(1), 0));
|
||||
|
||||
assert_eq!(Balances::reserved_balance(&1), 0);
|
||||
|
||||
assert!(!Asset::<Test>::contains_key(0));
|
||||
@@ -348,8 +352,12 @@ fn lifecycle_should_work() {
|
||||
assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 20, 100));
|
||||
assert_eq!(Account::<Test>::iter_prefix(0).count(), 2);
|
||||
|
||||
let w = Asset::<Test>::get(0).unwrap().destroy_witness();
|
||||
assert_ok!(Assets::destroy(RuntimeOrigin::root(), 0, w));
|
||||
assert_ok!(Assets::freeze_asset(RuntimeOrigin::signed(1), 0));
|
||||
assert_ok!(Assets::start_destroy(RuntimeOrigin::signed(1), 0));
|
||||
assert_ok!(Assets::destroy_accounts(RuntimeOrigin::signed(1), 0));
|
||||
assert_ok!(Assets::destroy_approvals(RuntimeOrigin::signed(1), 0));
|
||||
assert_ok!(Assets::finish_destroy(RuntimeOrigin::signed(1), 0));
|
||||
|
||||
assert_eq!(Balances::reserved_balance(&1), 0);
|
||||
|
||||
assert!(!Asset::<Test>::contains_key(0));
|
||||
@@ -358,24 +366,6 @@ fn lifecycle_should_work() {
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn destroy_with_bad_witness_should_not_work() {
|
||||
new_test_ext().execute_with(|| {
|
||||
Balances::make_free_balance_be(&1, 100);
|
||||
assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1));
|
||||
let mut w = Asset::<Test>::get(0).unwrap().destroy_witness();
|
||||
assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 10, 100));
|
||||
assert_eq!(asset_ids(), vec![0, 999]);
|
||||
// witness too low
|
||||
assert_noop!(Assets::destroy(RuntimeOrigin::signed(1), 0, w), Error::<Test>::BadWitness);
|
||||
// witness too high is okay though
|
||||
w.accounts += 2;
|
||||
w.sufficients += 2;
|
||||
assert_ok!(Assets::destroy(RuntimeOrigin::signed(1), 0, w));
|
||||
assert_eq!(asset_ids(), vec![999]);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn destroy_should_refund_approvals() {
|
||||
new_test_ext().execute_with(|| {
|
||||
@@ -388,8 +378,13 @@ fn destroy_should_refund_approvals() {
|
||||
assert_eq!(Balances::reserved_balance(&1), 3);
|
||||
assert_eq!(asset_ids(), vec![0, 999]);
|
||||
|
||||
let w = Asset::<Test>::get(0).unwrap().destroy_witness();
|
||||
assert_ok!(Assets::destroy(RuntimeOrigin::signed(1), 0, w));
|
||||
assert_ok!(Assets::freeze_asset(RuntimeOrigin::signed(1), 0));
|
||||
|
||||
assert_ok!(Assets::start_destroy(RuntimeOrigin::signed(1), 0));
|
||||
assert_ok!(Assets::destroy_accounts(RuntimeOrigin::signed(1), 0));
|
||||
assert_ok!(Assets::destroy_approvals(RuntimeOrigin::signed(1), 0));
|
||||
assert_ok!(Assets::finish_destroy(RuntimeOrigin::signed(1), 0));
|
||||
|
||||
assert_eq!(Balances::reserved_balance(&1), 0);
|
||||
assert_eq!(asset_ids(), vec![999]);
|
||||
|
||||
@@ -398,6 +393,58 @@ fn destroy_should_refund_approvals() {
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn partial_destroy_should_work() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1));
|
||||
assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 10));
|
||||
assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 2, 10));
|
||||
assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 3, 10));
|
||||
assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 4, 10));
|
||||
assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 5, 10));
|
||||
assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 6, 10));
|
||||
assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 7, 10));
|
||||
assert_ok!(Assets::freeze_asset(RuntimeOrigin::signed(1), 0));
|
||||
|
||||
assert_ok!(Assets::start_destroy(RuntimeOrigin::signed(1), 0));
|
||||
assert_ok!(Assets::destroy_accounts(RuntimeOrigin::signed(1), 0));
|
||||
assert_ok!(Assets::destroy_approvals(RuntimeOrigin::signed(1), 0));
|
||||
// Asset is in use, as all the accounts have not yet been destroyed.
|
||||
// We need to call destroy_accounts or destroy_approvals again until asset is completely
|
||||
// cleaned up.
|
||||
assert_noop!(Assets::finish_destroy(RuntimeOrigin::signed(1), 0), Error::<Test>::InUse);
|
||||
|
||||
System::assert_has_event(RuntimeEvent::Assets(crate::Event::AccountsDestroyed {
|
||||
asset_id: 0,
|
||||
accounts_destroyed: 5,
|
||||
accounts_remaining: 2,
|
||||
}));
|
||||
System::assert_has_event(RuntimeEvent::Assets(crate::Event::ApprovalsDestroyed {
|
||||
asset_id: 0,
|
||||
approvals_destroyed: 0,
|
||||
approvals_remaining: 0,
|
||||
}));
|
||||
// Partially destroyed Asset should continue to exist
|
||||
assert!(Asset::<Test>::contains_key(0));
|
||||
|
||||
// Second call to destroy on PartiallyDestroyed asset
|
||||
assert_ok!(Assets::destroy_accounts(RuntimeOrigin::signed(1), 0));
|
||||
System::assert_has_event(RuntimeEvent::Assets(crate::Event::AccountsDestroyed {
|
||||
asset_id: 0,
|
||||
accounts_destroyed: 2,
|
||||
accounts_remaining: 0,
|
||||
}));
|
||||
assert_ok!(Assets::destroy_approvals(RuntimeOrigin::signed(1), 0));
|
||||
assert_ok!(Assets::destroy_approvals(RuntimeOrigin::signed(1), 0));
|
||||
assert_ok!(Assets::finish_destroy(RuntimeOrigin::signed(1), 0));
|
||||
|
||||
System::assert_has_event(RuntimeEvent::Assets(crate::Event::Destroyed { asset_id: 0 }));
|
||||
|
||||
// Destroyed Asset should not exist
|
||||
assert!(!Asset::<Test>::contains_key(0));
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_providing_should_work() {
|
||||
new_test_ext().execute_with(|| {
|
||||
@@ -540,7 +587,10 @@ fn transferring_frozen_asset_should_not_work() {
|
||||
assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100));
|
||||
assert_eq!(Assets::balance(0, 1), 100);
|
||||
assert_ok!(Assets::freeze_asset(RuntimeOrigin::signed(1), 0));
|
||||
assert_noop!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50), Error::<Test>::Frozen);
|
||||
assert_noop!(
|
||||
Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50),
|
||||
Error::<Test>::AssetNotLive
|
||||
);
|
||||
assert_ok!(Assets::thaw_asset(RuntimeOrigin::signed(1), 0));
|
||||
assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50));
|
||||
});
|
||||
@@ -556,7 +606,7 @@ fn approve_transfer_frozen_asset_should_not_work() {
|
||||
assert_ok!(Assets::freeze_asset(RuntimeOrigin::signed(1), 0));
|
||||
assert_noop!(
|
||||
Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50),
|
||||
Error::<Test>::Frozen
|
||||
Error::<Test>::AssetNotLive
|
||||
);
|
||||
assert_ok!(Assets::thaw_asset(RuntimeOrigin::signed(1), 0));
|
||||
assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50));
|
||||
@@ -590,8 +640,10 @@ fn origin_guards_should_work() {
|
||||
Assets::force_transfer(RuntimeOrigin::signed(2), 0, 1, 2, 100),
|
||||
Error::<Test>::NoPermission
|
||||
);
|
||||
let w = Asset::<Test>::get(0).unwrap().destroy_witness();
|
||||
assert_noop!(Assets::destroy(RuntimeOrigin::signed(2), 0, w), Error::<Test>::NoPermission);
|
||||
assert_noop!(
|
||||
Assets::start_destroy(RuntimeOrigin::signed(2), 0),
|
||||
Error::<Test>::NoPermission
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -803,22 +855,37 @@ fn set_metadata_should_work() {
|
||||
|
||||
/// Destroying an asset calls the `FrozenBalance::died` hooks of all accounts.
|
||||
#[test]
|
||||
fn destroy_calls_died_hooks() {
|
||||
fn destroy_accounts_calls_died_hooks() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 50));
|
||||
// Create account 1 and 2.
|
||||
assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100));
|
||||
assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 2, 100));
|
||||
// Destroy the asset.
|
||||
let w = Asset::<Test>::get(0).unwrap().destroy_witness();
|
||||
assert_ok!(Assets::destroy(RuntimeOrigin::signed(1), 0, w));
|
||||
// Destroy the accounts.
|
||||
assert_ok!(Assets::freeze_asset(RuntimeOrigin::signed(1), 0));
|
||||
assert_ok!(Assets::start_destroy(RuntimeOrigin::signed(1), 0));
|
||||
assert_ok!(Assets::destroy_accounts(RuntimeOrigin::signed(1), 0));
|
||||
|
||||
// Asset is gone and accounts 1 and 2 died.
|
||||
assert!(Asset::<Test>::get(0).is_none());
|
||||
// Accounts 1 and 2 died.
|
||||
assert_eq!(hooks(), vec![Hook::Died(0, 1), Hook::Died(0, 2)]);
|
||||
})
|
||||
}
|
||||
|
||||
/// Destroying an asset calls the `FrozenBalance::died` hooks of all accounts.
|
||||
#[test]
|
||||
fn finish_destroy_asset_destroys_asset() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 50));
|
||||
// Destroy the accounts.
|
||||
assert_ok!(Assets::freeze_asset(RuntimeOrigin::signed(1), 0));
|
||||
assert_ok!(Assets::start_destroy(RuntimeOrigin::signed(1), 0));
|
||||
assert_ok!(Assets::finish_destroy(RuntimeOrigin::signed(1), 0));
|
||||
|
||||
// Asset is gone
|
||||
assert!(Asset::<Test>::get(0).is_none());
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn freezer_should_work() {
|
||||
new_test_ext().execute_with(|| {
|
||||
|
||||
@@ -29,6 +29,19 @@ pub(super) type DepositBalanceOf<T, I = ()> =
|
||||
pub(super) type AssetAccountOf<T, I> =
|
||||
AssetAccount<<T as Config<I>>::Balance, DepositBalanceOf<T, I>, <T as Config<I>>::Extra>;
|
||||
|
||||
/// AssetStatus holds the current state of the asset. It could either be Live and available for use,
|
||||
/// or in a Destroying state.
|
||||
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)]
|
||||
pub(super) enum AssetStatus {
|
||||
/// The asset is active and able to be used.
|
||||
Live,
|
||||
/// Whether the asset is frozen for non-admin transfers.
|
||||
Frozen,
|
||||
/// The asset is currently being destroyed, and all actions are no longer permitted on the
|
||||
/// asset. Once set to `Destroying`, the asset can never transition back to a `Live` state.
|
||||
Destroying,
|
||||
}
|
||||
|
||||
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)]
|
||||
pub struct AssetDetails<Balance, AccountId, DepositBalance> {
|
||||
/// Can change `owner`, `issuer`, `freezer` and `admin` accounts.
|
||||
@@ -54,18 +67,8 @@ pub struct AssetDetails<Balance, AccountId, DepositBalance> {
|
||||
pub(super) sufficients: u32,
|
||||
/// The total number of approvals.
|
||||
pub(super) approvals: u32,
|
||||
/// Whether the asset is frozen for non-admin transfers.
|
||||
pub(super) is_frozen: bool,
|
||||
}
|
||||
|
||||
impl<Balance, AccountId, DepositBalance> AssetDetails<Balance, AccountId, DepositBalance> {
|
||||
pub fn destroy_witness(&self) -> DestroyWitness {
|
||||
DestroyWitness {
|
||||
accounts: self.accounts,
|
||||
sufficients: self.sufficients,
|
||||
approvals: self.approvals,
|
||||
}
|
||||
}
|
||||
/// The status of the asset
|
||||
pub(super) status: AssetStatus,
|
||||
}
|
||||
|
||||
/// Data concerning an approval.
|
||||
@@ -139,20 +142,6 @@ pub struct AssetMetadata<DepositBalance, BoundedString> {
|
||||
pub(super) is_frozen: bool,
|
||||
}
|
||||
|
||||
/// Witness data for the destroy transactions.
|
||||
#[derive(Copy, Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)]
|
||||
pub struct DestroyWitness {
|
||||
/// The number of accounts holding the asset.
|
||||
#[codec(compact)]
|
||||
pub(super) accounts: u32,
|
||||
/// The number of accounts holding the asset with a self-sufficient reference.
|
||||
#[codec(compact)]
|
||||
pub(super) sufficients: u32,
|
||||
/// The number of transfer-approvals of the asset.
|
||||
#[codec(compact)]
|
||||
pub(super) approvals: u32,
|
||||
}
|
||||
|
||||
/// Trait for allowing a minimum balance on the account to be specified, beyond the
|
||||
/// `minimum_balance` of the asset. This is additive - the `minimum_balance` of the asset must be
|
||||
/// met *and then* anything here in addition.
|
||||
|
||||
@@ -49,7 +49,10 @@ use sp_std::marker::PhantomData;
|
||||
pub trait WeightInfo {
|
||||
fn create() -> Weight;
|
||||
fn force_create() -> Weight;
|
||||
fn destroy(c: u32, s: u32, a: u32, ) -> Weight;
|
||||
fn start_destroy() -> Weight;
|
||||
fn destroy_accounts(c: u32) -> Weight;
|
||||
fn destroy_approvals(m: u32) -> Weight;
|
||||
fn finish_destroy() -> Weight;
|
||||
fn mint() -> Weight;
|
||||
fn burn() -> Weight;
|
||||
fn transfer() -> Weight;
|
||||
@@ -89,30 +92,49 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
|
||||
.saturating_add(T::DbWeight::get().reads(1 as u64))
|
||||
.saturating_add(T::DbWeight::get().writes(1 as u64))
|
||||
}
|
||||
|
||||
// Storage: Assets Asset (r:1 w:1)
|
||||
// Storage: Assets Account (r:5002 w:5001)
|
||||
// Storage: System Account (r:5000 w:5000)
|
||||
// Storage: Assets Metadata (r:1 w:0)
|
||||
// Storage: Assets Approvals (r:501 w:500)
|
||||
/// The range of component `c` is `[0, 5000]`.
|
||||
/// The range of component `s` is `[0, 5000]`.
|
||||
/// The range of component `a` is `[0, 500]`.
|
||||
fn destroy(c: u32, s: u32, a: u32, ) -> Weight {
|
||||
// Minimum execution time: 76_222_544 nanoseconds.
|
||||
Weight::from_ref_time(76_864_587_000 as u64)
|
||||
// Standard Error: 127_086
|
||||
.saturating_add(Weight::from_ref_time(8_645_143 as u64).saturating_mul(c as u64))
|
||||
// Standard Error: 127_086
|
||||
.saturating_add(Weight::from_ref_time(11_281_301 as u64).saturating_mul(s as u64))
|
||||
.saturating_add(T::DbWeight::get().reads(5 as u64))
|
||||
fn start_destroy() -> Weight {
|
||||
Weight::from_ref_time(31_000_000 as u64)
|
||||
.saturating_add(T::DbWeight::get().reads(1 as u64))
|
||||
.saturating_add(T::DbWeight::get().writes(1 as u64))
|
||||
}
|
||||
|
||||
// Storage: Assets Asset (r:1 w:1)
|
||||
// Storage: Assets Account (r:1 w:0)
|
||||
// Storage: System Account (r:20 w:20)
|
||||
/// The range of component `c` is `[0, 1000]`.
|
||||
fn destroy_accounts(c: u32, ) -> Weight {
|
||||
Weight::from_ref_time(37_000_000 as u64)
|
||||
// Standard Error: 19_301
|
||||
.saturating_add(Weight::from_ref_time(25_467_908 as u64).saturating_mul(c as u64))
|
||||
.saturating_add(T::DbWeight::get().reads(2 as u64))
|
||||
.saturating_add(T::DbWeight::get().reads((2 as u64).saturating_mul(c as u64)))
|
||||
.saturating_add(T::DbWeight::get().reads((2 as u64).saturating_mul(s as u64)))
|
||||
.saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(a as u64)))
|
||||
.saturating_add(T::DbWeight::get().writes(2 as u64))
|
||||
.saturating_add(T::DbWeight::get().writes(1 as u64))
|
||||
.saturating_add(T::DbWeight::get().writes((2 as u64).saturating_mul(c as u64)))
|
||||
.saturating_add(T::DbWeight::get().writes((2 as u64).saturating_mul(s as u64)))
|
||||
}
|
||||
|
||||
// Storage: Assets Asset (r:1 w:1)
|
||||
// Storage: Assets Approvals (r:1 w:0)
|
||||
/// The range of component `a` is `[0, 1000]`.
|
||||
fn destroy_approvals(a: u32, ) -> Weight {
|
||||
Weight::from_ref_time(39_000_000 as u64)
|
||||
// Standard Error: 14_298
|
||||
.saturating_add(Weight::from_ref_time(27_632_144 as u64).saturating_mul(a as u64))
|
||||
.saturating_add(T::DbWeight::get().reads(2 as u64))
|
||||
.saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(a as u64)))
|
||||
.saturating_add(T::DbWeight::get().writes(1 as u64))
|
||||
.saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(a as u64)))
|
||||
}
|
||||
|
||||
// Storage: Assets Asset (r:1 w:1)
|
||||
// Storage: Assets Metadata (r:1 w:0)
|
||||
fn finish_destroy() -> Weight {
|
||||
Weight::from_ref_time(33_000_000 as u64)
|
||||
.saturating_add(T::DbWeight::get().reads(2 as u64))
|
||||
.saturating_add(T::DbWeight::get().writes(1 as u64))
|
||||
}
|
||||
|
||||
// Storage: Assets Asset (r:1 w:1)
|
||||
// Storage: Assets Account (r:1 w:1)
|
||||
fn mint() -> Weight {
|
||||
@@ -302,30 +324,49 @@ impl WeightInfo for () {
|
||||
.saturating_add(RocksDbWeight::get().reads(1 as u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(1 as u64))
|
||||
}
|
||||
|
||||
// Storage: Assets Asset (r:1 w:1)
|
||||
// Storage: Assets Account (r:5002 w:5001)
|
||||
// Storage: System Account (r:5000 w:5000)
|
||||
// Storage: Assets Metadata (r:1 w:0)
|
||||
// Storage: Assets Approvals (r:501 w:500)
|
||||
/// The range of component `c` is `[0, 5000]`.
|
||||
/// The range of component `s` is `[0, 5000]`.
|
||||
/// The range of component `a` is `[0, 500]`.
|
||||
fn destroy(c: u32, s: u32, a: u32, ) -> Weight {
|
||||
// Minimum execution time: 76_222_544 nanoseconds.
|
||||
Weight::from_ref_time(76_864_587_000 as u64)
|
||||
// Standard Error: 127_086
|
||||
.saturating_add(Weight::from_ref_time(8_645_143 as u64).saturating_mul(c as u64))
|
||||
// Standard Error: 127_086
|
||||
.saturating_add(Weight::from_ref_time(11_281_301 as u64).saturating_mul(s as u64))
|
||||
.saturating_add(RocksDbWeight::get().reads(5 as u64))
|
||||
fn start_destroy() -> Weight {
|
||||
Weight::from_ref_time(31_000_000 as u64)
|
||||
.saturating_add(RocksDbWeight::get().reads(1 as u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(1 as u64))
|
||||
}
|
||||
|
||||
// Storage: Assets Asset (r:1 w:1)
|
||||
// Storage: Assets Account (r:1 w:0)
|
||||
// Storage: System Account (r:20 w:20)
|
||||
/// The range of component `c` is `[0, 1000]`.
|
||||
fn destroy_accounts(c: u32, ) -> Weight {
|
||||
Weight::from_ref_time(37_000_000 as u64)
|
||||
// Standard Error: 19_301
|
||||
.saturating_add(Weight::from_ref_time(25_467_908 as u64).saturating_mul(c as u64))
|
||||
.saturating_add(RocksDbWeight::get().reads(2 as u64))
|
||||
.saturating_add(RocksDbWeight::get().reads((2 as u64).saturating_mul(c as u64)))
|
||||
.saturating_add(RocksDbWeight::get().reads((2 as u64).saturating_mul(s as u64)))
|
||||
.saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(a as u64)))
|
||||
.saturating_add(RocksDbWeight::get().writes(2 as u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(1 as u64))
|
||||
.saturating_add(RocksDbWeight::get().writes((2 as u64).saturating_mul(c as u64)))
|
||||
.saturating_add(RocksDbWeight::get().writes((2 as u64).saturating_mul(s as u64)))
|
||||
}
|
||||
|
||||
// Storage: Assets Asset (r:1 w:1)
|
||||
// Storage: Assets Approvals (r:1 w:0)
|
||||
/// The range of component `a` is `[0, 1000]`.
|
||||
fn destroy_approvals(a: u32, ) -> Weight {
|
||||
Weight::from_ref_time(39_000_000 as u64)
|
||||
// Standard Error: 14_298
|
||||
.saturating_add(Weight::from_ref_time(27_632_144 as u64).saturating_mul(a as u64))
|
||||
.saturating_add(RocksDbWeight::get().reads(2 as u64))
|
||||
.saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(a as u64)))
|
||||
.saturating_add(RocksDbWeight::get().writes(1 as u64))
|
||||
.saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(a as u64)))
|
||||
}
|
||||
|
||||
// Storage: Assets Asset (r:1 w:1)
|
||||
// Storage: Assets Metadata (r:1 w:0)
|
||||
fn finish_destroy() -> Weight {
|
||||
Weight::from_ref_time(33_000_000 as u64)
|
||||
.saturating_add(RocksDbWeight::get().reads(2 as u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(1 as u64))
|
||||
}
|
||||
|
||||
// Storage: Assets Asset (r:1 w:1)
|
||||
// Storage: Assets Account (r:1 w:1)
|
||||
fn mint() -> Weight {
|
||||
|
||||
Reference in New Issue
Block a user