subxt_metadata/from_into/
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;
6use crate::Metadata;
7use alloc::borrow::ToOwned;
8use alloc::string::String;
9use alloc::vec;
10use alloc::vec::Vec;
11use core::fmt::Write;
12use frame_metadata::{v14, v15};
13use scale_info::TypeDef;
14
15impl TryFrom<v14::RuntimeMetadataV14> for Metadata {
16    type Error = TryFromError;
17    fn try_from(value: v14::RuntimeMetadataV14) -> Result<Self, Self::Error> {
18        // Convert to v15 and then convert that into Metadata.
19        v14_to_v15(value)?.try_into()
20    }
21}
22
23impl From<Metadata> for v14::RuntimeMetadataV14 {
24    fn from(val: Metadata) -> Self {
25        let v15 = val.into();
26        v15_to_v14(v15)
27    }
28}
29
30fn v15_to_v14(mut metadata: v15::RuntimeMetadataV15) -> v14::RuntimeMetadataV14 {
31    let types = &mut metadata.types;
32
33    // In subxt we care about the `Address`, `Call`, `Signature` and `Extra` types.
34    let extrinsic_type = scale_info::Type {
35        path: scale_info::Path {
36            segments: vec![
37                "primitives".to_owned(),
38                "runtime".to_owned(),
39                "generic".to_owned(),
40                "UncheckedExtrinsic".to_owned(),
41            ],
42        },
43        type_params: vec![
44            scale_info::TypeParameter::<scale_info::form::PortableForm> {
45                name: "Address".to_owned(),
46                ty: Some(metadata.extrinsic.address_ty),
47            },
48            scale_info::TypeParameter::<scale_info::form::PortableForm> {
49                name: "Call".to_owned(),
50                ty: Some(metadata.extrinsic.call_ty),
51            },
52            scale_info::TypeParameter::<scale_info::form::PortableForm> {
53                name: "Signature".to_owned(),
54                ty: Some(metadata.extrinsic.signature_ty),
55            },
56            scale_info::TypeParameter::<scale_info::form::PortableForm> {
57                name: "Extra".to_owned(),
58                ty: Some(metadata.extrinsic.extra_ty),
59            },
60        ],
61        type_def: scale_info::TypeDef::Composite(scale_info::TypeDefComposite { fields: vec![] }),
62        docs: vec![],
63    };
64    let extrinsic_type_id = types.types.len() as u32;
65
66    types.types.push(scale_info::PortableType {
67        id: extrinsic_type_id,
68        ty: extrinsic_type,
69    });
70
71    v14::RuntimeMetadataV14 {
72        types: metadata.types,
73        pallets: metadata
74            .pallets
75            .into_iter()
76            .map(|pallet| frame_metadata::v14::PalletMetadata {
77                name: pallet.name,
78                storage: pallet
79                    .storage
80                    .map(|storage| frame_metadata::v14::PalletStorageMetadata {
81                        prefix: storage.prefix,
82                        entries: storage
83                            .entries
84                            .into_iter()
85                            .map(|entry| {
86                                let modifier = match entry.modifier {
87                                    frame_metadata::v15::StorageEntryModifier::Optional => {
88                                        frame_metadata::v14::StorageEntryModifier::Optional
89                                    }
90                                    frame_metadata::v15::StorageEntryModifier::Default => {
91                                        frame_metadata::v14::StorageEntryModifier::Default
92                                    }
93                                };
94
95                                let ty = match entry.ty {
96                                    frame_metadata::v15::StorageEntryType::Plain(ty) => {
97                                        frame_metadata::v14::StorageEntryType::Plain(ty)
98                                    },
99                                    frame_metadata::v15::StorageEntryType::Map {
100                                        hashers,
101                                        key,
102                                        value,
103                                    } => frame_metadata::v14::StorageEntryType::Map {
104                                        hashers: hashers.into_iter().map(|hasher| match hasher {
105                                            frame_metadata::v15::StorageHasher::Blake2_128 => frame_metadata::v14::StorageHasher::Blake2_128,
106                                            frame_metadata::v15::StorageHasher::Blake2_256 => frame_metadata::v14::StorageHasher::Blake2_256,
107                                            frame_metadata::v15::StorageHasher::Blake2_128Concat  => frame_metadata::v14::StorageHasher::Blake2_128Concat ,
108                                            frame_metadata::v15::StorageHasher::Twox128 => frame_metadata::v14::StorageHasher::Twox128,
109                                            frame_metadata::v15::StorageHasher::Twox256 => frame_metadata::v14::StorageHasher::Twox256,
110                                            frame_metadata::v15::StorageHasher::Twox64Concat => frame_metadata::v14::StorageHasher::Twox64Concat,
111                                            frame_metadata::v15::StorageHasher::Identity=> frame_metadata::v14::StorageHasher::Identity,
112                                        }).collect(),
113                                        key,
114                                        value,
115                                    },
116                                };
117
118                                frame_metadata::v14::StorageEntryMetadata {
119                                    name: entry.name,
120                                    modifier,
121                                    ty,
122                                    default: entry.default,
123                                    docs: entry.docs,
124                                }
125                            })
126                            .collect(),
127                    }),
128                calls: pallet.calls.map(|calls| frame_metadata::v14::PalletCallMetadata { ty: calls.ty } ),
129                event: pallet.event.map(|event| frame_metadata::v14::PalletEventMetadata { ty: event.ty } ),
130                constants: pallet.constants.into_iter().map(|constant| frame_metadata::v14::PalletConstantMetadata {
131                    name: constant.name,
132                    ty: constant.ty,
133                    value: constant.value,
134                    docs: constant.docs,
135                } ).collect(),
136                error: pallet.error.map(|error| frame_metadata::v14::PalletErrorMetadata { ty: error.ty } ),
137                index: pallet.index,
138            })
139            .collect(),
140        extrinsic: frame_metadata::v14::ExtrinsicMetadata {
141            ty: extrinsic_type_id.into(),
142            version: metadata.extrinsic.version,
143            signed_extensions: metadata.extrinsic.signed_extensions.into_iter().map(|ext| {
144                frame_metadata::v14::SignedExtensionMetadata {
145                    identifier: ext.identifier,
146                    ty: ext.ty,
147                    additional_signed: ext.additional_signed,
148                }
149            }).collect()
150        },
151        ty: metadata.ty,
152    }
153}
154
155fn v14_to_v15(
156    mut metadata: v14::RuntimeMetadataV14,
157) -> Result<v15::RuntimeMetadataV15, TryFromError> {
158    // Find the extrinsic types.
159    let extrinsic_parts = ExtrinsicPartTypeIds::new(&metadata)?;
160
161    let outer_enums = generate_outer_enums(&mut metadata)?;
162
163    Ok(v15::RuntimeMetadataV15 {
164        types: metadata.types,
165        pallets: metadata
166            .pallets
167            .into_iter()
168            .map(|pallet| frame_metadata::v15::PalletMetadata {
169                name: pallet.name,
170                storage: pallet
171                    .storage
172                    .map(|storage| frame_metadata::v15::PalletStorageMetadata {
173                        prefix: storage.prefix,
174                        entries: storage
175                            .entries
176                            .into_iter()
177                            .map(|entry| {
178                                let modifier = match entry.modifier {
179                                    frame_metadata::v14::StorageEntryModifier::Optional => {
180                                        frame_metadata::v15::StorageEntryModifier::Optional
181                                    }
182                                    frame_metadata::v14::StorageEntryModifier::Default => {
183                                        frame_metadata::v15::StorageEntryModifier::Default
184                                    }
185                                };
186
187                                let ty = match entry.ty {
188                                    frame_metadata::v14::StorageEntryType::Plain(ty) => {
189                                        frame_metadata::v15::StorageEntryType::Plain(ty)
190                                    },
191                                    frame_metadata::v14::StorageEntryType::Map {
192                                        hashers,
193                                        key,
194                                        value,
195                                    } => frame_metadata::v15::StorageEntryType::Map {
196                                        hashers: hashers.into_iter().map(|hasher| match hasher {
197                                            frame_metadata::v14::StorageHasher::Blake2_128 => frame_metadata::v15::StorageHasher::Blake2_128,
198                                            frame_metadata::v14::StorageHasher::Blake2_256 => frame_metadata::v15::StorageHasher::Blake2_256,
199                                            frame_metadata::v14::StorageHasher::Blake2_128Concat  => frame_metadata::v15::StorageHasher::Blake2_128Concat ,
200                                            frame_metadata::v14::StorageHasher::Twox128 => frame_metadata::v15::StorageHasher::Twox128,
201                                            frame_metadata::v14::StorageHasher::Twox256 => frame_metadata::v15::StorageHasher::Twox256,
202                                            frame_metadata::v14::StorageHasher::Twox64Concat => frame_metadata::v15::StorageHasher::Twox64Concat,
203                                            frame_metadata::v14::StorageHasher::Identity=> frame_metadata::v15::StorageHasher::Identity,
204                                        }).collect(),
205                                        key,
206                                        value,
207                                    },
208                                };
209
210                                frame_metadata::v15::StorageEntryMetadata {
211                                    name: entry.name,
212                                    modifier,
213                                    ty,
214                                    default: entry.default,
215                                    docs: entry.docs,
216                                }
217                            })
218                            .collect(),
219                    }),
220                calls: pallet.calls.map(|calls| frame_metadata::v15::PalletCallMetadata { ty: calls.ty } ),
221                event: pallet.event.map(|event| frame_metadata::v15::PalletEventMetadata { ty: event.ty } ),
222                constants: pallet.constants.into_iter().map(|constant| frame_metadata::v15::PalletConstantMetadata {
223                    name: constant.name,
224                    ty: constant.ty,
225                    value: constant.value,
226                    docs: constant.docs,
227                } ).collect(),
228                error: pallet.error.map(|error| frame_metadata::v15::PalletErrorMetadata { ty: error.ty } ),
229                index: pallet.index,
230                docs: Default::default(),
231            })
232            .collect(),
233        extrinsic: frame_metadata::v15::ExtrinsicMetadata {
234            version: metadata.extrinsic.version,
235            signed_extensions: metadata.extrinsic.signed_extensions.into_iter().map(|ext| {
236                frame_metadata::v15::SignedExtensionMetadata {
237                    identifier: ext.identifier,
238                    ty: ext.ty,
239                    additional_signed: ext.additional_signed,
240                }
241            }).collect(),
242            address_ty: extrinsic_parts.address.into(),
243            call_ty: extrinsic_parts.call.into(),
244            signature_ty: extrinsic_parts.signature.into(),
245            extra_ty: extrinsic_parts.extra.into(),
246        },
247        ty: metadata.ty,
248        apis: Default::default(),
249        outer_enums,
250        custom: v15::CustomMetadata {
251            map: Default::default(),
252        },
253    })
254}
255
256/// The type IDs extracted from the metadata that represent the
257/// generic type parameters passed to the `UncheckedExtrinsic` from
258/// the substrate-based chain.
259struct ExtrinsicPartTypeIds {
260    address: u32,
261    call: u32,
262    signature: u32,
263    extra: u32,
264}
265
266impl ExtrinsicPartTypeIds {
267    /// Extract the generic type parameters IDs from the extrinsic type.
268    fn new(metadata: &v14::RuntimeMetadataV14) -> Result<Self, TryFromError> {
269        const ADDRESS: &str = "Address";
270        const CALL: &str = "Call";
271        const SIGNATURE: &str = "Signature";
272        const EXTRA: &str = "Extra";
273
274        let extrinsic_id = metadata.extrinsic.ty.id;
275        let Some(extrinsic_ty) = metadata.types.resolve(extrinsic_id) else {
276            return Err(TryFromError::TypeNotFound(extrinsic_id));
277        };
278
279        let find_param = |name: &'static str| -> Option<u32> {
280            extrinsic_ty
281                .type_params
282                .iter()
283                .find(|param| param.name.as_str() == name)
284                .and_then(|param| param.ty.as_ref())
285                .map(|ty| ty.id)
286        };
287
288        let Some(address) = find_param(ADDRESS) else {
289            return Err(TryFromError::TypeNameNotFound(ADDRESS.into()));
290        };
291        let Some(call) = find_param(CALL) else {
292            return Err(TryFromError::TypeNameNotFound(CALL.into()));
293        };
294        let Some(signature) = find_param(SIGNATURE) else {
295            return Err(TryFromError::TypeNameNotFound(SIGNATURE.into()));
296        };
297        let Some(extra) = find_param(EXTRA) else {
298            return Err(TryFromError::TypeNameNotFound(EXTRA.into()));
299        };
300
301        Ok(ExtrinsicPartTypeIds {
302            address,
303            call,
304            signature,
305            extra,
306        })
307    }
308}
309
310fn generate_outer_enums(
311    metadata: &mut v14::RuntimeMetadataV14,
312) -> Result<v15::OuterEnums<scale_info::form::PortableForm>, TryFromError> {
313    let find_type = |name: &str| {
314        metadata.types.types.iter().find_map(|ty| {
315            let ident = ty.ty.path.ident()?;
316
317            if ident != name {
318                return None;
319            }
320
321            let TypeDef::Variant(_) = &ty.ty.type_def else {
322                return None;
323            };
324
325            Some((ty.id, ty.ty.path.segments.clone()))
326        })
327    };
328
329    let Some((call_enum, mut call_path)) = find_type("RuntimeCall") else {
330        return Err(TryFromError::TypeNameNotFound("RuntimeCall".into()));
331    };
332
333    let Some((event_enum, _)) = find_type("RuntimeEvent") else {
334        return Err(TryFromError::TypeNameNotFound("RuntimeEvent".into()));
335    };
336
337    let error_enum = if let Some((error_enum, _)) = find_type("RuntimeError") {
338        error_enum
339    } else {
340        let Some(last) = call_path.last_mut() else {
341            return Err(TryFromError::InvalidTypePath("RuntimeCall".into()));
342        };
343        "RuntimeError".clone_into(last);
344        generate_outer_error_enum_type(metadata, call_path)
345    };
346
347    Ok(v15::OuterEnums {
348        call_enum_ty: call_enum.into(),
349        event_enum_ty: event_enum.into(),
350        error_enum_ty: error_enum.into(),
351    })
352}
353
354/// Generates an outer `RuntimeError` enum type and adds it to the metadata.
355///
356/// Returns the id of the generated type from the registry.
357fn generate_outer_error_enum_type(
358    metadata: &mut v14::RuntimeMetadataV14,
359    path_segments: Vec<String>,
360) -> u32 {
361    let variants: Vec<_> = metadata
362        .pallets
363        .iter()
364        .filter_map(|pallet| {
365            let error = pallet.error.as_ref()?;
366
367            // Note:  using the `alloc::format!` macro like in `let path = format!("{}Error", pallet.name);`
368            // leads to linker errors about extern function `_Unwind_Resume` not being defined.
369            let mut path = String::new();
370            write!(path, "{}Error", pallet.name).expect("Cannot panic, qed;");
371            let ty = error.ty.id.into();
372
373            Some(scale_info::Variant {
374                name: pallet.name.clone(),
375                fields: vec![scale_info::Field {
376                    name: None,
377                    ty,
378                    type_name: Some(path),
379                    docs: vec![],
380                }],
381                index: pallet.index,
382                docs: vec![],
383            })
384        })
385        .collect();
386
387    let enum_type = scale_info::Type {
388        path: scale_info::Path {
389            segments: path_segments,
390        },
391        type_params: vec![],
392        type_def: scale_info::TypeDef::Variant(scale_info::TypeDefVariant { variants }),
393        docs: vec![],
394    };
395
396    let enum_type_id = metadata.types.types.len() as u32;
397
398    metadata.types.types.push(scale_info::PortableType {
399        id: enum_type_id,
400        ty: enum_type,
401    });
402
403    enum_type_id
404}
405
406#[cfg(test)]
407mod tests {
408    use super::*;
409    use codec::Decode;
410    use frame_metadata::{
411        v14::ExtrinsicMetadata, v15::RuntimeMetadataV15, RuntimeMetadata, RuntimeMetadataPrefixed,
412    };
413    use scale_info::{meta_type, IntoPortable, TypeDef, TypeInfo};
414    use std::{fs, marker::PhantomData, path::Path};
415
416    fn load_v15_metadata() -> RuntimeMetadataV15 {
417        let bytes = fs::read(Path::new("../artifacts/polkadot_metadata_full.scale"))
418            .expect("Cannot read metadata blob");
419        let meta: RuntimeMetadataPrefixed =
420            Decode::decode(&mut &*bytes).expect("Cannot decode scale metadata");
421
422        match meta.1 {
423            RuntimeMetadata::V15(v15) => v15,
424            _ => panic!("Unsupported metadata version {:?}", meta.1),
425        }
426    }
427
428    #[test]
429    fn test_extrinsic_id_generation() {
430        let v15 = load_v15_metadata();
431        let v14 = v15_to_v14(v15.clone());
432
433        let ext_ty = v14.types.resolve(v14.extrinsic.ty.id).unwrap();
434        let addr_id = ext_ty
435            .type_params
436            .iter()
437            .find_map(|ty| {
438                if ty.name == "Address" {
439                    Some(ty.ty.unwrap().id)
440                } else {
441                    None
442                }
443            })
444            .unwrap();
445        let call_id = ext_ty
446            .type_params
447            .iter()
448            .find_map(|ty| {
449                if ty.name == "Call" {
450                    Some(ty.ty.unwrap().id)
451                } else {
452                    None
453                }
454            })
455            .unwrap();
456        let extra_id = ext_ty
457            .type_params
458            .iter()
459            .find_map(|ty| {
460                if ty.name == "Extra" {
461                    Some(ty.ty.unwrap().id)
462                } else {
463                    None
464                }
465            })
466            .unwrap();
467        let signature_id = ext_ty
468            .type_params
469            .iter()
470            .find_map(|ty| {
471                if ty.name == "Signature" {
472                    Some(ty.ty.unwrap().id)
473                } else {
474                    None
475                }
476            })
477            .unwrap();
478
479        // Position in type registry shouldn't change.
480        assert_eq!(v15.extrinsic.address_ty.id, addr_id);
481        assert_eq!(v15.extrinsic.call_ty.id, call_id);
482        assert_eq!(v15.extrinsic.extra_ty.id, extra_id);
483        assert_eq!(v15.extrinsic.signature_ty.id, signature_id);
484
485        let v15_addr = v15.types.resolve(v15.extrinsic.address_ty.id).unwrap();
486        let v14_addr = v14.types.resolve(addr_id).unwrap();
487        assert_eq!(v15_addr, v14_addr);
488
489        let v15_call = v15.types.resolve(v15.extrinsic.call_ty.id).unwrap();
490        let v14_call = v14.types.resolve(call_id).unwrap();
491        assert_eq!(v15_call, v14_call);
492
493        let v15_extra = v15.types.resolve(v15.extrinsic.extra_ty.id).unwrap();
494        let v14_extra = v14.types.resolve(extra_id).unwrap();
495        assert_eq!(v15_extra, v14_extra);
496
497        let v15_sign = v15.types.resolve(v15.extrinsic.signature_ty.id).unwrap();
498        let v14_sign = v14.types.resolve(signature_id).unwrap();
499        assert_eq!(v15_sign, v14_sign);
500
501        // Ensure we don't lose the information when converting back to v15.
502        let converted_v15 = v14_to_v15(v14).unwrap();
503
504        let v15_addr = v15.types.resolve(v15.extrinsic.address_ty.id).unwrap();
505        let converted_v15_addr = converted_v15
506            .types
507            .resolve(converted_v15.extrinsic.address_ty.id)
508            .unwrap();
509        assert_eq!(v15_addr, converted_v15_addr);
510
511        let v15_call = v15.types.resolve(v15.extrinsic.call_ty.id).unwrap();
512        let converted_v15_call = converted_v15
513            .types
514            .resolve(converted_v15.extrinsic.call_ty.id)
515            .unwrap();
516        assert_eq!(v15_call, converted_v15_call);
517
518        let v15_extra = v15.types.resolve(v15.extrinsic.extra_ty.id).unwrap();
519        let converted_v15_extra = converted_v15
520            .types
521            .resolve(converted_v15.extrinsic.extra_ty.id)
522            .unwrap();
523        assert_eq!(v15_extra, converted_v15_extra);
524
525        let v15_sign = v15.types.resolve(v15.extrinsic.signature_ty.id).unwrap();
526        let converted_v15_sign = converted_v15
527            .types
528            .resolve(converted_v15.extrinsic.signature_ty.id)
529            .unwrap();
530        assert_eq!(v15_sign, converted_v15_sign);
531    }
532
533    #[test]
534    fn test_outer_enums_generation() {
535        let v15 = load_v15_metadata();
536        let v14 = v15_to_v14(v15.clone());
537
538        // Convert back to v15 and expect to have the enum types properly generated.
539        let converted_v15 = v14_to_v15(v14).unwrap();
540
541        // RuntimeCall and RuntimeEvent were already present in the metadata v14.
542        let v15_call = v15.types.resolve(v15.outer_enums.call_enum_ty.id).unwrap();
543        let converted_v15_call = converted_v15
544            .types
545            .resolve(converted_v15.outer_enums.call_enum_ty.id)
546            .unwrap();
547        assert_eq!(v15_call, converted_v15_call);
548
549        let v15_event = v15.types.resolve(v15.outer_enums.event_enum_ty.id).unwrap();
550        let converted_v15_event = converted_v15
551            .types
552            .resolve(converted_v15.outer_enums.event_enum_ty.id)
553            .unwrap();
554        assert_eq!(v15_event, converted_v15_event);
555
556        let v15_error = v15.types.resolve(v15.outer_enums.error_enum_ty.id).unwrap();
557        let converted_v15_error = converted_v15
558            .types
559            .resolve(converted_v15.outer_enums.error_enum_ty.id)
560            .unwrap();
561
562        // Ensure they match in terms of variants and fields ids.
563        assert_eq!(v15_error.path, converted_v15_error.path);
564
565        let TypeDef::Variant(v15_variant) = &v15_error.type_def else {
566            panic!("V15 error must be a variant");
567        };
568
569        let TypeDef::Variant(converted_v15_variant) = &converted_v15_error.type_def else {
570            panic!("Converted V15 error must be a variant");
571        };
572
573        assert_eq!(
574            v15_variant.variants.len(),
575            converted_v15_variant.variants.len()
576        );
577
578        for (v15_var, converted_v15_var) in v15_variant
579            .variants
580            .iter()
581            .zip(converted_v15_variant.variants.iter())
582        {
583            // Variant name must match.
584            assert_eq!(v15_var.name, converted_v15_var.name);
585            assert_eq!(v15_var.fields.len(), converted_v15_var.fields.len());
586
587            // Fields must have the same type.
588            for (v15_field, converted_v15_field) in
589                v15_var.fields.iter().zip(converted_v15_var.fields.iter())
590            {
591                assert_eq!(v15_field.ty.id, converted_v15_field.ty.id);
592
593                let ty = v15.types.resolve(v15_field.ty.id).unwrap();
594                let converted_ty = converted_v15
595                    .types
596                    .resolve(converted_v15_field.ty.id)
597                    .unwrap();
598                assert_eq!(ty, converted_ty);
599            }
600        }
601    }
602
603    #[test]
604    fn test_missing_extrinsic_types() {
605        #[derive(TypeInfo)]
606        struct Runtime;
607
608        let generate_metadata = |extrinsic_ty| {
609            let mut registry = scale_info::Registry::new();
610
611            let ty = registry.register_type(&meta_type::<Runtime>());
612
613            let extrinsic = ExtrinsicMetadata {
614                ty: extrinsic_ty,
615                version: 0,
616                signed_extensions: vec![],
617            }
618            .into_portable(&mut registry);
619
620            v14::RuntimeMetadataV14 {
621                types: registry.into(),
622                pallets: Vec::new(),
623                extrinsic,
624                ty,
625            }
626        };
627
628        let metadata = generate_metadata(meta_type::<()>());
629        let err = v14_to_v15(metadata).unwrap_err();
630        assert_eq!(err, TryFromError::TypeNameNotFound("Address".into()));
631
632        #[derive(TypeInfo)]
633        struct ExtrinsicNoCall<Address, Signature, Extra> {
634            _phantom: PhantomData<(Address, Signature, Extra)>,
635        }
636        let metadata = generate_metadata(meta_type::<ExtrinsicNoCall<(), (), ()>>());
637        let err = v14_to_v15(metadata).unwrap_err();
638        assert_eq!(err, TryFromError::TypeNameNotFound("Call".into()));
639
640        #[derive(TypeInfo)]
641        struct ExtrinsicNoSign<Call, Address, Extra> {
642            _phantom: PhantomData<(Call, Address, Extra)>,
643        }
644        let metadata = generate_metadata(meta_type::<ExtrinsicNoSign<(), (), ()>>());
645        let err = v14_to_v15(metadata).unwrap_err();
646        assert_eq!(err, TryFromError::TypeNameNotFound("Signature".into()));
647
648        #[derive(TypeInfo)]
649        struct ExtrinsicNoExtra<Call, Address, Signature> {
650            _phantom: PhantomData<(Call, Address, Signature)>,
651        }
652        let metadata = generate_metadata(meta_type::<ExtrinsicNoExtra<(), (), ()>>());
653        let err = v14_to_v15(metadata).unwrap_err();
654        assert_eq!(err, TryFromError::TypeNameNotFound("Extra".into()));
655    }
656
657    #[test]
658    fn test_missing_outer_enum_types() {
659        #[derive(TypeInfo)]
660        struct Runtime;
661
662        #[derive(TypeInfo)]
663        enum RuntimeCall {}
664        #[derive(TypeInfo)]
665        enum RuntimeEvent {}
666
667        #[allow(unused)]
668        #[derive(TypeInfo)]
669        struct ExtrinsicType<Address, Call, Signature, Extra> {
670            pub signature: Option<(Address, Signature, Extra)>,
671            pub function: Call,
672        }
673
674        // Missing runtime call.
675        {
676            let mut registry = scale_info::Registry::new();
677            let ty = registry.register_type(&meta_type::<Runtime>());
678            registry.register_type(&meta_type::<RuntimeEvent>());
679
680            let extrinsic = ExtrinsicMetadata {
681                ty: meta_type::<ExtrinsicType<(), (), (), ()>>(),
682                version: 0,
683                signed_extensions: vec![],
684            }
685            .into_portable(&mut registry);
686
687            let metadata = v14::RuntimeMetadataV14 {
688                types: registry.into(),
689                pallets: Vec::new(),
690                extrinsic,
691                ty,
692            };
693
694            let err = v14_to_v15(metadata).unwrap_err();
695            assert_eq!(err, TryFromError::TypeNameNotFound("RuntimeCall".into()));
696        }
697
698        // Missing runtime event.
699        {
700            let mut registry = scale_info::Registry::new();
701            let ty = registry.register_type(&meta_type::<Runtime>());
702            registry.register_type(&meta_type::<RuntimeCall>());
703
704            let extrinsic = ExtrinsicMetadata {
705                ty: meta_type::<ExtrinsicType<(), (), (), ()>>(),
706                version: 0,
707                signed_extensions: vec![],
708            }
709            .into_portable(&mut registry);
710
711            let metadata = v14::RuntimeMetadataV14 {
712                types: registry.into(),
713                pallets: Vec::new(),
714                extrinsic,
715                ty,
716            };
717
718            let err = v14_to_v15(metadata).unwrap_err();
719            assert_eq!(err, TryFromError::TypeNameNotFound("RuntimeEvent".into()));
720        }
721    }
722}