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
+85 -26
View File
@@ -66,11 +66,15 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
pub(super) fn new_account(
who: &T::AccountId,
d: &mut AssetDetails<T::Balance, T::AccountId, DepositBalanceOf<T, I>>,
maybe_deposit: Option<DepositBalanceOf<T, I>>,
) -> Result<ExistenceReason<DepositBalanceOf<T, I>>, DispatchError> {
maybe_deposit: Option<(&T::AccountId, DepositBalanceOf<T, I>)>,
) -> Result<ExistenceReasonOf<T, I>, DispatchError> {
let accounts = d.accounts.checked_add(1).ok_or(ArithmeticError::Overflow)?;
let reason = if let Some(deposit) = maybe_deposit {
ExistenceReason::DepositHeld(deposit)
let reason = if let Some((depositor, deposit)) = maybe_deposit {
if depositor == who {
ExistenceReason::DepositHeld(deposit)
} else {
ExistenceReason::DepositFrom(depositor.clone(), deposit)
}
} else if d.is_sufficient {
frame_system::Pallet::<T>::inc_sufficients(who);
d.sufficients += 1;
@@ -93,18 +97,19 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
pub(super) fn dead_account(
who: &T::AccountId,
d: &mut AssetDetails<T::Balance, T::AccountId, DepositBalanceOf<T, I>>,
reason: &ExistenceReason<DepositBalanceOf<T, I>>,
reason: &ExistenceReasonOf<T, I>,
force: bool,
) -> DeadConsequence {
use ExistenceReason::*;
match *reason {
ExistenceReason::Consumer => frame_system::Pallet::<T>::dec_consumers(who),
ExistenceReason::Sufficient => {
Consumer => frame_system::Pallet::<T>::dec_consumers(who),
Sufficient => {
d.sufficients = d.sufficients.saturating_sub(1);
frame_system::Pallet::<T>::dec_sufficients(who);
},
ExistenceReason::DepositRefunded => {},
ExistenceReason::DepositHeld(_) if !force => return Keep,
ExistenceReason::DepositHeld(_) => {},
DepositRefunded => {},
DepositHeld(_) | DepositFrom(..) if !force => return Keep,
DepositHeld(_) | DepositFrom(..) => {},
}
d.accounts = d.accounts.saturating_sub(1);
Remove
@@ -130,8 +135,11 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
if increase_supply && details.supply.checked_add(&amount).is_none() {
return DepositConsequence::Overflow
}
if let Some(balance) = Self::maybe_balance(id, who) {
if balance.checked_add(&amount).is_none() {
if let Some(account) = Account::<T, I>::get(id, who) {
if account.status.is_blocked() {
return DepositConsequence::Blocked
}
if account.balance.checked_add(&amount).is_none() {
return DepositConsequence::Overflow
}
} else {
@@ -174,7 +182,7 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
Some(a) => a,
None => return BalanceLow,
};
if account.is_frozen {
if account.status.is_frozen() {
return Frozen
}
if let Some(rest) = account.balance.checked_sub(&amount) {
@@ -215,7 +223,7 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
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);
ensure!(!account.status.is_frozen(), Error::<T, I>::Frozen);
let amount = if let Some(frozen) = T::Freezer::frozen_balance(id, who) {
// Frozen balance: account CANNOT be deleted
@@ -302,43 +310,63 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
Ok((credit, maybe_burn))
}
/// Creates a account for `who` to hold asset `id` with a zero balance and takes a deposit.
pub(super) fn do_touch(id: T::AssetId, who: T::AccountId) -> DispatchResult {
/// Creates an account for `who` to hold asset `id` with a zero balance and takes a deposit.
///
/// When `check_depositor` is set to true, the depositor must be either the asset's Admin or
/// Freezer, otherwise the depositor can be any account.
pub(super) fn do_touch(
id: T::AssetId,
who: T::AccountId,
depositor: T::AccountId,
check_depositor: bool,
) -> DispatchResult {
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)?;
ensure!(
!check_depositor || &depositor == &details.admin || &depositor == &details.freezer,
Error::<T, I>::NoPermission
);
let reason = Self::new_account(&who, &mut details, Some((&depositor, deposit)))?;
T::Currency::reserve(&depositor, deposit)?;
Asset::<T, I>::insert(&id, details);
Account::<T, I>::insert(
id,
&who,
AssetAccountOf::<T, I> {
balance: Zero::zero(),
is_frozen: false,
status: AccountStatus::Liquid,
reason,
extra: T::Extra::default(),
},
);
Self::deposit_event(Event::Touched { asset_id: id, who, depositor });
Ok(())
}
/// Returns a deposit, destroying an asset-account.
/// Returns a deposit or a consumer reference, destroying an asset-account.
/// Non-zero balance accounts refunded and destroyed only if `allow_burn` is true.
pub(super) fn do_refund(id: T::AssetId, who: T::AccountId, allow_burn: bool) -> DispatchResult {
use AssetStatus::*;
use ExistenceReason::*;
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)?;
ensure!(matches!(account.reason, Consumer | DepositHeld(..)), 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!(matches!(details.status, Live | Frozen), Error::<T, I>::IncorrectStatus);
ensure!(account.balance.is_zero() || allow_burn, Error::<T, I>::WouldBurn);
ensure!(!account.is_frozen, Error::<T, I>::Frozen);
T::Currency::unreserve(&who, deposit);
if let Some(deposit) = account.reason.take_deposit() {
T::Currency::unreserve(&who, deposit);
}
if let Remove = Self::dead_account(&who, &mut details, &account.reason, false) {
Account::<T, I>::remove(id, &who);
} else {
debug_assert!(false, "refund did not result in dead account?!");
// deposit may have been refunded, need to update `Account`
Account::<T, I>::insert(id, &who, account);
return Ok(())
}
Asset::<T, I>::insert(&id, details);
// Executing a hook here is safe, since it is not in a `mutate`.
@@ -346,6 +374,37 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
Ok(())
}
/// Returns a `DepositFrom` of an account only if balance is zero.
pub(super) fn do_refund_other(
id: T::AssetId,
who: &T::AccountId,
caller: &T::AccountId,
) -> DispatchResult {
let mut account = Account::<T, I>::get(id, &who).ok_or(Error::<T, I>::NoDeposit)?;
let (depositor, deposit) =
account.reason.take_deposit_from().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.status.is_frozen(), Error::<T, I>::Frozen);
ensure!(caller == &depositor || caller == &details.admin, Error::<T, I>::NoPermission);
ensure!(account.balance.is_zero(), Error::<T, I>::WouldBurn);
T::Currency::unreserve(&depositor, deposit);
if let Remove = Self::dead_account(&who, &mut details, &account.reason, false) {
Account::<T, I>::remove(id, &who);
} else {
debug_assert!(false, "refund did not result in dead account?!");
// deposit may have been refunded, need to update `Account`
Account::<T, I>::insert(id, &who, account);
return Ok(())
}
Asset::<T, I>::insert(&id, details);
// Executing a hook here is safe, since it is not in a `mutate`.
T::Freezer::died(id, &who);
return Ok(())
}
/// Increases the asset `id` balance of `beneficiary` by `amount`.
///
/// This alters the registered supply of the asset and emits an event.
@@ -408,7 +467,7 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
*maybe_account = Some(AssetAccountOf::<T, I> {
balance: amount,
reason: Self::new_account(beneficiary, details, None)?,
is_frozen: false,
status: AccountStatus::Liquid,
extra: T::Extra::default(),
});
},
@@ -602,7 +661,7 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
maybe_account @ None => {
*maybe_account = Some(AssetAccountOf::<T, I> {
balance: credit,
is_frozen: false,
status: AccountStatus::Liquid,
reason: Self::new_account(dest, details, None)?,
extra: T::Extra::default(),
});