substreams_ethereum_abigen/
lib.rs1#![recursion_limit = "256"]
10
11extern crate proc_macro;
12
13mod assertions;
14pub mod build;
15mod contract;
17mod event;
18mod function;
19
20use anyhow::format_err;
21use ethabi::{Contract, Error, Param, ParamType};
23use heck::ToSnakeCase;
24use proc_macro2::Span;
25use quote::{quote, ToTokens};
27use std::{
28 borrow::Cow,
29 env, fs,
30 path::{Path, PathBuf},
31};
32use syn::Index;
33
34pub fn generate_abi_code<S: AsRef<str>>(
35 path: S,
36) -> Result<proc_macro2::TokenStream, anyhow::Error> {
37 let normalized_path = normalize_path(path.as_ref())?;
38 let source_file = fs::File::open(&normalized_path).map_err(|_| {
39 Error::Other(Cow::Owned(format!(
40 "Cannot load contract abi from `{}`",
41 normalized_path.display()
42 )))
43 })?;
44 let contract = Contract::load(source_file)?;
45 let c = contract::Contract::from(&contract);
46 Ok(c.generate())
47}
48
49pub fn generate_abi_code_from_bytes(
50 bytes: &[u8],
51) -> Result<proc_macro2::TokenStream, anyhow::Error> {
52 let contract = Contract::load(bytes)?;
53 let c = contract::Contract::from(&contract);
54 Ok(c.generate())
55}
56
57fn normalize_path<S: AsRef<Path>>(relative_path: S) -> Result<PathBuf, anyhow::Error> {
58 let cargo_toml_directory =
60 env::var("CARGO_MANIFEST_DIR").map_err(|_| format_err!("Cannot find manifest file"))?;
61 let mut path: PathBuf = cargo_toml_directory.into();
62 path.push(relative_path);
63 Ok(path)
64}
65
66fn to_syntax_string(param_type: ðabi::ParamType) -> proc_macro2::TokenStream {
67 match *param_type {
68 ParamType::Address => quote! { ethabi::ParamType::Address },
69 ParamType::Bytes => quote! { ethabi::ParamType::Bytes },
70 ParamType::Int(x) => quote! { ethabi::ParamType::Int(#x) },
71 ParamType::Uint(x) => quote! { ethabi::ParamType::Uint(#x) },
72 ParamType::Bool => quote! { ethabi::ParamType::Bool },
73 ParamType::String => quote! { ethabi::ParamType::String },
74 ParamType::Array(ref param_type) => {
75 let param_type_quote = to_syntax_string(param_type);
76 quote! { ethabi::ParamType::Array(Box::new(#param_type_quote)) }
77 }
78 ParamType::FixedBytes(x) => quote! { ethabi::ParamType::FixedBytes(#x) },
79 ParamType::FixedArray(ref param_type, ref x) => {
80 let param_type_quote = to_syntax_string(param_type);
81 quote! { ethabi::ParamType::FixedArray(Box::new(#param_type_quote), #x) }
82 }
83 ParamType::Tuple(ref v) => {
84 let param_type_quotes = v.iter().map(|x| to_syntax_string(x));
85 quote! { ethabi::ParamType::Tuple(vec![#(#param_type_quotes),*]) }
86 }
87 }
88}
89
90fn rust_type_indexed(input: &ParamType) -> proc_macro2::TokenStream {
113 match input.is_dynamic() {
114 true => {
115 let t = rust_type(input);
116 return quote! { substreams_ethereum::IndexedDynamicValue<#t> };
117 }
118 false => rust_type(input),
119 }
120}
121
122fn rust_type(input: &ParamType) -> proc_macro2::TokenStream {
123 match *input {
124 ParamType::Address => quote! { Vec<u8> },
125 ParamType::Bytes => quote! { Vec<u8> },
126 ParamType::FixedBytes(size) => quote! { [u8; #size] },
127 ParamType::Int(_) => quote! { substreams::scalar::BigInt },
128 ParamType::Uint(_) => quote! { substreams::scalar::BigInt },
129 ParamType::Bool => quote! { bool },
130 ParamType::String => quote! { String },
131 ParamType::Array(ref kind) => {
132 let t = rust_type(&*kind);
133 quote! { Vec<#t> }
134 }
135 ParamType::FixedArray(ref kind, size) => {
136 let t = rust_type(&*kind);
137 quote! { [#t; #size] }
138 }
139 ParamType::Tuple(ref types) => {
140 let tuple_elements = types.iter().map(rust_type);
141 quote! { (#(#tuple_elements,)*) }
142 }
143 }
144}
145
146fn fixed_data_size(input: &ParamType) -> Option<usize> {
147 match input {
148 ParamType::Address
149 | ParamType::Int(_)
150 | ParamType::Uint(_)
151 | ParamType::Bool
152 | ParamType::FixedBytes(_) => Some(32),
153 ParamType::Bytes | ParamType::String | ParamType::Array(_) => None,
154 ParamType::FixedArray(ref sub_type, count) => match sub_type.is_dynamic() {
155 true => None,
156 false => Some(
157 count * fixed_data_size(sub_type).expect("not dynamic, will always be Some(_)"),
158 ),
159 },
160 ParamType::Tuple(ref types) => {
161 if types.iter().any(ParamType::is_dynamic) {
162 return None;
163 }
164 Some(types.iter().map(fixed_data_size).map(Option::unwrap).sum())
165 }
166 }
167}
168
169fn min_data_size(input: &ParamType) -> usize {
170 match input {
171 ParamType::Address
172 | ParamType::Int(_)
173 | ParamType::Uint(_)
174 | ParamType::Bool
175 | ParamType::FixedBytes(_) => {
176 fixed_data_size(input).expect("not dynamic, will always be Some(_)")
177 }
178 ParamType::FixedArray(ref sub_type, count) => match sub_type.is_dynamic() {
184 true => 32 + count * min_data_size(sub_type),
185 false => fixed_data_size(input).expect("not dynamic, will always be Some(_)"),
186 },
187 ParamType::Bytes | ParamType::String | ParamType::Array(_) => 32 + 32,
191 ParamType::Tuple(ref types) => types.iter().map(min_data_size).sum(),
192 }
193}
194
195fn is_long_tuple(input: &ParamType) -> bool {
199 match input {
200 ParamType::Address
201 | ParamType::Int(_)
202 | ParamType::Uint(_)
203 | ParamType::Bool
204 | ParamType::FixedBytes(_)
205 | ParamType::Bytes
206 | ParamType::String => false,
207 ParamType::Array(sub_type) => is_long_tuple(sub_type),
208 ParamType::FixedArray(ref sub_type, _) => is_long_tuple(sub_type),
209 ParamType::Tuple(ref types) => {
210 if types.len() > 12 {
211 return true;
212 }
213
214 types.iter().any(is_long_tuple)
215 }
216 }
217}
218
219fn to_token(name: &proc_macro2::TokenStream, kind: &ParamType) -> proc_macro2::TokenStream {
276 match *kind {
277 ParamType::Address => {
278 quote! { ethabi::Token::Address(ethabi::Address::from_slice(&#name)) }
279 }
280 ParamType::Bytes => quote! { ethabi::Token::Bytes(#name.clone()) },
281 ParamType::FixedBytes(_) => quote! { ethabi::Token::FixedBytes(#name.as_ref().to_vec()) },
282 ParamType::Int(_) => {
283 quote! {
286 {
287 let non_full_signed_bytes = #name.to_signed_bytes_be();
288 let full_signed_bytes_init = if non_full_signed_bytes[0] & 0x80 == 0x80 { 0xff } else { 0x00 };
289 let mut full_signed_bytes = [full_signed_bytes_init as u8; 32];
290 non_full_signed_bytes.into_iter().rev().enumerate().for_each(|(i, byte)| full_signed_bytes[31 - i] = byte);
291
292 ethabi::Token::Int(ethabi::Int::from_big_endian(full_signed_bytes.as_ref()))
293 }
294 }
295 }
296 ParamType::Uint(_) => {
297 quote! {
298 ethabi::Token::Uint(
299 ethabi::Uint::from_big_endian(
300 match #name.clone().to_bytes_be() {
301 (num_bigint::Sign::Plus, bytes) => bytes,
302 (num_bigint::Sign::NoSign, bytes) => bytes,
303 (num_bigint::Sign::Minus, _) => {
304 panic!("negative numbers are not supported")
305 },
306 }.as_slice(),
307 ),
308 )
309 }
310 }
311 ParamType::Bool => quote! { ethabi::Token::Bool(#name.clone()) },
312 ParamType::String => quote! { ethabi::Token::String(#name.clone()) },
313 ParamType::Array(ref kind) => {
314 let inner_name = quote! { inner };
315 let inner_loop = to_token(&inner_name, kind);
316 quote! {
317 {
319 let v = #name.iter().map(|#inner_name| #inner_loop).collect();
320 ethabi::Token::Array(v)
321 }
322 }
323 }
324 ParamType::FixedArray(ref kind, _) => {
325 let inner_name = quote! { inner };
326 let inner_loop = to_token(&inner_name, kind);
327 quote! {
328 {
330 let v = #name.iter().map(|#inner_name| #inner_loop).collect();
331 ethabi::Token::FixedArray(v)
332 }
333 }
334 }
335 ParamType::Tuple(ref types) => {
336 let inner_names = (0..types.len())
337 .map(|i| {
338 let i = Index::from(i);
339 quote! { #name.#i }
340 })
341 .collect::<Vec<_>>();
342
343 let inner_tokens = types
344 .iter()
345 .zip(&inner_names)
346 .map(|(kind, inner_name)| to_token(&inner_name.to_token_stream(), kind))
347 .collect::<Vec<_>>();
348
349 quote! {
350 ethabi::Token::Tuple(vec![
351 #(#inner_tokens),*
352 ])
353 }
354 }
355 }
356}
357
358fn from_token(kind: &ParamType, token: &proc_macro2::TokenStream) -> proc_macro2::TokenStream {
359 match *kind {
360 ParamType::Address => {
361 quote! { #token.into_address().expect(INTERNAL_ERR).as_bytes().to_vec() }
362 }
363 ParamType::Bytes => {
364 quote! { #token.into_bytes().expect(INTERNAL_ERR) }
365 }
366 ParamType::FixedBytes(size) => {
367 let size: syn::Index = size.into();
368 quote! {
369 {
370 let mut result = [0u8; #size];
371 let v = #token.into_fixed_bytes().expect(INTERNAL_ERR);
372 result.copy_from_slice(&v);
373 result
374 }
375 }
376 }
377 ParamType::Int(_) => quote! {
378 {
379 let mut v = [0 as u8; 32];
380 #token.into_int().expect(INTERNAL_ERR).to_big_endian(v.as_mut_slice());
381 substreams::scalar::BigInt::from_signed_bytes_be(&v)
382 }
383 },
384 ParamType::Uint(_) => quote! {
385 {
386 let mut v = [0 as u8; 32];
387 #token.into_uint().expect(INTERNAL_ERR).to_big_endian(v.as_mut_slice());
388 substreams::scalar::BigInt::from_unsigned_bytes_be(&v)
389 }
390 },
391 ParamType::Bool => quote! { #token.into_bool().expect(INTERNAL_ERR) },
392 ParamType::String => quote! { #token.into_string().expect(INTERNAL_ERR) },
393 ParamType::Array(ref kind) => {
394 let inner = quote! { inner };
395 let inner_loop = from_token(kind, &inner);
396 quote! {
397 #token.into_array().expect(INTERNAL_ERR).into_iter()
398 .map(|#inner| #inner_loop)
399 .collect()
400 }
401 }
402 ParamType::FixedArray(ref kind, size) => {
403 let inner = quote! { inner };
404 let inner_loop = from_token(kind, &inner);
405 let to_array = vec![quote! { iter.next().expect(INTERNAL_ERR) }; size];
406 quote! {
407 {
408 let mut iter = #token.into_fixed_array().expect(INTERNAL_ERR).into_iter()
409 .map(|#inner| #inner_loop);
410 [#(#to_array),*]
411 }
412 }
413 }
414 ParamType::Tuple(ref types) => {
415 let conversion = types.iter().enumerate().map(|(i, t)| {
416 let inner = quote! { tuple_elements[#i].clone() };
417 let inner_conversion = from_token(t, &inner);
418 quote! { #inner_conversion }
419 });
420
421 quote! {
422 {
423 let tuple_elements = #token.into_tuple().expect(INTERNAL_ERR);
424 (#(#conversion,)*)
425 }
426 }
427 }
428 }
429}
430
431fn decode_topic(
432 name: &String,
433 kind: &ParamType,
434 data_token: &proc_macro2::TokenStream,
435) -> proc_macro2::TokenStream {
436 let error_msg = format!(
437 "unable to decode param '{}' from topic of type '{}': {{:?}}",
438 name, kind
439 );
440
441 match kind {
442 ParamType::Int(_) => {
443 quote! {
444 substreams::scalar::BigInt::from_signed_bytes_be(#data_token)
445 }
446 }
447 _ if kind.is_dynamic() => {
448 let syntax_type = quote! { ethabi::ParamType::FixedBytes(32) };
449
450 quote! {
451 ethabi::decode(&[#syntax_type], #data_token)
452 .map_err(|e| format!(#error_msg, e))?
453 .pop()
454 .expect(INTERNAL_ERR)
455 .into_fixed_bytes()
456 .expect(INTERNAL_ERR)
457 .into()
458 }
459 }
460 _ => {
461 let syntax_type = to_syntax_string(kind);
462 let decode_topic = quote! {
463 ethabi::decode(&[#syntax_type], #data_token)
464 .map_err(|e| format!(#error_msg, e))?
465 .pop()
466 .expect(INTERNAL_ERR)
467 };
468
469 from_token(kind, &decode_topic)
470 }
471 }
472}
473
474fn param_names(inputs: &[Param]) -> Vec<syn::Ident> {
475 inputs
476 .iter()
477 .enumerate()
478 .map(|(index, param)| {
479 if param.name.is_empty() {
480 syn::Ident::new(&format!("param{}", index), Span::call_site())
481 } else {
482 syn::Ident::new(&rust_variable(¶m.name), Span::call_site())
483 }
484 })
485 .collect()
486}
487
488fn get_output_kinds(outputs: &[Param]) -> proc_macro2::TokenStream {
497 match outputs.len() {
498 0 => quote! {()},
499 1 => {
500 let t = rust_type(&outputs[0].kind);
501 quote! { #t }
502 }
503 _ => {
504 let outs: Vec<_> = outputs.iter().map(|param| rust_type(¶m.kind)).collect();
505 quote! { (#(#outs),*) }
506 }
507 }
508}
509
510fn rust_variable(name: &str) -> String {
514 match name {
516 "self" => "_self".to_string(),
517 other => other.to_snake_case(),
518 }
519}
520
521#[cfg(test)]
522mod tests {
523 use ethabi::ParamType;
524
525 use crate::{fixed_data_size, min_data_size};
526
527 #[test]
528 fn from_firehose_types_to_ethabi_token() {
529 use substreams::hex;
530
531 let firehose_address = hex!("0000000000000000000000000000000000000000").to_vec();
532
533 ethabi::Token::Address(ethabi::Address::from_slice(firehose_address.as_ref()));
535 }
536
537 #[test]
538 fn it_fixed_data_size_works() {
539 let inputs: Vec<(&str, ParamType, Option<usize>)> = vec![
540 (
541 "tuple(address)",
542 ParamType::Tuple(vec![ParamType::Address]),
543 Some(32),
544 ),
545 (
546 "bool[2]",
547 ParamType::FixedArray(Box::new(ParamType::Bool), 2),
548 Some(64),
549 ),
550 (
551 "string[2]",
552 ParamType::FixedArray(Box::new(ParamType::String), 2),
553 None,
554 ),
555 ];
556
557 for (name, actual, expected) in inputs {
558 assert_eq!(fixed_data_size(&actual), expected, "test case {}", name);
559 }
560 }
561
562 #[test]
563 fn it_min_data_size_works() {
564 let inputs: Vec<(&str, ParamType, usize)> = vec![
565 (
566 "tuple(address)",
567 ParamType::Tuple(vec![ParamType::Address]),
568 32,
569 ),
570 (
571 "bool[2]",
572 ParamType::FixedArray(Box::new(ParamType::Bool), 2),
573 2 * 32,
574 ),
575 (
576 "string[2]",
577 ParamType::FixedArray(Box::new(ParamType::String), 2),
578 32 + (2 * 32) + (2 * 32),
579 ),
580 ];
581
582 for (name, actual, expected) in inputs {
583 assert_eq!(min_data_size(&actual), expected, "test case {}", name);
584 }
585 }
586}