subxt_metadata/from/
v14.rs

1// Copyright 2019-2025 Parity Technologies (UK) Ltd.
2// This file is dual-licensed as Apache-2.0 or GPL-3.0.
3// see LICENSE for license details.
4
5use super::TryFromError;
6
7use crate::utils::variant_index::VariantIndex;
8use crate::{
9    ArcStr, ConstantMetadata, CustomMetadataInner, ExtrinsicMetadata, Metadata, OuterEnumsMetadata,
10    PalletMetadataInner, StorageEntryMetadata, StorageEntryModifier, StorageEntryType,
11    StorageHasher, StorageMetadata, TransactionExtensionMetadataInner,
12    utils::ordered_map::OrderedMap,
13};
14use alloc::borrow::ToOwned;
15use alloc::collections::BTreeMap;
16use alloc::string::String;
17use alloc::vec::Vec;
18use alloc::{format, vec};
19use frame_metadata::v14;
20use hashbrown::HashMap;
21use scale_info::form::PortableForm;
22
23impl TryFrom<v14::RuntimeMetadataV14> for Metadata {
24    type Error = TryFromError;
25    fn try_from(mut m: v14::RuntimeMetadataV14) -> Result<Self, TryFromError> {
26        let outer_enums = generate_outer_enums(&mut m)?;
27        let missing_extrinsic_type_ids = MissingExtrinsicTypeIds::generate_from(&m)?;
28
29        let mut pallets = OrderedMap::new();
30        let mut pallets_by_index = HashMap::new();
31        for (pos, p) in m.pallets.into_iter().enumerate() {
32            let name: ArcStr = p.name.into();
33
34            let storage = p.storage.map(|s| StorageMetadata {
35                prefix: s.prefix,
36                entries: s
37                    .entries
38                    .into_iter()
39                    .map(|s| {
40                        let name: ArcStr = s.name.clone().into();
41                        (name.clone(), from_storage_entry_metadata(name, s))
42                    })
43                    .collect(),
44            });
45            let constants = p.constants.into_iter().map(|c| {
46                let name: ArcStr = c.name.clone().into();
47                (name.clone(), from_constant_metadata(name, c))
48            });
49
50            let call_variant_index =
51                VariantIndex::build(p.calls.as_ref().map(|c| c.ty.id), &m.types);
52            let error_variant_index =
53                VariantIndex::build(p.error.as_ref().map(|e| e.ty.id), &m.types);
54            let event_variant_index =
55                VariantIndex::build(p.event.as_ref().map(|e| e.ty.id), &m.types);
56
57            pallets_by_index.insert(p.index, pos);
58            pallets.push_insert(
59                name.clone(),
60                PalletMetadataInner {
61                    name,
62                    index: p.index,
63                    storage,
64                    call_ty: p.calls.map(|c| c.ty.id),
65                    call_variant_index,
66                    event_ty: p.event.map(|e| e.ty.id),
67                    event_variant_index,
68                    error_ty: p.error.map(|e| e.ty.id),
69                    error_variant_index,
70                    constants: constants.collect(),
71                    view_functions: Default::default(),
72                    associated_types: Default::default(),
73                    docs: vec![],
74                },
75            );
76        }
77
78        let dispatch_error_ty = m
79            .types
80            .types
81            .iter()
82            .find(|ty| ty.ty.path.segments == ["sp_runtime", "DispatchError"])
83            .map(|ty| ty.id);
84
85        Ok(Metadata {
86            types: m.types,
87            pallets,
88            pallets_by_index,
89            extrinsic: from_extrinsic_metadata(m.extrinsic, missing_extrinsic_type_ids),
90            dispatch_error_ty,
91            outer_enums: OuterEnumsMetadata {
92                call_enum_ty: outer_enums.call_enum_ty.id,
93                event_enum_ty: outer_enums.event_enum_ty.id,
94                error_enum_ty: outer_enums.error_enum_ty.id,
95            },
96            apis: Default::default(),
97            custom: CustomMetadataInner {
98                map: Default::default(),
99            },
100        })
101    }
102}
103
104fn from_signed_extension_metadata(
105    value: v14::SignedExtensionMetadata<PortableForm>,
106) -> TransactionExtensionMetadataInner {
107    TransactionExtensionMetadataInner {
108        identifier: value.identifier,
109        extra_ty: value.ty.id,
110        additional_ty: value.additional_signed.id,
111    }
112}
113
114fn from_extrinsic_metadata(
115    value: v14::ExtrinsicMetadata<PortableForm>,
116    missing_ids: MissingExtrinsicTypeIds,
117) -> ExtrinsicMetadata {
118    let transaction_extensions: Vec<_> = value
119        .signed_extensions
120        .into_iter()
121        .map(from_signed_extension_metadata)
122        .collect();
123
124    let transaction_extension_indexes = (0..transaction_extensions.len() as u32).collect();
125
126    ExtrinsicMetadata {
127        supported_versions: vec![value.version],
128        transaction_extensions,
129        address_ty: missing_ids.address,
130        signature_ty: missing_ids.signature,
131        transaction_extensions_by_version: BTreeMap::from_iter([(
132            0,
133            transaction_extension_indexes,
134        )]),
135    }
136}
137
138fn from_storage_hasher(value: v14::StorageHasher) -> StorageHasher {
139    match value {
140        v14::StorageHasher::Blake2_128 => StorageHasher::Blake2_128,
141        v14::StorageHasher::Blake2_256 => StorageHasher::Blake2_256,
142        v14::StorageHasher::Blake2_128Concat => StorageHasher::Blake2_128Concat,
143        v14::StorageHasher::Twox128 => StorageHasher::Twox128,
144        v14::StorageHasher::Twox256 => StorageHasher::Twox256,
145        v14::StorageHasher::Twox64Concat => StorageHasher::Twox64Concat,
146        v14::StorageHasher::Identity => StorageHasher::Identity,
147    }
148}
149
150fn from_storage_entry_type(value: v14::StorageEntryType<PortableForm>) -> StorageEntryType {
151    match value {
152        v14::StorageEntryType::Plain(ty) => StorageEntryType::Plain(ty.id),
153        v14::StorageEntryType::Map {
154            hashers,
155            key,
156            value,
157        } => StorageEntryType::Map {
158            hashers: hashers.into_iter().map(from_storage_hasher).collect(),
159            key_ty: key.id,
160            value_ty: value.id,
161        },
162    }
163}
164
165fn from_storage_entry_modifier(value: v14::StorageEntryModifier) -> StorageEntryModifier {
166    match value {
167        v14::StorageEntryModifier::Optional => StorageEntryModifier::Optional,
168        v14::StorageEntryModifier::Default => StorageEntryModifier::Default,
169    }
170}
171
172fn from_storage_entry_metadata(
173    name: ArcStr,
174    s: v14::StorageEntryMetadata<PortableForm>,
175) -> StorageEntryMetadata {
176    StorageEntryMetadata {
177        name,
178        modifier: from_storage_entry_modifier(s.modifier),
179        entry_type: from_storage_entry_type(s.ty),
180        default: s.default,
181        docs: s.docs,
182    }
183}
184
185fn from_constant_metadata(
186    name: ArcStr,
187    s: v14::PalletConstantMetadata<PortableForm>,
188) -> ConstantMetadata {
189    ConstantMetadata {
190        name,
191        ty: s.ty.id,
192        value: s.value,
193        docs: s.docs,
194    }
195}
196
197fn generate_outer_enums(
198    metadata: &mut v14::RuntimeMetadataV14,
199) -> Result<frame_metadata::v15::OuterEnums<scale_info::form::PortableForm>, TryFromError> {
200    let outer_enums = OuterEnums::find_in(&metadata.types);
201
202    let Some(call_enum_id) = outer_enums.call_ty else {
203        return Err(TryFromError::TypeNameNotFound("RuntimeCall".into()));
204    };
205    let Some(event_type_id) = outer_enums.event_ty else {
206        return Err(TryFromError::TypeNameNotFound("RuntimeEvent".into()));
207    };
208    let error_type_id = if let Some(id) = outer_enums.error_ty {
209        id
210    } else {
211        let call_enum = &metadata.types.types[call_enum_id as usize];
212        let mut error_path = call_enum.ty.path.segments.clone();
213
214        let Some(last) = error_path.last_mut() else {
215            return Err(TryFromError::InvalidTypePath("RuntimeCall".into()));
216        };
217        "RuntimeError".clone_into(last);
218        generate_outer_error_enum_type(metadata, error_path)
219    };
220
221    Ok(frame_metadata::v15::OuterEnums {
222        call_enum_ty: call_enum_id.into(),
223        event_enum_ty: event_type_id.into(),
224        error_enum_ty: error_type_id.into(),
225    })
226}
227
228/// Generates an outer `RuntimeError` enum type and adds it to the metadata.
229///
230/// Returns the id of the generated type from the registry.
231fn generate_outer_error_enum_type(
232    metadata: &mut v14::RuntimeMetadataV14,
233    path_segments: Vec<String>,
234) -> u32 {
235    let variants: Vec<_> = metadata
236        .pallets
237        .iter()
238        .filter_map(|pallet| {
239            let error = pallet.error.as_ref()?;
240            let path = format!("{}Error", pallet.name);
241            let ty = error.ty.id.into();
242
243            Some(scale_info::Variant {
244                name: pallet.name.clone(),
245                fields: vec![scale_info::Field {
246                    name: None,
247                    ty,
248                    type_name: Some(path),
249                    docs: vec![],
250                }],
251                index: pallet.index,
252                docs: vec![],
253            })
254        })
255        .collect();
256
257    let enum_type = scale_info::Type {
258        path: scale_info::Path {
259            segments: path_segments,
260        },
261        type_params: vec![],
262        type_def: scale_info::TypeDef::Variant(scale_info::TypeDefVariant { variants }),
263        docs: vec![],
264    };
265
266    let enum_type_id = metadata.types.types.len() as u32;
267
268    metadata.types.types.push(scale_info::PortableType {
269        id: enum_type_id,
270        ty: enum_type,
271    });
272
273    enum_type_id
274}
275
276/// The type IDs extracted from the metadata that represent the
277/// generic type parameters passed to the `UncheckedExtrinsic` from
278/// the substrate-based chain.
279#[derive(Clone, Copy)]
280struct MissingExtrinsicTypeIds {
281    address: u32,
282    signature: u32,
283}
284
285impl MissingExtrinsicTypeIds {
286    fn generate_from(
287        metadata: &v14::RuntimeMetadataV14,
288    ) -> Result<MissingExtrinsicTypeIds, TryFromError> {
289        const ADDRESS: &str = "Address";
290        const SIGNATURE: &str = "Signature";
291
292        let extrinsic_id = metadata.extrinsic.ty.id;
293        let Some(extrinsic_ty) = metadata.types.resolve(extrinsic_id) else {
294            return Err(TryFromError::TypeNotFound(extrinsic_id));
295        };
296
297        let find_param = |name: &'static str| -> Option<u32> {
298            extrinsic_ty
299                .type_params
300                .iter()
301                .find(|param| param.name.as_str() == name)
302                .and_then(|param| param.ty.as_ref())
303                .map(|ty| ty.id)
304        };
305
306        let Some(address) = find_param(ADDRESS) else {
307            return Err(TryFromError::TypeNameNotFound(ADDRESS.into()));
308        };
309        let Some(signature) = find_param(SIGNATURE) else {
310            return Err(TryFromError::TypeNameNotFound(SIGNATURE.into()));
311        };
312
313        Ok(MissingExtrinsicTypeIds { address, signature })
314    }
315}
316
317/// Outer enum IDs, which are required in Subxt but are not present in V14 metadata.
318pub struct OuterEnums {
319    /// The RuntimeCall type ID.
320    pub call_ty: Option<u32>,
321    /// The RuntimeEvent type ID.
322    pub event_ty: Option<u32>,
323    /// The RuntimeError type ID.
324    pub error_ty: Option<u32>,
325}
326
327impl OuterEnums {
328    pub fn find_in(types: &scale_info::PortableRegistry) -> OuterEnums {
329        let find_type = |name: &str| {
330            types.types.iter().find_map(|ty| {
331                let ident = ty.ty.path.ident()?;
332
333                if ident != name {
334                    return None;
335                }
336
337                let scale_info::TypeDef::Variant(_) = &ty.ty.type_def else {
338                    return None;
339                };
340
341                Some(ty.id)
342            })
343        };
344
345        OuterEnums {
346            call_ty: find_type("RuntimeCall"),
347            event_ty: find_type("RuntimeEvent"),
348            error_ty: find_type("RuntimeError"),
349        }
350    }
351}