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}