Metadata V16: Implement support for Pallet View Functions (#1981)

* Support Pallet View Functions in Subxt

* fmt

* clippy

* Move a little view function logic to subxt_core

* clippy

* Add back check that prob isnt needed

* avoid vec macro in core

* Add view funciton test and apply various fixes to get it working

* Add test for dynamic view fn call and fix issues

* clippy

* fix test-runtime

* fmt

* remove export

* avoid vec for nostd core

* use const instead of fn for view fn call name

* Update to support latest unstable metadata

* Update metadata stripping tests for new v16 version
This commit is contained in:
James Wilson
2025-04-24 14:42:07 +01:00
committed by GitHub
parent 21b3f52191
commit 4524590821
28 changed files with 875 additions and 108 deletions
+22 -1
View File
@@ -11,6 +11,7 @@ use crate::{
runtime_api::RuntimeApiClient,
storage::StorageClient,
tx::TxClient,
view_functions::ViewFunctionsClient,
Metadata,
};
@@ -67,11 +68,16 @@ pub trait OfflineClientT<T: Config>: Clone + Send + Sync + 'static {
BlocksClient::new(self.clone())
}
/// Work with runtime API.
/// Work with runtime APIs.
fn runtime_api(&self) -> RuntimeApiClient<T, Self> {
RuntimeApiClient::new(self.clone())
}
/// Work with View Functions.
fn view_functions(&self) -> ViewFunctionsClient<T, Self> {
ViewFunctionsClient::new(self.clone())
}
/// Work this custom types.
fn custom_values(&self) -> CustomValuesClient<T, Self> {
CustomValuesClient::new(self.clone())
@@ -150,6 +156,21 @@ impl<T: Config> OfflineClient<T> {
<Self as OfflineClientT<T>>::constants(self)
}
/// Work with blocks.
pub fn blocks(&self) -> BlocksClient<T, Self> {
<Self as OfflineClientT<T>>::blocks(self)
}
/// Work with runtime APIs.
pub fn runtime_api(&self) -> RuntimeApiClient<T, Self> {
<Self as OfflineClientT<T>>::runtime_api(self)
}
/// Work with View Functions.
pub fn view_functions(&self) -> ViewFunctionsClient<T, Self> {
<Self as OfflineClientT<T>>::view_functions(self)
}
/// Access custom types
pub fn custom_values(&self) -> CustomValuesClient<T, Self> {
<Self as OfflineClientT<T>>::custom_values(self)
+11 -5
View File
@@ -14,6 +14,7 @@ use crate::{
runtime_api::RuntimeApiClient,
storage::StorageClient,
tx::TxClient,
view_functions::ViewFunctionsClient,
Metadata,
};
use derive_where::derive_where;
@@ -348,11 +349,6 @@ impl<T: Config> OnlineClient<T> {
<Self as OfflineClientT<T>>::constants(self)
}
/// Access custom types.
pub fn custom_values(&self) -> CustomValuesClient<T, Self> {
<Self as OfflineClientT<T>>::custom_values(self)
}
/// Work with blocks.
pub fn blocks(&self) -> BlocksClient<T, Self> {
<Self as OfflineClientT<T>>::blocks(self)
@@ -362,6 +358,16 @@ impl<T: Config> OnlineClient<T> {
pub fn runtime_api(&self) -> RuntimeApiClient<T, Self> {
<Self as OfflineClientT<T>>::runtime_api(self)
}
/// Work with View Functions.
pub fn view_functions(&self) -> ViewFunctionsClient<T, Self> {
<Self as OfflineClientT<T>>::view_functions(self)
}
/// Access custom types.
pub fn custom_values(&self) -> CustomValuesClient<T, Self> {
<Self as OfflineClientT<T>>::custom_values(self)
}
}
impl<T: Config> OfflineClientT<T> for OnlineClient<T> {
+3 -1
View File
@@ -49,6 +49,7 @@ pub mod runtime_api;
pub mod storage;
pub mod tx;
pub mod utils;
pub mod view_functions;
/// This module provides a [`Config`] type, which is used to define various
/// types that are important in order to speak to a particular chain.
@@ -75,7 +76,8 @@ pub mod metadata {
/// Submit dynamic transactions.
pub mod dynamic {
pub use subxt_core::dynamic::{
constant, runtime_api_call, storage, tx, At, DecodedValue, DecodedValueThunk, Value,
constant, runtime_api_call, storage, tx, view_function_call, At, DecodedValue,
DecodedValueThunk, Value,
};
}
+14
View File
@@ -0,0 +1,14 @@
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! Types associated with executing View Function calls.
mod view_function_types;
mod view_functions_client;
pub use subxt_core::view_functions::payload::{
dynamic, DefaultPayload, DynamicPayload, Payload, StaticPayload,
};
pub use view_function_types::ViewFunctionsApi;
pub use view_functions_client::ViewFunctionsClient;
@@ -0,0 +1,79 @@
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use super::Payload;
use crate::{
backend::BlockRef,
client::OnlineClientT,
config::{Config, HashFor},
error::Error,
};
use derive_where::derive_where;
use std::{future::Future, marker::PhantomData};
/// Execute View Function calls.
#[derive_where(Clone; Client)]
pub struct ViewFunctionsApi<T: Config, Client> {
client: Client,
block_ref: BlockRef<HashFor<T>>,
_marker: PhantomData<T>,
}
impl<T: Config, Client> ViewFunctionsApi<T, Client> {
/// Create a new [`ViewFunctionsApi`]
pub(crate) fn new(client: Client, block_ref: BlockRef<HashFor<T>>) -> Self {
Self {
client,
block_ref,
_marker: PhantomData,
}
}
}
impl<T, Client> ViewFunctionsApi<T, Client>
where
T: Config,
Client: OnlineClientT<T>,
{
/// Run the validation logic against some View Function payload you'd like to use. Returns `Ok(())`
/// if the payload is valid (or if it's not possible to check since the payload has no validation hash).
/// Return an error if the payload was not valid or something went wrong trying to validate it (ie
/// the View Function in question do not exist at all)
pub fn validate<Call: Payload>(&self, payload: &Call) -> Result<(), Error> {
subxt_core::view_functions::validate(payload, &self.client.metadata()).map_err(Into::into)
}
/// Execute a View Function call.
pub fn call<Call: Payload>(
&self,
payload: Call,
) -> impl Future<Output = Result<Call::ReturnType, Error>> {
let client = self.client.clone();
let block_hash = self.block_ref.hash();
// Ensure that the returned future doesn't have a lifetime tied to api.view_functions(),
// which is a temporary thing we'll be throwing away quickly:
async move {
let metadata = client.metadata();
// Validate the View Function payload hash against the compile hash from codegen.
subxt_core::view_functions::validate(&payload, &metadata)?;
// Assemble the data to call the "execute_view_function" runtime API, which
// then calls the relevant view function.
let call_name = subxt_core::view_functions::CALL_NAME;
let call_args = subxt_core::view_functions::call_args(&payload, &metadata)?;
// Make the call.
let bytes = client
.backend()
.call(call_name, Some(call_args.as_slice()), block_hash)
.await?;
// Decode the response.
let value =
subxt_core::view_functions::decode_value(&mut &*bytes, &payload, &metadata)?;
Ok(value)
}
}
}
@@ -0,0 +1,57 @@
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use super::view_function_types::ViewFunctionsApi;
use crate::{
backend::BlockRef,
client::OnlineClientT,
config::{Config, HashFor},
error::Error,
};
use derive_where::derive_where;
use std::{future::Future, marker::PhantomData};
/// Make View Function calls at some block.
#[derive_where(Clone; Client)]
pub struct ViewFunctionsClient<T, Client> {
client: Client,
_marker: PhantomData<T>,
}
impl<T, Client> ViewFunctionsClient<T, Client> {
/// Create a new [`ViewFunctionsClient`]
pub fn new(client: Client) -> Self {
Self {
client,
_marker: PhantomData,
}
}
}
impl<T, Client> ViewFunctionsClient<T, Client>
where
T: Config,
Client: OnlineClientT<T>,
{
/// Obtain an interface to call View Functions at some block hash.
pub fn at(&self, block_ref: impl Into<BlockRef<HashFor<T>>>) -> ViewFunctionsApi<T, Client> {
ViewFunctionsApi::new(self.client.clone(), block_ref.into())
}
/// Obtain an interface to call View Functions at the latest block hash.
pub fn at_latest(
&self,
) -> impl Future<Output = Result<ViewFunctionsApi<T, Client>, Error>> + Send + 'static {
// Clone and pass the client in like this so that we can explicitly
// return a Future that's Send + 'static, rather than tied to &self.
let client = self.client.clone();
async move {
// get the ref for the latest finalized block and use that.
let block_ref = client.backend().latest_finalized_block_ref().await?;
Ok(ViewFunctionsApi::new(client, block_ref))
}
}
}