subweight_core/parse/
storage.rs

1use std::path::Path;
2use syn::{BinOp, Expr, ExprStruct, Item, ItemConst, Type};
3
4use crate::{parse::path_to_string, term::SimpleTerm as Term};
5
6#[derive(Debug, PartialEq, Eq, Clone, Copy)]
7pub enum Db {
8	Parity,
9	Rocks,
10}
11
12#[derive(Debug, PartialEq, Eq, Clone)]
13pub struct RWs {
14	pub read: Term,
15	pub write: Term,
16}
17
18#[derive(Debug, PartialEq, Eq, Clone)]
19pub struct Weights {
20	pub db: Db,
21	pub weights: RWs,
22}
23
24/// Multiplies a [`Term`] with the [`crate::scope::STORAGE_READ_VAR`] constant.
25#[macro_export]
26macro_rules! reads {
27	($a:expr) => {
28		SimpleTerm::Mul($a.into(), SimpleTerm::Var($crate::scope::STORAGE_READ_VAR.into()).into())
29	};
30}
31
32/// Multiplies a [`Term`] with the [`crate::scope::STORAGE_READ_VAR`] constant.
33#[macro_export]
34macro_rules! creads {
35	($a:expr) => {
36		Term::Mul($a.into(), Term::Var($crate::scope::STORAGE_READ_VAR.into()).into())
37	};
38}
39
40/// Multiplies a [`Term`] with the [`crate::scope::STORAGE_WRITE_VAR`] constant.
41#[macro_export]
42macro_rules! writes {
43	($a:expr) => {
44		SimpleTerm::Mul($a.into(), SimpleTerm::Var($crate::scope::STORAGE_WRITE_VAR.into()).into())
45	};
46}
47
48/// Multiplies a [`Term`] with the [`crate::scope::STORAGE_WRITE_VAR`] constant.
49#[macro_export]
50macro_rules! cwrites {
51	($a:expr) => {
52		Term::Mul($a.into(), Term::Var($crate::scope::STORAGE_WRITE_VAR.into()).into())
53	};
54}
55
56/// Parses a storage weight file.
57///
58/// These files are often named: `paritydb_weights.rs.txt` or `rocksdb_weights.rs.txt`.
59pub fn parse_file(file: &Path) -> Result<Weights, String> {
60	let content = super::read_file(file)?;
61	parse_content(content)
62}
63
64pub fn parse_content(content: String) -> Result<Weights, String> {
65	let ast = syn::parse_file(&content).map_err(|e| e.to_string())?;
66	for item in ast.items {
67		if let Ok(res) = handle_item(&item) {
68			return Ok(res)
69		}
70	}
71	Err("No DB weights found".to_string())
72}
73
74fn handle_item(item: &Item) -> Result<Weights, String> {
75	match item {
76		// The current Substrate template has a useless `constants` mod.
77		Item::Mod(m) => {
78			if m.ident == "constants" {
79				if let Some((_, content)) = m.content.as_ref() {
80					for item in content {
81						let res = handle_item(item);
82						// Ignore errors
83						if res.is_ok() {
84							return res
85						}
86					}
87					return Err("Did not find parameter_types!".into())
88				}
89			}
90			Err(format!("Unexpected module: {}", m.ident))
91		},
92		Item::Macro(m) => {
93			let name = m.mac.path.segments.last();
94			if name.unwrap().ident == "parameter_types" {
95				parse_macro(m.mac.tokens.clone())
96			} else {
97				Err("Unexpected macro def".into())
98			}
99		},
100		_ => Err("Could not find DB weights in the file".into()),
101	}
102}
103
104/// Handles the content of the `parameter_types!` macro.
105///
106/// Example:
107/// ```nocompile
108/// pub const RocksDbWeight: RuntimeDbWeight = RuntimeDbWeight {
109///     read: 25_000 * constants::WEIGHT_PER_NANOS,
110///     write: 100_000 * constants::WEIGHT_PER_NANOS,
111/// };
112/// ```
113fn parse_macro(tokens: proc_macro2::TokenStream) -> Result<Weights, String> {
114	let def: ItemConst = syn::parse2(tokens).map_err(|e| e.to_string())?;
115	let name = def.ident.to_string();
116
117	let db = match name.as_str() {
118		"RocksDbWeight" => Db::Rocks,
119		"ParityDbWeight" => Db::Parity,
120		_ => return Err(format!("Unexpected const name: {}", name)),
121	};
122	let type_name = type_to_string(&def.ty, None)?;
123	if type_name != "RuntimeDbWeight" {
124		return Err(format!("Unexpected const type: {}", type_name))
125	}
126	match def.expr.as_ref() {
127		Expr::Struct(s) => {
128			let weights = parse_runtime_db_weight(s)?;
129			Ok(Weights { db, weights })
130		},
131		_ => Err("Unexpected const value".into()),
132	}
133}
134
135fn parse_runtime_db_weight(expr: &ExprStruct) -> Result<RWs, String> {
136	let name = path_to_string(&expr.path, None);
137	if name != "RuntimeDbWeight" {
138		return Err(format!("Unexpected struct name: {}", name))
139	} else if expr.fields.len() != 2 {
140		return Err("Unexpected struct fields".into())
141	}
142	let reads = expr
143		.fields
144		.iter()
145		.find(|f| member_to_string(&f.member) == "read")
146		.ok_or("No read field found")?;
147	let writes = expr
148		.fields
149		.iter()
150		.find(|f| member_to_string(&f.member) == "write")
151		.ok_or("No write field found")?;
152
153	let read = parse_expression(&reads.expr)?;
154	let write = parse_expression(&writes.expr)?;
155
156	Ok(RWs { read, write })
157}
158
159fn parse_expression(expr: &Expr) -> Result<Term, String> {
160	match expr {
161		Expr::Binary(bin) => {
162			let left = parse_expression(&bin.left)?.into();
163			let right = parse_expression(&bin.right)?.into();
164
165			let term = match bin.op {
166				BinOp::Mul(_) => Term::Mul(left, right),
167				BinOp::Add(_) => Term::Add(left, right),
168				_ => return Err("Unexpected operator".into()),
169			};
170			Ok(term)
171		},
172		Expr::Lit(lit) => Ok(Term::Scalar(super::pallet::lit_to_value(&lit.lit))),
173		Expr::Path(p) => Ok(Term::Var(crate::term::VarValue(path_to_string(&p.path, Some("::"))))),
174		_ => Err("Unexpected expression storage expr".into()),
175	}
176}
177
178/// Expects a path to a type and returns the type name.
179fn type_to_string(p: &syn::Type, delimiter: Option<&str>) -> Result<String, String> {
180	if let Type::Path(p) = p {
181		Ok(path_to_string(&p.path, delimiter))
182	} else {
183		Err("Unexpected type".into())
184	}
185}
186
187fn member_to_string(m: &syn::Member) -> String {
188	match m {
189		syn::Member::Named(ident) => ident.to_string(),
190		_ => "".into(),
191	}
192}