Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Commit

Permalink
Block asset account (#14070)
Browse files Browse the repository at this point in the history
* replace is_fronzen flag by status enum

* block asset account

* remove redundant brackets

* fix typo
  • Loading branch information
muharem authored May 4, 2023
1 parent 239af28 commit b9d0ef2
Show file tree
Hide file tree
Showing 8 changed files with 158 additions and 14 deletions.
7 changes: 7 additions & 0 deletions frame/assets/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -532,5 +532,12 @@ benchmarks_instance_pallet! {
assert!(T::Currency::reserved_balance(&asset_owner).is_zero());
}

block {
let (asset_id, caller, caller_lookup) = create_default_minted_asset::<T, I>(true, 100u32.into());
}: _(SystemOrigin::Signed(caller.clone()), asset_id, caller_lookup)
verify {
assert_last_event::<T, I>(Event::Blocked { asset_id: asset_id.into(), who: caller }.into());
}

impl_benchmark_test_suite!(Assets, crate::mock::new_test_ext(), crate::mock::Test)
}
19 changes: 11 additions & 8 deletions frame/assets/src/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,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 {
Expand Down Expand Up @@ -179,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) {
Expand Down Expand Up @@ -220,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
Expand Down Expand Up @@ -333,7 +336,7 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
&who,
AssetAccountOf::<T, I> {
balance: Zero::zero(),
is_frozen: false,
status: AccountStatus::Liquid,
reason,
extra: T::Extra::default(),
},
Expand Down Expand Up @@ -382,7 +385,7 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
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.is_frozen, Error::<T, I>::Frozen);
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);

Expand Down Expand Up @@ -464,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(),
});
},
Expand Down Expand Up @@ -658,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(),
});
Expand Down
51 changes: 47 additions & 4 deletions frame/assets/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,14 +102,16 @@
//! * `burn`: Decreases the asset balance of an account; called by the asset class's Admin.
//! * `force_transfer`: Transfers between arbitrary accounts; called by the asset class's Admin.
//! * `freeze`: Disallows further `transfer`s from an account; called by the asset class's Freezer.
//! * `thaw`: Allows further `transfer`s from an account; called by the asset class's Admin.
//! * `thaw`: Allows further `transfer`s to and from an account; called by the asset class's Admin.
//! * `transfer_ownership`: Changes an asset class's Owner; called by the asset class's Owner.
//! * `set_team`: Changes an asset class's Admin, Freezer and Issuer; called by the asset class's
//! Owner.
//! * `set_metadata`: Set the metadata of an asset class; called by the asset class's Owner.
//! * `clear_metadata`: Remove the metadata of an asset class; called by the asset class's Owner.
//! * `touch_other`: Create an asset account for specified account. Caller must place a deposit;
//! called by the asset class's Freezer or Admin.
//! * `block`: Disallows further `transfer`s to and from an account; called by the asset class's
//! Freezer.
//!
//! Please refer to the [`Call`] enum and its associated variants for documentation on each
//! function.
Expand Down Expand Up @@ -527,6 +529,8 @@ pub mod pallet {
AssetMinBalanceChanged { asset_id: T::AssetId, new_min_balance: T::Balance },
/// Some account `who` was created with a deposit from `depositor`.
Touched { asset_id: T::AssetId, who: T::AccountId, depositor: T::AccountId },
/// Some account `who` was blocked.
Blocked { asset_id: T::AssetId, who: T::AccountId },
}

#[pallet::error]
Expand Down Expand Up @@ -949,15 +953,16 @@ pub mod pallet {
let who = T::Lookup::lookup(who)?;

Account::<T, I>::try_mutate(id, &who, |maybe_account| -> DispatchResult {
maybe_account.as_mut().ok_or(Error::<T, I>::NoAccount)?.is_frozen = true;
maybe_account.as_mut().ok_or(Error::<T, I>::NoAccount)?.status =
AccountStatus::Frozen;
Ok(())
})?;

Self::deposit_event(Event::<T, I>::Frozen { asset_id: id, who });
Ok(())
}

/// Allow unprivileged transfers from an account again.
/// Allow unprivileged transfers to and from an account again.
///
/// Origin must be Signed and the sender should be the Admin of the asset `id`.
///
Expand Down Expand Up @@ -985,7 +990,8 @@ pub mod pallet {
let who = T::Lookup::lookup(who)?;

Account::<T, I>::try_mutate(id, &who, |maybe_account| -> DispatchResult {
maybe_account.as_mut().ok_or(Error::<T, I>::NoAccount)?.is_frozen = false;
maybe_account.as_mut().ok_or(Error::<T, I>::NoAccount)?.status =
AccountStatus::Liquid;
Ok(())
})?;

Expand Down Expand Up @@ -1599,6 +1605,43 @@ pub mod pallet {
let id: T::AssetId = id.into();
Self::do_refund_other(id, &who, &origin)
}

/// Disallow further unprivileged transfers of an asset `id` to and from an account `who`.
///
/// Origin must be Signed and the sender should be the Freezer of the asset `id`.
///
/// - `id`: The identifier of the account's asset.
/// - `who`: The account to be unblocked.
///
/// Emits `Blocked`.
///
/// Weight: `O(1)`
#[pallet::call_index(31)]
pub fn block(
origin: OriginFor<T>,
id: T::AssetIdParameter,
who: AccountIdLookupOf<T>,
) -> DispatchResult {
let origin = ensure_signed(origin)?;
let id: T::AssetId = id.into();

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)?;

Account::<T, I>::try_mutate(id, &who, |maybe_account| -> DispatchResult {
maybe_account.as_mut().ok_or(Error::<T, I>::NoAccount)?.status =
AccountStatus::Blocked;
Ok(())
})?;

Self::deposit_event(Event::<T, I>::Blocked { asset_id: id, who });
Ok(())
}
}

/// Implements [AccountTouch] trait.
Expand Down
31 changes: 31 additions & 0 deletions frame/assets/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -738,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(|| {
Expand Down
31 changes: 29 additions & 2 deletions frame/assets/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,12 +145,39 @@ where
}
}

#[test]
fn ensure_bool_decodes_to_liquid_or_frozen() {
assert_eq!(false.encode(), AccountStatus::Liquid.encode());
assert_eq!(true.encode(), AccountStatus::Frozen.encode());
}

/// The status of an asset account.
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)]
pub enum AccountStatus {
/// Asset account can receive and transfer the assets.
Liquid,
/// Asset account cannot transfer the assets.
Frozen,
/// Asset account cannot receive and transfer the assets.
Blocked,
}
impl AccountStatus {
/// Returns `true` if frozen or blocked.
pub(crate) fn is_frozen(&self) -> bool {
matches!(self, AccountStatus::Frozen | AccountStatus::Blocked)
}
/// Returns `true` if blocked.
pub(crate) fn is_blocked(&self) -> bool {
matches!(self, AccountStatus::Blocked)
}
}

#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)]
pub struct AssetAccount<Balance, DepositBalance, Extra, AccountId> {
/// The balance.
pub(super) balance: Balance,
/// Whether the account is frozen.
pub(super) is_frozen: bool,
/// The status of the account.
pub(super) status: AccountStatus,
/// The reason for the existence of the account.
pub(super) reason: ExistenceReason<DepositBalance, AccountId>,
/// Additional "sidecar" data, in case some other pallet wants to use this storage item.
Expand Down
27 changes: 27 additions & 0 deletions frame/assets/src/weights.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions frame/support/src/traits/tokens/misc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@ pub enum DepositConsequence {
Overflow,
/// Account continued in existence.
Success,
/// Account cannot receive the assets.
Blocked,
}

impl DepositConsequence {
Expand All @@ -152,6 +154,7 @@ impl DepositConsequence {
CannotCreate => TokenError::CannotCreate.into(),
UnknownAsset => TokenError::UnknownAsset.into(),
Overflow => ArithmeticError::Overflow.into(),
Blocked => TokenError::Blocked.into(),
Success => return Ok(()),
})
}
Expand Down
3 changes: 3 additions & 0 deletions primitives/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,8 @@ pub enum TokenError {
CannotCreateHold,
/// Withdrawal would cause unwanted loss of account.
NotExpendable,
/// Account cannot receive the assets.
Blocked,
}

impl From<TokenError> for &'static str {
Expand All @@ -641,6 +643,7 @@ impl From<TokenError> for &'static str {
TokenError::CannotCreateHold =>
"Account cannot be created for recording amount on hold",
TokenError::NotExpendable => "Account that is desired to remain would die",
TokenError::Blocked => "Account cannot receive the assets",
}
}
}
Expand Down

0 comments on commit b9d0ef2

Please sign in to comment.