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		let mac = match *expr {
186			Expr::Macro(syn::ExprMacro { ref mac, .. }) => mac,
187			_ => return Err(Error::new(expr.span(), "a macro expression is expected here")),
188		};
189
190		let lit: ExprLit = mac.parse_body().map_err(|e| {
191			Error::new(
192				e.span(),
193				format!("a single literal argument is expected, but parsing is failed: {}", e),
194			)
195		})?;
196
197		match lit.lit {
198			Lit::Str(ref lit) => Ok(lit.value()),
199			_ => Err(Error::new(lit.span(), "only string literals are supported here")),
200		}
201	}
202
203	fn build(self, span: Span) -> Result<RuntimeVersion> {
204		macro_rules! required {
205			($e:expr) => {
206				$e.ok_or_else(|| {
207					Error::new(span, format!("required field '{}' is missing", stringify!($e)))
208				})?
209			};
210		}
211
212		let Self {
213			spec_name,
214			impl_name,
215			authoring_version,
216			spec_version,
217			impl_version,
218			transaction_version,
219			system_version,
220		} = self;
221
222		Ok(RuntimeVersion {
223			spec_name: required!(spec_name),
224			impl_name: required!(impl_name),
225			authoring_version: required!(authoring_version),
226			spec_version: required!(spec_version),
227			impl_version: required!(impl_version),
228			transaction_version: required!(transaction_version),
229			system_version: required!(system_version),
230			apis: 0,
231		})
232	}
233}
234
235fn generate_emit_link_section_decl(contents: &[u8], section_name: &str) -> TokenStream {
236	let len = contents.len();
237	quote! {
238		const _: () = {
239			#[cfg(not(feature = "std"))]
240			#[link_section = #section_name]
241			static SECTION_CONTENTS: [u8; #len] = [#(#contents),*];
242		};
243	}
244}
245
246#[cfg(test)]
247mod tests {
248	use super::*;
249	use std::borrow::Cow;
250
251	#[test]
252	fn version_can_be_deserialized() {
253		let version_bytes = RuntimeVersion {
254			spec_name: "hello".to_string(),
255			impl_name: "world".to_string(),
256			authoring_version: 10,
257			spec_version: 265,
258			impl_version: 1,
259			apis: 0,
260			transaction_version: 2,
261			system_version: 1,
262		}
263		.encode();
264
265		assert_eq!(
266			sp_version::RuntimeVersion::decode_with_version_hint(&mut &version_bytes[..], Some(4))
267				.unwrap(),
268			sp_version::RuntimeVersion {
269				spec_name: "hello".into(),
270				impl_name: "world".into(),
271				authoring_version: 10,
272				spec_version: 265,
273				impl_version: 1,
274				apis: Cow::Owned(vec![]),
275				transaction_version: 2,
276				system_version: 1,
277			},
278		);
279	}
280}