referrerpolicy=no-referrer-when-downgrade

pallet_assets_freezer/
lib.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: MIT-0
5
6// Permission is hereby granted, free of charge, to any person obtaining a copy of
7// this software and associated documentation files (the "Software"), to deal in
8// the Software without restriction, including without limitation the rights to
9// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
10// of the Software, and to permit persons to whom the Software is furnished to do
11// so, subject to the following conditions:
12
13// The above copyright notice and this permission notice shall be included in all
14// copies or substantial portions of the Software.
15
16// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22// SOFTWARE.
23
24//! # Assets Freezer Pallet
25//!
26//! A pallet capable of freezing fungibles from `pallet-assets`. This is an extension of
27//! `pallet-assets`, wrapping [`fungibles::Inspect`](`Inspect`).
28//! It implements both
29//! [`fungibles::freeze::Inspect`](InspectFreeze) and
30//! [`fungibles::freeze::Mutate`](MutateFreeze). The complexity
31//! of the operations is `O(n)`. where `n` is the variant count of `RuntimeFreezeReason`.
32//!
33//! ## Pallet API
34//!
35//! See the [`pallet`] module for more information about the interfaces this pallet exposes,
36//! including its configuration trait, dispatchables, storage items, events and errors.
37//!
38//! ## Overview
39//!
40//! This pallet provides the following functionality:
41//!
42//! - Pallet hooks allowing [`pallet-assets`] to know the frozen balance for an account on a given
43//!   asset (see [`pallet_assets::FrozenBalance`]).
44//! - An implementation of [`fungibles::freeze::Inspect`](InspectFreeze) and
45//!   [`fungibles::freeze::Mutate`](MutateFreeze), allowing other pallets to manage freezes for the
46//!   `pallet-assets` assets.
47
48#![cfg_attr(not(feature = "std"), no_std)]
49
50use frame::{
51	prelude::*,
52	traits::{
53		fungibles::{Inspect, InspectFreeze, MutateFreeze},
54		tokens::{
55			DepositConsequence, Fortitude, IdAmount, Preservation, Provenance, WithdrawConsequence,
56		},
57	},
58};
59
60pub use pallet::*;
61
62#[cfg(feature = "try-runtime")]
63use frame::try_runtime::TryRuntimeError;
64
65#[cfg(test)]
66mod mock;
67#[cfg(test)]
68mod tests;
69
70mod impls;
71
72#[frame::pallet]
73pub mod pallet {
74	use super::*;
75
76	#[pallet::config(with_default)]
77	pub trait Config<I: 'static = ()>: frame_system::Config + pallet_assets::Config<I> {
78		/// The overarching freeze reason.
79		#[pallet::no_default_bounds]
80		type RuntimeFreezeReason: Parameter + Member + MaxEncodedLen + Copy + VariantCount;
81
82		/// The overarching event type.
83		#[pallet::no_default_bounds]
84		#[allow(deprecated)]
85		type RuntimeEvent: From<Event<Self, I>>
86			+ IsType<<Self as frame_system::Config>::RuntimeEvent>;
87	}
88
89	#[pallet::error]
90	pub enum Error<T, I = ()> {
91		/// Number of freezes on an account would exceed `MaxFreezes`.
92		TooManyFreezes,
93	}
94
95	#[pallet::pallet]
96	pub struct Pallet<T, I = ()>(_);
97
98	#[pallet::event]
99	#[pallet::generate_deposit(pub(super) fn deposit_event)]
100	pub enum Event<T: Config<I>, I: 'static = ()> {
101		// `who`s frozen balance was increased by `amount`.
102		Frozen { who: T::AccountId, asset_id: T::AssetId, amount: T::Balance },
103		// `who`s frozen balance was decreased by `amount`.
104		Thawed { who: T::AccountId, asset_id: T::AssetId, amount: T::Balance },
105	}
106
107	/// A map that stores freezes applied on an account for a given AssetId.
108	#[pallet::storage]
109	pub type Freezes<T: Config<I>, I: 'static = ()> = StorageDoubleMap<
110		_,
111		Blake2_128Concat,
112		T::AssetId,
113		Blake2_128Concat,
114		T::AccountId,
115		BoundedVec<
116			IdAmount<T::RuntimeFreezeReason, T::Balance>,
117			VariantCountOf<T::RuntimeFreezeReason>,
118		>,
119		ValueQuery,
120	>;
121
122	/// A map that stores the current total frozen balance for every account on a given AssetId.
123	#[pallet::storage]
124	pub type FrozenBalances<T: Config<I>, I: 'static = ()> = StorageDoubleMap<
125		_,
126		Blake2_128Concat,
127		T::AssetId,
128		Blake2_128Concat,
129		T::AccountId,
130		T::Balance,
131	>;
132
133	#[pallet::hooks]
134	impl<T: Config<I>, I: 'static> Hooks<BlockNumberFor<T>> for Pallet<T, I> {
135		#[cfg(feature = "try-runtime")]
136		fn try_state(_: BlockNumberFor<T>) -> Result<(), TryRuntimeError> {
137			Self::do_try_state()
138		}
139	}
140}
141
142impl<T: Config<I>, I: 'static> Pallet<T, I> {
143	fn update_freezes(
144		asset: T::AssetId,
145		who: &T::AccountId,
146		freezes: BoundedSlice<
147			IdAmount<T::RuntimeFreezeReason, T::Balance>,
148			VariantCountOf<T::RuntimeFreezeReason>,
149		>,
150	) -> DispatchResult {
151		let prev_frozen = FrozenBalances::<T, I>::get(asset.clone(), who).unwrap_or_default();
152		let after_frozen = freezes.into_iter().map(|f| f.amount).max().unwrap_or_else(Zero::zero);
153		FrozenBalances::<T, I>::set(asset.clone(), who, Some(after_frozen));
154		if freezes.is_empty() {
155			Freezes::<T, I>::remove(asset.clone(), who);
156			FrozenBalances::<T, I>::remove(asset.clone(), who);
157		} else {
158			Freezes::<T, I>::insert(asset.clone(), who, freezes);
159		}
160		if prev_frozen > after_frozen {
161			let amount = prev_frozen.saturating_sub(after_frozen);
162			Self::deposit_event(Event::Thawed { asset_id: asset, who: who.clone(), amount });
163		} else if after_frozen > prev_frozen {
164			let amount = after_frozen.saturating_sub(prev_frozen);
165			Self::deposit_event(Event::Frozen { asset_id: asset, who: who.clone(), amount });
166		}
167		Ok(())
168	}
169
170	#[cfg(feature = "try-runtime")]
171	fn do_try_state() -> Result<(), TryRuntimeError> {
172		for (asset, who, _) in FrozenBalances::<T, I>::iter() {
173			let max_frozen_amount =
174				Freezes::<T, I>::get(asset.clone(), who.clone()).iter().map(|l| l.amount).max();
175
176			ensure!(
177				FrozenBalances::<T, I>::get(asset, who) == max_frozen_amount,
178				"The `FrozenAmount` is not equal to the maximum amount in `Freezes` for (`asset`, `who`)"
179			);
180		}
181
182		Ok(())
183	}
184}