metadata: Generate runtime outer enums if not present in V14 (#1174)

* metadata: Extend outer enum generation for V14

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* metadata: Generate outer enums if not present

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* metadata: Porpagate v14 error instead of panic

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* metadata: Try to find `RuntimeCall` then `Call` enums

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* metadata: Ensure the returned type is variant for outer enums

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* metadata: Replace or with or_else

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* metadata: Apply clippy

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* metadata: Return error and generate only `RuntimeError`

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* metadata: Remove modified path

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* metadata/tests: Check missing runtime types

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

---------

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
This commit is contained in:
Alexandru Vasile
2023-09-25 18:37:38 +03:00
committed by GitHub
parent a731a92c1f
commit c2522c6b5b
2 changed files with 196 additions and 63 deletions
+8 -2
View File
@@ -7,11 +7,11 @@ mod v15;
/// An error emitted if something goes wrong converting [`frame_metadata`]
/// types into [`crate::Metadata`].
#[derive(Debug, thiserror::Error)]
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum TryFromError {
/// Type missing from type registry
#[error("Type {0} is expected but not found in the type registry")]
#[error("Type id {0} is expected but not found in the type registry")]
TypeNotFound(u32),
/// Type was not a variant/enum type
#[error("Type {0} was not a variant/enum type, but is expected to be one")]
@@ -19,6 +19,12 @@ pub enum TryFromError {
/// An unsupported metadata version was provided.
#[error("Cannot convert v{0} metadata into Metadata type")]
UnsupportedMetadataVersion(u32),
/// Type name missing from type registry
#[error("Type name {0} is expected but not found in the type registry")]
TypeNameNotFound(String),
/// Invalid type path.
#[error("Type has an invalid path {0}")]
InvalidTypePath(String),
}
impl From<crate::Metadata> for frame_metadata::RuntimeMetadataPrefixed {
+188 -61
View File
@@ -7,12 +7,13 @@ use std::collections::HashMap;
use super::TryFromError;
use crate::Metadata;
use frame_metadata::{v14, v15};
use scale_info::TypeDef;
impl TryFrom<v14::RuntimeMetadataV14> for Metadata {
type Error = TryFromError;
fn try_from(value: v14::RuntimeMetadataV14) -> Result<Self, Self::Error> {
// Convert to v15 and then convert that into Metadata.
v14_to_v15(value).try_into()
v14_to_v15(value)?.try_into()
}
}
@@ -148,14 +149,15 @@ fn v15_to_v14(mut metadata: v15::RuntimeMetadataV15) -> v14::RuntimeMetadataV14
}
}
fn v14_to_v15(mut metadata: v14::RuntimeMetadataV14) -> v15::RuntimeMetadataV15 {
fn v14_to_v15(
mut metadata: v14::RuntimeMetadataV14,
) -> Result<v15::RuntimeMetadataV15, TryFromError> {
// Find the extrinsic types.
let extrinsic_parts = ExtrinsicPartTypeIds::new(&metadata)
.expect("Extrinsic types are always present on V14; qed");
let extrinsic_parts = ExtrinsicPartTypeIds::new(&metadata)?;
let outer_enums = generate_outer_enums(&mut metadata);
let outer_enums = generate_outer_enums(&mut metadata)?;
v15::RuntimeMetadataV15 {
Ok(v15::RuntimeMetadataV15 {
types: metadata.types,
pallets: metadata
.pallets
@@ -245,7 +247,7 @@ fn v14_to_v15(mut metadata: v14::RuntimeMetadataV14) -> v15::RuntimeMetadataV15
custom: v15::CustomMetadata {
map: Default::default(),
},
}
})
}
/// The type IDs extracted from the metadata that represent the
@@ -260,7 +262,7 @@ struct ExtrinsicPartTypeIds {
impl ExtrinsicPartTypeIds {
/// Extract the generic type parameters IDs from the extrinsic type.
fn new(metadata: &v14::RuntimeMetadataV14) -> Result<Self, String> {
fn new(metadata: &v14::RuntimeMetadataV14) -> Result<Self, TryFromError> {
const ADDRESS: &str = "Address";
const CALL: &str = "Call";
const SIGNATURE: &str = "Signature";
@@ -268,7 +270,7 @@ impl ExtrinsicPartTypeIds {
let extrinsic_id = metadata.extrinsic.ty.id;
let Some(extrinsic_ty) = metadata.types.resolve(extrinsic_id) else {
return Err("Missing extrinsic type".into());
return Err(TryFromError::TypeNotFound(extrinsic_id));
};
let params: HashMap<_, _> = extrinsic_ty
@@ -276,7 +278,7 @@ impl ExtrinsicPartTypeIds {
.iter()
.map(|ty_param| {
let Some(ty) = ty_param.ty else {
return Err("Missing type param type from extrinsic".to_string());
return Err(TryFromError::TypeNameNotFound(ty_param.name.clone()));
};
Ok((ty_param.name.as_str(), ty.id))
@@ -284,16 +286,16 @@ impl ExtrinsicPartTypeIds {
.collect::<Result<_, _>>()?;
let Some(address) = params.get(ADDRESS) else {
return Err("Missing address type from extrinsic".into());
return Err(TryFromError::TypeNameNotFound(ADDRESS.into()));
};
let Some(call) = params.get(CALL) else {
return Err("Missing call type from extrinsic".into());
return Err(TryFromError::TypeNameNotFound(CALL.into()));
};
let Some(signature) = params.get(SIGNATURE) else {
return Err("Missing signature type from extrinsic".into());
return Err(TryFromError::TypeNameNotFound(SIGNATURE.into()));
};
let Some(extra) = params.get(EXTRA) else {
return Err("Missing extra type from extrinsic".into());
return Err(TryFromError::TypeNameNotFound(EXTRA.into()));
};
Ok(ExtrinsicPartTypeIds {
@@ -307,53 +309,54 @@ impl ExtrinsicPartTypeIds {
fn generate_outer_enums(
metadata: &mut v14::RuntimeMetadataV14,
) -> v15::OuterEnums<scale_info::form::PortableForm> {
let call_enum = metadata
.types
.types
.iter()
.find(|ty| {
) -> Result<v15::OuterEnums<scale_info::form::PortableForm>, TryFromError> {
let find_type = |name: &str| {
metadata.types.types.iter().find_map(|ty| {
let Some(ident) = ty.ty.path.ident() else {
return false;
return None;
};
ident == "RuntimeCall"
})
.expect("RuntimeCall exists in V14; qed");
let event_enum = metadata
.types
.types
.iter()
.find(|ty| {
let Some(ident) = ty.ty.path.ident() else {
return false;
if ident != name {
return None;
}
let TypeDef::Variant(_) = &ty.ty.type_def else {
return None;
};
ident == "RuntimeEvent"
Some((ty.id, ty.ty.path.segments.clone()))
})
.expect("RuntimeEvent exists in V14; qed");
};
let call_ty = call_enum.id.into();
let event_ty = event_enum.id.into();
let Some((call_enum, mut call_path)) = find_type("RuntimeCall") else {
return Err(TryFromError::TypeNameNotFound("RuntimeCall".into()));
};
let mut path_segments = call_enum.ty.path.segments.clone();
let last = path_segments
.last_mut()
.expect("Should have at least one segment checked above; qed");
*last = "RuntimeError".to_string();
let Some((event_enum, _)) = find_type("RuntimeEvent") else {
return Err(TryFromError::TypeNameNotFound("RuntimeEvent".into()));
};
let error_ty_id = generate_runtime_error_type(metadata, path_segments);
let error_enum = if let Some((error_enum, _)) = find_type("RuntimeError") {
error_enum
} else {
let Some(last) = call_path.last_mut() else {
return Err(TryFromError::InvalidTypePath("RuntimeCall".into()));
};
*last = "RuntimeError".to_string();
generate_outer_error_enum_type(metadata, call_path)
};
v15::OuterEnums {
call_enum_ty: call_ty,
event_enum_ty: event_ty,
error_enum_ty: error_ty_id.into(),
}
Ok(v15::OuterEnums {
call_enum_ty: call_enum.into(),
event_enum_ty: event_enum.into(),
error_enum_ty: error_enum.into(),
})
}
/// Generate the `RuntimeError` type and add it to the metadata.
/// Generates an outer `RuntimeError` enum type and adds it to the metadata.
///
/// Returns the `RuntimeError` Id from the registry.
fn generate_runtime_error_type(
/// Returns the id of the generated type from the registry.
fn generate_outer_error_enum_type(
metadata: &mut v14::RuntimeMetadataV14,
path_segments: Vec<String>,
) -> u32 {
@@ -361,16 +364,18 @@ fn generate_runtime_error_type(
.pallets
.iter()
.filter_map(|pallet| {
let Some(pallet_error) = &pallet.error else {
let Some(error) = &pallet.error else {
return None;
};
let path = format!("{}Error", pallet.name);
let ty = error.ty.id.into();
Some(scale_info::Variant {
name: pallet.name.clone(),
fields: vec![scale_info::Field {
name: None,
ty: pallet_error.ty.id.into(),
ty,
type_name: Some(path),
docs: vec![],
}],
@@ -380,7 +385,7 @@ fn generate_runtime_error_type(
})
.collect();
let error_type = scale_info::Type {
let enum_type = scale_info::Type {
path: scale_info::Path {
segments: path_segments,
},
@@ -389,23 +394,25 @@ fn generate_runtime_error_type(
docs: vec![],
};
let error_type_id = metadata.types.types.len() as u32;
let enum_type_id = metadata.types.types.len() as u32;
metadata.types.types.push(scale_info::PortableType {
id: error_type_id,
ty: error_type,
id: enum_type_id,
ty: enum_type,
});
error_type_id
enum_type_id
}
#[cfg(test)]
mod tests {
use super::*;
use codec::Decode;
use frame_metadata::{v15::RuntimeMetadataV15, RuntimeMetadata, RuntimeMetadataPrefixed};
use scale_info::TypeDef;
use std::{fs, path::Path};
use frame_metadata::{
v14::ExtrinsicMetadata, v15::RuntimeMetadataV15, RuntimeMetadata, RuntimeMetadataPrefixed,
};
use scale_info::{meta_type, IntoPortable, TypeDef, TypeInfo};
use std::{fs, marker::PhantomData, path::Path};
fn load_v15_metadata() -> RuntimeMetadataV15 {
let bytes = fs::read(Path::new("../artifacts/polkadot_metadata_full.scale"))
@@ -493,7 +500,7 @@ mod tests {
assert_eq!(v15_sign, v14_sign);
// Ensure we don't lose the information when converting back to v15.
let converted_v15 = v14_to_v15(v14);
let converted_v15 = v14_to_v15(v14).unwrap();
let v15_addr = v15.types.resolve(v15.extrinsic.address_ty.id).unwrap();
let converted_v15_addr = converted_v15
@@ -530,7 +537,7 @@ mod tests {
let v14 = v15_to_v14(v15.clone());
// Convert back to v15 and expect to have the enum types properly generated.
let converted_v15 = v14_to_v15(v14);
let converted_v15 = v14_to_v15(v14).unwrap();
// RuntimeCall and RuntimeEvent were already present in the metadata v14.
let v15_call = v15.types.resolve(v15.outer_enums.call_enum_ty.id).unwrap();
@@ -593,4 +600,124 @@ mod tests {
}
}
}
#[test]
fn test_missing_extrinsic_types() {
#[derive(TypeInfo)]
struct Runtime;
let generate_metadata = |extrinsic_ty| {
let mut registry = scale_info::Registry::new();
let ty = registry.register_type(&meta_type::<Runtime>());
let extrinsic = ExtrinsicMetadata {
ty: extrinsic_ty,
version: 0,
signed_extensions: vec![],
}
.into_portable(&mut registry);
v14::RuntimeMetadataV14 {
types: registry.into(),
pallets: Vec::new(),
extrinsic,
ty,
}
};
let metadata = generate_metadata(meta_type::<()>());
let err = v14_to_v15(metadata).unwrap_err();
assert_eq!(err, TryFromError::TypeNameNotFound("Address".into()));
#[derive(TypeInfo)]
struct ExtrinsicNoCall<Address, Signature, Extra> {
_phantom: PhantomData<(Address, Signature, Extra)>,
}
let metadata = generate_metadata(meta_type::<ExtrinsicNoCall<(), (), ()>>());
let err = v14_to_v15(metadata).unwrap_err();
assert_eq!(err, TryFromError::TypeNameNotFound("Call".into()));
#[derive(TypeInfo)]
struct ExtrinsicNoSign<Call, Address, Extra> {
_phantom: PhantomData<(Call, Address, Extra)>,
}
let metadata = generate_metadata(meta_type::<ExtrinsicNoSign<(), (), ()>>());
let err = v14_to_v15(metadata).unwrap_err();
assert_eq!(err, TryFromError::TypeNameNotFound("Signature".into()));
#[derive(TypeInfo)]
struct ExtrinsicNoExtra<Call, Address, Signature> {
_phantom: PhantomData<(Call, Address, Signature)>,
}
let metadata = generate_metadata(meta_type::<ExtrinsicNoExtra<(), (), ()>>());
let err = v14_to_v15(metadata).unwrap_err();
assert_eq!(err, TryFromError::TypeNameNotFound("Extra".into()));
}
#[test]
fn test_missing_outer_enum_types() {
#[derive(TypeInfo)]
struct Runtime;
#[derive(TypeInfo)]
enum RuntimeCall {}
#[derive(TypeInfo)]
enum RuntimeEvent {}
#[allow(unused)]
#[derive(TypeInfo)]
struct ExtrinsicType<Address, Call, Signature, Extra> {
pub signature: Option<(Address, Signature, Extra)>,
pub function: Call,
}
// Missing runtime call.
{
let mut registry = scale_info::Registry::new();
let ty = registry.register_type(&meta_type::<Runtime>());
registry.register_type(&meta_type::<RuntimeEvent>());
let extrinsic = ExtrinsicMetadata {
ty: meta_type::<ExtrinsicType<(), (), (), ()>>(),
version: 0,
signed_extensions: vec![],
}
.into_portable(&mut registry);
let metadata = v14::RuntimeMetadataV14 {
types: registry.into(),
pallets: Vec::new(),
extrinsic,
ty,
};
let err = v14_to_v15(metadata).unwrap_err();
assert_eq!(err, TryFromError::TypeNameNotFound("RuntimeCall".into()));
}
// Missing runtime event.
{
let mut registry = scale_info::Registry::new();
let ty = registry.register_type(&meta_type::<Runtime>());
registry.register_type(&meta_type::<RuntimeCall>());
let extrinsic = ExtrinsicMetadata {
ty: meta_type::<ExtrinsicType<(), (), (), ()>>(),
version: 0,
signed_extensions: vec![],
}
.into_portable(&mut registry);
let metadata = v14::RuntimeMetadataV14 {
types: registry.into(),
pallets: Vec::new(),
extrinsic,
ty,
};
let err = v14_to_v15(metadata).unwrap_err();
assert_eq!(err, TryFromError::TypeNameNotFound("RuntimeEvent".into()));
}
}
}