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