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 abi: JsonABI,
18
19 abi_parser: ABIParser,
21
22 contract_name: Ident,
24
25 custom_structs: HashMap<String, Property>,
26
27 custom_enums: HashMap<String, Property>,
28
29 rustfmt: bool,
31
32 no_std: bool,
34}
35
36enum CustomType {
37 Enum,
38 Struct,
39}
40
41impl Abigen {
42 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 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 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 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 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 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 if !structs.contains_key(&prop.name) {
221 structs.insert(prop.name.clone(), prop.clone());
222 }
223
224 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 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}