// Copyright 2019-2022 Parity Technologies (UK) Ltd. // This file is part of subxt. // // subxt is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // subxt is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with subxt. If not, see . use std::{ collections::HashMap, convert::TryFrom, }; use codec::Error as CodecError; use frame_metadata::{ PalletConstantMetadata, RuntimeMetadata, RuntimeMetadataLastVersion, RuntimeMetadataPrefixed, StorageEntryMetadata, META_RESERVED, }; use crate::{ Call, Encoded, }; use scale_info::{ form::PortableForm, Type, Variant, }; /// Metadata error. #[derive(Debug, thiserror::Error)] pub enum MetadataError { /// Module is not in metadata. #[error("Pallet {0} not found")] PalletNotFound(String), /// Pallet is not in metadata. #[error("Pallet index {0} not found")] PalletIndexNotFound(u8), /// Call is not in metadata. #[error("Call {0} not found")] CallNotFound(&'static str), /// Event is not in metadata. #[error("Pallet {0}, Event {0} not found")] EventNotFound(u8, u8), /// Event is not in metadata. #[error("Pallet {0}, Error {0} not found")] ErrorNotFound(u8, u8), /// Storage is not in metadata. #[error("Storage {0} not found")] StorageNotFound(&'static str), /// Storage type does not match requested type. #[error("Storage type error")] StorageTypeError, /// Default error. #[error("Failed to decode default: {0}")] DefaultError(CodecError), /// Failure to decode constant value. #[error("Failed to decode constant value: {0}")] ConstantValueError(CodecError), /// Constant is not in metadata. #[error("Constant {0} not found")] ConstantNotFound(&'static str), /// Type is not in metadata. #[error("Type {0} missing from type registry")] TypeNotFound(u32), } /// Runtime metadata. #[derive(Clone, Debug)] pub struct Metadata { metadata: RuntimeMetadataLastVersion, pallets: HashMap, events: HashMap<(u8, u8), EventMetadata>, errors: HashMap<(u8, u8), ErrorMetadata>, } impl Metadata { /// Returns a reference to [`PalletMetadata`]. pub fn pallet(&self, name: &'static str) -> Result<&PalletMetadata, MetadataError> { self.pallets .get(name) .ok_or_else(|| MetadataError::PalletNotFound(name.to_string())) } /// Returns the metadata for the event at the given pallet and event indices. pub fn event( &self, pallet_index: u8, event_index: u8, ) -> Result<&EventMetadata, MetadataError> { let event = self .events .get(&(pallet_index, event_index)) .ok_or(MetadataError::EventNotFound(pallet_index, event_index))?; Ok(event) } /// Returns the metadata for the error at the given pallet and error indices. pub fn error( &self, pallet_index: u8, error_index: u8, ) -> Result<&ErrorMetadata, MetadataError> { let error = self .errors .get(&(pallet_index, error_index)) .ok_or(MetadataError::ErrorNotFound(pallet_index, error_index))?; Ok(error) } /// Resolve a type definition. pub fn resolve_type(&self, id: u32) -> Option<&Type> { self.metadata.types.resolve(id) } /// Return the runtime metadata. pub fn runtime_metadata(&self) -> &RuntimeMetadataLastVersion { &self.metadata } } /// Metadata for a specific pallet. #[derive(Clone, Debug)] pub struct PalletMetadata { index: u8, name: String, calls: HashMap, storage: HashMap>, constants: HashMap>, } impl PalletMetadata { /// Get the name of the pallet. pub fn name(&self) -> &str { &self.name } /// Encode a call based on this pallet metadata. pub fn encode_call(&self, call: &C) -> Result where C: Call, { let fn_index = self .calls .get(C::FUNCTION) .ok_or(MetadataError::CallNotFound(C::FUNCTION))?; let mut bytes = vec![self.index, *fn_index]; bytes.extend(call.encode()); Ok(Encoded(bytes)) } /// Return [`StorageEntryMetadata`] given some storage key. pub fn storage( &self, key: &'static str, ) -> Result<&StorageEntryMetadata, MetadataError> { self.storage .get(key) .ok_or(MetadataError::StorageNotFound(key)) } /// Get a constant's metadata by name. pub fn constant( &self, key: &'static str, ) -> Result<&PalletConstantMetadata, MetadataError> { self.constants .get(key) .ok_or(MetadataError::ConstantNotFound(key)) } } #[derive(Clone, Debug)] pub struct EventMetadata { pallet: String, event: String, variant: Variant, } impl EventMetadata { /// Get the name of the pallet from which the event was emitted. pub fn pallet(&self) -> &str { &self.pallet } /// Get the name of the pallet event which was emitted. pub fn event(&self) -> &str { &self.event } /// Get the type def variant for the pallet event. pub fn variant(&self) -> &Variant { &self.variant } } #[derive(Clone, Debug)] pub struct ErrorMetadata { pallet: String, error: String, variant: Variant, } impl ErrorMetadata { /// Get the name of the pallet from which the error originates. pub fn pallet(&self) -> &str { &self.pallet } /// Get the name of the specific pallet error. pub fn error(&self) -> &str { &self.error } /// Get the description of the specific pallet error. pub fn description(&self) -> &[String] { self.variant.docs() } } #[derive(Debug, thiserror::Error)] pub enum InvalidMetadataError { #[error("Invalid prefix")] InvalidPrefix, #[error("Invalid version")] InvalidVersion, #[error("Type {0} missing from type registry")] MissingType(u32), #[error("Type {0} was not a variant/enum type")] TypeDefNotVariant(u32), } impl TryFrom for Metadata { type Error = InvalidMetadataError; fn try_from(metadata: RuntimeMetadataPrefixed) -> Result { if metadata.0 != META_RESERVED { return Err(InvalidMetadataError::InvalidPrefix) } let metadata = match metadata.1 { RuntimeMetadata::V14(meta) => meta, _ => return Err(InvalidMetadataError::InvalidVersion), }; let get_type_def_variant = |type_id: u32| { let ty = metadata .types .resolve(type_id) .ok_or(InvalidMetadataError::MissingType(type_id))?; if let scale_info::TypeDef::Variant(var) = ty.type_def() { Ok(var) } else { Err(InvalidMetadataError::TypeDefNotVariant(type_id)) } }; let pallets = metadata .pallets .iter() .map(|pallet| { let calls = pallet.calls.as_ref().map_or(Ok(HashMap::new()), |call| { let type_def_variant = get_type_def_variant(call.ty.id())?; let calls = type_def_variant .variants() .iter() .map(|v| (v.name().clone(), v.index())) .collect(); Ok(calls) })?; let storage = pallet.storage.as_ref().map_or(HashMap::new(), |storage| { storage .entries .iter() .map(|entry| (entry.name.clone(), entry.clone())) .collect() }); let constants = pallet .constants .iter() .map(|constant| (constant.name.clone(), constant.clone())) .collect(); let pallet_metadata = PalletMetadata { index: pallet.index, name: pallet.name.to_string(), calls, storage, constants, }; Ok((pallet.name.to_string(), pallet_metadata)) }) .collect::>()?; let pallet_events = metadata .pallets .iter() .filter_map(|pallet| { pallet.event.as_ref().map(|event| { let type_def_variant = get_type_def_variant(event.ty.id())?; Ok((pallet, type_def_variant)) }) }) .collect::, _>>()?; let events = pallet_events .iter() .flat_map(|(pallet, type_def_variant)| { type_def_variant.variants().iter().map(move |var| { let key = (pallet.index, var.index()); let value = EventMetadata { pallet: pallet.name.clone(), event: var.name().clone(), variant: var.clone(), }; (key, value) }) }) .collect(); let pallet_errors = metadata .pallets .iter() .filter_map(|pallet| { pallet.error.as_ref().map(|error| { let type_def_variant = get_type_def_variant(error.ty.id())?; Ok((pallet, type_def_variant)) }) }) .collect::, _>>()?; let errors = pallet_errors .iter() .flat_map(|(pallet, type_def_variant)| { type_def_variant.variants().iter().map(move |var| { let key = (pallet.index, var.index()); let value = ErrorMetadata { pallet: pallet.name.clone(), error: var.name().clone(), variant: var.clone(), }; (key, value) }) }) .collect(); Ok(Self { metadata, pallets, events, errors, }) } }