1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
// INFO: The earliest metadata versions are available in the substrate repo at
// commit: a31c01b398d958ccf0a24d8c1c11fb073df66212

#[macro_use]
extern crate serde;
#[macro_use]
extern crate parity_scale_codec;

use self::version::*;
use parity_scale_codec::{Decode, Error as ScaleError};
use serde_json::Error as SerdeJsonError;

type Result<T> = std::result::Result<T, Error>;

pub mod version;

pub struct ExtrinsicInfo<'a> {
    pub module_id: usize,
    pub dispatch_id: usize,
    pub module_name: &'a str,
    pub extrinsic_name: &'a str,
    pub args: Vec<(&'a str, &'a str)>,
    pub documentation: Vec<&'a str>,
}

pub trait ModuleMetadataExt {
    fn modules_extrinsics<'a>(&'a self) -> Vec<ExtrinsicInfo<'a>>;
    fn find_module_extrinsic<'a>(
        &'a self,
        method: &str,
        extrinsic: &str,
    ) -> Result<Option<ExtrinsicInfo<'a>>>;
}

#[derive(Debug)]
pub enum Error {
    ParseJsonRpcMetadata(SerdeJsonError),
    ParseHexMetadata(hex::FromHexError),
    ParseRawMetadata(ScaleError),
    InvalidMetadataVersion,
}

#[derive(Debug, Clone, Deserialize)]
pub struct JsonRpcResponse {
    pub jsonrpc: String,
    pub result: String,
}

// Convenience function for parsing the Json RPC response returned by
// `state_getMetadata`. Must fit the [`JsonRpcResponse`] structure.
pub fn parse_jsonrpc_metadata<T: AsRef<[u8]>>(json: T) -> Result<MetadataVersion> {
    let resp = serde_json::from_slice::<JsonRpcResponse>(json.as_ref())
        .map_err(|err| Error::ParseJsonRpcMetadata(err))?;

    parse_hex_metadata(resp.result.as_bytes())
}

// Convenience function for parsing the metadata from a HEX representation, as
// returned by `state_getMetadata`.
pub fn parse_hex_metadata<T: AsRef<[u8]>>(hex: T) -> Result<MetadataVersion> {
    let hex = hex.as_ref();

    // The `hex` crate does not handle `0x`...
    let slice = if hex.starts_with(b"0x") {
        hex[2..].as_ref()
    } else {
        hex
    };

    parse_raw_metadata(hex::decode(slice).map_err(|err| Error::ParseHexMetadata(err))?)
}

pub fn parse_raw_metadata<T: AsRef<[u8]>>(raw: T) -> Result<MetadataVersion> {
    let raw = raw.as_ref();

    // Remove the magic number before decoding, if it exists. From the substrate
    // docs:
    // > "The hex blob that is returned by the JSON-RPCs state_getMetadata
    // > method starts with a hard-coded magic number, 0x6d657461, which
    // > represents "meta" in plain text."
    let mut slice = if raw.starts_with(b"meta") {
        raw[4..].as_ref()
    } else {
        raw
    };

    MetadataVersion::decode(&mut slice).map_err(|err| Error::ParseRawMetadata(err))
}

#[derive(Debug, Clone, PartialEq, Encode, Decode)]
pub enum MetadataVersion {
    V0,
    V1,
    V2,
    V3,
    V4,
    V5,
    V6,
    V7,
    V8,
    V9,
    V10,
    V11,
    V12,
    V13(MetadataV13),
}

impl MetadataVersion {
    /// Consumes the object and returns the inner metadata structure, expecting
    /// the latest version. Results in an error if the version is not the latest.
    pub fn into_latest(self) -> Result<MetadataV13> {
        match self {
            MetadataVersion::V13(data) => Ok(data),
            _ => Err(Error::InvalidMetadataVersion),
        }
    }
    /// Returns the version number as an integer.
    pub fn version_number(&self) -> usize {
        use MetadataVersion::*;

        match self {
            V0 => 0,
            V1 => 1,
            V2 => 2,
            V3 => 3,
            V4 => 4,
            V5 => 5,
            V6 => 6,
            V7 => 7,
            V8 => 8,
            V9 => 9,
            V10 => 10,
            V11 => 11,
            V12 => 12,
            V13(_) => 13,
        }
    }
    pub fn into_inner(self) -> impl ModuleMetadataExt {
        match self {
            MetadataVersion::V13(m) => m,
            _ => panic!(),
        }
    }
}

#[test]
fn parse_file() {
    use std::fs::read_to_string;

    let content = read_to_string("dumps/metadata_polkadot_9050.json").unwrap();
    let res = parse_jsonrpc_metadata(content).unwrap();

    let data = match res {
        MetadataVersion::V13(data) => data,
        _ => panic!(),
    };

    for m in data.modules {
        println!("> {}", m.name);
        m.calls.map(|calls| {
            for c in calls {
                println!("  > {}", c.name);
                for arg in c.arguments {
                    println!("    > {}: {}", arg.name, arg.ty);
                }
            }
        });
    }
}