1use sp_io::hashing::keccak_256;
22use std::{
23 collections::HashMap,
24 fs::File,
25 io::{BufRead, BufReader, Read},
26};
27
28pub fn check_precompile_implements_solidity_interfaces<F>(
29 files: &[&'static str],
30 supports_selector: F,
31) where
32 F: Fn(u32) -> bool,
33{
34 for file in files {
35 for solidity_fn in get_selectors(file) {
36 assert_eq!(
37 solidity_fn.compute_selector_hex(),
38 solidity_fn.docs_selector,
39 "documented selector for '{}' did not match in file '{}'",
40 solidity_fn.signature(),
41 file,
42 );
43
44 let selector = solidity_fn.compute_selector();
45 if !supports_selector(selector) {
46 panic!(
47 "precompile don't support selector {selector:x} for function '{}' listed in file\
48 {file}",
49 solidity_fn.signature(),
50 )
51 }
52 }
53 }
54}
55
56#[derive(Clone, Default, Debug)]
58pub struct SolidityStruct {
59 pub name: String,
61 pub params: Vec<String>,
63 pub is_enum: bool,
65}
66
67impl SolidityStruct {
68 pub fn signature(&self) -> String {
70 if self.is_enum {
71 "uint8".to_string()
72 } else {
73 format!("({})", self.params.join(","))
74 }
75 }
76}
77
78#[derive(Clone, Default)]
80pub struct SolidityFunction {
81 pub name: String,
83 pub args: Vec<String>,
85 pub docs_selector: String,
87}
88
89impl SolidityFunction {
90 pub fn signature(&self) -> String {
92 format!("{}({})", self.name, self.args.join(","))
93 }
94
95 pub fn compute_selector(&self) -> u32 {
97 compute_selector(&self.signature())
98 }
99
100 pub fn compute_selector_hex(&self) -> String {
102 format!("{:0>8x}", self.compute_selector())
103 }
104}
105
106pub fn compute_selector(v: &str) -> u32 {
108 let output = keccak_256(v.as_bytes());
109 let mut buf = [0u8; 4];
110 buf.clone_from_slice(&output[..4]);
111 u32::from_be_bytes(buf)
112}
113
114pub fn get_selectors(filename: &str) -> Vec<SolidityFunction> {
116 let file =
117 File::open(filename).unwrap_or_else(|e| panic!("failed opening file '{filename}': {e}"));
118 get_selectors_from_reader(file)
119}
120
121fn try_lookup_custom_type(word: &str, custom_types: &HashMap<String, SolidityStruct>) -> String {
123 match word.strip_suffix("[]") {
124 Some(word) => {
125 if let Some(t) = custom_types.get(word) {
126 return format!("{}[]", t.signature());
127 }
128 }
129 None => {
130 if let Some(t) = custom_types.get(word) {
131 return t.signature();
132 }
133 }
134 };
135
136 word.to_string()
137}
138
139fn get_selectors_from_reader<R: Read>(reader: R) -> Vec<SolidityFunction> {
140 #[derive(Clone, Copy)]
141 enum Stage {
142 Start,
143 Enum,
144 Struct,
145 StructParams,
146 FnName,
147 Args,
148 }
149 #[derive(Clone, Copy)]
150 enum Pair {
151 First,
152 Second,
153 }
154 impl Pair {
155 fn next(&mut self) {
156 *self = match self {
157 Pair::First => Pair::Second,
158 Pair::Second => Pair::First,
159 }
160 }
161 }
162
163 let reader = BufReader::new(reader);
164 let mut functions = vec![];
165 let mut custom_types = HashMap::new();
166 let mut solidity_struct = SolidityStruct::default();
167
168 let mut stage = Stage::Start;
169 let mut pair = Pair::First;
170 let mut solidity_fn = SolidityFunction::default();
171 for line in reader.lines() {
172 let line = line.expect("failed unwrapping line").trim().to_string();
173 if line.starts_with("/// @custom:selector ") && matches!(stage, Stage::Start) {
175 solidity_fn.docs_selector = line.replace("/// @custom:selector ", "").to_string();
176 }
177
178 if line.starts_with("//") {
180 continue;
181 }
182
183 for word in line.split(&[';', ',', '(', ')', ' ']) {
184 if word.trim().is_empty() {
186 continue;
187 }
188 match (stage, pair, word) {
189 (Stage::Start, Pair::First, "enum") => {
191 stage = Stage::Enum;
192 pair.next();
193 }
194 (Stage::Enum, Pair::Second, _) => {
195 custom_types.insert(
196 word.to_string(),
197 SolidityStruct {
198 name: word.to_string(),
199 is_enum: true,
200 params: vec![],
201 },
202 );
203 stage = Stage::Start;
204 pair = Pair::First;
205 }
206
207 (Stage::Start, Pair::First, "struct") => {
209 stage = Stage::Struct;
210 pair.next();
211 }
212 (Stage::Struct, Pair::Second, _) => {
213 solidity_struct.name = word.to_string();
214 stage = Stage::StructParams;
215 pair.next();
216 }
217 (Stage::StructParams, Pair::First, "{") => (),
218 (Stage::StructParams, Pair::First, "}") => {
219 custom_types.insert(solidity_struct.name.clone(), solidity_struct);
220 stage = Stage::Start;
221 solidity_struct = SolidityStruct::default();
222 }
223 (Stage::StructParams, Pair::First, _) => {
224 let param = try_lookup_custom_type(word, &custom_types);
225 solidity_struct.params.push(param);
226 pair.next();
227 }
228 (Stage::StructParams, Pair::Second, _) => {
229 pair.next();
230 }
231
232 (Stage::Start, Pair::First, "function") => {
234 stage = Stage::FnName;
235 pair.next();
236 }
237 (Stage::FnName, Pair::Second, _) => {
238 solidity_fn.name = word.to_string();
239 stage = Stage::Args;
240 pair.next();
241 }
242 (Stage::Args, Pair::First, "external") => {
243 functions.push(solidity_fn);
244 stage = Stage::Start;
245 pair = Pair::First;
246 solidity_fn = SolidityFunction::default()
247 }
248 (Stage::Args, Pair::First, _) => {
249 let mut arg = word.to_string();
250 arg = try_lookup_custom_type(&arg, &custom_types);
251
252 solidity_fn.args.push(arg);
253 pair.next();
254 }
255 (Stage::Args, Pair::Second, "memory" | "calldata" | "storage") => (),
256 (Stage::Args, Pair::Second, _) => pair.next(),
257 _ => {
258 stage = Stage::Start;
259 pair = Pair::First;
260 solidity_fn = SolidityFunction::default()
261 }
262 }
263 }
264 }
265
266 functions
267}
268
269#[cfg(test)]
270mod tests {
271 use super::*;
272
273 #[test]
274 fn test_selectors_are_parsed() {
275 let actual = get_selectors("tests/solidity_test.sol")
276 .into_iter()
277 .map(|sol_fn| {
278 (
279 sol_fn.compute_selector_hex(),
280 sol_fn.docs_selector.clone(),
281 sol_fn.signature(),
282 )
283 })
284 .collect::<Vec<_>>();
285 let expected = vec![
286 (
287 String::from("f7af8d91"),
288 String::from(""),
289 String::from("fnNoArgs()"),
290 ),
291 (
292 String::from("d43a9a43"),
293 String::from("c4921133"),
294 String::from("fnOneArg(address)"),
295 ),
296 (
297 String::from("40d6a43d"),
298 String::from("67ea837e"),
299 String::from("fnTwoArgs(address,uint256)"),
300 ),
301 (
302 String::from("cee150c8"),
303 String::from("d6b423d9"),
304 String::from("fnSameArgs(uint64,uint64)"),
305 ),
306 (
307 String::from("c6024207"),
308 String::from("b9904a86"),
309 String::from("fnOneArgSameLine(uint64)"),
310 ),
311 (
312 String::from("fcbc04c3"),
313 String::from("28f0c44e"),
314 String::from("fnTwoArgsSameLine(uint64,bytes32)"),
315 ),
316 (
317 String::from("c590304c"),
318 String::from("06f0c1ce"),
319 String::from("fnTwoArgsSameLineExternalSplit(uint64,bytes32)"),
320 ),
321 (
322 String::from("a19a07e1"),
323 String::from("18001a4e"),
324 String::from("fnMemoryArrayArgs(address[],uint256[],bytes[])"),
325 ),
326 (
327 String::from("ec26cf1c"),
328 String::from("1ea61a4e"),
329 String::from("fnCalldataArgs(string,bytes[])"),
330 ),
331 (
332 String::from("f29f96de"),
333 String::from("d8af1a4e"),
334 String::from("fnCustomArgs((uint8,bytes[]),bytes[],uint64)"),
335 ),
336 (
337 String::from("d751d651"),
338 String::from("e8af1642"),
339 String::from("fnEnumArgs(uint8,uint64)"),
340 ),
341 (
342 String::from("b2c9f1a3"),
343 String::from("550c1a4e"),
344 String::from(
345 "fnCustomArgsMultiple((uint8,bytes[]),(address[],uint256[],bytes[]),bytes[],\
346 uint64)",
347 ),
348 ),
349 (
350 String::from("d5363eee"),
351 String::from("77af1a40"),
352 String::from("fnCustomArrayArgs((uint8,bytes[])[],bytes[])"),
353 ),
354 (
355 String::from("b82da727"),
356 String::from("80af0a40"),
357 String::from(
358 "fnCustomComposedArg(((uint8,bytes[]),\
359 (address[],uint256[],bytes[])[]),uint64)",
360 ),
361 ),
362 (
363 String::from("586a2193"),
364 String::from("97baa040"),
365 String::from(
366 "fnCustomComposedArrayArg(((uint8,bytes[]),\
367 (address[],uint256[],bytes[])[])[],uint64)",
368 ),
369 ),
370 ];
371
372 assert_eq!(expected, actual);
373 }
374}