fuels_rs/code_gen/
abigen.rs

1use std::collections::HashMap;
2
3use crate::code_gen::bindings::ContractBindings;
4use crate::code_gen::custom_types_gen::{expand_internal_enum, expand_internal_struct};
5use crate::code_gen::functions_gen::expand_function;
6use crate::errors::Error;
7use crate::json_abi::ABIParser;
8use crate::source::Source;
9use crate::utils::ident;
10use sway_types::{JsonABI, Property};
11
12use proc_macro2::{Ident, TokenStream};
13use quote::quote;
14
15pub struct Abigen {
16    /// The parsed ABI.
17    abi: JsonABI,
18
19    /// The parser used to transform the JSON format into `JsonABI`
20    abi_parser: ABIParser,
21
22    /// The contract name as an identifier.
23    contract_name: Ident,
24
25    custom_structs: HashMap<String, Property>,
26
27    custom_enums: HashMap<String, Property>,
28
29    /// Format the code using a locally installed copy of `rustfmt`.
30    rustfmt: bool,
31
32    /// Generate no-std safe code
33    no_std: bool,
34}
35
36enum CustomType {
37    Enum,
38    Struct,
39}
40
41impl Abigen {
42    /// Creates a new contract with the given ABI JSON source.
43    pub fn new<S: AsRef<str>>(contract_name: &str, abi_source: S) -> Result<Self, Error> {
44        let source = Source::parse(abi_source).unwrap();
45        let mut parsed_abi: JsonABI = serde_json::from_str(&source.get().unwrap())?;
46
47        // Filter out outputs with empty returns. These are
48        // generated by forc's json abi as `"name": ""` and `"type": "()"`
49        for f in &mut parsed_abi {
50            let index = f
51                .outputs
52                .iter()
53                .position(|p| p.name.is_empty() && p.type_field == "()");
54
55            match index {
56                Some(i) => f.outputs.remove(i),
57                None => continue,
58            };
59        }
60
61        Ok(Self {
62            custom_structs: Abigen::get_custom_types(&parsed_abi, &CustomType::Struct),
63            custom_enums: Abigen::get_custom_types(&parsed_abi, &CustomType::Enum),
64            abi: parsed_abi,
65            contract_name: ident(contract_name),
66            abi_parser: ABIParser::new(),
67            rustfmt: true,
68            no_std: false,
69        })
70    }
71
72    pub fn no_std(mut self) -> Self {
73        self.no_std = true;
74        self
75    }
76
77    /// Generates the contract bindings.
78    pub fn generate(self) -> Result<ContractBindings, Error> {
79        let rustfmt = self.rustfmt;
80        let tokens = self.expand()?;
81
82        Ok(ContractBindings { tokens, rustfmt })
83    }
84
85    /// Entry point of the Abigen's expansion logic.
86    /// The high-level goal of this function is to expand* a contract
87    /// defined as a JSON into type-safe bindings of that contract that can be
88    /// used after it is brought into scope after a successful generation.
89    ///
90    /// *: To expand, in procedural macro terms, means to automatically generate
91    /// Rust code after a transformation of `TokenStream` to another
92    /// set of `TokenStream`. This generated Rust code is the brought into scope
93    /// after it is called through a procedural macro (`abigen!()` in our case).
94    pub fn expand(&self) -> Result<TokenStream, Error> {
95        let name = &self.contract_name;
96        let name_mod = ident(&format!(
97            "{}_mod",
98            self.contract_name.to_string().to_lowercase()
99        ));
100
101        let contract_functions = self.functions()?;
102        let abi_structs = self.abi_structs()?;
103        let abi_enums = self.abi_enums()?;
104
105        let (includes, code) = if self.no_std {
106            (
107                quote! {
108                    use alloc::{vec, vec::Vec};
109                },
110                quote! {},
111            )
112        } else {
113            (
114                quote! {
115                    use fuels_rs::contract::{Contract, ContractCall, CompiledContract};
116                    use fuel_gql_client::client::FuelClient;
117                },
118                quote! {
119                    pub struct #name {
120                        compiled: CompiledContract,
121                        fuel_client: FuelClient
122                    }
123
124                    impl #name {
125                        pub fn new(compiled: CompiledContract, fuel_client: FuelClient) -> Self {
126                            Self{ compiled, fuel_client }
127                        }
128
129                        #contract_functions
130                    }
131                },
132            )
133        };
134
135        Ok(quote! {
136            pub use #name_mod::*;
137
138            #[allow(clippy::too_many_arguments)]
139            mod #name_mod {
140                #![allow(clippy::enum_variant_names)]
141                #![allow(dead_code)]
142                #![allow(unused_imports)]
143
144                #includes
145                use fuels_core::{EnumSelector, ParamType, Tokenizable, Token};
146
147                #code
148
149                #abi_structs
150                #abi_enums
151            }
152        })
153    }
154
155    pub fn functions(&self) -> Result<TokenStream, Error> {
156        let mut tokenized_functions = Vec::new();
157
158        for function in &self.abi {
159            let tokenized_fn = expand_function(
160                function,
161                &self.abi_parser,
162                &self.custom_enums,
163                &self.custom_structs,
164            )?;
165            tokenized_functions.push(tokenized_fn);
166        }
167
168        Ok(quote! { #( #tokenized_functions )* })
169    }
170
171    fn abi_structs(&self) -> Result<TokenStream, Error> {
172        let mut structs = TokenStream::new();
173
174        // Prevent expanding the same struct more than once
175        let mut seen_struct: Vec<&str> = vec![];
176
177        for prop in self.custom_structs.values() {
178            if !seen_struct.contains(&prop.type_field.as_str()) {
179                structs.extend(expand_internal_struct(prop)?);
180                seen_struct.push(&prop.type_field);
181            }
182        }
183
184        Ok(structs)
185    }
186
187    fn abi_enums(&self) -> Result<TokenStream, Error> {
188        let mut enums = TokenStream::new();
189
190        for (name, prop) in &self.custom_enums {
191            enums.extend(expand_internal_enum(name, prop)?);
192        }
193
194        Ok(enums)
195    }
196
197    /// Reads the parsed ABI and returns the custom structs found in it.
198    fn get_custom_types(abi: &JsonABI, ty: &CustomType) -> HashMap<String, Property> {
199        let mut structs = HashMap::new();
200        let mut inner_structs: Vec<Property> = Vec::new();
201
202        let type_string = match ty {
203            CustomType::Enum => "enum",
204            CustomType::Struct => "struct",
205        };
206
207        let mut all_properties: Vec<&Property> = vec![];
208        for function in abi {
209            for prop in &function.inputs {
210                all_properties.push(prop);
211            }
212            for prop in &function.outputs {
213                all_properties.push(prop);
214            }
215        }
216
217        for prop in all_properties {
218            if prop.type_field.contains(type_string) {
219                // Top level struct
220                if !structs.contains_key(&prop.name) {
221                    structs.insert(prop.name.clone(), prop.clone());
222                }
223
224                // Find inner structs in case of nested custom types
225                for inner_component in prop.components.as_ref().unwrap() {
226                    inner_structs.extend(Abigen::get_inner_custom_properties(
227                        inner_component,
228                        type_string,
229                    ));
230                }
231            }
232        }
233
234        for inner_struct in inner_structs {
235            if !structs.contains_key(&inner_struct.name) {
236                structs.insert(inner_struct.name.clone(), inner_struct);
237            }
238        }
239
240        structs
241    }
242
243    // Recursively gets inner properties defined in nested structs or nested enums
244    fn get_inner_custom_properties(prop: &Property, ty: &str) -> Vec<Property> {
245        let mut props = Vec::new();
246
247        if prop.type_field.contains(ty) {
248            props.push(prop.clone());
249
250            for inner_prop in prop.components.as_ref().unwrap() {
251                let inner = Abigen::get_inner_custom_properties(inner_prop, ty);
252                props.extend(inner);
253            }
254        }
255
256        props
257    }
258}
259
260#[cfg(test)]
261mod tests {
262    use super::*;
263
264    #[test]
265    fn generates_bindings() {
266        let contract = r#"
267        [
268            {
269                "type":"contract",
270                "inputs":[
271                    {
272                        "name":"arg",
273                        "type":"u32"
274                    }
275                ],
276                "name":"takes_u32_returns_bool",
277                "outputs":[
278                    {
279                        "name":"",
280                        "type":"bool"
281                    }
282                ]
283            }
284        ]
285        "#;
286
287        let bindings = Abigen::new("test", contract).unwrap().generate().unwrap();
288        bindings.write(std::io::stdout()).unwrap();
289    }
290
291    #[test]
292    fn generates_bindings_two_args() {
293        let contract = r#"
294        [
295            {
296                "type":"contract",
297                "inputs":[
298                    {
299                        "name":"arg",
300                        "type":"u32"
301                    },
302                    {
303                        "name":"second_arg",
304                        "type":"u16"
305                    }
306                ],
307                "name":"takes_ints_returns_bool",
308                "outputs":[
309                    {
310                        "name":"",
311                        "type":"bool"
312                    }
313                ]
314            }
315        ]
316        "#;
317
318        let bindings = Abigen::new("test", contract).unwrap().generate().unwrap();
319        bindings.write(std::io::stdout()).unwrap();
320    }
321
322    #[test]
323    fn custom_struct() {
324        let contract = r#"
325        [
326            {
327                "type":"contract",
328                "inputs":[
329                    {
330                        "name":"value",
331                        "type":"struct MyStruct",
332                        "components": [
333                            {
334                                "name": "foo",
335                                "type": "u8"
336                            },
337                            {
338                                "name": "bar",
339                                "type": "bool"
340                            }
341                        ]
342                    }
343                ],
344                "name":"takes_struct",
345                "outputs":[]
346            }
347        ]
348        "#;
349
350        let contract = Abigen::new("custom", contract).unwrap();
351
352        assert_eq!(1, contract.custom_structs.len());
353
354        assert!(contract.custom_structs.contains_key("value"));
355
356        let bindings = contract.generate().unwrap();
357        bindings.write(std::io::stdout()).unwrap();
358    }
359
360    #[test]
361    fn multiple_custom_types() {
362        let contract = r#"
363        [
364            {
365                "type":"contract",
366                "inputs":[
367                {
368                    "name":"input",
369                    "type":"struct MyNestedStruct",
370                    "components":[
371                    {
372                        "name":"x",
373                        "type":"u16"
374                    },
375                    {
376                        "name":"foo",
377                        "type":"struct InnerStruct",
378                        "components":[
379                        {
380                            "name":"a",
381                            "type":"bool"
382                        },
383                        {
384                            "name":"b",
385                            "type":"u8[2]"
386                        }
387                        ]
388                    }
389                    ]
390                },
391                {
392                    "name":"y",
393                    "type":"struct MySecondNestedStruct",
394                    "components":[
395                    {
396                        "name":"x",
397                        "type":"u16"
398                    },
399                    {
400                        "name":"bar",
401                        "type":"struct SecondInnerStruct",
402                        "components":[
403                        {
404                            "name":"inner_bar",
405                            "type":"struct ThirdInnerStruct",
406                            "components":[
407                            {
408                                "name":"foo",
409                                "type":"u8"
410                            }
411                            ]
412                        }
413                        ]
414                    }
415                    ]
416                }
417                ],
418                "name":"takes_nested_struct",
419                "outputs":[
420                
421                ]
422            }
423        ]
424        "#;
425
426        let contract = Abigen::new("custom", contract).unwrap();
427
428        assert_eq!(5, contract.custom_structs.len());
429
430        let expected_custom_struct_names = vec!["input", "foo", "y", "bar", "inner_bar"];
431
432        for name in expected_custom_struct_names {
433            assert!(contract.custom_structs.contains_key(name));
434        }
435
436        let bindings = contract.generate().unwrap();
437        bindings.write(std::io::stdout()).unwrap();
438    }
439
440    #[test]
441    fn single_nested_struct() {
442        let contract = r#"
443        [
444            {
445                "type":"contract",
446                "inputs":[
447                    {
448                        "name":"top_value",
449                        "type":"struct MyNestedStruct",
450                        "components": [
451                            {
452                                "name": "x",
453                                "type": "u16"
454                            },
455                            {
456                                "name": "foo",
457                                "type": "struct InnerStruct",
458                                "components": [
459                                    {
460                                        "name":"a",
461                                        "type": "bool"
462                                    }
463                                ]
464                            }
465                        ]
466                    }
467                ],
468                "name":"takes_nested_struct",
469                "outputs":[]
470            }
471        ]
472        "#;
473
474        let contract = Abigen::new("custom", contract).unwrap();
475
476        assert_eq!(2, contract.custom_structs.len());
477
478        assert!(contract.custom_structs.contains_key("top_value"));
479        assert!(contract.custom_structs.contains_key("foo"));
480
481        let bindings = contract.generate().unwrap();
482        bindings.write(std::io::stdout()).unwrap();
483    }
484
485    #[test]
486    fn custom_enum() {
487        let contract = r#"
488        [
489            {
490                "type":"contract",
491                "inputs":[
492                    {
493                        "name":"my_enum",
494                        "type":"enum MyEnum",
495                        "components": [
496                            {
497                                "name": "x",
498                                "type": "u32"
499                            },
500                            {
501                                "name": "y",
502                                "type": "bool"
503                            }
504                        ]
505                    }
506                ],
507                "name":"takes_enum",
508                "outputs":[]
509            }
510        ]
511        "#;
512
513        let contract = Abigen::new("custom", contract).unwrap();
514
515        assert_eq!(1, contract.custom_enums.len());
516        assert_eq!(0, contract.custom_structs.len());
517
518        assert!(contract.custom_enums.contains_key("my_enum"));
519
520        let bindings = contract.generate().unwrap();
521        bindings.write(std::io::stdout()).unwrap();
522    }
523    #[test]
524    fn output_types() {
525        let contract = r#"
526        [
527            {
528                "type":"contract",
529                "inputs":[
530                    {
531                        "name":"value",
532                        "type":"struct MyStruct",
533                        "components": [
534                            {
535                                "name": "a",
536                                "type": "str[4]"
537                            },
538                            {
539                                "name": "foo",
540                                "type": "u8[2]"
541                            },
542                            {
543                                "name": "bar",
544                                "type": "bool"
545                            }
546                        ]
547                    }
548                ],
549                "name":"takes_enum",
550                "outputs":[
551                    {
552                        "name":"ret",
553                        "type":"struct MyStruct",
554                        "components": [
555                            {
556                                "name": "a",
557                                "type": "str[4]"
558                            },
559                            {
560                                "name": "foo",
561                                "type": "u8[2]"
562                            },
563                            {
564                                "name": "bar",
565                                "type": "bool"
566                            }
567                        ]
568                    }
569                ]
570            }
571        ]
572        "#;
573
574        let contract = Abigen::new("custom", contract).unwrap();
575        let bindings = contract.generate().unwrap();
576        bindings.write(std::io::stdout()).unwrap();
577    }
578}