diff --git a/Cargo.lock b/Cargo.lock index 88442d3702e94881b25e10c0f6132e1ac5d7fe3e..0d62e43561a6930ee0416a54ddbd761d998c2039 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12415,6 +12415,28 @@ dependencies = [ "sp-weights 27.0.0", ] +[[package]] +name = "pallet-meta-tx" +version = "0.1.0" +dependencies = [ + "docify", + "frame-benchmarking", + "frame-support", + "frame-system", + "pallet-balances", + "pallet-transaction-payment", + "pallet-verify-signature", + "parity-scale-codec", + "scale-info", + "serde", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-keyring", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", + "sp-std 14.0.0", +] + [[package]] name = "pallet-migrations" version = "1.0.0" @@ -15892,6 +15914,7 @@ dependencies = [ "pallet-lottery", "pallet-membership", "pallet-message-queue", + "pallet-meta-tx", "pallet-migrations", "pallet-mixnet", "pallet-mmr", @@ -27489,6 +27512,7 @@ dependencies = [ "pallet-indices", "pallet-membership", "pallet-message-queue", + "pallet-meta-tx", "pallet-migrations", "pallet-mmr", "pallet-multisig", @@ -27516,6 +27540,7 @@ dependencies = [ "pallet-transaction-payment-rpc-runtime-api", "pallet-treasury", "pallet-utility", + "pallet-verify-signature", "pallet-vesting", "pallet-whitelist", "pallet-xcm", diff --git a/Cargo.toml b/Cargo.toml index c355679143037ae2b420ba416fc57371dc6b5943..36bb51390b39fc0f13c62a76d7a891274358b7be 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -379,6 +379,7 @@ members = [ "substrate/frame/membership", "substrate/frame/merkle-mountain-range", "substrate/frame/message-queue", + "substrate/frame/meta-tx", "substrate/frame/metadata-hash-extension", "substrate/frame/migrations", "substrate/frame/mixnet", @@ -962,6 +963,7 @@ pallet-insecure-randomness-collective-flip = { path = "substrate/frame/insecure- pallet-lottery = { default-features = false, path = "substrate/frame/lottery" } pallet-membership = { path = "substrate/frame/membership", default-features = false } pallet-message-queue = { path = "substrate/frame/message-queue", default-features = false } +pallet-meta-tx = { path = "substrate/frame/meta-tx", default-features = false } pallet-migrations = { path = "substrate/frame/migrations", default-features = false } pallet-minimal-template = { path = "templates/minimal/pallets/template", default-features = false } pallet-mixnet = { default-features = false, path = "substrate/frame/mixnet" } diff --git a/polkadot/runtime/westend/Cargo.toml b/polkadot/runtime/westend/Cargo.toml index 3317484419a9a2472da1b28f6dcf9ce1d04e0d45..43794cbee854498853b2a39fdccadaed30af9953 100644 --- a/polkadot/runtime/westend/Cargo.toml +++ b/polkadot/runtime/westend/Cargo.toml @@ -70,6 +70,7 @@ pallet-identity = { workspace = true } pallet-indices = { workspace = true } pallet-membership = { workspace = true } pallet-message-queue = { workspace = true } +pallet-meta-tx = { workspace = true } pallet-migrations = { workspace = true } pallet-mmr = { workspace = true } pallet-multisig = { workspace = true } @@ -94,6 +95,7 @@ pallet-transaction-payment = { workspace = true } pallet-transaction-payment-rpc-runtime-api = { workspace = true } pallet-treasury = { workspace = true } pallet-utility = { workspace = true } +pallet-verify-signature = { workspace = true } pallet-vesting = { workspace = true } pallet-whitelist = { workspace = true } pallet-xcm = { workspace = true } @@ -168,6 +170,7 @@ std = [ "pallet-indices/std", "pallet-membership/std", "pallet-message-queue/std", + "pallet-meta-tx/std", "pallet-migrations/std", "pallet-mmr/std", "pallet-multisig/std", @@ -195,6 +198,7 @@ std = [ "pallet-transaction-payment/std", "pallet-treasury/std", "pallet-utility/std", + "pallet-verify-signature/std", "pallet-vesting/std", "pallet-whitelist/std", "pallet-xcm-benchmarks?/std", @@ -257,6 +261,7 @@ runtime-benchmarks = [ "pallet-indices/runtime-benchmarks", "pallet-membership/runtime-benchmarks", "pallet-message-queue/runtime-benchmarks", + "pallet-meta-tx/runtime-benchmarks", "pallet-migrations/runtime-benchmarks", "pallet-mmr/runtime-benchmarks", "pallet-multisig/runtime-benchmarks", @@ -279,6 +284,7 @@ runtime-benchmarks = [ "pallet-transaction-payment/runtime-benchmarks", "pallet-treasury/runtime-benchmarks", "pallet-utility/runtime-benchmarks", + "pallet-verify-signature/runtime-benchmarks", "pallet-vesting/runtime-benchmarks", "pallet-whitelist/runtime-benchmarks", "pallet-xcm-benchmarks/runtime-benchmarks", @@ -319,6 +325,7 @@ try-runtime = [ "pallet-indices/try-runtime", "pallet-membership/try-runtime", "pallet-message-queue/try-runtime", + "pallet-meta-tx/try-runtime", "pallet-migrations/try-runtime", "pallet-mmr/try-runtime", "pallet-multisig/try-runtime", @@ -340,6 +347,7 @@ try-runtime = [ "pallet-transaction-payment/try-runtime", "pallet-treasury/try-runtime", "pallet-utility/try-runtime", + "pallet-verify-signature/try-runtime", "pallet-vesting/try-runtime", "pallet-whitelist/try-runtime", "pallet-xcm/try-runtime", diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index b5dc9b8f55cd1b43567405e45a368ed14cc3c445..bb758afdf12fbb18acdcf9df81bfb5ab565ab29f 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -104,7 +104,7 @@ use sp_runtime::{ OpaqueKeys, SaturatedConversion, Verify, }, transaction_validity::{TransactionPriority, TransactionSource, TransactionValidity}, - ApplyExtrinsicResult, FixedU128, KeyTypeId, Percent, Permill, + ApplyExtrinsicResult, FixedU128, KeyTypeId, MultiSignature, MultiSigner, Percent, Permill, }; use sp_staking::SessionIndex; #[cfg(any(feature = "std", test))] @@ -1616,6 +1616,35 @@ impl OnSwap for SwapLeases { } } +pub type MetaTxExtension = ( + pallet_verify_signature::VerifySignature<Runtime>, + pallet_meta_tx::MetaTxMarker<Runtime>, + frame_system::CheckNonZeroSender<Runtime>, + frame_system::CheckSpecVersion<Runtime>, + frame_system::CheckTxVersion<Runtime>, + frame_system::CheckGenesis<Runtime>, + frame_system::CheckMortality<Runtime>, + frame_system::CheckNonce<Runtime>, + frame_metadata_hash_extension::CheckMetadataHash<Runtime>, +); + +impl pallet_meta_tx::Config for Runtime { + type WeightInfo = weights::pallet_meta_tx::WeightInfo<Runtime>; + type RuntimeEvent = RuntimeEvent; + #[cfg(not(feature = "runtime-benchmarks"))] + type Extension = MetaTxExtension; + #[cfg(feature = "runtime-benchmarks")] + type Extension = pallet_meta_tx::WeightlessExtension<Runtime>; +} + +impl pallet_verify_signature::Config for Runtime { + type Signature = MultiSignature; + type AccountIdentifier = MultiSigner; + type WeightInfo = weights::pallet_verify_signature::WeightInfo<Runtime>; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = (); +} + #[frame_support::runtime(legacy_ordering)] mod runtime { #[runtime::runtime] @@ -1809,6 +1838,12 @@ mod runtime { #[runtime::pallet_index(102)] pub type RootTesting = pallet_root_testing; + #[runtime::pallet_index(103)] + pub type MetaTx = pallet_meta_tx::Pallet<Runtime>; + + #[runtime::pallet_index(104)] + pub type VerifySignature = pallet_verify_signature::Pallet<Runtime>; + // BEEFY Bridges support. #[runtime::pallet_index(200)] pub type Beefy = pallet_beefy; @@ -1959,6 +1994,8 @@ mod benches { [pallet_vesting, Vesting] [pallet_whitelist, Whitelist] [pallet_asset_rate, AssetRate] + [pallet_meta_tx, MetaTx] + [pallet_verify_signature, VerifySignature] // XCM [pallet_xcm, PalletXcmExtrinsicsBenchmark::<Runtime>] // NOTE: Make sure you point to the individual modules below. diff --git a/polkadot/runtime/westend/src/weights/mod.rs b/polkadot/runtime/westend/src/weights/mod.rs index efd18b38545a26e2bb0c829cff659ea2b04c0f72..2cebb9bc0d0a21fea288e6a52e6ed2f09de67d0c 100644 --- a/polkadot/runtime/westend/src/weights/mod.rs +++ b/polkadot/runtime/westend/src/weights/mod.rs @@ -28,6 +28,7 @@ pub mod pallet_fast_unstake; pub mod pallet_identity; pub mod pallet_indices; pub mod pallet_message_queue; +pub mod pallet_meta_tx; pub mod pallet_migrations; pub mod pallet_mmr; pub mod pallet_multisig; @@ -44,6 +45,7 @@ pub mod pallet_timestamp; pub mod pallet_transaction_payment; pub mod pallet_treasury; pub mod pallet_utility; +pub mod pallet_verify_signature; pub mod pallet_vesting; pub mod pallet_whitelist; pub mod pallet_xcm; diff --git a/polkadot/runtime/westend/src/weights/pallet_meta_tx.rs b/polkadot/runtime/westend/src/weights/pallet_meta_tx.rs new file mode 100644 index 0000000000000000000000000000000000000000..cf182ced3ce37e4eb3faea1e6c784d1ce27cc58c --- /dev/null +++ b/polkadot/runtime/westend/src/weights/pallet_meta_tx.rs @@ -0,0 +1,57 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see <http://www.gnu.org/licenses/>. + +//! Autogenerated weights for `pallet_meta_tx` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-11-08, STEPS: `50`, REPEAT: `2`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `cob`, CPU: `<UNKNOWN>` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("westend-dev")`, DB CACHE: 1024 + +// Executed Command: +// ./target/debug/polkadot +// benchmark +// pallet +// --chain=westend-dev +// --steps=50 +// --repeat=2 +// --pallet=pallet-meta-tx +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./polkadot/runtime/westend/src/weights/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weight functions for `pallet_meta_tx`. +pub struct WeightInfo<T>(PhantomData<T>); +impl<T: frame_system::Config> pallet_meta_tx::WeightInfo for WeightInfo<T> { + fn bare_dispatch() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 138_000_000 picoseconds. + Weight::from_parts(140_000_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + } +} diff --git a/polkadot/runtime/westend/src/weights/pallet_verify_signature.rs b/polkadot/runtime/westend/src/weights/pallet_verify_signature.rs new file mode 100644 index 0000000000000000000000000000000000000000..8796f0a613daee78151797c182e1221f2722fb13 --- /dev/null +++ b/polkadot/runtime/westend/src/weights/pallet_verify_signature.rs @@ -0,0 +1,59 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see <http://www.gnu.org/licenses/>. + +//! Autogenerated weights for `pallet_verify_signature` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2025-01-14, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-ys-ssygq-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("westend-dev")`, DB CACHE: 1024 + +// Executed Command: +// target/production/polkadot +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_verify_signature +// --chain=westend-dev +// --header=./polkadot/file_header.txt +// --output=./polkadot/runtime/westend/src/weights/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weight functions for `pallet_verify_signature`. +pub struct WeightInfo<T>(PhantomData<T>); +impl<T: frame_system::Config> pallet_verify_signature::WeightInfo for WeightInfo<T> { + fn verify_signature() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 42_915_000 picoseconds. + Weight::from_parts(43_522_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + } +} diff --git a/prdoc/pr_6428.prdoc b/prdoc/pr_6428.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..43391f8082600c8700af403ac93df0661e18d22c --- /dev/null +++ b/prdoc/pr_6428.prdoc @@ -0,0 +1,32 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: "FRAME: Meta Transaction" + +doc: + - audience: Runtime Dev + description: | + Introduces the meta-tx pallet that implements Meta Transactions. + + The meta transaction follows a layout similar to that of a regular transaction and can + leverage the same extensions that implement the `TransactionExtension` trait. Once signed and + shared by the signer, it can be relayed by a relayer. The relayer then submits a regular + transaction with the `meta-tx::dispatch` call, passing the signed meta transaction as an + argument. + + To see an example, refer to the mock setup and the `sign_and_execute_meta_tx` test case within + the pallet. + +crates: +- name: pallet-meta-tx + bump: major +- name: westend-runtime + bump: major +- name: kitchensink-runtime + bump: major +- name: polkadot-sdk + bump: major +- name: pallet-verify-signature + bump: patch +- name: pallet-example-authorization-tx-extension + bump: major diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index 94729a26b6d55f2a17a8991925635f91b866a596..659651fe837e76ca2d7d9c697918b675762d231a 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -2377,28 +2377,12 @@ impl pallet_transaction_storage::Config for Runtime { ConstU32<{ pallet_transaction_storage::DEFAULT_MAX_TRANSACTION_SIZE }>; } -#[cfg(feature = "runtime-benchmarks")] -pub struct VerifySignatureBenchmarkHelper; -#[cfg(feature = "runtime-benchmarks")] -impl pallet_verify_signature::BenchmarkHelper<MultiSignature, AccountId> - for VerifySignatureBenchmarkHelper -{ - fn create_signature(_entropy: &[u8], msg: &[u8]) -> (MultiSignature, AccountId) { - use sp_io::crypto::{sr25519_generate, sr25519_sign}; - use sp_runtime::traits::IdentifyAccount; - let public = sr25519_generate(0.into(), None); - let who_account: AccountId = MultiSigner::Sr25519(public).into_account().into(); - let signature = MultiSignature::Sr25519(sr25519_sign(0.into(), &public, msg).unwrap()); - (signature, who_account) - } -} - impl pallet_verify_signature::Config for Runtime { type Signature = MultiSignature; type AccountIdentifier = MultiSigner; type WeightInfo = pallet_verify_signature::weights::SubstrateWeight<Runtime>; #[cfg(feature = "runtime-benchmarks")] - type BenchmarkHelper = VerifySignatureBenchmarkHelper; + type BenchmarkHelper = (); } impl pallet_whitelist::Config for Runtime { @@ -2726,6 +2710,27 @@ impl pallet_parameters::Config for Runtime { type WeightInfo = (); } +pub type MetaTxExtension = ( + pallet_verify_signature::VerifySignature<Runtime>, + pallet_meta_tx::MetaTxMarker<Runtime>, + frame_system::CheckNonZeroSender<Runtime>, + frame_system::CheckSpecVersion<Runtime>, + frame_system::CheckTxVersion<Runtime>, + frame_system::CheckGenesis<Runtime>, + frame_system::CheckEra<Runtime>, + frame_system::CheckNonce<Runtime>, + frame_metadata_hash_extension::CheckMetadataHash<Runtime>, +); + +impl pallet_meta_tx::Config for Runtime { + type WeightInfo = (); + type RuntimeEvent = RuntimeEvent; + #[cfg(not(feature = "runtime-benchmarks"))] + type Extension = MetaTxExtension; + #[cfg(feature = "runtime-benchmarks")] + type Extension = pallet_meta_tx::WeightlessExtension<Runtime>; +} + #[frame_support::runtime] mod runtime { use super::*; @@ -3010,6 +3015,9 @@ mod runtime { pub type MultiBlockUnsigned = pallet_election_provider_multi_block::unsigned::Pallet<Runtime>; #[runtime::pallet_index(88)] pub type MultiBlockSigned = pallet_election_provider_multi_block::signed::Pallet<Runtime>; + + #[runtime::pallet_index(89)] + pub type MetaTx = pallet_meta_tx::Pallet<Runtime>; } impl TryFrom<RuntimeCall> for pallet_revive::Call<Runtime> { @@ -3277,6 +3285,7 @@ mod benches { [pallet_example_mbm, PalletExampleMbms] [pallet_asset_conversion_ops, AssetConversionMigration] [pallet_verify_signature, VerifySignature] + [pallet_meta_tx, MetaTx] ); } diff --git a/substrate/frame/examples/authorization-tx-extension/src/mock.rs b/substrate/frame/examples/authorization-tx-extension/src/mock.rs index a4a76fe207aa068e9ccd93db05252a3cad3b079a..9772e52050bd19b194d60e7d6bca141250aa460c 100644 --- a/substrate/frame/examples/authorization-tx-extension/src/mock.rs +++ b/substrate/frame/examples/authorization-tx-extension/src/mock.rs @@ -84,26 +84,12 @@ mod example_runtime { type Lookup = IdentityLookup<Self::AccountId>; } - #[cfg(feature = "runtime-benchmarks")] - pub struct BenchmarkHelper; - #[cfg(feature = "runtime-benchmarks")] - impl pallet_verify_signature::BenchmarkHelper<MultiSignature, AccountId> for BenchmarkHelper { - fn create_signature(_entropy: &[u8], msg: &[u8]) -> (MultiSignature, AccountId) { - use sp_io::crypto::{sr25519_generate, sr25519_sign}; - use sp_runtime::traits::IdentifyAccount; - let public = sr25519_generate(0.into(), None); - let who_account: AccountId = MultiSigner::Sr25519(public).into_account().into(); - let signature = MultiSignature::Sr25519(sr25519_sign(0.into(), &public, msg).unwrap()); - (signature, who_account) - } - } - impl pallet_verify_signature::Config for Runtime { type Signature = MultiSignature; type AccountIdentifier = MultiSigner; type WeightInfo = (); #[cfg(feature = "runtime-benchmarks")] - type BenchmarkHelper = BenchmarkHelper; + type BenchmarkHelper = (); } /// Type that enables any pallet to ask for a coowner origin. diff --git a/substrate/frame/meta-tx/Cargo.toml b/substrate/frame/meta-tx/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..78c18384d2fd2e993b84a15ff5609df7d2466f2a --- /dev/null +++ b/substrate/frame/meta-tx/Cargo.toml @@ -0,0 +1,65 @@ +[package] +name = "pallet-meta-tx" +description = "FRAME pallet enabling meta transactions." +license = "Apache-2.0" +version = "0.1.0" +edition.workspace = true +authors.workspace = true +homepage.workspace = true +repository.workspace = true + +[lints] +workspace = true + +[dependencies] +codec = { workspace = true, features = ["max-encoded-len"] } +docify = { workspace = true } +scale-info = { workspace = true, features = ["derive"] } +serde = { features = ["derive"], optional = true, workspace = true } + +frame-benchmarking = { workspace = true, optional = true } +frame-support = { workspace = true } +frame-system = { workspace = true } +sp-core = { workspace = true } +sp-io = { workspace = true } +sp-runtime = { workspace = true } +sp-std = { workspace = true } + +[dev-dependencies] +pallet-balances = { workspace = true, default-features = true } +pallet-transaction-payment = { workspace = true, default-features = true } +pallet-verify-signature = { workspace = true, default-features = true } +sp-keyring = { workspace = true, default-features = true } +sp-keystore = { workspace = true, default-features = true } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", + "scale-info/std", + "serde?/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "pallet-balances/runtime-benchmarks", + "pallet-transaction-payment/runtime-benchmarks", + "pallet-verify-signature/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "pallet-balances/try-runtime", + "pallet-transaction-payment/try-runtime", + "pallet-verify-signature/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/substrate/frame/meta-tx/src/benchmarking.rs b/substrate/frame/meta-tx/src/benchmarking.rs new file mode 100644 index 0000000000000000000000000000000000000000..49dd1388031f933ac980f03bb0e9acfc57f878f7 --- /dev/null +++ b/substrate/frame/meta-tx/src/benchmarking.rs @@ -0,0 +1,130 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![cfg(feature = "runtime-benchmarks")] + +use super::*; +use frame_benchmarking::v2::*; +use frame_support::traits::UnfilteredDispatchable; +use sp_runtime::impl_tx_ext_default; + +pub mod types { + use super::*; + use frame_support::traits::OriginTrait; + use sp_runtime::traits::DispatchInfoOf; + + type CallOf<T> = <T as frame_system::Config>::RuntimeCall; + + /// A weightless extension to facilitate the bare dispatch benchmark. + #[derive(TypeInfo, Eq, PartialEq, Clone, Encode, Decode, DecodeWithMemTracking)] + #[scale_info(skip_type_params(T))] + pub struct WeightlessExtension<T>(core::marker::PhantomData<T>); + impl<T: Config + Send + Sync> core::fmt::Debug for WeightlessExtension<T> { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!(f, "WeightlessExtension") + } + } + impl<T: Config + Send + Sync> Default for WeightlessExtension<T> { + fn default() -> Self { + WeightlessExtension(Default::default()) + } + } + impl<T: Config + Send + Sync> TransactionExtension<CallOf<T>> for WeightlessExtension<T> { + const IDENTIFIER: &'static str = "WeightlessExtension"; + type Implicit = (); + type Pre = (); + type Val = (); + fn weight(&self, _call: &CallOf<T>) -> Weight { + Weight::from_all(0) + } + fn validate( + &self, + mut origin: <CallOf<T> as Dispatchable>::RuntimeOrigin, + _: &CallOf<T>, + _: &DispatchInfoOf<CallOf<T>>, + _: usize, + _: (), + _: &impl Encode, + _: TransactionSource, + ) -> Result< + (ValidTransaction, Self::Val, <CallOf<T> as Dispatchable>::RuntimeOrigin), + TransactionValidityError, + > { + origin.set_caller_from_signed(whitelisted_caller()); + Ok((ValidTransaction::default(), (), origin)) + } + + impl_tx_ext_default!(CallOf<T>; prepare); + } +} + +fn assert_last_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) { + frame_system::Pallet::<T>::assert_last_event(generic_event.into()); +} + +#[benchmarks( + where + T: Config, + <T as Config>::Extension: Default, + )] +mod benchmarks { + use super::*; + + #[benchmark] + fn bare_dispatch() { + let meta_call = frame_system::Call::<T>::remark { remark: vec![] }.into(); + let meta_ext = T::Extension::default(); + let meta_ext_weight = meta_ext.weight(&meta_call); + + #[cfg(not(test))] + assert!( + meta_ext_weight.is_zero(), + "meta tx extension weight for the benchmarks must be zero. \ + use `pallet_meta_tx::WeightlessExtension` as `pallet_meta_tx::Config::Extension` \ + with the `runtime-benchmarks` feature enabled.", + ); + + let meta_tx = MetaTxFor::<T>::new(meta_call.clone(), 0u8, meta_ext.clone()); + + let caller = whitelisted_caller(); + let origin: <T as frame_system::Config>::RuntimeOrigin = + frame_system::RawOrigin::Signed(caller).into(); + let call = Call::<T>::dispatch { meta_tx: Box::new(meta_tx) }; + + #[block] + { + let _ = call.dispatch_bypass_filter(origin); + } + + let info = meta_call.get_dispatch_info(); + assert_last_event::<T>( + Event::Dispatched { + result: Ok(PostDispatchInfo { + actual_weight: Some(info.call_weight + meta_ext_weight), + pays_fee: Pays::Yes, + }), + } + .into(), + ); + } + + impl_benchmark_test_suite! { + Pallet, + crate::mock::new_test_ext(), + crate::mock::Runtime, + } +} diff --git a/substrate/frame/meta-tx/src/extension.rs b/substrate/frame/meta-tx/src/extension.rs new file mode 100644 index 0000000000000000000000000000000000000000..7c0f6c54d4902c25e562f46d7fd3d77092715327 --- /dev/null +++ b/substrate/frame/meta-tx/src/extension.rs @@ -0,0 +1,49 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::*; +use sp_runtime::impl_tx_ext_default; + +/// This type serves as a marker extension to differentiate meta-transactions from regular +/// transactions. It implements the `TransactionExtension` trait and carries constant implicit data +/// ("_meta_tx"). +#[derive(Encode, Decode, Clone, Eq, PartialEq, TypeInfo, DebugNoBound, DecodeWithMemTracking)] +#[scale_info(skip_type_params(T))] +pub struct MetaTxMarker<T> { + _phantom: core::marker::PhantomData<T>, +} + +impl<T> MetaTxMarker<T> { + /// Creates new `TransactionExtension` with implicit meta tx marked. + pub fn new() -> Self { + Self { _phantom: Default::default() } + } +} + +impl<T: Config + Send + Sync> TransactionExtension<T::RuntimeCall> for MetaTxMarker<T> { + const IDENTIFIER: &'static str = "MetaTxMarker"; + type Implicit = [u8; 8]; + type Val = (); + type Pre = (); + fn implicit(&self) -> Result<Self::Implicit, TransactionValidityError> { + Ok(*b"_meta_tx") + } + fn weight(&self, _: &T::RuntimeCall) -> Weight { + Weight::zero() + } + impl_tx_ext_default!(T::RuntimeCall; validate prepare); +} diff --git a/substrate/frame/meta-tx/src/lib.rs b/substrate/frame/meta-tx/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..25a38a5bb4f092d1738286d9ec99c01cc00362e1 --- /dev/null +++ b/substrate/frame/meta-tx/src/lib.rs @@ -0,0 +1,242 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! # Meta Tx (Meta Transaction) Pallet +//! +//! This pallet enables the dispatch of transactions that are authorized by one party (the signer) +//! and executed by an untrusted third party (the relayer), who covers the transaction fees. +//! +//! ## Pallet API +//! +//! See the [`pallet`] module for more information about the interfaces this pallet exposes, +//! including its configuration trait, dispatchables, storage items, events and errors. +//! +//! ## Overview +//! +//! The pallet provides a client-level API, typically not meant for direct use by end users. +//! A meta transaction, constructed with the help of a wallet, contains a target call, necessary +//! extensions, and the signer's signature. This transaction is then broadcast, and any interested +//! relayer can pick it up and execute it. The relayer submits a regular transaction via the +//! [`dispatch`](`Pallet::dispatch`) function, passing the meta transaction as an argument to +//! execute the target call on behalf of the signer while covering the fees. +//! +//! ### Example +#![doc = docify::embed!("src/tests.rs", sign_and_execute_meta_tx)] +//! +//! ## Low-Level / Implementation Details +//! +//! The structure of a meta transaction is identical to the +//! [`General`](sp_runtime::generic::Preamble::General) transaction. +//! It contains the target call along with a configurable set of extensions and its associated +//! version. Typically, these extensions include type like +//! `pallet_verify_signature::VerifySignature`, which provides the signer address +//! and the signature of the payload, encompassing the call and the meta-transaction’s +//! configurations, such as its mortality. The extensions follow the same [`TransactionExtension`] +//! contract, and common types such as [`frame_system::CheckGenesis`], +//! [`frame_system::CheckMortality`], [`frame_system::CheckNonce`], etc., are applicable in the +//! context of meta transactions. Check the `mock` setup for the example. + +#![cfg_attr(not(feature = "std"), no_std)] + +mod benchmarking; +#[cfg(test)] +mod mock; +#[cfg(all(test, not(feature = "runtime-benchmarks")))] +mod tests; +pub mod weights; +#[cfg(feature = "runtime-benchmarks")] +pub use benchmarking::types::WeightlessExtension; +pub use pallet::*; +pub use weights::WeightInfo; +mod extension; +pub use extension::MetaTxMarker; + +use core::ops::Add; +use frame_support::{ + dispatch::{DispatchInfo, GetDispatchInfo, PostDispatchInfo}, + pallet_prelude::*, +}; +use frame_system::{pallet_prelude::*, RawOrigin as SystemOrigin}; +use sp_runtime::{ + generic::ExtensionVersion, + traits::{ + AsTransactionAuthorizedOrigin, DispatchTransaction, Dispatchable, TransactionExtension, + }, +}; +use sp_std::prelude::*; + +/// Meta Transaction type. +/// +/// The data that is provided and signed by the signer and shared with the relayer. +#[derive(Encode, Decode, PartialEq, Eq, TypeInfo, Clone, RuntimeDebug, DecodeWithMemTracking)] +pub struct MetaTx<Call, Extension> { + /// The target call to be executed on behalf of the signer. + call: Call, + /// The extension version. + extension_version: ExtensionVersion, + /// The extension/s for the meta transaction. + extension: Extension, +} + +impl<Call, Extension> MetaTx<Call, Extension> { + /// Create a new meta transaction. + pub fn new(call: Call, extension_version: ExtensionVersion, extension: Extension) -> Self { + Self { call, extension_version, extension } + } +} + +/// The [`MetaTx`] for the given config. +pub type MetaTxFor<T> = MetaTx<<T as frame_system::Config>::RuntimeCall, <T as Config>::Extension>; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + + #[pallet::config] + pub trait Config: + frame_system::Config< + RuntimeCall: Dispatchable< + Info = DispatchInfo, + PostInfo = PostDispatchInfo, + RuntimeOrigin = <Self as frame_system::Config>::RuntimeOrigin, + >, + RuntimeOrigin: AsTransactionAuthorizedOrigin + From<SystemOrigin<Self::AccountId>>, + > + { + /// Weight information for calls in this pallet. + type WeightInfo: WeightInfo; + /// The overarching event type. + type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>; + /// Transaction extension/s for meta transactions. + /// + /// The extensions that must be present in every meta transaction. This generally includes + /// extensions like `pallet_verify_signature::VerifySignature`, + /// [frame_system::CheckSpecVersion], [frame_system::CheckTxVersion], + /// [frame_system::CheckGenesis], [frame_system::CheckMortality], + /// [frame_system::CheckNonce], etc. Check the `mock` setup for the example. + /// + /// The types implementing the [`TransactionExtension`] trait can be composed into a tuple + /// type that will implement the same trait by piping invocations through each type. + /// + /// In the `runtime-benchmarks` environment the type must implement [`Default`] trait. + /// The extension must provide an origin and the extension's weight must be zero. Use + /// `pallet_meta_tx::WeightlessExtension` type when the `runtime-benchmarks` feature + /// enabled. + type Extension: TransactionExtension<<Self as frame_system::Config>::RuntimeCall>; + } + + #[pallet::error] + pub enum Error<T> { + /// Invalid proof (e.g. signature). + BadProof, + /// The meta transaction is not yet valid (e.g. nonce too high). + Future, + /// The meta transaction is outdated (e.g. nonce too low). + Stale, + /// The meta transactions's birth block is ancient. + AncientBirthBlock, + /// The transaction extension did not authorize any origin. + UnknownOrigin, + /// The meta transaction is invalid. + Invalid, + } + + #[pallet::event] + #[pallet::generate_deposit(pub(crate) fn deposit_event)] + pub enum Event<T: Config> { + /// A meta transaction has been dispatched. + /// + /// Contains the dispatch result of the meta transaction along with post-dispatch + /// information. + Dispatched { result: DispatchResultWithPostInfo }, + } + + #[pallet::pallet] + pub struct Pallet<T>(_); + + #[pallet::call] + impl<T: Config> Pallet<T> { + /// Dispatch a given meta transaction. + /// + /// - `_origin`: Can be any kind of origin. + /// - `meta_tx`: Meta Transaction with a target call to be dispatched. + #[pallet::call_index(0)] + #[pallet::weight({ + let dispatch_info = meta_tx.call.get_dispatch_info(); + let extension_weight = meta_tx.extension.weight(&meta_tx.call); + let bare_call_weight = T::WeightInfo::bare_dispatch(); + ( + dispatch_info.call_weight.add(extension_weight).add(bare_call_weight), + dispatch_info.class, + ) + })] + pub fn dispatch( + _origin: OriginFor<T>, + meta_tx: Box<MetaTxFor<T>>, + ) -> DispatchResultWithPostInfo { + let origin = SystemOrigin::None; + let meta_tx_size = meta_tx.encoded_size(); + // `info` with worst-case call weight and extension weight. + let info = { + let mut info = meta_tx.call.get_dispatch_info(); + info.extension_weight = meta_tx.extension.weight(&meta_tx.call); + info + }; + + // dispatch the meta transaction. + let meta_dispatch_res = meta_tx + .extension + .dispatch_transaction( + origin.into(), + meta_tx.call, + &info, + meta_tx_size, + meta_tx.extension_version, + ) + .map_err(Error::<T>::from)?; + + Self::deposit_event(Event::Dispatched { result: meta_dispatch_res }); + + // meta weight after possible refunds. + let meta_weight = meta_dispatch_res + .map_or_else(|err| err.post_info.actual_weight, |info| info.actual_weight) + .unwrap_or(info.total_weight()); + + Ok((Some(T::WeightInfo::bare_dispatch().saturating_add(meta_weight)), true.into()) + .into()) + } + } + + /// Implements [`From<TransactionValidityError>`] for [`Error`] by mapping the relevant error + /// variants. + impl<T> From<TransactionValidityError> for Error<T> { + fn from(err: TransactionValidityError) -> Self { + use TransactionValidityError::*; + match err { + Unknown(_) => Error::<T>::Invalid, + Invalid(err) => match err { + InvalidTransaction::BadProof => Error::<T>::BadProof, + InvalidTransaction::Future => Error::<T>::Future, + InvalidTransaction::Stale => Error::<T>::Stale, + InvalidTransaction::AncientBirthBlock => Error::<T>::AncientBirthBlock, + InvalidTransaction::UnknownOrigin => Error::<T>::UnknownOrigin, + _ => Error::<T>::Invalid, + }, + } + } + } +} diff --git a/substrate/frame/meta-tx/src/mock.rs b/substrate/frame/meta-tx/src/mock.rs new file mode 100644 index 0000000000000000000000000000000000000000..c361fcceca181ad82c1c26b66886a46a9aa02075 --- /dev/null +++ b/substrate/frame/meta-tx/src/mock.rs @@ -0,0 +1,147 @@ +// This file is part of Substrate. +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Mock setup for tests. + +#![cfg(any(test, feature = "runtime-benchmarks"))] + +use crate as pallet_meta_tx; +use crate::*; +use frame_support::{ + construct_runtime, derive_impl, + weights::{FixedFee, NoFee}, +}; +use sp_core::ConstU8; +use sp_keystore::{testing::MemoryKeystore, KeystoreExt}; +use sp_runtime::{ + traits::{IdentifyAccount, IdentityLookup, Verify}, + MultiSignature, +}; + +pub type Balance = u64; + +pub type Signature = MultiSignature; +pub type AccountId = <<Signature as Verify>::Signer as IdentifyAccount>::AccountId; + +#[cfg(feature = "runtime-benchmarks")] +pub type MetaTxExtension = crate::benchmarking::types::WeightlessExtension<Runtime>; + +#[cfg(not(feature = "runtime-benchmarks"))] +pub use tx_ext::*; + +#[cfg(not(feature = "runtime-benchmarks"))] +mod tx_ext { + use super::*; + + pub type UncheckedExtrinsic = + sp_runtime::generic::UncheckedExtrinsic<AccountId, RuntimeCall, Signature, TxExtension>; + + /// Transaction extension. + pub type TxExtension = (pallet_verify_signature::VerifySignature<Runtime>, TxBareExtension); + + /// Transaction extension without signature information. + /// + /// Helper type used to decode the part of the extension which should be signed. + pub type TxBareExtension = ( + frame_system::CheckNonZeroSender<Runtime>, + frame_system::CheckSpecVersion<Runtime>, + frame_system::CheckTxVersion<Runtime>, + frame_system::CheckGenesis<Runtime>, + frame_system::CheckMortality<Runtime>, + frame_system::CheckNonce<Runtime>, + frame_system::CheckWeight<Runtime>, + pallet_transaction_payment::ChargeTransactionPayment<Runtime>, + ); + + pub const META_EXTENSION_VERSION: ExtensionVersion = 0; + + /// Meta transaction extension. + pub type MetaTxExtension = + (pallet_verify_signature::VerifySignature<Runtime>, MetaTxBareExtension); + + /// Meta transaction extension without signature information. + /// + /// Helper type used to decode the part of the extension which should be signed. + pub type MetaTxBareExtension = ( + MetaTxMarker<Runtime>, + frame_system::CheckNonZeroSender<Runtime>, + frame_system::CheckSpecVersion<Runtime>, + frame_system::CheckTxVersion<Runtime>, + frame_system::CheckGenesis<Runtime>, + frame_system::CheckMortality<Runtime>, + frame_system::CheckNonce<Runtime>, + ); +} + +impl Config for Runtime { + type WeightInfo = (); + type RuntimeEvent = RuntimeEvent; + type Extension = MetaTxExtension; +} + +impl pallet_verify_signature::Config for Runtime { + type Signature = MultiSignature; + type AccountIdentifier = <Signature as Verify>::Signer; + type WeightInfo = (); + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = (); +} + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for Runtime { + type AccountId = AccountId; + type Lookup = IdentityLookup<Self::AccountId>; + type Block = frame_system::mocking::MockBlock<Runtime>; + type AccountData = pallet_balances::AccountData<<Self as pallet_balances::Config>::Balance>; +} + +#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] +impl pallet_balances::Config for Runtime { + type ReserveIdentifier = [u8; 8]; + type AccountStore = System; +} + +pub const TX_FEE: u32 = 10; + +impl pallet_transaction_payment::Config for Runtime { + type WeightInfo = (); + type RuntimeEvent = RuntimeEvent; + type OnChargeTransaction = pallet_transaction_payment::FungibleAdapter<Balances, ()>; + type OperationalFeeMultiplier = ConstU8<1>; + type WeightToFee = FixedFee<TX_FEE, Balance>; + type LengthToFee = NoFee<Balance>; + type FeeMultiplierUpdate = (); +} + +construct_runtime!( + pub enum Runtime { + System: frame_system, + Balances: pallet_balances, + MetaTx: pallet_meta_tx, + TxPayment: pallet_transaction_payment, + VerifySignature: pallet_verify_signature, + } +); + +pub(crate) fn new_test_ext() -> sp_io::TestExternalities { + let mut ext = sp_io::TestExternalities::new(Default::default()); + ext.execute_with(|| { + frame_system::GenesisConfig::<Runtime>::default().build(); + System::set_block_number(1); + }); + ext.register_extension(KeystoreExt::new(MemoryKeystore::new())); + ext +} diff --git a/substrate/frame/meta-tx/src/tests.rs b/substrate/frame/meta-tx/src/tests.rs new file mode 100644 index 0000000000000000000000000000000000000000..bde1de2f6091bcfb2f5674e1f5ea572b3107529a --- /dev/null +++ b/substrate/frame/meta-tx/src/tests.rs @@ -0,0 +1,398 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::*; +use frame_support::traits::tokens::fungible::Inspect; +use mock::*; +use sp_io::hashing::blake2_256; +use sp_keyring::Sr25519Keyring; +use sp_runtime::{ + generic::Era, + traits::{Applyable, Checkable, Hash, IdentityLookup}, + DispatchErrorWithPostInfo, MultiSignature, +}; + +type VerifySignatureExt = pallet_verify_signature::VerifySignature<Runtime>; + +fn create_tx_bare_ext(account: AccountId) -> TxBareExtension { + ( + frame_system::CheckNonZeroSender::<Runtime>::new(), + frame_system::CheckSpecVersion::<Runtime>::new(), + frame_system::CheckTxVersion::<Runtime>::new(), + frame_system::CheckGenesis::<Runtime>::new(), + frame_system::CheckMortality::<Runtime>::from(Era::immortal()), + frame_system::CheckNonce::<Runtime>::from( + frame_system::Pallet::<Runtime>::account(&account).nonce, + ), + frame_system::CheckWeight::<Runtime>::new(), + pallet_transaction_payment::ChargeTransactionPayment::<Runtime>::from(0), + ) +} + +pub fn create_meta_tx_bare_ext(account: AccountId) -> MetaTxBareExtension { + ( + MetaTxMarker::new(), + frame_system::CheckNonZeroSender::<Runtime>::new(), + frame_system::CheckSpecVersion::<Runtime>::new(), + frame_system::CheckTxVersion::<Runtime>::new(), + frame_system::CheckGenesis::<Runtime>::new(), + frame_system::CheckMortality::<Runtime>::from(Era::immortal()), + frame_system::CheckNonce::<Runtime>::from( + frame_system::Pallet::<Runtime>::account(&account).nonce, + ), + ) +} + +fn create_signature<Call: Encode, Ext: Encode + TransactionExtension<RuntimeCall>>( + call: Call, + ext: Ext, + signer: Sr25519Keyring, +) -> MultiSignature { + MultiSignature::Sr25519( + (META_EXTENSION_VERSION, call, ext.clone(), ext.implicit().unwrap()) + .using_encoded(|e| signer.sign(&blake2_256(e))), + ) +} + +fn force_set_balance(account: AccountId) -> Balance { + let balance = Balances::minimum_balance() * 100; + Balances::force_set_balance(RuntimeOrigin::root(), account.into(), balance).unwrap(); + balance +} + +fn apply_extrinsic(uxt: UncheckedExtrinsic) -> DispatchResultWithPostInfo { + let uxt_info = uxt.get_dispatch_info(); + let uxt_len = uxt.using_encoded(|e| e.len()); + let xt = <UncheckedExtrinsic as Checkable<IdentityLookup<AccountId>>>::check( + uxt, + &Default::default(), + ) + .unwrap(); + xt.apply::<Runtime>(&uxt_info, uxt_len).unwrap() +} + +#[docify::export] +#[test] +fn sign_and_execute_meta_tx() { + new_test_ext().execute_with(|| { + // meta tx signer + let alice_keyring = Sr25519Keyring::Alice; + // meta tx relayer + let bob_keyring = Sr25519Keyring::Bob; + + let alice_account: AccountId = alice_keyring.public().into(); + let bob_account: AccountId = bob_keyring.public().into(); + + let tx_fee: Balance = (2 * TX_FEE).into(); // base tx fee + weight fee + let alice_balance = force_set_balance(alice_account.clone()); + let bob_balance = force_set_balance(bob_account.clone()); + + // Alice builds a meta transaction. + + let remark_call = + RuntimeCall::System(frame_system::Call::remark_with_event { remark: vec![1] }); + let meta_tx_bare_ext = create_meta_tx_bare_ext(alice_account.clone()); + let meta_tx_sig = + create_signature(remark_call.clone(), meta_tx_bare_ext.clone(), alice_keyring); + let meta_tx_ext = ( + VerifySignatureExt::new_with_signature(meta_tx_sig, alice_account.clone()), + // append signed part. + meta_tx_bare_ext, + ); + + let meta_tx = MetaTxFor::<Runtime>::new( + remark_call.clone(), + META_EXTENSION_VERSION, + meta_tx_ext.clone(), + ); + + // Encode and share with the world. + let meta_tx_encoded = meta_tx.encode(); + + // Bob acts as meta transaction relayer. + + let meta_tx = MetaTxFor::<Runtime>::decode(&mut &meta_tx_encoded[..]).unwrap(); + let call = RuntimeCall::MetaTx(Call::dispatch { meta_tx: Box::new(meta_tx.clone()) }); + let tx_bare_ext = create_tx_bare_ext(bob_account.clone()); + let tx_sig = create_signature(call.clone(), tx_bare_ext.clone(), bob_keyring); + let tx_ext = ( + VerifySignatureExt::new_with_signature(tx_sig, bob_account.clone()), + // append signed part + tx_bare_ext, + ); + + let uxt = UncheckedExtrinsic::new_transaction(call.clone(), tx_ext.clone()); + + // Check Extrinsic validity and apply it. + let result = apply_extrinsic(uxt); + + // Asserting the results and make sure the weight is correct. + + let tx_weight = tx_ext.weight(&call) + <Runtime as Config>::WeightInfo::bare_dispatch(); + let meta_tx_weight = remark_call + .get_dispatch_info() + .call_weight + .add(meta_tx_ext.weight(&remark_call)); + + assert_eq!( + result, + Ok(PostDispatchInfo { + actual_weight: Some(meta_tx_weight + tx_weight), + pays_fee: Pays::Yes, + }) + ); + + System::assert_has_event(RuntimeEvent::MetaTx(crate::Event::Dispatched { + result: Ok(PostDispatchInfo { + actual_weight: Some(meta_tx_weight), + pays_fee: Pays::Yes, + }), + })); + + System::assert_has_event(RuntimeEvent::System(frame_system::Event::Remarked { + sender: alice_account.clone(), + hash: <Runtime as frame_system::Config>::Hashing::hash(&[1]), + })); + + // Alice balance is unchanged, Bob paid the transaction fee. + assert_eq!(alice_balance, Balances::free_balance(alice_account)); + assert_eq!(bob_balance - tx_fee, Balances::free_balance(bob_account)); + }); +} + +#[test] +fn invalid_signature() { + new_test_ext().execute_with(|| { + // meta tx signer + let alice_keyring = Sr25519Keyring::Alice; + // meta tx relayer + let bob_keyring = Sr25519Keyring::Bob; + + let alice_account: AccountId = alice_keyring.public().into(); + let bob_account: AccountId = bob_keyring.public().into(); + + let tx_fee: Balance = (2 * TX_FEE).into(); // base tx fee + weight fee + let alice_balance = force_set_balance(alice_account.clone()); + let bob_balance = force_set_balance(bob_account.clone()); + + // Alice builds a meta transaction. + + let remark_call = + RuntimeCall::System(frame_system::Call::remark_with_event { remark: vec![1] }); + let meta_tx_bare_ext = create_meta_tx_bare_ext(alice_account.clone()); + // signature is invalid since it's signed by charlie instead of alice. + let invalid_meta_tx_sig = create_signature( + remark_call.clone(), + meta_tx_bare_ext.clone(), + Sr25519Keyring::Charlie, + ); + let meta_tx_ext = ( + VerifySignatureExt::new_with_signature(invalid_meta_tx_sig, alice_account.clone()), + // append signed part. + meta_tx_bare_ext, + ); + + let meta_tx = MetaTxFor::<Runtime>::new( + remark_call.clone(), + META_EXTENSION_VERSION, + meta_tx_ext.clone(), + ); + + // Encode and share with the world. + let meta_tx_encoded = meta_tx.encode(); + + // Bob acts as meta transaction relayer. + + let meta_tx = MetaTxFor::<Runtime>::decode(&mut &meta_tx_encoded[..]).unwrap(); + let call = RuntimeCall::MetaTx(Call::dispatch { meta_tx: Box::new(meta_tx.clone()) }); + let tx_bare_ext = create_tx_bare_ext(bob_account.clone()); + let tx_sig = create_signature(call.clone(), tx_bare_ext.clone(), bob_keyring); + let tx_ext = ( + VerifySignatureExt::new_with_signature(tx_sig, bob_account.clone()), + // append signed part + tx_bare_ext, + ); + + let uxt = UncheckedExtrinsic::new_transaction(call, tx_ext); + + // Check Extrinsic validity and apply it. + let result = apply_extrinsic(uxt); + + // Asserting the results. + + assert_eq!(result.unwrap_err().error, Error::<Runtime>::BadProof.into()); + + // Alice balance is unchanged, Bob paid the transaction fee. + assert_eq!(alice_balance, Balances::free_balance(alice_account)); + assert_eq!(bob_balance - tx_fee, Balances::free_balance(bob_account)); + }); +} + +#[cfg(not(feature = "runtime-benchmarks"))] +#[test] +fn meta_tx_extension_work() { + new_test_ext().execute_with(|| { + // meta tx signer + let alice_keyring = Sr25519Keyring::Alice; + // meta tx relayer + let bob_keyring = Sr25519Keyring::Bob; + + let alice_account: AccountId = alice_keyring.public().into(); + let bob_account: AccountId = bob_keyring.public().into(); + + let tx_fee: Balance = (2 * TX_FEE).into(); // base tx fee + weight fee + let alice_balance = force_set_balance(alice_account.clone()); + let bob_balance = force_set_balance(bob_account.clone()); + + // Alice builds a meta transaction. + + let remark_call = + RuntimeCall::System(frame_system::Call::remark_with_event { remark: vec![1] }); + + let meta_tx_bare_ext = create_meta_tx_bare_ext(alice_account.clone()); + let meta_tx_sig = + create_signature(remark_call.clone(), meta_tx_bare_ext.clone(), alice_keyring); + let meta_tx_ext = ( + VerifySignatureExt::new_with_signature(meta_tx_sig, alice_account.clone()), + // append signed part. + meta_tx_bare_ext, + ); + + let meta_tx = MetaTxFor::<Runtime>::new(remark_call, META_EXTENSION_VERSION, meta_tx_ext); + + // Encode and share with the world. + let meta_tx_encoded = meta_tx.encode(); + + // Bob acts as meta transaction relayer. + + let meta_tx = MetaTxFor::<Runtime>::decode(&mut &meta_tx_encoded[..]).unwrap(); + let call = RuntimeCall::MetaTx(Call::dispatch { meta_tx: Box::new(meta_tx.clone()) }); + let tx_bare_ext = create_tx_bare_ext(bob_account.clone()); + let tx_sig = create_signature(call.clone(), tx_bare_ext.clone(), bob_keyring); + let tx_ext = ( + VerifySignatureExt::new_with_signature(tx_sig, bob_account.clone()), + // append signed part + tx_bare_ext, + ); + + let uxt = UncheckedExtrinsic::new_transaction(call, tx_ext); + + // increment alice's nonce to invalidate the meta tx and verify that the + // meta tx extension works. + frame_system::Pallet::<Runtime>::inc_account_nonce(alice_account.clone()); + + // Check Extrinsic validity and apply it. + let result = apply_extrinsic(uxt); + + // Asserting the results. + assert_eq!(result.unwrap_err().error, Error::<Runtime>::Stale.into()); + + // Alice balance is unchanged, Bob paid the transaction fee. + assert_eq!(alice_balance, Balances::free_balance(alice_account)); + assert_eq!(bob_balance - tx_fee, Balances::free_balance(bob_account)); + }); +} + +#[test] +fn meta_tx_call_fails() { + new_test_ext().execute_with(|| { + // meta tx signer + let alice_keyring = Sr25519Keyring::Alice; + // meta tx relayer + let bob_keyring = Sr25519Keyring::Bob; + + let alice_account: AccountId = alice_keyring.public().into(); + let bob_account: AccountId = bob_keyring.public().into(); + + let tx_fee: Balance = (2 * TX_FEE).into(); // base tx fee + weight fee + let alice_balance = force_set_balance(alice_account.clone()); + let bob_balance = force_set_balance(bob_account.clone()); + + // Alice builds a meta transaction. + + // transfer more than alice has + let transfer_call = RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death { + dest: bob_account.clone(), + value: alice_balance * 2, + }); + + let meta_tx_bare_ext = create_meta_tx_bare_ext(alice_account.clone()); + let meta_tx_sig = + create_signature(transfer_call.clone(), meta_tx_bare_ext.clone(), alice_keyring); + let meta_tx_ext = ( + VerifySignatureExt::new_with_signature(meta_tx_sig, alice_account.clone()), + // append signed part. + meta_tx_bare_ext, + ); + + let meta_tx = MetaTxFor::<Runtime>::new( + transfer_call.clone(), + META_EXTENSION_VERSION, + meta_tx_ext.clone(), + ); + + // Encode and share with the world. + let meta_tx_encoded = meta_tx.encode(); + + // Bob acts as meta transaction relayer. + + let meta_tx = MetaTxFor::<Runtime>::decode(&mut &meta_tx_encoded[..]).unwrap(); + let call = RuntimeCall::MetaTx(Call::dispatch { meta_tx: Box::new(meta_tx.clone()) }); + let tx_bare_ext = create_tx_bare_ext(bob_account.clone()); + let tx_sig = create_signature(call.clone(), tx_bare_ext.clone(), bob_keyring); + let tx_ext = ( + VerifySignatureExt::new_with_signature(tx_sig, bob_account.clone()), + // append signed part + tx_bare_ext, + ); + + let uxt = UncheckedExtrinsic::new_transaction(call.clone(), tx_ext.clone()); + + // Check Extrinsic validity and apply it. + let result = apply_extrinsic(uxt); + + // Asserting the results and make sure the weight is correct. + + let tx_weight = tx_ext.weight(&call) + <Runtime as Config>::WeightInfo::bare_dispatch(); + let meta_tx_weight = transfer_call + .get_dispatch_info() + .call_weight + .add(meta_tx_ext.weight(&transfer_call)); + + assert_eq!( + result, + Ok(PostDispatchInfo { + actual_weight: Some(meta_tx_weight + tx_weight), + pays_fee: Pays::Yes, + }) + ); + + System::assert_has_event(RuntimeEvent::MetaTx(crate::Event::Dispatched { + result: Err(DispatchErrorWithPostInfo { + post_info: PostDispatchInfo { + actual_weight: Some(meta_tx_weight), + pays_fee: Pays::Yes, + }, + error: sp_runtime::DispatchError::Token(sp_runtime::TokenError::FundsUnavailable), + }), + })); + + // Alice balance is unchanged, Bob paid the transaction fee. + assert_eq!(alice_balance, Balances::free_balance(alice_account)); + assert_eq!(bob_balance - tx_fee, Balances::free_balance(bob_account)); + }); +} diff --git a/substrate/frame/meta-tx/src/weights.rs b/substrate/frame/meta-tx/src/weights.rs new file mode 100644 index 0000000000000000000000000000000000000000..fdc13d115941bcac4e2b1e8560891394d0be70b3 --- /dev/null +++ b/substrate/frame/meta-tx/src/weights.rs @@ -0,0 +1,86 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Autogenerated weights for `pallet_meta_tx` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2025-01-15, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-ys-ssygq-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` + +// Executed Command: +// target/production/substrate-node +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_meta_tx +// --chain=dev +// --header=./substrate/HEADER-APACHE2 +// --output=./substrate/frame/meta-tx/src/weights.rs +// --template=./substrate/.maintain/frame-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use core::marker::PhantomData; + +/// Weight functions needed for `pallet_meta_tx`. +pub trait WeightInfo { + fn bare_dispatch() -> Weight; +} + +/// Weights for `pallet_meta_tx` using the Substrate node and recommended hardware. +pub struct SubstrateWeight<T>(PhantomData<T>); +impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { + /// Storage: `SafeMode::EnteredUntil` (r:1 w:0) + /// Proof: `SafeMode::EnteredUntil` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `TxPause::PausedCalls` (r:1 w:0) + /// Proof: `TxPause::PausedCalls` (`max_values`: None, `max_size`: Some(532), added: 3007, mode: `MaxEncodedLen`) + fn bare_dispatch() -> Weight { + // Proof Size summary in bytes: + // Measured: `145` + // Estimated: `3997` + // Minimum execution time: 13_110_000 picoseconds. + Weight::from_parts(13_605_000, 3997) + .saturating_add(T::DbWeight::get().reads(2_u64)) + } +} + +// For backwards compatibility and tests. +impl WeightInfo for () { + /// Storage: `SafeMode::EnteredUntil` (r:1 w:0) + /// Proof: `SafeMode::EnteredUntil` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `TxPause::PausedCalls` (r:1 w:0) + /// Proof: `TxPause::PausedCalls` (`max_values`: None, `max_size`: Some(532), added: 3007, mode: `MaxEncodedLen`) + fn bare_dispatch() -> Weight { + // Proof Size summary in bytes: + // Measured: `145` + // Estimated: `3997` + // Minimum execution time: 13_110_000 picoseconds. + Weight::from_parts(13_605_000, 3997) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + } +} diff --git a/substrate/frame/verify-signature/src/benchmarking.rs b/substrate/frame/verify-signature/src/benchmarking.rs index 99e893e6f6aba228dd9564403d59b84acf0013d7..42c08c8888d47beb9d72d05ab53dfe0f22f2f0c6 100644 --- a/substrate/frame/verify-signature/src/benchmarking.rs +++ b/substrate/frame/verify-signature/src/benchmarking.rs @@ -32,16 +32,29 @@ use frame_support::{ pallet_prelude::TransactionSource, }; use frame_system::{Call as SystemCall, RawOrigin}; -use sp_io::hashing::blake2_256; +use sp_io::{ + crypto::{sr25519_generate, sr25519_sign}, + hashing::blake2_256, +}; use sp_runtime::{ generic::ExtensionVersion, - traits::{AsTransactionAuthorizedOrigin, DispatchTransaction, Dispatchable}, + traits::{AsTransactionAuthorizedOrigin, DispatchTransaction, Dispatchable, IdentifyAccount}, + AccountId32, MultiSignature, MultiSigner, }; pub trait BenchmarkHelper<Signature, Signer> { fn create_signature(entropy: &[u8], msg: &[u8]) -> (Signature, Signer); } +impl BenchmarkHelper<MultiSignature, AccountId32> for () { + fn create_signature(_entropy: &[u8], msg: &[u8]) -> (MultiSignature, AccountId32) { + let public = sr25519_generate(0.into(), None); + let who_account: AccountId32 = MultiSigner::Sr25519(public).into_account().into(); + let signature = MultiSignature::Sr25519(sr25519_sign(0.into(), &public, msg).unwrap()); + (signature, who_account) + } +} + #[benchmarks(where T: Config + Send + Sync, T::RuntimeCall: Dispatchable<Info = DispatchInfo> + GetDispatchInfo, diff --git a/umbrella/Cargo.toml b/umbrella/Cargo.toml index a1998e7bf2abd4eb8c743164dfd1f90e394003a1..98c30fd490dee01d3f6d168ba92693838dc97f2f 100644 --- a/umbrella/Cargo.toml +++ b/umbrella/Cargo.toml @@ -102,6 +102,7 @@ std = [ "pallet-lottery?/std", "pallet-membership?/std", "pallet-message-queue?/std", + "pallet-meta-tx?/std", "pallet-migrations?/std", "pallet-mixnet?/std", "pallet-mmr?/std", @@ -299,6 +300,7 @@ runtime-benchmarks = [ "pallet-lottery?/runtime-benchmarks", "pallet-membership?/runtime-benchmarks", "pallet-message-queue?/runtime-benchmarks", + "pallet-meta-tx?/runtime-benchmarks", "pallet-migrations?/runtime-benchmarks", "pallet-mmr?/runtime-benchmarks", "pallet-multisig?/runtime-benchmarks", @@ -439,6 +441,7 @@ try-runtime = [ "pallet-lottery?/try-runtime", "pallet-membership?/try-runtime", "pallet-message-queue?/try-runtime", + "pallet-meta-tx?/try-runtime", "pallet-migrations?/try-runtime", "pallet-mixnet?/try-runtime", "pallet-mmr?/try-runtime", @@ -511,6 +514,7 @@ serde = [ "pallet-conviction-voting?/serde", "pallet-democracy?/serde", "pallet-message-queue?/serde", + "pallet-meta-tx?/serde", "pallet-offences?/serde", "pallet-parameters?/serde", "pallet-referenda?/serde", @@ -654,6 +658,7 @@ runtime-full = [ "pallet-lottery", "pallet-membership", "pallet-message-queue", + "pallet-meta-tx", "pallet-migrations", "pallet-mixnet", "pallet-mmr", @@ -1500,6 +1505,11 @@ default-features = false optional = true path = "../substrate/frame/message-queue" +[dependencies.pallet-meta-tx] +default-features = false +optional = true +path = "../substrate/frame/meta-tx" + [dependencies.pallet-migrations] default-features = false optional = true diff --git a/umbrella/src/lib.rs b/umbrella/src/lib.rs index 19f80aac4a451d4d36cd0c58e7c91870fb80e625..26e8d8b5acc16c23e80d1a40d5d103b69701b05f 100644 --- a/umbrella/src/lib.rs +++ b/umbrella/src/lib.rs @@ -503,6 +503,10 @@ pub use pallet_membership; #[cfg(feature = "pallet-message-queue")] pub use pallet_message_queue; +/// FRAME pallet enabling meta transactions. +#[cfg(feature = "pallet-meta-tx")] +pub use pallet_meta_tx; + /// FRAME pallet to execute multi-block migrations. #[cfg(feature = "pallet-migrations")] pub use pallet_migrations;