Skip to main content

pallet_feeless/
extensions.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/>.
26use crate::types::RateLimiter;
27use codec::{Decode, DecodeWithMemTracking, Encode};
28use core::marker::PhantomData;
29use frame_support::pallet_prelude::InvalidTransaction::ExhaustsResources;
30use scale_info::TypeInfo;
31use sp_runtime::{
32    impl_tx_ext_default,
33    traits::{DispatchInfoOf, Dispatchable, PostDispatchInfoOf, TransactionExtension},
34    transaction_validity::{TransactionSource, TransactionValidityError, ValidTransaction},
35    DispatchResult, Weight,
36};
37
38/// A transaction extension for rate limiting.
39#[derive(Encode, Decode, DecodeWithMemTracking, Clone, Eq, PartialEq, TypeInfo)]
40#[scale_info(skip_type_params(T))]
41pub struct CheckRate<T: frame_system::Config + Send + Sync>(PhantomData<T>);
42
43impl<T: frame_system::Config + Send + Sync> core::fmt::Debug for CheckRate<T> {
44    #[cfg(feature = "std")]
45    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
46        write!(f, "CheckRate")
47    }
48
49    #[cfg(not(feature = "std"))]
50    fn fmt(&self, _: &mut core::fmt::Formatter) -> core::fmt::Result {
51        Ok(())
52    }
53}
54
55pub struct Pre<T: frame_system::Config> {
56    who: Option<T::AccountId>,
57}
58
59impl<T: frame_system::Config> core::fmt::Debug for Pre<T> {
60    #[cfg(feature = "std")]
61    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
62        write!(f, "who: {:?}", self.who)
63    }
64
65    #[cfg(not(feature = "std"))]
66    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
67        f.write_str("<wasm:stripped>")
68    }
69}
70
71impl<T: frame_system::Config + Send + Sync> Default for CheckRate<T> {
72    fn default() -> Self {
73        Self::new()
74    }
75}
76
77impl<T: frame_system::Config + Send + Sync> CheckRate<T> {
78    pub fn new() -> Self {
79        Self(PhantomData)
80    }
81}
82
83impl<T> TransactionExtension<T::RuntimeCall> for CheckRate<T>
84where
85    T: frame_system::Config + Send + Sync,
86    T::AccountData: RateLimiter<T>,
87{
88    type Implicit = ();
89    type Pre = Pre<T>;
90    type Val = Pre<T>;
91
92    const IDENTIFIER: &'static str = "CheckRate";
93
94    impl_tx_ext_default!(T::RuntimeCall; weight);
95
96    /// Validates a transaction based on rate limits.
97    fn validate(
98        &self,
99        origin: <T::RuntimeCall as Dispatchable>::RuntimeOrigin,
100        _call: &T::RuntimeCall,
101        _info: &DispatchInfoOf<T::RuntimeCall>,
102        len: usize,
103        _: (),
104        _implication: &impl Encode,
105        _source: TransactionSource,
106    ) -> Result<
107        (
108            ValidTransaction,
109            Self::Val,
110            <T::RuntimeCall as Dispatchable>::RuntimeOrigin,
111        ),
112        TransactionValidityError,
113    > {
114        let Ok(who) = frame_system::ensure_signed(origin.clone()) else {
115            return Ok((Default::default(), Pre { who: None }, origin));
116        };
117
118        let account_data = frame_system::Account::<T>::get(who.clone()).data;
119        let block = frame_system::Pallet::<T>::block_number();
120        if account_data.is_allowed(block, len as u32) {
121            Ok((
122                Default::default(),
123                Pre {
124                    who: Some(who.clone()),
125                },
126                origin,
127            ))
128        } else {
129            Err(TransactionValidityError::Invalid(ExhaustsResources))
130        }
131    }
132
133    /// Prepares data for post-dispatch processing.
134    fn prepare(
135        self,
136        val: Self::Val,
137        _origin: &<T::RuntimeCall as Dispatchable>::RuntimeOrigin,
138        _call: &T::RuntimeCall,
139        _info: &DispatchInfoOf<T::RuntimeCall>,
140        _len: usize,
141    ) -> Result<Self::Pre, TransactionValidityError> {
142        Ok(val)
143    }
144
145    /// Updates rate limits after transaction execution.
146    fn post_dispatch_details(
147        pre: Self::Pre,
148        _info: &DispatchInfoOf<T::RuntimeCall>,
149        _post_info: &PostDispatchInfoOf<T::RuntimeCall>,
150        len: usize,
151        _result: &DispatchResult,
152    ) -> Result<Weight, TransactionValidityError> {
153        if let Some(who) = pre.who {
154            let mut account_data = frame_system::Account::<T>::get(who.clone()).data;
155            let block = frame_system::Pallet::<T>::block_number();
156            account_data.update_rate(block, len as u32);
157            frame_system::Account::<T>::mutate(who, |account| account.data = account_data);
158        }
159        Ok(Weight::zero())
160    }
161}