substreams_ethereum_abigen/
lib.rs

1// Copyright 2015-2019 Parity Technologies
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9#![recursion_limit = "256"]
10
11extern crate proc_macro;
12
13mod assertions;
14pub mod build;
15// mod constructor;
16mod contract;
17mod event;
18mod function;
19
20use anyhow::format_err;
21// use ethabi::{Contract, Error, Param, ParamType, Result};
22use ethabi::{Contract, Error, Param, ParamType};
23use heck::ToSnakeCase;
24use proc_macro2::Span;
25// use heck::ToSnakeCase;
26use 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    // workaround for https://github.com/rust-lang/rust/issues/43860
59    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: &ethabi::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
90// fn to_ethabi_param_vec<'a, P: 'a>(params: P) -> proc_macro2::TokenStream
91// where
92//     P: IntoIterator<Item = &'a Param>,
93// {
94//     let p = params
95//         .into_iter()
96//         .map(|x| {
97//             let name = &x.name;
98//             let kind = to_syntax_string(&x.kind);
99//             quote! {
100//                 ethabi::Param {
101//                     name: #name.to_owned(),
102//                     kind: #kind,
103//                     internal_type: None
104//                 }
105//             }
106//         })
107//         .collect::<Vec<_>>();
108
109//     quote! { vec![ #(#p),* ] }
110// }
111
112fn 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        // FixedArray with dynamic element becomes "dynamic" so we have
179        // an initial data offset (32) plus the minimal data size of the sub type multipled
180        // by number of element in the fixed array (count * min_data_size(sub_type)).
181        //
182        // If the sub type is not dynamic, we use its fixed data size.
183        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        // Those are dynamic type meaning there is first an offset where to find the data written (32 bytes)
188        // and then minimally a length (32 bytes) so minimum size is `size(offset) + size(length)` which is
189        // `32 + 32`.
190        ParamType::Bytes | ParamType::String | ParamType::Array(_) => 32 + 32,
191        ParamType::Tuple(ref types) => types.iter().map(min_data_size).sum(),
192    }
193}
194
195/// Check if the given ParamType (recursively navigating through the types if necessary)
196/// is a long tuple (i.e. has more than 12 elements). Those indeed cannot have Debug/PartialEq
197/// defined.
198fn 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
219// fn template_param_type(input: &ParamType, index: usize) -> proc_macro2::TokenStream {
220//     let t_ident = syn::Ident::new(&format!("T{}", index), Span::call_site());
221//     let u_ident = syn::Ident::new(&format!("U{}", index), Span::call_site());
222//     match *input {
223//         ParamType::Address => quote! { #t_ident: Into<ethabi::Address> },
224//         ParamType::Bytes => quote! { #t_ident: Into<ethabi::Bytes> },
225//         ParamType::FixedBytes(32) => quote! { #t_ident: Into<ethabi::Hash> },
226//         ParamType::FixedBytes(size) => quote! { #t_ident: Into<[u8; #size]> },
227//         ParamType::Int(_) => quote! { #t_ident: Into<ethabi::Int> },
228//         ParamType::Uint(_) => quote! { #t_ident: Into<ethabi::Uint> },
229//         ParamType::Bool => quote! { #t_ident: Into<bool> },
230//         ParamType::String => quote! { #t_ident: Into<String> },
231//         ParamType::Array(ref kind) => {
232//             let t = rust_type(&*kind);
233//             quote! {
234//                 #t_ident: IntoIterator<Item = #u_ident>, #u_ident: Into<#t>
235//             }
236//         }
237//         ParamType::FixedArray(ref kind, size) => {
238//             let t = rust_type(&*kind);
239//             quote! {
240//                 #t_ident: Into<[#u_ident; #size]>, #u_ident: Into<#t>
241//             }
242//         }
243//         ParamType::Tuple(_) => {
244//             unimplemented!(
245//                 "Tuples are not supported. https://github.com/openethereum/ethabi/issues/175"
246//             )
247//         }
248//     }
249// }
250
251// fn from_template_param(input: &ParamType, name: &syn::Ident) -> proc_macro2::TokenStream {
252//     match *input {
253//         ParamType::Array(_) => {
254//             quote! { self.#name.into_iter().map(Into::into).collect::<Vec<_>>() }
255//         }
256//         ParamType::FixedArray(_, _) => {
257//             quote! { (Box::new(self.#name.into()) as Box<[_]>).into_vec().into_iter().map(Into::into).collect::<Vec<_>>() }
258//         }
259//         ParamType::Address => quote! { ethabi::Address::from_slice(self.#name.as_ref() ) },
260//         _ => firehose_into_ethabi_type(input, quote! { self.#name }),
261//     }
262// }
263
264// fn firehose_into_ethabi_type(
265//     input: &ParamType,
266//     variable: proc_macro2::TokenStream,
267// ) -> proc_macro2::TokenStream {
268//     match *input {
269//         ParamType::Address => quote! { ethabi::Address::from_slice(#variable) },
270//         ParamType::String => quote! { #variable.clone() },
271//         _ => quote! {#variable.into() },
272//     }
273// }
274
275fn 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            // The check non_full_signed_bytes[0] & 0x80 == 0x80 is checking if the leftmost bit of the first byte is set.
284            // If it is, the number is negative and full_signed_bytes_init is set to 0xff. Otherwise, it's set to 0x00.
285            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                // note the double {{
318                {
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                // note the double {{
329                {
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(&param.name), Span::call_site())
483            }
484        })
485        .collect()
486}
487
488// fn get_template_names(kinds: &[proc_macro2::TokenStream]) -> Vec<syn::Ident> {
489//     kinds
490//         .iter()
491//         .enumerate()
492//         .map(|(index, _)| syn::Ident::new(&format!("T{}", index), Span::call_site()))
493//         .collect()
494// }
495
496fn 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(&param.kind)).collect();
505            quote! { (#(#outs),*) }
506        }
507    }
508}
509
510/// Convert input into a rust variable name.
511///
512/// Avoid using keywords by escaping them.
513fn rust_variable(name: &str) -> String {
514    // avoid keyword parameters
515    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        // Compilation is enough for those tests
534        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}