diff --git a/historic/CHANGELOG.md b/historic/CHANGELOG.md index afe5cafc26..a7683f2889 100644 --- a/historic/CHANGELOG.md +++ b/historic/CHANGELOG.md @@ -4,6 +4,10 @@ This is separate from the Subxt changelog as subxt-historic is currently releasa Eventually this project will merge with Subxt and no longer exist, but until then it's being maintained and updated where needed. +## 0.0.8 (2025-12-04) + +Expose `ClientAtBlock::resolver()`. This hands back a type resolver which is capable of resolving type IDs given by the `.visit()` methods on extrinsic fields and storage values. The extrinsics example has been modified to show how this can be used. + ## 0.0.7 (2025-12-03) Expose `OfflineClientAtBlock`, `OfflineClientAtBlockT`, `OnlinelientAtBlock`, `OnlineClientAtBlockT`. diff --git a/historic/examples/extrinsics.rs b/historic/examples/extrinsics.rs index 13dc2631aa..b3f13905c3 100644 --- a/historic/examples/extrinsics.rs +++ b/historic/examples/extrinsics.rs @@ -48,7 +48,8 @@ async fn main() -> Result<(), Box), - Variant(String, VariantFields), + VariantWithoutData(String), + VariantWithData(String, VariantFields), } pub enum VariantFields { @@ -198,23 +200,23 @@ mod value { Decode(#[from] scale_decode::visitor::DecodeError), #[error("Cannot decode bit sequence: {0}")] CannotDecodeBitSequence(codec::Error), + #[error("Cannot resolve variant type information: {0}")] + CannotResolveVariantType(String), } /// This is a visitor which obtains type names. - pub struct GetValue { - marker: core::marker::PhantomData, + pub struct GetValue<'r, R> { + resolver: &'r R, } - impl GetValue { + impl<'r, R> GetValue<'r, R> { /// Construct our TypeName visitor. - pub fn new() -> Self { - GetValue { - marker: core::marker::PhantomData, - } + pub fn new(resolver: &'r R) -> Self { + GetValue { resolver } } } - impl Visitor for GetValue { + impl<'r, R: TypeResolver> Visitor for GetValue<'r, R> { type Value<'scale, 'resolver> = Value; type Error = ValueError; type TypeResolver = R; @@ -346,7 +348,11 @@ mod value { values: &mut Array<'scale, 'resolver, Self::TypeResolver>, _type_id: TypeIdFor, ) -> Result, Self::Error> { - Ok(Value::Array(to_array(values.remaining(), values)?)) + Ok(Value::Array(to_array( + self.resolver, + values.remaining(), + values, + )?)) } fn visit_sequence<'scale, 'resolver>( @@ -354,7 +360,11 @@ mod value { values: &mut Sequence<'scale, 'resolver, Self::TypeResolver>, _type_id: TypeIdFor, ) -> Result, Self::Error> { - Ok(Value::Array(to_array(values.remaining(), values)?)) + Ok(Value::Array(to_array( + self.resolver, + values.remaining(), + values, + )?)) } fn visit_str<'scale, 'resolver>( @@ -370,7 +380,11 @@ mod value { values: &mut Tuple<'scale, 'resolver, Self::TypeResolver>, _type_id: TypeIdFor, ) -> Result, Self::Error> { - Ok(Value::Array(to_array(values.remaining(), values)?)) + Ok(Value::Array(to_array( + self.resolver, + values.remaining(), + values, + )?)) } fn visit_bitsequence<'scale, 'resolver>( @@ -401,7 +415,7 @@ mod value { } // Reuse logic for decoding variant fields: - match to_variant_fieldish(value)? { + match to_variant_fieldish(self.resolver, value)? { VariantFields::Named(s) => Ok(Value::Struct(s)), VariantFields::Unnamed(a) => Ok(Value::Array(a)), } @@ -410,20 +424,50 @@ mod value { fn visit_variant<'scale, 'resolver>( self, value: &mut Variant<'scale, 'resolver, Self::TypeResolver>, - _type_id: TypeIdFor, + type_id: TypeIdFor, ) -> Result, Self::Error> { + // Because we have access to a type resolver on self, we can + // look up the type IDs we're given back and base decode decisions + // on them. here we see whether the enum type has any data attached: + let has_data_visitor = scale_type_resolver::visitor::new((), |_, _| false) + .visit_variant(|_, _, variants| { + for mut variant in variants { + if variant.fields.next().is_some() { + return true; + } + } + false + }); + + // Do any variants have data in this enum type? + let has_data = self + .resolver + .resolve_type(type_id, has_data_visitor) + .map_err(|e| ValueError::CannotResolveVariantType(e.to_string()))?; + let name = value.name().to_owned(); - let fields = to_variant_fieldish(value.fields())?; - Ok(Value::Variant(name, fields)) + + // base our decoding on whether any data in enum type. + if has_data { + let fields = to_variant_fieldish(self.resolver, value.fields())?; + Ok(Value::VariantWithData(name, fields)) + } else { + Ok(Value::VariantWithoutData(name)) + } } } - fn to_variant_fieldish<'scale, 'resolver, R: TypeResolver>( + fn to_variant_fieldish<'r, 'scale, 'resolver, R: TypeResolver>( + resolver: &'r R, value: &mut Composite<'scale, 'resolver, R>, ) -> Result { // If fields are unnamed, treat as array: if value.fields().iter().all(|f| f.name.is_none()) { - return Ok(VariantFields::Unnamed(to_array(value.remaining(), value)?)); + return Ok(VariantFields::Unnamed(to_array( + resolver, + value.remaining(), + value, + )?)); } // Otherwise object: @@ -431,18 +475,19 @@ mod value { for field in value { let field = field?; let name = field.name().unwrap().to_string(); - let value = field.decode_with_visitor(GetValue::new())?; + let value = field.decode_with_visitor(GetValue::new(resolver))?; out.insert(name, value); } Ok(VariantFields::Named(out)) } - fn to_array<'scale, 'resolver, R: TypeResolver>( + fn to_array<'r, 'scale, 'resolver, R: TypeResolver>( + resolver: &'r R, len: usize, mut values: impl scale_decode::visitor::DecodeItemIterator<'scale, 'resolver, R>, ) -> Result, ValueError> { let mut out = Vec::with_capacity(len); - while let Some(value) = values.decode_item(GetValue::new()) { + while let Some(value) = values.decode_item(GetValue::new(resolver)) { out.push(value?); } Ok(out) diff --git a/historic/src/client.rs b/historic/src/client.rs index c0a2e2d419..421e36a28c 100644 --- a/historic/src/client.rs +++ b/historic/src/client.rs @@ -4,6 +4,7 @@ mod online_client; use crate::config::Config; use crate::extrinsics::ExtrinsicsClient; use crate::storage::StorageClient; +use crate::utils::AnyResolver; use frame_metadata::RuntimeMetadata; use std::marker::PhantomData; @@ -45,4 +46,28 @@ where pub fn metadata(&self) -> &RuntimeMetadata { self.client.metadata() } + + /// Return something which implements [`scale_type_resolver::TypeResolver`] and + /// can be used in conjnction with type IDs in `.visit` methods. + pub fn resolver(&self) -> AnyResolver<'_, 'client> { + match self.client.metadata() { + RuntimeMetadata::V0(_) + | RuntimeMetadata::V1(_) + | RuntimeMetadata::V2(_) + | RuntimeMetadata::V3(_) + | RuntimeMetadata::V4(_) + | RuntimeMetadata::V5(_) + | RuntimeMetadata::V6(_) + | RuntimeMetadata::V7(_) + | RuntimeMetadata::V8(_) + | RuntimeMetadata::V9(_) + | RuntimeMetadata::V10(_) + | RuntimeMetadata::V11(_) + | RuntimeMetadata::V12(_) + | RuntimeMetadata::V13(_) => AnyResolver::B(self.client.legacy_types()), + RuntimeMetadata::V14(m) => AnyResolver::A(&m.types), + RuntimeMetadata::V15(m) => AnyResolver::A(&m.types), + RuntimeMetadata::V16(m) => AnyResolver::A(&m.types), + } + } } diff --git a/historic/src/extrinsics/extrinsic_call.rs b/historic/src/extrinsics/extrinsic_call.rs index 3f767b58a2..83f346c0b9 100644 --- a/historic/src/extrinsics/extrinsic_call.rs +++ b/historic/src/extrinsics/extrinsic_call.rs @@ -54,7 +54,7 @@ impl<'extrinsics, 'atblock> ExtrinsicCall<'extrinsics, 'atblock> { pub struct ExtrinsicCallFields<'extrinsics, 'atblock> { all_bytes: &'extrinsics [u8], info: &'extrinsics AnyExtrinsicInfo<'atblock>, - resolver: AnyResolver<'atblock>, + resolver: AnyResolver<'atblock, 'atblock>, } impl<'extrinsics, 'atblock> ExtrinsicCallFields<'extrinsics, 'atblock> { @@ -135,7 +135,7 @@ impl<'extrinsics, 'atblock> ExtrinsicCallFields<'extrinsics, 'atblock> { pub struct ExtrinsicCallField<'fields, 'extrinsics, 'atblock> { field_bytes: &'extrinsics [u8], info: AnyExtrinsicCallFieldInfo<'extrinsics, 'atblock>, - resolver: &'fields AnyResolver<'atblock>, + resolver: &'fields AnyResolver<'atblock, 'atblock>, } enum AnyExtrinsicCallFieldInfo<'extrinsics, 'atblock> { @@ -172,7 +172,9 @@ impl<'fields, 'extrinsics, 'atblock> ExtrinsicCallField<'fields, 'extrinsics, 'a /// Visit the given field with a [`scale_decode::visitor::Visitor`]. This is like a lower level /// version of [`ExtrinsicCallField::decode_as`], as the visitor is able to preserve lifetimes /// and has access to more type information than is available via [`ExtrinsicCallField::decode_as`]. - pub fn visit>>( + pub fn visit< + V: scale_decode::visitor::Visitor>, + >( &self, visitor: V, ) -> Result, V::Error> { diff --git a/historic/src/storage/storage_value.rs b/historic/src/storage/storage_value.rs index c19bebaa67..933cf3a233 100644 --- a/historic/src/storage/storage_value.rs +++ b/historic/src/storage/storage_value.rs @@ -10,7 +10,7 @@ use std::sync::Arc; pub struct StorageValue<'atblock> { pub(crate) info: Arc>, bytes: Cow<'atblock, [u8]>, - resolver: AnyResolver<'atblock>, + resolver: AnyResolver<'atblock, 'atblock>, } impl<'atblock> StorageValue<'atblock> { @@ -41,7 +41,9 @@ impl<'atblock> StorageValue<'atblock> { /// Visit the given field with a [`scale_decode::visitor::Visitor`]. This is like a lower level /// version of [`StorageValue::decode_as`], as the visitor is able to preserve lifetimes /// and has access to more type information than is available via [`StorageValue::decode_as`]. - pub fn visit>>( + pub fn visit< + V: scale_decode::visitor::Visitor>, + >( &self, visitor: V, ) -> Result, V::Error> { diff --git a/historic/src/utils/any_resolver.rs b/historic/src/utils/any_resolver.rs index 8d92bcf4bd..c65b6a1b46 100644 --- a/historic/src/utils/any_resolver.rs +++ b/historic/src/utils/any_resolver.rs @@ -3,10 +3,8 @@ use scale_info_legacy::LookupName; use scale_type_resolver::ResolvedTypeVisitor; /// A type resolver which could either be for modern or historic resolving. -pub type AnyResolver<'resolver> = Either< - &'resolver scale_info::PortableRegistry, - &'resolver scale_info_legacy::TypeRegistrySet<'resolver>, ->; +pub type AnyResolver<'a, 'b> = + Either<&'a scale_info::PortableRegistry, &'a scale_info_legacy::TypeRegistrySet<'b>>; /// A type ID which is either a modern or historic ID. pub type AnyTypeId = Either; @@ -60,7 +58,7 @@ pub enum AnyResolverError { ScaleInfoLegacy(scale_info_legacy::type_registry::TypeRegistryResolveError), } -impl<'resolver> scale_type_resolver::TypeResolver for AnyResolver<'resolver> { +impl<'a, 'b> scale_type_resolver::TypeResolver for AnyResolver<'a, 'b> { type TypeId = AnyTypeId; type Error = AnyResolverError; diff --git a/testing/integration-tests/src/full_client/codegen/documentation.rs b/testing/integration-tests/src/full_client/codegen/documentation.rs index 3de4215142..63130ef444 100644 --- a/testing/integration-tests/src/full_client/codegen/documentation.rs +++ b/testing/integration-tests/src/full_client/codegen/documentation.rs @@ -99,12 +99,18 @@ fn interface_docs(should_gen_docs: bool) -> Vec { #[test] fn check_documentation() { - // Inspect metadata recursively and obtain all associated documentation. + // Inspect metadata and obtain all associated documentation. let raw_docs = metadata_docs(); // Obtain documentation from the generated API. let runtime_docs = interface_docs(true); for raw in raw_docs.iter() { + if raw.contains(|c: char| !c.is_ascii()) { + // Ignore lines containing on-ascii chars; they are encoded currently + // as "\u{nn}" which doesn't match their input which is the raw non-ascii + // char. + continue; + } assert!( runtime_docs.contains(raw), "Documentation not present in runtime API: {raw}" @@ -114,7 +120,7 @@ fn check_documentation() { #[test] fn check_no_documentation() { - // Inspect metadata recursively and obtain all associated documentation. + // Inspect metadata and obtain all associated documentation. let raw_docs = metadata_docs(); // Obtain documentation from the generated API. let runtime_docs = interface_docs(false);