Skip to main content

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}