subxt_metadata/from/legacy/
mod.rs

1mod portable_registry_builder;
2#[cfg(test)]
3mod tests;
4
5use crate::Metadata;
6use crate::utils::ordered_map::OrderedMap;
7use crate::utils::variant_index::VariantIndex;
8use alloc::borrow::ToOwned;
9use alloc::collections::BTreeMap;
10use alloc::format;
11use alloc::string::ToString;
12use alloc::vec::Vec;
13use frame_decode::constants::{ConstantEntryInfo, ConstantTypeInfo};
14use frame_decode::extrinsics::ExtrinsicTypeInfo;
15use frame_decode::runtime_apis::RuntimeApiTypeInfo;
16use frame_decode::storage::{StorageEntryInfo, StorageTypeInfo};
17use frame_metadata::v15;
18use portable_registry_builder::PortableRegistryBuilder;
19use scale_info_legacy::TypeRegistrySet;
20use scale_info_legacy::type_registry::RuntimeApiName;
21
22/// Options to configure the legacy translating.
23pub(crate) struct Opts {
24    pub sanitize_paths: bool,
25    pub ignore_not_found: bool,
26}
27
28impl Opts {
29    /// Opts tuned for best compatibility translating.
30    pub(crate) fn compat() -> Self {
31        Opts {
32            sanitize_paths: true,
33            ignore_not_found: true,
34        }
35    }
36}
37
38macro_rules! from_historic {
39    ($vis:vis fn $fn_name:ident($metadata:path $(, builtin_index: $builtin_index:ident)? )) => {
40        $vis fn $fn_name(metadata: &$metadata, types: &TypeRegistrySet<'_>, opts: Opts) -> Result<Metadata, Error> {
41            // This will be used to construct our `PortableRegistry` from old-style types.
42            let mut portable_registry_builder = PortableRegistryBuilder::new(&types);
43            portable_registry_builder.ignore_not_found(opts.ignore_not_found);
44            portable_registry_builder.sanitize_paths(opts.sanitize_paths);
45
46
47            // We use this type in a few places to denote that we don't know how to decode it.
48            let unknown_type_id = portable_registry_builder.add_type_str("special::Unknown", None)
49                .map_err(|e| Error::add_type("constructing 'Unknown' type", e))?;
50
51            // Pallet metadata
52            let mut call_index = 0u8;
53            let mut error_index = 0u8;
54            let mut event_index = 0u8;
55
56            let new_pallets = as_decoded(&metadata.modules).iter().map(|pallet| {
57                // In older metadatas, calls and event enums can have different indexes
58                // in a given pallet. Pallets without calls or events don't increment
59                // the respective index for them.
60                //
61                // We assume since errors are non optional, that the pallet index _always_
62                // increments for errors (no `None`s to skip).
63                let (call_index, event_index, error_index) = {
64                    let out = (call_index, event_index, error_index);
65                    if pallet.calls.is_some() {
66                        call_index += 1;
67                    }
68                    if pallet.event.is_some() {
69                        event_index += 1;
70                    }
71                    error_index += 1;
72
73                    out
74                };
75
76                // For v12 and v13 metadata, there is a builtin index for everything in a pallet.
77                // We enable this logic for those metadatas to get the correct index.
78                $(
79                    let $builtin_index = true;
80                    let (call_index, event_index, error_index) = if $builtin_index {
81                        (pallet.index, pallet.index, pallet.index)
82                    } else {
83                        (call_index, event_index, error_index)
84                    };
85                )?
86
87                let pallet_name = as_decoded(&pallet.name).to_string();
88
89                // Storage entries:
90                let storage = pallet.storage.as_ref().map(|s| {
91                    let storage = as_decoded(s);
92                    let prefix = as_decoded(&storage.prefix);
93                    let entries = metadata.storage_in_pallet(&pallet_name).map(|entry_name| {
94                        let info = metadata
95                            .storage_info(&pallet_name, &entry_name)
96                            .map_err(|e| Error::StorageInfoError(e.into_owned()))?;
97                        let entry_name = entry_name.into_owned();
98
99                        let info = info.map_ids(|old_id| {
100                            portable_registry_builder.add_type(old_id)
101                        }).map_err(|e| {
102                            let ctx = format!("adding type used in storage entry {pallet_name}.{entry_name}");
103                            Error::add_type(ctx, e)
104                        })?;
105
106                        let entry = crate::StorageEntryMetadata {
107                            name: entry_name.clone(),
108                            info: info.into_owned(),
109                            // We don't expose docs via our storage info yet.
110                            docs: Vec::new(),
111                        };
112
113                        Ok((entry_name, entry))
114                    }).collect::<Result<OrderedMap<_, _>, _>>()?;
115                    Ok(crate::StorageMetadata {
116                        prefix: prefix.clone(),
117                        entries,
118                    })
119                }).transpose()?;
120
121                // Pallet error type is just a builtin type:
122                let error_ty = portable_registry_builder.add_type_str(&format!("builtin::module::error::{pallet_name}"), None)
123                    .map_err(|e| {
124                        let ctx = format!("converting the error enum for pallet {pallet_name}");
125                        Error::add_type(ctx, e)
126                    })?;
127
128                // Pallet calls also just a builtin type:
129                let call_ty = pallet.calls.as_ref().map(|_| {
130                    portable_registry_builder.add_type_str(&format!("builtin::module::call::{pallet_name}"), None)
131                        .map_err(|e| {
132                            let ctx = format!("converting the call enum for pallet {pallet_name}");
133                            Error::add_type(ctx, e)
134                        })
135                }).transpose()?;
136
137                // Pallet events also just a builtin type:
138                let event_ty = pallet.event.as_ref().map(|_| {
139                    portable_registry_builder.add_type_str(&format!("builtin::module::event::{pallet_name}"), None)
140                        .map_err(|e| {
141                            let ctx = format!("converting the event enum for pallet {pallet_name}");
142                            Error::add_type(ctx, e)
143                        })
144                }).transpose()?;
145
146                let call_variant_index =
147                    VariantIndex::build(call_ty, portable_registry_builder.types());
148                let error_variant_index =
149                    VariantIndex::build(Some(error_ty), portable_registry_builder.types());
150                let event_variant_index =
151                    VariantIndex::build(event_ty, portable_registry_builder.types());
152
153                let constants = metadata.constants_in_pallet(&pallet_name).map(|name| {
154                    let name = name.into_owned();
155                    let info = metadata.constant_info(&pallet_name, &name)
156                        .map_err(|e| Error::ConstantInfoError(e.into_owned()))?;
157                    let new_type_id = portable_registry_builder.add_type(info.type_id)
158                        .map_err(|e| {
159                            let ctx = format!("converting the constant {name} for pallet {pallet_name}");
160                            Error::add_type(ctx, e)
161                        })?;
162
163                    let constant = crate::ConstantMetadata {
164                        name: name.clone(),
165                        ty: new_type_id,
166                        value: info.bytes.to_vec(),
167                        // We don't expose docs via our constant info yet.
168                        docs: Vec::new(),
169                    };
170
171                    Ok((name, constant))
172                }).collect::<Result<_,Error>>()?;
173
174                let pallet_metadata = crate::PalletMetadataInner {
175                    name: pallet_name.clone(),
176                    call_index,
177                    event_index,
178                    error_index,
179                    storage,
180                    error_ty: Some(error_ty),
181                    call_ty,
182                    event_ty,
183                    call_variant_index,
184                    error_variant_index,
185                    event_variant_index,
186                    constants,
187                    view_functions: Default::default(),
188                    associated_types: Default::default(),
189                    // Pallets did not have docs prior to V15.
190                    docs: Default::default(),
191                };
192
193                Ok((pallet_name, pallet_metadata))
194            }).collect::<Result<OrderedMap<_,_>,Error>>()?;
195
196            // Extrinsic metadata
197            let new_extrinsic = {
198                let signature_info = metadata
199                    .extrinsic_signature_info()
200                    .map_err(|e| Error::ExtrinsicInfoError(e.into_owned()))?;
201
202                let address_ty_id = portable_registry_builder.add_type(signature_info.address_id)
203                    .map_err(|_| Error::CannotFindAddressType)?;
204
205                let signature_ty_id = portable_registry_builder.add_type(signature_info.signature_id)
206                    .map_err(|_| Error::CannotFindCallType)?;
207
208                let transaction_extensions = metadata
209                    .extrinsic_extension_info(None)
210                    .map_err(|e| Error::ExtrinsicInfoError(e.into_owned()))?
211                    .extension_ids
212                    .into_iter()
213                    .map(|ext| {
214                        let ext_name = ext.name.into_owned();
215                        let ext_type = portable_registry_builder.add_type(ext.id)
216                            .map_err(|e| {
217                                let ctx = format!("converting the signed extension {ext_name}");
218                                Error::add_type(ctx, e)
219                            })?;
220
221                        Ok(crate::TransactionExtensionMetadataInner {
222                            identifier: ext_name,
223                            extra_ty: ext_type,
224                            // This only started existing in V14+ metadata, but in any case,
225                            // we don't need to know how to decode the signed payload for
226                            // historic blocks (hopefully), so set to unknown.
227                            additional_ty: unknown_type_id.into()
228                        })
229                    })
230                    .collect::<Result<Vec<_>,Error>>()?;
231
232                let transaction_extensions_by_version = BTreeMap::from_iter([(
233                    0,
234                    (0..transaction_extensions.len() as u32).collect()
235                )]);
236
237                crate::ExtrinsicMetadata {
238                    address_ty: address_ty_id.into(),
239                    signature_ty: signature_ty_id.into(),
240                    supported_versions: Vec::from_iter([4]),
241                    transaction_extensions,
242                    transaction_extensions_by_version,
243                }
244            };
245
246            // Outer enum types
247            let outer_enums = crate::OuterEnumsMetadata {
248                call_enum_ty: portable_registry_builder.add_type_str("builtin::Call", None)
249                    .map_err(|e| {
250                        let ctx = format!("constructing the 'builtin::Call' type to put in the OuterEnums metadata");
251                        Error::add_type(ctx, e)
252                    })?,
253                event_enum_ty: portable_registry_builder.add_type_str("builtin::Event", None)
254                    .map_err(|e| {
255                        let ctx = format!("constructing the 'builtin::Event' type to put in the OuterEnums metadata");
256                        Error::add_type(ctx, e)
257                    })?,
258                error_enum_ty: portable_registry_builder.add_type_str("builtin::Error", None)
259                    .map_err(|e| {
260                        let ctx = format!("constructing the 'builtin::Error' type to put in the OuterEnums metadata");
261                        Error::add_type(ctx, e)
262                    })?,
263            };
264
265            // These are all the same in V13, but be explicit anyway for clarity.
266            let pallets_by_call_index = new_pallets
267                .values()
268                .iter()
269                .enumerate()
270                .map(|(idx,p)| (p.call_index, idx))
271                .collect();
272            let pallets_by_error_index = new_pallets
273                .values()
274                .iter()
275                .enumerate()
276                .map(|(idx,p)| (p.error_index, idx))
277                .collect();
278            let pallets_by_event_index = new_pallets
279                .values()
280                .iter()
281                .enumerate()
282                .map(|(idx,p)| (p.event_index, idx))
283                .collect();
284
285            // This is optional in the sense that Subxt will return an error if it needs to decode this type,
286            // and I think for historic metadata we wouldn't end up down that path anyway. Historic metadata
287            // tends to call it just "DispatchError" but search more specific paths first.
288            let dispatch_error_ty = portable_registry_builder
289                .try_add_type_str("hardcoded::DispatchError", None)
290                .or_else(|| portable_registry_builder.try_add_type_str("sp_runtime::DispatchError", None))
291                .or_else(|| portable_registry_builder.try_add_type_str("DispatchError", None))
292                .transpose()
293                .map_err(|e| Error::add_type("constructing DispatchError", e))?;
294
295            // Runtime API definitions live with type definitions.
296            let apis = type_registry_to_runtime_apis(&types, &mut portable_registry_builder)?;
297
298            Ok(crate::Metadata {
299                types: portable_registry_builder.finish(),
300                pallets: new_pallets,
301                pallets_by_call_index,
302                pallets_by_error_index,
303                pallets_by_event_index,
304                extrinsic: new_extrinsic,
305                outer_enums,
306                dispatch_error_ty,
307                apis,
308                // Nothing custom existed in V13
309                custom: v15::CustomMetadata { map: Default::default() },
310            })
311    }}
312}
313
314from_historic!(pub fn from_v13(frame_metadata::v13::RuntimeMetadataV13, builtin_index: yes));
315from_historic!(pub fn from_v12(frame_metadata::v12::RuntimeMetadataV12, builtin_index: yes));
316from_historic!(pub fn from_v11(frame_metadata::v11::RuntimeMetadataV11));
317from_historic!(pub fn from_v10(frame_metadata::v10::RuntimeMetadataV10));
318from_historic!(pub fn from_v9(frame_metadata::v9::RuntimeMetadataV9));
319from_historic!(pub fn from_v8(frame_metadata::v8::RuntimeMetadataV8));
320
321fn as_decoded<A, B>(item: &frame_metadata::decode_different::DecodeDifferent<A, B>) -> &B {
322    match item {
323        frame_metadata::decode_different::DecodeDifferent::Encode(_a) => {
324            panic!("Expecting decoded data")
325        }
326        frame_metadata::decode_different::DecodeDifferent::Decoded(b) => b,
327    }
328}
329
330// Obtain Runtime API information from some type registry.
331pub fn type_registry_to_runtime_apis(
332    types: &TypeRegistrySet<'_>,
333    portable_registry_builder: &mut PortableRegistryBuilder,
334) -> Result<OrderedMap<String, crate::RuntimeApiMetadataInner>, Error> {
335    let mut apis = OrderedMap::new();
336    let mut trait_name = "";
337    let mut trait_methods = OrderedMap::new();
338
339    for api in types.runtime_apis() {
340        match api {
341            RuntimeApiName::Trait(name) => {
342                if !trait_methods.is_empty() {
343                    apis.push_insert(
344                        trait_name.into(),
345                        crate::RuntimeApiMetadataInner {
346                            name: trait_name.into(),
347                            methods: trait_methods,
348                            docs: Vec::new(),
349                        },
350                    );
351                }
352                trait_methods = OrderedMap::new();
353                trait_name = name;
354            }
355            RuntimeApiName::Method(name) => {
356                let info = types
357                    .runtime_api_info(trait_name, name)
358                    .map_err(|e| Error::RuntimeApiInfoError(e.into_owned()))?;
359
360                let info = info.map_ids(|id| {
361                    portable_registry_builder.add_type(id).map_err(|e| {
362                        let c = format!("converting type for runtime API {trait_name}.{name}");
363                        Error::add_type(c, e)
364                    })
365                })?;
366
367                trait_methods.push_insert(
368                    name.to_owned(),
369                    crate::RuntimeApiMethodMetadataInner {
370                        name: name.into(),
371                        info,
372                        docs: Vec::new(),
373                    },
374                );
375            }
376        }
377    }
378
379    Ok(apis)
380}
381
382/// An error encountered converting some legacy metadata to our internal format.
383#[allow(missing_docs)]
384#[derive(Debug, thiserror::Error)]
385pub enum Error {
386    /// Cannot add a type.
387    #[error("Cannot add type ({context}): {error}")]
388    AddTypeError {
389        context: String,
390        error: portable_registry_builder::PortableRegistryAddTypeError,
391    },
392    #[error("Cannot find 'hardcoded::ExtrinsicAddress' type in legacy types")]
393    CannotFindAddressType,
394    #[error("Cannot find 'hardcoded::ExtrinsicSignature' type in legacy types")]
395    CannotFindSignatureType,
396    #[error(
397        "Cannot find 'builtin::Call' type in legacy types (this should have been automatically added)"
398    )]
399    CannotFindCallType,
400    #[error("Cannot obtain the storage information we need to convert storage entries")]
401    StorageInfoError(frame_decode::storage::StorageInfoError<'static>),
402    #[error("Cannot obtain the extrinsic information we need to convert transaction extensions")]
403    ExtrinsicInfoError(frame_decode::extrinsics::ExtrinsicInfoError<'static>),
404    #[error("Cannot obtain the Runtime API information we need")]
405    RuntimeApiInfoError(frame_decode::runtime_apis::RuntimeApiInfoError<'static>),
406    #[error("Cannot obtain the Constant information we need")]
407    ConstantInfoError(frame_decode::constants::ConstantInfoError<'static>),
408}
409
410impl Error {
411    /// A shorthand for the [`Error::AddTypeError`] variant.
412    fn add_type(
413        context: impl Into<String>,
414        error: impl Into<portable_registry_builder::PortableRegistryAddTypeError>,
415    ) -> Self {
416        Error::AddTypeError {
417            context: context.into(),
418            error: error.into(),
419        }
420    }
421}