gekko_metadata/
lib.rs

1//! Utilities to parse and process substrate metadata. Can also be enabled in
2//! `gekko` with the `"metadata"` feature.
3//!
4//! # Example
5//!
6//! ```no_run
7//! use gekko_metadata::*;
8//!
9//! // Parse runtime metadata
10//! let content = std::fs::read_to_string("metadata_kusama_9080.hex").unwrap();
11//! let data = parse_hex_metadata(content).unwrap().into_inner();
12//!
13//! // Get information about the extrinsic.
14//! let extr = data
15//!     .find_module_extrinsic("Balances", "transfer_keep_alive")
16//!     .unwrap();
17//!
18//! assert_eq!(extr.module_id, 4);
19//! assert_eq!(extr.dispatch_id, 3);
20//! assert_eq!(
21//!     extr.args,
22//!     vec![
23//!         ("dest", "<T::Lookup as StaticLookup>::Source"),
24//!         ("value", "Compact<T::Balance>"),
25//!     ]
26//! );
27//! ```
28
29// INFO: The earliest metadata versions are available in the substrate repo at
30// commit: a31c01b398d958ccf0a24d8c1c11fb073df66212
31
32#[macro_use]
33extern crate serde;
34#[macro_use]
35extern crate parity_scale_codec;
36
37use self::version::*;
38use parity_scale_codec::{Decode, Error as ScaleError};
39use serde_json::Error as SerdeJsonError;
40
41type Result<T> = std::result::Result<T, Error>;
42
43pub mod version;
44
45/// Parameters and other information about an individual extrinsic.
46#[derive(Debug, Clone, Eq, PartialEq)]
47pub struct ExtrinsicInfo<'a> {
48    /// The module Id. This is required when encoding the final extrinsic.
49    pub module_id: usize,
50    /// The dispatch Id. This is required when encoding the final extrinsic.
51    pub dispatch_id: usize,
52    /// The name of the module.
53    pub module_name: &'a str,
54    /// The name of the extrinsic.
55    pub extrinsic_name: &'a str,
56    /// Arguments that must be passed as the extrinsics body. A sequence of
57    /// key-value pairs, indicating the name and the type, respectively.
58    pub args: Vec<(&'a str, &'a str)>,
59    /// Documentation of the extrinsic, as provided by the Substrate metadata.
60    pub documentation: Vec<&'a str>,
61}
62
63/// An interface to retrieve information about extrinsics on any Substrate
64/// metadata version.
65pub trait ModuleMetadataExt {
66    fn modules_extrinsics<'a>(&'a self) -> Vec<ExtrinsicInfo<'a>>;
67    fn find_module_extrinsic<'a>(
68        &'a self,
69        method: &str,
70        extrinsic: &str,
71    ) -> Option<ExtrinsicInfo<'a>>;
72}
73
74/// Errors that can occur when parsing Substrate metadata.
75#[derive(Debug)]
76pub enum Error {
77    ParseJsonRpcMetadata(SerdeJsonError),
78    ParseHexMetadata(hex::FromHexError),
79    ParseRawMetadata(ScaleError),
80    InvalidMetadataVersion,
81}
82
83/// Helper type when dealing with the Json RPC response returned by
84/// Substrates `state_getMetadata`.
85#[derive(Debug, Clone, Deserialize)]
86pub struct JsonRpcResponse {
87    pub jsonrpc: String,
88    pub result: String,
89}
90
91/// Convenience function for parsing the Json RPC response returned by Substrates
92/// `state_getMetadata`.
93///
94/// Must fit the [`JsonRpcResponse`] structure.
95pub fn parse_jsonrpc_metadata<T: AsRef<[u8]>>(json: T) -> Result<MetadataVersion> {
96    let resp = serde_json::from_slice::<JsonRpcResponse>(json.as_ref())
97        .map_err(|err| Error::ParseJsonRpcMetadata(err))?;
98
99    parse_hex_metadata(resp.result.as_bytes())
100}
101
102/// Convenience function for parsing the metadata from a HEX representation, as
103/// returned by `state_getMetadata`.
104pub fn parse_hex_metadata<T: AsRef<[u8]>>(hex: T) -> Result<MetadataVersion> {
105    let hex = hex.as_ref();
106
107    // The `hex` crate does not handle `0x`...
108    let slice = if hex.starts_with(b"0x") {
109        hex[2..].as_ref()
110    } else {
111        hex
112    };
113
114    parse_raw_metadata(hex::decode(slice).map_err(|err| Error::ParseHexMetadata(err))?)
115}
116
117/// Parse the raw Substrate metadata.
118pub fn parse_raw_metadata<T: AsRef<[u8]>>(raw: T) -> Result<MetadataVersion> {
119    let raw = raw.as_ref();
120
121    // Remove the magic number before decoding, if it exists. From the substrate
122    // docs:
123    // > "The hex blob that is returned by the JSON-RPCs state_getMetadata
124    // > method starts with a hard-coded magic number, 0x6d657461, which
125    // > represents "meta" in plain text."
126    let mut slice = if raw.starts_with(b"meta") {
127        raw[4..].as_ref()
128    } else {
129        raw
130    };
131
132    MetadataVersion::decode(&mut slice).map_err(|err| Error::ParseRawMetadata(err))
133}
134
135/// Identifier of all the available Substrate metadata versions.
136#[derive(Debug, Clone, PartialEq, Encode, Decode)]
137pub enum MetadataVersion {
138    V0,
139    V1,
140    V2,
141    V3,
142    V4,
143    V5,
144    V6,
145    V7,
146    V8,
147    V9,
148    V10,
149    V11,
150    V12,
151    V13(MetadataV13),
152}
153
154impl MetadataVersion {
155    /// Consumes the object and returns the inner metadata structure, expecting
156    /// the latest version. Results in an error if the version is not the latest.
157    pub fn into_latest(self) -> Result<MetadataV13> {
158        match self {
159            MetadataVersion::V13(data) => Ok(data),
160            _ => Err(Error::InvalidMetadataVersion),
161        }
162    }
163    /// Returns the version number as an integer.
164    pub fn version_number(&self) -> usize {
165        use MetadataVersion::*;
166
167        match self {
168            V0 => 0,
169            V1 => 1,
170            V2 => 2,
171            V3 => 3,
172            V4 => 4,
173            V5 => 5,
174            V6 => 6,
175            V7 => 7,
176            V8 => 8,
177            V9 => 9,
178            V10 => 10,
179            V11 => 11,
180            V12 => 12,
181            V13(_) => 13,
182        }
183    }
184    pub fn into_inner(self) -> impl ModuleMetadataExt {
185        match self {
186            MetadataVersion::V13(m) => m,
187            _ => panic!(),
188        }
189    }
190}