diff --git a/.github/workflows/check-semver.yml b/.github/workflows/check-semver.yml index 43c70d6abc78b0aca6fe0738617632a9b33b809a..df1a1c8be6038c3cbd996d1fa0dceb46aeb87b07 100644 --- a/.github/workflows/check-semver.yml +++ b/.github/workflows/check-semver.yml @@ -76,6 +76,7 @@ jobs: if: ${{ !contains(github.event.pull_request.labels.*.name, 'R0-silent') }} run: | rustup default $TOOLCHAIN + rustup target add wasm32-unknown-unknown --toolchain $TOOLCHAIN rustup component add rust-src --toolchain $TOOLCHAIN - name: install parity-publish diff --git a/Cargo.lock b/Cargo.lock index d04808c0f0896db0fc3c1575fe5a4219f9b047ff..f12dff812452c8da7d4c58a1a33ed4704681a6a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13635,6 +13635,24 @@ dependencies = [ "sp-runtime 31.0.1", ] +[[package]] +name = "pallet-example-view-functions" +version = "1.0.0" +dependencies = [ + "frame-benchmarking 28.0.0", + "frame-metadata 18.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", + "log", + "parity-scale-codec", + "pretty_assertions", + "scale-info", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-metadata-ir 0.6.0", + "sp-runtime 31.0.1", +] + [[package]] name = "pallet-examples" version = "4.0.0-dev" @@ -13649,6 +13667,7 @@ dependencies = [ "pallet-example-single-block-migrations", "pallet-example-split", "pallet-example-tasks", + "pallet-example-view-functions", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 7a906e7c0d64f1181d8227296c98862a9e4debec..36c048d77c8ec0b62e95bf7ac02277df5086738f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -363,6 +363,7 @@ members = [ "substrate/frame/examples/single-block-migrations", "substrate/frame/examples/split", "substrate/frame/examples/tasks", + "substrate/frame/examples/view-functions", "substrate/frame/executive", "substrate/frame/fast-unstake", "substrate/frame/glutton", @@ -941,6 +942,7 @@ pallet-example-offchain-worker = { path = "substrate/frame/examples/offchain-wor pallet-example-single-block-migrations = { path = "substrate/frame/examples/single-block-migrations", default-features = false } pallet-example-split = { path = "substrate/frame/examples/split", default-features = false } pallet-example-tasks = { path = "substrate/frame/examples/tasks", default-features = false } +pallet-example-view-functions = { path = "substrate/frame/examples/view-functions", default-features = false } pallet-examples = { path = "substrate/frame/examples" } pallet-fast-unstake = { path = "substrate/frame/fast-unstake", default-features = false } pallet-glutton = { path = "substrate/frame/glutton", default-features = false } diff --git a/cumulus/pallets/weight-reclaim/src/tests.rs b/cumulus/pallets/weight-reclaim/src/tests.rs index b87c107c7ec71ce8dea8b04b702f673373dfd16f..ce647445b33272b71929ad1ac51042f079e75c4e 100644 --- a/cumulus/pallets/weight-reclaim/src/tests.rs +++ b/cumulus/pallets/weight-reclaim/src/tests.rs @@ -89,7 +89,8 @@ mod runtime { RuntimeHoldReason, RuntimeSlashReason, RuntimeLockId, - RuntimeTask + RuntimeTask, + RuntimeViewFunction )] pub struct Test; diff --git a/docs/sdk/src/reference_docs/chain_spec_runtime/src/runtime.rs b/docs/sdk/src/reference_docs/chain_spec_runtime/src/runtime.rs index 282fc1ff489c0195973d712c085688bdaf77a30a..3ffa3d61263f2cc6c9204ec208b3c68cbfdc236a 100644 --- a/docs/sdk/src/reference_docs/chain_spec_runtime/src/runtime.rs +++ b/docs/sdk/src/reference_docs/chain_spec_runtime/src/runtime.rs @@ -57,7 +57,14 @@ type SignedExtra = (); mod runtime { /// The main runtime type. #[runtime::runtime] - #[runtime::derive(RuntimeCall, RuntimeEvent, RuntimeError, RuntimeOrigin, RuntimeTask)] + #[runtime::derive( + RuntimeCall, + RuntimeEvent, + RuntimeError, + RuntimeOrigin, + RuntimeTask, + RuntimeViewFunction + )] pub struct Runtime; /// Mandatory system pallet that should always be included in a FRAME runtime. diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index 935b62c23388ee1c9dd47ccad4e70e488e074b53..4d5b56bcd911ad67e9d986f7567b5a119ce72c82 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -1613,7 +1613,8 @@ mod runtime { RuntimeHoldReason, RuntimeSlashReason, RuntimeLockId, - RuntimeTask + RuntimeTask, + RuntimeViewFunction )] pub struct Runtime; @@ -1975,6 +1976,12 @@ sp_api::impl_runtime_apis! { } } + impl frame_support::view_functions::runtime_api::RuntimeViewFunction<Block> for Runtime { + fn execute_view_function(id: frame_support::view_functions::ViewFunctionId, input: Vec<u8>) -> Result<Vec<u8>, frame_support::view_functions::ViewFunctionDispatchError> { + Runtime::execute_view_function(id, input) + } + } + impl sp_block_builder::BlockBuilder<Block> for Runtime { fn apply_extrinsic(extrinsic: <Block as BlockT>::Extrinsic) -> ApplyExtrinsicResult { Executive::apply_extrinsic(extrinsic) diff --git a/prdoc/pr_4722.prdoc b/prdoc/pr_4722.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..a5bdbbeb3df9aa45868aa8cdd0156d40ae23e683 --- /dev/null +++ b/prdoc/pr_4722.prdoc @@ -0,0 +1,33 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Implement pallet view functions + +doc: + - audience: Runtime Dev + description: | + Read-only view functions can now be defined on pallets. These functions provide an interface for querying state, + from both outside and inside the runtime. Common queries can be defined on pallets, without users having to + access the storage directly. + + - audience: Runtime User + description: | + Querying the runtime state is now easier with the introduction of pallet view functions. Clients can call commonly + defined view functions rather than accessing the storage directly. These are similar to the Runtime APIs, but + are defined within the runtime itself. + +crates: + - name: frame-support + bump: minor + - name: sp-metadata-ir + bump: major + - name: frame-support-procedural + bump: patch + - name: pallet-example-view-functions + bump: patch + - name: cumulus-pov-validator + bump: none + - name: cumulus-pallet-weight-reclaim + bump: patch + - name: westend-runtime + bump: minor \ No newline at end of file diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index 220929fdfd838532a84ef5d2256c562666583672..202aee37074db7013e33a1d2e8b7e3179fdc9026 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -2459,7 +2459,8 @@ mod runtime { RuntimeHoldReason, RuntimeSlashReason, RuntimeLockId, - RuntimeTask + RuntimeTask, + RuntimeViewFunction )] pub struct Runtime; @@ -3013,6 +3014,12 @@ impl_runtime_apis! { } } + impl frame_support::view_functions::runtime_api::RuntimeViewFunction<Block> for Runtime { + fn execute_view_function(id: frame_support::view_functions::ViewFunctionId, input: Vec<u8>) -> Result<Vec<u8>, frame_support::view_functions::ViewFunctionDispatchError> { + Runtime::execute_view_function(id, input) + } + } + impl sp_block_builder::BlockBuilder<Block> for Runtime { fn apply_extrinsic(extrinsic: <Block as BlockT>::Extrinsic) -> ApplyExtrinsicResult { Executive::apply_extrinsic(extrinsic) diff --git a/substrate/frame/examples/Cargo.toml b/substrate/frame/examples/Cargo.toml index 9eac53f0d98b064c3b271357db6c0bf6b4f3a005..40d6959378b8799e3b1e6e655373bf4e5409652e 100644 --- a/substrate/frame/examples/Cargo.toml +++ b/substrate/frame/examples/Cargo.toml @@ -26,6 +26,7 @@ pallet-example-offchain-worker = { workspace = true } pallet-example-single-block-migrations = { workspace = true } pallet-example-split = { workspace = true } pallet-example-tasks = { workspace = true } +pallet-example-view-functions = { workspace = true } [features] default = ["std"] @@ -40,6 +41,7 @@ std = [ "pallet-example-single-block-migrations/std", "pallet-example-split/std", "pallet-example-tasks/std", + "pallet-example-view-functions/std", ] try-runtime = [ "pallet-default-config-example/try-runtime", @@ -51,4 +53,5 @@ try-runtime = [ "pallet-example-single-block-migrations/try-runtime", "pallet-example-split/try-runtime", "pallet-example-tasks/try-runtime", + "pallet-example-view-functions/try-runtime", ] diff --git a/substrate/frame/examples/src/lib.rs b/substrate/frame/examples/src/lib.rs index d0d30830f2f04d1bfd4c4afa5a39be91ee549a54..200e92112a3f3b4ba9552a8de68401fc290469e4 100644 --- a/substrate/frame/examples/src/lib.rs +++ b/substrate/frame/examples/src/lib.rs @@ -48,6 +48,9 @@ //! //! - [`pallet_example_tasks`]: This pallet demonstrates the use of `Tasks` to execute service work. //! +//! - [`pallet_example_view_functions`]: This pallet demonstrates the use of view functions to query +//! pallet state. +//! //! - [`pallet_example_authorization_tx_extension`]: An example `TransactionExtension` that //! authorizes a custom origin through signature validation, along with two support pallets to //! showcase the usage. diff --git a/substrate/frame/examples/view-functions/Cargo.toml b/substrate/frame/examples/view-functions/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..54d8322643917151ec8cf4be9aced7b758afadf2 --- /dev/null +++ b/substrate/frame/examples/view-functions/Cargo.toml @@ -0,0 +1,61 @@ +[package] +name = "pallet-example-view-functions" +version = "1.0.0" +authors.workspace = true +edition.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +description = "Pallet to demonstrate the usage of view functions to query pallet state" + +[lints] +workspace = true + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.6.12", default-features = false, workspace = true } +frame-metadata = { features = ["current"], workspace = true } +log = { workspace = true } +scale-info = { version = "2.11.1", default-features = false, features = ["derive"], workspace = true } + +frame-support = { path = "../../support", default-features = false, workspace = true } +frame-system = { path = "../../system", default-features = false, workspace = true } + +sp-core = { default-features = false, path = "../../../primitives/core", workspace = true } +sp-io = { path = "../../../primitives/io", default-features = false, workspace = true } +sp-metadata-ir = { path = "../../../primitives/metadata-ir", default-features = false, workspace = true } +sp-runtime = { path = "../../../primitives/runtime", default-features = false, workspace = true } + +frame-benchmarking = { path = "../../benchmarking", default-features = false, optional = true, workspace = true } + +[dev-dependencies] +pretty_assertions = { version = "1.3.0" } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-benchmarking?/std", + "frame-metadata/std", + "frame-support/std", + "frame-system/std", + "log/std", + "scale-info/std", + "sp-core/std", + "sp-io/std", + "sp-metadata-ir/std", + "sp-runtime/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/substrate/frame/examples/view-functions/src/lib.rs b/substrate/frame/examples/view-functions/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..e842a718ad33442b1f83a88963d637b2e6ca77bf --- /dev/null +++ b/substrate/frame/examples/view-functions/src/lib.rs @@ -0,0 +1,114 @@ +// 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::view_functions_experimental` api for service +//! work. +#![cfg_attr(not(feature = "std"), no_std)] + +pub mod tests; + +use frame_support::Parameter; +use scale_info::TypeInfo; + +pub struct SomeType1; +impl From<SomeType1> for u64 { + fn from(_t: SomeType1) -> Self { + 0u64 + } +} + +pub trait SomeAssociation1 { + type _1: Parameter + codec::MaxEncodedLen + TypeInfo; +} +impl SomeAssociation1 for u64 { + type _1 = u64; +} + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::pallet_prelude::*; + + #[pallet::error] + pub enum Error<T> {} + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet<T>(_); + + #[pallet::storage] + pub type SomeValue<T: Config> = StorageValue<_, u32>; + + #[pallet::storage] + pub type SomeMap<T: Config> = StorageMap<_, Twox64Concat, u32, u32, OptionQuery>; + + #[pallet::view_functions_experimental] + impl<T: Config> Pallet<T> + where + T::AccountId: From<SomeType1> + SomeAssociation1, + { + /// Query value no args. + pub fn get_value() -> Option<u32> { + SomeValue::<T>::get() + } + + /// Query value with args. + pub fn get_value_with_arg(key: u32) -> Option<u32> { + SomeMap::<T>::get(key) + } + } +} + +#[frame_support::pallet] +pub mod pallet2 { + use super::*; + use frame_support::pallet_prelude::*; + + #[pallet::error] + pub enum Error<T, I = ()> {} + + #[pallet::config] + pub trait Config<I: 'static = ()>: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet<T, I = ()>(PhantomData<(T, I)>); + + #[pallet::storage] + pub type SomeValue<T: Config<I>, I: 'static = ()> = StorageValue<_, u32>; + + #[pallet::storage] + pub type SomeMap<T: Config<I>, I: 'static = ()> = + StorageMap<_, Twox64Concat, u32, u32, OptionQuery>; + + #[pallet::view_functions_experimental] + impl<T: Config<I>, I: 'static> Pallet<T, I> + where + T::AccountId: From<SomeType1> + SomeAssociation1, + { + /// Query value no args. + pub fn get_value() -> Option<u32> { + SomeValue::<T, I>::get() + } + + /// Query value with args. + pub fn get_value_with_arg(key: u32) -> Option<u32> { + SomeMap::<T, I>::get(key) + } + } +} diff --git a/substrate/frame/examples/view-functions/src/tests.rs b/substrate/frame/examples/view-functions/src/tests.rs new file mode 100644 index 0000000000000000000000000000000000000000..25f5f094651d6a085c041702118a8e69a5f7e4c1 --- /dev/null +++ b/substrate/frame/examples/view-functions/src/tests.rs @@ -0,0 +1,188 @@ +// 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-view-functions`. +#![cfg(test)] + +use crate::{ + pallet::{self, Pallet}, + pallet2, +}; +use codec::{Decode, Encode}; +use scale_info::{form::PortableForm, meta_type}; + +use frame_support::{derive_impl, pallet_prelude::PalletInfoAccess, view_functions::ViewFunction}; +use sp_io::hashing::twox_128; +use sp_metadata_ir::{ViewFunctionArgMetadataIR, ViewFunctionGroupIR, ViewFunctionMetadataIR}; +use sp_runtime::testing::TestXt; + +pub type AccountId = u32; +pub type Balance = u32; + +type Block = frame_system::mocking::MockBlock<Runtime>; +frame_support::construct_runtime!( + pub enum Runtime { + System: frame_system, + ViewFunctionsExample: pallet, + ViewFunctionsInstance: pallet2, + ViewFunctionsInstance1: pallet2::<Instance1>, + } +); + +pub type Extrinsic = TestXt<RuntimeCall, ()>; + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for Runtime { + type Block = Block; +} + +impl pallet::Config for Runtime {} +impl pallet2::Config<pallet2::Instance1> for Runtime {} + +impl pallet2::Config for Runtime {} + +pub fn new_test_ext() -> sp_io::TestExternalities { + use sp_runtime::BuildStorage; + + let t = RuntimeGenesisConfig { system: Default::default() }.build_storage().unwrap(); + t.into() +} + +#[test] +fn pallet_get_value_query() { + new_test_ext().execute_with(|| { + let some_value = Some(99); + pallet::SomeValue::<Runtime>::set(some_value); + assert_eq!(some_value, Pallet::<Runtime>::get_value()); + + let query = pallet::GetValueViewFunction::<Runtime>::new(); + test_dispatch_view_function(&query, some_value); + }); +} + +#[test] +fn pallet_get_value_with_arg_query() { + new_test_ext().execute_with(|| { + let some_key = 1u32; + let some_value = Some(123); + pallet::SomeMap::<Runtime>::set(some_key, some_value); + assert_eq!(some_value, Pallet::<Runtime>::get_value_with_arg(some_key)); + + let query = pallet::GetValueWithArgViewFunction::<Runtime>::new(some_key); + test_dispatch_view_function(&query, some_value); + }); +} + +#[test] +fn pallet_multiple_instances() { + use pallet2::Instance1; + + new_test_ext().execute_with(|| { + let instance_value = Some(123); + let instance1_value = Some(456); + + pallet2::SomeValue::<Runtime>::set(instance_value); + pallet2::SomeValue::<Runtime, Instance1>::set(instance1_value); + + let query = pallet2::GetValueViewFunction::<Runtime>::new(); + test_dispatch_view_function(&query, instance_value); + + let query_instance1 = pallet2::GetValueViewFunction::<Runtime, Instance1>::new(); + test_dispatch_view_function(&query_instance1, instance1_value); + }); +} + +#[test] +fn metadata_ir_definitions() { + new_test_ext().execute_with(|| { + let metadata_ir = Runtime::metadata_ir(); + let pallet1 = metadata_ir + .view_functions + .groups + .iter() + .find(|pallet| pallet.name == "ViewFunctionsExample") + .unwrap(); + + fn view_fn_id(preifx_hash: [u8; 16], view_fn_signature: &str) -> [u8; 32] { + let mut id = [0u8; 32]; + id[..16].copy_from_slice(&preifx_hash); + id[16..].copy_from_slice(&twox_128(view_fn_signature.as_bytes())); + id + } + + let get_value_id = view_fn_id( + <ViewFunctionsExample as PalletInfoAccess>::name_hash(), + "get_value() -> Option<u32>", + ); + + let get_value_with_arg_id = view_fn_id( + <ViewFunctionsExample as PalletInfoAccess>::name_hash(), + "get_value_with_arg(u32) -> Option<u32>", + ); + + pretty_assertions::assert_eq!( + pallet1.view_functions, + vec![ + ViewFunctionMetadataIR { + name: "get_value", + id: get_value_id, + args: vec![], + output: meta_type::<Option<u32>>(), + docs: vec![" Query value no args."], + }, + ViewFunctionMetadataIR { + name: "get_value_with_arg", + id: get_value_with_arg_id, + args: vec![ViewFunctionArgMetadataIR { name: "key", ty: meta_type::<u32>() },], + output: meta_type::<Option<u32>>(), + docs: vec![" Query value with args."], + }, + ] + ); + }); +} + +#[test] +fn metadata_encoded_to_custom_value() { + new_test_ext().execute_with(|| { + let metadata = sp_metadata_ir::into_latest(Runtime::metadata_ir()); + // metadata is currently experimental so lives as a custom value. + let frame_metadata::RuntimeMetadata::V15(v15) = metadata.1 else { + panic!("Expected metadata v15") + }; + let custom_value = v15 + .custom + .map + .get("view_functions_experimental") + .expect("Expected custom value"); + let view_function_groups: Vec<ViewFunctionGroupIR<PortableForm>> = + Decode::decode(&mut &custom_value.value[..]).unwrap(); + assert_eq!(view_function_groups.len(), 4); + }); +} + +fn test_dispatch_view_function<Q, V>(query: &Q, expected: V) +where + Q: ViewFunction + Encode, + V: Decode + Eq + PartialEq + std::fmt::Debug, +{ + let input = query.encode(); + let output = Runtime::execute_view_function(Q::id(), input).unwrap(); + let query_result = V::decode(&mut &output[..]).unwrap(); + + assert_eq!(expected, query_result,); +} diff --git a/substrate/frame/support/procedural/examples/proc_main/main.rs b/substrate/frame/support/procedural/examples/proc_main/main.rs index 4bdfc76dd92f0821865fbe7a74fbf0bea77f35e3..946bd5ff03ed298824ec7edd0ba31c8885d5df0f 100644 --- a/substrate/frame/support/procedural/examples/proc_main/main.rs +++ b/substrate/frame/support/procedural/examples/proc_main/main.rs @@ -234,7 +234,8 @@ mod runtime { RuntimeHoldReason, RuntimeSlashReason, RuntimeLockId, - RuntimeTask + RuntimeTask, + RuntimeViewFunction )] pub struct Runtime; diff --git a/substrate/frame/support/procedural/examples/proc_main/runtime.rs b/substrate/frame/support/procedural/examples/proc_main/runtime.rs index 109ca4f6dc488228cc596c2d3ff2447aa4cacff4..8de560555895bf64b3e21d8c0e892b77d6e77d4a 100644 --- a/substrate/frame/support/procedural/examples/proc_main/runtime.rs +++ b/substrate/frame/support/procedural/examples/proc_main/runtime.rs @@ -99,7 +99,8 @@ mod runtime { RuntimeHoldReason, RuntimeSlashReason, RuntimeLockId, - RuntimeTask + RuntimeTask, + RuntimeViewFunction )] pub struct Runtime; diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/call.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/call.rs index f055e8ce28e904639dba11f9f7639bf64c934d95..411d74ecbb3d23c84e5b62c14e10a1b047e49248 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/expand/call.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/expand/call.rs @@ -18,7 +18,6 @@ use crate::construct_runtime::Pallet; use proc_macro2::TokenStream; use quote::quote; -use std::str::FromStr; use syn::Ident; pub fn expand_outer_dispatch( @@ -40,15 +39,7 @@ pub fn expand_outer_dispatch( let name = &pallet_declaration.name; let path = &pallet_declaration.path; let index = pallet_declaration.index; - let attr = - pallet_declaration.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| { - let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original())) - .expect("was successfully parsed before; qed"); - quote! { - #acc - #attr - } - }); + let attr = pallet_declaration.get_attributes(); variant_defs.extend(quote! { #attr diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/config.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/config.rs index dbbe6ba6e6c32ec09a8514033108617883075243..7a51ba6ecf1da9c9f9af5abe83f85fee59323995 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/expand/config.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/expand/config.rs @@ -19,7 +19,6 @@ use crate::construct_runtime::Pallet; use inflector::Inflector; use proc_macro2::TokenStream; use quote::{format_ident, quote, ToTokens}; -use std::str::FromStr; use syn::Ident; pub fn expand_outer_config( @@ -41,14 +40,7 @@ pub fn expand_outer_config( let field_name = &Ident::new(&pallet_name.to_string().to_snake_case(), decl.name.span()); let part_is_generic = !pallet_entry.generics.params.is_empty(); - let attr = &decl.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| { - let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original())) - .expect("was successfully parsed before; qed"); - quote! { - #acc - #attr - } - }); + let attr = &decl.get_attributes(); types.extend(expand_config_types(attr, runtime, decl, &config, part_is_generic)); fields.extend(quote!(#attr pub #field_name: #config,)); diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/inherent.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/inherent.rs index e34c6ac5016a9f8597fae03f6012f1e7d2f79659..e25492802c3293d02321b2b1fcc0f26aabe8dce4 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/expand/inherent.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/expand/inherent.rs @@ -18,7 +18,6 @@ use crate::construct_runtime::Pallet; use proc_macro2::TokenStream; use quote::quote; -use std::str::FromStr; use syn::Ident; pub fn expand_outer_inherent( @@ -36,14 +35,7 @@ pub fn expand_outer_inherent( if pallet_decl.exists_part("Inherent") { let name = &pallet_decl.name; let path = &pallet_decl.path; - let attr = pallet_decl.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| { - let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original())) - .expect("was successfully parsed before; qed"); - quote! { - #acc - #attr - } - }); + let attr = pallet_decl.get_attributes(); pallet_names.push(name); pallet_attrs.push(attr); diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/metadata.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/metadata.rs index 0b3bd516886513faee90ea367aaf0ed86593e9a3..d246c00628640d8dfd27a133a2c06637b3953eb8 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/expand/metadata.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/expand/metadata.rs @@ -18,7 +18,6 @@ use crate::construct_runtime::{parse::PalletPath, Pallet}; use proc_macro2::TokenStream; use quote::quote; -use std::str::FromStr; use syn::Ident; pub fn expand_runtime_metadata( @@ -51,14 +50,7 @@ pub fn expand_runtime_metadata( let errors = expand_pallet_metadata_errors(runtime, decl); let associated_types = expand_pallet_metadata_associated_types(runtime, decl); let docs = expand_pallet_metadata_docs(runtime, decl); - let attr = decl.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| { - let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original())) - .expect("was successfully parsed before; qed"); - quote! { - #acc - #attr - } - }); + let attr = decl.get_attributes(); let deprecation_info = expand_pallet_metadata_deprecation(runtime, decl); quote! { #attr @@ -78,6 +70,20 @@ pub fn expand_runtime_metadata( }) .collect::<Vec<_>>(); + let view_functions = pallet_declarations.iter().map(|decl| { + let name = &decl.name; + let path = &decl.path; + let instance = decl.instance.as_ref().into_iter(); + let attr = decl.get_attributes(); + + quote! { + #attr + #path::Pallet::<#runtime #(, #path::#instance)*>::pallet_view_functions_metadata( + ::core::stringify!(#name) + ) + } + }); + quote! { impl #runtime { fn metadata_ir() -> #scrate::__private::metadata_ir::MetadataIR { @@ -149,6 +155,10 @@ pub fn expand_runtime_metadata( >(), event_enum_ty: #scrate::__private::scale_info::meta_type::<RuntimeEvent>(), error_enum_ty: #scrate::__private::scale_info::meta_type::<RuntimeError>(), + }, + view_functions: #scrate::__private::metadata_ir::RuntimeViewFunctionsIR { + ty: #scrate::__private::scale_info::meta_type::<RuntimeViewFunction>(), + groups: #scrate::__private::sp_std::vec![ #(#view_functions),* ], } } } diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/mod.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/mod.rs index 88f9a3c6e33fd3fc99b2f4e511d5a6c0afd9263a..823aa69dbdf2b1a4d725f8b33c28a231a1699977 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/expand/mod.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/expand/mod.rs @@ -28,6 +28,7 @@ mod outer_enums; mod slash_reason; mod task; mod unsigned; +mod view_function; pub use call::expand_outer_dispatch; pub use config::expand_outer_config; @@ -41,3 +42,4 @@ 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; +pub use view_function::expand_outer_query; diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/origin.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/origin.rs index 1c4ab436ad92aaee824a7f2916c32483fe443f6f..4742e68e2e2a8d86c14a27e038868e25892557e0 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/expand/origin.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/expand/origin.rs @@ -18,7 +18,6 @@ use crate::construct_runtime::{Pallet, SYSTEM_PALLET_NAME}; use proc_macro2::TokenStream; use quote::quote; -use std::str::FromStr; use syn::{Generics, Ident}; pub fn expand_outer_origin( @@ -335,14 +334,7 @@ fn expand_origin_caller_variant( let part_is_generic = !generics.params.is_empty(); let variant_name = &pallet.name; let path = &pallet.path; - let attr = pallet.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| { - let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original())) - .expect("was successfully parsed before; qed"); - quote! { - #acc - #attr - } - }); + let attr = pallet.get_attributes(); match instance { Some(inst) if part_is_generic => quote! { @@ -387,14 +379,7 @@ fn expand_origin_pallet_conversions( }; let doc_string = get_intra_doc_string(" Convert to runtime origin using", &path.module_name()); - let attr = pallet.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| { - let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original())) - .expect("was successfully parsed before; qed"); - quote! { - #acc - #attr - } - }); + let attr = pallet.get_attributes(); quote! { #attr diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/outer_enums.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/outer_enums.rs index 80b242ccbe493607a59e672b7f0958fc9d0a213c..80d3a5af26627f9cdcdb58cd1ef6645c523dbf86 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/expand/outer_enums.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/expand/outer_enums.rs @@ -18,7 +18,6 @@ use crate::construct_runtime::Pallet; use proc_macro2::{Span, TokenStream}; use quote::{quote, ToTokens}; -use std::str::FromStr; use syn::{Generics, Ident}; /// Represents the types supported for creating an outer enum. @@ -185,14 +184,7 @@ fn expand_enum_variant( let path = &pallet.path; let variant_name = &pallet.name; let part_is_generic = !generics.params.is_empty(); - let attr = pallet.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| { - let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original())) - .expect("was successfully parsed before; qed"); - quote! { - #acc - #attr - } - }); + let attr = pallet.get_attributes(); match instance { Some(inst) if part_is_generic => quote! { @@ -224,14 +216,7 @@ fn expand_enum_conversion( enum_name_ident: &Ident, ) -> TokenStream { let variant_name = &pallet.name; - let attr = pallet.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| { - let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original())) - .expect("was successfully parsed before; qed"); - quote! { - #acc - #attr - } - }); + let attr = pallet.get_attributes(); quote! { #attr diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/task.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/task.rs index 1302f86455f2ceeb7af58e46e30b05afccbc44cf..b9b8efb8c006364dc48e25926c158664066a9d65 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/expand/task.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/expand/task.rs @@ -16,7 +16,6 @@ // limitations under the License use crate::construct_runtime::Pallet; -use core::str::FromStr; use proc_macro2::{Ident, TokenStream as TokenStream2}; use quote::quote; @@ -42,14 +41,7 @@ pub fn expand_outer_task( let instance = decl.instance.as_ref().map(|instance| quote!(, #path::#instance)); let task_type = quote!(#path::Task<#runtime_name #instance>); - let attr = decl.cfg_pattern.iter().fold(TokenStream2::new(), |acc, pattern| { - let attr = TokenStream2::from_str(&format!("#[cfg({})]", pattern.original())) - .expect("was successfully parsed before; qed"); - quote! { - #acc - #attr - } - }); + let attr = decl.get_attributes(); from_impls.push(quote! { #attr diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/unsigned.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/unsigned.rs index 33aadba0d1f1c522aae13fb647b9fe8a918d8e24..737a39ea681e0d1c09c458c4b59229f5159136d4 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/expand/unsigned.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/expand/unsigned.rs @@ -18,7 +18,6 @@ use crate::construct_runtime::Pallet; use proc_macro2::TokenStream; use quote::quote; -use std::str::FromStr; use syn::Ident; pub fn expand_outer_validate_unsigned( @@ -34,14 +33,7 @@ pub fn expand_outer_validate_unsigned( if pallet_decl.exists_part("ValidateUnsigned") { let name = &pallet_decl.name; let path = &pallet_decl.path; - let attr = pallet_decl.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| { - let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original())) - .expect("was successfully parsed before; qed"); - quote! { - #acc - #attr - } - }); + let attr = pallet_decl.get_attributes(); pallet_names.push(name); pallet_attrs.push(attr); diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/view_function.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/view_function.rs new file mode 100644 index 0000000000000000000000000000000000000000..094dcca4a5b5249cfe3026790d9938a84f5649fe --- /dev/null +++ b/substrate/frame/support/procedural/src/construct_runtime/expand/view_function.rs @@ -0,0 +1,78 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License + +use crate::construct_runtime::Pallet; +use proc_macro2::{Ident, Span, TokenStream as TokenStream2}; + +/// Expands implementation of runtime level `DispatchViewFunction`. +pub fn expand_outer_query( + runtime_name: &Ident, + pallet_decls: &[Pallet], + scrate: &TokenStream2, +) -> TokenStream2 { + let runtime_view_function = syn::Ident::new("RuntimeViewFunction", Span::call_site()); + + let prefix_conditionals = pallet_decls.iter().map(|pallet| { + let pallet_name = &pallet.name; + let attr = pallet.get_attributes(); + quote::quote! { + #attr + if id.prefix == <#pallet_name as #scrate::view_functions::ViewFunctionIdPrefix>::prefix() { + return <#pallet_name as #scrate::view_functions::DispatchViewFunction>::dispatch_view_function(id, input, output) + } + } + }); + + quote::quote! { + /// Runtime query type. + #[derive( + Clone, PartialEq, Eq, + #scrate::__private::codec::Encode, + #scrate::__private::codec::Decode, + #scrate::__private::scale_info::TypeInfo, + #scrate::__private::RuntimeDebug, + )] + pub enum #runtime_view_function {} + + const _: () = { + impl #scrate::view_functions::DispatchViewFunction for #runtime_view_function { + fn dispatch_view_function<O: #scrate::__private::codec::Output>( + id: & #scrate::view_functions::ViewFunctionId, + input: &mut &[u8], + output: &mut O + ) -> Result<(), #scrate::view_functions::ViewFunctionDispatchError> + { + #( #prefix_conditionals )* + Err(#scrate::view_functions::ViewFunctionDispatchError::NotFound(id.clone())) + } + } + + impl #runtime_name { + /// Convenience function for query execution from the runtime API. + pub fn execute_view_function( + id: #scrate::view_functions::ViewFunctionId, + input: #scrate::__private::Vec<::core::primitive::u8>, + ) -> Result<#scrate::__private::Vec<::core::primitive::u8>, #scrate::view_functions::ViewFunctionDispatchError> + { + let mut output = #scrate::__private::vec![]; + <#runtime_view_function as #scrate::view_functions::DispatchViewFunction>::dispatch_view_function(&id, &mut &input[..], &mut output)?; + Ok(output) + } + } + }; + } +} diff --git a/substrate/frame/support/procedural/src/construct_runtime/mod.rs b/substrate/frame/support/procedural/src/construct_runtime/mod.rs index 087faf37252de9fc32f8b1ca691c32d52e612982..c6018e048f2f8c15acdc5264a52c35df52c6aa7a 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/mod.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/mod.rs @@ -400,6 +400,7 @@ fn construct_runtime_final_expansion( let dispatch = expand::expand_outer_dispatch(&name, system_pallet, &pallets, &scrate); let tasks = expand::expand_outer_task(&name, &pallets, &scrate); + let query = expand::expand_outer_query(&name, &pallets, &scrate); let metadata = expand::expand_runtime_metadata( &name, &pallets, @@ -492,6 +493,8 @@ fn construct_runtime_final_expansion( #tasks + #query + #metadata #outer_config @@ -650,16 +653,7 @@ pub(crate) fn decl_pallet_runtime_setup( .collect::<Vec<_>>(); let pallet_attrs = pallet_declarations .iter() - .map(|pallet| { - pallet.cfg_pattern.iter().fold(TokenStream2::new(), |acc, pattern| { - let attr = TokenStream2::from_str(&format!("#[cfg({})]", pattern.original())) - .expect("was successfully parsed before; qed"); - quote! { - #acc - #attr - } - }) - }) + .map(|pallet| pallet.get_attributes()) .collect::<Vec<_>>(); quote!( diff --git a/substrate/frame/support/procedural/src/construct_runtime/parse.rs b/substrate/frame/support/procedural/src/construct_runtime/parse.rs index 729a803a302ed7451f890ab6a81b666379d68d07..2df08123821a3972d5053aeb0ce9e6a7e3477a79 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/parse.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/parse.rs @@ -15,6 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use core::str::FromStr; use frame_support_procedural_tools::syn_ext as ext; use proc_macro2::{Span, TokenStream}; use quote::ToTokens; @@ -609,6 +610,18 @@ impl Pallet { pub fn exists_part(&self, name: &str) -> bool { self.find_part(name).is_some() } + + // Get runtime attributes for the pallet, mostly used for macros + pub fn get_attributes(&self) -> TokenStream { + self.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| { + let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original())) + .expect("was successfully parsed before; qed"); + quote::quote! { + #acc + #attr + } + }) + } } /// Result of a conversion of a declaration of pallets. diff --git a/substrate/frame/support/procedural/src/lib.rs b/substrate/frame/support/procedural/src/lib.rs index c2f546d92048ac2bc259138b27ac5820227fefd6..26703a2438ef9ad368c3718bc497c9563bb407fe 100644 --- a/substrate/frame/support/procedural/src/lib.rs +++ b/substrate/frame/support/procedural/src/lib.rs @@ -817,6 +817,7 @@ pub fn inject_runtime_type(_: TokenStream, tokens: TokenStream) -> TokenStream { if item.ident != "RuntimeCall" && item.ident != "RuntimeEvent" && item.ident != "RuntimeTask" && + item.ident != "RuntimeViewFunction" && item.ident != "RuntimeOrigin" && item.ident != "RuntimeHoldReason" && item.ident != "RuntimeFreezeReason" && @@ -826,7 +827,7 @@ 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`, \ - `RuntimeTask`, `RuntimeOrigin`, `RuntimeParameters` or `PalletInfo`", + `RuntimeTask`, `RuntimeViewFunction`, `RuntimeOrigin`, `RuntimeParameters` or `PalletInfo`", ) .to_compile_error() .into(); diff --git a/substrate/frame/support/procedural/src/pallet/expand/mod.rs b/substrate/frame/support/procedural/src/pallet/expand/mod.rs index 3f9b50f79c0ccf8ea37e23cd79d1bd1aead2064f..439ec55e269d43aaab8ec91296c48df970ca6985 100644 --- a/substrate/frame/support/procedural/src/pallet/expand/mod.rs +++ b/substrate/frame/support/procedural/src/pallet/expand/mod.rs @@ -35,6 +35,7 @@ mod tasks; mod tt_default_parts; mod type_value; mod validate_unsigned; +mod view_functions; mod warnings; use crate::pallet::Def; @@ -66,6 +67,7 @@ pub fn expand(mut def: Def) -> proc_macro2::TokenStream { let error = error::expand_error(&mut def); let event = event::expand_event(&mut def); let storages = storage::expand_storages(&mut def); + let view_functions = view_functions::expand_view_functions(&def); let inherents = inherent::expand_inherents(&mut def); let instances = instances::expand_instances(&mut def); let hooks = hooks::expand_hooks(&mut def); @@ -108,6 +110,7 @@ storage item. Otherwise, all storage items are listed among [*Type Definitions*] #error #event #storages + #view_functions #inherents #instances #hooks diff --git a/substrate/frame/support/procedural/src/pallet/expand/view_functions.rs b/substrate/frame/support/procedural/src/pallet/expand/view_functions.rs new file mode 100644 index 0000000000000000000000000000000000000000..587e74a2ac182f2dc817db9bb6058e2c93611bc2 --- /dev/null +++ b/substrate/frame/support/procedural/src/pallet/expand/view_functions.rs @@ -0,0 +1,263 @@ +// 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::pallet::{parse::view_functions::ViewFunctionDef, Def}; +use proc_macro2::{Span, TokenStream}; +use syn::spanned::Spanned; + +pub fn expand_view_functions(def: &Def) -> TokenStream { + let (span, where_clause, view_fns, docs) = match def.view_functions.as_ref() { + Some(view_fns) => ( + view_fns.attr_span, + view_fns.where_clause.clone(), + view_fns.view_functions.clone(), + view_fns.docs.clone(), + ), + None => (def.item.span(), def.config.where_clause.clone(), Vec::new(), Vec::new()), + }; + + let view_function_prefix_impl = + expand_view_function_prefix_impl(def, span, where_clause.as_ref()); + + let view_fn_impls = view_fns + .iter() + .map(|view_fn| expand_view_function(def, span, where_clause.as_ref(), view_fn)); + let impl_dispatch_view_function = + impl_dispatch_view_function(def, span, where_clause.as_ref(), &view_fns); + let impl_view_function_metadata = + impl_view_function_metadata(def, span, where_clause.as_ref(), &view_fns, &docs); + + quote::quote! { + #view_function_prefix_impl + #( #view_fn_impls )* + #impl_dispatch_view_function + #impl_view_function_metadata + } +} + +fn expand_view_function_prefix_impl( + def: &Def, + span: Span, + where_clause: Option<&syn::WhereClause>, +) -> TokenStream { + let pallet_ident = &def.pallet_struct.pallet; + let frame_support = &def.frame_support; + let frame_system = &def.frame_system; + let type_impl_gen = &def.type_impl_generics(span); + let type_use_gen = &def.type_use_generics(span); + + quote::quote! { + impl<#type_impl_gen> #frame_support::view_functions::ViewFunctionIdPrefix for #pallet_ident<#type_use_gen> #where_clause { + fn prefix() -> [::core::primitive::u8; 16usize] { + < + <T as #frame_system::Config>::PalletInfo + as #frame_support::traits::PalletInfo + >::name_hash::<Pallet<#type_use_gen>>() + .expect("No name_hash found for the pallet in the runtime! This usually means that the pallet wasn't added to `construct_runtime!`.") + } + } + } +} + +fn expand_view_function( + def: &Def, + span: Span, + where_clause: Option<&syn::WhereClause>, + view_fn: &ViewFunctionDef, +) -> TokenStream { + let frame_support = &def.frame_support; + let pallet_ident = &def.pallet_struct.pallet; + let type_impl_gen = &def.type_impl_generics(span); + let type_decl_bounded_gen = &def.type_decl_bounded_generics(span); + let type_use_gen = &def.type_use_generics(span); + let capture_docs = if cfg!(feature = "no-metadata-docs") { "never" } else { "always" }; + + let view_function_struct_ident = view_fn.view_function_struct_ident(); + let view_fn_name = &view_fn.name; + let (arg_names, arg_types) = match view_fn.args_names_types() { + Ok((arg_names, arg_types)) => (arg_names, arg_types), + Err(e) => return e.into_compile_error(), + }; + let return_type = &view_fn.return_type; + let docs = &view_fn.docs; + + let view_function_id_suffix_bytes_raw = match view_fn.view_function_id_suffix_bytes() { + Ok(view_function_id_suffix_bytes_raw) => view_function_id_suffix_bytes_raw, + Err(e) => return e.into_compile_error(), + }; + let view_function_id_suffix_bytes = view_function_id_suffix_bytes_raw + .map(|byte| syn::LitInt::new(&format!("0x{:X}_u8", byte), Span::call_site())); + + quote::quote! { + #( #[doc = #docs] )* + #[allow(missing_docs)] + #[derive( + #frame_support::RuntimeDebugNoBound, + #frame_support::CloneNoBound, + #frame_support::EqNoBound, + #frame_support::PartialEqNoBound, + #frame_support::__private::codec::Encode, + #frame_support::__private::codec::Decode, + #frame_support::__private::scale_info::TypeInfo, + )] + #[codec(encode_bound())] + #[codec(decode_bound())] + #[scale_info(skip_type_params(#type_use_gen), capture_docs = #capture_docs)] + pub struct #view_function_struct_ident<#type_decl_bounded_gen> #where_clause { + #( + pub #arg_names: #arg_types, + )* + _marker: ::core::marker::PhantomData<(#type_use_gen,)>, + } + + impl<#type_impl_gen> #view_function_struct_ident<#type_use_gen> #where_clause { + /// Create a new [`#view_function_struct_ident`] instance. + pub fn new(#( #arg_names: #arg_types, )*) -> Self { + Self { + #( #arg_names, )* + _marker: ::core::default::Default::default() + } + } + } + + impl<#type_impl_gen> #frame_support::view_functions::ViewFunctionIdSuffix for #view_function_struct_ident<#type_use_gen> #where_clause { + const SUFFIX: [::core::primitive::u8; 16usize] = [ #( #view_function_id_suffix_bytes ),* ]; + } + + impl<#type_impl_gen> #frame_support::view_functions::ViewFunction for #view_function_struct_ident<#type_use_gen> #where_clause { + fn id() -> #frame_support::view_functions::ViewFunctionId { + #frame_support::view_functions::ViewFunctionId { + prefix: <#pallet_ident<#type_use_gen> as #frame_support::view_functions::ViewFunctionIdPrefix>::prefix(), + suffix: <Self as #frame_support::view_functions::ViewFunctionIdSuffix>::SUFFIX, + } + } + + type ReturnType = #return_type; + + fn invoke(self) -> Self::ReturnType { + let Self { #( #arg_names, )* _marker } = self; + #pallet_ident::<#type_use_gen> :: #view_fn_name( #( #arg_names, )* ) + } + } + } +} + +fn impl_dispatch_view_function( + def: &Def, + span: Span, + where_clause: Option<&syn::WhereClause>, + view_fns: &[ViewFunctionDef], +) -> TokenStream { + let frame_support = &def.frame_support; + let pallet_ident = &def.pallet_struct.pallet; + let type_impl_gen = &def.type_impl_generics(span); + let type_use_gen = &def.type_use_generics(span); + + let query_match_arms = view_fns.iter().map(|view_fn| { + let view_function_struct_ident = view_fn.view_function_struct_ident(); + quote::quote! { + <#view_function_struct_ident<#type_use_gen> as #frame_support::view_functions::ViewFunctionIdSuffix>::SUFFIX => { + <#view_function_struct_ident<#type_use_gen> as #frame_support::view_functions::ViewFunction>::execute(input, output) + } + } + }); + + quote::quote! { + impl<#type_impl_gen> #frame_support::view_functions::DispatchViewFunction + for #pallet_ident<#type_use_gen> #where_clause + { + #[deny(unreachable_patterns)] + fn dispatch_view_function<O: #frame_support::__private::codec::Output>( + id: & #frame_support::view_functions::ViewFunctionId, + input: &mut &[u8], + output: &mut O + ) -> Result<(), #frame_support::view_functions::ViewFunctionDispatchError> + { + match id.suffix { + #( #query_match_arms )* + _ => Err(#frame_support::view_functions::ViewFunctionDispatchError::NotFound(id.clone())), + } + } + } + } +} + +fn impl_view_function_metadata( + def: &Def, + span: Span, + where_clause: Option<&syn::WhereClause>, + view_fns: &[ViewFunctionDef], + docs: &[syn::Expr], +) -> TokenStream { + let frame_support = &def.frame_support; + let pallet_ident = &def.pallet_struct.pallet; + let type_impl_gen = &def.type_impl_generics(span); + let type_use_gen = &def.type_use_generics(span); + + let view_functions = view_fns.iter().map(|view_fn| { + let view_function_struct_ident = view_fn.view_function_struct_ident(); + let name = &view_fn.name; + let args = view_fn.args.iter().filter_map(|fn_arg| { + match fn_arg { + syn::FnArg::Receiver(_) => None, + syn::FnArg::Typed(typed) => { + let pat = &typed.pat; + let ty = &typed.ty; + Some(quote::quote! { + #frame_support::__private::metadata_ir::ViewFunctionArgMetadataIR { + name: ::core::stringify!(#pat), + ty: #frame_support::__private::scale_info::meta_type::<#ty>(), + } + }) + } + } + }); + + let no_docs = vec![]; + let doc = if cfg!(feature = "no-metadata-docs") { &no_docs } else { &view_fn.docs }; + + quote::quote! { + #frame_support::__private::metadata_ir::ViewFunctionMetadataIR { + name: ::core::stringify!(#name), + id: <#view_function_struct_ident<#type_use_gen> as #frame_support::view_functions::ViewFunction>::id().into(), + args: #frame_support::__private::sp_std::vec![ #( #args ),* ], + output: #frame_support::__private::scale_info::meta_type::< + <#view_function_struct_ident<#type_use_gen> as #frame_support::view_functions::ViewFunction>::ReturnType + >(), + docs: #frame_support::__private::sp_std::vec![ #( #doc ),* ], + } + } + }); + + let no_docs = vec![]; + let doc = if cfg!(feature = "no-metadata-docs") { &no_docs } else { docs }; + + quote::quote! { + impl<#type_impl_gen> #pallet_ident<#type_use_gen> #where_clause { + #[doc(hidden)] + pub fn pallet_view_functions_metadata(name: &'static ::core::primitive::str) + -> #frame_support::__private::metadata_ir::ViewFunctionGroupIR + { + #frame_support::__private::metadata_ir::ViewFunctionGroupIR { + name, + view_functions: #frame_support::__private::sp_std::vec![ #( #view_functions ),* ], + docs: #frame_support::__private::sp_std::vec![ #( #doc ),* ], + } + } + } + } +} diff --git a/substrate/frame/support/procedural/src/pallet/parse/mod.rs b/substrate/frame/support/procedural/src/pallet/parse/mod.rs index c9a150effccbee6ecf7ebb308837556828c1c071..89875974b8b5d023c161600a96518ad65cb855fd 100644 --- a/substrate/frame/support/procedural/src/pallet/parse/mod.rs +++ b/substrate/frame/support/procedural/src/pallet/parse/mod.rs @@ -36,6 +36,7 @@ pub mod storage; pub mod tasks; pub mod type_value; pub mod validate_unsigned; +pub mod view_functions; #[cfg(test)] pub mod tests; @@ -70,6 +71,7 @@ pub struct Def { pub frame_system: syn::Path, pub frame_support: syn::Path, pub dev_mode: bool, + pub view_functions: Option<view_functions::ViewFunctionsImplDef>, } impl Def { @@ -103,6 +105,7 @@ impl Def { let mut storages = vec![]; let mut type_values = vec![]; let mut composites: Vec<CompositeDef> = vec![]; + let mut view_functions = None; for (index, item) in items.iter_mut().enumerate() { let pallet_attr: Option<PalletAttr> = helper::take_first_item_pallet_attr(item)?; @@ -205,6 +208,9 @@ impl Def { } composites.push(composite); }, + Some(PalletAttr::ViewFunctions(span)) => { + view_functions = Some(view_functions::ViewFunctionsImplDef::try_from(span, item)?); + } Some(attr) => { let msg = "Invalid duplicated attribute"; return Err(syn::Error::new(attr.span(), msg)) @@ -250,6 +256,7 @@ impl Def { frame_system, frame_support, dev_mode, + view_functions, }; def.check_instance_usage()?; @@ -563,6 +570,7 @@ mod keyword { syn::custom_keyword!(pallet); syn::custom_keyword!(extra_constants); syn::custom_keyword!(composite_enum); + syn::custom_keyword!(view_functions_experimental); } /// The possible values for the `#[pallet::config]` attribute. @@ -652,6 +660,7 @@ enum PalletAttr { TypeValue(proc_macro2::Span), ExtraConstants(proc_macro2::Span), Composite(proc_macro2::Span), + ViewFunctions(proc_macro2::Span), } impl PalletAttr { @@ -677,6 +686,7 @@ impl PalletAttr { Self::TypeValue(span) => *span, Self::ExtraConstants(span) => *span, Self::Composite(span) => *span, + Self::ViewFunctions(span) => *span, } } } @@ -778,6 +788,10 @@ impl syn::parse::Parse for PalletAttr { Ok(PalletAttr::ExtraConstants(content.parse::<keyword::extra_constants>()?.span())) } else if lookahead.peek(keyword::composite_enum) { Ok(PalletAttr::Composite(content.parse::<keyword::composite_enum>()?.span())) + } else if lookahead.peek(keyword::view_functions_experimental) { + Ok(PalletAttr::ViewFunctions( + content.parse::<keyword::view_functions_experimental>()?.span(), + )) } else { Err(lookahead.error()) } diff --git a/substrate/frame/support/procedural/src/pallet/parse/view_functions.rs b/substrate/frame/support/procedural/src/pallet/parse/view_functions.rs new file mode 100644 index 0000000000000000000000000000000000000000..766bcb13da8b3cbddc164a8fdd2a2ecab066f6d4 --- /dev/null +++ b/substrate/frame/support/procedural/src/pallet/parse/view_functions.rs @@ -0,0 +1,155 @@ +// 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 governsing permissions and +// limitations under the License. + +use frame_support_procedural_tools::get_doc_literals; +use inflector::Inflector; +use syn::spanned::Spanned; + +/// Parsed representation of an impl block annotated with `pallet::view_functions_experimental`. +pub struct ViewFunctionsImplDef { + /// The where_clause used. + pub where_clause: Option<syn::WhereClause>, + /// The span of the pallet::view_functions_experimental attribute. + pub attr_span: proc_macro2::Span, + /// Docs, specified on the impl Block. + pub docs: Vec<syn::Expr>, + /// The view function definitions. + pub view_functions: Vec<ViewFunctionDef>, +} + +impl ViewFunctionsImplDef { + pub fn try_from(attr_span: proc_macro2::Span, item: &mut syn::Item) -> syn::Result<Self> { + let syn::Item::Impl(item_impl) = item else { + return Err(syn::Error::new( + item.span(), + "Invalid pallet::view_functions_experimental, expected item impl", + )) + }; + let mut view_functions = Vec::new(); + for item in &mut item_impl.items { + if let syn::ImplItem::Fn(method) = item { + if !matches!(method.vis, syn::Visibility::Public(_)) { + let msg = "Invalid pallet::view_functions_experimental, view function must be public: \ + `pub fn`"; + + let span = match method.vis { + syn::Visibility::Inherited => method.sig.span(), + _ => method.vis.span(), + }; + + return Err(syn::Error::new(span, msg)) + } + + let view_fn_def = ViewFunctionDef::try_from(method.clone())?; + view_functions.push(view_fn_def) + } else { + return Err(syn::Error::new( + item.span(), + "Invalid pallet::view_functions_experimental, expected a function", + )) + } + } + Ok(Self { + view_functions, + attr_span, + where_clause: item_impl.generics.where_clause.clone(), + docs: get_doc_literals(&item_impl.attrs), + }) + } +} + +/// Parsed representation of a view function definition. +#[derive(Clone)] +pub struct ViewFunctionDef { + pub name: syn::Ident, + pub docs: Vec<syn::Expr>, + pub args: Vec<syn::FnArg>, + pub return_type: syn::Type, +} + +impl TryFrom<syn::ImplItemFn> for ViewFunctionDef { + type Error = syn::Error; + fn try_from(method: syn::ImplItemFn) -> Result<Self, Self::Error> { + let syn::ReturnType::Type(_, type_) = method.sig.output else { + return Err(syn::Error::new(method.sig.span(), "view functions must return a value")) + }; + + Ok(Self { + name: method.sig.ident.clone(), + docs: get_doc_literals(&method.attrs), + args: method.sig.inputs.iter().cloned().collect::<Vec<_>>(), + return_type: *type_.clone(), + }) + } +} + +impl ViewFunctionDef { + pub fn view_function_struct_ident(&self) -> syn::Ident { + syn::Ident::new( + &format!("{}ViewFunction", self.name.to_string().to_pascal_case()), + self.name.span(), + ) + } + + pub fn view_function_id_suffix_bytes(&self) -> Result<[u8; 16], syn::Error> { + let mut output = [0u8; 16]; + + // concatenate the signature string + let arg_types = self + .args_names_types()? + .1 + .iter() + .map(|ty| quote::quote!(#ty).to_string().replace(" ", "")) + .collect::<Vec<_>>() + .join(","); + let return_type = &self.return_type; + let return_type = quote::quote!(#return_type).to_string().replace(" ", ""); + let view_fn_signature = format!( + "{view_function_name}({arg_types}) -> {return_type}", + view_function_name = &self.name, + ); + + // hash the signature string + let hash = sp_crypto_hashing::twox_128(view_fn_signature.as_bytes()); + output.copy_from_slice(&hash[..]); + Ok(output) + } + + pub fn args_names_types(&self) -> Result<(Vec<syn::Ident>, Vec<syn::Type>), syn::Error> { + Ok(self + .args + .iter() + .map(|arg| { + let syn::FnArg::Typed(pat_type) = arg else { + return Err(syn::Error::new( + arg.span(), + "Unsupported argument in view function", + )); + }; + let syn::Pat::Ident(ident) = &*pat_type.pat else { + return Err(syn::Error::new( + pat_type.pat.span(), + "Unsupported pattern in view function argument", + )); + }; + Ok((ident.ident.clone(), *pat_type.ty.clone())) + }) + .collect::<Result<Vec<(syn::Ident, syn::Type)>, syn::Error>>()? + .into_iter() + .unzip()) + } +} diff --git a/substrate/frame/support/procedural/src/runtime/expand/mod.rs b/substrate/frame/support/procedural/src/runtime/expand/mod.rs index 666bc03aa415df6b495cd479e62cb929f8767cd8..005b109c0eb5fe2a1c79f44f664d2dad47a5bf84 100644 --- a/substrate/frame/support/procedural/src/runtime/expand/mod.rs +++ b/substrate/frame/support/procedural/src/runtime/expand/mod.rs @@ -182,6 +182,7 @@ fn construct_runtime_final_expansion( let mut slash_reason = None; let mut lock_id = None; let mut task = None; + let mut query = None; for runtime_type in runtime_types.iter() { match runtime_type { @@ -224,6 +225,9 @@ fn construct_runtime_final_expansion( RuntimeType::RuntimeTask(_) => { task = Some(expand::expand_outer_task(&name, &pallets, &scrate)); }, + RuntimeType::RuntimeViewFunction(_) => { + query = Some(expand::expand_outer_query(&name, &pallets, &scrate)); + }, } } @@ -301,6 +305,8 @@ fn construct_runtime_final_expansion( #task + #query + #metadata #outer_config diff --git a/substrate/frame/support/procedural/src/runtime/parse/runtime_types.rs b/substrate/frame/support/procedural/src/runtime/parse/runtime_types.rs index a4480e2a1fd32622bea3a7f20294b4c2bee88309..9a385146a811e85211aa0bf6791154ac76276687 100644 --- a/substrate/frame/support/procedural/src/runtime/parse/runtime_types.rs +++ b/substrate/frame/support/procedural/src/runtime/parse/runtime_types.rs @@ -32,6 +32,7 @@ mod keyword { custom_keyword!(RuntimeSlashReason); custom_keyword!(RuntimeLockId); custom_keyword!(RuntimeTask); + custom_keyword!(RuntimeViewFunction); } #[derive(Debug, Clone, PartialEq)] @@ -45,6 +46,7 @@ pub enum RuntimeType { RuntimeSlashReason(keyword::RuntimeSlashReason), RuntimeLockId(keyword::RuntimeLockId), RuntimeTask(keyword::RuntimeTask), + RuntimeViewFunction(keyword::RuntimeViewFunction), } impl Parse for RuntimeType { @@ -69,6 +71,8 @@ impl Parse for RuntimeType { Ok(Self::RuntimeLockId(input.parse()?)) } else if lookahead.peek(keyword::RuntimeTask) { Ok(Self::RuntimeTask(input.parse()?)) + } else if lookahead.peek(keyword::RuntimeViewFunction) { + Ok(Self::RuntimeViewFunction(input.parse()?)) } else { Err(lookahead.error()) } diff --git a/substrate/frame/support/src/lib.rs b/substrate/frame/support/src/lib.rs index a6969260e6a26bedef86f55e7b27689978891aa7..97d16e2a06d23349fe4ea653be73428dde976a68 100644 --- a/substrate/frame/support/src/lib.rs +++ b/substrate/frame/support/src/lib.rs @@ -87,6 +87,7 @@ pub mod storage; #[cfg(test)] mod tests; pub mod traits; +pub mod view_functions; pub mod weights; #[doc(hidden)] pub mod unsigned { diff --git a/substrate/frame/support/src/tests/mod.rs b/substrate/frame/support/src/tests/mod.rs index 7c90a12d4167e376841cb0c5a683d56d36559c5c..b10e719b9ac36caaa7e283de1c9e415d00c9edef 100644 --- a/substrate/frame/support/src/tests/mod.rs +++ b/substrate/frame/support/src/tests/mod.rs @@ -237,7 +237,8 @@ mod runtime { RuntimeHoldReason, RuntimeSlashReason, RuntimeLockId, - RuntimeTask + RuntimeTask, + RuntimeViewFunction )] pub struct Runtime; diff --git a/substrate/frame/support/src/view_functions.rs b/substrate/frame/support/src/view_functions.rs new file mode 100644 index 0000000000000000000000000000000000000000..dd23fad94a4fd578bdc3d63f86bcb1a1750fe064 --- /dev/null +++ b/substrate/frame/support/src/view_functions.rs @@ -0,0 +1,128 @@ +// 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 fsor the specific language governing permissions and +// limitations under the License. + +//! Traits for querying pallet view functions. + +use alloc::vec::Vec; +use codec::{Decode, DecodeAll, Encode, Output}; +use scale_info::TypeInfo; +use sp_runtime::RuntimeDebug; + +/// The unique identifier for a view function. +#[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct ViewFunctionId { + /// The part of the id for dispatching view functions from the top level of the runtime. + /// + /// Specifies which view function grouping this view function belongs to. This could be a group + /// of view functions associated with a pallet, or a pallet agnostic group of view functions. + pub prefix: [u8; 16], + /// The part of the id for dispatching to a view function within a group. + pub suffix: [u8; 16], +} + +impl From<ViewFunctionId> for [u8; 32] { + fn from(value: ViewFunctionId) -> Self { + let mut output = [0u8; 32]; + output[..16].copy_from_slice(&value.prefix); + output[16..].copy_from_slice(&value.suffix); + output + } +} + +/// Error type for view function dispatching. +#[derive(Encode, Decode, RuntimeDebug, TypeInfo)] +pub enum ViewFunctionDispatchError { + /// View functions are not implemented for this runtime. + NotImplemented, + /// A view function with the given `ViewFunctionId` was not found + NotFound(ViewFunctionId), + /// Failed to decode the view function input. + Codec, +} + +impl From<codec::Error> for ViewFunctionDispatchError { + fn from(_: codec::Error) -> Self { + ViewFunctionDispatchError::Codec + } +} + +/// Implemented by both pallets and the runtime. The runtime is dispatching by prefix using the +/// pallet implementation of `ViewFunctionIdPrefix` then the pallet is dispatching by suffix using +/// the methods implementation of `ViewFunctionIdSuffix`. +pub trait DispatchViewFunction { + fn dispatch_view_function<O: Output>( + id: &ViewFunctionId, + input: &mut &[u8], + output: &mut O, + ) -> Result<(), ViewFunctionDispatchError>; +} + +impl DispatchViewFunction for () { + fn dispatch_view_function<O: Output>( + _id: &ViewFunctionId, + _input: &mut &[u8], + _output: &mut O, + ) -> Result<(), ViewFunctionDispatchError> { + Err(ViewFunctionDispatchError::NotImplemented) + } +} + +/// Automatically implemented for each pallet by the macro [`pallet`](crate::pallet). +pub trait ViewFunctionIdPrefix { + fn prefix() -> [u8; 16]; +} + +/// Automatically implemented for each pallet view function method by the macro +/// [`pallet`](crate::pallet). +pub trait ViewFunctionIdSuffix { + const SUFFIX: [u8; 16]; +} + +/// Automatically implemented for each pallet view function method by the macro +/// [`pallet`](crate::pallet). +pub trait ViewFunction: DecodeAll { + fn id() -> ViewFunctionId; + type ReturnType: Encode; + + fn invoke(self) -> Self::ReturnType; + + fn execute<O: Output>( + input: &mut &[u8], + output: &mut O, + ) -> Result<(), ViewFunctionDispatchError> { + let view_function = Self::decode_all(input)?; + let result = view_function.invoke(); + Encode::encode_to(&result, output); + Ok(()) + } +} + +pub mod runtime_api { + use super::*; + + sp_api::decl_runtime_apis! { + #[api_version(1)] + /// Runtime API for executing view functions + pub trait RuntimeViewFunction { + /// Execute a view function query. + fn execute_view_function( + query_id: ViewFunctionId, + input: Vec<u8>, + ) -> Result<Vec<u8>, ViewFunctionDispatchError>; + } + } +} diff --git a/substrate/frame/support/test/tests/construct_runtime_ui/deprecated_where_block.stderr b/substrate/frame/support/test/tests/construct_runtime_ui/deprecated_where_block.stderr index 726b09cf54c997b04ffa17a6256cae25e4c17886..faa9cb558c262f09111a488a4e2f4bf114a74375 100644 --- a/substrate/frame/support/test/tests/construct_runtime_ui/deprecated_where_block.stderr +++ b/substrate/frame/support/test/tests/construct_runtime_ui/deprecated_where_block.stderr @@ -561,6 +561,15 @@ note: the trait `Config` must be implemented | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) +error[E0277]: the trait bound `Runtime: Config` is not satisfied + --> tests/construct_runtime_ui/deprecated_where_block.rs:26:3 + | +26 | System: frame_system::{Pallet, Call, Storage, Config<T>, Event<T>}, + | ^^^^^^ the trait `Config` is not implemented for `Runtime`, which is required by `Pallet<Runtime>: ViewFunctionIdPrefix` + | + = help: the trait `ViewFunctionIdPrefix` is implemented for `Pallet<T>` + = note: required for `Pallet<Runtime>` to implement `ViewFunctionIdPrefix` + error[E0599]: the function or associated item `storage_metadata` exists for struct `Pallet<Runtime>`, but its trait bounds were not satisfied --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | @@ -736,6 +745,31 @@ note: the trait `Config` must be implemented | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) +error[E0599]: the function or associated item `pallet_view_functions_metadata` exists for struct `Pallet<Runtime>`, but its trait bounds were not satisfied + --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 + | +20 | construct_runtime! { + | __^ + | | _| + | || +21 | || pub struct Runtime where + | ||______________________- doesn't satisfy `Runtime: Config` +22 | | Block = Block, +23 | | NodeBlock = Block, +... | +27 | | } +28 | | } + | |__^ function or associated item cannot be called on `Pallet<Runtime>` due to unsatisfied trait bounds + | + = note: the following trait bounds were not satisfied: + `Runtime: Config` +note: the trait `Config` must be implemented + --> $WORKSPACE/substrate/frame/system/src/lib.rs + | + | pub trait Config: 'static + Eq + Clone { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + error[E0277]: the trait bound `Runtime: Config` is not satisfied --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | diff --git a/substrate/frame/support/test/tests/derive_impl_ui/inject_runtime_type_invalid.stderr b/substrate/frame/support/test/tests/derive_impl_ui/inject_runtime_type_invalid.stderr index c7159b34afb3d22737fb5d8ed6662027f04a3bd6..aafc6b5a2c874a7a1ba219fa4b442d5da6114bb3 100644 --- a/substrate/frame/support/test/tests/derive_impl_ui/inject_runtime_type_invalid.stderr +++ b/substrate/frame/support/test/tests/derive_impl_ui/inject_runtime_type_invalid.stderr @@ -1,4 +1,4 @@ -error: `#[inject_runtime_type]` can only be attached to `RuntimeCall`, `RuntimeEvent`, `RuntimeTask`, `RuntimeOrigin`, `RuntimeParameters` or `PalletInfo` +error: `#[inject_runtime_type]` can only be attached to `RuntimeCall`, `RuntimeEvent`, `RuntimeTask`, `RuntimeViewFunction`, `RuntimeOrigin`, `RuntimeParameters` or `PalletInfo` --> tests/derive_impl_ui/inject_runtime_type_invalid.rs:32:5 | 32 | type RuntimeInfo = (); diff --git a/substrate/frame/support/test/tests/pallet.rs b/substrate/frame/support/test/tests/pallet.rs index 9df1f461bba2511538da7ff1f3455c9a36653163..e45ff64e4c26eda45147fb9d65b688914735bfca 100644 --- a/substrate/frame/support/test/tests/pallet.rs +++ b/substrate/frame/support/test/tests/pallet.rs @@ -461,6 +461,22 @@ pub mod pallet { _myfield: u32, } + #[pallet::view_functions_experimental] + impl<T: Config> Pallet<T> + where + T::AccountId: From<SomeType1> + SomeAssociation1, + { + /// Query value no args. + pub fn get_value() -> Option<u32> { + Value::<T>::get() + } + + /// Query value with args. + pub fn get_value_with_arg(key: u16) -> Option<u32> { + Map2::<T>::get(key) + } + } + #[pallet::genesis_build] impl<T: Config> BuildGenesisConfig for GenesisConfig<T> where @@ -814,7 +830,8 @@ mod runtime { RuntimeHoldReason, RuntimeSlashReason, RuntimeLockId, - RuntimeTask + RuntimeTask, + RuntimeViewFunction )] pub struct Runtime; diff --git a/substrate/frame/support/test/tests/runtime.rs b/substrate/frame/support/test/tests/runtime.rs index 5335e08837e4adb2735fd3beecd21f8023be7778..cbcdf8d27b39a6e3f48f7cbef5e02c350d56a943 100644 --- a/substrate/frame/support/test/tests/runtime.rs +++ b/substrate/frame/support/test/tests/runtime.rs @@ -296,7 +296,8 @@ mod runtime { RuntimeHoldReason, RuntimeSlashReason, RuntimeLockId, - RuntimeTask + RuntimeTask, + RuntimeViewFunction )] pub struct Runtime; diff --git a/substrate/frame/support/test/tests/runtime_legacy_ordering.rs b/substrate/frame/support/test/tests/runtime_legacy_ordering.rs index 7b92073a82b1a789ca2b33296893fcb8d62a3a62..1594356ad8fe846b329818634c2a235fd2cae77e 100644 --- a/substrate/frame/support/test/tests/runtime_legacy_ordering.rs +++ b/substrate/frame/support/test/tests/runtime_legacy_ordering.rs @@ -296,7 +296,8 @@ mod runtime { RuntimeHoldReason, RuntimeSlashReason, RuntimeLockId, - RuntimeTask + RuntimeTask, + RuntimeViewFunction )] pub struct Runtime; diff --git a/substrate/frame/support/test/tests/runtime_ui/invalid_runtime_type_derive.stderr b/substrate/frame/support/test/tests/runtime_ui/invalid_runtime_type_derive.stderr index 0b128c3dd4579ba93f7950f42f0ff97440eadd38..daa6721ff051dac23d66cde5f56c4b1e023b5e97 100644 --- a/substrate/frame/support/test/tests/runtime_ui/invalid_runtime_type_derive.stderr +++ b/substrate/frame/support/test/tests/runtime_ui/invalid_runtime_type_derive.stderr @@ -1,4 +1,4 @@ -error: expected one of: `RuntimeCall`, `RuntimeEvent`, `RuntimeError`, `RuntimeOrigin`, `RuntimeFreezeReason`, `RuntimeHoldReason`, `RuntimeSlashReason`, `RuntimeLockId`, `RuntimeTask` +error: expected one of: `RuntimeCall`, `RuntimeEvent`, `RuntimeError`, `RuntimeOrigin`, `RuntimeFreezeReason`, `RuntimeHoldReason`, `RuntimeSlashReason`, `RuntimeLockId`, `RuntimeTask`, `RuntimeViewFunction` --> tests/runtime_ui/invalid_runtime_type_derive.rs:21:23 | 21 | #[runtime::derive(RuntimeInfo)] diff --git a/substrate/frame/support/test/tests/runtime_ui/pass/basic.rs b/substrate/frame/support/test/tests/runtime_ui/pass/basic.rs index 514f150180153692caf55ba9b3ecb171ca4e1a2a..8350211335a5251ec7ef1356dd491dbe7034cc18 100644 --- a/substrate/frame/support/test/tests/runtime_ui/pass/basic.rs +++ b/substrate/frame/support/test/tests/runtime_ui/pass/basic.rs @@ -27,7 +27,7 @@ impl frame_system::Config for Runtime { #[frame_support::runtime] mod runtime { #[runtime::runtime] - #[runtime::derive(RuntimeCall, RuntimeEvent, RuntimeOrigin, RuntimeError, RuntimeTask)] + #[runtime::derive(RuntimeCall, RuntimeEvent, RuntimeOrigin, RuntimeError, RuntimeTask, RuntimeViewFunction)] pub struct Runtime; #[runtime::pallet_index(0)] diff --git a/substrate/primitives/metadata-ir/src/lib.rs b/substrate/primitives/metadata-ir/src/lib.rs index dc01f7eaadb3337f1a4ff42322417d2e353ba299..e048010a34b75a7facb2cd0abe019eb305734096 100644 --- a/substrate/primitives/metadata-ir/src/lib.rs +++ b/substrate/primitives/metadata-ir/src/lib.rs @@ -122,6 +122,7 @@ mod test { event_enum_ty: meta_type::<()>(), error_enum_ty: meta_type::<()>(), }, + view_functions: RuntimeViewFunctionsIR { ty: meta_type::<()>(), groups: vec![] }, } } diff --git a/substrate/primitives/metadata-ir/src/types.rs b/substrate/primitives/metadata-ir/src/types.rs index af217ffe16eeb829cdbbf42f6483a09505709fd0..0617fc7dfb94f7d8c397d4ccbcd4219d400c488d 100644 --- a/substrate/primitives/metadata-ir/src/types.rs +++ b/substrate/primitives/metadata-ir/src/types.rs @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use codec::{Compact, Encode}; +use codec::{Compact, Decode, Encode}; use scale_info::{ form::{Form, MetaForm, PortableForm}, prelude::{collections::BTreeMap, vec::Vec}, @@ -41,6 +41,8 @@ pub struct MetadataIR<T: Form = MetaForm> { pub apis: Vec<RuntimeApiMetadataIR<T>>, /// The outer enums types as found in the runtime. pub outer_enums: OuterEnumsIR<T>, + /// Metadata of view function queries + pub view_functions: RuntimeViewFunctionsIR<T>, } /// Metadata of a runtime trait. @@ -118,6 +120,89 @@ impl IntoPortable for RuntimeApiMethodParamMetadataIR { } } +/// Metadata of the top level runtime view function dispatch. +#[derive(Clone, PartialEq, Eq, Encode, Decode, Debug)] +pub struct RuntimeViewFunctionsIR<T: Form = MetaForm> { + /// The type implementing the runtime query dispatch. + pub ty: T::Type, + /// The view function groupings metadata. + pub groups: Vec<ViewFunctionGroupIR<T>>, +} + +/// Metadata of a runtime view function group. +/// +/// For example, view functions associated with a pallet would form a view function group. +#[derive(Clone, PartialEq, Eq, Encode, Decode, Debug)] +pub struct ViewFunctionGroupIR<T: Form = MetaForm> { + /// Name of the view function group. + pub name: T::String, + /// View functions belonging to the group. + pub view_functions: Vec<ViewFunctionMetadataIR<T>>, + /// View function group documentation. + pub docs: Vec<T::String>, +} + +impl IntoPortable for ViewFunctionGroupIR { + type Output = ViewFunctionGroupIR<PortableForm>; + + fn into_portable(self, registry: &mut Registry) -> Self::Output { + ViewFunctionGroupIR { + name: self.name.into_portable(registry), + view_functions: registry.map_into_portable(self.view_functions), + docs: registry.map_into_portable(self.docs), + } + } +} + +/// Metadata of a runtime view function. +#[derive(Clone, PartialEq, Eq, Encode, Decode, Debug)] +pub struct ViewFunctionMetadataIR<T: Form = MetaForm> { + /// Query name. + pub name: T::String, + /// Query id. + pub id: [u8; 32], + /// Query args. + pub args: Vec<ViewFunctionArgMetadataIR<T>>, + /// Query output. + pub output: T::Type, + /// Query documentation. + pub docs: Vec<T::String>, +} + +impl IntoPortable for ViewFunctionMetadataIR { + type Output = ViewFunctionMetadataIR<PortableForm>; + + fn into_portable(self, registry: &mut Registry) -> Self::Output { + ViewFunctionMetadataIR { + name: self.name.into_portable(registry), + id: self.id, + args: registry.map_into_portable(self.args), + output: registry.register_type(&self.output), + docs: registry.map_into_portable(self.docs), + } + } +} + +/// Metadata of a runtime method argument. +#[derive(Clone, PartialEq, Eq, Encode, Decode, Debug)] +pub struct ViewFunctionArgMetadataIR<T: Form = MetaForm> { + /// Query argument name. + pub name: T::String, + /// Query argument type. + pub ty: T::Type, +} + +impl IntoPortable for ViewFunctionArgMetadataIR { + type Output = ViewFunctionArgMetadataIR<PortableForm>; + + fn into_portable(self, registry: &mut Registry) -> Self::Output { + ViewFunctionArgMetadataIR { + name: self.name.into_portable(registry), + ty: registry.register_type(&self.ty), + } + } +} + /// The intermediate representation for a pallet metadata. #[derive(Clone, PartialEq, Eq, Encode, Debug)] pub struct PalletMetadataIR<T: Form = MetaForm> { diff --git a/substrate/primitives/metadata-ir/src/v15.rs b/substrate/primitives/metadata-ir/src/v15.rs index ed315a31e6dc9f4f9e1c4af9142818756a64ab56..7bc76f22b58d004507c900bd139c9c58f1e8dbf3 100644 --- a/substrate/primitives/metadata-ir/src/v15.rs +++ b/substrate/primitives/metadata-ir/src/v15.rs @@ -17,31 +17,39 @@ //! Convert the IR to V15 metadata. -use crate::OuterEnumsIR; - use super::types::{ - ExtrinsicMetadataIR, MetadataIR, PalletMetadataIR, RuntimeApiMetadataIR, + ExtrinsicMetadataIR, MetadataIR, OuterEnumsIR, PalletMetadataIR, RuntimeApiMetadataIR, RuntimeApiMethodMetadataIR, RuntimeApiMethodParamMetadataIR, TransactionExtensionMetadataIR, }; use frame_metadata::v15::{ - CustomMetadata, ExtrinsicMetadata, OuterEnums, PalletMetadata, RuntimeApiMetadata, - RuntimeApiMethodMetadata, RuntimeApiMethodParamMetadata, RuntimeMetadataV15, - SignedExtensionMetadata, + CustomMetadata, CustomValueMetadata, ExtrinsicMetadata, OuterEnums, PalletMetadata, + RuntimeApiMetadata, RuntimeApiMethodMetadata, RuntimeApiMethodParamMetadata, + RuntimeMetadataV15, SignedExtensionMetadata, }; +use scale_info::{IntoPortable, Registry}; impl From<MetadataIR> for RuntimeMetadataV15 { fn from(ir: MetadataIR) -> Self { - RuntimeMetadataV15::new( - ir.pallets.into_iter().map(Into::into).collect(), - ir.extrinsic.into(), - ir.ty, - ir.apis.into_iter().map(Into::into).collect(), - ir.outer_enums.into(), - // Substrate does not collect yet the custom metadata fields. - // This allows us to extend the V15 easily. - CustomMetadata { map: Default::default() }, - ) + let mut registry = Registry::new(); + let pallets = + registry.map_into_portable(ir.pallets.into_iter().map(Into::<PalletMetadata>::into)); + let extrinsic = Into::<ExtrinsicMetadata>::into(ir.extrinsic).into_portable(&mut registry); + let ty = registry.register_type(&ir.ty); + let apis = + registry.map_into_portable(ir.apis.into_iter().map(Into::<RuntimeApiMetadata>::into)); + let outer_enums = Into::<OuterEnums>::into(ir.outer_enums).into_portable(&mut registry); + + let view_function_groups = registry.map_into_portable(ir.view_functions.groups.into_iter()); + let view_functions_custom_metadata = CustomValueMetadata { + ty: ir.view_functions.ty, + value: codec::Encode::encode(&view_function_groups), + }; + let mut custom_map = alloc::collections::BTreeMap::new(); + custom_map.insert("view_functions_experimental", view_functions_custom_metadata); + let custom = CustomMetadata { map: custom_map }.into_portable(&mut registry); + + Self { types: registry.into(), pallets, extrinsic, ty, apis, outer_enums, custom } } } diff --git a/templates/minimal/runtime/src/lib.rs b/templates/minimal/runtime/src/lib.rs index 972c7500f3993f5a362e422e300262025146ce1c..5d549bf1a912d7be414b206d9cdf73ad75c3161c 100644 --- a/templates/minimal/runtime/src/lib.rs +++ b/templates/minimal/runtime/src/lib.rs @@ -138,7 +138,8 @@ mod runtime { RuntimeHoldReason, RuntimeSlashReason, RuntimeLockId, - RuntimeTask + RuntimeTask, + RuntimeViewFunction )] pub struct Runtime; diff --git a/templates/parachain/pallets/template/src/mock.rs b/templates/parachain/pallets/template/src/mock.rs index b924428d4145c51ec7de253d59380cdb8dce57f6..3eeb9604f015334cd71c485f648b0dc796d33a9b 100644 --- a/templates/parachain/pallets/template/src/mock.rs +++ b/templates/parachain/pallets/template/src/mock.rs @@ -18,7 +18,8 @@ mod test_runtime { RuntimeHoldReason, RuntimeSlashReason, RuntimeLockId, - RuntimeTask + RuntimeTask, + RuntimeViewFunction )] pub struct Test; diff --git a/templates/parachain/runtime/src/apis.rs b/templates/parachain/runtime/src/apis.rs index 05a508ca655fb1fe6be4db53a839b76c8a9c1cec..d7da43b86af166e991371acb78806c46072cdfc0 100644 --- a/templates/parachain/runtime/src/apis.rs +++ b/templates/parachain/runtime/src/apis.rs @@ -114,6 +114,12 @@ impl_runtime_apis! { } } + impl frame_support::view_functions::runtime_api::RuntimeViewFunction<Block> for Runtime { + fn execute_view_function(id: frame_support::view_functions::ViewFunctionId, input: Vec<u8>) -> Result<Vec<u8>, frame_support::view_functions::ViewFunctionDispatchError> { + Runtime::execute_view_function(id, input) + } + } + impl sp_block_builder::BlockBuilder<Block> for Runtime { fn apply_extrinsic(extrinsic: <Block as BlockT>::Extrinsic) -> ApplyExtrinsicResult { Executive::apply_extrinsic(extrinsic) diff --git a/templates/parachain/runtime/src/lib.rs b/templates/parachain/runtime/src/lib.rs index 0be27ecce73945a6fbda409f7d19994493d9be0f..f312e9f80192fd767cdf93288c3331186aabf45a 100644 --- a/templates/parachain/runtime/src/lib.rs +++ b/templates/parachain/runtime/src/lib.rs @@ -262,7 +262,8 @@ mod runtime { RuntimeHoldReason, RuntimeSlashReason, RuntimeLockId, - RuntimeTask + RuntimeTask, + RuntimeViewFunction )] pub struct Runtime; diff --git a/templates/solochain/pallets/template/src/mock.rs b/templates/solochain/pallets/template/src/mock.rs index 1b86cd9b7709a43bf7abe45893569b53a614f0e3..44085bc3bff18e90d301c5275944c6e8aa41fd20 100644 --- a/templates/solochain/pallets/template/src/mock.rs +++ b/templates/solochain/pallets/template/src/mock.rs @@ -18,7 +18,8 @@ mod runtime { RuntimeHoldReason, RuntimeSlashReason, RuntimeLockId, - RuntimeTask + RuntimeTask, + RuntimeViewFunction )] pub struct Test; diff --git a/templates/solochain/runtime/src/apis.rs b/templates/solochain/runtime/src/apis.rs index 06c645fa0c53959b209fa4bcfe867eb6c34a5a6d..9dc588c43a2d5eaaa823802972a735dab40d49b0 100644 --- a/templates/solochain/runtime/src/apis.rs +++ b/templates/solochain/runtime/src/apis.rs @@ -75,6 +75,12 @@ impl_runtime_apis! { } } + impl frame_support::view_functions::runtime_api::RuntimeViewFunction<Block> for Runtime { + fn execute_view_function(id: frame_support::view_functions::ViewFunctionId, input: Vec<u8>) -> Result<Vec<u8>, frame_support::view_functions::ViewFunctionDispatchError> { + Runtime::execute_view_function(id, input) + } + } + impl sp_block_builder::BlockBuilder<Block> for Runtime { fn apply_extrinsic(extrinsic: <Block as BlockT>::Extrinsic) -> ApplyExtrinsicResult { Executive::apply_extrinsic(extrinsic) diff --git a/templates/solochain/runtime/src/lib.rs b/templates/solochain/runtime/src/lib.rs index 6a2149ec8b637c2b253a9327abd81260259cf150..f25b8413721ea2a2ede609fcfaf515f15d163468 100644 --- a/templates/solochain/runtime/src/lib.rs +++ b/templates/solochain/runtime/src/lib.rs @@ -196,7 +196,8 @@ mod runtime { RuntimeHoldReason, RuntimeSlashReason, RuntimeLockId, - RuntimeTask + RuntimeTask, + RuntimeViewFunction )] pub struct Runtime;