ac_node_api/metadata/
from_v14_to_v15.rs

1// This file was taken from subxt (Parity Technologies (UK))
2// https://github.com/paritytech/subxt/
3// And was adapted by Supercomputing Systems AG.
4//
5// Copyright 2019-2023 Parity Technologies (UK) Ltd and Supercomputing Systems AG.
6// This file is licensed as Apache-2.0
7// see LICENSE for license details.
8
9use 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	// Find the extrinsic types.
24	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
121/// The type IDs extracted from the metadata that represent the
122/// generic type parameters passed to the `UncheckedExtrinsic` from
123/// the substrate-based chain.
124struct ExtrinsicPartTypeIds {
125	address: u32,
126	call: u32,
127	signature: u32,
128	extra: u32,
129}
130
131impl ExtrinsicPartTypeIds {
132	/// Extract the generic type parameters IDs from the extrinsic type.
133	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
220/// Generates an outer `RuntimeError` enum type and adds it to the metadata.
221///
222/// Returns the id of the generated type from the registry.
223fn 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		// Position in type registry shouldn't change.
319		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		// Missing runtime call.
405		{
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		// Missing runtime event.
429		{
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}