pallet_meta_tx/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//! # Meta Tx (Meta Transaction) Pallet
19//!
20//! This pallet enables the dispatch of transactions that are authorized by one party (the signer)
21//! and executed by an untrusted third party (the relayer), who covers the transaction fees.
22//!
23//! ## Pallet API
24//!
25//! See the [`pallet`] module for more information about the interfaces this pallet exposes,
26//! including its configuration trait, dispatchables, storage items, events and errors.
27//!
28//! ## Overview
29//!
30//! The pallet provides a client-level API, typically not meant for direct use by end users.
31//! A meta transaction, constructed with the help of a wallet, contains a target call, necessary
32//! extensions, and the signer's signature. This transaction is then broadcast, and any interested
33//! relayer can pick it up and execute it. The relayer submits a regular transaction via the
34//! [`dispatch`](`Pallet::dispatch`) function, passing the meta transaction as an argument to
35//! execute the target call on behalf of the signer while covering the fees.
36//!
37//! ### Example
38#![doc = docify::embed!("src/tests.rs", sign_and_execute_meta_tx)]
39//! ## Low-Level / Implementation Details
40//!
41//! The structure of a meta transaction is identical to the
42//! [`General`](sp_runtime::generic::Preamble::General) transaction.
43//! It contains the target call along with a configurable set of extensions and its associated
44//! version. Typically, these extensions include type like
45//! `pallet_verify_signature::VerifySignature`, which provides the signer address
46//! and the signature of the payload, encompassing the call and the meta-transaction’s
47//! configurations, such as its mortality. The extensions follow the same [`TransactionExtension`]
48//! contract, and common types such as [`frame_system::CheckGenesis`],
49//! [`frame_system::CheckMortality`], [`frame_system::CheckNonce`], etc., are applicable in the
50//! context of meta transactions. Check the `mock` setup for the example.
51
52#![cfg_attr(not(feature = "std"), no_std)]
53
54mod benchmarking;
55#[cfg(test)]
56mod mock;
57#[cfg(all(test, not(feature = "runtime-benchmarks")))]
58mod tests;
59pub mod weights;
60#[cfg(feature = "runtime-benchmarks")]
61pub use benchmarking::types::WeightlessExtension;
62pub use pallet::*;
63pub use weights::WeightInfo;
64mod extension;
65pub use extension::MetaTxMarker;
66
67use frame_support::{
68 dispatch::{DispatchInfo, GetDispatchInfo, PostDispatchInfo},
69 pallet_prelude::*,
70};
71use frame_system::{pallet_prelude::*, RawOrigin as SystemOrigin};
72use sp_runtime::{
73 generic::ExtensionVersion,
74 traits::{
75 AsTransactionAuthorizedOrigin, DispatchTransaction, Dispatchable, TransactionExtension,
76 },
77};
78use sp_std::prelude::*;
79
80/// Meta Transaction type.
81///
82/// The data that is provided and signed by the signer and shared with the relayer.
83#[derive(Encode, Decode, PartialEq, Eq, TypeInfo, Clone, Debug, DecodeWithMemTracking)]
84pub struct MetaTx<Call, Extension> {
85 /// The target call to be executed on behalf of the signer.
86 call: Call,
87 /// The extension version.
88 extension_version: ExtensionVersion,
89 /// The extension/s for the meta transaction.
90 extension: Extension,
91}
92
93impl<Call, Extension> MetaTx<Call, Extension> {
94 /// Create a new meta transaction.
95 pub fn new(call: Call, extension_version: ExtensionVersion, extension: Extension) -> Self {
96 Self { call, extension_version, extension }
97 }
98}
99
100/// The [`MetaTx`] for the given config.
101pub type MetaTxFor<T> = MetaTx<<T as frame_system::Config>::RuntimeCall, <T as Config>::Extension>;
102
103#[frame_support::pallet]
104pub mod pallet {
105 use super::*;
106
107 #[pallet::config]
108 pub trait Config:
109 frame_system::Config<
110 RuntimeCall: Dispatchable<
111 Info = DispatchInfo,
112 PostInfo = PostDispatchInfo,
113 RuntimeOrigin = <Self as frame_system::Config>::RuntimeOrigin,
114 >,
115 RuntimeOrigin: AsTransactionAuthorizedOrigin + From<SystemOrigin<Self::AccountId>>,
116 >
117 {
118 /// Weight information for calls in this pallet.
119 type WeightInfo: WeightInfo;
120 /// The overarching event type.
121 #[allow(deprecated)]
122 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
123 /// Transaction extension/s for meta transactions.
124 ///
125 /// The extensions that must be present in every meta transaction. This generally includes
126 /// extensions like `pallet_verify_signature::VerifySignature`,
127 /// [frame_system::CheckSpecVersion], [frame_system::CheckTxVersion],
128 /// [frame_system::CheckGenesis], [frame_system::CheckMortality],
129 /// [frame_system::CheckNonce], etc. Check the `mock` setup for the example.
130 ///
131 /// The types implementing the [`TransactionExtension`] trait can be composed into a tuple
132 /// type that will implement the same trait by piping invocations through each type.
133 ///
134 /// In the `runtime-benchmarks` environment the type must implement [`Default`] trait.
135 /// The extension must provide an origin and the extension's weight must be zero. Use
136 /// `pallet_meta_tx::WeightlessExtension` type when the `runtime-benchmarks` feature
137 /// enabled.
138 type Extension: TransactionExtension<<Self as frame_system::Config>::RuntimeCall>;
139 }
140
141 #[pallet::error]
142 pub enum Error<T> {
143 /// Invalid proof (e.g. signature).
144 BadProof,
145 /// The meta transaction is not yet valid (e.g. nonce too high).
146 Future,
147 /// The meta transaction is outdated (e.g. nonce too low).
148 Stale,
149 /// The meta transactions's birth block is ancient.
150 AncientBirthBlock,
151 /// The transaction extension did not authorize any origin.
152 UnknownOrigin,
153 /// The meta transaction is invalid.
154 Invalid,
155 /// The meta transaction length is invalid.
156 InvalidLength,
157 }
158
159 #[pallet::event]
160 #[pallet::generate_deposit(pub(crate) fn deposit_event)]
161 pub enum Event<T: Config> {
162 /// A meta transaction has been dispatched.
163 ///
164 /// Contains the dispatch result of the meta transaction along with post-dispatch
165 /// information.
166 Dispatched { result: DispatchResultWithPostInfo },
167 }
168
169 #[pallet::pallet]
170 pub struct Pallet<T>(_);
171
172 #[pallet::call]
173 impl<T: Config> Pallet<T> {
174 /// Dispatch a given meta transaction.
175 ///
176 /// - `_origin`: Can be any kind of origin.
177 /// - `meta_tx`: Meta Transaction with a target call to be dispatched.
178 /// - `meta_tx_encoded_len`: The size of the encoded meta transaction in bytes.
179 #[pallet::call_index(0)]
180 #[pallet::weight({
181 let dispatch_info = meta_tx.call.get_dispatch_info();
182 let extension_weight = meta_tx.extension.weight(&meta_tx.call);
183 let bare_call_weight = T::WeightInfo::bare_dispatch(*meta_tx_encoded_len);
184 (
185 dispatch_info.call_weight
186 .saturating_add(extension_weight)
187 .saturating_add(bare_call_weight),
188 dispatch_info.class,
189 )
190 })]
191 pub fn dispatch(
192 _origin: OriginFor<T>,
193 meta_tx: Box<MetaTxFor<T>>,
194 meta_tx_encoded_len: u32, // The size of the encoded meta transaction in bytes.
195 ) -> DispatchResultWithPostInfo {
196 let origin = SystemOrigin::None;
197 let meta_tx_size = meta_tx.encoded_size();
198 ensure!(meta_tx_size <= meta_tx_encoded_len as usize, Error::<T>::InvalidLength);
199 // `info` with worst-case call weight and extension weight.
200 let info = {
201 let mut info = meta_tx.call.get_dispatch_info();
202 info.extension_weight = meta_tx.extension.weight(&meta_tx.call);
203 info
204 };
205 // dispatch the meta transaction.
206 let meta_dispatch_res = meta_tx
207 .extension
208 .dispatch_transaction(
209 origin.into(),
210 meta_tx.call,
211 &info,
212 meta_tx_size,
213 meta_tx.extension_version,
214 )
215 .map_err(Error::<T>::from)?;
216
217 Self::deposit_event(Event::Dispatched { result: meta_dispatch_res });
218
219 // meta weight after possible refunds.
220 let meta_weight = meta_dispatch_res
221 .map_or_else(|err| err.post_info.actual_weight, |info| info.actual_weight)
222 .unwrap_or(info.total_weight());
223
224 Ok((
225 Some(T::WeightInfo::bare_dispatch(meta_tx_size as u32).saturating_add(meta_weight)),
226 true.into(),
227 )
228 .into())
229 }
230 }
231
232 /// Implements [`From<TransactionValidityError>`] for [`Error`] by mapping the relevant error
233 /// variants.
234 impl<T> From<TransactionValidityError> for Error<T> {
235 fn from(err: TransactionValidityError) -> Self {
236 use TransactionValidityError::*;
237 match err {
238 Unknown(_) => Error::<T>::Invalid,
239 Invalid(err) => match err {
240 InvalidTransaction::BadProof => Error::<T>::BadProof,
241 InvalidTransaction::Future => Error::<T>::Future,
242 InvalidTransaction::Stale => Error::<T>::Stale,
243 InvalidTransaction::AncientBirthBlock => Error::<T>::AncientBirthBlock,
244 InvalidTransaction::UnknownOrigin => Error::<T>::UnknownOrigin,
245 _ => Error::<T>::Invalid,
246 },
247 }
248 }
249 }
250}