Skip to content

Commit

Permalink
Pallet view functions: improve metadata, API docs and testing (#7412)
Browse files Browse the repository at this point in the history
- [x] refactor view functions metadata according to #6833 in preparation
for V16, and move them to pallet-level metadata
- [x] add `view_functions_experimental` macro to `pallet_macros` with
API docs
- [x] improve pallet-level UI testing for view functions

---------

Co-authored-by: cmd[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Guillaume Thiolliere <[email protected]>
  • Loading branch information
3 people authored Feb 7, 2025
1 parent 6875d36 commit de3c420
Show file tree
Hide file tree
Showing 22 changed files with 350 additions and 156 deletions.
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

0 comments on commit de3c420

Please sign in to comment.