From ac3f14d23ba0f4d7e92d70c2fe6b202ccdcb3a62 Mon Sep 17 00:00:00 2001
From: Sam Johnson <sam@durosoft.com>
Date: Fri, 8 Dec 2023 00:40:26 -0500
Subject: [PATCH] Tasks: general system for recognizing and executing service
 work (#1343)

`polkadot-sdk` version of original tasks PR located here:
https://github.com/paritytech/substrate/pull/14329

Fixes #206

## Status
- [x] Generic `Task` trait
- [x] `RuntimeTask` aggregated enum, compatible with
`construct_runtime!`
- [x] Casting between `Task` and `RuntimeTask` without needing `dyn` or
`Box`
- [x] Tasks Example pallet
- [x] Runtime tests for Tasks example pallet
- [x] Parsing for task-related macros
- [x] Retrofit parsing to make macros optional
- [x] Expansion for task-related macros
- [x] Adds support for args in tasks
- [x] Retrofit tasks example pallet to use macros instead of manual
syntax
- [x] Weights
- [x] Cleanup
- [x] UI tests
- [x] Docs

## Target Syntax
Adapted from
https://github.com/paritytech/polkadot-sdk/issues/206#issue-1865172283

```rust
// NOTE: this enum is optional and is auto-generated by the other macros if not present
#[pallet::task]
pub enum Task<T: Config> {
    AddNumberIntoTotal {
        i: u32,
    }
}

/// Some running total.
#[pallet::storage]
pub(super) type Total<T: Config<I>, I: 'static = ()> =
StorageValue<_, (u32, u32), ValueQuery>;

/// Numbers to be added into the total.
#[pallet::storage]
pub(super) type Numbers<T: Config<I>, I: 'static = ()> =
StorageMap<_, Twox64Concat, u32, u32, OptionQuery>;

#[pallet::tasks_experimental]
impl<T: Config<I>, I: 'static> Pallet<T, I> {
	/// Add a pair of numbers into the totals and remove them.
	#[pallet::task_list(Numbers::<T, I>::iter_keys())]
	#[pallet::task_condition(|i| Numbers::<T, I>::contains_key(i))]
	#[pallet::task_index(0)]
	pub fn add_number_into_total(i: u32) -> DispatchResult {
		let v = Numbers::<T, I>::take(i).ok_or(Error::<T, I>::NotFound)?;
		Total::<T, I>::mutate(|(total_keys, total_values)| {
			*total_keys += i;
			*total_values += v;
		});
		Ok(())
	}
}
```

---------

Co-authored-by: Nikhil Gupta <17176722+gupnik@users.noreply.github.com>
Co-authored-by: kianenigma <kian@parity.io>
Co-authored-by: Nikhil Gupta <>
Co-authored-by: Gavin Wood <gavin@parity.io>
Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>
Co-authored-by: gupnik <nikhilgupta.iitk@gmail.com>
---
 Cargo.lock                                    |  19 +
 Cargo.toml                                    |   1 +
 .../pallets/collator-selection/src/mock.rs    |   3 +-
 .../pallets/template/src/mock.rs              |   3 +-
 prdoc/pr_1343.prdoc                           |  29 +
 substrate/bin/node/runtime/Cargo.toml         |   4 +
 substrate/bin/node/runtime/src/lib.rs         |   7 +
 substrate/frame/examples/Cargo.toml           |   3 +
 .../frame/examples/default-config/src/lib.rs  |   5 +
 .../frame/examples/kitchensink/Cargo.toml     |   1 +
 substrate/frame/examples/src/lib.rs           |   2 +
 substrate/frame/examples/tasks/Cargo.toml     |  52 +
 .../frame/examples/tasks/src/benchmarking.rs  |  42 +
 substrate/frame/examples/tasks/src/lib.rs     |  78 ++
 substrate/frame/examples/tasks/src/mock.rs    |  43 +
 substrate/frame/examples/tasks/src/tests.rs   | 127 +++
 substrate/frame/examples/tasks/src/weights.rs |  84 ++
 substrate/frame/scheduler/src/mock.rs         |   4 +-
 substrate/frame/support/procedural/Cargo.toml |   3 +
 .../src/construct_runtime/expand/mod.rs       |   2 +
 .../src/construct_runtime/expand/task.rs      | 131 +++
 .../procedural/src/construct_runtime/mod.rs   |   3 +
 .../procedural/src/construct_runtime/parse.rs |   8 +-
 substrate/frame/support/procedural/src/lib.rs |  58 +-
 .../procedural/src/pallet/expand/mod.rs       |   3 +
 .../procedural/src/pallet/expand/tasks.rs     | 267 +++++
 .../src/pallet/expand/tt_default_parts.rs     |   4 +-
 .../procedural/src/pallet/parse/call.rs       |   3 +-
 .../procedural/src/pallet/parse/composite.rs  |   9 +-
 .../procedural/src/pallet/parse/mod.rs        | 159 ++-
 .../procedural/src/pallet/parse/tasks.rs      | 968 ++++++++++++++++++
 .../procedural/src/pallet/parse/tests/mod.rs  | 264 +++++
 .../src/pallet/parse/tests/tasks.rs           | 240 +++++
 substrate/frame/support/src/dispatch.rs       |   2 +
 substrate/frame/support/src/lib.rs            |  57 +-
 .../support/src/storage/generator/mod.rs      |   2 +
 substrate/frame/support/src/tests/mod.rs      |  57 +-
 substrate/frame/support/src/tests/tasks.rs    |  62 ++
 substrate/frame/support/src/traits.rs         |   3 +
 substrate/frame/support/src/traits/tasks.rs   |  87 ++
 .../support/test/compile_pass/src/lib.rs      |   3 +-
 substrate/frame/support/test/src/lib.rs       |   2 +
 .../generics_in_invalid_module.stderr         |   2 +-
 .../invalid_module_details_keyword.stderr     |   2 +-
 .../invalid_module_entry.stderr               |   2 +-
 ...umber_of_pallets_exceeds_tuple_size.stderr |   8 +
 .../inject_runtime_type_invalid.stderr        |   2 +-
 .../test/tests/pallet_outer_enums_explicit.rs |   2 +
 .../test/tests/pallet_outer_enums_implicit.rs |   2 +
 ...mposite_enum_unsupported_identifier.stderr |   2 +-
 .../test/tests/pallet_ui/pass/task_valid.rs   |  43 +
 .../task_can_only_be_attached_to_impl.rs      |  34 +
 .../task_can_only_be_attached_to_impl.stderr  |   5 +
 .../pallet_ui/task_condition_invalid_arg.rs   |  42 +
 .../task_condition_invalid_arg.stderr         |  22 +
 .../tests/pallet_ui/task_invalid_condition.rs |  42 +
 .../pallet_ui/task_invalid_condition.stderr   |  27 +
 .../tests/pallet_ui/task_invalid_index.rs     |  39 +
 .../tests/pallet_ui/task_invalid_index.stderr |   5 +
 .../test/tests/pallet_ui/task_invalid_list.rs |  42 +
 .../tests/pallet_ui/task_invalid_list.stderr  |  19 +
 .../tests/pallet_ui/task_invalid_weight.rs    |  42 +
 .../pallet_ui/task_invalid_weight.stderr      |  27 +
 .../tests/pallet_ui/task_missing_condition.rs |  39 +
 .../pallet_ui/task_missing_condition.stderr   |   5 +
 .../tests/pallet_ui/task_missing_index.rs     |  38 +
 .../tests/pallet_ui/task_missing_index.stderr |   5 +
 .../test/tests/pallet_ui/task_missing_list.rs |  40 +
 .../tests/pallet_ui/task_missing_list.stderr  |   5 +
 .../tests/pallet_ui/task_missing_weight.rs    |  41 +
 .../pallet_ui/task_missing_weight.stderr      |   5 +
 substrate/frame/system/src/lib.rs             |  40 +
 substrate/frame/system/src/mock.rs            |   3 +-
 substrate/test-utils/runtime/src/lib.rs       |   3 +-
 substrate/utils/frame/rpc/support/src/lib.rs  |   1 +
 75 files changed, 3516 insertions(+), 24 deletions(-)
 create mode 100644 prdoc/pr_1343.prdoc
 create mode 100644 substrate/frame/examples/tasks/Cargo.toml
 create mode 100644 substrate/frame/examples/tasks/src/benchmarking.rs
 create mode 100644 substrate/frame/examples/tasks/src/lib.rs
 create mode 100644 substrate/frame/examples/tasks/src/mock.rs
 create mode 100644 substrate/frame/examples/tasks/src/tests.rs
 create mode 100644 substrate/frame/examples/tasks/src/weights.rs
 create mode 100644 substrate/frame/support/procedural/src/construct_runtime/expand/task.rs
 create mode 100644 substrate/frame/support/procedural/src/pallet/expand/tasks.rs
 create mode 100644 substrate/frame/support/procedural/src/pallet/parse/tasks.rs
 create mode 100644 substrate/frame/support/procedural/src/pallet/parse/tests/mod.rs
 create mode 100644 substrate/frame/support/procedural/src/pallet/parse/tests/tasks.rs
 create mode 100644 substrate/frame/support/src/tests/tasks.rs
 create mode 100644 substrate/frame/support/src/traits/tasks.rs
 create mode 100644 substrate/frame/support/test/tests/pallet_ui/pass/task_valid.rs
 create mode 100644 substrate/frame/support/test/tests/pallet_ui/task_can_only_be_attached_to_impl.rs
 create mode 100644 substrate/frame/support/test/tests/pallet_ui/task_can_only_be_attached_to_impl.stderr
 create mode 100644 substrate/frame/support/test/tests/pallet_ui/task_condition_invalid_arg.rs
 create mode 100644 substrate/frame/support/test/tests/pallet_ui/task_condition_invalid_arg.stderr
 create mode 100644 substrate/frame/support/test/tests/pallet_ui/task_invalid_condition.rs
 create mode 100644 substrate/frame/support/test/tests/pallet_ui/task_invalid_condition.stderr
 create mode 100644 substrate/frame/support/test/tests/pallet_ui/task_invalid_index.rs
 create mode 100644 substrate/frame/support/test/tests/pallet_ui/task_invalid_index.stderr
 create mode 100644 substrate/frame/support/test/tests/pallet_ui/task_invalid_list.rs
 create mode 100644 substrate/frame/support/test/tests/pallet_ui/task_invalid_list.stderr
 create mode 100644 substrate/frame/support/test/tests/pallet_ui/task_invalid_weight.rs
 create mode 100644 substrate/frame/support/test/tests/pallet_ui/task_invalid_weight.stderr
 create mode 100644 substrate/frame/support/test/tests/pallet_ui/task_missing_condition.rs
 create mode 100644 substrate/frame/support/test/tests/pallet_ui/task_missing_condition.stderr
 create mode 100644 substrate/frame/support/test/tests/pallet_ui/task_missing_index.rs
 create mode 100644 substrate/frame/support/test/tests/pallet_ui/task_missing_index.stderr
 create mode 100644 substrate/frame/support/test/tests/pallet_ui/task_missing_list.rs
 create mode 100644 substrate/frame/support/test/tests/pallet_ui/task_missing_list.stderr
 create mode 100644 substrate/frame/support/test/tests/pallet_ui/task_missing_weight.rs
 create mode 100644 substrate/frame/support/test/tests/pallet_ui/task_missing_weight.stderr

diff --git a/Cargo.lock b/Cargo.lock
index 945698059ec..5184a8137a3 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -5505,6 +5505,7 @@ dependencies = [
  "proc-macro-warning",
  "proc-macro2",
  "quote",
+ "regex",
  "sp-core-hashing",
  "syn 2.0.39",
 ]
@@ -6879,6 +6880,7 @@ dependencies = [
  "pallet-election-provider-multi-phase",
  "pallet-election-provider-support-benchmarking",
  "pallet-elections-phragmen",
+ "pallet-example-tasks",
  "pallet-fast-unstake",
  "pallet-glutton",
  "pallet-grandpa",
@@ -9842,6 +9844,22 @@ dependencies = [
  "sp-std 8.0.0",
 ]
 
+[[package]]
+name = "pallet-example-tasks"
+version = "1.0.0-dev"
+dependencies = [
+ "frame-benchmarking",
+ "frame-support",
+ "frame-system",
+ "log",
+ "parity-scale-codec",
+ "scale-info",
+ "sp-core",
+ "sp-io",
+ "sp-runtime",
+ "sp-std 8.0.0",
+]
+
 [[package]]
 name = "pallet-examples"
 version = "4.0.0-dev"
@@ -9853,6 +9871,7 @@ dependencies = [
  "pallet-example-kitchensink",
  "pallet-example-offchain-worker",
  "pallet-example-split",
+ "pallet-example-tasks",
 ]
 
 [[package]]
diff --git a/Cargo.toml b/Cargo.toml
index e33b843eb1f..26d48ba0ced 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -308,6 +308,7 @@ members = [
 	"substrate/frame/examples/kitchensink",
 	"substrate/frame/examples/offchain-worker",
 	"substrate/frame/examples/split",
+	"substrate/frame/examples/tasks",
 	"substrate/frame/executive",
 	"substrate/frame/fast-unstake",
 	"substrate/frame/glutton",
diff --git a/cumulus/pallets/collator-selection/src/mock.rs b/cumulus/pallets/collator-selection/src/mock.rs
index 46143674bb3..ab9ad5ec11a 100644
--- a/cumulus/pallets/collator-selection/src/mock.rs
+++ b/cumulus/pallets/collator-selection/src/mock.rs
@@ -16,7 +16,7 @@
 use super::*;
 use crate as collator_selection;
 use frame_support::{
-	ord_parameter_types, parameter_types,
+	derive_impl, ord_parameter_types, parameter_types,
 	traits::{ConstBool, ConstU32, ConstU64, FindAuthor, ValidatorRegistration},
 	PalletId,
 };
@@ -50,6 +50,7 @@ parameter_types! {
 	pub const SS58Prefix: u8 = 42;
 }
 
+#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)]
 impl system::Config for Test {
 	type BaseCallFilter = frame_support::traits::Everything;
 	type BlockWeights = ();
diff --git a/cumulus/parachain-template/pallets/template/src/mock.rs b/cumulus/parachain-template/pallets/template/src/mock.rs
index 8fae1019f42..411a16b116c 100644
--- a/cumulus/parachain-template/pallets/template/src/mock.rs
+++ b/cumulus/parachain-template/pallets/template/src/mock.rs
@@ -1,4 +1,4 @@
-use frame_support::{parameter_types, traits::Everything};
+use frame_support::{derive_impl, parameter_types, traits::Everything};
 use frame_system as system;
 use sp_core::H256;
 use sp_runtime::{
@@ -22,6 +22,7 @@ parameter_types! {
 	pub const SS58Prefix: u8 = 42;
 }
 
+#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)]
 impl system::Config for Test {
 	type BaseCallFilter = Everything;
 	type BlockWeights = ();
diff --git a/prdoc/pr_1343.prdoc b/prdoc/pr_1343.prdoc
new file mode 100644
index 00000000000..84168230e0a
--- /dev/null
+++ b/prdoc/pr_1343.prdoc
@@ -0,0 +1,29 @@
+title: Tasks API - A general system for recognizing and executing service work
+
+doc:
+  - audience: Runtime Dev
+    description: |
+      The Tasks API allows you to define some service work that can be recognized by a script or an off-chain worker.
+      Such a script can then create and submit all such work items at any given time.
+      `#[pallet:tasks_experimental]` provides a convenient way to define such work items. It can be attached to an 
+      `impl` block inside a pallet, whose functions can then be annotated by the following attributes:
+      1. `#[pallet::task_list]`: Define an iterator over the available work items for a task
+      2. `#[pallet::task_condition]`: Define the conditions for a given work item to be valid
+      3. `#[pallet::task_weight]`: Define the weight of a given work item
+      4. `#[pallet::task_index]`: Define the index of a given work item
+      Each such function becomes a variant of the autogenerated enum `Task<T>` for this pallet. 
+      All such enums are aggregated into a `RuntimeTask` by `construct_runtime`.
+      An example pallet that uses the Tasks API is available at `substrate/frame/example/tasks`.
+
+migrations:
+  db: []
+
+  runtime: []
+
+crates:
+  - name: frame-system
+  - name: frame-support
+  - name: frame-support-procedural
+  - name: pallet-example-tasks
+
+host_functions: []
diff --git a/substrate/bin/node/runtime/Cargo.toml b/substrate/bin/node/runtime/Cargo.toml
index e53646c0ef4..8ea2988bee0 100644
--- a/substrate/bin/node/runtime/Cargo.toml
+++ b/substrate/bin/node/runtime/Cargo.toml
@@ -80,6 +80,7 @@ pallet-democracy = { path = "../../../frame/democracy", default-features = false
 pallet-election-provider-multi-phase = { path = "../../../frame/election-provider-multi-phase", default-features = false }
 pallet-election-provider-support-benchmarking = { path = "../../../frame/election-provider-support/benchmarking", default-features = false, optional = true }
 pallet-elections-phragmen = { path = "../../../frame/elections-phragmen", default-features = false }
+pallet-example-tasks = { path = "../../../frame/examples/tasks", default-features = false }
 pallet-fast-unstake = { path = "../../../frame/fast-unstake", default-features = false }
 pallet-nis = { path = "../../../frame/nis", default-features = false }
 pallet-grandpa = { path = "../../../frame/grandpa", default-features = false }
@@ -177,6 +178,7 @@ std = [
 	"pallet-election-provider-multi-phase/std",
 	"pallet-election-provider-support-benchmarking?/std",
 	"pallet-elections-phragmen/std",
+	"pallet-example-tasks/std",
 	"pallet-fast-unstake/std",
 	"pallet-glutton/std",
 	"pallet-grandpa/std",
@@ -279,6 +281,7 @@ runtime-benchmarks = [
 	"pallet-election-provider-multi-phase/runtime-benchmarks",
 	"pallet-election-provider-support-benchmarking/runtime-benchmarks",
 	"pallet-elections-phragmen/runtime-benchmarks",
+	"pallet-example-tasks/runtime-benchmarks",
 	"pallet-fast-unstake/runtime-benchmarks",
 	"pallet-glutton/runtime-benchmarks",
 	"pallet-grandpa/runtime-benchmarks",
@@ -353,6 +356,7 @@ try-runtime = [
 	"pallet-democracy/try-runtime",
 	"pallet-election-provider-multi-phase/try-runtime",
 	"pallet-elections-phragmen/try-runtime",
+	"pallet-example-tasks/try-runtime",
 	"pallet-fast-unstake/try-runtime",
 	"pallet-glutton/try-runtime",
 	"pallet-grandpa/try-runtime",
diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs
index 1f4f22f224a..eded60adfd7 100644
--- a/substrate/bin/node/runtime/src/lib.rs
+++ b/substrate/bin/node/runtime/src/lib.rs
@@ -304,6 +304,11 @@ impl frame_system::Config for Runtime {
 
 impl pallet_insecure_randomness_collective_flip::Config for Runtime {}
 
+impl pallet_example_tasks::Config for Runtime {
+	type RuntimeTask = RuntimeTask;
+	type WeightInfo = pallet_example_tasks::weights::SubstrateWeight<Runtime>;
+}
+
 impl pallet_utility::Config for Runtime {
 	type RuntimeEvent = RuntimeEvent;
 	type RuntimeCall = RuntimeCall;
@@ -2135,6 +2140,7 @@ construct_runtime!(
 		SafeMode: pallet_safe_mode,
 		Statement: pallet_statement,
 		Broker: pallet_broker,
+		TasksExample: pallet_example_tasks,
 		Mixnet: pallet_mixnet,
 		SkipFeelessPayment: pallet_skip_feeless_payment,
 	}
@@ -2227,6 +2233,7 @@ mod benches {
 		[pallet_conviction_voting, ConvictionVoting]
 		[pallet_contracts, Contracts]
 		[pallet_core_fellowship, CoreFellowship]
+		[tasks_example, TasksExample]
 		[pallet_democracy, Democracy]
 		[pallet_asset_conversion, AssetConversion]
 		[pallet_election_provider_multi_phase, ElectionProviderMultiPhase]
diff --git a/substrate/frame/examples/Cargo.toml b/substrate/frame/examples/Cargo.toml
index 779baa432fc..b272a15dd63 100644
--- a/substrate/frame/examples/Cargo.toml
+++ b/substrate/frame/examples/Cargo.toml
@@ -20,6 +20,7 @@ pallet-example-frame-crate = { path = "frame-crate", default-features = false }
 pallet-example-kitchensink = { path = "kitchensink", default-features = false }
 pallet-example-offchain-worker = { path = "offchain-worker", default-features = false }
 pallet-example-split = { path = "split", default-features = false }
+pallet-example-tasks = { path = "tasks", default-features = false }
 
 [features]
 default = ["std"]
@@ -31,6 +32,7 @@ std = [
 	"pallet-example-kitchensink/std",
 	"pallet-example-offchain-worker/std",
 	"pallet-example-split/std",
+	"pallet-example-tasks/std",
 ]
 try-runtime = [
 	"pallet-default-config-example/try-runtime",
@@ -39,4 +41,5 @@ try-runtime = [
 	"pallet-example-kitchensink/try-runtime",
 	"pallet-example-offchain-worker/try-runtime",
 	"pallet-example-split/try-runtime",
+	"pallet-example-tasks/try-runtime",
 ]
diff --git a/substrate/frame/examples/default-config/src/lib.rs b/substrate/frame/examples/default-config/src/lib.rs
index 8a1f6f9d6a8..f1611bca2ce 100644
--- a/substrate/frame/examples/default-config/src/lib.rs
+++ b/substrate/frame/examples/default-config/src/lib.rs
@@ -47,6 +47,10 @@ pub mod pallet {
 		#[pallet::no_default] // optional. `RuntimeEvent` is automatically excluded as well.
 		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
 
+		/// The overarching task type.
+		#[pallet::no_default]
+		type RuntimeTask: Task;
+
 		/// An input parameter to this pallet. This value can have a default, because it is not
 		/// reliant on `frame_system::Config` or the overarching runtime in any way.
 		type WithDefaultValue: Get<u32>;
@@ -193,6 +197,7 @@ pub mod tests {
 	impl pallet_default_config_example::Config for Runtime {
 		// These two both cannot have defaults.
 		type RuntimeEvent = RuntimeEvent;
+		type RuntimeTask = RuntimeTask;
 
 		type HasNoDefault = frame_support::traits::ConstU32<1>;
 		type CannotHaveDefault = SomeCall;
diff --git a/substrate/frame/examples/kitchensink/Cargo.toml b/substrate/frame/examples/kitchensink/Cargo.toml
index e63e1192458..3b3b7a3761a 100644
--- a/substrate/frame/examples/kitchensink/Cargo.toml
+++ b/substrate/frame/examples/kitchensink/Cargo.toml
@@ -7,6 +7,7 @@ license = "MIT-0"
 homepage = "https://substrate.io"
 repository.workspace = true
 description = "FRAME example kitchensink pallet"
+publish = false
 
 [package.metadata.docs.rs]
 targets = ["x86_64-unknown-linux-gnu"]
diff --git a/substrate/frame/examples/src/lib.rs b/substrate/frame/examples/src/lib.rs
index 8d65639f835..f38bbe52dc1 100644
--- a/substrate/frame/examples/src/lib.rs
+++ b/substrate/frame/examples/src/lib.rs
@@ -43,4 +43,6 @@
 //! - [`pallet_example_frame_crate`]: Example pallet showcasing how one can be
 //! built using only the `frame` umbrella crate.
 //!
+//! - [`pallet_example_tasks`]: This pallet demonstrates the use of `Tasks` to execute service work.
+//!
 //! **Tip**: Use `cargo doc --package <pallet-name> --open` to view each pallet's documentation.
diff --git a/substrate/frame/examples/tasks/Cargo.toml b/substrate/frame/examples/tasks/Cargo.toml
new file mode 100644
index 00000000000..e26e822e40f
--- /dev/null
+++ b/substrate/frame/examples/tasks/Cargo.toml
@@ -0,0 +1,52 @@
+[package]
+name = "pallet-example-tasks"
+version = "1.0.0-dev"
+authors.workspace = true
+edition.workspace = true
+license.workspace = true
+repository.workspace = true
+description = "Pallet to demonstrate the usage of Tasks to recongnize and execute service work"
+
+[package.metadata.docs.rs]
+targets = ["x86_64-unknown-linux-gnu"]
+
+[dependencies]
+codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false }
+log = { version = "0.4.17", default-features = false }
+scale-info = { version = "2.10.0", default-features = false, features = ["derive"] }
+
+frame-support = { path = "../../support", default-features = false }
+frame-system = { path = "../../system", default-features = false }
+
+sp-io = { path = "../../../primitives/io", default-features = false }
+sp-runtime = { path = "../../../primitives/runtime", default-features = false }
+sp-std = { path = "../../../primitives/std", default-features = false }
+sp-core = { version = "21.0.0", default-features = false, path = "../../../primitives/core" }
+
+frame-benchmarking = { path = "../../benchmarking", default-features = false, optional = true }
+
+[features]
+default = ["std"]
+std = [
+	"codec/std",
+	"frame-benchmarking?/std",
+	"frame-support/std",
+	"frame-system/std",
+	"log/std",
+	"scale-info/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",
+	"sp-runtime/runtime-benchmarks",
+]
+try-runtime = [
+	"frame-support/try-runtime",
+	"frame-system/try-runtime",
+	"sp-runtime/try-runtime",
+]
diff --git a/substrate/frame/examples/tasks/src/benchmarking.rs b/substrate/frame/examples/tasks/src/benchmarking.rs
new file mode 100644
index 00000000000..81f7d3d3b21
--- /dev/null
+++ b/substrate/frame/examples/tasks/src/benchmarking.rs
@@ -0,0 +1,42 @@
+// 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.
+
+//! Benchmarking for `pallet-example-tasks`.
+
+#![cfg(feature = "runtime-benchmarks")]
+
+use crate::*;
+use frame_benchmarking::v2::*;
+
+#[benchmarks]
+mod benchmarks {
+	use super::*;
+
+	#[benchmark]
+	fn add_number_into_total() {
+		Numbers::<T>::insert(0, 1);
+
+		#[block]
+		{
+			Task::<T>::add_number_into_total(0).unwrap();
+		}
+
+		assert_eq!(Numbers::<T>::get(0), None);
+	}
+
+	impl_benchmark_test_suite!(Pallet, crate::tests::new_test_ext(), crate::mock::Runtime);
+}
diff --git a/substrate/frame/examples/tasks/src/lib.rs b/substrate/frame/examples/tasks/src/lib.rs
new file mode 100644
index 00000000000..c65d8095bcf
--- /dev/null
+++ b/substrate/frame/examples/tasks/src/lib.rs
@@ -0,0 +1,78 @@
+// 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.
+
+//! This pallet demonstrates the use of the `pallet::task` api for service work.
+#![cfg_attr(not(feature = "std"), no_std)]
+
+use frame_support::dispatch::DispatchResult;
+// Re-export pallet items so that they can be accessed from the crate namespace.
+pub use pallet::*;
+
+pub mod mock;
+pub mod tests;
+
+#[cfg(feature = "runtime-benchmarks")]
+mod benchmarking;
+
+pub mod weights;
+pub use weights::*;
+
+#[frame_support::pallet(dev_mode)]
+pub mod pallet {
+	use super::*;
+	use frame_support::pallet_prelude::*;
+
+	#[pallet::error]
+	pub enum Error<T> {
+		/// The referenced task was not found.
+		NotFound,
+	}
+
+	#[pallet::tasks_experimental]
+	impl<T: Config> Pallet<T> {
+		/// Add a pair of numbers into the totals and remove them.
+		#[pallet::task_list(Numbers::<T>::iter_keys())]
+		#[pallet::task_condition(|i| Numbers::<T>::contains_key(i))]
+		#[pallet::task_weight(T::WeightInfo::add_number_into_total())]
+		#[pallet::task_index(0)]
+		pub fn add_number_into_total(i: u32) -> DispatchResult {
+			let v = Numbers::<T>::take(i).ok_or(Error::<T>::NotFound)?;
+			Total::<T>::mutate(|(total_keys, total_values)| {
+				*total_keys += i;
+				*total_values += v;
+			});
+			Ok(())
+		}
+	}
+
+	#[pallet::config]
+	pub trait Config: frame_system::Config {
+		type RuntimeTask: frame_support::traits::Task;
+		type WeightInfo: WeightInfo;
+	}
+
+	#[pallet::pallet]
+	pub struct Pallet<T>(_);
+
+	/// Some running total.
+	#[pallet::storage]
+	pub type Total<T: Config> = StorageValue<_, (u32, u32), ValueQuery>;
+
+	/// Numbers to be added into the total.
+	#[pallet::storage]
+	pub type Numbers<T: Config> = StorageMap<_, Twox64Concat, u32, u32, OptionQuery>;
+}
diff --git a/substrate/frame/examples/tasks/src/mock.rs b/substrate/frame/examples/tasks/src/mock.rs
new file mode 100644
index 00000000000..e0fbec3eb76
--- /dev/null
+++ b/substrate/frame/examples/tasks/src/mock.rs
@@ -0,0 +1,43 @@
+// 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 runtime for `tasks-example` tests.
+#![cfg(test)]
+
+use crate::{self as tasks_example};
+use frame_support::derive_impl;
+
+pub type AccountId = u32;
+pub type Balance = u32;
+
+type Block = frame_system::mocking::MockBlock<Runtime>;
+frame_support::construct_runtime!(
+	pub struct Runtime {
+		System: frame_system,
+		TasksExample: tasks_example,
+	}
+);
+
+#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)]
+impl frame_system::Config for Runtime {
+	type Block = Block;
+}
+
+impl tasks_example::Config for Runtime {
+	type RuntimeTask = RuntimeTask;
+	type WeightInfo = ();
+}
diff --git a/substrate/frame/examples/tasks/src/tests.rs b/substrate/frame/examples/tasks/src/tests.rs
new file mode 100644
index 00000000000..6b255491091
--- /dev/null
+++ b/substrate/frame/examples/tasks/src/tests.rs
@@ -0,0 +1,127 @@
+// 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.
+
+//! Tests for `pallet-example-tasks`.
+#![cfg(test)]
+
+use crate::{mock::*, Numbers, Total};
+use frame_support::{assert_noop, assert_ok, traits::Task};
+use sp_runtime::BuildStorage;
+
+// This function basically just builds a genesis storage key/value store according to
+// our desired mockup.
+pub fn new_test_ext() -> sp_io::TestExternalities {
+	let t = RuntimeGenesisConfig {
+		// We use default for brevity, but you can configure as desired if needed.
+		system: Default::default(),
+	}
+	.build_storage()
+	.unwrap();
+	t.into()
+}
+
+#[test]
+fn task_enumerate_works() {
+	new_test_ext().execute_with(|| {
+		Numbers::<Runtime>::insert(0, 1);
+		assert_eq!(crate::pallet::Task::<Runtime>::iter().collect::<Vec<_>>().len(), 1);
+	});
+}
+
+#[test]
+fn runtime_task_enumerate_works_via_frame_system_config() {
+	new_test_ext().execute_with(|| {
+		Numbers::<Runtime>::insert(0, 1);
+		Numbers::<Runtime>::insert(1, 4);
+		assert_eq!(
+			<Runtime as frame_system::Config>::RuntimeTask::iter().collect::<Vec<_>>().len(),
+			2
+		);
+	});
+}
+
+#[test]
+fn runtime_task_enumerate_works_via_pallet_config() {
+	new_test_ext().execute_with(|| {
+		Numbers::<Runtime>::insert(1, 4);
+		assert_eq!(
+			<Runtime as crate::pallet::Config>::RuntimeTask::iter()
+				.collect::<Vec<_>>()
+				.len(),
+			1
+		);
+	});
+}
+
+#[test]
+fn task_index_works_at_pallet_level() {
+	new_test_ext().execute_with(|| {
+		assert_eq!(crate::pallet::Task::<Runtime>::AddNumberIntoTotal { i: 2u32 }.task_index(), 0);
+	});
+}
+
+#[test]
+fn task_index_works_at_runtime_level() {
+	new_test_ext().execute_with(|| {
+		assert_eq!(
+			<Runtime as frame_system::Config>::RuntimeTask::TasksExample(crate::pallet::Task::<
+				Runtime,
+			>::AddNumberIntoTotal {
+				i: 1u32
+			})
+			.task_index(),
+			0
+		);
+	});
+}
+
+#[test]
+fn task_execution_works() {
+	new_test_ext().execute_with(|| {
+		System::set_block_number(1);
+		Numbers::<Runtime>::insert(0, 1);
+		Numbers::<Runtime>::insert(1, 4);
+
+		let task =
+			<Runtime as frame_system::Config>::RuntimeTask::TasksExample(crate::pallet::Task::<
+				Runtime,
+			>::AddNumberIntoTotal {
+				i: 1u32,
+			});
+		assert_ok!(System::do_task(RuntimeOrigin::signed(1), task.clone(),));
+		assert_eq!(Numbers::<Runtime>::get(0), Some(1));
+		assert_eq!(Numbers::<Runtime>::get(1), None);
+		assert_eq!(Total::<Runtime>::get(), (1, 4));
+		System::assert_last_event(frame_system::Event::<Runtime>::TaskCompleted { task }.into());
+	});
+}
+
+#[test]
+fn task_execution_fails_for_invalid_task() {
+	new_test_ext().execute_with(|| {
+		Numbers::<Runtime>::insert(1, 4);
+		assert_noop!(
+			System::do_task(
+				RuntimeOrigin::signed(1),
+				<Runtime as frame_system::Config>::RuntimeTask::TasksExample(
+					crate::pallet::Task::<Runtime>::AddNumberIntoTotal { i: 0u32 }
+				),
+			),
+			frame_system::Error::<Runtime>::InvalidTask
+		);
+	});
+}
diff --git a/substrate/frame/examples/tasks/src/weights.rs b/substrate/frame/examples/tasks/src/weights.rs
new file mode 100644
index 00000000000..793af6e9622
--- /dev/null
+++ b/substrate/frame/examples/tasks/src/weights.rs
@@ -0,0 +1,84 @@
+// 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_example_tasks`
+//!
+//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
+//! DATE: 2023-06-02, STEPS: `20`, REPEAT: `10`, LOW RANGE: `[]`, HIGH RANGE: `[]`
+//! WORST CASE MAP SIZE: `1000000`
+//! HOSTNAME: `MacBook.local`, CPU: `<UNKNOWN>`
+//! EXECUTION: None, WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024
+
+// Executed Command:
+// ./target/release/node-template
+// benchmark
+// pallet
+// --chain
+// dev
+// --pallet
+// pallet_example_tasks
+// --extrinsic
+// *
+// --steps
+// 20
+// --repeat
+// 10
+// --output
+// frame/examples/tasks/src/weights.rs
+
+#![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_template.
+pub trait WeightInfo {
+	fn add_number_into_total() -> Weight;
+}
+
+/// Weight functions for `pallet_example_kitchensink`.
+pub struct SubstrateWeight<T>(PhantomData<T>);
+impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
+	/// Storage: Kitchensink OtherFoo (r:0 w:1)
+	/// Proof Skipped: Kitchensink OtherFoo (max_values: Some(1), max_size: None, mode: Measured)
+	fn add_number_into_total() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `0`
+		//  Estimated: `0`
+		// Minimum execution time: 1_000_000 picoseconds.
+		Weight::from_parts(1_000_000, 0)
+			.saturating_add(Weight::from_parts(0, 0))
+			.saturating_add(T::DbWeight::get().writes(1))
+	}
+}
+
+impl WeightInfo for () {
+	/// Storage: Kitchensink OtherFoo (r:0 w:1)
+	/// Proof Skipped: Kitchensink OtherFoo (max_values: Some(1), max_size: None, mode: Measured)
+	fn add_number_into_total() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `0`
+		//  Estimated: `0`
+		// Minimum execution time: 1_000_000 picoseconds.
+		Weight::from_parts(1_000_000, 0)
+			.saturating_add(Weight::from_parts(0, 0))
+			.saturating_add(RocksDbWeight::get().writes(1))
+	}
+}
diff --git a/substrate/frame/scheduler/src/mock.rs b/substrate/frame/scheduler/src/mock.rs
index b6eb1d044fa..4edcfa0a7bf 100644
--- a/substrate/frame/scheduler/src/mock.rs
+++ b/substrate/frame/scheduler/src/mock.rs
@@ -21,7 +21,7 @@ use super::*;
 
 use crate as scheduler;
 use frame_support::{
-	ord_parameter_types, parameter_types,
+	derive_impl, ord_parameter_types, parameter_types,
 	traits::{
 		ConstU32, ConstU64, Contains, EitherOfDiverse, EqualPrivilegeOnly, OnFinalize, OnInitialize,
 	},
@@ -118,6 +118,8 @@ parameter_types! {
 			Weight::from_parts(2_000_000_000_000, u64::MAX),
 		);
 }
+
+#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)]
 impl system::Config for Test {
 	type BaseCallFilter = BaseFilter;
 	type BlockWeights = BlockWeights;
diff --git a/substrate/frame/support/procedural/Cargo.toml b/substrate/frame/support/procedural/Cargo.toml
index f1d8a7d4ca9..7c207b230f3 100644
--- a/substrate/frame/support/procedural/Cargo.toml
+++ b/substrate/frame/support/procedural/Cargo.toml
@@ -28,6 +28,9 @@ proc-macro-warning = { version = "1.0.0", default-features = false }
 expander = "2.0.0"
 sp-core-hashing = { path = "../../../primitives/core/hashing" }
 
+[dev-dependencies]
+regex = "1"
+
 [features]
 default = ["std"]
 std = []
diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/mod.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/mod.rs
index a0fc6b8130b..88f9a3c6e33 100644
--- a/substrate/frame/support/procedural/src/construct_runtime/expand/mod.rs
+++ b/substrate/frame/support/procedural/src/construct_runtime/expand/mod.rs
@@ -26,6 +26,7 @@ mod metadata;
 mod origin;
 mod outer_enums;
 mod slash_reason;
+mod task;
 mod unsigned;
 
 pub use call::expand_outer_dispatch;
@@ -38,4 +39,5 @@ pub use metadata::expand_runtime_metadata;
 pub use origin::expand_outer_origin;
 pub use outer_enums::{expand_outer_enum, OuterEnumType};
 pub use slash_reason::expand_outer_slash_reason;
+pub use task::expand_outer_task;
 pub use unsigned::expand_outer_validate_unsigned;
diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/task.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/task.rs
new file mode 100644
index 00000000000..bd952202bbb
--- /dev/null
+++ b/substrate/frame/support/procedural/src/construct_runtime/expand/task.rs
@@ -0,0 +1,131 @@
+// 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::construct_runtime::Pallet;
+use proc_macro2::{Ident, TokenStream as TokenStream2};
+use quote::quote;
+
+/// Expands aggregate `RuntimeTask` enum.
+pub fn expand_outer_task(
+	runtime_name: &Ident,
+	pallet_decls: &[Pallet],
+	scrate: &TokenStream2,
+) -> TokenStream2 {
+	let mut from_impls = Vec::new();
+	let mut task_variants = Vec::new();
+	let mut variant_names = Vec::new();
+	let mut task_paths = Vec::new();
+	for decl in pallet_decls {
+		if decl.find_part("Task").is_none() {
+			continue;
+		}
+
+		let variant_name = &decl.name;
+		let path = &decl.path;
+		let index = decl.index;
+
+		from_impls.push(quote! {
+			impl From<#path::Task<#runtime_name>> for RuntimeTask {
+				fn from(hr: #path::Task<#runtime_name>) -> Self {
+					RuntimeTask::#variant_name(hr)
+				}
+			}
+
+			impl TryInto<#path::Task<#runtime_name>> for RuntimeTask {
+				type Error = ();
+
+				fn try_into(self) -> Result<#path::Task<#runtime_name>, Self::Error> {
+					match self {
+						RuntimeTask::#variant_name(hr) => Ok(hr),
+						_ => Err(()),
+					}
+				}
+			}
+		});
+
+		task_variants.push(quote! {
+			#[codec(index = #index)]
+			#variant_name(#path::Task<#runtime_name>),
+		});
+
+		variant_names.push(quote!(#variant_name));
+
+		task_paths.push(quote!(#path::Task));
+	}
+
+	let prelude = quote!(#scrate::traits::tasks::__private);
+
+	const INCOMPLETE_MATCH_QED: &'static str =
+		"cannot have an instantiated RuntimeTask without some Task variant in the runtime. QED";
+
+	let output = quote! {
+		/// An aggregation of all `Task` enums across all pallets included in the current runtime.
+		#[derive(
+			Clone, Eq, PartialEq,
+			#scrate::__private::codec::Encode,
+			#scrate::__private::codec::Decode,
+			#scrate::__private::scale_info::TypeInfo,
+			#scrate::__private::RuntimeDebug,
+		)]
+		pub enum RuntimeTask {
+			#( #task_variants )*
+		}
+
+		#[automatically_derived]
+		impl #scrate::traits::Task for RuntimeTask {
+			type Enumeration = #prelude::IntoIter<RuntimeTask>;
+
+			fn is_valid(&self) -> bool {
+				match self {
+					#(RuntimeTask::#variant_names(val) => val.is_valid(),)*
+					_ => unreachable!(#INCOMPLETE_MATCH_QED),
+				}
+			}
+
+			fn run(&self) -> Result<(), #scrate::traits::tasks::__private::DispatchError> {
+				match self {
+					#(RuntimeTask::#variant_names(val) => val.run(),)*
+					_ => unreachable!(#INCOMPLETE_MATCH_QED),
+				}
+			}
+
+			fn weight(&self) -> #scrate::pallet_prelude::Weight {
+				match self {
+					#(RuntimeTask::#variant_names(val) => val.weight(),)*
+					_ => unreachable!(#INCOMPLETE_MATCH_QED),
+				}
+			}
+
+			fn task_index(&self) -> u32 {
+				match self {
+					#(RuntimeTask::#variant_names(val) => val.task_index(),)*
+					_ => unreachable!(#INCOMPLETE_MATCH_QED),
+				}
+			}
+
+			fn iter() -> Self::Enumeration {
+				let mut all_tasks = Vec::new();
+				#(all_tasks.extend(#task_paths::iter().map(RuntimeTask::from).collect::<Vec<_>>());)*
+				all_tasks.into_iter()
+			}
+		}
+
+		#( #from_impls )*
+	};
+
+	output
+}
diff --git a/substrate/frame/support/procedural/src/construct_runtime/mod.rs b/substrate/frame/support/procedural/src/construct_runtime/mod.rs
index 010143574ed..7a9c4d89a74 100644
--- a/substrate/frame/support/procedural/src/construct_runtime/mod.rs
+++ b/substrate/frame/support/procedural/src/construct_runtime/mod.rs
@@ -386,6 +386,7 @@ fn construct_runtime_final_expansion(
 	let pallet_to_index = decl_pallet_runtime_setup(&name, &pallets, &scrate);
 
 	let dispatch = expand::expand_outer_dispatch(&name, system_pallet, &pallets, &scrate);
+	let tasks = expand::expand_outer_task(&name, &pallets, &scrate);
 	let metadata = expand::expand_runtime_metadata(
 		&name,
 		&pallets,
@@ -475,6 +476,8 @@ fn construct_runtime_final_expansion(
 
 		#dispatch
 
+		#tasks
+
 		#metadata
 
 		#outer_config
diff --git a/substrate/frame/support/procedural/src/construct_runtime/parse.rs b/substrate/frame/support/procedural/src/construct_runtime/parse.rs
index 9b08e164697..88f3f14dc86 100644
--- a/substrate/frame/support/procedural/src/construct_runtime/parse.rs
+++ b/substrate/frame/support/procedural/src/construct_runtime/parse.rs
@@ -42,6 +42,7 @@ mod keyword {
 	syn::custom_keyword!(ValidateUnsigned);
 	syn::custom_keyword!(FreezeReason);
 	syn::custom_keyword!(HoldReason);
+	syn::custom_keyword!(Task);
 	syn::custom_keyword!(LockId);
 	syn::custom_keyword!(SlashReason);
 	syn::custom_keyword!(exclude_parts);
@@ -404,6 +405,7 @@ pub enum PalletPartKeyword {
 	ValidateUnsigned(keyword::ValidateUnsigned),
 	FreezeReason(keyword::FreezeReason),
 	HoldReason(keyword::HoldReason),
+	Task(keyword::Task),
 	LockId(keyword::LockId),
 	SlashReason(keyword::SlashReason),
 }
@@ -434,6 +436,8 @@ impl Parse for PalletPartKeyword {
 			Ok(Self::FreezeReason(input.parse()?))
 		} else if lookahead.peek(keyword::HoldReason) {
 			Ok(Self::HoldReason(input.parse()?))
+		} else if lookahead.peek(keyword::Task) {
+			Ok(Self::Task(input.parse()?))
 		} else if lookahead.peek(keyword::LockId) {
 			Ok(Self::LockId(input.parse()?))
 		} else if lookahead.peek(keyword::SlashReason) {
@@ -459,6 +463,7 @@ impl PalletPartKeyword {
 			Self::ValidateUnsigned(_) => "ValidateUnsigned",
 			Self::FreezeReason(_) => "FreezeReason",
 			Self::HoldReason(_) => "HoldReason",
+			Self::Task(_) => "Task",
 			Self::LockId(_) => "LockId",
 			Self::SlashReason(_) => "SlashReason",
 		}
@@ -471,7 +476,7 @@ impl PalletPartKeyword {
 
 	/// Returns the names of all pallet parts that allow to have a generic argument.
 	fn all_generic_arg() -> &'static [&'static str] {
-		&["Event", "Error", "Origin", "Config"]
+		&["Event", "Error", "Origin", "Config", "Task"]
 	}
 }
 
@@ -489,6 +494,7 @@ impl ToTokens for PalletPartKeyword {
 			Self::ValidateUnsigned(inner) => inner.to_tokens(tokens),
 			Self::FreezeReason(inner) => inner.to_tokens(tokens),
 			Self::HoldReason(inner) => inner.to_tokens(tokens),
+			Self::Task(inner) => inner.to_tokens(tokens),
 			Self::LockId(inner) => inner.to_tokens(tokens),
 			Self::SlashReason(inner) => inner.to_tokens(tokens),
 		}
diff --git a/substrate/frame/support/procedural/src/lib.rs b/substrate/frame/support/procedural/src/lib.rs
index 682b135fa14..349b6ee6599 100644
--- a/substrate/frame/support/procedural/src/lib.rs
+++ b/substrate/frame/support/procedural/src/lib.rs
@@ -646,7 +646,6 @@ pub fn storage_alias(attributes: TokenStream, input: TokenStream) -> TokenStream
 /// ```
 ///
 /// where `TestDefaultConfig` was defined and registered as follows:
-///
 /// ```ignore
 /// pub struct TestDefaultConfig;
 ///
@@ -673,7 +672,6 @@ pub fn storage_alias(attributes: TokenStream, input: TokenStream) -> TokenStream
 /// ```
 ///
 /// The above call to `derive_impl` would expand to roughly the following:
-///
 /// ```ignore
 /// impl frame_system::Config for Test {
 ///     use frame_system::config_preludes::TestDefaultConfig;
@@ -881,6 +879,7 @@ pub fn inject_runtime_type(_: TokenStream, tokens: TokenStream) -> TokenStream {
 	let item = syn::parse_macro_input!(item as TraitItemType);
 	if item.ident != "RuntimeCall" &&
 		item.ident != "RuntimeEvent" &&
+		item.ident != "RuntimeTask" &&
 		item.ident != "RuntimeOrigin" &&
 		item.ident != "RuntimeHoldReason" &&
 		item.ident != "RuntimeFreezeReason" &&
@@ -888,10 +887,11 @@ pub fn inject_runtime_type(_: TokenStream, tokens: TokenStream) -> TokenStream {
 	{
 		return syn::Error::new_spanned(
 			item,
-			"`#[inject_runtime_type]` can only be attached to `RuntimeCall`, `RuntimeEvent`, `RuntimeOrigin` or `PalletInfo`",
+			"`#[inject_runtime_type]` can only be attached to `RuntimeCall`, `RuntimeEvent`, \
+			`RuntimeTask`, `RuntimeOrigin` or `PalletInfo`",
 		)
 		.to_compile_error()
-		.into();
+		.into()
 	}
 	tokens
 }
@@ -1518,6 +1518,56 @@ pub fn composite_enum(_: TokenStream, _: TokenStream) -> TokenStream {
 	pallet_macro_stub()
 }
 
+///
+/// ---
+///
+/// **Rust-Analyzer users**: See the documentation of the Rust item in
+/// `frame_support::pallet_macros::tasks_experimental`.
+#[proc_macro_attribute]
+pub fn tasks_experimental(_: TokenStream, _: TokenStream) -> TokenStream {
+	pallet_macro_stub()
+}
+
+///
+/// ---
+///
+/// **Rust-Analyzer users**: See the documentation of the Rust item in
+/// `frame_support::pallet_macros::task_list`.
+#[proc_macro_attribute]
+pub fn task_list(_: TokenStream, _: TokenStream) -> TokenStream {
+	pallet_macro_stub()
+}
+
+///
+/// ---
+///
+/// **Rust-Analyzer users**: See the documentation of the Rust item in
+/// `frame_support::pallet_macros::task_condition`.
+#[proc_macro_attribute]
+pub fn task_condition(_: TokenStream, _: TokenStream) -> TokenStream {
+	pallet_macro_stub()
+}
+
+///
+/// ---
+///
+/// **Rust-Analyzer users**: See the documentation of the Rust item in
+/// `frame_support::pallet_macros::task_weight`.
+#[proc_macro_attribute]
+pub fn task_weight(_: TokenStream, _: TokenStream) -> TokenStream {
+	pallet_macro_stub()
+}
+
+///
+/// ---
+///
+/// **Rust-Analyzer users**: See the documentation of the Rust item in
+/// `frame_support::pallet_macros::task_index`.
+#[proc_macro_attribute]
+pub fn task_index(_: TokenStream, _: TokenStream) -> TokenStream {
+	pallet_macro_stub()
+}
+
 /// Can be attached to a module. Doing so will declare that module as importable into a pallet
 /// via [`#[import_section]`](`macro@import_section`).
 ///
diff --git a/substrate/frame/support/procedural/src/pallet/expand/mod.rs b/substrate/frame/support/procedural/src/pallet/expand/mod.rs
index 6f32e569751..db242df781b 100644
--- a/substrate/frame/support/procedural/src/pallet/expand/mod.rs
+++ b/substrate/frame/support/procedural/src/pallet/expand/mod.rs
@@ -31,6 +31,7 @@ mod origin;
 mod pallet_struct;
 mod storage;
 mod store_trait;
+mod tasks;
 mod tt_default_parts;
 mod type_value;
 mod validate_unsigned;
@@ -60,6 +61,7 @@ pub fn expand(mut def: Def) -> proc_macro2::TokenStream {
 	let pallet_struct = pallet_struct::expand_pallet_struct(&mut def);
 	let config = config::expand_config(&mut def);
 	let call = call::expand_call(&mut def);
+	let tasks = tasks::expand_tasks(&mut def);
 	let error = error::expand_error(&mut def);
 	let event = event::expand_event(&mut def);
 	let storages = storage::expand_storages(&mut def);
@@ -100,6 +102,7 @@ storage item. Otherwise, all storage items are listed among [*Type Definitions*]
 		#pallet_struct
 		#config
 		#call
+		#tasks
 		#error
 		#event
 		#storages
diff --git a/substrate/frame/support/procedural/src/pallet/expand/tasks.rs b/substrate/frame/support/procedural/src/pallet/expand/tasks.rs
new file mode 100644
index 00000000000..6697e5c822a
--- /dev/null
+++ b/substrate/frame/support/procedural/src/pallet/expand/tasks.rs
@@ -0,0 +1,267 @@
+//! Contains logic for expanding task-related items.
+
+// 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.
+
+//! Home of the expansion code for the Tasks API
+
+use crate::pallet::{parse::tasks::*, Def};
+use derive_syn_parse::Parse;
+use inflector::Inflector;
+use proc_macro2::TokenStream as TokenStream2;
+use quote::{format_ident, quote, ToTokens};
+use syn::{parse_quote, spanned::Spanned, ItemEnum, ItemImpl};
+
+impl TaskEnumDef {
+	/// Since we optionally allow users to manually specify a `#[pallet::task_enum]`, in the
+	/// event they _don't_ specify one (which is actually the most common behavior) we have to
+	/// generate one based on the existing [`TasksDef`]. This method performs that generation.
+	pub fn generate(
+		tasks: &TasksDef,
+		type_decl_bounded_generics: TokenStream2,
+		type_use_generics: TokenStream2,
+	) -> Self {
+		let variants = if tasks.tasks_attr.is_some() {
+			tasks
+				.tasks
+				.iter()
+				.map(|task| {
+					let ident = &task.item.sig.ident;
+					let ident =
+						format_ident!("{}", ident.to_string().to_class_case(), span = ident.span());
+
+					let args = task.item.sig.inputs.iter().collect::<Vec<_>>();
+
+					if args.is_empty() {
+						quote!(#ident)
+					} else {
+						quote!(#ident {
+							#(#args),*
+						})
+					}
+				})
+				.collect::<Vec<_>>()
+		} else {
+			Vec::new()
+		};
+		let mut task_enum_def: TaskEnumDef = parse_quote! {
+			/// Auto-generated enum that encapsulates all tasks defined by this pallet.
+			///
+			/// Conceptually similar to the [`Call`] enum, but for tasks. This is only
+			/// generated if there are tasks present in this pallet.
+			#[pallet::task_enum]
+			pub enum Task<#type_decl_bounded_generics> {
+				#(
+					#variants,
+				)*
+			}
+		};
+		task_enum_def.type_use_generics = type_use_generics;
+		task_enum_def
+	}
+}
+
+impl ToTokens for TaskEnumDef {
+	fn to_tokens(&self, tokens: &mut TokenStream2) {
+		let item_enum = &self.item_enum;
+		let ident = &item_enum.ident;
+		let vis = &item_enum.vis;
+		let attrs = &item_enum.attrs;
+		let generics = &item_enum.generics;
+		let variants = &item_enum.variants;
+		let scrate = &self.scrate;
+		let type_use_generics = &self.type_use_generics;
+		if self.attr.is_some() {
+			// `item_enum` is short-hand / generated enum
+			tokens.extend(quote! {
+				#(#attrs)*
+				#[derive(
+					#scrate::CloneNoBound,
+					#scrate::EqNoBound,
+					#scrate::PartialEqNoBound,
+					#scrate::pallet_prelude::Encode,
+					#scrate::pallet_prelude::Decode,
+					#scrate::pallet_prelude::TypeInfo,
+				)]
+				#[codec(encode_bound())]
+				#[codec(decode_bound())]
+				#[scale_info(skip_type_params(#type_use_generics))]
+				#vis enum #ident #generics {
+					#variants
+					#[doc(hidden)]
+					#[codec(skip)]
+					__Ignore(core::marker::PhantomData<T>, #scrate::Never),
+				}
+
+				impl<T: Config> core::fmt::Debug for #ident<#type_use_generics> {
+					fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+						f.debug_struct(stringify!(#ident)).field("value", self).finish()
+					}
+				}
+			});
+		} else {
+			// `item_enum` is a manually specified enum (no attribute)
+			tokens.extend(item_enum.to_token_stream());
+		}
+	}
+}
+
+/// Represents an already-expanded [`TasksDef`].
+#[derive(Parse)]
+pub struct ExpandedTasksDef {
+	pub task_item_impl: ItemImpl,
+	pub task_trait_impl: ItemImpl,
+}
+
+impl ToTokens for TasksDef {
+	fn to_tokens(&self, tokens: &mut TokenStream2) {
+		let scrate = &self.scrate;
+		let enum_ident = syn::Ident::new("Task", self.enum_ident.span());
+		let enum_arguments = &self.enum_arguments;
+		let enum_use = quote!(#enum_ident #enum_arguments);
+
+		let task_fn_idents = self
+			.tasks
+			.iter()
+			.map(|task| {
+				format_ident!(
+					"{}",
+					&task.item.sig.ident.to_string().to_class_case(),
+					span = task.item.sig.ident.span()
+				)
+			})
+			.collect::<Vec<_>>();
+		let task_indices = self.tasks.iter().map(|task| &task.index_attr.meta.index);
+		let task_conditions = self.tasks.iter().map(|task| &task.condition_attr.meta.expr);
+		let task_weights = self.tasks.iter().map(|task| &task.weight_attr.meta.expr);
+		let task_iters = self.tasks.iter().map(|task| &task.list_attr.meta.expr);
+
+		let task_fn_impls = self.tasks.iter().map(|task| {
+			let mut task_fn_impl = task.item.clone();
+			task_fn_impl.attrs = vec![];
+			task_fn_impl
+		});
+
+		let task_fn_names = self.tasks.iter().map(|task| &task.item.sig.ident);
+		let task_arg_names = self.tasks.iter().map(|task| &task.arg_names).collect::<Vec<_>>();
+
+		let sp_std = quote!(#scrate::__private::sp_std);
+		let impl_generics = &self.item_impl.generics;
+		tokens.extend(quote! {
+			impl #impl_generics #enum_use
+			{
+				#(#task_fn_impls)*
+			}
+
+			impl #impl_generics #scrate::traits::Task for #enum_use
+			{
+				type Enumeration = #sp_std::vec::IntoIter<#enum_use>;
+
+				fn iter() -> Self::Enumeration {
+					let mut all_tasks = #sp_std::vec![];
+					#(all_tasks
+						.extend(#task_iters.map(|(#(#task_arg_names),*)| #enum_ident::#task_fn_idents { #(#task_arg_names: #task_arg_names.clone()),* })
+						.collect::<#sp_std::vec::Vec<_>>());
+					)*
+					all_tasks.into_iter()
+				}
+
+				fn task_index(&self) -> u32 {
+					match self.clone() {
+						#(#enum_ident::#task_fn_idents { .. } => #task_indices,)*
+						Task::__Ignore(_, _) => unreachable!(),
+					}
+				}
+
+				fn is_valid(&self) -> bool {
+					match self.clone() {
+						#(#enum_ident::#task_fn_idents { #(#task_arg_names),* } => (#task_conditions)(#(#task_arg_names),* ),)*
+						Task::__Ignore(_, _) => unreachable!(),
+					}
+				}
+
+				fn run(&self) -> Result<(), #scrate::pallet_prelude::DispatchError> {
+					match self.clone() {
+						#(#enum_ident::#task_fn_idents { #(#task_arg_names),* } => {
+							<#enum_use>::#task_fn_names(#( #task_arg_names, )* )
+						},)*
+						Task::__Ignore(_, _) => unreachable!(),
+					}
+				}
+
+				#[allow(unused_variables)]
+				fn weight(&self) -> #scrate::pallet_prelude::Weight {
+					match self.clone() {
+						#(#enum_ident::#task_fn_idents { #(#task_arg_names),* } => #task_weights,)*
+						Task::__Ignore(_, _) => unreachable!(),
+					}
+				}
+			}
+		});
+	}
+}
+
+/// Expands the [`TasksDef`] in the enclosing [`Def`], if present, and returns its tokens.
+///
+/// This modifies the underlying [`Def`] in addition to returning any tokens that were added.
+pub fn expand_tasks_impl(def: &mut Def) -> TokenStream2 {
+	let Some(tasks) = &mut def.tasks else { return quote!() };
+	let ExpandedTasksDef { task_item_impl, task_trait_impl } = parse_quote!(#tasks);
+	quote! {
+		#task_item_impl
+		#task_trait_impl
+	}
+}
+
+/// Represents a fully-expanded [`TaskEnumDef`].
+#[derive(Parse)]
+pub struct ExpandedTaskEnum {
+	pub item_enum: ItemEnum,
+	pub debug_impl: ItemImpl,
+}
+
+/// Modifies a [`Def`] to expand the underlying [`TaskEnumDef`] if present, and also returns
+/// its tokens. A blank [`TokenStream2`] is returned if no [`TaskEnumDef`] has been generated
+/// or defined.
+pub fn expand_task_enum(def: &mut Def) -> TokenStream2 {
+	let Some(task_enum) = &mut def.task_enum else { return quote!() };
+	let ExpandedTaskEnum { item_enum, debug_impl } = parse_quote!(#task_enum);
+	quote! {
+		#item_enum
+		#debug_impl
+	}
+}
+
+/// Modifies a [`Def`] to expand the underlying [`TasksDef`] and also generate a
+/// [`TaskEnumDef`] if applicable. The tokens for these items are returned if they are created.
+pub fn expand_tasks(def: &mut Def) -> TokenStream2 {
+	if let Some(tasks_def) = &def.tasks {
+		if def.task_enum.is_none() {
+			def.task_enum = Some(TaskEnumDef::generate(
+				&tasks_def,
+				def.type_decl_bounded_generics(tasks_def.item_impl.span()),
+				def.type_use_generics(tasks_def.item_impl.span()),
+			));
+		}
+	}
+	let tasks_extra_output = expand_tasks_impl(def);
+	let task_enum_extra_output = expand_task_enum(def);
+	quote! {
+		#tasks_extra_output
+		#task_enum_extra_output
+	}
+}
diff --git a/substrate/frame/support/procedural/src/pallet/expand/tt_default_parts.rs b/substrate/frame/support/procedural/src/pallet/expand/tt_default_parts.rs
index c9a776ee247..7cc1415dfdd 100644
--- a/substrate/frame/support/procedural/src/pallet/expand/tt_default_parts.rs
+++ b/substrate/frame/support/procedural/src/pallet/expand/tt_default_parts.rs
@@ -31,6 +31,8 @@ pub fn expand_tt_default_parts(def: &mut Def) -> proc_macro2::TokenStream {
 
 	let call_part = def.call.as_ref().map(|_| quote::quote!(Call,));
 
+	let task_part = def.task_enum.as_ref().map(|_| quote::quote!(Task,));
+
 	let storage_part = (!def.storages.is_empty()).then(|| quote::quote!(Storage,));
 
 	let event_part = def.event.as_ref().map(|event| {
@@ -99,7 +101,7 @@ pub fn expand_tt_default_parts(def: &mut Def) -> proc_macro2::TokenStream {
 					tokens = [{
 						expanded::{
 							Pallet, #call_part #storage_part #event_part #error_part #origin_part #config_part
-							#inherent_part #validate_unsigned_part #freeze_reason_part
+							#inherent_part #validate_unsigned_part #freeze_reason_part #task_part
 							#hold_reason_part #lock_id_part #slash_reason_part
 						}
 					}]
diff --git a/substrate/frame/support/procedural/src/pallet/parse/call.rs b/substrate/frame/support/procedural/src/pallet/parse/call.rs
index f78f2baa9d1..58e14365e76 100644
--- a/substrate/frame/support/procedural/src/pallet/parse/call.rs
+++ b/substrate/frame/support/procedural/src/pallet/parse/call.rs
@@ -286,8 +286,7 @@ impl CallDef {
 				if weight_attrs.is_empty() && dev_mode {
 					// inject a default O(1) weight when dev mode is enabled and no weight has
 					// been specified on the call
-					let empty_weight: syn::Expr = syn::parse(quote::quote!(0).into())
-						.expect("we are parsing a quoted string; qed");
+					let empty_weight: syn::Expr = syn::parse_quote!(0);
 					weight_attrs.push(FunctionAttr::Weight(empty_weight));
 				}
 
diff --git a/substrate/frame/support/procedural/src/pallet/parse/composite.rs b/substrate/frame/support/procedural/src/pallet/parse/composite.rs
index 6e6ea6a795c..fa5f47dfdfa 100644
--- a/substrate/frame/support/procedural/src/pallet/parse/composite.rs
+++ b/substrate/frame/support/procedural/src/pallet/parse/composite.rs
@@ -26,11 +26,14 @@ pub mod keyword {
 	syn::custom_keyword!(HoldReason);
 	syn::custom_keyword!(LockId);
 	syn::custom_keyword!(SlashReason);
+	syn::custom_keyword!(Task);
+
 	pub enum CompositeKeyword {
 		FreezeReason(FreezeReason),
 		HoldReason(HoldReason),
 		LockId(LockId),
 		SlashReason(SlashReason),
+		Task(Task),
 	}
 
 	impl ToTokens for CompositeKeyword {
@@ -41,6 +44,7 @@ pub mod keyword {
 				HoldReason(inner) => inner.to_tokens(tokens),
 				LockId(inner) => inner.to_tokens(tokens),
 				SlashReason(inner) => inner.to_tokens(tokens),
+				Task(inner) => inner.to_tokens(tokens),
 			}
 		}
 	}
@@ -56,6 +60,8 @@ pub mod keyword {
 				Ok(Self::LockId(input.parse()?))
 			} else if lookahead.peek(SlashReason) {
 				Ok(Self::SlashReason(input.parse()?))
+			} else if lookahead.peek(Task) {
+				Ok(Self::Task(input.parse()?))
 			} else {
 				Err(lookahead.error())
 			}
@@ -71,6 +77,7 @@ pub mod keyword {
 				match self {
 					FreezeReason(_) => "FreezeReason",
 					HoldReason(_) => "HoldReason",
+					Task(_) => "Task",
 					LockId(_) => "LockId",
 					SlashReason(_) => "SlashReason",
 				}
@@ -80,7 +87,7 @@ pub mod keyword {
 }
 
 pub struct CompositeDef {
-	/// The index of the HoldReason item in the pallet module.
+	/// The index of the CompositeDef item in the pallet module.
 	pub index: usize,
 	/// The composite keyword used (contains span).
 	pub composite_keyword: keyword::CompositeKeyword,
diff --git a/substrate/frame/support/procedural/src/pallet/parse/mod.rs b/substrate/frame/support/procedural/src/pallet/parse/mod.rs
index 83a881751ef..e1efdbcc202 100644
--- a/substrate/frame/support/procedural/src/pallet/parse/mod.rs
+++ b/substrate/frame/support/procedural/src/pallet/parse/mod.rs
@@ -33,11 +33,16 @@ pub mod inherent;
 pub mod origin;
 pub mod pallet_struct;
 pub mod storage;
+pub mod tasks;
 pub mod type_value;
 pub mod validate_unsigned;
 
+#[cfg(test)]
+pub mod tests;
+
 use composite::{keyword::CompositeKeyword, CompositeDef};
 use frame_support_procedural_tools::generate_access_from_frame_or_crate;
+use quote::ToTokens;
 use syn::spanned::Spanned;
 
 /// Parsed definition of a pallet.
@@ -49,6 +54,8 @@ pub struct Def {
 	pub pallet_struct: pallet_struct::PalletStructDef,
 	pub hooks: Option<hooks::HooksDef>,
 	pub call: Option<call::CallDef>,
+	pub tasks: Option<tasks::TasksDef>,
+	pub task_enum: Option<tasks::TaskEnumDef>,
 	pub storages: Vec<storage::StorageDef>,
 	pub error: Option<error::ErrorDef>,
 	pub event: Option<event::EventDef>,
@@ -84,6 +91,8 @@ impl Def {
 		let mut pallet_struct = None;
 		let mut hooks = None;
 		let mut call = None;
+		let mut tasks = None;
+		let mut task_enum = None;
 		let mut error = None;
 		let mut event = None;
 		let mut origin = None;
@@ -118,6 +127,32 @@ impl Def {
 				},
 				Some(PalletAttr::RuntimeCall(cw, span)) if call.is_none() =>
 					call = Some(call::CallDef::try_from(span, index, item, dev_mode, cw)?),
+				Some(PalletAttr::Tasks(_)) if tasks.is_none() => {
+					let item_tokens = item.to_token_stream();
+					// `TasksDef::parse` needs to know if attr was provided so we artificially
+					// re-insert it here
+					tasks = Some(syn::parse2::<tasks::TasksDef>(quote::quote! {
+						#[pallet::tasks_experimental]
+						#item_tokens
+					})?);
+
+					// replace item with a no-op because it will be handled by the expansion of tasks
+					*item = syn::Item::Verbatim(quote::quote!());
+				}
+				Some(PalletAttr::TaskCondition(span)) => return Err(syn::Error::new(
+					span,
+					"`#[pallet::task_condition]` can only be used on items within an `impl` statement."
+				)),
+				Some(PalletAttr::TaskIndex(span)) => return Err(syn::Error::new(
+					span,
+					"`#[pallet::task_index]` can only be used on items within an `impl` statement."
+				)),
+				Some(PalletAttr::TaskList(span)) => return Err(syn::Error::new(
+					span,
+					"`#[pallet::task_list]` can only be used on items within an `impl` statement."
+				)),
+				Some(PalletAttr::RuntimeTask(_)) if task_enum.is_none() =>
+					task_enum = Some(syn::parse2::<tasks::TaskEnumDef>(item.to_token_stream())?),
 				Some(PalletAttr::Error(span)) if error.is_none() =>
 					error = Some(error::ErrorDef::try_from(span, index, item)?),
 				Some(PalletAttr::RuntimeEvent(span)) if event.is_none() =>
@@ -190,6 +225,8 @@ impl Def {
 			return Err(syn::Error::new(item_span, msg))
 		}
 
+		Self::resolve_tasks(&item_span, &mut tasks, &mut task_enum, items)?;
+
 		let def = Def {
 			item,
 			config: config
@@ -198,6 +235,8 @@ impl Def {
 				.ok_or_else(|| syn::Error::new(item_span, "Missing `#[pallet::pallet]`"))?,
 			hooks,
 			call,
+			tasks,
+			task_enum,
 			extra_constants,
 			genesis_config,
 			genesis_build,
@@ -220,6 +259,99 @@ impl Def {
 		Ok(def)
 	}
 
+	/// Performs extra logic checks necessary for the `#[pallet::tasks_experimental]` feature.
+	fn resolve_tasks(
+		item_span: &proc_macro2::Span,
+		tasks: &mut Option<tasks::TasksDef>,
+		task_enum: &mut Option<tasks::TaskEnumDef>,
+		items: &mut Vec<syn::Item>,
+	) -> syn::Result<()> {
+		// fallback for manual (without macros) definition of tasks impl
+		Self::resolve_manual_tasks_impl(tasks, task_enum, items)?;
+
+		// fallback for manual (without macros) definition of task enum
+		Self::resolve_manual_task_enum(tasks, task_enum, items)?;
+
+		// ensure that if `task_enum` is specified, `tasks` is also specified
+		match (&task_enum, &tasks) {
+			(Some(_), None) =>
+				return Err(syn::Error::new(
+					*item_span,
+					"Missing `#[pallet::tasks_experimental]` impl",
+				)),
+			(None, Some(tasks)) =>
+				if tasks.tasks_attr.is_none() {
+					return Err(syn::Error::new(
+						tasks.item_impl.impl_token.span(),
+						"A `#[pallet::tasks_experimental]` attribute must be attached to your `Task` impl if the \
+						task enum has been omitted",
+					))
+				} else {
+				},
+			_ => (),
+		}
+
+		Ok(())
+	}
+
+	/// Tries to locate task enum based on the tasks impl target if attribute is not specified
+	/// but impl is present. If one is found, `task_enum` is set appropriately.
+	fn resolve_manual_task_enum(
+		tasks: &Option<tasks::TasksDef>,
+		task_enum: &mut Option<tasks::TaskEnumDef>,
+		items: &mut Vec<syn::Item>,
+	) -> syn::Result<()> {
+		let (None, Some(tasks)) = (&task_enum, &tasks) else { return Ok(()) };
+		let syn::Type::Path(type_path) = &*tasks.item_impl.self_ty else { return Ok(()) };
+		let type_path = type_path.path.segments.iter().collect::<Vec<_>>();
+		let (Some(seg), None) = (type_path.get(0), type_path.get(1)) else { return Ok(()) };
+		let mut result = None;
+		for item in items {
+			let syn::Item::Enum(item_enum) = item else { continue };
+			if item_enum.ident == seg.ident {
+				result = Some(syn::parse2::<tasks::TaskEnumDef>(item_enum.to_token_stream())?);
+				// replace item with a no-op because it will be handled by the expansion of
+				// `task_enum`. We use a no-op instead of simply removing it from the vec
+				// so that any indices collected by `Def::try_from` remain accurate
+				*item = syn::Item::Verbatim(quote::quote!());
+				break
+			}
+		}
+		*task_enum = result;
+		Ok(())
+	}
+
+	/// Tries to locate a manual tasks impl (an impl impling a trait whose last path segment is
+	/// `Task`) in the event that one has not been found already via the attribute macro
+	pub fn resolve_manual_tasks_impl(
+		tasks: &mut Option<tasks::TasksDef>,
+		task_enum: &Option<tasks::TaskEnumDef>,
+		items: &Vec<syn::Item>,
+	) -> syn::Result<()> {
+		let None = tasks else { return Ok(()) };
+		let mut result = None;
+		for item in items {
+			let syn::Item::Impl(item_impl) = item else { continue };
+			let Some((_, path, _)) = &item_impl.trait_ else { continue };
+			let Some(trait_last_seg) = path.segments.last() else { continue };
+			let syn::Type::Path(target_path) = &*item_impl.self_ty else { continue };
+			let target_path = target_path.path.segments.iter().collect::<Vec<_>>();
+			let (Some(target_ident), None) = (target_path.get(0), target_path.get(1)) else {
+				continue
+			};
+			let matches_task_enum = match task_enum {
+				Some(task_enum) => task_enum.item_enum.ident == target_ident.ident,
+				None => true,
+			};
+			if trait_last_seg.ident == "Task" && matches_task_enum {
+				result = Some(syn::parse2::<tasks::TasksDef>(item_impl.to_token_stream())?);
+				break
+			}
+		}
+		*tasks = result;
+		Ok(())
+	}
+
 	/// Check that usage of trait `Event` is consistent with the definition, i.e. it is declared
 	/// and trait defines type RuntimeEvent, or not declared and no trait associated type.
 	fn check_event_usage(&self) -> syn::Result<()> {
@@ -408,6 +540,11 @@ impl GenericKind {
 mod keyword {
 	syn::custom_keyword!(origin);
 	syn::custom_keyword!(call);
+	syn::custom_keyword!(tasks_experimental);
+	syn::custom_keyword!(task_enum);
+	syn::custom_keyword!(task_list);
+	syn::custom_keyword!(task_condition);
+	syn::custom_keyword!(task_index);
 	syn::custom_keyword!(weight);
 	syn::custom_keyword!(event);
 	syn::custom_keyword!(config);
@@ -472,6 +609,11 @@ enum PalletAttr {
 	/// instead of the zero weight. So to say: it works together with `dev_mode`.
 	RuntimeCall(Option<InheritedCallWeightAttr>, proc_macro2::Span),
 	Error(proc_macro2::Span),
+	Tasks(proc_macro2::Span),
+	TaskList(proc_macro2::Span),
+	TaskCondition(proc_macro2::Span),
+	TaskIndex(proc_macro2::Span),
+	RuntimeTask(proc_macro2::Span),
 	RuntimeEvent(proc_macro2::Span),
 	RuntimeOrigin(proc_macro2::Span),
 	Inherent(proc_macro2::Span),
@@ -490,8 +632,13 @@ impl PalletAttr {
 			Self::Config(span, _) => *span,
 			Self::Pallet(span) => *span,
 			Self::Hooks(span) => *span,
-			Self::RuntimeCall(_, span) => *span,
+			Self::Tasks(span) => *span,
+			Self::TaskCondition(span) => *span,
+			Self::TaskIndex(span) => *span,
+			Self::TaskList(span) => *span,
 			Self::Error(span) => *span,
+			Self::RuntimeTask(span) => *span,
+			Self::RuntimeCall(_, span) => *span,
 			Self::RuntimeEvent(span) => *span,
 			Self::RuntimeOrigin(span) => *span,
 			Self::Inherent(span) => *span,
@@ -535,6 +682,16 @@ impl syn::parse::Parse for PalletAttr {
 				false => Some(InheritedCallWeightAttr::parse(&content)?),
 			};
 			Ok(PalletAttr::RuntimeCall(attr, span))
+		} else if lookahead.peek(keyword::tasks_experimental) {
+			Ok(PalletAttr::Tasks(content.parse::<keyword::tasks_experimental>()?.span()))
+		} else if lookahead.peek(keyword::task_enum) {
+			Ok(PalletAttr::RuntimeTask(content.parse::<keyword::task_enum>()?.span()))
+		} else if lookahead.peek(keyword::task_condition) {
+			Ok(PalletAttr::TaskCondition(content.parse::<keyword::task_condition>()?.span()))
+		} else if lookahead.peek(keyword::task_index) {
+			Ok(PalletAttr::TaskIndex(content.parse::<keyword::task_index>()?.span()))
+		} else if lookahead.peek(keyword::task_list) {
+			Ok(PalletAttr::TaskList(content.parse::<keyword::task_list>()?.span()))
 		} else if lookahead.peek(keyword::error) {
 			Ok(PalletAttr::Error(content.parse::<keyword::error>()?.span()))
 		} else if lookahead.peek(keyword::event) {
diff --git a/substrate/frame/support/procedural/src/pallet/parse/tasks.rs b/substrate/frame/support/procedural/src/pallet/parse/tasks.rs
new file mode 100644
index 00000000000..6405bb415a6
--- /dev/null
+++ b/substrate/frame/support/procedural/src/pallet/parse/tasks.rs
@@ -0,0 +1,968 @@
+// 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.
+
+//! Home of the parsing code for the Tasks API
+
+use std::collections::HashSet;
+
+#[cfg(test)]
+use crate::assert_parse_error_matches;
+
+#[cfg(test)]
+use crate::pallet::parse::tests::simulate_manifest_dir;
+
+use derive_syn_parse::Parse;
+use frame_support_procedural_tools::generate_access_from_frame_or_crate;
+use proc_macro2::TokenStream as TokenStream2;
+use quote::{quote, ToTokens};
+use syn::{
+	parse::ParseStream,
+	parse2,
+	spanned::Spanned,
+	token::{Bracket, Paren, PathSep, Pound},
+	Attribute, Error, Expr, Ident, ImplItem, ImplItemFn, ItemEnum, ItemImpl, LitInt, Path,
+	PathArguments, Result, TypePath,
+};
+
+pub mod keywords {
+	use syn::custom_keyword;
+
+	custom_keyword!(tasks_experimental);
+	custom_keyword!(task_enum);
+	custom_keyword!(task_list);
+	custom_keyword!(task_condition);
+	custom_keyword!(task_index);
+	custom_keyword!(task_weight);
+	custom_keyword!(pallet);
+}
+
+/// Represents the `#[pallet::tasks_experimental]` attribute and its attached item. Also includes
+/// metadata about the linked [`TaskEnumDef`] if applicable.
+#[derive(Clone, Debug)]
+pub struct TasksDef {
+	pub tasks_attr: Option<PalletTasksAttr>,
+	pub tasks: Vec<TaskDef>,
+	pub item_impl: ItemImpl,
+	/// Path to `frame_support`
+	pub scrate: Path,
+	pub enum_ident: Ident,
+	pub enum_arguments: PathArguments,
+}
+
+impl syn::parse::Parse for TasksDef {
+	fn parse(input: ParseStream) -> Result<Self> {
+		let item_impl: ItemImpl = input.parse()?;
+		let (tasks_attrs, normal_attrs) = partition_tasks_attrs(&item_impl);
+		let tasks_attr = match tasks_attrs.first() {
+			Some(attr) => Some(parse2::<PalletTasksAttr>(attr.to_token_stream())?),
+			None => None,
+		};
+		if let Some(extra_tasks_attr) = tasks_attrs.get(1) {
+			return Err(Error::new(
+				extra_tasks_attr.span(),
+				"unexpected extra `#[pallet::tasks_experimental]` attribute",
+			))
+		}
+		let tasks: Vec<TaskDef> = if tasks_attr.is_some() {
+			item_impl
+				.items
+				.clone()
+				.into_iter()
+				.filter(|impl_item| matches!(impl_item, ImplItem::Fn(_)))
+				.map(|item| parse2::<TaskDef>(item.to_token_stream()))
+				.collect::<Result<_>>()?
+		} else {
+			Vec::new()
+		};
+		let mut task_indices = HashSet::<LitInt>::new();
+		for task in tasks.iter() {
+			let task_index = &task.index_attr.meta.index;
+			if !task_indices.insert(task_index.clone()) {
+				return Err(Error::new(
+					task_index.span(),
+					format!("duplicate task index `{}`", task_index),
+				))
+			}
+		}
+		let mut item_impl = item_impl;
+		item_impl.attrs = normal_attrs;
+
+		// we require the path on the impl to be a TypePath
+		let enum_path = parse2::<TypePath>(item_impl.self_ty.to_token_stream())?;
+		let segments = enum_path.path.segments.iter().collect::<Vec<_>>();
+		let (Some(last_seg), None) = (segments.get(0), segments.get(1)) else {
+			return Err(Error::new(
+				enum_path.span(),
+				"if specified manually, the task enum must be defined locally in this \
+				pallet and cannot be a re-export",
+			))
+		};
+		let enum_ident = last_seg.ident.clone();
+		let enum_arguments = last_seg.arguments.clone();
+
+		// We do this here because it would be improper to do something fallible like this at
+		// the expansion phase. Fallible stuff should happen during parsing.
+		let scrate = generate_access_from_frame_or_crate("frame-support")?;
+
+		Ok(TasksDef { tasks_attr, item_impl, tasks, scrate, enum_ident, enum_arguments })
+	}
+}
+
+/// Parsing for a `#[pallet::tasks_experimental]` attr.
+pub type PalletTasksAttr = PalletTaskAttr<keywords::tasks_experimental>;
+
+/// Parsing for any of the attributes that can be used within a `#[pallet::tasks_experimental]`
+/// [`ItemImpl`].
+pub type TaskAttr = PalletTaskAttr<TaskAttrMeta>;
+
+/// Parsing for a `#[pallet::task_index]` attr.
+pub type TaskIndexAttr = PalletTaskAttr<TaskIndexAttrMeta>;
+
+/// Parsing for a `#[pallet::task_condition]` attr.
+pub type TaskConditionAttr = PalletTaskAttr<TaskConditionAttrMeta>;
+
+/// Parsing for a `#[pallet::task_list]` attr.
+pub type TaskListAttr = PalletTaskAttr<TaskListAttrMeta>;
+
+/// Parsing for a `#[pallet::task_weight]` attr.
+pub type TaskWeightAttr = PalletTaskAttr<TaskWeightAttrMeta>;
+
+/// Parsing for a `#[pallet:task_enum]` attr.
+pub type PalletTaskEnumAttr = PalletTaskAttr<keywords::task_enum>;
+
+/// Parsing for a manually-specified (or auto-generated) task enum, optionally including the
+/// attached `#[pallet::task_enum]` attribute.
+#[derive(Clone, Debug)]
+pub struct TaskEnumDef {
+	pub attr: Option<PalletTaskEnumAttr>,
+	pub item_enum: ItemEnum,
+	pub scrate: Path,
+	pub type_use_generics: TokenStream2,
+}
+
+impl syn::parse::Parse for TaskEnumDef {
+	fn parse(input: ParseStream) -> Result<Self> {
+		let mut item_enum = input.parse::<ItemEnum>()?;
+		let attr = extract_pallet_attr(&mut item_enum)?;
+		let attr = match attr {
+			Some(attr) => Some(parse2(attr)?),
+			None => None,
+		};
+
+		// We do this here because it would be improper to do something fallible like this at
+		// the expansion phase. Fallible stuff should happen during parsing.
+		let scrate = generate_access_from_frame_or_crate("frame-support")?;
+
+		let type_use_generics = quote!(T);
+
+		Ok(TaskEnumDef { attr, item_enum, scrate, type_use_generics })
+	}
+}
+
+/// Represents an individual tasks within a [`TasksDef`].
+#[derive(Debug, Clone)]
+pub struct TaskDef {
+	pub index_attr: TaskIndexAttr,
+	pub condition_attr: TaskConditionAttr,
+	pub list_attr: TaskListAttr,
+	pub weight_attr: TaskWeightAttr,
+	pub normal_attrs: Vec<Attribute>,
+	pub item: ImplItemFn,
+	pub arg_names: Vec<Ident>,
+}
+
+impl syn::parse::Parse for TaskDef {
+	fn parse(input: ParseStream) -> Result<Self> {
+		let item = input.parse::<ImplItemFn>()?;
+		// we only want to activate TaskAttrType parsing errors for tasks-related attributes,
+		// so we filter them here
+		let (task_attrs, normal_attrs) = partition_task_attrs(&item);
+
+		let task_attrs: Vec<TaskAttr> = task_attrs
+			.into_iter()
+			.map(|attr| parse2(attr.to_token_stream()))
+			.collect::<Result<_>>()?;
+
+		let Some(index_attr) = task_attrs
+			.iter()
+			.find(|attr| matches!(attr.meta, TaskAttrMeta::TaskIndex(_)))
+			.cloned()
+		else {
+			return Err(Error::new(
+				item.sig.ident.span(),
+				"missing `#[pallet::task_index(..)]` attribute",
+			))
+		};
+
+		let Some(condition_attr) = task_attrs
+			.iter()
+			.find(|attr| matches!(attr.meta, TaskAttrMeta::TaskCondition(_)))
+			.cloned()
+		else {
+			return Err(Error::new(
+				item.sig.ident.span(),
+				"missing `#[pallet::task_condition(..)]` attribute",
+			))
+		};
+
+		let Some(list_attr) = task_attrs
+			.iter()
+			.find(|attr| matches!(attr.meta, TaskAttrMeta::TaskList(_)))
+			.cloned()
+		else {
+			return Err(Error::new(
+				item.sig.ident.span(),
+				"missing `#[pallet::task_list(..)]` attribute",
+			))
+		};
+
+		let Some(weight_attr) = task_attrs
+			.iter()
+			.find(|attr| matches!(attr.meta, TaskAttrMeta::TaskWeight(_)))
+			.cloned()
+		else {
+			return Err(Error::new(
+				item.sig.ident.span(),
+				"missing `#[pallet::task_weight(..)]` attribute",
+			))
+		};
+
+		if let Some(duplicate) = task_attrs
+			.iter()
+			.filter(|attr| matches!(attr.meta, TaskAttrMeta::TaskCondition(_)))
+			.collect::<Vec<_>>()
+			.get(1)
+		{
+			return Err(Error::new(
+				duplicate.span(),
+				"unexpected extra `#[pallet::task_condition(..)]` attribute",
+			))
+		}
+
+		if let Some(duplicate) = task_attrs
+			.iter()
+			.filter(|attr| matches!(attr.meta, TaskAttrMeta::TaskList(_)))
+			.collect::<Vec<_>>()
+			.get(1)
+		{
+			return Err(Error::new(
+				duplicate.span(),
+				"unexpected extra `#[pallet::task_list(..)]` attribute",
+			))
+		}
+
+		if let Some(duplicate) = task_attrs
+			.iter()
+			.filter(|attr| matches!(attr.meta, TaskAttrMeta::TaskIndex(_)))
+			.collect::<Vec<_>>()
+			.get(1)
+		{
+			return Err(Error::new(
+				duplicate.span(),
+				"unexpected extra `#[pallet::task_index(..)]` attribute",
+			))
+		}
+
+		let mut arg_names = vec![];
+		for input in item.sig.inputs.iter() {
+			match input {
+				syn::FnArg::Typed(pat_type) => match &*pat_type.pat {
+					syn::Pat::Ident(ident) => arg_names.push(ident.ident.clone()),
+					_ => return Err(Error::new(input.span(), "unexpected pattern type")),
+				},
+				_ => return Err(Error::new(input.span(), "unexpected function argument type")),
+			}
+		}
+
+		let index_attr = index_attr.try_into().expect("we check the type above; QED");
+		let condition_attr = condition_attr.try_into().expect("we check the type above; QED");
+		let list_attr = list_attr.try_into().expect("we check the type above; QED");
+		let weight_attr = weight_attr.try_into().expect("we check the type above; QED");
+
+		Ok(TaskDef {
+			index_attr,
+			condition_attr,
+			list_attr,
+			weight_attr,
+			normal_attrs,
+			item,
+			arg_names,
+		})
+	}
+}
+
+/// The contents of a [`TasksDef`]-related attribute.
+#[derive(Parse, Debug, Clone)]
+pub enum TaskAttrMeta {
+	#[peek(keywords::task_list, name = "#[pallet::task_list(..)]")]
+	TaskList(TaskListAttrMeta),
+	#[peek(keywords::task_index, name = "#[pallet::task_index(..)")]
+	TaskIndex(TaskIndexAttrMeta),
+	#[peek(keywords::task_condition, name = "#[pallet::task_condition(..)")]
+	TaskCondition(TaskConditionAttrMeta),
+	#[peek(keywords::task_weight, name = "#[pallet::task_weight(..)")]
+	TaskWeight(TaskWeightAttrMeta),
+}
+
+/// The contents of a `#[pallet::task_list]` attribute.
+#[derive(Parse, Debug, Clone)]
+pub struct TaskListAttrMeta {
+	pub task_list: keywords::task_list,
+	#[paren]
+	_paren: Paren,
+	#[inside(_paren)]
+	pub expr: Expr,
+}
+
+/// The contents of a `#[pallet::task_index]` attribute.
+#[derive(Parse, Debug, Clone)]
+pub struct TaskIndexAttrMeta {
+	pub task_index: keywords::task_index,
+	#[paren]
+	_paren: Paren,
+	#[inside(_paren)]
+	pub index: LitInt,
+}
+
+/// The contents of a `#[pallet::task_condition]` attribute.
+#[derive(Parse, Debug, Clone)]
+pub struct TaskConditionAttrMeta {
+	pub task_condition: keywords::task_condition,
+	#[paren]
+	_paren: Paren,
+	#[inside(_paren)]
+	pub expr: Expr,
+}
+
+/// The contents of a `#[pallet::task_weight]` attribute.
+#[derive(Parse, Debug, Clone)]
+pub struct TaskWeightAttrMeta {
+	pub task_weight: keywords::task_weight,
+	#[paren]
+	_paren: Paren,
+	#[inside(_paren)]
+	pub expr: Expr,
+}
+
+/// The contents of a `#[pallet::task]` attribute.
+#[derive(Parse, Debug, Clone)]
+pub struct PalletTaskAttr<T: syn::parse::Parse + core::fmt::Debug + ToTokens> {
+	pub pound: Pound,
+	#[bracket]
+	_bracket: Bracket,
+	#[inside(_bracket)]
+	pub pallet: keywords::pallet,
+	#[inside(_bracket)]
+	pub colons: PathSep,
+	#[inside(_bracket)]
+	pub meta: T,
+}
+
+impl ToTokens for TaskListAttrMeta {
+	fn to_tokens(&self, tokens: &mut TokenStream2) {
+		let task_list = self.task_list;
+		let expr = &self.expr;
+		tokens.extend(quote!(#task_list(#expr)));
+	}
+}
+
+impl ToTokens for TaskConditionAttrMeta {
+	fn to_tokens(&self, tokens: &mut TokenStream2) {
+		let task_condition = self.task_condition;
+		let expr = &self.expr;
+		tokens.extend(quote!(#task_condition(#expr)));
+	}
+}
+
+impl ToTokens for TaskWeightAttrMeta {
+	fn to_tokens(&self, tokens: &mut TokenStream2) {
+		let task_weight = self.task_weight;
+		let expr = &self.expr;
+		tokens.extend(quote!(#task_weight(#expr)));
+	}
+}
+
+impl ToTokens for TaskIndexAttrMeta {
+	fn to_tokens(&self, tokens: &mut TokenStream2) {
+		let task_index = self.task_index;
+		let index = &self.index;
+		tokens.extend(quote!(#task_index(#index)))
+	}
+}
+
+impl ToTokens for TaskAttrMeta {
+	fn to_tokens(&self, tokens: &mut TokenStream2) {
+		match self {
+			TaskAttrMeta::TaskList(list) => tokens.extend(list.to_token_stream()),
+			TaskAttrMeta::TaskIndex(index) => tokens.extend(index.to_token_stream()),
+			TaskAttrMeta::TaskCondition(condition) => tokens.extend(condition.to_token_stream()),
+			TaskAttrMeta::TaskWeight(weight) => tokens.extend(weight.to_token_stream()),
+		}
+	}
+}
+
+impl<T: syn::parse::Parse + core::fmt::Debug + ToTokens> ToTokens for PalletTaskAttr<T> {
+	fn to_tokens(&self, tokens: &mut TokenStream2) {
+		let pound = self.pound;
+		let pallet = self.pallet;
+		let colons = self.colons;
+		let meta = &self.meta;
+		tokens.extend(quote!(#pound[#pallet #colons #meta]));
+	}
+}
+
+impl TryFrom<PalletTaskAttr<TaskAttrMeta>> for TaskIndexAttr {
+	type Error = syn::Error;
+
+	fn try_from(value: PalletTaskAttr<TaskAttrMeta>) -> Result<Self> {
+		let pound = value.pound;
+		let pallet = value.pallet;
+		let colons = value.colons;
+		match value.meta {
+			TaskAttrMeta::TaskIndex(meta) => parse2(quote!(#pound[#pallet #colons #meta])),
+			_ =>
+				return Err(Error::new(
+					value.span(),
+					format!("`{:?}` cannot be converted to a `TaskIndexAttr`", value.meta),
+				)),
+		}
+	}
+}
+
+impl TryFrom<PalletTaskAttr<TaskAttrMeta>> for TaskConditionAttr {
+	type Error = syn::Error;
+
+	fn try_from(value: PalletTaskAttr<TaskAttrMeta>) -> Result<Self> {
+		let pound = value.pound;
+		let pallet = value.pallet;
+		let colons = value.colons;
+		match value.meta {
+			TaskAttrMeta::TaskCondition(meta) => parse2(quote!(#pound[#pallet #colons #meta])),
+			_ =>
+				return Err(Error::new(
+					value.span(),
+					format!("`{:?}` cannot be converted to a `TaskConditionAttr`", value.meta),
+				)),
+		}
+	}
+}
+
+impl TryFrom<PalletTaskAttr<TaskAttrMeta>> for TaskWeightAttr {
+	type Error = syn::Error;
+
+	fn try_from(value: PalletTaskAttr<TaskAttrMeta>) -> Result<Self> {
+		let pound = value.pound;
+		let pallet = value.pallet;
+		let colons = value.colons;
+		match value.meta {
+			TaskAttrMeta::TaskWeight(meta) => parse2(quote!(#pound[#pallet #colons #meta])),
+			_ =>
+				return Err(Error::new(
+					value.span(),
+					format!("`{:?}` cannot be converted to a `TaskWeightAttr`", value.meta),
+				)),
+		}
+	}
+}
+
+impl TryFrom<PalletTaskAttr<TaskAttrMeta>> for TaskListAttr {
+	type Error = syn::Error;
+
+	fn try_from(value: PalletTaskAttr<TaskAttrMeta>) -> Result<Self> {
+		let pound = value.pound;
+		let pallet = value.pallet;
+		let colons = value.colons;
+		match value.meta {
+			TaskAttrMeta::TaskList(meta) => parse2(quote!(#pound[#pallet #colons #meta])),
+			_ =>
+				return Err(Error::new(
+					value.span(),
+					format!("`{:?}` cannot be converted to a `TaskListAttr`", value.meta),
+				)),
+		}
+	}
+}
+
+fn extract_pallet_attr(item_enum: &mut ItemEnum) -> Result<Option<TokenStream2>> {
+	let mut duplicate = None;
+	let mut attr = None;
+	item_enum.attrs = item_enum
+		.attrs
+		.iter()
+		.filter(|found_attr| {
+			let segs = found_attr
+				.path()
+				.segments
+				.iter()
+				.map(|seg| seg.ident.clone())
+				.collect::<Vec<_>>();
+			let (Some(seg1), Some(_), None) = (segs.get(0), segs.get(1), segs.get(2)) else {
+				return true
+			};
+			if seg1 != "pallet" {
+				return true
+			}
+			if attr.is_some() {
+				duplicate = Some(found_attr.span());
+			}
+			attr = Some(found_attr.to_token_stream());
+			false
+		})
+		.cloned()
+		.collect();
+	if let Some(span) = duplicate {
+		return Err(Error::new(span, "only one `#[pallet::_]` attribute is supported on this item"))
+	}
+	Ok(attr)
+}
+
+fn partition_tasks_attrs(item_impl: &ItemImpl) -> (Vec<syn::Attribute>, Vec<syn::Attribute>) {
+	item_impl.attrs.clone().into_iter().partition(|attr| {
+		let mut path_segs = attr.path().segments.iter();
+		let (Some(prefix), Some(suffix), None) =
+			(path_segs.next(), path_segs.next(), path_segs.next())
+		else {
+			return false
+		};
+		prefix.ident == "pallet" && suffix.ident == "tasks_experimental"
+	})
+}
+
+fn partition_task_attrs(item: &ImplItemFn) -> (Vec<syn::Attribute>, Vec<syn::Attribute>) {
+	item.attrs.clone().into_iter().partition(|attr| {
+		let mut path_segs = attr.path().segments.iter();
+		let (Some(prefix), Some(suffix)) = (path_segs.next(), path_segs.next()) else {
+			return false
+		};
+		// N.B: the `PartialEq` impl between `Ident` and `&str` is more efficient than
+		// parsing and makes no stack or heap allocations
+		prefix.ident == "pallet" &&
+			(suffix.ident == "tasks_experimental" ||
+				suffix.ident == "task_list" ||
+				suffix.ident == "task_condition" ||
+				suffix.ident == "task_weight" ||
+				suffix.ident == "task_index")
+	})
+}
+
+#[test]
+fn test_parse_task_list_() {
+	parse2::<TaskAttr>(quote!(#[pallet::task_list(Something::iter())])).unwrap();
+	parse2::<TaskAttr>(quote!(#[pallet::task_list(Numbers::<T, I>::iter_keys())])).unwrap();
+	parse2::<TaskAttr>(quote!(#[pallet::task_list(iter())])).unwrap();
+	assert_parse_error_matches!(
+		parse2::<TaskAttr>(quote!(#[pallet::task_list()])),
+		"expected an expression"
+	);
+	assert_parse_error_matches!(
+		parse2::<TaskAttr>(quote!(#[pallet::task_list])),
+		"expected parentheses"
+	);
+}
+
+#[test]
+fn test_parse_task_index() {
+	parse2::<TaskAttr>(quote!(#[pallet::task_index(3)])).unwrap();
+	parse2::<TaskAttr>(quote!(#[pallet::task_index(0)])).unwrap();
+	parse2::<TaskAttr>(quote!(#[pallet::task_index(17)])).unwrap();
+	assert_parse_error_matches!(
+		parse2::<TaskAttr>(quote!(#[pallet::task_index])),
+		"expected parentheses"
+	);
+	assert_parse_error_matches!(
+		parse2::<TaskAttr>(quote!(#[pallet::task_index("hey")])),
+		"expected integer literal"
+	);
+	assert_parse_error_matches!(
+		parse2::<TaskAttr>(quote!(#[pallet::task_index(0.3)])),
+		"expected integer literal"
+	);
+}
+
+#[test]
+fn test_parse_task_condition() {
+	parse2::<TaskAttr>(quote!(#[pallet::task_condition(|x| x.is_some())])).unwrap();
+	parse2::<TaskAttr>(quote!(#[pallet::task_condition(|_x| some_expr())])).unwrap();
+	parse2::<TaskAttr>(quote!(#[pallet::task_condition(|| some_expr())])).unwrap();
+	parse2::<TaskAttr>(quote!(#[pallet::task_condition(some_expr())])).unwrap();
+}
+
+#[test]
+fn test_parse_tasks_attr() {
+	parse2::<PalletTasksAttr>(quote!(#[pallet::tasks_experimental])).unwrap();
+	assert_parse_error_matches!(
+		parse2::<PalletTasksAttr>(quote!(#[pallet::taskss])),
+		"expected `tasks_experimental`"
+	);
+	assert_parse_error_matches!(
+		parse2::<PalletTasksAttr>(quote!(#[pallet::tasks_])),
+		"expected `tasks_experimental`"
+	);
+	assert_parse_error_matches!(
+		parse2::<PalletTasksAttr>(quote!(#[pal::tasks])),
+		"expected `pallet`"
+	);
+	assert_parse_error_matches!(
+		parse2::<PalletTasksAttr>(quote!(#[pallet::tasks_experimental()])),
+		"unexpected token"
+	);
+}
+
+#[test]
+fn test_parse_tasks_def_basic() {
+	simulate_manifest_dir("../../examples/basic", || {
+		let parsed = parse2::<TasksDef>(quote! {
+			#[pallet::tasks_experimental]
+			impl<T: Config<I>, I: 'static> Pallet<T, I> {
+				/// Add a pair of numbers into the totals and remove them.
+				#[pallet::task_list(Numbers::<T, I>::iter_keys())]
+				#[pallet::task_condition(|i| Numbers::<T, I>::contains_key(i))]
+				#[pallet::task_index(0)]
+				#[pallet::task_weight(0)]
+				pub fn add_number_into_total(i: u32) -> DispatchResult {
+					let v = Numbers::<T, I>::take(i).ok_or(Error::<T, I>::NotFound)?;
+					Total::<T, I>::mutate(|(total_keys, total_values)| {
+						*total_keys += i;
+						*total_values += v;
+					});
+					Ok(())
+				}
+			}
+		})
+		.unwrap();
+		assert_eq!(parsed.tasks.len(), 1);
+	});
+}
+
+#[test]
+fn test_parse_tasks_def_basic_increment_decrement() {
+	simulate_manifest_dir("../../examples/basic", || {
+		let parsed = parse2::<TasksDef>(quote! {
+			#[pallet::tasks_experimental]
+			impl<T: Config<I>, I: 'static> Pallet<T, I> {
+				/// Get the value and check if it can be incremented
+				#[pallet::task_index(0)]
+				#[pallet::task_condition(|| {
+					let value = Value::<T>::get().unwrap();
+					value < 255
+				})]
+				#[pallet::task_list(Vec::<Task<T>>::new())]
+				#[pallet::task_weight(0)]
+				fn increment() -> DispatchResult {
+					let value = Value::<T>::get().unwrap_or_default();
+					if value >= 255 {
+						Err(Error::<T>::ValueOverflow.into())
+					} else {
+						let new_val = value.checked_add(1).ok_or(Error::<T>::ValueOverflow)?;
+						Value::<T>::put(new_val);
+						Pallet::<T>::deposit_event(Event::Incremented { new_val });
+						Ok(())
+					}
+				}
+
+				// Get the value and check if it can be decremented
+				#[pallet::task_index(1)]
+				#[pallet::task_condition(|| {
+					let value = Value::<T>::get().unwrap();
+					value > 0
+				})]
+				#[pallet::task_list(Vec::<Task<T>>::new())]
+				#[pallet::task_weight(0)]
+				fn decrement() -> DispatchResult {
+					let value = Value::<T>::get().unwrap_or_default();
+					if value == 0 {
+						Err(Error::<T>::ValueUnderflow.into())
+					} else {
+						let new_val = value.checked_sub(1).ok_or(Error::<T>::ValueUnderflow)?;
+						Value::<T>::put(new_val);
+						Pallet::<T>::deposit_event(Event::Decremented { new_val });
+						Ok(())
+					}
+				}
+			}
+		})
+		.unwrap();
+		assert_eq!(parsed.tasks.len(), 2);
+	});
+}
+
+#[test]
+fn test_parse_tasks_def_duplicate_index() {
+	simulate_manifest_dir("../../examples/basic", || {
+		assert_parse_error_matches!(
+			parse2::<TasksDef>(quote! {
+				#[pallet::tasks_experimental]
+				impl<T: Config<I>, I: 'static> Pallet<T, I> {
+					#[pallet::task_list(Something::iter())]
+					#[pallet::task_condition(|i| i % 2 == 0)]
+					#[pallet::task_index(0)]
+					#[pallet::task_weight(0)]
+					pub fn foo(i: u32) -> DispatchResult {
+						Ok(())
+					}
+
+					#[pallet::task_list(Numbers::<T, I>::iter_keys())]
+					#[pallet::task_condition(|i| Numbers::<T, I>::contains_key(i))]
+					#[pallet::task_index(0)]
+					#[pallet::task_weight(0)]
+					pub fn bar(i: u32) -> DispatchResult {
+						Ok(())
+					}
+				}
+			}),
+			"duplicate task index `0`"
+		);
+	});
+}
+
+#[test]
+fn test_parse_tasks_def_missing_task_list() {
+	simulate_manifest_dir("../../examples/basic", || {
+		assert_parse_error_matches!(
+			parse2::<TasksDef>(quote! {
+				#[pallet::tasks_experimental]
+				impl<T: Config<I>, I: 'static> Pallet<T, I> {
+					#[pallet::task_condition(|i| i % 2 == 0)]
+					#[pallet::task_index(0)]
+					pub fn foo(i: u32) -> DispatchResult {
+						Ok(())
+					}
+				}
+			}),
+			r"missing `#\[pallet::task_list\(\.\.\)\]`"
+		);
+	});
+}
+
+#[test]
+fn test_parse_tasks_def_missing_task_condition() {
+	simulate_manifest_dir("../../examples/basic", || {
+		assert_parse_error_matches!(
+			parse2::<TasksDef>(quote! {
+				#[pallet::tasks_experimental]
+				impl<T: Config<I>, I: 'static> Pallet<T, I> {
+					#[pallet::task_list(Something::iter())]
+					#[pallet::task_index(0)]
+					pub fn foo(i: u32) -> DispatchResult {
+						Ok(())
+					}
+				}
+			}),
+			r"missing `#\[pallet::task_condition\(\.\.\)\]`"
+		);
+	});
+}
+
+#[test]
+fn test_parse_tasks_def_missing_task_index() {
+	simulate_manifest_dir("../../examples/basic", || {
+		assert_parse_error_matches!(
+			parse2::<TasksDef>(quote! {
+				#[pallet::tasks_experimental]
+				impl<T: Config<I>, I: 'static> Pallet<T, I> {
+					#[pallet::task_condition(|i| i % 2 == 0)]
+					#[pallet::task_list(Something::iter())]
+					pub fn foo(i: u32) -> DispatchResult {
+						Ok(())
+					}
+				}
+			}),
+			r"missing `#\[pallet::task_index\(\.\.\)\]`"
+		);
+	});
+}
+
+#[test]
+fn test_parse_tasks_def_missing_task_weight() {
+	simulate_manifest_dir("../../examples/basic", || {
+		assert_parse_error_matches!(
+			parse2::<TasksDef>(quote! {
+				#[pallet::tasks_experimental]
+				impl<T: Config<I>, I: 'static> Pallet<T, I> {
+					#[pallet::task_condition(|i| i % 2 == 0)]
+					#[pallet::task_list(Something::iter())]
+					#[pallet::task_index(0)]
+					pub fn foo(i: u32) -> DispatchResult {
+						Ok(())
+					}
+				}
+			}),
+			r"missing `#\[pallet::task_weight\(\.\.\)\]`"
+		);
+	});
+}
+
+#[test]
+fn test_parse_tasks_def_unexpected_extra_task_list_attr() {
+	simulate_manifest_dir("../../examples/basic", || {
+		assert_parse_error_matches!(
+			parse2::<TasksDef>(quote! {
+				#[pallet::tasks_experimental]
+				impl<T: Config<I>, I: 'static> Pallet<T, I> {
+					#[pallet::task_condition(|i| i % 2 == 0)]
+					#[pallet::task_index(0)]
+					#[pallet::task_weight(0)]
+					#[pallet::task_list(Something::iter())]
+					#[pallet::task_list(SomethingElse::iter())]
+					pub fn foo(i: u32) -> DispatchResult {
+						Ok(())
+					}
+				}
+			}),
+			r"unexpected extra `#\[pallet::task_list\(\.\.\)\]`"
+		);
+	});
+}
+
+#[test]
+fn test_parse_tasks_def_unexpected_extra_task_condition_attr() {
+	simulate_manifest_dir("../../examples/basic", || {
+		assert_parse_error_matches!(
+			parse2::<TasksDef>(quote! {
+				#[pallet::tasks_experimental]
+				impl<T: Config<I>, I: 'static> Pallet<T, I> {
+					#[pallet::task_condition(|i| i % 2 == 0)]
+					#[pallet::task_condition(|i| i % 4 == 0)]
+					#[pallet::task_index(0)]
+					#[pallet::task_list(Something::iter())]
+					#[pallet::task_weight(0)]
+					pub fn foo(i: u32) -> DispatchResult {
+						Ok(())
+					}
+				}
+			}),
+			r"unexpected extra `#\[pallet::task_condition\(\.\.\)\]`"
+		);
+	});
+}
+
+#[test]
+fn test_parse_tasks_def_unexpected_extra_task_index_attr() {
+	simulate_manifest_dir("../../examples/basic", || {
+		assert_parse_error_matches!(
+			parse2::<TasksDef>(quote! {
+				#[pallet::tasks_experimental]
+				impl<T: Config<I>, I: 'static> Pallet<T, I> {
+					#[pallet::task_condition(|i| i % 2 == 0)]
+					#[pallet::task_index(0)]
+					#[pallet::task_index(0)]
+					#[pallet::task_list(Something::iter())]
+					#[pallet::task_weight(0)]
+					pub fn foo(i: u32) -> DispatchResult {
+						Ok(())
+					}
+				}
+			}),
+			r"unexpected extra `#\[pallet::task_index\(\.\.\)\]`"
+		);
+	});
+}
+
+#[test]
+fn test_parse_tasks_def_extra_tasks_attribute() {
+	simulate_manifest_dir("../../examples/basic", || {
+		assert_parse_error_matches!(
+			parse2::<TasksDef>(quote! {
+				#[pallet::tasks_experimental]
+				#[pallet::tasks_experimental]
+				impl<T: Config<I>, I: 'static> Pallet<T, I> {}
+			}),
+			r"unexpected extra `#\[pallet::tasks_experimental\]` attribute"
+		);
+	});
+}
+
+#[test]
+fn test_parse_task_enum_def_basic() {
+	simulate_manifest_dir("../../examples/basic", || {
+		parse2::<TaskEnumDef>(quote! {
+			#[pallet::task_enum]
+			pub enum Task<T: Config> {
+				Increment,
+				Decrement,
+			}
+		})
+		.unwrap();
+	});
+}
+
+#[test]
+fn test_parse_task_enum_def_non_task_name() {
+	simulate_manifest_dir("../../examples/basic", || {
+		parse2::<TaskEnumDef>(quote! {
+			#[pallet::task_enum]
+			pub enum Something {
+				Foo
+			}
+		})
+		.unwrap();
+	});
+}
+
+#[test]
+fn test_parse_task_enum_def_missing_attr_allowed() {
+	simulate_manifest_dir("../../examples/basic", || {
+		parse2::<TaskEnumDef>(quote! {
+			pub enum Task<T: Config> {
+				Increment,
+				Decrement,
+			}
+		})
+		.unwrap();
+	});
+}
+
+#[test]
+fn test_parse_task_enum_def_missing_attr_alternate_name_allowed() {
+	simulate_manifest_dir("../../examples/basic", || {
+		parse2::<TaskEnumDef>(quote! {
+			pub enum Foo {
+				Red,
+			}
+		})
+		.unwrap();
+	});
+}
+
+#[test]
+fn test_parse_task_enum_def_wrong_attr() {
+	simulate_manifest_dir("../../examples/basic", || {
+		assert_parse_error_matches!(
+			parse2::<TaskEnumDef>(quote! {
+				#[pallet::something]
+				pub enum Task<T: Config> {
+					Increment,
+					Decrement,
+				}
+			}),
+			"expected `task_enum`"
+		);
+	});
+}
+
+#[test]
+fn test_parse_task_enum_def_wrong_item() {
+	simulate_manifest_dir("../../examples/basic", || {
+		assert_parse_error_matches!(
+			parse2::<TaskEnumDef>(quote! {
+				#[pallet::task_enum]
+				pub struct Something;
+			}),
+			"expected `enum`"
+		);
+	});
+}
diff --git a/substrate/frame/support/procedural/src/pallet/parse/tests/mod.rs b/substrate/frame/support/procedural/src/pallet/parse/tests/mod.rs
new file mode 100644
index 00000000000..a3661f3076d
--- /dev/null
+++ b/substrate/frame/support/procedural/src/pallet/parse/tests/mod.rs
@@ -0,0 +1,264 @@
+// 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 std::{panic, sync::Mutex};
+use syn::parse_quote;
+
+#[doc(hidden)]
+pub mod __private {
+	pub use regex;
+}
+
+/// Allows you to assert that the input expression resolves to an error whose string
+/// representation matches the specified regex literal.
+///
+/// ## Example:
+///
+/// ```
+/// use super::tasks::*;
+///
+/// assert_parse_error_matches!(
+/// 	parse2::<TaskEnumDef>(quote! {
+/// 		#[pallet::task_enum]
+/// 		pub struct Something;
+/// 	}),
+/// 	"expected `enum`"
+/// );
+/// ```
+///
+/// More complex regular expressions are also possible (anything that could pass as a regex for
+/// use with the [`regex`] crate.):
+///
+/// ```ignore
+/// assert_parse_error_matches!(
+/// 	parse2::<TasksDef>(quote! {
+/// 		#[pallet::tasks_experimental]
+/// 		impl<T: Config<I>, I: 'static> Pallet<T, I> {
+/// 			#[pallet::task_condition(|i| i % 2 == 0)]
+/// 			#[pallet::task_index(0)]
+/// 			pub fn foo(i: u32) -> DispatchResult {
+/// 				Ok(())
+/// 			}
+/// 		}
+/// 	}),
+/// 	r"missing `#\[pallet::task_list\(\.\.\)\]`"
+/// );
+/// ```
+///
+/// Although this is primarily intended to be used with parsing errors, this macro is general
+/// enough that it will work with any error with a reasonable [`core::fmt::Display`] impl.
+#[macro_export]
+macro_rules! assert_parse_error_matches {
+	($expr:expr, $reg:literal) => {
+		match $expr {
+			Ok(_) => panic!("Expected an `Error(..)`, but got Ok(..)"),
+			Err(e) => {
+				let error_message = e.to_string();
+				let re = $crate::pallet::parse::tests::__private::regex::Regex::new($reg)
+					.expect("Invalid regex pattern");
+				assert!(
+					re.is_match(&error_message),
+					"Error message \"{}\" does not match the pattern \"{}\"",
+					error_message,
+					$reg
+				);
+			},
+		}
+	};
+}
+
+/// Allows you to assert that an entire pallet parses successfully. A custom syntax is used for
+/// specifying arguments so please pay attention to the docs below.
+///
+/// The general syntax is:
+///
+/// ```ignore
+/// assert_pallet_parses! {
+/// 	#[manifest_dir("../../examples/basic")]
+/// 	#[frame_support::pallet]
+/// 	pub mod pallet {
+/// 		#[pallet::config]
+/// 		pub trait Config: frame_system::Config {}
+///
+/// 		#[pallet::pallet]
+/// 		pub struct Pallet<T>(_);
+/// 	}
+/// };
+/// ```
+///
+/// The `#[manifest_dir(..)]` attribute _must_ be specified as the _first_ attribute on the
+/// pallet module, and should reference the relative (to your current directory) path of a
+/// directory containing containing the `Cargo.toml` of a valid pallet. Typically you will only
+/// ever need to use the `examples/basic` pallet, but sometimes it might be advantageous to
+/// specify a different one that has additional dependencies.
+///
+/// The reason this must be specified is that our underlying parsing of pallets depends on
+/// reaching out into the file system to look for particular `Cargo.toml` dependencies via the
+/// [`generate_access_from_frame_or_crate`] method, so to simulate this properly in a proc
+/// macro crate, we need to temporarily convince this function that we are running from the
+/// directory of a valid pallet.
+#[macro_export]
+macro_rules! assert_pallet_parses {
+	(
+		#[manifest_dir($manifest_dir:literal)]
+		$($tokens:tt)*
+	) => {
+		{
+			let mut pallet: Option<$crate::pallet::parse::Def> = None;
+			$crate::pallet::parse::tests::simulate_manifest_dir($manifest_dir, core::panic::AssertUnwindSafe(|| {
+				pallet = Some($crate::pallet::parse::Def::try_from(syn::parse_quote! {
+					$($tokens)*
+				}, false).unwrap());
+			}));
+			pallet.unwrap()
+		}
+	}
+}
+
+/// Similar to [`assert_pallet_parses`], except this instead expects the pallet not to parse,
+/// and allows you to specify a regex matching the expected parse error.
+///
+/// This is identical syntactically to [`assert_pallet_parses`] in every way except there is a
+/// second attribute that must be specified immediately after `#[manifest_dir(..)]` which is
+/// `#[error_regex(..)]` which should contain a string/regex literal designed to match what you
+/// consider to be the correct parsing error we should see when we try to parse this particular
+/// pallet.
+///
+/// ## Example:
+///
+/// ```
+/// assert_pallet_parse_error! {
+/// 	#[manifest_dir("../../examples/basic")]
+/// 	#[error_regex("Missing `\\#\\[pallet::pallet\\]`")]
+/// 	#[frame_support::pallet]
+/// 	pub mod pallet {
+/// 		#[pallet::config]
+/// 		pub trait Config: frame_system::Config {}
+/// 	}
+/// }
+/// ```
+#[macro_export]
+macro_rules! assert_pallet_parse_error {
+	(
+		#[manifest_dir($manifest_dir:literal)]
+		#[error_regex($reg:literal)]
+		$($tokens:tt)*
+	) => {
+		$crate::pallet::parse::tests::simulate_manifest_dir($manifest_dir, || {
+			$crate::assert_parse_error_matches!(
+				$crate::pallet::parse::Def::try_from(
+					parse_quote! {
+						$($tokens)*
+					},
+					false
+				),
+				$reg
+			);
+		});
+	}
+}
+
+/// Safely runs the specified `closure` while simulating an alternative `CARGO_MANIFEST_DIR`,
+/// restoring `CARGO_MANIFEST_DIR` to its original value upon completion regardless of whether
+/// the closure panics.
+///
+/// This is useful in tests of `Def::try_from` and other pallet-related methods that internally
+/// make use of [`generate_access_from_frame_or_crate`], which is sensitive to entries in the
+/// "current" `Cargo.toml` files.
+///
+/// This function uses a [`Mutex`] to avoid a race condition created when multiple tests try to
+/// modify and then restore the `CARGO_MANIFEST_DIR` ENV var in an overlapping way.
+pub fn simulate_manifest_dir<P: AsRef<std::path::Path>, F: FnOnce() + std::panic::UnwindSafe>(
+	path: P,
+	closure: F,
+) {
+	use std::{env::*, path::*};
+
+	/// Ensures that only one thread can modify/restore the `CARGO_MANIFEST_DIR` ENV var at a time,
+	/// avoiding a race condition because `cargo test` runs tests in parallel.
+	///
+	/// Although this forces all tests that use [`simulate_manifest_dir`] to run sequentially with
+	/// respect to each other, this is still several orders of magnitude faster than using UI
+	/// tests, even if they are run in parallel.
+	static MANIFEST_DIR_LOCK: Mutex<()> = Mutex::new(());
+
+	// avoid race condition when swapping out `CARGO_MANIFEST_DIR`
+	let guard = MANIFEST_DIR_LOCK.lock().unwrap();
+
+	// obtain the current/original `CARGO_MANIFEST_DIR`
+	let orig = PathBuf::from(
+		var("CARGO_MANIFEST_DIR").expect("failed to read ENV var `CARGO_MANIFEST_DIR`"),
+	);
+
+	// set `CARGO_MANIFEST_DIR` to the provided path, relative to current working dir
+	set_var("CARGO_MANIFEST_DIR", orig.join(path.as_ref()));
+
+	// safely run closure catching any panics
+	let result = panic::catch_unwind(closure);
+
+	// restore original `CARGO_MANIFEST_DIR` before unwinding
+	set_var("CARGO_MANIFEST_DIR", &orig);
+
+	// unlock the mutex so we don't poison it if there is a panic
+	drop(guard);
+
+	// unwind any panics originally encountered when running closure
+	result.unwrap();
+}
+
+mod tasks;
+
+#[test]
+fn test_parse_minimal_pallet() {
+	assert_pallet_parses! {
+		#[manifest_dir("../../examples/basic")]
+		#[frame_support::pallet]
+		pub mod pallet {
+			#[pallet::config]
+			pub trait Config: frame_system::Config {}
+
+			#[pallet::pallet]
+			pub struct Pallet<T>(_);
+		}
+	};
+}
+
+#[test]
+fn test_parse_pallet_missing_pallet() {
+	assert_pallet_parse_error! {
+		#[manifest_dir("../../examples/basic")]
+		#[error_regex("Missing `\\#\\[pallet::pallet\\]`")]
+		#[frame_support::pallet]
+		pub mod pallet {
+			#[pallet::config]
+			pub trait Config: frame_system::Config {}
+		}
+	}
+}
+
+#[test]
+fn test_parse_pallet_missing_config() {
+	assert_pallet_parse_error! {
+		#[manifest_dir("../../examples/basic")]
+		#[error_regex("Missing `\\#\\[pallet::config\\]`")]
+		#[frame_support::pallet]
+		pub mod pallet {
+			#[pallet::pallet]
+			pub struct Pallet<T>(_);
+		}
+	}
+}
diff --git a/substrate/frame/support/procedural/src/pallet/parse/tests/tasks.rs b/substrate/frame/support/procedural/src/pallet/parse/tests/tasks.rs
new file mode 100644
index 00000000000..9f143628404
--- /dev/null
+++ b/substrate/frame/support/procedural/src/pallet/parse/tests/tasks.rs
@@ -0,0 +1,240 @@
+// 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 syn::parse_quote;
+
+#[test]
+fn test_parse_pallet_with_task_enum_missing_impl() {
+	assert_pallet_parse_error! {
+		#[manifest_dir("../../examples/basic")]
+		#[error_regex("Missing `\\#\\[pallet::tasks_experimental\\]` impl")]
+		#[frame_support::pallet]
+		pub mod pallet {
+			#[pallet::task_enum]
+			pub enum Task<T: Config> {
+				Something,
+			}
+
+			#[pallet::config]
+			pub trait Config: frame_system::Config {}
+
+			#[pallet::pallet]
+			pub struct Pallet<T>(_);
+		}
+	}
+}
+
+#[test]
+fn test_parse_pallet_with_task_enum_wrong_attribute() {
+	assert_pallet_parse_error! {
+		#[manifest_dir("../../examples/basic")]
+		#[error_regex("expected one of")]
+		#[frame_support::pallet]
+		pub mod pallet {
+			#[pallet::wrong_attribute]
+			pub enum Task<T: Config> {
+				Something,
+			}
+
+			#[pallet::task_list]
+			impl<T: Config> frame_support::traits::Task for Task<T>
+			where
+				T: TypeInfo,
+			{}
+
+			#[pallet::config]
+			pub trait Config: frame_system::Config {}
+
+			#[pallet::pallet]
+			pub struct Pallet<T>(_);
+		}
+	}
+}
+
+#[test]
+fn test_parse_pallet_missing_task_enum() {
+	assert_pallet_parses! {
+		#[manifest_dir("../../examples/basic")]
+		#[frame_support::pallet]
+		pub mod pallet {
+			#[pallet::tasks_experimental]
+			#[cfg(test)] // aha, this means it's being eaten
+			impl<T: Config> frame_support::traits::Task for Task<T>
+			where
+				T: TypeInfo,
+			{}
+
+			#[pallet::config]
+			pub trait Config: frame_system::Config {}
+
+			#[pallet::pallet]
+			pub struct Pallet<T>(_);
+		}
+	};
+}
+
+#[test]
+fn test_parse_pallet_task_list_in_wrong_place() {
+	assert_pallet_parse_error! {
+		#[manifest_dir("../../examples/basic")]
+		#[error_regex("can only be used on items within an `impl` statement.")]
+		#[frame_support::pallet]
+		pub mod pallet {
+			pub enum MyCustomTaskEnum<T: Config> {
+				Something,
+			}
+
+			#[pallet::task_list]
+			pub fn something() {
+				println!("hey");
+			}
+
+			#[pallet::config]
+			pub trait Config: frame_system::Config {}
+
+			#[pallet::pallet]
+			pub struct Pallet<T>(_);
+		}
+	}
+}
+
+#[test]
+fn test_parse_pallet_manual_tasks_impl_without_manual_tasks_enum() {
+	assert_pallet_parse_error! {
+		#[manifest_dir("../../examples/basic")]
+		#[error_regex(".*attribute must be attached to your.*")]
+		#[frame_support::pallet]
+		pub mod pallet {
+
+			impl<T: Config> frame_support::traits::Task for Task<T>
+			where
+				T: TypeInfo,
+			{
+				type Enumeration = sp_std::vec::IntoIter<Task<T>>;
+
+				fn iter() -> Self::Enumeration {
+					sp_std::vec![Task::increment, Task::decrement].into_iter()
+				}
+			}
+
+			#[pallet::config]
+			pub trait Config: frame_system::Config {}
+
+			#[pallet::pallet]
+			pub struct Pallet<T>(_);
+		}
+	}
+}
+
+#[test]
+fn test_parse_pallet_manual_task_enum_non_manual_impl() {
+	assert_pallet_parses! {
+		#[manifest_dir("../../examples/basic")]
+		#[frame_support::pallet]
+		pub mod pallet {
+			pub enum MyCustomTaskEnum<T: Config> {
+				Something,
+			}
+
+			#[pallet::tasks_experimental]
+			impl<T: Config> frame_support::traits::Task for MyCustomTaskEnum<T>
+			where
+				T: TypeInfo,
+			{}
+
+			#[pallet::config]
+			pub trait Config: frame_system::Config {}
+
+			#[pallet::pallet]
+			pub struct Pallet<T>(_);
+		}
+	};
+}
+
+#[test]
+fn test_parse_pallet_non_manual_task_enum_manual_impl() {
+	assert_pallet_parses! {
+		#[manifest_dir("../../examples/basic")]
+		#[frame_support::pallet]
+		pub mod pallet {
+			#[pallet::task_enum]
+			pub enum MyCustomTaskEnum<T: Config> {
+				Something,
+			}
+
+			impl<T: Config> frame_support::traits::Task for MyCustomTaskEnum<T>
+			where
+				T: TypeInfo,
+			{}
+
+			#[pallet::config]
+			pub trait Config: frame_system::Config {}
+
+			#[pallet::pallet]
+			pub struct Pallet<T>(_);
+		}
+	};
+}
+
+#[test]
+fn test_parse_pallet_manual_task_enum_manual_impl() {
+	assert_pallet_parses! {
+		#[manifest_dir("../../examples/basic")]
+		#[frame_support::pallet]
+		pub mod pallet {
+			pub enum MyCustomTaskEnum<T: Config> {
+				Something,
+			}
+
+			impl<T: Config> frame_support::traits::Task for MyCustomTaskEnum<T>
+			where
+				T: TypeInfo,
+			{}
+
+			#[pallet::config]
+			pub trait Config: frame_system::Config {}
+
+			#[pallet::pallet]
+			pub struct Pallet<T>(_);
+		}
+	};
+}
+
+#[test]
+fn test_parse_pallet_manual_task_enum_mismatch_ident() {
+	assert_pallet_parses! {
+		#[manifest_dir("../../examples/basic")]
+		#[frame_support::pallet]
+		pub mod pallet {
+			pub enum WrongIdent<T: Config> {
+				Something,
+			}
+
+			#[pallet::tasks_experimental]
+			impl<T: Config> frame_support::traits::Task for MyCustomTaskEnum<T>
+			where
+				T: TypeInfo,
+			{}
+
+			#[pallet::config]
+			pub trait Config: frame_system::Config {}
+
+			#[pallet::pallet]
+			pub struct Pallet<T>(_);
+		}
+	};
+}
diff --git a/substrate/frame/support/src/dispatch.rs b/substrate/frame/support/src/dispatch.rs
index c81dba12766..449b6f23165 100644
--- a/substrate/frame/support/src/dispatch.rs
+++ b/substrate/frame/support/src/dispatch.rs
@@ -695,6 +695,7 @@ mod weight_tests {
 			type BaseCallFilter: crate::traits::Contains<Self::RuntimeCall>;
 			type RuntimeOrigin;
 			type RuntimeCall;
+			type RuntimeTask;
 			type PalletInfo: crate::traits::PalletInfo;
 			type DbWeight: Get<crate::weights::RuntimeDbWeight>;
 		}
@@ -791,6 +792,7 @@ mod weight_tests {
 		type BaseCallFilter = crate::traits::Everything;
 		type RuntimeOrigin = RuntimeOrigin;
 		type RuntimeCall = RuntimeCall;
+		type RuntimeTask = RuntimeTask;
 		type DbWeight = DbWeight;
 		type PalletInfo = PalletInfo;
 	}
diff --git a/substrate/frame/support/src/lib.rs b/substrate/frame/support/src/lib.rs
index fd348f62b4f..af1f99be103 100644
--- a/substrate/frame/support/src/lib.rs
+++ b/substrate/frame/support/src/lib.rs
@@ -849,7 +849,7 @@ pub mod pallet_prelude {
 		},
 		traits::{
 			BuildGenesisConfig, ConstU32, EnsureOrigin, Get, GetDefault, GetStorageVersion, Hooks,
-			IsType, PalletInfoAccess, StorageInfoTrait, StorageVersion, TypedGet,
+			IsType, PalletInfoAccess, StorageInfoTrait, StorageVersion, Task, TypedGet,
 		},
 		Blake2_128, Blake2_128Concat, Blake2_256, CloneNoBound, DebugNoBound, EqNoBound, Identity,
 		PartialEqNoBound, RuntimeDebugNoBound, Twox128, Twox256, Twox64Concat,
@@ -2674,6 +2674,61 @@ pub mod pallet_macros {
 	/// }
 	/// ```
 	pub use frame_support_procedural::storage;
+	/// This attribute is attached to a function inside an `impl` block annoated with
+	/// [`pallet::tasks_experimental`](`tasks_experimental`) to define the conditions for a
+	/// given work item to be valid.
+	///
+	/// It takes a closure as input, which is then used to define the condition. The closure
+	/// should have the same signature as the function it is attached to, except that it should
+	/// return a `bool` instead.
+	pub use frame_support_procedural::task_condition;
+	/// This attribute is attached to a function inside an `impl` block annoated with
+	/// [`pallet::tasks_experimental`](`tasks_experimental`) to define the index of a given
+	/// work item.
+	///
+	/// It takes an integer literal as input, which is then used to define the index. This
+	/// index should be unique for each function in the `impl` block.
+	pub use frame_support_procedural::task_index;
+	/// This attribute is attached to a function inside an `impl` block annoated with
+	/// [`pallet::tasks_experimental`](`tasks_experimental`) to define an iterator over the
+	/// available work items for a task.
+	///
+	/// It takes an iterator as input that yields a tuple with same types as the function
+	/// arguments.
+	pub use frame_support_procedural::task_list;
+	/// This attribute is attached to a function inside an `impl` block annoated with
+	/// [`pallet::tasks_experimental`](`tasks_experimental`) define the weight of a given work
+	/// item.
+	///
+	/// It takes a closure as input, which should return a `Weight` value.
+	pub use frame_support_procedural::task_weight;
+	/// Allows you to define some service work that can be recognized by a script or an
+	/// off-chain worker. Such a script can then create and submit all such work items at any
+	/// given time.
+	///
+	/// These work items are defined as instances of the [`Task`](frame_support::traits::Task)
+	/// trait. [`pallet:tasks_experimental`](`tasks_experimental`) when attached to an `impl`
+	/// block inside a pallet, will generate an enum `Task<T>` whose variants are mapped to
+	/// functions inside this `impl` block.
+	///
+	/// Each such function must have the following set of attributes:
+	///
+	/// * [`pallet::task_list`](`task_list`)
+	/// * [`pallet::task_condition`](`task_condition`)
+	/// * [`pallet::task_weight`](`task_weight`)
+	/// * [`pallet::task_index`](`task_index`)
+	///
+	/// All of such Tasks are then aggregated into a `RuntimeTask` by
+	/// [`construct_runtime`](frame_support::construct_runtime).
+	///
+	/// Finally, the `RuntimeTask` can then used by a script or off-chain worker to create and
+	/// submit such tasks via an extrinsic defined in `frame_system` called `do_task`.
+	///
+	/// ## Example
+	#[doc = docify::embed!("src/tests/tasks.rs", tasks_example)]
+	/// Now, this can be executed as follows:
+	#[doc = docify::embed!("src/tests/tasks.rs", tasks_work)]
+	pub use frame_support_procedural::tasks_experimental;
 }
 
 #[deprecated(note = "Will be removed after July 2023; Use `sp_runtime::traits` directly instead.")]
diff --git a/substrate/frame/support/src/storage/generator/mod.rs b/substrate/frame/support/src/storage/generator/mod.rs
index 2b2abdc2e83..dd6d622852d 100644
--- a/substrate/frame/support/src/storage/generator/mod.rs
+++ b/substrate/frame/support/src/storage/generator/mod.rs
@@ -63,6 +63,7 @@ mod tests {
 			type BaseCallFilter: crate::traits::Contains<Self::RuntimeCall>;
 			type RuntimeOrigin;
 			type RuntimeCall;
+			type RuntimeTask;
 			type PalletInfo: crate::traits::PalletInfo;
 			type DbWeight: Get<crate::weights::RuntimeDbWeight>;
 		}
@@ -129,6 +130,7 @@ mod tests {
 		type BaseCallFilter = crate::traits::Everything;
 		type RuntimeOrigin = RuntimeOrigin;
 		type RuntimeCall = RuntimeCall;
+		type RuntimeTask = RuntimeTask;
 		type PalletInfo = PalletInfo;
 		type DbWeight = ();
 	}
diff --git a/substrate/frame/support/src/tests/mod.rs b/substrate/frame/support/src/tests/mod.rs
index 3690159c599..c6a0b6cde77 100644
--- a/substrate/frame/support/src/tests/mod.rs
+++ b/substrate/frame/support/src/tests/mod.rs
@@ -16,6 +16,7 @@
 // limitations under the License.
 
 use super::*;
+use frame_support_procedural::import_section;
 use sp_io::{MultiRemovalResults, TestExternalities};
 use sp_metadata_ir::{
 	PalletStorageMetadataIR, StorageEntryMetadataIR, StorageEntryModifierIR, StorageEntryTypeIR,
@@ -27,13 +28,15 @@ pub use self::frame_system::{pallet_prelude::*, Config, Pallet};
 
 mod inject_runtime_type;
 mod storage_alias;
+mod tasks;
 
+#[import_section(tasks::tasks_example)]
 #[pallet]
 pub mod frame_system {
 	#[allow(unused)]
 	use super::{frame_system, frame_system::pallet_prelude::*};
 	pub use crate::dispatch::RawOrigin;
-	use crate::pallet_prelude::*;
+	use crate::{pallet_prelude::*, traits::tasks::Task as TaskTrait};
 
 	pub mod config_preludes {
 		use super::{inject_runtime_type, DefaultConfig};
@@ -49,6 +52,8 @@ pub mod frame_system {
 			type RuntimeCall = ();
 			#[inject_runtime_type]
 			type PalletInfo = ();
+			#[inject_runtime_type]
+			type RuntimeTask = ();
 			type DbWeight = ();
 		}
 	}
@@ -69,6 +74,8 @@ pub mod frame_system {
 		#[pallet::no_default_bounds]
 		type RuntimeCall;
 		#[pallet::no_default_bounds]
+		type RuntimeTask: crate::traits::tasks::Task;
+		#[pallet::no_default_bounds]
 		type PalletInfo: crate::traits::PalletInfo;
 		type DbWeight: Get<crate::weights::RuntimeDbWeight>;
 	}
@@ -77,13 +84,33 @@ pub mod frame_system {
 	pub enum Error<T> {
 		/// Required by construct_runtime
 		CallFiltered,
+		/// Used in tasks example.
+		NotFound,
+		/// The specified [`Task`] is not valid.
+		InvalidTask,
+		/// The specified [`Task`] failed during execution.
+		FailedTask,
 	}
 
 	#[pallet::origin]
 	pub type Origin<T> = RawOrigin<<T as Config>::AccountId>;
 
 	#[pallet::call]
-	impl<T: Config> Pallet<T> {}
+	impl<T: Config> Pallet<T> {
+		#[pallet::call_index(0)]
+		#[pallet::weight(task.weight())]
+		pub fn do_task(_origin: OriginFor<T>, task: T::RuntimeTask) -> DispatchResultWithPostInfo {
+			if !task.is_valid() {
+				return Err(Error::<T>::InvalidTask.into())
+			}
+
+			if let Err(_err) = task.run() {
+				return Err(Error::<T>::FailedTask.into())
+			}
+
+			Ok(().into())
+		}
+	}
 
 	#[pallet::storage]
 	pub type Data<T> = StorageMap<_, Twox64Concat, u32, u64, ValueQuery>;
@@ -169,6 +196,14 @@ pub mod frame_system {
 		}
 	}
 
+	/// Some running total.
+	#[pallet::storage]
+	pub type Total<T: Config> = StorageValue<_, (u32, u32), ValueQuery>;
+
+	/// Numbers to be added into the total.
+	#[pallet::storage]
+	pub type Numbers<T: Config> = StorageMap<_, Twox64Concat, u32, u32, OptionQuery>;
+
 	pub mod pallet_prelude {
 		pub type OriginFor<T> = <T as super::Config>::RuntimeOrigin;
 
@@ -622,6 +657,24 @@ fn expected_metadata() -> PalletStorageMetadataIR {
 				default: vec![0],
 				docs: vec![],
 			},
+			StorageEntryMetadataIR {
+				name: "Total",
+				modifier: StorageEntryModifierIR::Default,
+				ty: StorageEntryTypeIR::Plain(scale_info::meta_type::<(u32, u32)>()),
+				default: vec![0, 0, 0, 0, 0, 0, 0, 0],
+				docs: vec![" Some running total."],
+			},
+			StorageEntryMetadataIR {
+				name: "Numbers",
+				modifier: StorageEntryModifierIR::Optional,
+				ty: StorageEntryTypeIR::Map {
+					hashers: vec![StorageHasherIR::Twox64Concat],
+					key: scale_info::meta_type::<u32>(),
+					value: scale_info::meta_type::<u32>(),
+				},
+				default: vec![0],
+				docs: vec![" Numbers to be added into the total."],
+			},
 		],
 	}
 }
diff --git a/substrate/frame/support/src/tests/tasks.rs b/substrate/frame/support/src/tests/tasks.rs
new file mode 100644
index 00000000000..2774c130075
--- /dev/null
+++ b/substrate/frame/support/src/tests/tasks.rs
@@ -0,0 +1,62 @@
+// 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::{
+	assert_ok,
+	tests::{
+		frame_system::{Numbers, Total},
+		new_test_ext, Runtime, RuntimeOrigin, RuntimeTask, System,
+	},
+};
+use frame_support_procedural::pallet_section;
+
+#[pallet_section]
+mod tasks_example {
+	#[docify::export(tasks_example)]
+	#[pallet::tasks_experimental]
+	impl<T: Config> Pallet<T> {
+		/// Add a pair of numbers into the totals and remove them.
+		#[pallet::task_list(Numbers::<T>::iter_keys())]
+		#[pallet::task_condition(|i| Numbers::<T>::contains_key(i))]
+		#[pallet::task_weight(0.into())]
+		#[pallet::task_index(0)]
+		pub fn add_number_into_total(i: u32) -> DispatchResult {
+			let v = Numbers::<T>::take(i).ok_or(Error::<T>::NotFound)?;
+			Total::<T>::mutate(|(total_keys, total_values)| {
+				*total_keys += i;
+				*total_values += v;
+			});
+			Ok(())
+		}
+	}
+}
+
+#[docify::export]
+#[test]
+fn tasks_work() {
+	new_test_ext().execute_with(|| {
+		Numbers::<Runtime>::insert(0, 1);
+
+		let task = RuntimeTask::System(super::frame_system::Task::<Runtime>::AddNumberIntoTotal {
+			i: 0u32,
+		});
+
+		assert_ok!(System::do_task(RuntimeOrigin::signed(1), task.clone(),));
+		assert_eq!(Numbers::<Runtime>::get(0), None);
+		assert_eq!(Total::<Runtime>::get(), (0, 1));
+	});
+}
diff --git a/substrate/frame/support/src/traits.rs b/substrate/frame/support/src/traits.rs
index 6362e750d2a..9afd9c16130 100644
--- a/substrate/frame/support/src/traits.rs
+++ b/substrate/frame/support/src/traits.rs
@@ -123,6 +123,9 @@ pub use safe_mode::{SafeMode, SafeModeError, SafeModeNotify};
 mod tx_pause;
 pub use tx_pause::{TransactionPause, TransactionPauseError};
 
+pub mod tasks;
+pub use tasks::Task;
+
 #[cfg(feature = "try-runtime")]
 mod try_runtime;
 #[cfg(feature = "try-runtime")]
diff --git a/substrate/frame/support/src/traits/tasks.rs b/substrate/frame/support/src/traits/tasks.rs
new file mode 100644
index 00000000000..24f3430cf50
--- /dev/null
+++ b/substrate/frame/support/src/traits/tasks.rs
@@ -0,0 +1,87 @@
+// 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.
+
+//! Contains the [`Task`] trait, which defines a general-purpose way for defining and executing
+//! service work, and supporting types.
+
+use codec::FullCodec;
+use scale_info::TypeInfo;
+use sp_runtime::DispatchError;
+use sp_std::{fmt::Debug, iter::Iterator, vec, vec::IntoIter};
+use sp_weights::Weight;
+
+/// Contain's re-exports of all the supporting types for the [`Task`] trait. Used in the macro
+/// expansion of `RuntimeTask`.
+#[doc(hidden)]
+pub mod __private {
+	pub use codec::FullCodec;
+	pub use scale_info::TypeInfo;
+	pub use sp_runtime::DispatchError;
+	pub use sp_std::{fmt::Debug, iter::Iterator, vec, vec::IntoIter};
+	pub use sp_weights::Weight;
+}
+
+/// A general-purpose trait which defines a type of service work (i.e., work to performed by an
+/// off-chain worker) including methods for enumerating, validating, indexing, and running
+/// tasks of this type.
+pub trait Task: Sized + FullCodec + TypeInfo + Clone + Debug + PartialEq + Eq {
+	/// An [`Iterator`] over tasks of this type used as the return type for `enumerate`.
+	type Enumeration: Iterator;
+
+	/// Inspects the pallet's state and enumerates tasks of this type.
+	fn iter() -> Self::Enumeration;
+
+	/// Checks if a particular instance of this `Task` variant is a valid piece of work.
+	fn is_valid(&self) -> bool;
+
+	/// Performs the work for this particular `Task` variant.
+	fn run(&self) -> Result<(), DispatchError>;
+
+	/// Returns the weight of executing this `Task`.
+	fn weight(&self) -> Weight;
+
+	/// A unique value representing this `Task` within the current pallet. Analogous to
+	/// `call_index`, but for tasks.'
+	///
+	/// This value should be unique within the current pallet and can overlap with task indices
+	/// in other pallets.
+	fn task_index(&self) -> u32;
+}
+
+impl Task for () {
+	type Enumeration = IntoIter<Self>;
+
+	fn iter() -> Self::Enumeration {
+		vec![].into_iter()
+	}
+
+	fn is_valid(&self) -> bool {
+		true
+	}
+
+	fn run(&self) -> Result<(), DispatchError> {
+		Ok(())
+	}
+
+	fn weight(&self) -> Weight {
+		Weight::default()
+	}
+
+	fn task_index(&self) -> u32 {
+		0
+	}
+}
diff --git a/substrate/frame/support/test/compile_pass/src/lib.rs b/substrate/frame/support/test/compile_pass/src/lib.rs
index 6ea37fb27e7..b304dfcb282 100644
--- a/substrate/frame/support/test/compile_pass/src/lib.rs
+++ b/substrate/frame/support/test/compile_pass/src/lib.rs
@@ -22,7 +22,7 @@
 #![cfg_attr(not(feature = "std"), no_std)]
 
 use renamed_frame_support::{
-	construct_runtime, parameter_types,
+	construct_runtime, derive_impl, parameter_types,
 	traits::{ConstU16, ConstU32, ConstU64, Everything},
 };
 use sp_core::{sr25519, H256};
@@ -51,6 +51,7 @@ parameter_types! {
 	pub const Version: RuntimeVersion = VERSION;
 }
 
+#[derive_impl(renamed_frame_system::config_preludes::TestDefaultConfig as renamed_frame_system::DefaultConfig)]
 impl renamed_frame_system::Config for Runtime {
 	type BaseCallFilter = Everything;
 	type BlockWeights = ();
diff --git a/substrate/frame/support/test/src/lib.rs b/substrate/frame/support/test/src/lib.rs
index 6b38d42d33d..a8a72337503 100644
--- a/substrate/frame/support/test/src/lib.rs
+++ b/substrate/frame/support/test/src/lib.rs
@@ -50,6 +50,8 @@ pub mod pallet {
 			+ From<RawOrigin<Self::AccountId>>;
 		/// The runtime call type.
 		type RuntimeCall;
+		/// Contains an aggregation of all tasks in this runtime.
+		type RuntimeTask;
 		/// The runtime event type.
 		type RuntimeEvent: Parameter
 			+ Member
diff --git a/substrate/frame/support/test/tests/construct_runtime_ui/generics_in_invalid_module.stderr b/substrate/frame/support/test/tests/construct_runtime_ui/generics_in_invalid_module.stderr
index bf53f43b9ba..8458de97f6d 100644
--- a/substrate/frame/support/test/tests/construct_runtime_ui/generics_in_invalid_module.stderr
+++ b/substrate/frame/support/test/tests/construct_runtime_ui/generics_in_invalid_module.stderr
@@ -1,4 +1,4 @@
-error: `Call` is not allowed to have generics. Only the following pallets are allowed to have generics: `Event`, `Error`, `Origin`, `Config`.
+error: `Call` is not allowed to have generics. Only the following pallets are allowed to have generics: `Event`, `Error`, `Origin`, `Config`, `Task`.
   --> tests/construct_runtime_ui/generics_in_invalid_module.rs:24:36
    |
 24 |         Balance: balances::<Instance1>::{Call<T>, Origin<T>},
diff --git a/substrate/frame/support/test/tests/construct_runtime_ui/invalid_module_details_keyword.stderr b/substrate/frame/support/test/tests/construct_runtime_ui/invalid_module_details_keyword.stderr
index ad631de204e..feb61793151 100644
--- a/substrate/frame/support/test/tests/construct_runtime_ui/invalid_module_details_keyword.stderr
+++ b/substrate/frame/support/test/tests/construct_runtime_ui/invalid_module_details_keyword.stderr
@@ -1,4 +1,4 @@
-error: expected one of: `Pallet`, `Call`, `Storage`, `Event`, `Error`, `Config`, `Origin`, `Inherent`, `ValidateUnsigned`, `FreezeReason`, `HoldReason`, `LockId`, `SlashReason`
+error: expected one of: `Pallet`, `Call`, `Storage`, `Event`, `Error`, `Config`, `Origin`, `Inherent`, `ValidateUnsigned`, `FreezeReason`, `HoldReason`, `Task`, `LockId`, `SlashReason`
   --> tests/construct_runtime_ui/invalid_module_details_keyword.rs:23:20
    |
 23 |         system: System::{enum},
diff --git a/substrate/frame/support/test/tests/construct_runtime_ui/invalid_module_entry.stderr b/substrate/frame/support/test/tests/construct_runtime_ui/invalid_module_entry.stderr
index b5b89a5a270..97943dfc176 100644
--- a/substrate/frame/support/test/tests/construct_runtime_ui/invalid_module_entry.stderr
+++ b/substrate/frame/support/test/tests/construct_runtime_ui/invalid_module_entry.stderr
@@ -1,4 +1,4 @@
-error: expected one of: `Pallet`, `Call`, `Storage`, `Event`, `Error`, `Config`, `Origin`, `Inherent`, `ValidateUnsigned`, `FreezeReason`, `HoldReason`, `LockId`, `SlashReason`
+error: expected one of: `Pallet`, `Call`, `Storage`, `Event`, `Error`, `Config`, `Origin`, `Inherent`, `ValidateUnsigned`, `FreezeReason`, `HoldReason`, `Task`, `LockId`, `SlashReason`
   --> tests/construct_runtime_ui/invalid_module_entry.rs:24:23
    |
 24 |         Balance: balances::{Unexpected},
diff --git a/substrate/frame/support/test/tests/construct_runtime_ui/number_of_pallets_exceeds_tuple_size.stderr b/substrate/frame/support/test/tests/construct_runtime_ui/number_of_pallets_exceeds_tuple_size.stderr
index a8c1daf88da..6838b7f4c0a 100644
--- a/substrate/frame/support/test/tests/construct_runtime_ui/number_of_pallets_exceeds_tuple_size.stderr
+++ b/substrate/frame/support/test/tests/construct_runtime_ui/number_of_pallets_exceeds_tuple_size.stderr
@@ -76,3 +76,11 @@ help: consider importing one of these items
    |
 18 + use frame_support::traits::PalletInfo;
    |
+
+error[E0412]: cannot find type `RuntimeTask` in this scope
+  --> tests/construct_runtime_ui/number_of_pallets_exceeds_tuple_size.rs:39:1
+   |
+39 | #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)]
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: you might have meant to use the associated type: `Self::RuntimeTask`
+   |
+   = note: this error originates in the macro `frame_system::config_preludes::TestDefaultConfig` which comes from the expansion of the macro `frame_support::macro_magic::forward_tokens_verbatim` (in Nightly builds, run with -Z macro-backtrace for more info)
diff --git a/substrate/frame/support/test/tests/derive_impl_ui/inject_runtime_type_invalid.stderr b/substrate/frame/support/test/tests/derive_impl_ui/inject_runtime_type_invalid.stderr
index 501aad0419f..cda20288984 100644
--- a/substrate/frame/support/test/tests/derive_impl_ui/inject_runtime_type_invalid.stderr
+++ b/substrate/frame/support/test/tests/derive_impl_ui/inject_runtime_type_invalid.stderr
@@ -1,4 +1,4 @@
-error: `#[inject_runtime_type]` can only be attached to `RuntimeCall`, `RuntimeEvent`, `RuntimeOrigin` or `PalletInfo`
+error: `#[inject_runtime_type]` can only be attached to `RuntimeCall`, `RuntimeEvent`, `RuntimeTask`, `RuntimeOrigin` or `PalletInfo`
   --> tests/derive_impl_ui/inject_runtime_type_invalid.rs:32:5
    |
 32 |     type RuntimeInfo = ();
diff --git a/substrate/frame/support/test/tests/pallet_outer_enums_explicit.rs b/substrate/frame/support/test/tests/pallet_outer_enums_explicit.rs
index a8250f8b153..6246ad93d67 100644
--- a/substrate/frame/support/test/tests/pallet_outer_enums_explicit.rs
+++ b/substrate/frame/support/test/tests/pallet_outer_enums_explicit.rs
@@ -90,6 +90,8 @@ fn module_error_outer_enum_expand_explicit() {
 			frame_system::Error::NonDefaultComposite => (),
 			frame_system::Error::NonZeroRefCount => (),
 			frame_system::Error::CallFiltered => (),
+			frame_system::Error::InvalidTask => (),
+			frame_system::Error::FailedTask => (),
 			frame_system::Error::__Ignore(_, _) => (),
 		},
 
diff --git a/substrate/frame/support/test/tests/pallet_outer_enums_implicit.rs b/substrate/frame/support/test/tests/pallet_outer_enums_implicit.rs
index 191f095f5d7..26023dfa7b7 100644
--- a/substrate/frame/support/test/tests/pallet_outer_enums_implicit.rs
+++ b/substrate/frame/support/test/tests/pallet_outer_enums_implicit.rs
@@ -90,6 +90,8 @@ fn module_error_outer_enum_expand_implicit() {
 			frame_system::Error::NonDefaultComposite => (),
 			frame_system::Error::NonZeroRefCount => (),
 			frame_system::Error::CallFiltered => (),
+			frame_system::Error::InvalidTask => (),
+			frame_system::Error::FailedTask => (),
 			frame_system::Error::__Ignore(_, _) => (),
 		},
 
diff --git a/substrate/frame/support/test/tests/pallet_ui/composite_enum_unsupported_identifier.stderr b/substrate/frame/support/test/tests/pallet_ui/composite_enum_unsupported_identifier.stderr
index cdc8f623142..8de9c8990b0 100644
--- a/substrate/frame/support/test/tests/pallet_ui/composite_enum_unsupported_identifier.stderr
+++ b/substrate/frame/support/test/tests/pallet_ui/composite_enum_unsupported_identifier.stderr
@@ -1,4 +1,4 @@
-error: expected one of: `FreezeReason`, `HoldReason`, `LockId`, `SlashReason`
+error: expected one of: `FreezeReason`, `HoldReason`, `LockId`, `SlashReason`, `Task`
   --> tests/pallet_ui/composite_enum_unsupported_identifier.rs:27:11
    |
 27 |     pub enum HoldReasons {}
diff --git a/substrate/frame/support/test/tests/pallet_ui/pass/task_valid.rs b/substrate/frame/support/test/tests/pallet_ui/pass/task_valid.rs
new file mode 100644
index 00000000000..234e220f49d
--- /dev/null
+++ b/substrate/frame/support/test/tests/pallet_ui/pass/task_valid.rs
@@ -0,0 +1,43 @@
+// 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.
+
+#[frame_support::pallet(dev_mode)]
+mod pallet {
+	use frame_support::{ensure, pallet_prelude::DispatchResult};
+
+	#[pallet::config]
+	pub trait Config: frame_system::Config {}
+
+	#[pallet::pallet]
+	pub struct Pallet<T>(core::marker::PhantomData<T>);
+
+    #[pallet::tasks_experimental]
+	impl<T: Config> Pallet<T> {
+		#[pallet::task_index(0)]
+		#[pallet::task_condition(|i, j| i == 0u32 && j == 2u64)]
+		#[pallet::task_list(vec![(0u32, 2u64), (2u32, 4u64)].iter())]
+		#[pallet::task_weight(0.into())]
+		fn foo(i: u32, j: u64) -> DispatchResult {
+			ensure!(i == 0, "i must be 0");
+			ensure!(j == 2, "j must be 2");
+			Ok(())
+		}
+	}
+}
+
+fn main() {
+}
diff --git a/substrate/frame/support/test/tests/pallet_ui/task_can_only_be_attached_to_impl.rs b/substrate/frame/support/test/tests/pallet_ui/task_can_only_be_attached_to_impl.rs
new file mode 100644
index 00000000000..95f5655af19
--- /dev/null
+++ b/substrate/frame/support/test/tests/pallet_ui/task_can_only_be_attached_to_impl.rs
@@ -0,0 +1,34 @@
+// 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.
+
+#[frame_support::pallet(dev_mode)]
+mod pallet {
+	use frame_support::pallet_prelude::DispatchResult;
+	use frame_system::pallet_prelude::OriginFor;
+
+	#[pallet::config]
+	pub trait Config: frame_system::Config {}
+
+	#[pallet::pallet]
+	pub struct Pallet<T>(core::marker::PhantomData<T>);
+
+	#[pallet::tasks_experimental]
+    pub struct Task;
+}
+
+fn main() {
+}
diff --git a/substrate/frame/support/test/tests/pallet_ui/task_can_only_be_attached_to_impl.stderr b/substrate/frame/support/test/tests/pallet_ui/task_can_only_be_attached_to_impl.stderr
new file mode 100644
index 00000000000..eaa8e718840
--- /dev/null
+++ b/substrate/frame/support/test/tests/pallet_ui/task_can_only_be_attached_to_impl.stderr
@@ -0,0 +1,5 @@
+error: expected `impl`
+  --> tests/pallet_ui/task_can_only_be_attached_to_impl.rs:30:5
+   |
+30 |     pub struct Task;
+   |     ^^^
diff --git a/substrate/frame/support/test/tests/pallet_ui/task_condition_invalid_arg.rs b/substrate/frame/support/test/tests/pallet_ui/task_condition_invalid_arg.rs
new file mode 100644
index 00000000000..1db96869155
--- /dev/null
+++ b/substrate/frame/support/test/tests/pallet_ui/task_condition_invalid_arg.rs
@@ -0,0 +1,42 @@
+// 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.
+
+#[frame_support::pallet(dev_mode)]
+mod pallet {
+	use frame_support::pallet_prelude::DispatchResult;
+	use frame_system::pallet_prelude::OriginFor;
+
+	#[pallet::config]
+	pub trait Config: frame_system::Config {}
+
+	#[pallet::pallet]
+	pub struct Pallet<T>(core::marker::PhantomData<T>);
+
+    #[pallet::tasks_experimental]
+	impl<T: Config> Pallet<T> {
+		#[pallet::task_index(0)]
+		#[pallet::task_condition(|flag: bool| flag)]
+		#[pallet::task_list(vec![1, 2].iter())]
+		#[pallet::task_weight(0.into())]
+		fn foo(_i: u32) -> DispatchResult {
+			Ok(())
+		}
+	}
+}
+
+fn main() {
+}
diff --git a/substrate/frame/support/test/tests/pallet_ui/task_condition_invalid_arg.stderr b/substrate/frame/support/test/tests/pallet_ui/task_condition_invalid_arg.stderr
new file mode 100644
index 00000000000..ef259656688
--- /dev/null
+++ b/substrate/frame/support/test/tests/pallet_ui/task_condition_invalid_arg.stderr
@@ -0,0 +1,22 @@
+error: unused import: `frame_system::pallet_prelude::OriginFor`
+  --> tests/pallet_ui/task_condition_invalid_arg.rs:21:6
+   |
+21 |     use frame_system::pallet_prelude::OriginFor;
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: `-D unused-imports` implied by `-D warnings`
+
+error[E0308]: mismatched types
+  --> tests/pallet_ui/task_condition_invalid_arg.rs:35:10
+   |
+32 |         #[pallet::task_condition(|flag: bool| flag)]
+   |                                  ----------------- arguments to this function are incorrect
+...
+35 |         fn foo(_i: u32) -> DispatchResult {
+   |                ^^ expected `bool`, found `u32`
+   |
+note: closure parameter defined here
+  --> tests/pallet_ui/task_condition_invalid_arg.rs:32:29
+   |
+32 |         #[pallet::task_condition(|flag: bool| flag)]
+   |                                   ^^^^^^^^^^
diff --git a/substrate/frame/support/test/tests/pallet_ui/task_invalid_condition.rs b/substrate/frame/support/test/tests/pallet_ui/task_invalid_condition.rs
new file mode 100644
index 00000000000..6875bc13b8f
--- /dev/null
+++ b/substrate/frame/support/test/tests/pallet_ui/task_invalid_condition.rs
@@ -0,0 +1,42 @@
+// 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.
+
+#[frame_support::pallet(dev_mode)]
+mod pallet {
+	use frame_support::pallet_prelude::DispatchResult;
+	use frame_system::pallet_prelude::OriginFor;
+
+	#[pallet::config]
+	pub trait Config: frame_system::Config {}
+
+	#[pallet::pallet]
+	pub struct Pallet<T>(core::marker::PhantomData<T>);
+
+    #[pallet::tasks_experimental]
+	impl<T: Config> Pallet<T> {
+		#[pallet::task_index(0)]
+		#[pallet::task_condition(0)]
+		#[pallet::task_list(vec![1, 2].iter())]
+		#[pallet::task_weight(0.into())]
+		fn foo() -> DispatchResult {
+			Ok(())
+		}
+	}
+}
+
+fn main() {
+}
diff --git a/substrate/frame/support/test/tests/pallet_ui/task_invalid_condition.stderr b/substrate/frame/support/test/tests/pallet_ui/task_invalid_condition.stderr
new file mode 100644
index 00000000000..ff57017b3fd
--- /dev/null
+++ b/substrate/frame/support/test/tests/pallet_ui/task_invalid_condition.stderr
@@ -0,0 +1,27 @@
+error: unused import: `frame_system::pallet_prelude::OriginFor`
+  --> tests/pallet_ui/task_invalid_condition.rs:21:6
+   |
+21 |     use frame_system::pallet_prelude::OriginFor;
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: `-D unused-imports` implied by `-D warnings`
+
+error[E0308]: mismatched types
+  --> tests/pallet_ui/task_invalid_condition.rs:18:1
+   |
+18 | #[frame_support::pallet(dev_mode)]
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   | |
+   | expected integer, found `()`
+   | expected due to this
+   |
+   = note: this error originates in the attribute macro `frame_support::pallet` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error[E0618]: expected function, found `{integer}`
+  --> tests/pallet_ui/task_invalid_condition.rs:32:28
+   |
+18 | #[frame_support::pallet(dev_mode)]
+   | ---------------------------------- call expression requires function
+...
+32 |         #[pallet::task_condition(0)]
+   |                                  ^
diff --git a/substrate/frame/support/test/tests/pallet_ui/task_invalid_index.rs b/substrate/frame/support/test/tests/pallet_ui/task_invalid_index.rs
new file mode 100644
index 00000000000..2a4b40523a6
--- /dev/null
+++ b/substrate/frame/support/test/tests/pallet_ui/task_invalid_index.rs
@@ -0,0 +1,39 @@
+// 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.
+
+#[frame_support::pallet(dev_mode)]
+mod pallet {
+	use frame_support::pallet_prelude::DispatchResult;
+	use frame_system::pallet_prelude::OriginFor;
+
+	#[pallet::config]
+	pub trait Config: frame_system::Config {}
+
+	#[pallet::pallet]
+	pub struct Pallet<T>(core::marker::PhantomData<T>);
+
+    #[pallet::tasks_experimental]
+	impl<T: Config> Pallet<T> {
+		#[pallet::task_index("0")]
+		fn foo() -> DispatchResult {
+			Ok(())
+		}
+	}
+}
+
+fn main() {
+}
diff --git a/substrate/frame/support/test/tests/pallet_ui/task_invalid_index.stderr b/substrate/frame/support/test/tests/pallet_ui/task_invalid_index.stderr
new file mode 100644
index 00000000000..d33600455bf
--- /dev/null
+++ b/substrate/frame/support/test/tests/pallet_ui/task_invalid_index.stderr
@@ -0,0 +1,5 @@
+error: expected integer literal
+  --> tests/pallet_ui/task_invalid_index.rs:31:24
+   |
+31 |         #[pallet::task_index("0")]
+   |                              ^^^
diff --git a/substrate/frame/support/test/tests/pallet_ui/task_invalid_list.rs b/substrate/frame/support/test/tests/pallet_ui/task_invalid_list.rs
new file mode 100644
index 00000000000..bb6438aaf10
--- /dev/null
+++ b/substrate/frame/support/test/tests/pallet_ui/task_invalid_list.rs
@@ -0,0 +1,42 @@
+// 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.
+
+#[frame_support::pallet(dev_mode)]
+mod pallet {
+	use frame_support::pallet_prelude::DispatchResult;
+	use frame_system::pallet_prelude::OriginFor;
+
+	#[pallet::config]
+	pub trait Config: frame_system::Config {}
+
+	#[pallet::pallet]
+	pub struct Pallet<T>(core::marker::PhantomData<T>);
+
+    #[pallet::tasks_experimental]
+	impl<T: Config> Pallet<T> {
+		#[pallet::task_index(0)]
+		#[pallet::task_condition(|| true)]
+		#[pallet::task_list(0)]
+		#[pallet::task_weight(0.into())]
+		fn foo() -> DispatchResult {
+			Ok(())
+		}
+	}
+}
+
+fn main() {
+}
diff --git a/substrate/frame/support/test/tests/pallet_ui/task_invalid_list.stderr b/substrate/frame/support/test/tests/pallet_ui/task_invalid_list.stderr
new file mode 100644
index 00000000000..8dda0c3a1aa
--- /dev/null
+++ b/substrate/frame/support/test/tests/pallet_ui/task_invalid_list.stderr
@@ -0,0 +1,19 @@
+error: unused import: `frame_system::pallet_prelude::OriginFor`
+  --> tests/pallet_ui/task_invalid_list.rs:21:6
+   |
+21 |     use frame_system::pallet_prelude::OriginFor;
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: `-D unused-imports` implied by `-D warnings`
+
+error[E0689]: can't call method `map` on ambiguous numeric type `{integer}`
+  --> tests/pallet_ui/task_invalid_list.rs:18:1
+   |
+18 | #[frame_support::pallet(dev_mode)]
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: this error originates in the attribute macro `frame_support::pallet` (in Nightly builds, run with -Z macro-backtrace for more info)
+help: you must specify a concrete type for this numeric value, like `i32`
+   |
+33 |         #[pallet::task_list(0_i32)]
+   |                             ~~~~~
diff --git a/substrate/frame/support/test/tests/pallet_ui/task_invalid_weight.rs b/substrate/frame/support/test/tests/pallet_ui/task_invalid_weight.rs
new file mode 100644
index 00000000000..a0c4040347a
--- /dev/null
+++ b/substrate/frame/support/test/tests/pallet_ui/task_invalid_weight.rs
@@ -0,0 +1,42 @@
+// 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.
+
+#[frame_support::pallet(dev_mode)]
+mod pallet {
+	use frame_support::pallet_prelude::DispatchResult;
+	use frame_system::pallet_prelude::OriginFor;
+
+	#[pallet::config]
+	pub trait Config: frame_system::Config {}
+
+	#[pallet::pallet]
+	pub struct Pallet<T>(core::marker::PhantomData<T>);
+
+    #[pallet::tasks_experimental]
+	impl<T: Config> Pallet<T> {
+		#[pallet::task_index(0)]
+		#[pallet::task_condition(|| true)]
+		#[pallet::task_list(vec![1, 2].iter())]
+		#[pallet::task_weight("0")]
+		fn foo() -> DispatchResult {
+			Ok(())
+		}
+	}
+}
+
+fn main() {
+}
diff --git a/substrate/frame/support/test/tests/pallet_ui/task_invalid_weight.stderr b/substrate/frame/support/test/tests/pallet_ui/task_invalid_weight.stderr
new file mode 100644
index 00000000000..3537bb59028
--- /dev/null
+++ b/substrate/frame/support/test/tests/pallet_ui/task_invalid_weight.stderr
@@ -0,0 +1,27 @@
+error: unused import: `frame_system::pallet_prelude::OriginFor`
+  --> tests/pallet_ui/task_invalid_weight.rs:21:6
+   |
+21 |     use frame_system::pallet_prelude::OriginFor;
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: `-D unused-imports` implied by `-D warnings`
+
+error[E0308]: mismatched types
+  --> tests/pallet_ui/task_invalid_weight.rs:18:1
+   |
+18 | #[frame_support::pallet(dev_mode)]
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   | |
+   | expected integer, found `()`
+   | expected due to this
+   |
+   = note: this error originates in the attribute macro `frame_support::pallet` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error[E0308]: mismatched types
+  --> tests/pallet_ui/task_invalid_weight.rs:34:25
+   |
+18 | #[frame_support::pallet(dev_mode)]
+   | ---------------------------------- expected `Weight` because of return type
+...
+34 |         #[pallet::task_weight("0")]
+   |                               ^^^ expected `Weight`, found `&str`
diff --git a/substrate/frame/support/test/tests/pallet_ui/task_missing_condition.rs b/substrate/frame/support/test/tests/pallet_ui/task_missing_condition.rs
new file mode 100644
index 00000000000..6ca6e37a5bd
--- /dev/null
+++ b/substrate/frame/support/test/tests/pallet_ui/task_missing_condition.rs
@@ -0,0 +1,39 @@
+// 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.
+
+#[frame_support::pallet(dev_mode)]
+mod pallet {
+	use frame_support::pallet_prelude::DispatchResult;
+	use frame_system::pallet_prelude::OriginFor;
+
+	#[pallet::config]
+	pub trait Config: frame_system::Config {}
+
+	#[pallet::pallet]
+	pub struct Pallet<T>(core::marker::PhantomData<T>);
+
+    #[pallet::tasks_experimental]
+	impl<T: Config> Pallet<T> {
+		#[pallet::task_index(0)]
+		fn foo() -> DispatchResult {
+			Ok(())
+		}
+	}
+}
+
+fn main() {
+}
diff --git a/substrate/frame/support/test/tests/pallet_ui/task_missing_condition.stderr b/substrate/frame/support/test/tests/pallet_ui/task_missing_condition.stderr
new file mode 100644
index 00000000000..c709ec7eac9
--- /dev/null
+++ b/substrate/frame/support/test/tests/pallet_ui/task_missing_condition.stderr
@@ -0,0 +1,5 @@
+error: missing `#[pallet::task_condition(..)]` attribute
+  --> tests/pallet_ui/task_missing_condition.rs:32:6
+   |
+32 |         fn foo() -> DispatchResult {
+   |            ^^^
diff --git a/substrate/frame/support/test/tests/pallet_ui/task_missing_index.rs b/substrate/frame/support/test/tests/pallet_ui/task_missing_index.rs
new file mode 100644
index 00000000000..ed98d229f18
--- /dev/null
+++ b/substrate/frame/support/test/tests/pallet_ui/task_missing_index.rs
@@ -0,0 +1,38 @@
+// 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.
+
+#[frame_support::pallet(dev_mode)]
+mod pallet {
+	use frame_support::pallet_prelude::DispatchResult;
+	use frame_system::pallet_prelude::OriginFor;
+
+	#[pallet::config]
+	pub trait Config: frame_system::Config {}
+
+	#[pallet::pallet]
+	pub struct Pallet<T>(core::marker::PhantomData<T>);
+
+    #[pallet::tasks_experimental]
+	impl<T: Config> Pallet<T> {
+		fn foo() -> DispatchResult {
+			Ok(())
+		}
+	}
+}
+
+fn main() {
+}
diff --git a/substrate/frame/support/test/tests/pallet_ui/task_missing_index.stderr b/substrate/frame/support/test/tests/pallet_ui/task_missing_index.stderr
new file mode 100644
index 00000000000..ba3c9d132b8
--- /dev/null
+++ b/substrate/frame/support/test/tests/pallet_ui/task_missing_index.stderr
@@ -0,0 +1,5 @@
+error: missing `#[pallet::task_index(..)]` attribute
+  --> tests/pallet_ui/task_missing_index.rs:31:6
+   |
+31 |         fn foo() -> DispatchResult {
+   |            ^^^
diff --git a/substrate/frame/support/test/tests/pallet_ui/task_missing_list.rs b/substrate/frame/support/test/tests/pallet_ui/task_missing_list.rs
new file mode 100644
index 00000000000..427efe12763
--- /dev/null
+++ b/substrate/frame/support/test/tests/pallet_ui/task_missing_list.rs
@@ -0,0 +1,40 @@
+// 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.
+
+#[frame_support::pallet(dev_mode)]
+mod pallet {
+	use frame_support::pallet_prelude::DispatchResult;
+	use frame_system::pallet_prelude::OriginFor;
+
+	#[pallet::config]
+	pub trait Config: frame_system::Config {}
+
+	#[pallet::pallet]
+	pub struct Pallet<T>(core::marker::PhantomData<T>);
+
+    #[pallet::tasks_experimental]
+	impl<T: Config> Pallet<T> {
+		#[pallet::task_index(0)]
+		#[pallet::task_condition(|| true)]
+		fn foo() -> DispatchResult {
+			Ok(())
+		}
+	}
+}
+
+fn main() {
+}
diff --git a/substrate/frame/support/test/tests/pallet_ui/task_missing_list.stderr b/substrate/frame/support/test/tests/pallet_ui/task_missing_list.stderr
new file mode 100644
index 00000000000..f4ae26a75ad
--- /dev/null
+++ b/substrate/frame/support/test/tests/pallet_ui/task_missing_list.stderr
@@ -0,0 +1,5 @@
+error: missing `#[pallet::task_list(..)]` attribute
+  --> tests/pallet_ui/task_missing_list.rs:33:6
+   |
+33 |         fn foo() -> DispatchResult {
+   |            ^^^
diff --git a/substrate/frame/support/test/tests/pallet_ui/task_missing_weight.rs b/substrate/frame/support/test/tests/pallet_ui/task_missing_weight.rs
new file mode 100644
index 00000000000..704be1f1e0b
--- /dev/null
+++ b/substrate/frame/support/test/tests/pallet_ui/task_missing_weight.rs
@@ -0,0 +1,41 @@
+// 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.
+
+#[frame_support::pallet(dev_mode)]
+mod pallet {
+	use frame_support::pallet_prelude::DispatchResult;
+	use frame_system::pallet_prelude::OriginFor;
+
+	#[pallet::config]
+	pub trait Config: frame_system::Config {}
+
+	#[pallet::pallet]
+	pub struct Pallet<T>(core::marker::PhantomData<T>);
+
+    #[pallet::tasks_experimental]
+	impl<T: Config> Pallet<T> {
+		#[pallet::task_index(0)]
+		#[pallet::task_condition(|| true)]
+		#[pallet::task_list(vec![1, 2].iter())]
+		fn foo() -> DispatchResult {
+			Ok(())
+		}
+	}
+}
+
+fn main() {
+}
diff --git a/substrate/frame/support/test/tests/pallet_ui/task_missing_weight.stderr b/substrate/frame/support/test/tests/pallet_ui/task_missing_weight.stderr
new file mode 100644
index 00000000000..de7b2eb1720
--- /dev/null
+++ b/substrate/frame/support/test/tests/pallet_ui/task_missing_weight.stderr
@@ -0,0 +1,5 @@
+error: missing `#[pallet::task_weight(..)]` attribute
+  --> tests/pallet_ui/task_missing_weight.rs:34:6
+   |
+34 |         fn foo() -> DispatchResult {
+   |            ^^^
diff --git a/substrate/frame/system/src/lib.rs b/substrate/frame/system/src/lib.rs
index 640cb133213..6624bb43a80 100644
--- a/substrate/frame/system/src/lib.rs
+++ b/substrate/frame/system/src/lib.rs
@@ -241,6 +241,8 @@ pub mod pallet {
 			type RuntimeCall = ();
 			#[inject_runtime_type]
 			type PalletInfo = ();
+			#[inject_runtime_type]
+			type RuntimeTask = ();
 			type BaseCallFilter = frame_support::traits::Everything;
 			type BlockHashCount = frame_support::traits::ConstU64<10>;
 			type OnSetCode = ();
@@ -323,6 +325,8 @@ pub mod pallet {
 
 			/// Converts a module to the index of the module, injected by `construct_runtime!`.
 			#[inject_runtime_type]
+			type RuntimeTask = ();
+			#[inject_runtime_type]
 			type PalletInfo = ();
 
 			/// The basic call filter to use in dispatchable. Supports everything as the default.
@@ -400,6 +404,10 @@ pub mod pallet {
 			+ Debug
 			+ From<Call<Self>>;
 
+		/// The aggregated `RuntimeTask` type.
+		#[pallet::no_default_bounds]
+		type RuntimeTask: Task;
+
 		/// This stores the number of previous transactions associated with a sender account.
 		type Nonce: Parameter
 			+ Member
@@ -628,6 +636,28 @@ pub mod pallet {
 			Self::deposit_event(Event::Remarked { sender: who, hash });
 			Ok(().into())
 		}
+
+		#[pallet::call_index(8)]
+		#[pallet::weight(task.weight())]
+		pub fn do_task(origin: OriginFor<T>, task: T::RuntimeTask) -> DispatchResultWithPostInfo {
+			ensure_signed(origin)?;
+
+			if !task.is_valid() {
+				return Err(Error::<T>::InvalidTask.into())
+			}
+
+			Self::deposit_event(Event::TaskStarted { task: task.clone() });
+			if let Err(err) = task.run() {
+				Self::deposit_event(Event::TaskFailed { task, err });
+				return Err(Error::<T>::FailedTask.into())
+			}
+
+			// Emit a success event, if your design includes events for this pallet.
+			Self::deposit_event(Event::TaskCompleted { task });
+
+			// Return success.
+			Ok(().into())
+		}
 	}
 
 	/// Event for the System pallet.
@@ -645,6 +675,12 @@ pub mod pallet {
 		KilledAccount { account: T::AccountId },
 		/// On on-chain remark happened.
 		Remarked { sender: T::AccountId, hash: T::Hash },
+		/// A [`Task`] has started executing
+		TaskStarted { task: T::RuntimeTask },
+		/// A [`Task`] has finished executing.
+		TaskCompleted { task: T::RuntimeTask },
+		/// A [`Task`] failed during execution.
+		TaskFailed { task: T::RuntimeTask, err: DispatchError },
 	}
 
 	/// Error for the System pallet
@@ -666,6 +702,10 @@ pub mod pallet {
 		NonZeroRefCount,
 		/// The origin filter prevent the call to be dispatched.
 		CallFiltered,
+		/// The specified [`Task`] is not valid.
+		InvalidTask,
+		/// The specified [`Task`] failed during execution.
+		FailedTask,
 	}
 
 	/// Exposed trait-generic origin type.
diff --git a/substrate/frame/system/src/mock.rs b/substrate/frame/system/src/mock.rs
index c016ea9e1cd..e33ac2f56c8 100644
--- a/substrate/frame/system/src/mock.rs
+++ b/substrate/frame/system/src/mock.rs
@@ -17,7 +17,7 @@
 
 use crate::{self as frame_system, *};
 use frame_support::{
-	parameter_types,
+	derive_impl, parameter_types,
 	traits::{ConstU32, ConstU64},
 };
 use sp_core::H256;
@@ -85,6 +85,7 @@ impl OnKilledAccount<u64> for RecordKilled {
 	}
 }
 
+#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)]
 impl Config for Test {
 	type BaseCallFilter = frame_support::traits::Everything;
 	type BlockWeights = RuntimeBlockWeights;
diff --git a/substrate/test-utils/runtime/src/lib.rs b/substrate/test-utils/runtime/src/lib.rs
index 1a4e9fd0466..16ab467772f 100644
--- a/substrate/test-utils/runtime/src/lib.rs
+++ b/substrate/test-utils/runtime/src/lib.rs
@@ -27,7 +27,7 @@ pub mod substrate_test_pallet;
 
 use codec::{Decode, Encode};
 use frame_support::{
-	construct_runtime,
+	construct_runtime, derive_impl,
 	dispatch::DispatchClass,
 	genesis_builder_helper::{build_config, create_default_config},
 	parameter_types,
@@ -342,6 +342,7 @@ parameter_types! {
 		.build_or_panic();
 }
 
+#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)]
 impl frame_system::pallet::Config for Runtime {
 	type BaseCallFilter = frame_support::traits::Everything;
 	type BlockWeights = RuntimeBlockWeights;
diff --git a/substrate/utils/frame/rpc/support/src/lib.rs b/substrate/utils/frame/rpc/support/src/lib.rs
index b91ae436127..bb5098293c2 100644
--- a/substrate/utils/frame/rpc/support/src/lib.rs
+++ b/substrate/utils/frame/rpc/support/src/lib.rs
@@ -63,6 +63,7 @@ use sp_storage::{StorageData, StorageKey};
 /// # 	type Lookup = IdentityLookup<Self::AccountId>;
 /// # 	type Block = frame_system::mocking::MockBlock<TestRuntime>;
 /// # 	type RuntimeEvent = RuntimeEvent;
+/// # 	type RuntimeTask = RuntimeTask;
 /// # 	type BlockHashCount = ();
 /// # 	type DbWeight = ();
 /// # 	type Version = ();
-- 
GitLab