1use super::error::MetadataConversionError;
10use alloc::{
11 collections::BTreeMap,
12 format,
13 string::{String, ToString},
14 vec,
15 vec::Vec,
16};
17use frame_metadata::{v14, v15};
18use scale_info::TypeDef;
19
20pub fn v14_to_v15(
21 mut metadata: v14::RuntimeMetadataV14,
22) -> Result<v15::RuntimeMetadataV15, MetadataConversionError> {
23 let extrinsic_parts = ExtrinsicPartTypeIds::new(&metadata)?;
25
26 let outer_enums = generate_outer_enums(&mut metadata)?;
27
28 Ok(v15::RuntimeMetadataV15 {
29 types: metadata.types,
30 pallets: metadata
31 .pallets
32 .into_iter()
33 .map(|pallet| frame_metadata::v15::PalletMetadata {
34 name: pallet.name,
35 storage: pallet
36 .storage
37 .map(|storage| frame_metadata::v15::PalletStorageMetadata {
38 prefix: storage.prefix,
39 entries: storage
40 .entries
41 .into_iter()
42 .map(|entry| {
43 let modifier = match entry.modifier {
44 frame_metadata::v14::StorageEntryModifier::Optional => {
45 frame_metadata::v15::StorageEntryModifier::Optional
46 }
47 frame_metadata::v14::StorageEntryModifier::Default => {
48 frame_metadata::v15::StorageEntryModifier::Default
49 }
50 };
51
52 let ty = match entry.ty {
53 frame_metadata::v14::StorageEntryType::Plain(ty) => {
54 frame_metadata::v15::StorageEntryType::Plain(ty)
55 },
56 frame_metadata::v14::StorageEntryType::Map {
57 hashers,
58 key,
59 value,
60 } => frame_metadata::v15::StorageEntryType::Map {
61 hashers: hashers.into_iter().map(|hasher| match hasher {
62 frame_metadata::v14::StorageHasher::Blake2_128 => frame_metadata::v15::StorageHasher::Blake2_128,
63 frame_metadata::v14::StorageHasher::Blake2_256 => frame_metadata::v15::StorageHasher::Blake2_256,
64 frame_metadata::v14::StorageHasher::Blake2_128Concat => frame_metadata::v15::StorageHasher::Blake2_128Concat ,
65 frame_metadata::v14::StorageHasher::Twox128 => frame_metadata::v15::StorageHasher::Twox128,
66 frame_metadata::v14::StorageHasher::Twox256 => frame_metadata::v15::StorageHasher::Twox256,
67 frame_metadata::v14::StorageHasher::Twox64Concat => frame_metadata::v15::StorageHasher::Twox64Concat,
68 frame_metadata::v14::StorageHasher::Identity=> frame_metadata::v15::StorageHasher::Identity,
69 }).collect(),
70 key,
71 value,
72 },
73 };
74
75 frame_metadata::v15::StorageEntryMetadata {
76 name: entry.name,
77 modifier,
78 ty,
79 default: entry.default,
80 docs: entry.docs,
81 }
82 })
83 .collect(),
84 }),
85 calls: pallet.calls.map(|calls| frame_metadata::v15::PalletCallMetadata { ty: calls.ty } ),
86 event: pallet.event.map(|event| frame_metadata::v15::PalletEventMetadata { ty: event.ty } ),
87 constants: pallet.constants.into_iter().map(|constant| frame_metadata::v15::PalletConstantMetadata {
88 name: constant.name,
89 ty: constant.ty,
90 value: constant.value,
91 docs: constant.docs,
92 } ).collect(),
93 error: pallet.error.map(|error| frame_metadata::v15::PalletErrorMetadata { ty: error.ty } ),
94 index: pallet.index,
95 docs: Default::default(),
96 })
97 .collect(),
98 extrinsic: frame_metadata::v15::ExtrinsicMetadata {
99 version: metadata.extrinsic.version,
100 signed_extensions: metadata.extrinsic.signed_extensions.into_iter().map(|ext| {
101 frame_metadata::v15::SignedExtensionMetadata {
102 identifier: ext.identifier,
103 ty: ext.ty,
104 additional_signed: ext.additional_signed,
105 }
106 }).collect(),
107 address_ty: extrinsic_parts.address.into(),
108 call_ty: extrinsic_parts.call.into(),
109 signature_ty: extrinsic_parts.signature.into(),
110 extra_ty: extrinsic_parts.extra.into(),
111 },
112 ty: metadata.ty,
113 apis: Default::default(),
114 outer_enums,
115 custom: v15::CustomMetadata {
116 map: Default::default(),
117 },
118 })
119}
120
121struct ExtrinsicPartTypeIds {
125 address: u32,
126 call: u32,
127 signature: u32,
128 extra: u32,
129}
130
131impl ExtrinsicPartTypeIds {
132 fn new(metadata: &v14::RuntimeMetadataV14) -> Result<Self, MetadataConversionError> {
134 const ADDRESS: &str = "Address";
135 const CALL: &str = "Call";
136 const SIGNATURE: &str = "Signature";
137 const EXTRA: &str = "Extra";
138
139 let extrinsic_id = metadata.extrinsic.ty.id;
140 let Some(extrinsic_ty) = metadata.types.resolve(extrinsic_id) else {
141 return Err(MetadataConversionError::TypeNotFound(extrinsic_id))
142 };
143
144 let params: BTreeMap<_, _> = extrinsic_ty
145 .type_params
146 .iter()
147 .map(|ty_param| {
148 let Some(ty) = ty_param.ty else {
149 return Err(MetadataConversionError::TypeNameNotFound(ty_param.name.clone()))
150 };
151
152 Ok((ty_param.name.as_str(), ty.id))
153 })
154 .collect::<Result<_, _>>()?;
155
156 let Some(address) = params.get(ADDRESS) else {
157 return Err(MetadataConversionError::TypeNameNotFound(ADDRESS.into()))
158 };
159 let Some(call) = params.get(CALL) else {
160 return Err(MetadataConversionError::TypeNameNotFound(CALL.into()))
161 };
162 let Some(signature) = params.get(SIGNATURE) else {
163 return Err(MetadataConversionError::TypeNameNotFound(SIGNATURE.into()))
164 };
165 let Some(extra) = params.get(EXTRA) else {
166 return Err(MetadataConversionError::TypeNameNotFound(EXTRA.into()))
167 };
168
169 Ok(ExtrinsicPartTypeIds {
170 address: *address,
171 call: *call,
172 signature: *signature,
173 extra: *extra,
174 })
175 }
176}
177
178fn generate_outer_enums(
179 metadata: &mut v14::RuntimeMetadataV14,
180) -> Result<v15::OuterEnums<scale_info::form::PortableForm>, MetadataConversionError> {
181 let find_type = |name: &str| {
182 metadata.types.types.iter().find_map(|ty| {
183 let ident = ty.ty.path.ident()?;
184
185 if ident != name {
186 return None
187 }
188
189 let TypeDef::Variant(_) = &ty.ty.type_def else { return None };
190
191 Some((ty.id, ty.ty.path.segments.clone()))
192 })
193 };
194
195 let Some((call_enum, mut call_path)) = find_type("RuntimeCall") else {
196 return Err(MetadataConversionError::TypeNameNotFound("RuntimeCall".into()))
197 };
198
199 let Some((event_enum, _)) = find_type("RuntimeEvent") else {
200 return Err(MetadataConversionError::TypeNameNotFound("RuntimeEvent".into()))
201 };
202
203 let error_enum = if let Some((error_enum, _)) = find_type("RuntimeError") {
204 error_enum
205 } else {
206 let Some(last) = call_path.last_mut() else {
207 return Err(MetadataConversionError::InvalidTypePath("RuntimeCall".into()))
208 };
209 *last = "RuntimeError".to_string();
210 generate_outer_error_enum_type(metadata, call_path)
211 };
212
213 Ok(v15::OuterEnums {
214 call_enum_ty: call_enum.into(),
215 event_enum_ty: event_enum.into(),
216 error_enum_ty: error_enum.into(),
217 })
218}
219
220fn generate_outer_error_enum_type(
224 metadata: &mut v14::RuntimeMetadataV14,
225 path_segments: Vec<String>,
226) -> u32 {
227 let variants: Vec<_> = metadata
228 .pallets
229 .iter()
230 .filter_map(|pallet| {
231 let error = &pallet.error.clone()?;
232
233 let path = format!("{}Error", pallet.name);
234 let ty = error.ty.id.into();
235
236 Some(scale_info::Variant {
237 name: pallet.name.clone(),
238 fields: vec![scale_info::Field {
239 name: None,
240 ty,
241 type_name: Some(path),
242 docs: vec![],
243 }],
244 index: pallet.index,
245 docs: vec![],
246 })
247 })
248 .collect();
249
250 let enum_type = scale_info::Type {
251 path: scale_info::Path { segments: path_segments },
252 type_params: vec![],
253 type_def: scale_info::TypeDef::Variant(scale_info::TypeDefVariant { variants }),
254 docs: vec![],
255 };
256
257 let enum_type_id = metadata.types.types.len() as u32;
258
259 metadata
260 .types
261 .types
262 .push(scale_info::PortableType { id: enum_type_id, ty: enum_type });
263
264 enum_type_id
265}
266
267#[cfg(test)]
268mod tests {
269 use super::*;
270 use codec::Decode;
271 use frame_metadata::{
272 v14::{ExtrinsicMetadata, RuntimeMetadataV14},
273 RuntimeMetadata, RuntimeMetadataPrefixed,
274 };
275 use scale_info::{meta_type, IntoPortable, TypeInfo};
276 use sp_core::Bytes;
277 use std::{fs, marker::PhantomData};
278
279 fn load_v14_metadata() -> RuntimeMetadataV14 {
280 let encoded_metadata: Bytes = fs::read("./../ksm_metadata_v14.bin").unwrap().into();
281 let runtime_metadata_prefixed: RuntimeMetadataPrefixed =
282 Decode::decode(&mut encoded_metadata.0.as_slice()).unwrap();
283
284 match runtime_metadata_prefixed.1 {
285 RuntimeMetadata::V14(ref metadata) => metadata.clone(),
286 _ => unimplemented!(),
287 }
288 }
289
290 #[test]
291 fn test_extrinsic_id_generation() {
292 let v14 = load_v14_metadata();
293
294 let v15 = v14_to_v15(v14.clone()).unwrap();
295
296 let ext_ty = v14.types.resolve(v14.extrinsic.ty.id).unwrap();
297 let addr_id = ext_ty
298 .type_params
299 .iter()
300 .find_map(|ty| if ty.name == "Address" { Some(ty.ty.unwrap().id) } else { None })
301 .unwrap();
302 let call_id = ext_ty
303 .type_params
304 .iter()
305 .find_map(|ty| if ty.name == "Call" { Some(ty.ty.unwrap().id) } else { None })
306 .unwrap();
307 let extra_id = ext_ty
308 .type_params
309 .iter()
310 .find_map(|ty| if ty.name == "Extra" { Some(ty.ty.unwrap().id) } else { None })
311 .unwrap();
312 let signature_id = ext_ty
313 .type_params
314 .iter()
315 .find_map(|ty| if ty.name == "Signature" { Some(ty.ty.unwrap().id) } else { None })
316 .unwrap();
317
318 assert_eq!(v15.extrinsic.address_ty.id, addr_id);
320 assert_eq!(v15.extrinsic.call_ty.id, call_id);
321 assert_eq!(v15.extrinsic.extra_ty.id, extra_id);
322 assert_eq!(v15.extrinsic.signature_ty.id, signature_id);
323
324 let v15_addr = v15.types.resolve(v15.extrinsic.address_ty.id).unwrap();
325 let v14_addr = v14.types.resolve(addr_id).unwrap();
326 assert_eq!(v15_addr, v14_addr);
327
328 let v15_call = v15.types.resolve(v15.extrinsic.call_ty.id).unwrap();
329 let v14_call = v14.types.resolve(call_id).unwrap();
330 assert_eq!(v15_call, v14_call);
331
332 let v15_extra = v15.types.resolve(v15.extrinsic.extra_ty.id).unwrap();
333 let v14_extra = v14.types.resolve(extra_id).unwrap();
334 assert_eq!(v15_extra, v14_extra);
335
336 let v15_sign = v15.types.resolve(v15.extrinsic.signature_ty.id).unwrap();
337 let v14_sign = v14.types.resolve(signature_id).unwrap();
338 assert_eq!(v15_sign, v14_sign);
339 }
340
341 #[test]
342 fn test_missing_extrinsic_types() {
343 #[derive(TypeInfo)]
344 struct Runtime;
345
346 let generate_metadata = |extrinsic_ty| {
347 let mut registry = scale_info::Registry::new();
348
349 let ty = registry.register_type(&meta_type::<Runtime>());
350
351 let extrinsic =
352 ExtrinsicMetadata { ty: extrinsic_ty, version: 0, signed_extensions: vec![] }
353 .into_portable(&mut registry);
354
355 v14::RuntimeMetadataV14 { types: registry.into(), pallets: Vec::new(), extrinsic, ty }
356 };
357
358 let metadata = generate_metadata(meta_type::<()>());
359 let err = v14_to_v15(metadata).unwrap_err();
360 assert_eq!(err, MetadataConversionError::TypeNameNotFound("Address".into()));
361
362 #[derive(TypeInfo)]
363 struct ExtrinsicNoCall<Address, Signature, Extra> {
364 _phantom: PhantomData<(Address, Signature, Extra)>,
365 }
366 let metadata = generate_metadata(meta_type::<ExtrinsicNoCall<(), (), ()>>());
367 let err = v14_to_v15(metadata).unwrap_err();
368 assert_eq!(err, MetadataConversionError::TypeNameNotFound("Call".into()));
369
370 #[derive(TypeInfo)]
371 struct ExtrinsicNoSign<Call, Address, Extra> {
372 _phantom: PhantomData<(Call, Address, Extra)>,
373 }
374 let metadata = generate_metadata(meta_type::<ExtrinsicNoSign<(), (), ()>>());
375 let err = v14_to_v15(metadata).unwrap_err();
376 assert_eq!(err, MetadataConversionError::TypeNameNotFound("Signature".into()));
377
378 #[derive(TypeInfo)]
379 struct ExtrinsicNoExtra<Call, Address, Signature> {
380 _phantom: PhantomData<(Call, Address, Signature)>,
381 }
382 let metadata = generate_metadata(meta_type::<ExtrinsicNoExtra<(), (), ()>>());
383 let err = v14_to_v15(metadata).unwrap_err();
384 assert_eq!(err, MetadataConversionError::TypeNameNotFound("Extra".into()));
385 }
386
387 #[test]
388 fn test_missing_outer_enum_types() {
389 #[derive(TypeInfo)]
390 struct Runtime;
391
392 #[derive(TypeInfo)]
393 enum RuntimeCall {}
394 #[derive(TypeInfo)]
395 enum RuntimeEvent {}
396
397 #[allow(unused)]
398 #[derive(TypeInfo)]
399 struct ExtrinsicType<Address, Call, Signature, Extra> {
400 pub signature: Option<(Address, Signature, Extra)>,
401 pub function: Call,
402 }
403
404 {
406 let mut registry = scale_info::Registry::new();
407 let ty = registry.register_type(&meta_type::<Runtime>());
408 registry.register_type(&meta_type::<RuntimeEvent>());
409
410 let extrinsic = ExtrinsicMetadata {
411 ty: meta_type::<ExtrinsicType<(), (), (), ()>>(),
412 version: 0,
413 signed_extensions: vec![],
414 }
415 .into_portable(&mut registry);
416
417 let metadata = v14::RuntimeMetadataV14 {
418 types: registry.into(),
419 pallets: Vec::new(),
420 extrinsic,
421 ty,
422 };
423
424 let err = v14_to_v15(metadata).unwrap_err();
425 assert_eq!(err, MetadataConversionError::TypeNameNotFound("RuntimeCall".into()));
426 }
427
428 {
430 let mut registry = scale_info::Registry::new();
431 let ty = registry.register_type(&meta_type::<Runtime>());
432 registry.register_type(&meta_type::<RuntimeCall>());
433
434 let extrinsic = ExtrinsicMetadata {
435 ty: meta_type::<ExtrinsicType<(), (), (), ()>>(),
436 version: 0,
437 signed_extensions: vec![],
438 }
439 .into_portable(&mut registry);
440
441 let metadata = v14::RuntimeMetadataV14 {
442 types: registry.into(),
443 pallets: Vec::new(),
444 extrinsic,
445 ty,
446 };
447
448 let err = v14_to_v15(metadata).unwrap_err();
449 assert_eq!(err, MetadataConversionError::TypeNameNotFound("RuntimeEvent".into()));
450 }
451 }
452}