1#![allow(clippy::indexing_slicing, clippy::arithmetic_side_effects)]
15
16pub mod type_string;
17
18use std::collections::HashMap;
19
20use codec::Decode;
21use frame_metadata::{v14, v15, RuntimeMetadata, RuntimeMetadataPrefixed};
22use scale_info::{form::PortableForm, PortableRegistry, TypeDef};
23
24use crate::error::CoreError;
25
26#[derive(Debug, Clone)]
29pub struct StorageInfo {
30 pub name: String,
31 pub prefix: String,
33 pub modifier: String,
35 pub hashers: Vec<String>,
36 pub key_types: Vec<u32>,
38 pub value_type: u32,
39 pub default_bytes: Vec<u8>,
40}
41
42#[derive(Debug, Clone)]
43pub struct ConstantInfo {
44 pub name: String,
45 pub ty: u32,
46 pub value: Vec<u8>,
47 pub docs: Vec<String>,
48}
49
50#[derive(Debug, Clone)]
51pub struct PalletInfo {
52 pub name: String,
53 pub index: u8,
54 pub calls_type: Option<u32>,
56 pub events_type: Option<u32>,
58 pub errors_type: Option<u32>,
60 pub constants: Vec<ConstantInfo>,
61 pub storage: Vec<StorageInfo>,
62}
63
64#[derive(Debug, Clone)]
65pub struct SignedExtensionInfo {
66 pub identifier: String,
67 pub ty: u32,
69 pub additional_signed: u32,
71}
72
73#[derive(Debug, Clone)]
74pub struct ExtrinsicInfo {
75 pub version: u8,
76 pub address_type: Option<u32>,
77 pub call_type: Option<u32>,
78 pub signature_type: Option<u32>,
79 pub signed_extensions: Vec<SignedExtensionInfo>,
80}
81
82#[derive(Debug, Clone)]
83pub struct RuntimeApiParamInfo {
84 pub name: String,
85 pub ty: u32,
86}
87
88#[derive(Debug, Clone)]
89pub struct RuntimeApiMethodInfo {
90 pub name: String,
91 pub inputs: Vec<RuntimeApiParamInfo>,
92 pub output: u32,
93 pub docs: Vec<String>,
94}
95
96#[derive(Debug, Clone)]
97pub struct RuntimeApiInfo {
98 pub name: String,
99 pub methods: Vec<RuntimeApiMethodInfo>,
100}
101
102macro_rules! pallet_info {
105 ($pallet:expr, $types:expr) => {{
106 let pallet = $pallet;
107 let storage = match &pallet.storage {
108 Some(s) => s
109 .entries
110 .iter()
111 .map(|e| storage_info(&s.prefix, e, $types))
112 .collect(),
113 None => Vec::new(),
114 };
115 PalletInfo {
116 name: pallet.name.clone(),
117 index: pallet.index,
118 calls_type: pallet.calls.as_ref().map(|c| c.ty.id),
119 events_type: pallet.event.as_ref().map(|e| e.ty.id),
120 errors_type: pallet.error.as_ref().map(|e| e.ty.id),
121 constants: pallet
122 .constants
123 .iter()
124 .map(|c| ConstantInfo {
125 name: c.name.clone(),
126 ty: c.ty.id,
127 value: c.value.clone(),
128 docs: c.docs.clone(),
129 })
130 .collect(),
131 storage,
132 }
133 }};
134}
135
136pub struct Runtime {
138 pub spec_version: u32,
139 pub transaction_version: u32,
140 pub ss58_format: u16,
141 pub is_v15: bool,
142 pub metadata_bytes: Vec<u8>,
145 pub types: PortableRegistry,
146 pub pallets: Vec<PalletInfo>,
147 pub extrinsic: ExtrinsicInfo,
148 pub apis: Vec<RuntimeApiInfo>,
149 pub outer_event_type: Option<u32>,
151 pallet_by_name: HashMap<String, usize>,
152 pallet_by_index: HashMap<u8, usize>,
153 name_to_id: HashMap<String, u32>,
156 id_to_name: HashMap<u32, String>,
157}
158
159impl Runtime {
160 pub fn parse(
164 metadata_bytes: &[u8],
165 spec_version: u32,
166 transaction_version: u32,
167 ss58_format: u16,
168 ) -> Result<Self, CoreError> {
169 let prefixed = RuntimeMetadataPrefixed::decode(&mut &metadata_bytes[..])
170 .map_err(|e| CoreError::Codec(format!("cannot decode runtime metadata: {e}")))?;
171 let (types, pallets, extrinsic, apis, outer_event_type, is_v15) = match prefixed.1 {
172 RuntimeMetadata::V14(m) => {
173 let pallets = m
174 .pallets
175 .iter()
176 .map(|p| pallet_info!(p, &m.types))
177 .collect();
178 let extrinsic = extrinsic_info_v14(&m.extrinsic, &m.types);
179 let outer_event = find_outer_event_v14(&m.types);
180 (m.types, pallets, extrinsic, Vec::new(), outer_event, false)
181 }
182 RuntimeMetadata::V15(m) => {
183 let pallets = m
184 .pallets
185 .iter()
186 .map(|p| pallet_info!(p, &m.types))
187 .collect();
188 let extrinsic = extrinsic_info_v15(&m.extrinsic);
189 let apis = m.apis.iter().map(api_info_v15).collect();
190 let outer_event = Some(m.outer_enums.event_enum_ty.id);
191 (m.types, pallets, extrinsic, apis, outer_event, true)
192 }
193 other => {
194 return Err(CoreError::Codec(format!(
195 "unsupported metadata version V{}",
196 other.version()
197 )))
198 }
199 };
200 let pallets: Vec<PalletInfo> = pallets;
201 let pallet_by_name = pallets
202 .iter()
203 .enumerate()
204 .map(|(i, p)| (p.name.clone(), i))
205 .collect();
206 let pallet_by_index = pallets
207 .iter()
208 .enumerate()
209 .map(|(i, p)| (p.index, i))
210 .collect();
211 let (name_to_id, id_to_name) = build_name_maps(&types);
212 Ok(Self {
213 spec_version,
214 transaction_version,
215 ss58_format,
216 is_v15,
217 metadata_bytes: metadata_bytes.to_vec(),
218 types,
219 pallets,
220 extrinsic,
221 apis,
222 outer_event_type,
223 pallet_by_name,
224 pallet_by_index,
225 name_to_id,
226 id_to_name,
227 })
228 }
229
230 pub fn pallet(&self, name: &str) -> Option<&PalletInfo> {
231 self.pallet_by_name.get(name).map(|&i| &self.pallets[i])
232 }
233
234 pub fn pallet_at(&self, index: u8) -> Option<&PalletInfo> {
235 self.pallet_by_index.get(&index).map(|&i| &self.pallets[i])
236 }
237
238 pub fn storage_entry(&self, pallet: &str, name: &str) -> Option<&StorageInfo> {
239 self.pallet(pallet)?.storage.iter().find(|s| s.name == name)
240 }
241
242 pub fn constant(&self, pallet: &str, name: &str) -> Option<&ConstantInfo> {
243 self.pallet(pallet)?
244 .constants
245 .iter()
246 .find(|c| c.name == name)
247 }
248
249 pub fn type_id_of(&self, name: &str) -> Option<u32> {
250 self.name_to_id.get(name).copied()
251 }
252
253 pub fn type_name_of(&self, id: u32) -> Option<&str> {
254 self.id_to_name.get(&id).map(String::as_str)
255 }
256
257 pub fn resolve(&self, id: u32) -> Result<&scale_info::Type<PortableForm>, CoreError> {
258 self.types
259 .resolve(id)
260 .ok_or_else(|| CoreError::Codec(format!("unknown type id {id}")))
261 }
262
263 pub fn type_ident(&self, id: u32) -> String {
273 self.type_ident_bounded(id, 0)
274 }
275
276 fn type_ident_bounded(&self, id: u32, depth: usize) -> String {
277 if depth > 8 {
280 return format!("scale_info::{id}");
281 }
282 let Ok(ty) = self.resolve(id) else {
283 return format!("scale_info::{id}");
284 };
285 if let Some(last) = ty.path.segments.last() {
286 if last == "Option" && ty.path.segments.len() == 1 {
287 if let TypeDef::Variant(variant) = &ty.type_def {
288 if let Some(field) = variant
289 .variants
290 .iter()
291 .find(|v| v.name == "Some")
292 .and_then(|v| v.fields.first())
293 {
294 return format!(
295 "Option<{}>",
296 self.type_ident_bounded(field.ty.id, depth + 1)
297 );
298 }
299 }
300 }
301 return last.clone();
302 }
303 match &ty.type_def {
304 TypeDef::Primitive(p) => primitive_name(p).to_string(),
305 TypeDef::Compact(c) => format!(
306 "Compact<{}>",
307 self.type_ident_bounded(c.type_param.id, depth + 1)
308 ),
309 TypeDef::Sequence(s) => format!(
310 "Vec<{}>",
311 self.type_ident_bounded(s.type_param.id, depth + 1)
312 ),
313 TypeDef::Array(a) => format!(
314 "[{}; {}]",
315 self.type_ident_bounded(a.type_param.id, depth + 1),
316 a.len
317 ),
318 TypeDef::Tuple(t) => {
319 let parts: Vec<String> = t
320 .fields
321 .iter()
322 .map(|f| self.type_ident_bounded(f.id, depth + 1))
323 .collect();
324 format!("({})", parts.join(", "))
325 }
326 _ => format!("scale_info::{id}"),
327 }
328 }
329
330 pub fn registry_json(&self) -> Result<String, CoreError> {
335 serde_json::to_string(&self.types)
336 .map_err(|e| CoreError::Codec(format!("registry serialization failed: {e}")))
337 }
338
339 pub fn module_error(
341 &self,
342 module_index: u8,
343 error_index: u8,
344 ) -> Result<(String, Vec<String>), CoreError> {
345 let pallet = self
346 .pallet_at(module_index)
347 .ok_or_else(|| CoreError::Codec(format!("no pallet at index {module_index}")))?;
348 let errors_type = pallet
349 .errors_type
350 .ok_or_else(|| CoreError::Codec(format!("pallet {} has no errors", pallet.name)))?;
351 let ty = self.resolve(errors_type)?;
352 let TypeDef::Variant(variant) = &ty.type_def else {
353 return Err(CoreError::Codec("error type is not an enum".into()));
354 };
355 let error = variant
356 .variants
357 .iter()
358 .find(|v| v.index == error_index)
359 .ok_or_else(|| {
360 CoreError::Codec(format!(
361 "no error at index {error_index} in pallet {}",
362 pallet.name
363 ))
364 })?;
365 Ok((error.name.clone(), error.docs.clone()))
366 }
367}
368
369fn storage_info(
370 prefix: &str,
371 entry: &v14::StorageEntryMetadata<PortableForm>,
372 types: &PortableRegistry,
373) -> StorageInfo {
374 let modifier = match entry.modifier {
375 v14::StorageEntryModifier::Optional => "Optional",
376 v14::StorageEntryModifier::Default => "Default",
377 };
378 let (hashers, key_types, value_type) = match &entry.ty {
379 v14::StorageEntryType::Plain(ty) => (Vec::new(), Vec::new(), ty.id),
380 v14::StorageEntryType::Map {
381 hashers,
382 key,
383 value,
384 } => {
385 let hasher_names: Vec<String> = hashers.iter().map(|h| format!("{h:?}")).collect();
386 let key_ids: Vec<u32> = if hasher_names.len() == 1 {
389 vec![key.id]
390 } else {
391 match types.resolve(key.id).map(|t| &t.type_def) {
392 Some(TypeDef::Tuple(tuple)) => tuple.fields.iter().map(|f| f.id).collect(),
393 _ => vec![key.id],
394 }
395 };
396 (hasher_names, key_ids, value.id)
397 }
398 };
399 StorageInfo {
400 name: entry.name.clone(),
401 prefix: prefix.to_string(),
402 modifier: modifier.to_string(),
403 hashers,
404 key_types,
405 value_type,
406 default_bytes: entry.default.clone(),
407 }
408}
409
410fn extrinsic_info_v14(
411 extrinsic: &v14::ExtrinsicMetadata<PortableForm>,
412 types: &PortableRegistry,
413) -> ExtrinsicInfo {
414 let mut address_type = None;
417 let mut call_type = None;
418 let mut signature_type = None;
419 if let Some(ty) = types.resolve(extrinsic.ty.id) {
420 for param in &ty.type_params {
421 let target = match param.name.as_str() {
422 "Address" => &mut address_type,
423 "Call" => &mut call_type,
424 "Signature" => &mut signature_type,
425 _ => continue,
426 };
427 *target = param.ty.map(|t| t.id);
428 }
429 }
430 ExtrinsicInfo {
431 version: extrinsic.version,
432 address_type,
433 call_type,
434 signature_type,
435 signed_extensions: extrinsic
436 .signed_extensions
437 .iter()
438 .map(|e| SignedExtensionInfo {
439 identifier: e.identifier.clone(),
440 ty: e.ty.id,
441 additional_signed: e.additional_signed.id,
442 })
443 .collect(),
444 }
445}
446
447fn extrinsic_info_v15(extrinsic: &v15::ExtrinsicMetadata<PortableForm>) -> ExtrinsicInfo {
448 ExtrinsicInfo {
449 version: extrinsic.version,
450 address_type: Some(extrinsic.address_ty.id),
451 call_type: Some(extrinsic.call_ty.id),
452 signature_type: Some(extrinsic.signature_ty.id),
453 signed_extensions: extrinsic
454 .signed_extensions
455 .iter()
456 .map(|e| SignedExtensionInfo {
457 identifier: e.identifier.clone(),
458 ty: e.ty.id,
459 additional_signed: e.additional_signed.id,
460 })
461 .collect(),
462 }
463}
464
465fn find_outer_event_v14(types: &PortableRegistry) -> Option<u32> {
468 let record = types.types.iter().find(|t| {
469 t.ty.path.segments.len() == 2
470 && t.ty.path.segments[0] == "frame_system"
471 && t.ty.path.segments[1] == "EventRecord"
472 })?;
473 let TypeDef::Composite(composite) = &record.ty.type_def else {
474 return None;
475 };
476 composite
477 .fields
478 .iter()
479 .find(|f| f.name.as_deref() == Some("event"))
480 .map(|f| f.ty.id)
481}
482
483fn api_info_v15(api: &v15::RuntimeApiMetadata<PortableForm>) -> RuntimeApiInfo {
484 RuntimeApiInfo {
485 name: api.name.clone(),
486 methods: api
487 .methods
488 .iter()
489 .map(|m| RuntimeApiMethodInfo {
490 name: m.name.clone(),
491 inputs: m
492 .inputs
493 .iter()
494 .map(|p| RuntimeApiParamInfo {
495 name: p.name.clone(),
496 ty: p.ty.id,
497 })
498 .collect(),
499 output: m.output.id,
500 docs: m.docs.clone(),
501 })
502 .collect(),
503 }
504}
505
506fn build_name_maps(types: &PortableRegistry) -> (HashMap<String, u32>, HashMap<u32, String>) {
511 let mut name_to_id: HashMap<String, u32> = HashMap::new();
512 let mut id_to_name: HashMap<u32, String> = HashMap::new();
513
514 for ty in types.types.iter() {
515 let def = &ty.ty.type_def;
516 if !ty.ty.type_params.is_empty() || matches!(def, TypeDef::Variant(_)) {
517 continue;
518 }
519 if let Some(segment) = ty.ty.path.segments.last() {
520 name_to_id.insert(segment.clone(), ty.id);
521 id_to_name.insert(ty.id, segment.clone());
522 } else if let TypeDef::Primitive(p) = def {
523 let name = primitive_name(p);
524 name_to_id.insert(name.to_string(), ty.id);
525 id_to_name.insert(ty.id, name.to_string());
526 }
527 }
528
529 let mut progressed = true;
530 while progressed {
531 progressed = false;
532 for ty in types.types.iter() {
533 if id_to_name.contains_key(&ty.id) {
534 continue;
535 }
536 let resolved: Option<String> = (|| {
537 let def = &ty.ty.type_def;
538 if let Some(segment) = ty.ty.path.segments.last() {
539 if !ty.ty.type_params.is_empty() {
540 let mut inner = Vec::new();
541 for param in &ty.ty.type_params {
542 let dep = param.ty?.id;
543 inner.push(id_to_name.get(&dep)?.clone());
544 }
545 return Some(format!("{segment}<{}>", inner.join(", ")));
546 }
547 if matches!(def, TypeDef::Variant(_)) {
548 return None;
549 }
550 return Some(segment.clone());
551 }
552 match def {
553 TypeDef::Sequence(s) => {
554 Some(format!("Vec<{}>", id_to_name.get(&s.type_param.id)?))
555 }
556 TypeDef::Array(a) => Some(format!(
557 "[{}; {}]",
558 id_to_name.get(&a.type_param.id)?,
559 a.len
560 )),
561 TypeDef::Compact(c) => {
562 Some(format!("Compact<{}>", id_to_name.get(&c.type_param.id)?))
563 }
564 TypeDef::Tuple(t) => {
565 let mut names = Vec::new();
566 for field in &t.fields {
567 names.push(id_to_name.get(&field.id)?.clone());
568 }
569 Some(format!("({})", names.join(", ")))
570 }
571 _ => None,
572 }
573 })();
574 if let Some(name) = resolved {
575 name_to_id.insert(name.clone(), ty.id);
576 id_to_name.insert(ty.id, name);
577 progressed = true;
578 }
579 }
580 }
581 (name_to_id, id_to_name)
582}
583
584pub(crate) fn primitive_name(p: &scale_info::TypeDefPrimitive) -> &'static str {
585 use scale_info::TypeDefPrimitive as P;
586 match p {
587 P::Bool => "bool",
588 P::Char => "char",
589 P::Str => "str",
590 P::U8 => "u8",
591 P::U16 => "u16",
592 P::U32 => "u32",
593 P::U64 => "u64",
594 P::U128 => "u128",
595 P::U256 => "u256",
596 P::I8 => "i8",
597 P::I16 => "i16",
598 P::I32 => "i32",
599 P::I64 => "i64",
600 P::I128 => "i128",
601 P::I256 => "i256",
602 }
603}