diff --git a/frame/support/procedural/src/construct_runtime/mod.rs b/frame/support/procedural/src/construct_runtime/mod.rs index 8aacd8f0aa810..04bb2ead645d2 100644 --- a/frame/support/procedural/src/construct_runtime/mod.rs +++ b/frame/support/procedural/src/construct_runtime/mod.rs @@ -132,7 +132,7 @@ fn construct_runtime_parsed(definition: RuntimeDefinition) -> Result( } fn decl_pallet_runtime_setup( + runtime: &Ident, pallet_declarations: &[Pallet], scrate: &TokenStream2, ) -> TokenStream2 { - let names = pallet_declarations.iter().map(|d| &d.name); - let names2 = pallet_declarations.iter().map(|d| &d.name); + let names = pallet_declarations.iter().map(|d| &d.name).collect::>(); let name_strings = pallet_declarations.iter().map(|d| d.name.to_string()); + let module_names = pallet_declarations.iter().map(|d| d.path.module_name()); let indices = pallet_declarations.iter().map(|pallet| pallet.index as usize); + let pallet_structs = pallet_declarations + .iter() + .map(|pallet| { + let path = &pallet.path; + match pallet.instance.as_ref() { + Some(inst) => quote!(#path::Pallet<#runtime, #path::#inst>), + None => quote!(#path::Pallet<#runtime>), + } + }) + .collect::>(); quote!( /// Provides an implementation of `PalletInfo` to provide information @@ -264,13 +275,37 @@ fn decl_pallet_runtime_setup( fn name() -> Option<&'static str> { let type_id = #scrate::sp_std::any::TypeId::of::

(); #( - if type_id == #scrate::sp_std::any::TypeId::of::<#names2>() { + if type_id == #scrate::sp_std::any::TypeId::of::<#names>() { return Some(#name_strings) } )* None } + + fn module_name() -> Option<&'static str> { + let type_id = #scrate::sp_std::any::TypeId::of::

(); + #( + if type_id == #scrate::sp_std::any::TypeId::of::<#names>() { + return Some(#module_names) + } + )* + + None + } + + fn crate_version() -> Option<#scrate::traits::CrateVersion> { + let type_id = #scrate::sp_std::any::TypeId::of::

(); + #( + if type_id == #scrate::sp_std::any::TypeId::of::<#names>() { + return Some( + <#pallet_structs as #scrate::traits::PalletInfoAccess>::crate_version() + ) + } + )* + + None + } } ) } diff --git a/frame/support/procedural/src/construct_runtime/parse.rs b/frame/support/procedural/src/construct_runtime/parse.rs index 6f2fd82e73f4b..a0ec6dfa5803e 100644 --- a/frame/support/procedural/src/construct_runtime/parse.rs +++ b/frame/support/procedural/src/construct_runtime/parse.rs @@ -188,6 +188,18 @@ pub struct PalletPath { pub inner: Path, } +impl PalletPath { + pub fn module_name(&self) -> String { + self.inner.segments.iter().fold(String::new(), |mut acc, segment| { + if !acc.is_empty() { + acc.push_str("::"); + } + acc.push_str(&segment.ident.to_string()); + acc + }) + } +} + impl Parse for PalletPath { fn parse(input: ParseStream) -> Result { let mut lookahead = input.lookahead1(); diff --git a/frame/support/procedural/src/crate_version.rs b/frame/support/procedural/src/crate_version.rs new file mode 100644 index 0000000000000..cfa35c6190e15 --- /dev/null +++ b/frame/support/procedural/src/crate_version.rs @@ -0,0 +1,54 @@ +// This file is part of Substrate. + +// Copyright (C) 2020-2021 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. + +//! Implementation of macros related to crate versioning. + +use super::get_cargo_env_var; +use frame_support_procedural_tools::generate_crate_access_2018; +use proc_macro2::{Span, TokenStream}; +use syn::{Error, Result}; + +/// Create an error that will be shown by rustc at the call site of the macro. +fn create_error(message: &str) -> Error { + Error::new(Span::call_site(), message) +} + +/// Implementation of the `crate_to_crate_version!` macro. +pub fn crate_to_crate_version(input: proc_macro::TokenStream) -> Result { + if !input.is_empty() { + return Err(create_error("No arguments expected!")) + } + + let major_version = get_cargo_env_var::("CARGO_PKG_VERSION_MAJOR") + .map_err(|_| create_error("Major version needs to fit into `u16`"))?; + + let minor_version = get_cargo_env_var::("CARGO_PKG_VERSION_MINOR") + .map_err(|_| create_error("Minor version needs to fit into `u8`"))?; + + let patch_version = get_cargo_env_var::("CARGO_PKG_VERSION_PATCH") + .map_err(|_| create_error("Patch version needs to fit into `u8`"))?; + + let crate_ = generate_crate_access_2018("frame-support")?; + + Ok(quote::quote! { + #crate_::traits::CrateVersion { + major: #major_version, + minor: #minor_version, + patch: #patch_version, + } + }) +} diff --git a/frame/support/procedural/src/lib.rs b/frame/support/procedural/src/lib.rs index a8ac022c35c6b..6987fc49b9a8c 100644 --- a/frame/support/procedural/src/lib.rs +++ b/frame/support/procedural/src/lib.rs @@ -21,6 +21,7 @@ mod clone_no_bound; mod construct_runtime; +mod crate_version; mod debug_no_bound; mod default_no_bound; mod dummy_part_checker; @@ -31,7 +32,7 @@ mod storage; mod transactional; use proc_macro::TokenStream; -use std::cell::RefCell; +use std::{cell::RefCell, str::FromStr}; pub(crate) use storage::INHERENT_INSTANCE_NAME; thread_local! { @@ -52,6 +53,16 @@ impl Counter { } } +/// Get the value from the given environment variable set by cargo. +/// +/// The value is parsed into the requested destination type. +fn get_cargo_env_var(version_env: &str) -> std::result::Result { + let version = std::env::var(version_env) + .unwrap_or_else(|_| panic!("`{}` is always set by cargo; qed", version_env)); + + T::from_str(&version).map_err(drop) +} + /// Declares strongly-typed wrappers around codec-compatible types in storage. /// /// ## Example @@ -462,6 +473,13 @@ pub fn require_transactional(attr: TokenStream, input: TokenStream) -> TokenStre .unwrap_or_else(|e| e.to_compile_error().into()) } +#[proc_macro] +pub fn crate_to_crate_version(input: TokenStream) -> TokenStream { + crate_version::crate_to_crate_version(input) + .unwrap_or_else(|e| e.to_compile_error()) + .into() +} + /// The number of module instances supported by the runtime, starting at index 1, /// and up to `NUMBER_OF_INSTANCE`. pub(crate) const NUMBER_OF_INSTANCE: u8 = 16; diff --git a/frame/support/procedural/src/pallet/expand/pallet_struct.rs b/frame/support/procedural/src/pallet/expand/pallet_struct.rs index a217742fec55d..bedc7b54b6996 100644 --- a/frame/support/procedural/src/pallet/expand/pallet_struct.rs +++ b/frame/support/procedural/src/pallet/expand/pallet_struct.rs @@ -208,6 +208,18 @@ pub fn expand_pallet_struct(def: &mut Def) -> proc_macro2::TokenStream { .expect("Pallet is part of the runtime because pallet `Config` trait is \ implemented by the runtime") } + + fn module_name() -> &'static str { + < + ::PalletInfo as #frame_support::traits::PalletInfo + >::module_name::() + .expect("Pallet is part of the runtime because pallet `Config` trait is \ + implemented by the runtime") + } + + fn crate_version() -> #frame_support::traits::CrateVersion { + #frame_support::crate_to_crate_version!() + } } #storage_info diff --git a/frame/support/src/dispatch.rs b/frame/support/src/dispatch.rs index 2e6777fee2af2..b4e9071e361aa 100644 --- a/frame/support/src/dispatch.rs +++ b/frame/support/src/dispatch.rs @@ -2151,6 +2151,18 @@ macro_rules! decl_module { .expect("Pallet is part of the runtime because pallet `Config` trait is \ implemented by the runtime") } + + fn module_name() -> &'static str { + < + <$trait_instance as $system::Config>::PalletInfo as $crate::traits::PalletInfo + >::module_name::() + .expect("Pallet is part of the runtime because pallet `Config` trait is \ + implemented by the runtime") + } + + fn crate_version() -> $crate::traits::CrateVersion { + $crate::crate_to_crate_version!() + } } // Implement GetCallName for the Call. @@ -2529,8 +2541,8 @@ mod tests { use crate::{ metadata::*, traits::{ - Get, GetCallName, IntegrityTest, OnFinalize, OnIdle, OnInitialize, OnRuntimeUpgrade, - PalletInfo, + CrateVersion, Get, GetCallName, IntegrityTest, OnFinalize, OnIdle, OnInitialize, + OnRuntimeUpgrade, PalletInfo, }, weights::{DispatchClass, DispatchInfo, Pays, RuntimeDbWeight}, }; @@ -2631,6 +2643,22 @@ mod tests { return Some("Test") } + None + } + fn module_name() -> Option<&'static str> { + let type_id = sp_std::any::TypeId::of::

(); + if type_id == sp_std::any::TypeId::of::() { + return Some("tests") + } + + None + } + fn crate_version() -> Option { + let type_id = sp_std::any::TypeId::of::

(); + if type_id == sp_std::any::TypeId::of::() { + return Some(frame_support::crate_to_crate_version!()) + } + None } } diff --git a/frame/support/src/lib.rs b/frame/support/src/lib.rs index 459698707366d..1348ca02a7f2c 100644 --- a/frame/support/src/lib.rs +++ b/frame/support/src/lib.rs @@ -702,6 +702,21 @@ pub use frame_support_procedural::DefaultNoBound; /// ``` pub use frame_support_procedural::require_transactional; +/// Convert the current crate version into a [`CrateVersion`](crate::traits::CrateVersion). +/// +/// It uses the `CARGO_PKG_VERSION_MAJOR`, `CARGO_PKG_VERSION_MINOR` and +/// `CARGO_PKG_VERSION_PATCH` environment variables to fetch the crate version. +/// This means that the [`CrateVersion`](crate::traits::CrateVersion) +/// object will correspond to the version of the crate the macro is called in! +/// +/// # Example +/// +/// ``` +/// # use frame_support::{traits::CrateVersion, crate_to_crate_version}; +/// const Version: CrateVersion = crate_to_crate_version!(); +/// ``` +pub use frame_support_procedural::crate_to_crate_version; + /// Return Err of the expression: `return Err($expression);`. /// /// Used as `fail!(expression)`. @@ -819,6 +834,7 @@ pub mod tests { StorageHasher, }; use codec::{Codec, EncodeLike}; + use frame_support::traits::CrateVersion; use sp_io::TestExternalities; use sp_std::result; @@ -832,6 +848,12 @@ pub mod tests { fn name() -> Option<&'static str> { unimplemented!("PanicPalletInfo mustn't be triggered by tests"); } + fn module_name() -> Option<&'static str> { + unimplemented!("PanicPalletInfo mustn't be triggered by tests"); + } + fn crate_version() -> Option { + unimplemented!("PanicPalletInfo mustn't be triggered by tests"); + } } pub trait Config: 'static { diff --git a/frame/support/src/traits.rs b/frame/support/src/traits.rs index d5d0decd117eb..5ac0208dc2033 100644 --- a/frame/support/src/traits.rs +++ b/frame/support/src/traits.rs @@ -62,8 +62,8 @@ pub use randomness::Randomness; mod metadata; pub use metadata::{ - CallMetadata, GetCallMetadata, GetCallName, GetStorageVersion, PalletInfo, PalletInfoAccess, - StorageVersion, STORAGE_VERSION_STORAGE_KEY_POSTFIX, + CallMetadata, CrateVersion, GetCallMetadata, GetCallName, GetStorageVersion, PalletInfo, + PalletInfoAccess, StorageVersion, STORAGE_VERSION_STORAGE_KEY_POSTFIX, }; mod hooks; diff --git a/frame/support/src/traits/metadata.rs b/frame/support/src/traits/metadata.rs index e877f29e0a137..14b7e6d7355e2 100644 --- a/frame/support/src/traits/metadata.rs +++ b/frame/support/src/traits/metadata.rs @@ -20,7 +20,7 @@ use codec::{Decode, Encode}; use sp_runtime::RuntimeDebug; -/// Provides information about the pallet setup in the runtime. +/// Provides information about the pallet itself and its setup in the runtime. /// /// An implementor should be able to provide information about each pallet that /// is configured in `construct_runtime!`. @@ -29,16 +29,25 @@ pub trait PalletInfo { fn index() -> Option; /// Convert the given pallet `P` into its name as configured in the runtime. fn name() -> Option<&'static str>; + /// Convert the given pallet `P` into its Rust module name as used in `construct_runtime!`. + fn module_name() -> Option<&'static str>; + /// Convert the given pallet `P` into its containing crate version. + fn crate_version() -> Option; } -/// Provides information about the pallet setup in the runtime. +/// Provides information about the pallet itself and its setup in the runtime. /// -/// Access the information provided by [`PalletInfo`] for a specific pallet. +/// Declare some information and access the information provided by [`PalletInfo`] for a specific +/// pallet. pub trait PalletInfoAccess { /// Index of the pallet as configured in the runtime. fn index() -> usize; /// Name of the pallet as configured in the runtime. fn name() -> &'static str; + /// Name of the Rust module containing the pallet. + fn module_name() -> &'static str; + /// Version of the crate containing the pallet. + fn crate_version() -> CrateVersion; } /// The function and pallet name of the Call. @@ -68,6 +77,34 @@ pub trait GetCallMetadata { fn get_call_metadata(&self) -> CallMetadata; } +/// The version of a crate. +#[derive(RuntimeDebug, Eq, PartialEq, Encode, Decode, Ord, Clone, Copy, Default)] +pub struct CrateVersion { + /// The major version of the crate. + pub major: u16, + /// The minor version of the crate. + pub minor: u8, + /// The patch version of the crate. + pub patch: u8, +} + +impl CrateVersion { + pub const fn new(major: u16, minor: u8, patch: u8) -> Self { + Self { major, minor, patch } + } +} + +impl sp_std::cmp::PartialOrd for CrateVersion { + fn partial_cmp(&self, other: &Self) -> Option { + let res = self + .major + .cmp(&other.major) + .then_with(|| self.minor.cmp(&other.minor).then_with(|| self.patch.cmp(&other.patch))); + + Some(res) + } +} + /// The storage key postfix that is used to store the [`StorageVersion`] per pallet. /// /// The full storage key is built by using: diff --git a/frame/support/test/src/lib.rs b/frame/support/test/src/lib.rs index 52c0a6270d47f..073f8c9c19352 100644 --- a/frame/support/test/src/lib.rs +++ b/frame/support/test/src/lib.rs @@ -49,6 +49,12 @@ impl frame_support::traits::PalletInfo for PanicPalletInfo { fn name() -> Option<&'static str> { unimplemented!("PanicPalletInfo mustn't be triggered by tests"); } + fn module_name() -> Option<&'static str> { + unimplemented!("PanicPalletInfo mustn't be triggered by tests"); + } + fn crate_version() -> Option { + unimplemented!("PanicPalletInfo mustn't be triggered by tests"); + } } /// Provides an implementation of [`frame_support::traits::Randomness`] that should only be used in diff --git a/frame/support/test/tests/construct_runtime.rs b/frame/support/test/tests/construct_runtime.rs index 062993fe10fbb..dd5538370449d 100644 --- a/frame/support/test/tests/construct_runtime.rs +++ b/frame/support/test/tests/construct_runtime.rs @@ -21,7 +21,7 @@ #![recursion_limit = "128"] -use frame_support::traits::PalletInfo as _; +use frame_support::traits::{CrateVersion, PalletInfo as _}; use scale_info::TypeInfo; use sp_core::{sr25519, H256}; use sp_runtime::{ @@ -739,40 +739,66 @@ fn test_metadata() { fn pallet_in_runtime_is_correct() { assert_eq!(PalletInfo::index::().unwrap(), 30); assert_eq!(PalletInfo::name::().unwrap(), "System"); + assert_eq!(PalletInfo::module_name::().unwrap(), "system"); + assert_eq!(PalletInfo::crate_version::().unwrap(), CrateVersion::new(3, 0, 0)); assert_eq!(PalletInfo::index::().unwrap(), 31); assert_eq!(PalletInfo::name::().unwrap(), "Module1_1"); + assert_eq!(PalletInfo::module_name::().unwrap(), "module1"); + assert_eq!(PalletInfo::crate_version::().unwrap(), CrateVersion::new(3, 0, 0)); assert_eq!(PalletInfo::index::().unwrap(), 32); assert_eq!(PalletInfo::name::().unwrap(), "Module2"); + assert_eq!(PalletInfo::module_name::().unwrap(), "module2"); + assert_eq!(PalletInfo::crate_version::().unwrap(), CrateVersion::new(3, 0, 0)); assert_eq!(PalletInfo::index::().unwrap(), 33); assert_eq!(PalletInfo::name::().unwrap(), "Module1_2"); + assert_eq!(PalletInfo::module_name::().unwrap(), "module1"); + assert_eq!(PalletInfo::crate_version::().unwrap(), CrateVersion::new(3, 0, 0)); assert_eq!(PalletInfo::index::().unwrap(), 34); assert_eq!(PalletInfo::name::().unwrap(), "NestedModule3"); + assert_eq!(PalletInfo::module_name::().unwrap(), "nested::module3"); + assert_eq!(PalletInfo::crate_version::().unwrap(), CrateVersion::new(3, 0, 0)); assert_eq!(PalletInfo::index::().unwrap(), 35); assert_eq!(PalletInfo::name::().unwrap(), "Module3"); + assert_eq!(PalletInfo::module_name::().unwrap(), "self::module3"); + assert_eq!(PalletInfo::crate_version::().unwrap(), CrateVersion::new(3, 0, 0)); assert_eq!(PalletInfo::index::().unwrap(), 6); assert_eq!(PalletInfo::name::().unwrap(), "Module1_3"); + assert_eq!(PalletInfo::module_name::().unwrap(), "module1"); + assert_eq!(PalletInfo::crate_version::().unwrap(), CrateVersion::new(3, 0, 0)); assert_eq!(PalletInfo::index::().unwrap(), 3); assert_eq!(PalletInfo::name::().unwrap(), "Module1_4"); + assert_eq!(PalletInfo::module_name::().unwrap(), "module1"); + assert_eq!(PalletInfo::crate_version::().unwrap(), CrateVersion::new(3, 0, 0)); assert_eq!(PalletInfo::index::().unwrap(), 4); assert_eq!(PalletInfo::name::().unwrap(), "Module1_5"); + assert_eq!(PalletInfo::module_name::().unwrap(), "module1"); + assert_eq!(PalletInfo::crate_version::().unwrap(), CrateVersion::new(3, 0, 0)); assert_eq!(PalletInfo::index::().unwrap(), 1); assert_eq!(PalletInfo::name::().unwrap(), "Module1_6"); + assert_eq!(PalletInfo::module_name::().unwrap(), "module1"); + assert_eq!(PalletInfo::crate_version::().unwrap(), CrateVersion::new(3, 0, 0)); assert_eq!(PalletInfo::index::().unwrap(), 2); assert_eq!(PalletInfo::name::().unwrap(), "Module1_7"); + assert_eq!(PalletInfo::module_name::().unwrap(), "module1"); + assert_eq!(PalletInfo::crate_version::().unwrap(), CrateVersion::new(3, 0, 0)); assert_eq!(PalletInfo::index::().unwrap(), 12); assert_eq!(PalletInfo::name::().unwrap(), "Module1_8"); + assert_eq!(PalletInfo::module_name::().unwrap(), "module1"); + assert_eq!(PalletInfo::crate_version::().unwrap(), CrateVersion::new(3, 0, 0)); assert_eq!(PalletInfo::index::().unwrap(), 13); assert_eq!(PalletInfo::name::().unwrap(), "Module1_9"); + assert_eq!(PalletInfo::module_name::().unwrap(), "module1"); + assert_eq!(PalletInfo::crate_version::().unwrap(), CrateVersion::new(3, 0, 0)); } diff --git a/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr b/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr index ffbc5aeea6b4f..2b70102fdac24 100644 --- a/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr +++ b/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr @@ -4,8 +4,8 @@ error[E0277]: the trait bound `Bar: MaxEncodedLen` is not satisfied 10 | #[pallet::generate_storage_info] | ^^^^^^^^^^^^^^^^^^^^^ the trait `MaxEncodedLen` is not implemented for `Bar` | - = note: required because of the requirements on the impl of `KeyGeneratorMaxEncodedLen` for `NMapKey` - = note: required because of the requirements on the impl of `StorageInfoTrait` for `frame_support::pallet_prelude::StorageNMap<_GeneratedPrefixForStorageFoo, NMapKey, u32>` + = note: required because of the requirements on the impl of `KeyGeneratorMaxEncodedLen` for `Key` + = note: required because of the requirements on the impl of `StorageInfoTrait` for `frame_support::pallet_prelude::StorageNMap<_GeneratedPrefixForStorageFoo, Key, u32>` note: required by `storage_info` --> $DIR/storage.rs:71:2 | diff --git a/test-utils/runtime/src/lib.rs b/test-utils/runtime/src/lib.rs index 479d69c437567..943c41c247f75 100644 --- a/test-utils/runtime/src/lib.rs +++ b/test-utils/runtime/src/lib.rs @@ -36,7 +36,11 @@ use sp_trie::{ use trie_db::{Trie, TrieMut}; use cfg_if::cfg_if; -use frame_support::{parameter_types, traits::KeyOwnerProofSystem, weights::RuntimeDbWeight}; +use frame_support::{ + parameter_types, + traits::{CrateVersion, KeyOwnerProofSystem}, + weights::RuntimeDbWeight, +}; use frame_system::limits::{BlockLength, BlockWeights}; use sp_api::{decl_runtime_apis, impl_runtime_apis}; pub use sp_core::hash::H256; @@ -520,6 +524,35 @@ impl frame_support::traits::PalletInfo for Runtime { return Some("Babe") } + None + } + fn module_name() -> Option<&'static str> { + let type_id = sp_std::any::TypeId::of::

(); + if type_id == sp_std::any::TypeId::of::>() { + return Some("system") + } + if type_id == sp_std::any::TypeId::of::>() { + return Some("pallet_timestamp") + } + if type_id == sp_std::any::TypeId::of::>() { + return Some("pallet_babe") + } + + None + } + fn crate_version() -> Option { + use frame_support::traits::PalletInfoAccess as _; + let type_id = sp_std::any::TypeId::of::

(); + if type_id == sp_std::any::TypeId::of::>() { + return Some(system::Pallet::::crate_version()) + } + if type_id == sp_std::any::TypeId::of::>() { + return Some(pallet_timestamp::Pallet::::crate_version()) + } + if type_id == sp_std::any::TypeId::of::>() { + return Some(pallet_babe::Pallet::::crate_version()) + } + None } }