This repository has been archived by the owner on Nov 15, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Asset Pallet: Support repeated destroys to safely destroy large assets #12310
Merged
paritytech-processbot
merged 73 commits into
master
from
aa/safely-destroy-large-assets
Nov 15, 2022
Merged
Changes from all commits
Commits
Show all changes
73 commits
Select commit
Hold shift + click to select a range
6a9d9d1
Support repeated destroys to safely destroy large assets
tonyalaribe 7db1fd5
require freezing accounts before destroying
tonyalaribe cbe14b2
support only deleting asset as final stage when there's no assets left
tonyalaribe 001eaef
Merge branch 'master' into aa/safely-destroy-large-assets
tonyalaribe 1beae07
pre: introduce the RemoveKeyLimit config parameter
tonyalaribe c9ce171
debug_ensure empty account in the right if block
tonyalaribe 3067e38
update to having separate max values for accounts and approvals
tonyalaribe 7c4f610
add tests and use RemoveKeyLimit constant
tonyalaribe aca497f
add useful comments to the extrinsics, and calculate returned weight
tonyalaribe 634345a
Merge branch 'master' of github.com:paritytech/substrate into aa/safe…
tonyalaribe 93d886d
add benchmarking for start_destroy and finish destroy
tonyalaribe 2d3b650
push failing benchmark logic
tonyalaribe 5a612d1
add benchmark tests for new functions
tonyalaribe b961255
update weights via local benchmarks
tonyalaribe 91e3c18
remove extra weight file
tonyalaribe 60f954a
Update frame/assets/src/lib.rs
tonyalaribe ce00b5b
Update frame/assets/src/types.rs
tonyalaribe 5492b6d
Update frame/assets/src/lib.rs
tonyalaribe bc20a6c
effect some changes from codereview
tonyalaribe 6ceb592
use NotFrozen error
tonyalaribe 2edac52
remove origin checks, as anyone can complete destruction after owner …
tonyalaribe aa2aecf
fix comments about Origin behaviour
tonyalaribe 10fdd90
add AssetStatus docs
tonyalaribe 139d2e9
modularize logic to allow calling logic in on_idle and on_initialize …
tonyalaribe 597f532
introduce simple migration for assets details
tonyalaribe a09e762
Merge remote-tracking branch 'origin/master' into aa/safely-destroy-l…
70930ac
reintroduce logging in the migrations
tonyalaribe dc4a243
move deposit_Event out of the mutate block
tonyalaribe 05d778d
Update frame/assets/src/functions.rs
tonyalaribe 62d4f13
Update frame/assets/src/migration.rs
tonyalaribe e9e108b
move AssetNotLive checkout out of the mutate blocks
tonyalaribe 5df4a62
Merge branch 'aa/safely-destroy-large-assets' of github.com:paritytec…
tonyalaribe 544ac52
rename RemoveKeysLimit to RemoveItemsLimit
tonyalaribe 9206acf
update docs
tonyalaribe 29c7745
fix event name in benchmark
tonyalaribe 2986f29
fix cargo fmt.
tonyalaribe 6ac84a1
fix lint in benchmarking
tonyalaribe 1f2930f
Merge branch 'master' of github.com:paritytech/substrate into aa/safe…
tonyalaribe 791aa7f
Empty commit to trigger CI
tonyalaribe 450a698
Update frame/assets/src/lib.rs
tonyalaribe 3daef79
Update frame/assets/src/lib.rs
tonyalaribe ea34cf5
Update frame/assets/src/functions.rs
tonyalaribe 84cbb47
Update frame/assets/src/functions.rs
tonyalaribe 082a10c
Update frame/assets/src/functions.rs
tonyalaribe e84d5ae
Update frame/assets/src/lib.rs
tonyalaribe 78cde15
Update frame/assets/src/functions.rs
tonyalaribe c6470fb
effect change suggested during code review
tonyalaribe 85e50b9
move limit to a single location
tonyalaribe c97e850
Merge branch 'master' of github.com:paritytech/substrate into aa/safe…
tonyalaribe df27e0b
Update frame/assets/src/functions.rs
tonyalaribe ca9fa2e
rename events
tonyalaribe 9609f13
fix weight typo, using rocksdb instead of T::DbWeight. Pending genera…
tonyalaribe 26d27cc
switch to using dead_account.len()
tonyalaribe 72ec8f8
rename event in the benchmarks
tonyalaribe ee30cf8
empty to retrigger CI
tonyalaribe 87a7877
Merge branch 'master' of github.com:paritytech/substrate into aa/safe…
tonyalaribe 773212e
trigger CI to check cumulus dependency
tonyalaribe a4be57f
trigger CI for dependent cumulus
tonyalaribe 111da0e
Merge branch 'master' into aa/safely-destroy-large-assets
tonyalaribe 9c06bb8
Update frame/assets/src/migration.rs
tonyalaribe 4eef069
move is-frozen to the assetStatus enum (#12547)
tonyalaribe 486814f
add pre and post migration hooks
tonyalaribe c185cb0
update do_transfer logic to add new assert for more correct error mes…
tonyalaribe 91095af
trigger CI
tonyalaribe 9ef3c2b
switch checking AssetStatus from checking Destroying state to checkin…
tonyalaribe 6165576
fix error type in tests from Frozen to AssetNotLive
tonyalaribe 7811b62
trigger CI
tonyalaribe cd3e28d
change ensure check for fn reducible_balance()
tonyalaribe aee596e
change the error type to Error:<T,I>::IncorrectStatus to be clearer
tonyalaribe 30814df
Trigger CI
tonyalaribe 9b653a5
Merge branch 'master' into aa/safely-destroy-large-assets
tonyalaribe 57fe747
Merge branch 'master' into aa/safely-destroy-large-assets
tonyalaribe 7ba1ef5
Merge branch 'master' into aa/safely-destroy-large-assets
tonyalaribe File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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); | ||
muharem marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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 | ||
tonyalaribe marked this conversation as resolved.
Show resolved
Hide resolved
|
||
); | ||
|
||
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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this could have it's own There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're right, but this logic is in a lot of places, and I don't want to add more things to this PR. I would rather as a different task |
||
|
||
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. | ||
muharem marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/// * `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 _ = | ||
ggwpez marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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(()) | ||
})?; | ||
|
||
for who in &dead_accounts { | ||
T::Freezer::died(id, &who); | ||
} | ||
|
||
let metadata = Metadata::<T, I>::take(&id); | ||
T::Currency::unreserve( | ||
&details.owner, | ||
details.deposit.saturating_add(metadata.deposit), | ||
); | ||
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) | ||
} | ||
|
||
for ((owner, _), approval) in Approvals::<T, I>::drain_prefix((&id,)) { | ||
/// Destroy approvals associated with a given asset up to the max (T::RemoveItemsLimit). | ||
/// | ||
/// Each call emits the `Event::DestroyedApprovals` event | ||
tonyalaribe marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/// 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::Destroyed { asset_id: id }); | ||
Self::deposit_event(Event::ApprovalsDestroyed { | ||
asset_id: id, | ||
approvals_destroyed: removed_approvals as u32, | ||
approvals_remaining: details.approvals as u32, | ||
}); | ||
Ok(()) | ||
})?; | ||
Ok(removed_approvals) | ||
} | ||
|
||
Ok(DestroyWitness { | ||
accounts: details.accounts, | ||
sufficients: details.sufficients, | ||
approvals: details.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 }); | ||
|
||
// Execute hooks outside of `mutate`. | ||
for who in dead_accounts { | ||
T::Freezer::died(id, &who); | ||
} | ||
Ok(result_witness) | ||
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| { | ||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It the
is_frozen
depends on the asset status, then this is superfluous.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think they depend on each other, since they have different meanings. But in a future PR, we might also make is_frozen an asset status.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's probably simpler to do it with current changes, the migration already in place.
If not, let's have an issue.