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
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This file is part of Frontier.
//
// Copyright (c) 2019-2022 Moonsong Labs.
// Copyright (c) 2023 Parity Technologies (UK) Ltd.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

#![doc = include_str!("../../docs/precompile_macro.md")]

use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::{format_ident, quote, quote_spanned, ToTokens};
use sp_crypto_hashing::keccak_256;
use std::collections::BTreeMap;
use syn::{parse_macro_input, spanned::Spanned};

pub mod attr;
pub mod expand;
pub mod parse;

pub fn main(_attr: TokenStream, item: TokenStream) -> TokenStream {
	// Macro must be used on `impl` block.
	let mut impl_item = parse_macro_input!(item as syn::ItemImpl);

	// We inspect the block to collect all the data we need for the
	// expansion, and make various checks.
	let precompile = match Precompile::try_from(&mut impl_item) {
		Ok(p) => p,
		Err(e) => return e.into_compile_error().into(),
	};

	// We generate additional code based on the collected data.
	let new_items = precompile.expand();
	let output = quote!(
		#impl_item
		#new_items
	);

	output.into()
}

struct Precompile {
	/// Impl struct type.
	impl_type: syn::Type,

	/// Impl struct ident.
	impl_ident: syn::Ident,

	/// New parsing enum ident.
	enum_ident: syn::Ident,

	/// Generic part that needs to also be used by the input enum.
	generics: syn::Generics,

	/// Which selector corresponds to which variant of the input enum.
	selector_to_variant: BTreeMap<u32, syn::Ident>,

	/// Optional fallback function if no selector matches.
	fallback_to_variant: Option<syn::Ident>,

	/// Describes the content of each variant based on the precompile methods.
	variants_content: BTreeMap<syn::Ident, Variant>,

	/// Since being a precompile set implies lots of changes, we must know it early
	/// in the form of an attribute on the impl block itself.
	tagged_as_precompile_set: bool,

	/// Ident of the function returning the PrecompileSet discriminant.
	precompile_set_discriminant_fn: Option<syn::Ident>,

	/// Type of the PrecompileSet discriminant.
	precompile_set_discriminant_type: Option<syn::Type>,

	/// When generating the selector test the data types might depend on type parameters.
	/// The test thus need to be written using concrete types.
	test_concrete_types: Option<Vec<syn::Type>>,

	/// Ident of a function that performs a check before the call is dispatched to the proper
	/// function.
	pre_check: Option<syn::Ident>,
}

#[derive(Debug, PartialEq, Eq)]
enum Modifier {
	NonPayable,
	Payable,
	View,
}

#[derive(Debug)]
struct Variant {
	/// Description of the arguments of this method, which will also
	/// be members of a struct variant.
	arguments: Vec<Argument>,

	/// String extracted from the selector attribute.
	/// A unit test will be generated to check that this selector matches
	/// the Rust arguments.
	///
	/// > solidity::Codec trait allows to generate this string at runtime only. Thus
	/// > it is required to write it manually in the selector attribute, and
	/// > a unit test is generated to check it matches.
	solidity_arguments_type: String,

	/// Modifier of the function. They are all exclusive and defaults to
	/// `NonPayable`.
	modifier: Modifier,

	/// Selectors of this function to be able to encode back the data.
	/// Empty if it only the fallback function.
	selectors: Vec<u32>,

	/// Output of the variant fn (for better error messages).
	fn_output: syn::Type,
}

#[derive(Debug)]
struct Argument {
	/// Identifier of the argument, which will be used in the struct variant.
	ident: syn::Ident,

	/// Type of the argument, which will be used in the struct variant and
	/// to parse the input.
	ty: syn::Type,
}