precompile_utils_macro/
derive_codec.rs

1// This file is part of Frontier.
2
3// Copyright (c) Moonsong Labs.
4// Copyright (C) Parity Technologies (UK) Ltd.
5// SPDX-License-Identifier: Apache-2.0
6
7// Licensed under the Apache License, Version 2.0 (the "License");
8// you may not use this file except in compliance with the License.
9// You may obtain a copy of the License at
10//
11// 	http://www.apache.org/licenses/LICENSE-2.0
12//
13// Unless required by applicable law or agreed to in writing, software
14// distributed under the License is distributed on an "AS IS" BASIS,
15// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16// See the License for the specific language governing permissions and
17// limitations under the License.
18
19use proc_macro::TokenStream;
20use proc_macro2::Span;
21use quote::{quote, quote_spanned};
22use syn::{
23	parse_macro_input, punctuated::Punctuated, spanned::Spanned, DeriveInput, Ident, LitStr, Path,
24	PathSegment, PredicateType, TraitBound, TraitBoundModifier,
25};
26
27pub fn main(input: TokenStream) -> TokenStream {
28	let DeriveInput {
29		ident,
30		mut generics,
31		data,
32		..
33	} = parse_macro_input!(input as DeriveInput);
34
35	let syn::Data::Struct(syn::DataStruct {
36		fields: syn::Fields::Named(fields),
37		..
38	}) = data
39	else {
40		return quote_spanned! { ident.span() =>
41			compile_error!("Codec can only be derived for structs with named fields");
42		}
43		.into();
44	};
45	let fields = fields.named;
46
47	if fields.is_empty() {
48		return quote_spanned! { ident.span() =>
49			compile_error!("Codec can only be derived for structs with at least one field");
50		}
51		.into();
52	}
53
54	if let Some(unamed_field) = fields.iter().find(|f| f.ident.is_none()) {
55		return quote_spanned! { unamed_field.ty.span() =>
56			compile_error!("Codec can only be derived for structs with named fields");
57		}
58		.into();
59	}
60
61	let fields_ty: Vec<_> = fields.iter().map(|f| &f.ty).collect();
62	let fields_ident: Vec<_> = fields
63		.iter()
64		.map(|f| f.ident.as_ref().expect("None case checked above"))
65		.collect();
66	let fields_name_lit: Vec<_> = fields_ident
67		.iter()
68		.map(|i| LitStr::new(&i.to_string(), i.span()))
69		.collect();
70
71	let evm_data_trait_path = {
72		let mut segments = Punctuated::<PathSegment, _>::new();
73		segments.push(Ident::new("precompile_utils", Span::call_site()).into());
74		segments.push(Ident::new("solidity", Span::call_site()).into());
75		segments.push(Ident::new("Codec", Span::call_site()).into());
76		Path {
77			leading_colon: Some(Default::default()),
78			segments,
79		}
80	};
81	let where_clause = generics.make_where_clause();
82
83	for ty in &fields_ty {
84		let mut bounds = Punctuated::new();
85		bounds.push(
86			TraitBound {
87				paren_token: None,
88				modifier: TraitBoundModifier::None,
89				lifetimes: None,
90				path: evm_data_trait_path.clone(),
91			}
92			.into(),
93		);
94
95		where_clause.predicates.push(
96			PredicateType {
97				lifetimes: None,
98				bounded_ty: (*ty).clone(),
99				colon_token: Default::default(),
100				bounds,
101			}
102			.into(),
103		);
104	}
105
106	let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
107	quote! {
108		impl #impl_generics ::precompile_utils::solidity::codec::Codec for #ident #ty_generics
109		#where_clause {
110			fn read(
111				reader: &mut ::precompile_utils::solidity::codec::Reader
112			) -> ::precompile_utils::solidity::revert::MayRevert<Self> {
113				use ::precompile_utils::solidity::revert::BacktraceExt as _;
114				let (#(#fields_ident,)*): (#(#fields_ty,)*) = reader
115					.read()
116					.map_in_tuple_to_field(&[#(#fields_name_lit),*])?;
117				Ok(Self {
118					#(#fields_ident,)*
119				})
120			}
121
122			fn write(writer: &mut ::precompile_utils::solidity::codec::Writer, value: Self) {
123				::precompile_utils::solidity::codec::Codec::write(writer, (#(value.#fields_ident,)*));
124			}
125
126			fn has_static_size() -> bool {
127				<(#(#fields_ty,)*)>::has_static_size()
128			}
129
130			fn signature() -> String {
131				<(#(#fields_ty,)*)>::signature()
132			}
133		}
134	}
135	.into()
136}