diff --git a/Cargo.lock b/Cargo.lock index e001fdab27352..49cc265993aa0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6512,6 +6512,21 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-example-split" +version = "4.0.0-dev" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-std", +] + [[package]] name = "pallet-examples" version = "4.0.0-dev" @@ -6521,6 +6536,7 @@ dependencies = [ "pallet-example-basic", "pallet-example-kitchensink", "pallet-example-offchain-worker", + "pallet-example-split", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 0ad71911ae017..2d894b0be6e81 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -111,6 +111,7 @@ members = [ "frame/examples/offchain-worker", "frame/examples/kitchensink", "frame/examples/dev-mode", + "frame/examples/split", "frame/examples/default-config", "frame/executive", "frame/nis", diff --git a/frame/examples/Cargo.toml b/frame/examples/Cargo.toml index 57def091b1f63..af67bef792b6f 100644 --- a/frame/examples/Cargo.toml +++ b/frame/examples/Cargo.toml @@ -17,6 +17,7 @@ pallet-default-config-example = { default-features = false, path = "./default-co pallet-example-offchain-worker = { default-features = false, path = "./offchain-worker" } pallet-example-kitchensink = { default-features = false, path = "./kitchensink" } pallet-dev-mode = { default-features = false, path = "./dev-mode" } +pallet-example-split = { default-features = false, path = "./split" } [features] default = [ "std" ] @@ -26,6 +27,7 @@ std = [ "pallet-example-offchain-worker/std", "pallet-example-kitchensink/std", "pallet-dev-mode/std", + "pallet-example-split/std", ] try-runtime = [ "pallet-example-basic/try-runtime", @@ -33,4 +35,5 @@ try-runtime = [ "pallet-example-offchain-worker/try-runtime", "pallet-example-kitchensink/try-runtime", "pallet-dev-mode/try-runtime", -] \ No newline at end of file + "pallet-example-split/try-runtime", +] diff --git a/frame/examples/split/Cargo.toml b/frame/examples/split/Cargo.toml new file mode 100644 index 0000000000000..21ecf89330dd7 --- /dev/null +++ b/frame/examples/split/Cargo.toml @@ -0,0 +1,47 @@ +[package] +name = "pallet-example-split" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "MIT-0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "FRAME example splitted pallet" +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false } +log = { version = "0.4.17", default-features = false } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } + +frame-support = { version = "4.0.0-dev", default-features = false, path = "../../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../../system" } + +sp-io = { version = "23.0.0", default-features = false, path = "../../../primitives/io" } +sp-std = { version = "8.0.0", default-features = false, path = "../../../primitives/std" } + +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../../benchmarking" } + +[dev-dependencies] +sp-core = { version = "21.0.0", default-features = false, path = "../../../primitives/core" } + +[features] +default = ["std"] +std = [ + "codec/std", + "log/std", + "scale-info/std", + + "frame-support/std", + "frame-system/std", + + "sp-io/std", + "sp-std/std", + + "frame-benchmarking?/std", +] +runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks"] +try-runtime = ["frame-support/try-runtime"] diff --git a/frame/examples/split/README.md b/frame/examples/split/README.md new file mode 100644 index 0000000000000..413ce9b913cb9 --- /dev/null +++ b/frame/examples/split/README.md @@ -0,0 +1,10 @@ + +# Basic Example For Splitting A Pallet +A simple example of a FRAME pallet demonstrating the ability to split sections across multiple +files. + +Note that this is purely experimental at this point. + +Run `cargo doc --package pallet-example-split --open` to view this pallet's documentation. + +License: MIT-0 diff --git a/frame/examples/split/src/benchmarking.rs b/frame/examples/split/src/benchmarking.rs new file mode 100644 index 0000000000000..5a50300937203 --- /dev/null +++ b/frame/examples/split/src/benchmarking.rs @@ -0,0 +1,54 @@ +// 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 setup for pallet-example-split + +// Only enable this module for benchmarking. +#![cfg(feature = "runtime-benchmarks")] +use super::*; + +#[allow(unused)] +use crate::Pallet as Template; +use frame_benchmarking::v2::*; +use frame_system::RawOrigin; + +#[benchmarks] +mod benchmarks { + use super::*; + + #[benchmark] + fn do_something() { + let value = 100u32.into(); + let caller: T::AccountId = whitelisted_caller(); + #[extrinsic_call] + do_something(RawOrigin::Signed(caller), value); + + assert_eq!(Something::::get(), Some(value)); + } + + #[benchmark] + fn cause_error() { + Something::::put(100u32); + let caller: T::AccountId = whitelisted_caller(); + #[extrinsic_call] + cause_error(RawOrigin::Signed(caller)); + + assert_eq!(Something::::get(), Some(101u32)); + } + + impl_benchmark_test_suite!(Template, crate::mock::new_test_ext(), crate::mock::Test); +} diff --git a/frame/examples/split/src/events.rs b/frame/examples/split/src/events.rs new file mode 100644 index 0000000000000..7560766bacb33 --- /dev/null +++ b/frame/examples/split/src/events.rs @@ -0,0 +1,31 @@ +// 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 frame_support::pallet_macros::*; + +/// A [`pallet_section`] that defines the events for a pallet. +/// This can later be imported into the pallet using [`import_section`]. +#[pallet_section] +mod events { + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// Event documentation should end with an array that provides descriptive names for event + /// parameters. [something, who] + SomethingStored { something: u32, who: T::AccountId }, + } +} diff --git a/frame/examples/split/src/lib.rs b/frame/examples/split/src/lib.rs new file mode 100644 index 0000000000000..74d2e0cc24b7b --- /dev/null +++ b/frame/examples/split/src/lib.rs @@ -0,0 +1,123 @@ +// 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. + +//! # Split Example Pallet +//! +//! **This pallet serves as an example and is not meant to be used in production.** +//! +//! A FRAME pallet demonstrating the ability to split sections across multiple files. +//! +//! Note that this is purely experimental at this point. + +#![cfg_attr(not(feature = "std"), no_std)] + +// Re-export pallet items so that they can be accessed from the crate namespace. +pub use pallet::*; + +#[cfg(test)] +mod mock; + +#[cfg(test)] +mod tests; + +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; +mod events; + +pub mod weights; +pub use weights::*; + +use frame_support::pallet_macros::*; + +/// Imports a [`pallet_section`] defined at [`events::events`]. +/// This brings the events defined in that section into the pallet's namespace. +#[import_section(events::events)] +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::pallet] + pub struct Pallet(_); + + /// Configure the pallet by specifying the parameters and types on which it depends. + #[pallet::config] + pub trait Config: frame_system::Config { + /// Because this pallet emits events, it depends on the runtime's definition of an event. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + /// Type representing the weight of this pallet + type WeightInfo: WeightInfo; + } + + // The pallet's runtime storage items. + #[pallet::storage] + pub type Something = StorageValue<_, u32>; + + // Errors inform users that something went wrong. + #[pallet::error] + pub enum Error { + /// Error names should be descriptive. + NoneValue, + /// Errors should have helpful documentation associated with them. + StorageOverflow, + } + + // Dispatchable functions allows users to interact with the pallet and invoke state changes. + // These functions materialize as "extrinsics", which are often compared to transactions. + // Dispatchable functions must be annotated with a weight and must return a DispatchResult. + #[pallet::call] + impl Pallet { + /// An example dispatchable that takes a singles value as a parameter, writes the value to + /// storage and emits an event. This function must be dispatched by a signed extrinsic. + #[pallet::call_index(0)] + #[pallet::weight(T::WeightInfo::do_something())] + pub fn do_something(origin: OriginFor, something: u32) -> DispatchResult { + // Check that the extrinsic was signed and get the signer. + // This function will return an error if the extrinsic is not signed. + let who = ensure_signed(origin)?; + + // Update storage. + >::put(something); + + // Emit an event. + Self::deposit_event(Event::SomethingStored { something, who }); + // Return a successful DispatchResultWithPostInfo + Ok(()) + } + + /// An example dispatchable that may throw a custom error. + #[pallet::call_index(1)] + #[pallet::weight(T::WeightInfo::cause_error())] + pub fn cause_error(origin: OriginFor) -> DispatchResult { + let _who = ensure_signed(origin)?; + + // Read a value from storage. + match >::get() { + // Return an error if the value has not been set. + None => return Err(Error::::NoneValue.into()), + Some(old) => { + // Increment the value read from storage; will error in the event of overflow. + let new = old.checked_add(1).ok_or(Error::::StorageOverflow)?; + // Update the value in storage with the incremented result. + >::put(new); + Ok(()) + }, + } + } + } +} diff --git a/frame/examples/split/src/mock.rs b/frame/examples/split/src/mock.rs new file mode 100644 index 0000000000000..e479e821f4262 --- /dev/null +++ b/frame/examples/split/src/mock.rs @@ -0,0 +1,56 @@ +// 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 as pallet_template; +use frame_support::derive_impl; + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +// Configure a mock runtime to test the pallet. +frame_support::construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system, + TemplatePallet: pallet_template, + } +); + +/// Using a default config for [`frame_system`] in tests. See `default-config` example for more +/// details. +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type RuntimeEvent = RuntimeEvent; + type PalletInfo = PalletInfo; + type OnSetCode = (); +} + +impl pallet_template::Config for Test { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); +} + +// Build genesis storage according to the mock runtime. +pub fn new_test_ext() -> sp_io::TestExternalities { + frame_system::GenesisConfig::default().build_storage::().unwrap().into() +} diff --git a/frame/examples/split/src/tests.rs b/frame/examples/split/src/tests.rs new file mode 100644 index 0000000000000..1d4b6dfcff9d5 --- /dev/null +++ b/frame/examples/split/src/tests.rs @@ -0,0 +1,44 @@ +// 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::{mock::*, Error, Event, Something}; +use frame_support::{assert_noop, assert_ok}; + +#[test] +fn it_works_for_default_value() { + new_test_ext().execute_with(|| { + // Go past genesis block so events get deposited + System::set_block_number(1); + // Dispatch a signed extrinsic. + assert_ok!(TemplatePallet::do_something(RuntimeOrigin::signed(1), 42)); + // Read pallet storage and assert an expected result. + assert_eq!(Something::::get(), Some(42)); + // Assert that the correct event was deposited + System::assert_last_event(Event::SomethingStored { something: 42, who: 1 }.into()); + }); +} + +#[test] +fn correct_error_for_none_value() { + new_test_ext().execute_with(|| { + // Ensure the expected error is thrown when no value is present. + assert_noop!( + TemplatePallet::cause_error(RuntimeOrigin::signed(1)), + Error::::NoneValue + ); + }); +} diff --git a/frame/examples/split/src/weights.rs b/frame/examples/split/src/weights.rs new file mode 100644 index 0000000000000..4219ce1e2697c --- /dev/null +++ b/frame/examples/split/src/weights.rs @@ -0,0 +1,91 @@ + +//! Autogenerated weights for pallet_template +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-04-06, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `Alexs-MacBook-Pro-2.local`, CPU: `` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// ../../target/release/node-template +// benchmark +// pallet +// --chain +// dev +// --pallet +// pallet_template +// --extrinsic +// * +// --steps=50 +// --repeat=20 +// --execution=wasm +// --wasm-execution=compiled +// --output +// pallets/template/src/weights.rs +// --template +// ../../.maintain/frame-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use core::marker::PhantomData; + +/// Weight functions needed for pallet_template. +pub trait WeightInfo { + fn do_something() -> Weight; + fn cause_error() -> Weight; +} + +/// Weights for pallet_template using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + /// Storage: TemplatePallet Something (r:0 w:1) + /// Proof: TemplatePallet Something (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + fn do_something() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 8_000_000 picoseconds. + Weight::from_parts(9_000_000, 0) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: TemplatePallet Something (r:1 w:1) + /// Proof: TemplatePallet Something (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + fn cause_error() -> Weight { + // Proof Size summary in bytes: + // Measured: `32` + // Estimated: `1489` + // Minimum execution time: 6_000_000 picoseconds. + Weight::from_parts(6_000_000, 1489) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + /// Storage: TemplatePallet Something (r:0 w:1) + /// Proof: TemplatePallet Something (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + fn do_something() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 8_000_000 picoseconds. + Weight::from_parts(9_000_000, 0) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: TemplatePallet Something (r:1 w:1) + /// Proof: TemplatePallet Something (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + fn cause_error() -> Weight { + // Proof Size summary in bytes: + // Measured: `32` + // Estimated: `1489` + // Minimum execution time: 6_000_000 picoseconds. + Weight::from_parts(6_000_000, 1489) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } +} diff --git a/frame/examples/src/lib.rs b/frame/examples/src/lib.rs index 3ff1aa7b9f1f2..d1cd32bb50f26 100644 --- a/frame/examples/src/lib.rs +++ b/frame/examples/src/lib.rs @@ -35,3 +35,6 @@ //! //! - [**`pallet-example-kitchensink`**](./kitchensink): A simple example of a FRAME pallet //! demonstrating a catalog of the the FRAME macros and their various syntax options. +//! +//! - [**`pallet-example-split`**](./split): A simple example of a FRAME pallet demonstrating the +//! ability to split sections across multiple files. diff --git a/frame/support/procedural/src/lib.rs b/frame/support/procedural/src/lib.rs index bde5b55148b4b..1f4162a0b7551 100644 --- a/frame/support/procedural/src/lib.rs +++ b/frame/support/procedural/src/lib.rs @@ -43,7 +43,7 @@ use proc_macro::TokenStream; use quote::{quote, ToTokens}; use std::{cell::RefCell, str::FromStr}; pub(crate) use storage::INHERENT_INSTANCE_NAME; -use syn::{parse_macro_input, ItemImpl}; +use syn::{parse_macro_input, Error, ItemImpl, ItemMod}; thread_local! { /// A global counter, can be used to generate a relatively unique identifier. @@ -1761,3 +1761,113 @@ pub fn origin(_: TokenStream, _: TokenStream) -> TokenStream { pub fn composite_enum(_: 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`). +/// +/// Note that sections are imported by their module name/ident, and should be referred to by +/// their _full path_ from the perspective of the target pallet. Do not attempt to make use +/// of `use` statements to bring pallet sections into scope, as this will not work (unless +/// you do so as part of a wildcard import, in which case it will work). +/// +/// ## Naming Logistics +/// +/// Also note that because of how `#[pallet_section]` works, pallet section names must be +/// globally unique _within the crate in which they are defined_. For more information on +/// why this must be the case, see macro_magic's +/// [`#[export_tokens]`](https://docs.rs/macro_magic/latest/macro_magic/attr.export_tokens.html) macro. +/// +/// Optionally, you may provide an argument to `#[pallet_section]` such as +/// `#[pallet_section(some_ident)]`, in the event that there is another pallet section in +/// same crate with the same ident/name. The ident you specify can then be used instead of +/// the module's ident name when you go to import it via `#[import_section]`. +#[proc_macro_attribute] +pub fn pallet_section(attr: TokenStream, tokens: TokenStream) -> TokenStream { + let tokens_clone = tokens.clone(); + // ensure this can only be attached to a module + let _mod = parse_macro_input!(tokens_clone as ItemMod); + + // use macro_magic's export_tokens as the internal implementation otherwise + match macro_magic::mm_core::export_tokens_internal(attr, tokens, false) { + Ok(tokens) => tokens.into(), + Err(err) => err.to_compile_error().into(), + } +} + +/// An attribute macro that can be attached to a module declaration. Doing so will +/// Imports the contents of the specified external pallet section that was defined +/// previously using [`#[pallet_section]`](`macro@pallet_section`). +/// +/// ## Example +/// ```ignore +/// #[import_section(some_section)] +/// #[pallet] +/// pub mod pallet { +/// // ... +/// } +/// ``` +/// where `some_section` was defined elsewhere via: +/// ```ignore +/// #[pallet_section] +/// pub mod some_section { +/// // ... +/// } +/// ``` +/// +/// This will result in the contents of `some_section` being _verbatim_ imported into +/// the pallet above. Note that since the tokens for `some_section` are essentially +/// copy-pasted into the target pallet, you cannot refer to imports that don't also +/// exist in the target pallet, but this is easily resolved by including all relevant +/// `use` statements within your pallet section, so they are imported as well, or by +/// otherwise ensuring that you have the same imports on the target pallet. +/// +/// It is perfectly permissible to import multiple pallet sections into the same pallet, +/// which can be done by having multiple `#[import_section(something)]` attributes +/// attached to the pallet. +/// +/// Note that sections are imported by their module name/ident, and should be referred to by +/// their _full path_ from the perspective of the target pallet. +#[import_tokens_attr { + format!( + "{}::macro_magic", + match generate_crate_access_2018("frame-support") { + Ok(path) => Ok(path), + Err(_) => generate_crate_access_2018("frame"), + } + .expect("Failed to find either `frame-support` or `frame` in `Cargo.toml` dependencies.") + .to_token_stream() + .to_string() + ) +}] +#[proc_macro_attribute] +pub fn import_section(attr: TokenStream, tokens: TokenStream) -> TokenStream { + let foreign_mod = parse_macro_input!(attr as ItemMod); + let mut internal_mod = parse_macro_input!(tokens as ItemMod); + + // check that internal_mod is a pallet module + if !internal_mod.attrs.iter().any(|attr| { + if let Some(last_seg) = attr.path().segments.last() { + last_seg.ident == "pallet" + } else { + false + } + }) { + return Error::new( + internal_mod.ident.span(), + "`#[import_section]` can only be applied to a valid pallet module", + ) + .to_compile_error() + .into() + } + + if let Some(ref mut content) = internal_mod.content { + if let Some(foreign_content) = foreign_mod.content { + content.1.extend(foreign_content.1); + } + } + + quote! { + #internal_mod + } + .into() +} diff --git a/frame/support/src/lib.rs b/frame/support/src/lib.rs index 8d021045ed675..fe8ede54bac57 100644 --- a/frame/support/src/lib.rs +++ b/frame/support/src/lib.rs @@ -2896,9 +2896,9 @@ pub mod pallet_macros { pub use frame_support_procedural::{ call_index, compact, composite_enum, config, constant, disable_frame_system_supertrait_check, error, event, extra_constants, generate_deposit, - generate_store, genesis_build, genesis_config, getter, hooks, inherent, no_default, origin, - storage, storage_prefix, storage_version, type_value, unbounded, validate_unsigned, weight, - whitelist_storage, + generate_store, genesis_build, genesis_config, getter, hooks, import_section, inherent, + no_default, origin, pallet_section, storage, storage_prefix, storage_version, type_value, + unbounded, validate_unsigned, weight, whitelist_storage, }; } diff --git a/frame/support/test/tests/split_ui.rs b/frame/support/test/tests/split_ui.rs new file mode 100644 index 0000000000000..14f99b8ecdab1 --- /dev/null +++ b/frame/support/test/tests/split_ui.rs @@ -0,0 +1,36 @@ +// 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. + +#[rustversion::attr(not(stable), ignore)] +#[cfg(not(feature = "disable-ui-tests"))] +#[test] +fn split_ui() { + // Only run the ui tests when `RUN_UI_TESTS` is set. + if std::env::var("RUN_UI_TESTS").is_err() { + return + } + + // As trybuild is using `cargo check`, we don't need the real WASM binaries. + std::env::set_var("SKIP_WASM_BUILD", "1"); + + // Deny all warnings since we emit warnings as part of a Pallet's UI. + std::env::set_var("RUSTFLAGS", "--deny warnings"); + + let t = trybuild::TestCases::new(); + t.compile_fail("tests/split_ui/*.rs"); + t.pass("tests/split_ui/pass/*.rs"); +} diff --git a/frame/support/test/tests/split_ui/import_without_pallet.rs b/frame/support/test/tests/split_ui/import_without_pallet.rs new file mode 100644 index 0000000000000..874a92e461098 --- /dev/null +++ b/frame/support/test/tests/split_ui/import_without_pallet.rs @@ -0,0 +1,17 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +use frame_support::pallet_macros::*; + +#[pallet_section] +mod storages { + #[pallet::storage] + pub type MyStorageMap = StorageMap<_, _, u32, u64>; +} + +#[import_section(storages)] +pub mod pallet { + +} + +fn main() { +} diff --git a/frame/support/test/tests/split_ui/import_without_pallet.stderr b/frame/support/test/tests/split_ui/import_without_pallet.stderr new file mode 100644 index 0000000000000..0d7b5414b1016 --- /dev/null +++ b/frame/support/test/tests/split_ui/import_without_pallet.stderr @@ -0,0 +1,5 @@ +error: `#[import_section]` can only be applied to a valid pallet module + --> tests/split_ui/import_without_pallet.rs:12:9 + | +12 | pub mod pallet { + | ^^^^^^ diff --git a/frame/support/test/tests/split_ui/no_section_found.rs b/frame/support/test/tests/split_ui/no_section_found.rs new file mode 100644 index 0000000000000..fe12c6dc51b72 --- /dev/null +++ b/frame/support/test/tests/split_ui/no_section_found.rs @@ -0,0 +1,29 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +use frame_support::pallet_macros::*; + +pub use pallet::*; + +#[import_section(storages_dev)] +#[frame_support::pallet(dev_mode)] +pub mod pallet { + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::call] + impl Pallet { + pub fn my_call(_origin: OriginFor) -> DispatchResult { + MyStorageMap::::insert(1, 2); + Ok(()) + } + } +} + +fn main() { +} diff --git a/frame/support/test/tests/split_ui/no_section_found.stderr b/frame/support/test/tests/split_ui/no_section_found.stderr new file mode 100644 index 0000000000000..e0a9322b188e3 --- /dev/null +++ b/frame/support/test/tests/split_ui/no_section_found.stderr @@ -0,0 +1,13 @@ +error[E0432]: unresolved import `pallet` + --> tests/split_ui/no_section_found.rs:5:9 + | +5 | pub use pallet::*; + | ^^^^^^ help: a similar path exists: `test_pallet::pallet` + +error: cannot find macro `__export_tokens_tt_storages_dev` in this scope + --> tests/split_ui/no_section_found.rs:7:1 + | +7 | #[import_section(storages_dev)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the macro `frame_support::macro_magic::forward_tokens` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/frame/support/test/tests/split_ui/pass/split_valid.rs b/frame/support/test/tests/split_ui/pass/split_valid.rs new file mode 100644 index 0000000000000..8b5839ecd28a0 --- /dev/null +++ b/frame/support/test/tests/split_ui/pass/split_valid.rs @@ -0,0 +1,40 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +use frame_support::pallet_macros::*; + +pub use pallet::*; + +#[pallet_section] +mod events { + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + SomethingDone, + } +} + +#[import_section(events)] +#[frame_support::pallet(dev_mode)] +pub mod pallet { + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + } + + #[pallet::call] + impl Pallet { + pub fn my_call(_origin: OriginFor) -> DispatchResult { + Self::deposit_event(Event::SomethingDone); + Ok(()) + } + } +} + +fn main() { +} diff --git a/frame/support/test/tests/split_ui/pass/split_valid_disambiguation.rs b/frame/support/test/tests/split_ui/pass/split_valid_disambiguation.rs new file mode 100644 index 0000000000000..8d8d50422e9ce --- /dev/null +++ b/frame/support/test/tests/split_ui/pass/split_valid_disambiguation.rs @@ -0,0 +1,61 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +use frame_support::pallet_macros::*; + +pub use pallet::*; + +mod first { + use super::*; + + #[pallet_section] + mod section { + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + SomethingDone, + } + } +} + +mod second { + use super::*; + + #[pallet_section(section2)] + mod section { + #[pallet::error] + pub enum Error { + NoneValue, + } + } +} + +#[import_section(first::section)] +#[import_section(second::section2)] +#[frame_support::pallet(dev_mode)] +pub mod pallet { + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + } + + #[pallet::call] + impl Pallet { + pub fn my_call(_origin: OriginFor) -> DispatchResult { + Self::deposit_event(Event::SomethingDone); + Ok(()) + } + + pub fn my_call_2(_origin: OriginFor) -> DispatchResult { + return Err(Error::::NoneValue.into()) + } + } +} + +fn main() { +} diff --git a/frame/support/test/tests/split_ui/section_not_imported.rs b/frame/support/test/tests/split_ui/section_not_imported.rs new file mode 100644 index 0000000000000..bcabf66256771 --- /dev/null +++ b/frame/support/test/tests/split_ui/section_not_imported.rs @@ -0,0 +1,34 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +use frame_support::pallet_macros::*; + +pub use pallet::*; + +#[pallet_section] +mod storages { + #[pallet::storage] + pub type MyStorageMap = StorageMap<_, _, u32, u64>; +} + +#[frame_support::pallet(dev_mode)] +pub mod pallet { + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::call] + impl Pallet { + pub fn my_call(_origin: OriginFor) -> DispatchResult { + MyStorageMap::::insert(1, 2); + Ok(()) + } + } +} + +fn main() { +} diff --git a/frame/support/test/tests/split_ui/section_not_imported.stderr b/frame/support/test/tests/split_ui/section_not_imported.stderr new file mode 100644 index 0000000000000..41ac2a5f58d25 --- /dev/null +++ b/frame/support/test/tests/split_ui/section_not_imported.stderr @@ -0,0 +1,8 @@ +error[E0433]: failed to resolve: use of undeclared type `MyStorageMap` + --> tests/split_ui/section_not_imported.rs:27:4 + | +27 | MyStorageMap::::insert(1, 2); + | ^^^^^^^^^^^^ + | | + | use of undeclared type `MyStorageMap` + | help: a struct with a similar name exists: `StorageMap`