Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pallet view functions: improve metadata, API docs and testing #7412

Merged
merged 24 commits into from
Feb 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
0678c94
change metadata for view fns
re-gius Jan 31, 2025
04b430e
fix metadata and example tests accordingly
re-gius Jan 31, 2025
3445807
fix runtime ui test error
re-gius Feb 3, 2025
528a349
Merge branch 'master' into re-gius/view-fns-metadata-docs-testing
re-gius Feb 3, 2025
5dda057
Add API docs in `pallet_macros`
re-gius Feb 3, 2025
9b026d3
fix API docs
re-gius Feb 4, 2025
52637d4
fix API docs imports
re-gius Feb 4, 2025
431c43f
Add `pallet_ui` tests
re-gius Feb 4, 2025
0d9026d
Merge branch 'master' into re-gius/view-fns-metadata-docs-testing
re-gius Feb 4, 2025
40a07bd
fix runtime ui test
re-gius Feb 4, 2025
ec9bb4f
Improve API docs
re-gius Feb 4, 2025
0a2e08f
docs nit
re-gius Feb 4, 2025
0999e52
docs nits
re-gius Feb 4, 2025
d6f73a2
test fix + file rename
re-gius Feb 4, 2025
7ade40b
Merge branch 'master' into re-gius/view-fns-metadata-docs-testing
re-gius Feb 4, 2025
e9580d6
Update from re-gius running command 'prdoc --audience runtime_dev --b…
github-actions[bot] Feb 4, 2025
f312085
fix prdoc
re-gius Feb 4, 2025
dd11f69
Update substrate/frame/support/src/view_functions.rs
re-gius Feb 5, 2025
32a8d15
Update substrate/frame/support/test/tests/pallet_ui/view_function_no_…
re-gius Feb 5, 2025
e169176
Merge branch 'master' into re-gius/view-fns-metadata-docs-testing
re-gius Feb 5, 2025
7013d1f
fix pallet ui tests
re-gius Feb 5, 2025
b54ec59
Merge branch 'master' into re-gius/view-fns-metadata-docs-testing
re-gius Feb 6, 2025
ce120ca
Merge branch 'master' into re-gius/view-fns-metadata-docs-testing
re-gius Feb 6, 2025
d229f3b
Merge branch 'master' into re-gius/view-fns-metadata-docs-testing
re-gius Feb 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions prdoc/pr_7412.prdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
title: 'Pallet view functions: improve metadata, API docs and testing'
doc:
- audience: Runtime Dev
description: |-
- refactor view functions metadata according to #6833 in preparation for V16, and move them to pallet-level metadata
- add `view_functions_experimental` macro to `pallet_macros` with API docs
- improve UI testing for view functions
crates:
- name: frame-support-procedural
bump: minor
- name: sp-metadata-ir
bump: major
- name: pallet-example-view-functions
bump: patch
- name: frame-support
bump: minor
8 changes: 4 additions & 4 deletions substrate/frame/examples/view-functions/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,12 @@ pub mod pallet {
where
T::AccountId: From<SomeType1> + SomeAssociation1,
{
/// Query value no args.
/// Query value with no input args.
pub fn get_value() -> Option<u32> {
SomeValue::<T>::get()
}

/// Query value with args.
/// Query value with input args.
pub fn get_value_with_arg(key: u32) -> Option<u32> {
SomeMap::<T>::get(key)
}
Expand Down Expand Up @@ -101,12 +101,12 @@ pub mod pallet2 {
where
T::AccountId: From<SomeType1> + SomeAssociation1,
{
/// Query value no args.
/// Query value with no input args.
pub fn get_value() -> Option<u32> {
SomeValue::<T, I>::get()
}

/// Query value with args.
/// Query value with input args.
pub fn get_value_with_arg(key: u32) -> Option<u32> {
SomeMap::<T, I>::get(key)
}
Expand Down
46 changes: 17 additions & 29 deletions substrate/frame/examples/view-functions/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,14 @@ use crate::{
pallet2,
};
use codec::{Decode, Encode};
use scale_info::{form::PortableForm, meta_type};
use scale_info::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_metadata_ir::{
DeprecationStatusIR, PalletViewFunctionMethodMetadataIR,
PalletViewFunctionMethodParamMetadataIR,
};
use sp_runtime::testing::TestXt;

pub type AccountId = u32;
Expand Down Expand Up @@ -111,8 +114,7 @@ fn metadata_ir_definitions() {
new_test_ext().execute_with(|| {
let metadata_ir = Runtime::metadata_ir();
let pallet1 = metadata_ir
.view_functions
.groups
.pallets
.iter()
.find(|pallet| pallet.name == "ViewFunctionsExample")
.unwrap();
Expand All @@ -137,44 +139,30 @@ fn metadata_ir_definitions() {
pretty_assertions::assert_eq!(
pallet1.view_functions,
vec![
ViewFunctionMetadataIR {
PalletViewFunctionMethodMetadataIR {
name: "get_value",
id: get_value_id,
args: vec![],
inputs: vec![],
output: meta_type::<Option<u32>>(),
docs: vec![" Query value no args."],
docs: vec![" Query value with no input args."],
deprecation_info: DeprecationStatusIR::NotDeprecated,
},
ViewFunctionMetadataIR {
PalletViewFunctionMethodMetadataIR {
name: "get_value_with_arg",
id: get_value_with_arg_id,
args: vec![ViewFunctionArgMetadataIR { name: "key", ty: meta_type::<u32>() },],
inputs: vec![PalletViewFunctionMethodParamMetadataIR {
name: "key",
ty: meta_type::<u32>()
},],
output: meta_type::<Option<u32>>(),
docs: vec![" Query value with args."],
docs: vec![" Query value with input args."],
deprecation_info: DeprecationStatusIR::NotDeprecated,
},
]
);
});
}

#[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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ pub fn expand_runtime_metadata(
let index = &decl.index;
let storage = expand_pallet_metadata_storage(&filtered_names, runtime, decl);
let calls = expand_pallet_metadata_calls(&filtered_names, runtime, decl);
let view_functions = expand_pallet_metadata_view_functions(runtime, decl);
let event = expand_pallet_metadata_events(&filtered_names, runtime, decl);
let constants = expand_pallet_metadata_constants(runtime, decl);
let errors = expand_pallet_metadata_errors(runtime, decl);
Expand All @@ -59,6 +60,7 @@ pub fn expand_runtime_metadata(
index: #index,
storage: #storage,
calls: #calls,
view_functions: #view_functions,
event: #event,
constants: #constants,
error: #errors,
Expand All @@ -70,20 +72,6 @@ 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 {
Expand Down Expand Up @@ -156,10 +144,6 @@ 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),* ],
}
}
}

Expand Down Expand Up @@ -216,6 +200,15 @@ fn expand_pallet_metadata_calls(
}
}

fn expand_pallet_metadata_view_functions(runtime: &Ident, decl: &Pallet) -> TokenStream {
let path = &decl.path;
let instance = decl.instance.as_ref().into_iter();

quote! {
#path::Pallet::<#runtime #(, #path::#instance)*>::pallet_view_functions_metadata()
}
}

fn expand_pallet_metadata_events(
filtered_names: &[&'static str],
runtime: &Ident,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ pub fn expand_outer_query(
}

impl #runtime_name {
/// Convenience function for query execution from the runtime API.
/// Convenience function for view functions dispatching and execution from the runtime API.
pub fn execute_view_function(
id: #scrate::view_functions::ViewFunctionId,
input: #scrate::__private::Vec<::core::primitive::u8>,
Expand Down
10 changes: 10 additions & 0 deletions substrate/frame/support/procedural/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1091,6 +1091,16 @@ pub fn validate_unsigned(_: TokenStream, _: TokenStream) -> TokenStream {
pallet_macro_stub()
}

///
/// ---
///
/// Documentation for this macro can be found at
/// `frame_support::pallet_macros::view_functions_experimental`.
#[proc_macro_attribute]
pub fn view_functions_experimental(_: TokenStream, _: TokenStream) -> TokenStream {
pallet_macro_stub()
}

///
/// ---
///
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,10 @@ 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 (span, where_clause, view_fns) = match def.view_functions.as_ref() {
Some(view_fns) =>
(view_fns.attr_span, view_fns.where_clause.clone(), view_fns.view_functions.clone()),
None => (def.item.span(), def.config.where_clause.clone(), Vec::new()),
};

let view_function_prefix_impl =
Expand All @@ -39,7 +35,7 @@ pub fn expand_view_functions(def: &Def) -> TokenStream {
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);
impl_view_function_metadata(def, span, where_clause.as_ref(), &view_fns);

quote::quote! {
#view_function_prefix_impl
Expand Down Expand Up @@ -201,7 +197,6 @@ fn impl_view_function_metadata(
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;
Expand All @@ -211,14 +206,14 @@ fn impl_view_function_metadata(
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| {
let inputs = 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 {
#frame_support::__private::metadata_ir::PalletViewFunctionMethodParamMetadataIR {
name: ::core::stringify!(#pat),
ty: #frame_support::__private::scale_info::meta_type::<#ty>(),
}
Expand All @@ -230,33 +225,34 @@ fn impl_view_function_metadata(
let no_docs = vec![];
let doc = if cfg!(feature = "no-metadata-docs") { &no_docs } else { &view_fn.docs };

let deprecation = match crate::deprecation::get_deprecation(
&quote::quote! { #frame_support },
&def.item.attrs,
) {
Ok(deprecation) => deprecation,
Err(e) => return e.into_compile_error(),
};

quote::quote! {
#frame_support::__private::metadata_ir::ViewFunctionMetadataIR {
#frame_support::__private::metadata_ir::PalletViewFunctionMethodMetadataIR {
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 ),* ],
inputs: #frame_support::__private::sp_std::vec![ #( #inputs ),* ],
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 ),* ],
deprecation_info: #deprecation,
}
}
});

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 ),* ],
}
pub fn pallet_view_functions_metadata()
-> #frame_support::__private::Vec<#frame_support::__private::metadata_ir::PalletViewFunctionMethodMetadataIR> {
#frame_support::__private::vec![ #( #view_functions ),* ]
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ pub struct ViewFunctionsImplDef {
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>,
}
Expand Down Expand Up @@ -67,7 +65,6 @@ impl ViewFunctionsImplDef {
view_functions,
attr_span,
where_clause: item_impl.generics.where_clause.clone(),
docs: get_doc_literals(&item_impl.attrs),
})
}
}
Expand Down
55 changes: 55 additions & 0 deletions substrate/frame/support/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1709,6 +1709,61 @@ pub mod pallet_macros {
/// in the future to give information directly to [`frame_support::construct_runtime`].
pub use frame_support_procedural::validate_unsigned;

/// Allows defining view functions on a pallet.
///
/// A pallet view function is a read-only function providing access to the state of the
/// pallet from both outside and inside the runtime. It should provide a _stable_ interface
/// for querying the state of the pallet, avoiding direct storage access and upgrading
/// along with the runtime.
///
/// ## Syntax
/// View functions methods must be read-only and always return some output. A
/// `view_functions_experimental` impl block only allows methods to be defined inside of
/// it.
///
/// ## Example
/// ```
/// #[frame_support::pallet]
/// pub mod pallet {
/// use frame_support::pallet_prelude::*;
///
/// #[pallet::config]
/// pub trait Config: frame_system::Config {}
///
/// #[pallet::pallet]
/// pub struct Pallet<T>(_);
///
/// #[pallet::storage]
/// pub type SomeMap<T: Config> = StorageMap<_, Twox64Concat, u32, u32, OptionQuery>;
///
/// #[pallet::view_functions_experimental]
/// impl<T: Config> Pallet<T> {
/// /// Retrieve a map storage value by key.
/// pub fn get_value_with_arg(key: u32) -> Option<u32> {
/// SomeMap::<T>::get(key)
/// }
/// }
/// }
/// ```
///
///
/// ## Usage and implementation details
/// To allow outside access to pallet view functions, you need to add a runtime API that
/// accepts view function queries and dispatches them to the right pallet. You can do that
/// by implementing the
/// [`RuntimeViewFunction`](frame_support::view_functions::runtime_api::RuntimeViewFunction)
/// trait for the runtime inside an [`impl_runtime_apis!`](sp_api::impl_runtime_apis)
/// block.
///
/// The `RuntimeViewFunction` trait implements a hashing-based dispatching mechanism to
/// dispatch view functions to the right method in the right pallet based on their IDs. A
/// view function ID depends both on its pallet and on its method signature, so it remains
/// stable as long as those two elements are not modified. In general, pallet view
/// functions should expose a _stable_ interface and changes to the method signature are
/// strongly discouraged. For more details on the dispatching mechanism, see the
/// [`DispatchViewFunction`](frame_support::view_functions::DispatchViewFunction) trait.
pub use frame_support_procedural::view_functions_experimental;

/// Allows defining a struct implementing the [`Get`](frame_support::traits::Get) trait to
/// ease the use of storage types.
///
Expand Down
Loading
Loading