Allow Creation of Asset Accounts That Don't Exist Yet and Add Blocked Status (#13843)

* prevent frozen accounts from receiving assets

* refund deposits correctly

* plus refund_other

* add benchmarks

* start migration work

* docs

* add migration logic

* fix freeze_creating benchmark

* support instanced migrations

* review

* correct deposit refund

* only allow depositor, admin, or account origin to refund deposits

* make sure refund actually removes account

* do refund changes

* Asset's account deposit owner (#13874)

* assets deposit owner

* doc typo

* remove migration

* empty commit

* can transfer to frozen account

* remove allow_burn from refund_other

* storage version back to 1

* update doc

* fix benches

* update docs

* more tests

* Update frame/assets/src/types.rs

* refund updating the reason

* refactor

* separate refund and refund_foreign

* refunds, touch_other, tests

* fixes

* fmt

* ".git/.scripts/commands/bench/bench.sh" pallet dev pallet_assets

* tests: asserts asset account counts

* Account touch trait (#14063)

* assets touch trait

* docs

* move touch trait into support/traits

* permissionless flag for do_touch

* Apply suggestions from code review

Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>

* move trait to misc, drop option

* Apply suggestions from code review

Co-authored-by: Gavin Wood <gavin@parity.io>

* correct doc

* Update frame/assets/src/functions.rs

---------

Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>
Co-authored-by: Gavin Wood <gavin@parity.io>
Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>

* Block asset account (#14070)

* replace is_fronzen flag by status enum

* block asset account

* remove redundant brackets

* fix typo

* fmt

* Apply suggestions from code review

Co-authored-by: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com>

* rename permissionless to check_depositor

* doc fix

* use account id lookup instead account id

* add benchmark for touch_other

---------

Co-authored-by: muharem <ismailov.m.h@gmail.com>
Co-authored-by: command-bot <>
Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>
Co-authored-by: Gavin Wood <gavin@parity.io>
Co-authored-by: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com>
This commit is contained in:
joe petrowski
2023-05-08 12:17:35 +02:00
committed by GitHub
parent bd96f00146
commit be56fd3f53
11 changed files with 1040 additions and 219 deletions
+348 -1
View File
@@ -34,6 +34,12 @@ fn asset_ids() -> Vec<u32> {
s
}
/// returns tuple of asset's account and sufficient counts
fn asset_account_counts(asset_id: u32) -> (u32, u32) {
let asset = Asset::<Test>::get(asset_id).unwrap();
(asset.accounts, asset.sufficients)
}
#[test]
fn transfer_should_never_burn() {
new_test_ext().execute_with(|| {
@@ -154,9 +160,11 @@ fn refunding_asset_deposit_without_burn_should_work() {
assert_eq!(Assets::balance(0, 2), 100);
assert_eq!(Assets::balance(0, 1), 0);
assert_eq!(Balances::reserved_balance(&1), 10);
assert_eq!(asset_account_counts(0), (2, 0));
assert_ok!(Assets::refund(RuntimeOrigin::signed(1), 0, false));
assert_eq!(Balances::reserved_balance(&1), 0);
assert_eq!(Assets::balance(1, 0), 0);
assert_eq!(asset_account_counts(0), (1, 0));
});
}
@@ -176,6 +184,99 @@ fn refunding_calls_died_hook() {
});
}
#[test]
fn refunding_with_sufficient_existence_reason_should_fail() {
new_test_ext().execute_with(|| {
// create sufficient asset
assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1));
assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100));
// create an asset account with sufficient existence reason
// by transferring some sufficient assets
assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50));
assert_eq!(Assets::balance(0, 1), 50);
assert_eq!(Assets::balance(0, 2), 50);
assert_eq!(asset_account_counts(0), (2, 2));
// fails to refund
assert_noop!(Assets::refund(RuntimeOrigin::signed(2), 0, true), Error::<Test>::NoDeposit);
});
}
#[test]
fn refunding_with_deposit_from_should_fail() {
new_test_ext().execute_with(|| {
assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1));
Balances::make_free_balance_be(&1, 100);
// create asset account `2` with deposit from `1`
assert_ok!(Assets::touch_other(RuntimeOrigin::signed(1), 0, 2));
assert_eq!(Balances::reserved_balance(&1), 10);
// fails to refund
assert_noop!(Assets::refund(RuntimeOrigin::signed(2), 0, true), Error::<Test>::NoDeposit);
assert!(Account::<Test>::contains_key(0, &2));
});
}
#[test]
fn refunding_frozen_with_consumer_ref_works() {
new_test_ext().execute_with(|| {
// 1 will be an admin
// 2 will be a frozen account
Balances::make_free_balance_be(&1, 100);
Balances::make_free_balance_be(&2, 100);
// create non-sufficient asset
assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1));
assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100));
assert_eq!(System::consumers(&2), 0);
// create asset account `2` with a consumer reference by transferring
// non-sufficient funds into
assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50));
assert_eq!(System::consumers(&2), 1);
assert_eq!(Assets::balance(0, 1), 50);
assert_eq!(Assets::balance(0, 2), 50);
assert_eq!(asset_account_counts(0), (2, 0));
// freeze asset account `2` and asset `0`
assert_ok!(Assets::freeze(RuntimeOrigin::signed(1), 0, 2));
assert_ok!(Assets::freeze_asset(RuntimeOrigin::signed(1), 0));
// refund works
assert_ok!(Assets::refund(RuntimeOrigin::signed(2), 0, true));
assert!(!Account::<Test>::contains_key(0, &2));
assert_eq!(System::consumers(&2), 0);
assert_eq!(asset_account_counts(0), (1, 0));
});
}
#[test]
fn refunding_frozen_with_deposit_works() {
new_test_ext().execute_with(|| {
// 1 will be an asset admin
// 2 will be a frozen account
Balances::make_free_balance_be(&1, 100);
Balances::make_free_balance_be(&2, 100);
assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1));
assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100));
assert_eq!(System::consumers(&2), 0);
assert_ok!(Assets::touch(RuntimeOrigin::signed(2), 0));
// reserve deposit holds one consumer ref
assert_eq!(System::consumers(&2), 1);
assert_eq!(Balances::reserved_balance(&2), 10);
assert!(Account::<Test>::contains_key(0, &2));
// transfer some assets to `2`
assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50));
assert_eq!(System::consumers(&2), 1);
assert_eq!(Assets::balance(0, 1), 50);
assert_eq!(Assets::balance(0, 2), 50);
assert_eq!(asset_account_counts(0), (2, 0));
// ensure refundable even if asset account and asset is frozen
assert_ok!(Assets::freeze(RuntimeOrigin::signed(1), 0, 2));
assert_ok!(Assets::freeze_asset(RuntimeOrigin::signed(1), 0));
// success
assert_ok!(Assets::refund(RuntimeOrigin::signed(2), 0, true));
assert!(!Account::<Test>::contains_key(0, &2));
assert_eq!(Balances::reserved_balance(&2), 0);
assert_eq!(System::consumers(&2), 0);
assert_eq!(asset_account_counts(0), (1, 0));
});
}
#[test]
fn approval_lifecycle_works() {
new_test_ext().execute_with(|| {
@@ -637,6 +738,37 @@ fn approve_transfer_frozen_asset_should_not_work() {
});
}
#[test]
fn transferring_from_blocked_account_should_not_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, 100));
assert_eq!(Assets::balance(0, 1), 100);
assert_ok!(Assets::block(RuntimeOrigin::signed(1), 0, 1));
// behaves as frozen when transferring from blocked
assert_noop!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50), Error::<Test>::Frozen);
assert_ok!(Assets::thaw(RuntimeOrigin::signed(1), 0, 1));
assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50));
assert_ok!(Assets::transfer(RuntimeOrigin::signed(2), 0, 1, 50));
});
}
#[test]
fn transferring_to_blocked_account_should_not_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, 100));
assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 2, 100));
assert_eq!(Assets::balance(0, 1), 100);
assert_eq!(Assets::balance(0, 2), 100);
assert_ok!(Assets::block(RuntimeOrigin::signed(1), 0, 1));
assert_noop!(Assets::transfer(RuntimeOrigin::signed(2), 0, 1, 50), TokenError::Blocked);
assert_ok!(Assets::thaw(RuntimeOrigin::signed(1), 0, 1));
assert_ok!(Assets::transfer(RuntimeOrigin::signed(2), 0, 1, 50));
assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50));
});
}
#[test]
fn origin_guards_should_work() {
new_test_ext().execute_with(|| {
@@ -719,7 +851,7 @@ fn set_team_should_work() {
}
#[test]
fn transferring_to_frozen_account_should_work() {
fn transferring_from_frozen_account_should_not_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, 100));
@@ -727,11 +859,226 @@ fn transferring_to_frozen_account_should_work() {
assert_eq!(Assets::balance(0, 1), 100);
assert_eq!(Assets::balance(0, 2), 100);
assert_ok!(Assets::freeze(RuntimeOrigin::signed(1), 0, 2));
// can transfer to `2`
assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50));
// cannot transfer from `2`
assert_noop!(Assets::transfer(RuntimeOrigin::signed(2), 0, 1, 25), Error::<Test>::Frozen);
assert_eq!(Assets::balance(0, 1), 50);
assert_eq!(Assets::balance(0, 2), 150);
});
}
#[test]
fn touching_and_freezing_account_with_zero_asset_balance_should_work() {
new_test_ext().execute_with(|| {
// need some deposit for the touch
Balances::make_free_balance_be(&2, 100);
assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1));
assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100));
assert_eq!(Assets::balance(0, 1), 100);
assert_eq!(Assets::balance(0, 2), 0);
// cannot freeze an account that doesn't have an `Assets` entry
assert_noop!(Assets::freeze(RuntimeOrigin::signed(1), 0, 2), Error::<Test>::NoAccount);
assert_ok!(Assets::touch(RuntimeOrigin::signed(2), 0));
// now it can be frozen
assert_ok!(Assets::freeze(RuntimeOrigin::signed(1), 0, 2));
// can transfer to `2` even though its frozen
assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50));
// cannot transfer from `2`
assert_noop!(Assets::transfer(RuntimeOrigin::signed(2), 0, 1, 25), Error::<Test>::Frozen);
assert_eq!(Assets::balance(0, 1), 50);
assert_eq!(Assets::balance(0, 2), 50);
});
}
#[test]
fn touch_other_works() {
new_test_ext().execute_with(|| {
// 1 will be admin
// 2 will be freezer
// 4 will be an account attempting to execute `touch_other`
Balances::make_free_balance_be(&1, 100);
Balances::make_free_balance_be(&2, 100);
Balances::make_free_balance_be(&4, 100);
assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1));
assert_ok!(Assets::set_team(RuntimeOrigin::signed(1), 0, 1, 1, 2));
assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100));
assert_eq!(Assets::balance(0, 1), 100);
// account `3` does not exist
assert!(!Account::<Test>::contains_key(0, &3));
// creation of asset account `3` by account `4` fails
assert_noop!(
Assets::touch_other(RuntimeOrigin::signed(4), 0, 3),
Error::<Test>::NoPermission
);
// creation of asset account `3` by admin `1` works
assert!(!Account::<Test>::contains_key(0, &3));
assert_ok!(Assets::touch_other(RuntimeOrigin::signed(1), 0, 3));
assert!(Account::<Test>::contains_key(0, &3));
// creation of asset account `4` by freezer `2` works
assert!(!Account::<Test>::contains_key(0, &4));
assert_ok!(Assets::touch_other(RuntimeOrigin::signed(2), 0, 4));
assert!(Account::<Test>::contains_key(0, &4));
});
}
#[test]
fn touch_other_and_freeze_works() {
new_test_ext().execute_with(|| {
Balances::make_free_balance_be(&1, 100);
assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1));
assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100));
assert_eq!(Assets::balance(0, 1), 100);
// account `2` does not exist
assert!(!Account::<Test>::contains_key(0, &2));
// create account `2` with touch_other
assert_ok!(Assets::touch_other(RuntimeOrigin::signed(1), 0, 2));
assert!(Account::<Test>::contains_key(0, &2));
// now it can be frozen
assert_ok!(Assets::freeze(RuntimeOrigin::signed(1), 0, 2));
// can transfer to `2` even though its frozen
assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50));
// cannot transfer from `2`
assert_noop!(Assets::transfer(RuntimeOrigin::signed(2), 0, 1, 25), Error::<Test>::Frozen);
assert_eq!(Assets::balance(0, 1), 50);
assert_eq!(Assets::balance(0, 2), 50);
});
}
#[test]
fn account_with_deposit_not_destroyed() {
new_test_ext().execute_with(|| {
// 1 will be the asset admin
// 2 will exist without balance but with deposit
Balances::make_free_balance_be(&1, 100);
Balances::make_free_balance_be(&2, 100);
assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1));
assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100));
assert_eq!(Assets::balance(0, 1), 100);
assert_eq!(Assets::balance(0, 2), 0);
// case 1; account `2` not destroyed with a holder's deposit
assert_ok!(Assets::touch(RuntimeOrigin::signed(2), 0));
assert_eq!(Balances::reserved_balance(&2), 10);
assert!(Account::<Test>::contains_key(0, &2));
assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50));
assert_ok!(Assets::transfer(RuntimeOrigin::signed(2), 0, 1, 50));
assert_eq!(Assets::balance(0, 2), 0);
assert!(Account::<Test>::contains_key(0, &2));
// destroy account `2`
assert_ok!(Assets::refund(RuntimeOrigin::signed(2), 0, false));
assert!(!Account::<Test>::contains_key(0, &2));
// case 2; account `2` not destroyed with a deposit from `1`
assert_ok!(Assets::touch_other(RuntimeOrigin::signed(1), 0, 2));
assert_eq!(Balances::reserved_balance(&1), 10);
assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50));
assert_ok!(Assets::transfer(RuntimeOrigin::signed(2), 0, 1, 50));
assert!(Account::<Test>::contains_key(0, &2));
});
}
#[test]
fn refund_other_should_fails() {
new_test_ext().execute_with(|| {
// 1 will be the asset admin
// 2 will be the asset freezer
// 3 will be created with deposit of 2
Balances::make_free_balance_be(&1, 100);
Balances::make_free_balance_be(&2, 100);
Balances::make_free_balance_be(&3, 0);
assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1));
assert_ok!(Assets::set_team(RuntimeOrigin::signed(1), 0, 1, 1, 2));
assert!(!Account::<Test>::contains_key(0, &3));
// create asset account `3` with a deposit from freezer `2`
assert_ok!(Assets::touch_other(RuntimeOrigin::signed(2), 0, 3));
assert_eq!(Balances::reserved_balance(&2), 10);
// fail case; non-existing asset account `10`
assert_noop!(
Assets::refund_other(RuntimeOrigin::signed(2), 0, 10),
Error::<Test>::NoDeposit
);
// fail case; non-existing asset `3`
assert_noop!(
Assets::refund_other(RuntimeOrigin::signed(2), 1, 3),
Error::<Test>::NoDeposit
);
// fail case; no `DepositFrom` for asset account `1`
assert_noop!(
Assets::refund_other(RuntimeOrigin::signed(2), 0, 1),
Error::<Test>::NoDeposit
);
// fail case; asset `0` is frozen
assert_ok!(Assets::freeze_asset(RuntimeOrigin::signed(2), 0));
assert_noop!(
Assets::refund_other(RuntimeOrigin::signed(2), 0, 3),
Error::<Test>::AssetNotLive
);
assert_ok!(Assets::thaw_asset(RuntimeOrigin::signed(1), 0));
// fail case; asset `1` is being destroyed
assert_ok!(Assets::force_create(RuntimeOrigin::root(), 10, 1, true, 1));
assert_ok!(Assets::touch_other(RuntimeOrigin::signed(1), 10, 3));
assert_ok!(Assets::start_destroy(RuntimeOrigin::signed(1), 10));
assert_noop!(
Assets::refund_other(RuntimeOrigin::signed(2), 10, 3),
Error::<Test>::AssetNotLive
);
assert_ok!(Assets::destroy_accounts(RuntimeOrigin::signed(1), 10));
assert_ok!(Assets::finish_destroy(RuntimeOrigin::signed(1), 10));
// fail case; account is frozen
assert_ok!(Assets::freeze(RuntimeOrigin::signed(2), 0, 3));
assert_noop!(Assets::refund_other(RuntimeOrigin::signed(2), 0, 3), Error::<Test>::Frozen);
assert_ok!(Assets::thaw(RuntimeOrigin::signed(1), 0, 3));
// fail case; not a freezer or an admin
assert_noop!(
Assets::refund_other(RuntimeOrigin::signed(4), 0, 3),
Error::<Test>::NoPermission
);
// fail case; would burn
assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 3, 100));
assert_noop!(
Assets::refund_other(RuntimeOrigin::signed(1), 0, 3),
Error::<Test>::WouldBurn
);
assert_ok!(Assets::burn(RuntimeOrigin::signed(1), 0, 3, 100));
})
}
#[test]
fn refund_other_works() {
new_test_ext().execute_with(|| {
// 1 will be the asset admin
// 2 will be the asset freezer
// 3 will be created with deposit of 2
Balances::make_free_balance_be(&1, 100);
Balances::make_free_balance_be(&2, 100);
assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1));
assert_ok!(Assets::set_team(RuntimeOrigin::signed(1), 0, 1, 1, 2));
assert!(!Account::<Test>::contains_key(0, &3));
assert_eq!(asset_account_counts(0), (0, 0));
// success case; freezer is depositor
assert_ok!(Assets::touch_other(RuntimeOrigin::signed(2), 0, 3));
assert_eq!(Balances::reserved_balance(&2), 10);
assert_eq!(asset_account_counts(0), (1, 0));
assert_ok!(Assets::refund_other(RuntimeOrigin::signed(2), 0, 3));
assert_eq!(Balances::reserved_balance(&2), 0);
assert!(!Account::<Test>::contains_key(0, &3));
assert_eq!(asset_account_counts(0), (0, 0));
// success case; admin is depositor
assert_ok!(Assets::touch_other(RuntimeOrigin::signed(1), 0, 3));
assert_eq!(Balances::reserved_balance(&1), 10);
assert_eq!(asset_account_counts(0), (1, 0));
assert_ok!(Assets::refund_other(RuntimeOrigin::signed(1), 0, 3));
assert_eq!(Balances::reserved_balance(&1), 0);
assert!(!Account::<Test>::contains_key(0, &3));
assert_eq!(asset_account_counts(0), (0, 0));
})
}
#[test]
fn transferring_amount_more_than_available_balance_should_not_work() {
new_test_ext().execute_with(|| {