pallet_feeless/lib.rs
1// GNU General Public License (GPL)
2// Version 3, 29 June 2007
3// http://www.gnu.org/licenses/gpl-3.0.html
4//
5// Copyright 2024 Benjamin Gallois
6//
7// Licensed under the GNU General Public License, Version 3 (the "License");
8// you may not use this file except in compliance with the License.
9// You may obtain a copy of the License at
10//
11// http://www.gnu.org/licenses/gpl-3.0.html
12//
13// Unless required by applicable law or agreed to in writing, software
14// distributed under the License is distributed on an "AS IS" BASIS,
15// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16// See the License for the specific language governing permissions and
17// limitations under the License.
18//
19// You may not distribute modified versions of the software without providing
20// the source code, and any derivative works must be licensed under the GPL
21// License as well. This ensures that the software remains free and open
22// for all users.
23//
24// You should have received a copy of the GPL along with this program.
25// If not, see <http://www.gnu.org/licenses/>.
26#![cfg_attr(not(feature = "std"), no_std)]
27#![doc = include_str!("../README.md")]
28use frame_support::{
29 pallet_prelude::{EnsureOrigin, IsType},
30 traits::Get,
31};
32use frame_system::pallet_prelude::{BlockNumberFor, OriginFor};
33pub use pallet::*;
34use sp_runtime::{DispatchError, DispatchResult, SaturatedConversion};
35
36#[cfg(feature = "runtime-benchmarks")]
37mod benchmarking;
38pub mod weights;
39pub use weights::*;
40
41#[cfg(test)]
42mod mock;
43
44#[cfg(test)]
45mod tests;
46
47pub mod types;
48pub use types::*;
49
50pub mod extensions;
51pub use extensions::*;
52
53#[frame_support::pallet]
54pub mod pallet {
55 use super::*;
56
57 #[pallet::pallet]
58 pub struct Pallet<T>(_);
59
60 #[pallet::config]
61 pub trait Config: frame_system::Config {
62 /// The origin which may change account status. Root can always do this.
63 type StatusOrigin: EnsureOrigin<Self::RuntimeOrigin>;
64 /// The overarching runtime event type.
65 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
66 /// Maximum number of transactions allowed per account within the defined period.
67 type MaxTxByPeriod: Get<u32>;
68 /// Maximum size of transactions allowed per account within the defined period.
69 type MaxSizeByPeriod: Get<u32>;
70 /// Duration (in blocks) defining the rate-limiting period.
71 type Period: Get<u32>;
72 /// A type representing the weights required by the dispatchables of this pallet.
73 type WeightInfo: WeightInfo;
74 }
75
76 #[pallet::event]
77 #[pallet::generate_deposit(pub(super) fn deposit_event)]
78 pub enum Event<T: Config> {
79 StatusChanged { who: T::AccountId, status: Status },
80 }
81
82 #[pallet::error]
83 pub enum Error<T> {
84 StatusNotChanged,
85 }
86
87 #[pallet::call]
88 impl<T: Config> Pallet<T>
89 where
90 T: frame_system::Config<AccountData = AccountData<T::Balance, BlockNumberFor<T>>>
91 + Config
92 + pallet_balances::Config,
93 {
94 /// Sets the status of a specific account.
95 ///
96 /// This function allows a protected origin to update the status of an account.
97 /// It is typically used for management tasks, such as managing account states
98 /// during runtime upgrades or other administrative actions.
99 ///
100 /// The status of the account will be updated to the provided `status` value.
101 ///
102 /// ## Arguments:
103 /// - `origin`: The origin of the transaction (must be the root account).
104 /// - `who`: The `AccountId` of the account whose status is being set.
105 /// - `status`: The new `Status` to assign to the account.
106 #[pallet::call_index(0)]
107 #[pallet::weight(<T as pallet::Config>::WeightInfo::set_status())]
108 pub fn set_status(
109 origin: OriginFor<T>,
110 who: T::AccountId,
111 status: Status,
112 ) -> DispatchResult {
113 T::StatusOrigin::ensure_origin(origin)?;
114
115 Self::deposit_event(Event::StatusChanged {
116 who: who.clone(),
117 status: status.clone(),
118 });
119 frame_system::Account::<T>::try_mutate_exists(who.clone(), |account| {
120 if let Some(ref mut account) = account {
121 account.data.rate.status = status.clone();
122 Ok(())
123 } else {
124 Err(Error::<T>::StatusNotChanged.into())
125 }
126 })
127 }
128 }
129}
130
131/// Implements the storage backend for custom account data (same as the default from pallet
132/// balances.
133impl<T> frame_support::traits::StoredMap<T::AccountId, pallet_balances::AccountData<T::Balance>>
134 for Pallet<T>
135where
136 T: frame_system::Config<AccountData = AccountData<T::Balance, BlockNumberFor<T>>>
137 + pallet_balances::Config,
138{
139 fn get(k: &T::AccountId) -> pallet_balances::AccountData<T::Balance> {
140 frame_system::Account::<T>::get(k).data.balance
141 }
142
143 fn try_mutate_exists<R, E: From<DispatchError>>(
144 k: &T::AccountId,
145 f: impl FnOnce(&mut Option<pallet_balances::AccountData<T::Balance>>) -> Result<R, E>,
146 ) -> Result<R, E> {
147 let account = frame_system::Account::<T>::get(k);
148 let is_default =
149 account.data.balance == pallet_balances::AccountData::<T::Balance>::default();
150 let mut some_data = if is_default {
151 None
152 } else {
153 Some(account.data.balance)
154 };
155 let result = f(&mut some_data)?;
156 if frame_system::Pallet::<T>::providers(k) > 0
157 || frame_system::Pallet::<T>::sufficients(k) > 0
158 {
159 frame_system::Account::<T>::mutate(k, |a| {
160 a.data.balance = some_data.unwrap_or_default()
161 });
162 } else {
163 frame_system::Account::<T>::remove(k)
164 }
165 Ok(result)
166 }
167}
168
169/// A rate limiter implementation for managing transaction limits
170/// and data size constraints based on a specified block number period.
171///
172/// This implementation limits the number of transactions and the total
173/// size of transactions that can be processed within a given period. The
174/// rate limiter checks whether the rate limit has been exceeded, and updates
175/// the rate statistics accordingly.
176impl<T> RateLimiter<T> for AccountData<T::Balance, BlockNumberFor<T>>
177where
178 T: frame_system::Config<AccountData = AccountData<T::Balance, BlockNumberFor<T>>>
179 + Config
180 + pallet_balances::Config,
181{
182 /// Determines whether a transaction is allowed based on the current rate
183 /// limiter settings, considering the block number and the transaction size.
184 ///
185 /// # Arguments
186 /// * `b` - The current block number.
187 /// * `len` - The size of the transaction in bytes.
188 ///
189 /// # Returns
190 /// `true` if the transaction is allowed, `false` otherwise.
191 fn is_allowed(&self, b: BlockNumberFor<T>, len: u32) -> bool {
192 if self.rate.status == Status::Unlimited {
193 true
194 } else if (b - self.rate.last_block).saturated_into::<u32>() < T::Period::get() {
195 self.rate.tx_since_last < T::MaxTxByPeriod::get()
196 && self.rate.size_since_last.saturating_add(len) < T::MaxSizeByPeriod::get()
197 } else {
198 len < T::MaxSizeByPeriod::get()
199 }
200 }
201
202 /// Updates the rate limiter's internal statistics, such as the number of
203 /// transactions and the total data size for the current period, based on
204 /// the current block number and transaction size.
205 ///
206 /// # Arguments
207 /// * `b` - The current block number.
208 /// * `len` - The size of the transaction in bytes.
209 ///
210 /// This method will reset the transaction count and size if the current
211 /// block number exceeds the specified period. Otherwise, it will update
212 /// the transaction count and size based on the new transaction.
213 fn update_rate(&mut self, b: BlockNumberFor<T>, len: u32) {
214 if (b - self.rate.last_block).saturated_into::<u32>() < T::Period::get() {
215 self.rate.tx_since_last += 1;
216 self.rate.size_since_last += len;
217 } else {
218 self.rate.tx_since_last = 1;
219 self.rate.size_since_last = len;
220 self.rate.last_block = b;
221 }
222 }
223}