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