Skip to main content

fuels_code_gen/program_bindings/
abigen.rs

1use std::{collections::HashSet, path::PathBuf};
2
3pub use abigen_target::{Abi, AbigenTarget, ProgramType};
4use fuel_abi_types::abi::full_program::{FullLoggedType, FullTypeDeclaration};
5use inflector::Inflector;
6use itertools::Itertools;
7use proc_macro2::TokenStream;
8use quote::quote;
9use regex::Regex;
10
11use crate::{
12    error::Result,
13    program_bindings::{
14        abigen::bindings::generate_bindings, custom_types::generate_types,
15        generated_code::GeneratedCode,
16    },
17    utils::ident,
18};
19
20mod abigen_target;
21mod bindings;
22mod configurables;
23mod logs;
24
25pub struct Abigen;
26
27impl Abigen {
28    /// Generate code which can be used to interact with the underlying
29    /// contract, script or predicate in a type-safe manner.
30    ///
31    /// # Arguments
32    ///
33    /// * `targets`: `AbigenTargets` detailing which ABI to generate bindings
34    ///   for, and of what nature (Contract, Script or Predicate).
35    /// * `no_std`: don't use the Rust std library.
36    pub fn generate(targets: Vec<AbigenTarget>, no_std: bool) -> Result<TokenStream> {
37        let generated_code = Self::generate_code(no_std, targets)?;
38
39        let use_statements = generated_code.use_statements_for_uniquely_named_types();
40
41        let code = if no_std {
42            Self::wasm_paths_hotfix(&generated_code.code())
43        } else {
44            generated_code.code()
45        };
46
47        Ok(quote! {
48            #code
49            #use_statements
50        })
51    }
52    fn wasm_paths_hotfix(code: &TokenStream) -> TokenStream {
53        [
54            (r"::\s*std\s*::\s*string", "::alloc::string"),
55            (r"::\s*std\s*::\s*format", "::alloc::format"),
56            (r"::\s*std\s*::\s*vec", "::alloc::vec"),
57            (r"::\s*std\s*::\s*boxed", "::alloc::boxed"),
58        ]
59        .map(|(reg_expr_str, substitute)| (Regex::new(reg_expr_str).unwrap(), substitute))
60        .into_iter()
61        .fold(code.to_string(), |code, (regex, wasm_include)| {
62            regex.replace_all(&code, wasm_include).to_string()
63        })
64        .parse()
65        .expect("Wasm hotfix failed!")
66    }
67
68    fn generate_code(no_std: bool, parsed_targets: Vec<AbigenTarget>) -> Result<GeneratedCode> {
69        let custom_types = Self::filter_custom_types(&parsed_targets);
70        let shared_types = Self::filter_shared_types(custom_types);
71
72        let logged_types = parsed_targets
73            .iter()
74            .flat_map(|abi| abi.source.abi.logged_types.clone())
75            .collect_vec();
76        let bindings = Self::generate_all_bindings(parsed_targets, no_std, &shared_types)?;
77
78        let shared_types = Self::generate_shared_types(shared_types, &logged_types, no_std)?;
79
80        let mod_name = ident("abigen_bindings");
81        Ok(shared_types.merge(bindings).wrap_in_mod(mod_name))
82    }
83
84    fn generate_all_bindings(
85        targets: Vec<AbigenTarget>,
86        no_std: bool,
87        shared_types: &HashSet<FullTypeDeclaration>,
88    ) -> Result<GeneratedCode> {
89        targets
90            .into_iter()
91            .map(|target| Self::generate_binding(target, no_std, shared_types))
92            .fold_ok(GeneratedCode::default(), |acc, generated_code| {
93                acc.merge(generated_code)
94            })
95    }
96
97    fn generate_binding(
98        target: AbigenTarget,
99        no_std: bool,
100        shared_types: &HashSet<FullTypeDeclaration>,
101    ) -> Result<GeneratedCode> {
102        let mod_name = ident(&format!("{}_mod", &target.name.to_snake_case()));
103
104        let recompile_trigger =
105            Self::generate_macro_recompile_trigger(target.source.path.as_ref(), no_std);
106        let types = generate_types(
107            &target.source.abi.types,
108            shared_types,
109            &target.source.abi.logged_types,
110            no_std,
111        )?;
112        let bindings = generate_bindings(target, no_std)?;
113        Ok(recompile_trigger
114            .merge(types)
115            .merge(bindings)
116            .wrap_in_mod(mod_name))
117    }
118
119    /// Any changes to the file pointed to by `path` will cause the reevaluation of the current
120    /// procedural macro. This is a hack until <https://github.com/rust-lang/rust/issues/99515>
121    /// lands.
122    fn generate_macro_recompile_trigger(path: Option<&PathBuf>, no_std: bool) -> GeneratedCode {
123        let code = path
124            .as_ref()
125            .map(|path| {
126                let stringified_path = path.display().to_string();
127                quote! {
128                    const _: &[u8] = include_bytes!(#stringified_path);
129                }
130            })
131            .unwrap_or_default();
132        GeneratedCode::new(code, Default::default(), no_std)
133    }
134
135    fn generate_shared_types(
136        shared_types: HashSet<FullTypeDeclaration>,
137        logged_types: &Vec<FullLoggedType>,
138        no_std: bool,
139    ) -> Result<GeneratedCode> {
140        let types = generate_types(&shared_types, &HashSet::default(), logged_types, no_std)?;
141
142        if types.is_empty() {
143            Ok(Default::default())
144        } else {
145            let mod_name = ident("shared_types");
146            Ok(types.wrap_in_mod(mod_name))
147        }
148    }
149
150    fn filter_custom_types(
151        all_types: &[AbigenTarget],
152    ) -> impl Iterator<Item = &FullTypeDeclaration> {
153        all_types
154            .iter()
155            .flat_map(|target| &target.source.abi.types)
156            .filter(|ttype| ttype.is_custom_type())
157    }
158
159    /// A type is considered "shared" if it appears at least twice in
160    /// `all_custom_types`.
161    ///
162    /// # Arguments
163    ///
164    /// * `all_custom_types`: types from all ABIs whose bindings are being
165    ///   generated.
166    fn filter_shared_types<'a>(
167        all_custom_types: impl IntoIterator<Item = &'a FullTypeDeclaration>,
168    ) -> HashSet<FullTypeDeclaration> {
169        all_custom_types.into_iter().duplicates().cloned().collect()
170    }
171}
172
173#[cfg(test)]
174mod tests {
175    use super::*;
176
177    #[test]
178    fn correctly_determines_shared_types() {
179        let types = ["type_0", "type_1", "type_0"].map(|type_field| FullTypeDeclaration {
180            type_field: type_field.to_string(),
181            components: vec![],
182            type_parameters: vec![],
183            alias_of: None,
184        });
185
186        let shared_types = Abigen::filter_shared_types(&types);
187
188        assert_eq!(shared_types, HashSet::from([types[0].clone()]))
189    }
190}