Skip to main content

pallet_dap/
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//! # Dynamic Allocation Pool (DAP) Pallet
19//!
20//! Intercepts native token burns (staking slashes, transaction fees, dust removal, reward
21//! remainders, EVM gas rounding) on AssetHub and redirects them into a buffer account instead
22//! of destroying them.
23//! The buffer account must be pre-funded with at least ED (existential deposit), e.g., via
24//! balances genesis config or a transfer. If the buffer account is not pre-funded, deposits
25//! below ED will be silently burned.
26//!
27//! Incoming funds are deactivated to exclude them from governance voting.
28//! When DAP distributes funds (e.g., to validators, nominators, treasury, collators), those funds
29//! must be reactivated before transfer.
30//!
31//! - **Burns**: Use `Dap` as `OnUnbalanced` handler for any burn source (e.g., `type Slash = Dap`,
32//!   `type DustRemoval = Dap`, `type OnBurn = Dap`)
33//! Note: Direct calls to `pallet_balances::Pallet::burn()` extrinsic are not redirected to
34//! the buffer — they still reduce total issuance directly.
35
36#![cfg_attr(not(feature = "std"), no_std)]
37
38#[cfg(test)]
39pub(crate) mod mock;
40#[cfg(test)]
41mod tests;
42
43use frame_support::{
44	defensive,
45	pallet_prelude::*,
46	traits::{
47		fungible::{Balanced, Credit, Inspect, Unbalanced},
48		Imbalance, OnUnbalanced,
49	},
50	PalletId,
51};
52
53pub use pallet::*;
54
55const LOG_TARGET: &str = "runtime::dap";
56
57/// Type alias for balance.
58pub type BalanceOf<T> =
59	<<T as Config>::Currency as Inspect<<T as frame_system::Config>::AccountId>>::Balance;
60
61#[frame_support::pallet]
62pub mod pallet {
63	use super::*;
64	use frame_support::{sp_runtime::traits::AccountIdConversion, traits::StorageVersion};
65
66	/// The in-code storage version.
67	const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
68
69	#[pallet::pallet]
70	#[pallet::storage_version(STORAGE_VERSION)]
71	pub struct Pallet<T>(_);
72
73	#[pallet::config]
74	pub trait Config: frame_system::Config {
75		/// The currency type (new fungible traits).
76		type Currency: Inspect<Self::AccountId>
77			+ Unbalanced<Self::AccountId>
78			+ Balanced<Self::AccountId>;
79
80		/// The pallet ID used to derive the buffer account.
81		///
82		/// Each runtime should configure a unique ID to avoid collisions if multiple
83		/// DAP instances are used.
84		#[pallet::constant]
85		type PalletId: Get<PalletId>;
86	}
87
88	impl<T: Config> Pallet<T> {
89		/// Get the DAP buffer account
90		/// NOTE: We may need more accounts in the future, for instance, to manage the strategic
91		/// reserve. We will add them as necessary, generating them with additional seed.
92		pub fn buffer_account() -> T::AccountId {
93			T::PalletId::get().into_account_truncating()
94		}
95	}
96}
97
98/// Type alias for credit (negative imbalance - funds that were slashed/removed).
99/// This is for the `fungible::Balanced` trait as used by staking-async.
100pub type CreditOf<T> = Credit<<T as frame_system::Config>::AccountId, <T as Config>::Currency>;
101
102/// Implementation of OnUnbalanced for the fungible::Balanced trait.
103/// Example: use as `type Slash = Dap` in staking-async config.
104///
105/// Only the new fungible `Credit` type is supported. An `OnUnbalanced<NegativeImbalance>` impl
106/// for the old `Currency` trait is not provided because there are no consumers.
107impl<T: Config> OnUnbalanced<CreditOf<T>> for Pallet<T> {
108	fn on_nonzero_unbalanced(amount: CreditOf<T>) {
109		let buffer = Self::buffer_account();
110		let numeric_amount = amount.peek();
111
112		// Resolve should never fail because:
113		// - can_deposit on destination succeeds since buffer exists (created with provider at
114		//   genesis/runtime upgrade so no ED issue)
115		// - amount is guaranteed non-zero by the trait method signature
116		// The only failure would be overflow on destination.
117		let _ = T::Currency::resolve(&buffer, amount)
118			.inspect_err(|_| {
119				defensive!(
120					"🚨 Failed to deposit slash to DAP buffer - funds burned, it should never happen!"
121				);
122			})
123			.inspect(|_| {
124				// Mark funds as inactive so they don't participate in governance voting.
125				// Only deactivate on success; if resolve failed, tokens were burned.
126				<T::Currency as Unbalanced<T::AccountId>>::deactivate(numeric_amount);
127				log::debug!(
128					target: LOG_TARGET,
129					"💸 Deposited slash of {numeric_amount:?} to DAP buffer"
130				);
131			});
132	}
133}