1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
//! # Fees pallet for runtime
//!
//! This pallet provides a storing functionality for setting and getting fees
//! associated with an Hash key. Fees can only be set by FeeOrigin or RootOrigin
//!
//! Also, for its internal usage from the runtime or other pallets,
//! it offers some utilities to transfer the fees to the author, the treasury or
//! burn it.
#![cfg_attr(not(feature = "std"), no_std)]

use cfg_traits::fees::{self, Fee};
use frame_support::{
	pallet_prelude::{DispatchError, DispatchResult},
	traits::{Currency, ExistenceRequirement, Imbalance, OnUnbalanced, WithdrawReasons},
	DefaultNoBound,
};
pub use pallet::*;

#[cfg(test)]
mod mock;

#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;

#[cfg(test)]
mod tests;

pub mod weights;
pub use weights::*;

pub type BalanceOf<T> =
	<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;

pub type ImbalanceOf<T> = <<T as Config>::Currency as Currency<
	<T as frame_system::Config>::AccountId,
>>::NegativeImbalance;

#[frame_support::pallet]
pub mod pallet {
	// Import various types used to declare pallet in scope.
	use frame_support::pallet_prelude::*;
	use frame_system::pallet_prelude::*;

	use super::*;

	// Simple declaration of the `Pallet` type. It is placeholder we use to
	// implement traits and method.
	#[pallet::pallet]

	pub struct Pallet<T>(_);

	#[pallet::config]
	pub trait Config: frame_system::Config + pallet_authorship::Config {
		/// Key type used for storing and identifying fees.
		type FeeKey: Parameter + MaybeSerializeDeserialize + MaxEncodedLen;

		/// The currency mechanism.
		type Currency: Currency<Self::AccountId>;

		/// The treasury destination.
		type Treasury: OnUnbalanced<ImbalanceOf<Self>>;

		/// The overarching event type.
		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;

		/// Required origin for changing fees.
		type FeeChangeOrigin: EnsureOrigin<Self::RuntimeOrigin>;

		/// Default value for fee keys.
		type DefaultFeeValue: Get<BalanceOf<Self>>;

		/// Type representing the weight of this pallet.
		type WeightInfo: WeightInfo;
	}

	/// Stores the fee balances associated with a Hash identifier
	#[pallet::storage]
	#[pallet::getter(fn fee)]
	pub(super) type FeeBalances<T: Config> =
		StorageMap<_, Blake2_256, T::FeeKey, BalanceOf<T>, ValueQuery, T::DefaultFeeValue>;

	// The genesis config type.
	#[pallet::genesis_config]
	#[derive(DefaultNoBound)]
	pub struct GenesisConfig<T: Config> {
		pub initial_fees: sp_std::vec::Vec<(T::FeeKey, BalanceOf<T>)>,
	}

	// The build of genesis for the pallet.
	#[pallet::genesis_build]
	impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
		fn build(&self) {
			for (key, fee) in self.initial_fees.iter() {
				<FeeBalances<T>>::insert(key, fee);
			}
		}
	}

	#[pallet::event]
	#[pallet::generate_deposit(pub(super) fn deposit_event)]
	pub enum Event<T: Config> {
		FeeChanged {
			key: T::FeeKey,
			fee: BalanceOf<T>,
		},
		FeeToAuthor {
			from: T::AccountId,
			balance: BalanceOf<T>,
		},
		FeeToBurn {
			from: T::AccountId,
			balance: BalanceOf<T>,
		},
		FeeToTreasury {
			from: T::AccountId,
			balance: BalanceOf<T>,
		},
	}

	#[pallet::call]
	impl<T: Config> Pallet<T> {
		/// Set the given fee for the key
		#[pallet::weight(<T as pallet::Config>::WeightInfo::set_fee())]
		#[pallet::call_index(0)]
		pub fn set_fee(origin: OriginFor<T>, key: T::FeeKey, fee: BalanceOf<T>) -> DispatchResult {
			T::FeeChangeOrigin::ensure_origin(origin)?;

			<FeeBalances<T>>::insert(key.clone(), fee);
			Self::deposit_event(Event::FeeChanged { key, fee });

			Ok(())
		}
	}
}

impl<T: Config> fees::Fees for Pallet<T> {
	type AccountId = T::AccountId;
	type Balance = BalanceOf<T>;
	type FeeKey = T::FeeKey;

	fn fee_value(key: Self::FeeKey) -> BalanceOf<T> {
		<FeeBalances<T>>::get(key)
	}

	fn fee_to_author(
		from: &Self::AccountId,
		fee: Fee<BalanceOf<T>, Self::FeeKey>,
	) -> DispatchResult {
		if let Some(author) = <pallet_authorship::Pallet<T>>::author() {
			let imbalance = Self::withdraw_fee(from, fee)?;
			let balance = imbalance.peek();

			T::Currency::resolve_creating(&author, imbalance);

			Self::deposit_event(Event::FeeToAuthor {
				from: author,
				balance,
			});
		}
		Ok(())
	}

	fn fee_to_burn(from: &Self::AccountId, fee: Fee<BalanceOf<T>, Self::FeeKey>) -> DispatchResult {
		let imbalance = Self::withdraw_fee(from, fee)?;
		let balance = imbalance.peek();

		Self::deposit_event(Event::FeeToBurn {
			from: from.clone(),
			balance,
		});
		Ok(())
	}

	fn fee_to_treasury(
		from: &Self::AccountId,
		fee: Fee<BalanceOf<T>, Self::FeeKey>,
	) -> DispatchResult {
		let imbalance = Self::withdraw_fee(from, fee)?;
		let balance = imbalance.peek();

		T::Treasury::on_unbalanced(imbalance);

		Self::deposit_event(Event::FeeToTreasury {
			from: from.clone(),
			balance,
		});
		Ok(())
	}

	#[cfg(feature = "runtime-benchmarks")]
	fn add_fee_requirements(from: &Self::AccountId, fee: Fee<Self::Balance, Self::FeeKey>) {
		let _ = T::Currency::deposit_creating(from, T::Currency::minimum_balance());
		let _ = T::Currency::deposit_creating(from, fee.value::<Self>());
	}
}

impl<T: Config> Pallet<T> {
	fn withdraw_fee(
		from: &T::AccountId,
		fee: Fee<BalanceOf<T>, T::FeeKey>,
	) -> Result<ImbalanceOf<T>, DispatchError> {
		T::Currency::withdraw(
			from,
			fee.value::<Self>(),
			WithdrawReasons::FEE,
			ExistenceRequirement::KeepAlive,
		)
	}
}