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:
Anthony Alaribe
2022-11-15 11:59:47 +02:00
committed by GitHub
parent 8fef631a95
commit 679d2dcd25
11 changed files with 630 additions and 233 deletions
+101 -34
View File
@@ -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(|| {