referrerpolicy=no-referrer-when-downgrade

pallet_assets_holder/
lib.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! # Assets Holder Pallet
19//!
20//! A pallet capable of holding fungibles from `pallet-assets`. This is an extension of
21//! `pallet-assets`, wrapping [`fungibles::Inspect`](`frame_support::traits::fungibles::Inspect`).
22//! It implements both
23//! [`fungibles::hold::Inspect`](frame_support::traits::fungibles::hold::Inspect),
24//! [`fungibles::hold::Mutate`](frame_support::traits::fungibles::hold::Mutate), and especially
25//! [`fungibles::hold::Unbalanced`](frame_support::traits::fungibles::hold::Unbalanced). The
26//! complexity of the operations is `O(1)`.
27//!
28//! ## Pallet API
29//!
30//! See the [`pallet`] module for more information about the interfaces this pallet exposes,
31//! including its configuration trait, dispatchables, storage items, events and errors.
32//!
33//! ## Overview
34//!
35//! This pallet provides the following functionality:
36//!
37//! - Pallet hooks allowing [`pallet-assets`] to know the balance on hold for an account on a given
38//!   asset (see [`pallet_assets::BalanceOnHold`]).
39//! - An implementation of
40//!   [`fungibles::hold::Inspect`](frame_support::traits::fungibles::hold::Inspect),
41//!   [`fungibles::hold::Mutate`](frame_support::traits::fungibles::hold::Mutate) and
42//!   [`fungibles::hold::Unbalanced`](frame_support::traits::fungibles::hold::Unbalanced), allowing
43//!   other pallets to manage holds for the `pallet-assets` assets.
44
45#![cfg_attr(not(feature = "std"), no_std)]
46
47use frame_support::{
48	pallet_prelude::*,
49	traits::{tokens::IdAmount, VariantCount, VariantCountOf},
50	BoundedVec,
51};
52use frame_system::pallet_prelude::BlockNumberFor;
53
54pub use pallet::*;
55
56#[cfg(test)]
57mod mock;
58#[cfg(test)]
59mod tests;
60
61mod impl_fungibles;
62
63#[frame_support::pallet]
64pub mod pallet {
65	use super::*;
66
67	#[pallet::config(with_default)]
68	pub trait Config<I: 'static = ()>:
69		frame_system::Config + pallet_assets::Config<I, Holder = Pallet<Self, I>>
70	{
71		/// The overarching freeze reason.
72		#[pallet::no_default_bounds]
73		type RuntimeHoldReason: Parameter + Member + MaxEncodedLen + Copy + VariantCount;
74
75		/// The overarching event type.
76		#[pallet::no_default_bounds]
77		#[allow(deprecated)]
78		type RuntimeEvent: From<Event<Self, I>>
79			+ IsType<<Self as frame_system::Config>::RuntimeEvent>;
80	}
81
82	#[pallet::error]
83	pub enum Error<T, I = ()> {
84		/// Number of holds on an account would exceed the count of `RuntimeHoldReason`.
85		TooManyHolds,
86	}
87
88	#[pallet::pallet]
89	pub struct Pallet<T, I = ()>(_);
90
91	#[pallet::event]
92	#[pallet::generate_deposit(pub(super) fn deposit_event)]
93	pub enum Event<T: Config<I>, I: 'static = ()> {
94		/// `who`s balance on hold was increased by `amount`.
95		Held {
96			who: T::AccountId,
97			asset_id: T::AssetId,
98			reason: T::RuntimeHoldReason,
99			amount: T::Balance,
100		},
101		/// `who`s balance on hold was decreased by `amount`.
102		Released {
103			who: T::AccountId,
104			asset_id: T::AssetId,
105			reason: T::RuntimeHoldReason,
106			amount: T::Balance,
107		},
108		/// `who`s balance on hold was burned by `amount`.
109		Burned {
110			who: T::AccountId,
111			asset_id: T::AssetId,
112			reason: T::RuntimeHoldReason,
113			amount: T::Balance,
114		},
115	}
116
117	/// A map that stores holds applied on an account for a given AssetId.
118	#[pallet::storage]
119	pub(super) type Holds<T: Config<I>, I: 'static = ()> = StorageDoubleMap<
120		_,
121		Blake2_128Concat,
122		T::AssetId,
123		Blake2_128Concat,
124		T::AccountId,
125		BoundedVec<
126			IdAmount<T::RuntimeHoldReason, T::Balance>,
127			VariantCountOf<T::RuntimeHoldReason>,
128		>,
129		ValueQuery,
130	>;
131
132	/// A map that stores the current total balance on hold for every account on a given AssetId.
133	#[pallet::storage]
134	pub(super) type BalancesOnHold<T: Config<I>, I: 'static = ()> = StorageDoubleMap<
135		_,
136		Blake2_128Concat,
137		T::AssetId,
138		Blake2_128Concat,
139		T::AccountId,
140		T::Balance,
141	>;
142
143	#[pallet::hooks]
144	impl<T: Config<I>, I: 'static> Hooks<BlockNumberFor<T>> for Pallet<T, I> {
145		#[cfg(feature = "try-runtime")]
146		fn try_state(_: BlockNumberFor<T>) -> Result<(), sp_runtime::TryRuntimeError> {
147			Self::do_try_state()
148		}
149	}
150}
151
152impl<T: Config<I>, I: 'static> Pallet<T, I> {
153	#[cfg(any(test, feature = "try-runtime"))]
154	fn do_try_state() -> Result<(), sp_runtime::TryRuntimeError> {
155		use sp_runtime::{
156			traits::{CheckedAdd, Zero},
157			ArithmeticError,
158		};
159
160		for (asset, who, balance_on_hold) in BalancesOnHold::<T, I>::iter() {
161			ensure!(balance_on_hold != Zero::zero(), "zero on hold must not be in state");
162
163			let mut amount_from_holds: T::Balance = Zero::zero();
164			for l in Holds::<T, I>::get(asset.clone(), who.clone()).iter() {
165				ensure!(l.amount != Zero::zero(), "zero amount is invalid");
166				amount_from_holds =
167					amount_from_holds.checked_add(&l.amount).ok_or(ArithmeticError::Overflow)?;
168			}
169
170			frame_support::ensure!(
171				balance_on_hold == amount_from_holds,
172				"The `BalancesOnHold` amount is not equal to the sum of `Holds` for (`asset`, `who`)"
173			);
174		}
175
176		Ok(())
177	}
178}