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

V15 Metadata: Support accessing custom types #1106

Merged
merged 12 commits into from
Aug 11, 2023
9 changes: 5 additions & 4 deletions metadata/src/from_into/v15.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
use super::TryFromError;
use crate::utils::variant_index::VariantIndex;
use crate::{
utils::ordered_map::OrderedMap, ArcStr, ConstantMetadata, ExtrinsicMetadata, Metadata,
OuterEnumsMetadata, PalletMetadataInner, RuntimeApiMetadataInner, RuntimeApiMethodMetadata,
RuntimeApiMethodParamMetadata, SignedExtensionMetadata, StorageEntryMetadata,
StorageEntryModifier, StorageEntryType, StorageHasher, StorageMetadata,
utils::ordered_map::OrderedMap, ArcStr, ConstantMetadata, CustomMetadata, ExtrinsicMetadata,
Metadata, OuterEnumsMetadata, PalletMetadataInner, RuntimeApiMetadataInner,
RuntimeApiMethodMetadata, RuntimeApiMethodParamMetadata, SignedExtensionMetadata,
StorageEntryMetadata, StorageEntryModifier, StorageEntryType, StorageHasher, StorageMetadata,
};
use frame_metadata::v15;
use scale_info::form::PortableForm;
Expand Down Expand Up @@ -93,6 +93,7 @@ mod from_v15 {
event_enum_ty: m.outer_enums.event_enum_ty.id,
error_enum_ty: m.outer_enums.error_enum_ty.id,
},
custom: CustomMetadata { map: m.custom.map },
})
}
}
Expand Down
43 changes: 42 additions & 1 deletion metadata/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ mod from_into;
mod utils;

use scale_info::{form::PortableForm, PortableRegistry, Variant};
use std::collections::HashMap;
use std::collections::{BTreeMap, HashMap};
use std::sync::Arc;
use utils::ordered_map::OrderedMap;
use utils::variant_index::VariantIndex;
Expand Down Expand Up @@ -51,6 +51,8 @@ pub struct Metadata {
dispatch_error_ty: Option<u32>,
/// Details about each of the runtime API traits.
apis: OrderedMap<ArcStr, RuntimeApiMetadataInner>,
/// Allows users to add custom types to the metadata. A map that associates a string key to a `CustomValueMetadata`.
custom: CustomMetadata,
}

impl Metadata {
Expand Down Expand Up @@ -132,6 +134,11 @@ impl Metadata {
})
}

/// Returns custom user defined types
pub fn custom(&self) -> &CustomMetadata {
&self.custom
}

/// Obtain a unique hash representing this metadata or specific parts of it.
pub fn hasher(&self) -> MetadataHasher {
MetadataHasher::new(self)
Expand Down Expand Up @@ -631,6 +638,40 @@ pub struct RuntimeApiMethodParamMetadata {
pub ty: u32,
}

/// Metadata of custom types with custom values, basically the same as `frame_metadata::v15::CustomMetadata<PortableForm>>`.
#[derive(Debug, Clone)]
pub struct CustomMetadata {
map: BTreeMap<String, frame_metadata::v15::CustomValueMetadata<PortableForm>>,
}

impl CustomMetadata {
/// Get a certain [CustomMetadataValue] by its name.
pub fn get(&self, name: &str) -> Option<CustomMetadataValue<'_>> {
self.map.get(name).map(|e| CustomMetadataValue {
type_id: e.ty.id,
data: &e.value,
})
}
}

/// Basically the same as `frame_metadata::v15::CustomValueMetadata<PortableForm>>`, but borrowed.
pub struct CustomMetadataValue<'a> {
type_id: u32,
data: &'a [u8],
}

impl<'a> CustomMetadataValue<'a> {
/// the scale encoded value
pub fn bytes(&self) -> &'a [u8] {
self.data
}

/// the type id in the TypeRegistry
pub fn type_id(&self) -> u32 {
self.type_id
}
}

// Support decoding metadata from the "wire" format directly into this.
// Errors may be lost in the case that the metadata content is somehow invalid.
impl codec::Decode for Metadata {
Expand Down
2 changes: 1 addition & 1 deletion subxt/src/book/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// see LICENSE for license details.

// Dev note; I used the following command to normalize and wrap comments:
// rustfmt +nightly --config wrap_comments=true,comment_width=100,normalize_comments=true subxt/src/book/mod.rs
// rustfmt +nightly --config wrap_comments=true,comment_width=100,normalize_comments=true subxt/src/book/custom_values
// It messed up comments in code blocks though, so be prepared to go and fix those.

//! # The Subxt Guide
Expand Down
54 changes: 54 additions & 0 deletions subxt/src/book/usage/custom_values.rs
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lovely docs; nice one!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two small things you'll need to do to make tests pass:

  • import metadata from the right location (look at other doc examples for this)
  • wrap the doc stuff in a hidden #[tokio::main] async fn main() thing (use # to hide this; check out other examples to see :))

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lovely docs indeed! 🚀

Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.

//! # Custom Values
//!
//! Substrate-based chains can expose custom values in their metadata.
//! Each of these values:
//!
//! - can be accessed by a unique __name__.
//! - refers to a concrete __type__ stored in the metadata.
//! - contains a scale encoded __value__ of that type.
//!
//! ## Getting a custom value
//!
//! Custom values can be accessed via a [`CustomValuesClient`](crate::custom_values::CustomValuesClient).
//! The client exposes an `at` function by which a custom value can be fetched, given an address to this custom value.
//! An address can be as simple as the aforementioned __name__ as a [str]. This will return a dynamic value, that you can manually decode into the type you want.
//! Suppose, the custom types contain a value of type `Foo` under the name `"foo"` you can access it like in this example:
//!
//! ```rust,ignore
//! use subxt::{OnlineClient, PolkadotConfig, ext::{codec::Decode, scale_decode::DecodeAsType}};
//!
//! #[derive(Decode, DecodeAsType, Debug)]
//! struct Foo {
//! n: u8,
//! b: bool,
//! }
//!
//! let api = OnlineClient::<PolkadotConfig>::new().await?;
//! let custom_value_client = api.custom_values();
//! let foo_dynamic = custom_value_client.at("foo")?;
//! let foo: Foo = foo_dynamic.as_type()?;
//!
//! ```
//!
//! Alternatively we also provide a statically generated api for custom values:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess the logic for this is a followup PR, but as long as we don't do another release before the static stuff merges too then it's ok to leave the docs here :)

//!
//! ```rust,ignore
//! #[subxt::subxt(runtime_metadata_path = "some_metadata.scale")]
//! pub mod interface {}
//!
//! let static_address = interface::custom().foo();
//!
//! let api = OnlineClient::<PolkadotConfig>::new().await?;
//! let custom_value_client = api.custom_values();
//!
//! // Now the `at()` function already decodes the value into the Foo type:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
//! // Now the `at()` function already decodes the value into the Foo type:
//! // Now, the `at()` function decodes the value into the correct type:

//! let foo = custom_value_client.at(&static_address)?;
//! ```
//!
//! Note: Names of custom values are converted to __snake_case__ to produce a valid function name during code generation.
//! If there are multiple values where the names would be equal when converted to __snake_case__, functions might not be statically generated for some of them, because of naming conflicts.
//! Make sure names in the custom values of your metadata differ significantly.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
//! Make sure names in the custom values of your metadata differ significantly.

Just because I think it's covered above, and users often won't have the ability to influence chain metadata anyway :)

2 changes: 2 additions & 0 deletions subxt/src/book/usage/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@
//! - [Blocks](blocks)
//! - [Runtime APIs](runtime_apis)
//! - [Unstable Light Client](light_client)
//! - [Custom Values](custom_values)
//!
//! Alternately, [go back](super).

pub mod blocks;
pub mod constants;
pub mod custom_values;
pub mod events;
pub mod light_client;
pub mod runtime_apis;
Expand Down
6 changes: 6 additions & 0 deletions subxt/src/client/lightclient/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use crate::{
client::{OfflineClientT, OnlineClientT},
config::Config,
constants::ConstantsClient,
custom_values::CustomValuesClient,
events::EventsClient,
runtime_api::RuntimeApiClient,
storage::StorageClient,
Expand Down Expand Up @@ -100,6 +101,11 @@ impl<T: Config> LightClient<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)
Expand Down
12 changes: 12 additions & 0 deletions subxt/src/client/offline_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.

use crate::custom_values::CustomValuesClient;
use crate::{
blocks::BlocksClient, constants::ConstantsClient, events::EventsClient,
rpc::types::RuntimeVersion, runtime_api::RuntimeApiClient, storage::StorageClient,
tx::TxClient, Config, Metadata,
};
use derivative::Derivative;

use std::sync::Arc;

/// A trait representing a client that can perform
Expand Down Expand Up @@ -49,6 +51,11 @@ pub trait OfflineClientT<T: Config>: Clone + Send + Sync + 'static {
fn runtime_api(&self) -> RuntimeApiClient<T, Self> {
RuntimeApiClient::new(self.clone())
}

/// Work this custom types.
fn custom_values(&self) -> CustomValuesClient<T, Self> {
CustomValuesClient::new(self.clone())
}
}

/// A client that is capable of performing offline-only operations.
Expand Down Expand Up @@ -121,6 +128,11 @@ impl<T: Config> OfflineClient<T> {
pub fn constants(&self) -> ConstantsClient<T, Self> {
<Self as OfflineClientT<T>>::constants(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 OfflineClient<T> {
Expand Down
8 changes: 8 additions & 0 deletions subxt/src/client/online_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// see LICENSE for license details.

use super::{OfflineClient, OfflineClientT};
use crate::custom_values::CustomValuesClient;
use crate::{
blocks::BlocksClient,
constants::ConstantsClient,
Expand All @@ -18,7 +19,9 @@ use crate::{
Config, Metadata,
};
use derivative::Derivative;

use futures::future;

use std::sync::{Arc, RwLock};

/// A trait representing a client that can perform
Expand Down Expand Up @@ -292,6 +295,11 @@ 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)
Expand Down
21 changes: 21 additions & 0 deletions subxt/src/custom_values/custom_value_address.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
use crate::dynamic::DecodedValueThunk;
use crate::metadata::DecodeWithMetadata;

/// This represents the address of a custom value in in the metadata.
/// Anything, that implements the [CustomValueAddress] trait can be used, to fetch
/// custom values from the metadata.
pub trait CustomValueAddress {
/// The type of the custom value.
type Target: DecodeWithMetadata;

/// the name (key) by which the custom value can be accessed in the metadata.
fn name(&self) -> &str;
}

impl CustomValueAddress for str {
type Target = DecodedValueThunk;

fn name(&self) -> &str {
self
}
}
Loading