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>
This commit is contained in:
Sam Johnson
2023-12-08 00:40:26 -05:00
committed by GitHub
parent 34c991e2cf
commit ac3f14d23b
75 changed files with 3516 additions and 24 deletions
Generated
+19
View File
@@ -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]]
+1
View File
@@ -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",
@@ -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 = ();
@@ -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 = ();
+29
View File
@@ -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: []
+4
View File
@@ -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",
+7
View File
@@ -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]
+3
View File
@@ -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",
]
@@ -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;
@@ -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"]
+2
View File
@@ -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.
+52
View File
@@ -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",
]
@@ -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);
}
+78
View File
@@ -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>;
}
@@ -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 = ();
}
+127
View File
@@ -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
);
});
}
+84
View File
@@ -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))
}
}
+3 -1
View File
@@ -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;
@@ -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 = []
@@ -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;
@@ -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
}
@@ -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
@@ -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),
}
+54 -4
View File
@@ -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`).
///
@@ -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
@@ -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
}
}
@@ -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
}
}]
@@ -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));
}
@@ -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,
@@ -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) {
@@ -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`"
);
});
}
@@ -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>(_);
}
}
}
@@ -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>(_);
}
};
}
+2
View File
@@ -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;
}
+56 -1
View File
@@ -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.")]
@@ -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 = ();
}
+55 -2
View File
@@ -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."],
},
],
}
}
@@ -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));
});
}
+3
View File
@@ -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")]
@@ -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
}
}
@@ -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 = ();
+2
View File
@@ -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
@@ -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>},
@@ -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},
@@ -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},
@@ -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)
@@ -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 = ();
@@ -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(_, _) => (),
},
@@ -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(_, _) => (),
},
@@ -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 {}
@@ -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() {
}
@@ -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() {
}
@@ -0,0 +1,5 @@
error: expected `impl`
--> tests/pallet_ui/task_can_only_be_attached_to_impl.rs:30:5
|
30 | pub struct Task;
| ^^^
@@ -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() {
}
@@ -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)]
| ^^^^^^^^^^
@@ -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() {
}
@@ -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)]
| ^
@@ -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() {
}
@@ -0,0 +1,5 @@
error: expected integer literal
--> tests/pallet_ui/task_invalid_index.rs:31:24
|
31 | #[pallet::task_index("0")]
| ^^^
@@ -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() {
}
@@ -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)]
| ~~~~~
@@ -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() {
}
@@ -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`
@@ -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() {
}
@@ -0,0 +1,5 @@
error: missing `#[pallet::task_condition(..)]` attribute
--> tests/pallet_ui/task_missing_condition.rs:32:6
|
32 | fn foo() -> DispatchResult {
| ^^^
@@ -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() {
}
@@ -0,0 +1,5 @@
error: missing `#[pallet::task_index(..)]` attribute
--> tests/pallet_ui/task_missing_index.rs:31:6
|
31 | fn foo() -> DispatchResult {
| ^^^
@@ -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() {
}
@@ -0,0 +1,5 @@
error: missing `#[pallet::task_list(..)]` attribute
--> tests/pallet_ui/task_missing_list.rs:33:6
|
33 | fn foo() -> DispatchResult {
| ^^^
@@ -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() {
}
@@ -0,0 +1,5 @@
error: missing `#[pallet::task_weight(..)]` attribute
--> tests/pallet_ui/task_missing_weight.rs:34:6
|
34 | fn foo() -> DispatchResult {
| ^^^
+40
View File
@@ -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.
+2 -1
View File
@@ -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;
+2 -1
View File
@@ -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;
@@ -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 = ();