referrerpolicy=no-referrer-when-downgrade

sp_version_proc_macro/
decl_runtime_version.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18use codec::Encode;
19use proc_macro2::{Span, TokenStream};
20use proc_macro_warning::Warning;
21use quote::quote;
22use syn::{
23	parse::{Error, Result},
24	parse_macro_input,
25	spanned::Spanned as _,
26	Expr, ExprLit, FieldValue, ItemConst, Lit,
27};
28
29/// This macro accepts a `const` item that has a struct initializer expression of
30/// `RuntimeVersion`-like type. The macro will pass through this declaration and append an item
31/// declaration that will lead to emitting a wasm custom section with the contents of
32/// `RuntimeVersion`.
33pub fn decl_runtime_version_impl(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
34	let item = parse_macro_input!(input as ItemConst);
35	decl_runtime_version_impl_inner(item)
36		.unwrap_or_else(|e| e.to_compile_error())
37		.into()
38}
39
40fn decl_runtime_version_impl_inner(item: ItemConst) -> Result<TokenStream> {
41	let (parsed_runtime_version, warnings) = ParseRuntimeVersion::parse_expr(&item.expr)?;
42	let runtime_version = parsed_runtime_version.build(item.expr.span())?;
43	let link_section =
44		generate_emit_link_section_decl(&runtime_version.encode(), "runtime_version");
45
46	Ok(quote! {
47		#item
48		#link_section
49		const _:() = {
50			#(
51				#warnings
52			)*
53		};
54	})
55}
56
57/// This is a duplicate of `sp_version::RuntimeVersion`. We cannot unfortunately use the original
58/// declaration, because if we directly depend on `sp_version` from this proc-macro cargo will
59/// enable `std` feature even for `no_std` wasm runtime builds.
60///
61/// One difference from the original definition is the `apis` field. Since we don't actually parse
62/// `apis` from this macro it will always be emitted as empty. An empty vector can be encoded as
63/// a zero-byte, thus `u8` is sufficient here.
64#[derive(Encode)]
65struct RuntimeVersion {
66	spec_name: String,
67	impl_name: String,
68	authoring_version: u32,
69	spec_version: u32,
70	impl_version: u32,
71	apis: u8,
72	transaction_version: u32,
73	system_version: u8,
74}
75
76#[derive(Default, Debug)]
77struct ParseRuntimeVersion {
78	spec_name: Option<String>,
79	impl_name: Option<String>,
80	authoring_version: Option<u32>,
81	spec_version: Option<u32>,
82	impl_version: Option<u32>,
83	transaction_version: Option<u32>,
84	system_version: Option<u8>,
85}
86
87impl ParseRuntimeVersion {
88	fn parse_expr(init_expr: &Expr) -> Result<(ParseRuntimeVersion, Vec<Warning>)> {
89		let init_expr = match init_expr {
90			Expr::Struct(ref e) => e,
91			_ =>
92				return Err(Error::new(init_expr.span(), "expected a struct initializer expression")),
93		};
94
95		let mut parsed = ParseRuntimeVersion::default();
96		let mut warnings = vec![];
97		for field_value in init_expr.fields.iter() {
98			warnings.append(&mut parsed.parse_field_value(field_value)?)
99		}
100		Ok((parsed, warnings))
101	}
102
103	fn parse_field_value(&mut self, field_value: &FieldValue) -> Result<Vec<Warning>> {
104		let field_name = match field_value.member {
105			syn::Member::Named(ref ident) => ident,
106			syn::Member::Unnamed(_) =>
107				return Err(Error::new(field_value.span(), "only named members must be used")),
108		};
109
110		fn parse_once<T>(
111			value: &mut Option<T>,
112			field: &FieldValue,
113			parser: impl FnOnce(&Expr) -> Result<T>,
114		) -> Result<()> {
115			if value.is_some() {
116				Err(Error::new(field.span(), "field is already initialized before"))
117			} else {
118				*value = Some(parser(&field.expr)?);
119				Ok(())
120			}
121		}
122
123		let mut warnings = vec![];
124		if field_name == "spec_name" {
125			parse_once(&mut self.spec_name, field_value, Self::parse_str_literal)?;
126		} else if field_name == "impl_name" {
127			parse_once(&mut self.impl_name, field_value, Self::parse_str_literal)?;
128		} else if field_name == "authoring_version" {
129			parse_once(&mut self.authoring_version, field_value, Self::parse_num_literal)?;
130		} else if field_name == "spec_version" {
131			parse_once(&mut self.spec_version, field_value, Self::parse_num_literal)?;
132		} else if field_name == "impl_version" {
133			parse_once(&mut self.impl_version, field_value, Self::parse_num_literal)?;
134		} else if field_name == "transaction_version" {
135			parse_once(&mut self.transaction_version, field_value, Self::parse_num_literal)?;
136		} else if field_name == "state_version" {
137			let warning = Warning::new_deprecated("RuntimeVersion")
138				.old("state_version")
139				.new("system_version)")
140				.help_link("https://github.com/paritytech/polkadot-sdk/pull/4257")
141				.span(field_name.span())
142				.build_or_panic();
143			warnings.push(warning);
144			parse_once(&mut self.system_version, field_value, Self::parse_num_literal_u8)?;
145		} else if field_name == "system_version" {
146			parse_once(&mut self.system_version, field_value, Self::parse_num_literal_u8)?;
147		} else if field_name == "apis" {
148			// Intentionally ignored
149			//
150			// The definition will pass through for the declaration, however, it won't get into
151			// the "runtime_version" custom section. `impl_runtime_apis` is responsible for
152			// generating a custom section with the supported runtime apis descriptor.
153		} else {
154			return Err(Error::new(field_name.span(), "unknown field"))
155		}
156
157		Ok(warnings)
158	}
159
160	fn parse_num_literal(expr: &Expr) -> Result<u32> {
161		let lit = match *expr {
162			Expr::Lit(ExprLit { lit: Lit::Int(ref lit), .. }) => lit,
163			_ =>
164				return Err(Error::new(
165					expr.span(),
166					"only numeric literals (e.g. `10`) are supported here",
167				)),
168		};
169		lit.base10_parse::<u32>()
170	}
171
172	fn parse_num_literal_u8(expr: &Expr) -> Result<u8> {
173		let lit = match *expr {
174			Expr::Lit(ExprLit { lit: Lit::Int(ref lit), .. }) => lit,
175			_ =>
176				return Err(Error::new(
177					expr.span(),
178					"only numeric literals (e.g. `10`) are supported here",
179				)),
180		};
181		lit.base10_parse::<u8>()
182	}
183
184	fn parse_str_literal(expr: &Expr) -> Result<String> {
185		match expr {
186			// TODO: Remove this branch when `sp_runtime::create_runtime_str` is removed
187			Expr::Macro(syn::ExprMacro { mac, .. }) => {
188				let lit: ExprLit = mac.parse_body().map_err(|e| {
189					Error::new(
190						e.span(),
191						format!(
192							"a single literal argument is expected, but parsing is failed: {}",
193							e
194						),
195					)
196				})?;
197
198				match &lit.lit {
199					Lit::Str(lit) => Ok(lit.value()),
200					_ => Err(Error::new(lit.span(), "only string literals are supported here")),
201				}
202			},
203			Expr::Call(call) => {
204				if call.args.len() != 1 {
205					return Err(Error::new(
206						expr.span(),
207						"a single literal argument is expected, but parsing is failed",
208					));
209				}
210				let Expr::Lit(lit) = call.args.first().expect("Length checked above; qed") else {
211					return Err(Error::new(
212						expr.span(),
213						"a single literal argument is expected, but parsing is failed",
214					));
215				};
216
217				match &lit.lit {
218					Lit::Str(lit) => Ok(lit.value()),
219					_ => Err(Error::new(lit.span(), "only string literals are supported here")),
220				}
221			},
222			_ => Err(Error::new(
223				expr.span(),
224				format!("a function call is expected here, instead of: {expr:?}"),
225			)),
226		}
227	}
228
229	fn build(self, span: Span) -> Result<RuntimeVersion> {
230		macro_rules! required {
231			($e:expr) => {
232				$e.ok_or_else(|| {
233					Error::new(span, format!("required field '{}' is missing", stringify!($e)))
234				})?
235			};
236		}
237
238		let Self {
239			spec_name,
240			impl_name,
241			authoring_version,
242			spec_version,
243			impl_version,
244			transaction_version,
245			system_version,
246		} = self;
247
248		Ok(RuntimeVersion {
249			spec_name: required!(spec_name),
250			impl_name: required!(impl_name),
251			authoring_version: required!(authoring_version),
252			spec_version: required!(spec_version),
253			impl_version: required!(impl_version),
254			transaction_version: required!(transaction_version),
255			system_version: required!(system_version),
256			apis: 0,
257		})
258	}
259}
260
261fn generate_emit_link_section_decl(contents: &[u8], section_name: &str) -> TokenStream {
262	let len = contents.len();
263	quote! {
264		const _: () = {
265			#[cfg(not(feature = "std"))]
266			#[link_section = #section_name]
267			static SECTION_CONTENTS: [u8; #len] = [#(#contents),*];
268		};
269	}
270}
271
272#[cfg(test)]
273mod tests {
274	use super::*;
275	use std::borrow::Cow;
276
277	#[test]
278	fn version_can_be_deserialized() {
279		let version_bytes = RuntimeVersion {
280			spec_name: "hello".to_string(),
281			impl_name: "world".to_string(),
282			authoring_version: 10,
283			spec_version: 265,
284			impl_version: 1,
285			apis: 0,
286			transaction_version: 2,
287			system_version: 1,
288		}
289		.encode();
290
291		assert_eq!(
292			sp_version::RuntimeVersion::decode_with_version_hint(&mut &version_bytes[..], Some(4))
293				.unwrap(),
294			sp_version::RuntimeVersion {
295				spec_name: "hello".into(),
296				impl_name: "world".into(),
297				authoring_version: 10,
298				spec_version: 265,
299				impl_version: 1,
300				apis: Cow::Owned(vec![]),
301				transaction_version: 2,
302				system_version: 1,
303			},
304		);
305	}
306}