leo_bindings_core/
generator.rs

1use crate::generator_interpreter::generate_interpreter_impl;
2use crate::signature::{FunctionBinding, SimplifiedBindings};
3use crate::types::get_rust_type;
4use convert_case::{Case::Pascal, Casing};
5use itertools::Itertools;
6use proc_macro2::{Ident, Literal, Span, TokenStream};
7use quote::quote;
8
9pub fn generate_program_module(simplified: &SimplifiedBindings) -> TokenStream {
10    let program_name_pascal = simplified.program_name.to_case(Pascal);
11
12    let program_module = Ident::new(&simplified.program_name, Span::call_site());
13    let program_trait = Ident::new(&format!("{}Aleo", program_name_pascal), Span::call_site());
14    let program_struct = Ident::new(
15        &format!("{}Network", program_name_pascal),
16        Span::call_site(),
17    );
18
19    let network_aliases = generate_network_aliases(&program_name_pascal, &program_struct);
20
21    let records = generate_records(&simplified.records);
22    let structs = generate_structs(&simplified.structs);
23
24    let function_types = generate_function_types(&simplified.functions);
25    let mapping_types = generate_mapping_types(&simplified.mappings);
26
27    let trait_definition = generate_trait(&function_types, &mapping_types, &program_trait);
28
29    let network_impl = generate_network_impl(
30        simplified,
31        &function_types,
32        &mapping_types,
33        &program_trait,
34        &program_struct,
35    );
36
37    let interpreter_impl =
38        generate_interpreter_impl(simplified, &function_types, &mapping_types, &program_trait);
39
40    let type_imports = generate_type_imports(&simplified.imports);
41
42    quote! {
43        pub mod #program_module {
44            use leo_bindings::{anyhow, snarkvm, indexmap};
45            use anyhow::{anyhow, Result};
46            use snarkvm::prelude::*;
47            use snarkvm::prelude::Network;
48            use indexmap::IndexMap;
49            use leo_bindings::{ToValue, FromValue};
50            use leo_bindings::utils::Account;
51
52            #type_imports
53
54            #network_aliases
55
56            #(#structs)*
57
58            #(#records)*
59
60            #trait_definition
61
62            /// Main bindings that connect to the Provable API or a local devnet.
63            ///
64            /// The network bindings can optionally use the Provable delegated proving service.
65            pub mod network {
66                use super::*;
67                #network_impl
68            }
69
70            #interpreter_impl
71        }
72    }
73}
74
75fn generate_trait(
76    function_types: &[FunctionTypes],
77    mapping_types: &[MappingTypes],
78    program_trait: &Ident,
79) -> TokenStream {
80    let function_signatures: Vec<TokenStream> = function_types
81        .iter()
82        .map(|types| {
83            let name = &types.name;
84            let input_params = &types.input_params;
85            let return_type = &types.return_type;
86            quote! { fn #name (&self, account: &Account<N>, #input_params) -> #return_type; }
87        })
88        .collect();
89
90    let mapping_signatures: Vec<TokenStream> = mapping_types
91        .iter()
92        .map(|types| {
93            let getter_name = &types.getter_name;
94            let key_type = &types.key_type;
95            let value_type = &types.value_type;
96            quote! { fn #getter_name(&self, key: #key_type) -> Option<#value_type>; }
97        })
98        .collect();
99
100    quote! {
101        /// Program trait with network and interpreter implementations.
102        pub trait #program_trait<N: snarkvm::prelude::Network> {
103            fn new(deployer: &Account<N>, endpoint: &str) -> Result<Self, anyhow::Error> where Self: Sized;
104            #(#function_signatures)*
105            #(#mapping_signatures)*
106        }
107    }
108}
109
110fn generate_network_impl(
111    simplified: &SimplifiedBindings,
112    function_types: &[FunctionTypes],
113    mapping_types: &[MappingTypes],
114    program_trait: &Ident,
115    program_struct: &Ident,
116) -> TokenStream {
117    let program_id = Literal::string(&format!("{}.aleo", &simplified.program_name));
118
119    let (deployment_calls, trait_imports, dependency_additions): (Vec<_>, Vec<_>, Vec<_>) = simplified
120        .imports
121        .iter()
122        .map(|import| {
123            let import_pascal = import.to_case(Pascal);
124            let import_module = Ident::new(import, Span::call_site());
125            let import_struct = Ident::new(&format!("{}Network", import_pascal), Span::call_site());
126            let import_trait = Ident::new(&format!("{}Aleo", import_pascal), Span::call_site());
127            let dependency_id = format!("{}.aleo", import);
128            let import_crate_name = Ident::new(&format!("{}_bindings", import), Span::call_site());
129
130            let deployment = quote! { #import_crate_name::#import_module::network::#import_struct::<N>::new(deployer, endpoint)?; };
131            let trait_import = quote! { use #import_crate_name::#import_module::#import_trait; };
132            let dependency_addition = quote! {
133                let dependency_id = ProgramID::<N>::from_str(#dependency_id)?;
134                let api_endpoint = format!("{}/v2", endpoint);
135                wait_for_program_availability(&dependency_id.to_string(), &api_endpoint, N::SHORT_NAME, 60).map_err(|e| anyhow!(e.to_string()))?;
136                let dependency_program: Program<N> = {
137                    let mut response = ureq::get(&format!("{}/{}/program/{}", api_endpoint, N::SHORT_NAME, dependency_id)).call().unwrap();
138                    let json_text = response.body_mut().read_to_string().unwrap();
139                    let json_response: serde_json::Value = serde_json::from_str(&json_text).unwrap();
140                    json_response.as_str().unwrap().to_string().parse().unwrap()
141                };
142                vm.process().write().add_program(&dependency_program)?;
143            };
144            (deployment, trait_import, dependency_addition)
145        })
146        .multiunzip();
147    let dependency_additions = quote! { #(#dependency_additions)* };
148
149    let function_implementations: Vec<TokenStream> = function_types
150        .iter()
151        .map(|types| generate_function(&dependency_additions, types, &program_id))
152        .collect();
153
154    let mapping_implementations: Vec<TokenStream> = mapping_types
155        .iter()
156        .map(|types| generate_mapping(types, &program_id))
157        .collect();
158
159    let new_implementation = generate_new(
160        &deployment_calls,
161        &dependency_additions,
162        &trait_imports,
163        &simplified.program_name,
164    );
165
166    quote! {
167        use leo_bindings::{serde_json, leo_package, leo_ast, leo_span, aleo_std, http, ureq, rand, print_execution_stats, print_deployment_stats};
168        use leo_bindings::utils::*;
169        use anyhow::ensure;
170        use snarkvm::ledger::query::*;
171        use snarkvm::ledger::store::helpers::memory::{ConsensusMemory, BlockMemory};
172        use snarkvm::ledger::store::ConsensusStore;
173        use snarkvm::ledger::block::Transaction;
174        use snarkvm::console::program::{Record, Plaintext};
175        use snarkvm::synthesizer::VM;
176        use snarkvm::synthesizer::process::execution_cost;
177        use snarkvm::prelude::ConsensusVersion;
178        use snarkvm::ledger::query::{QueryTrait, Query};
179        use leo_package::Package;
180        use leo_ast::NetworkName;
181        use leo_span::create_session_if_not_set_then;
182        use aleo_std::StorageMode;
183        use std::str::FromStr;
184
185        #[derive(Debug)]
186        pub struct #program_struct<N: Network> {
187            pub package: Package,
188            pub endpoint: String,
189            pub delegated_proving_config: Option<leo_bindings::DelegatedProvingConfig>,
190            _network: std::marker::PhantomData<N>,
191        }
192
193        impl<N: Network> #program_trait<N> for #program_struct<N> {
194            #new_implementation
195
196            #(#function_implementations)*
197
198            #(#mapping_implementations)*
199        }
200
201        impl<N: Network> #program_struct<N> {
202            pub fn configure_delegation(mut self, config: leo_bindings::DelegatedProvingConfig) -> Self {
203                self.delegated_proving_config = Some(config);
204                self
205            }
206            pub fn enable_delegation(mut self) -> Self {
207                if let Some(config) = &mut self.delegated_proving_config {
208                    config.enabled = true;
209                    log::info!("✅ Delegated proving enabled");
210                }
211                self
212            }
213            pub fn disable_delegation(mut self) -> Self {
214                if let Some(config) = &mut self.delegated_proving_config {
215                    config.enabled = false;
216                    log::info!("â„šī¸ Delegated proving disabled");
217                }
218                self
219            }
220        }
221    }
222}
223
224pub fn generate_records(records: &[crate::signature::StructBinding]) -> Vec<TokenStream> {
225    records.iter().map(|record| {
226        let record_name = Ident::new(&record.name.to_case(Pascal), Span::call_site());
227        let member_definitions = record.members.iter().map(|member| {
228            let member_name = Ident::new(&member.name, Span::call_site());
229            if member.name == "owner" {
230                quote! { #member_name: Owner<N, Plaintext<N>> }
231            } else {
232                let member_type = get_rust_type(&member.type_name);
233                quote! { #member_name: #member_type }
234            }
235        });
236        let extra_record_fields = quote! { __nonce: Group<N>, __version: U8<N> };
237
238        let member_conversions = record.members.iter().filter(|member| member.name != "owner").map(|member| {
239            let member_name = Ident::new(&member.name, Span::call_site());
240            let mode = &member.mode;
241
242            let entry_creation = match mode.to_lowercase().as_str() {
243                "public" => quote! { Entry::Public(plaintext_value) },
244                "private" | "none" => quote! { Entry::Private(plaintext_value) },
245                _ => panic!("Unsupported mode '{}' for field '{}'. Only 'Private' and 'Public' modes are supported.", mode, member.name),
246            };
247
248            quote! {
249                (
250                    Identifier::try_from(stringify!(#member_name)).unwrap(),
251                    {
252                        let plaintext_value = match self.#member_name.to_value() {
253                            Value::Plaintext(p) => p,
254                            _ => panic!("Expected plaintext value from record member"),
255                        };
256                        #entry_creation
257                    }
258                )
259            }
260        });
261
262        let (member_extractions, struct_member_extractions): (Vec<_>, Vec<_>) = record.members
263            .iter()
264            .map(|member| {
265                let member_name = Ident::new(&member.name, Span::call_site());
266                let member_type = get_rust_type(&member.type_name);
267                let field_name = &member.name;
268
269                let record_extraction = if field_name == "owner" {
270                    quote! {
271                        let #member_name = record.owner().clone();
272                    }
273                } else {
274                    quote! {
275                        let #member_name = {
276                            let member_id = &Identifier::try_from(#field_name).unwrap();
277                            let entry = record.data().get(member_id)
278                                .expect(&format!("Field '{}' not found in record data", #field_name));
279                            let plaintext = match entry {
280                                Entry::Public(p) | Entry::Private(p) | Entry::Constant(p) => p,
281                            };
282                            let value = Value::Plaintext(plaintext.clone());
283                            <#member_type>::from_value(value)
284                        };
285                    }
286                };
287
288                // Needed for interpreter compatibility
289                let struct_extraction = if field_name == "owner" {
290                    quote! {
291                        let #member_name = {
292                            let member_id = &Identifier::try_from(#field_name).unwrap();
293                            let plaintext = struct_members.get(member_id)
294                                .expect("Owner field not found in record struct");
295                            match plaintext {
296                                Plaintext::Literal(Literal::Address(addr), _) => Owner::Public(*addr),
297                                _ => panic!("Expected address for owner field"),
298                            }
299                        };
300                    }
301                } else {
302                    quote! {
303                        let #member_name = {
304                            let member_id = &Identifier::try_from(#field_name).unwrap();
305                            let plaintext = struct_members.get(member_id)
306                                .expect(&format!("Field '{}' not found in record data", #field_name));
307                            <#member_type>::from_value(Value::Plaintext(plaintext.clone()))
308                        };
309                    }
310                };
311
312                (record_extraction, struct_extraction)
313            })
314            .unzip();
315
316        let member_names: Vec<_> = record.members.iter().map(|member| {
317            Ident::new(&member.name, Span::call_site())
318        }).collect();
319        let extra_member_inits = quote! { __nonce: record.nonce().clone(), __version: record.version().clone() };
320
321        let getter_methods = record.members.iter().map(|member| {
322            let member_name = Ident::new(&member.name, Span::call_site());
323
324            if member.name == "owner" {
325                quote! {
326                    pub fn #member_name(&self) -> Address<N> {
327                        match &self.#member_name {
328                            Owner::Public(addr) => *addr,
329                            Owner::Private(plaintext) => {
330                                match plaintext {
331                                    Plaintext::Literal(Literal::Address(addr), _) => *addr,
332                                    _ => panic!("Expected address in private owner field"),
333                                }
334                            }
335                        }
336                    }
337                }
338            } else {
339                let member_type = get_rust_type(&member.type_name);
340                quote! {
341                    pub fn #member_name(&self) -> &#member_type {
342                        &self.#member_name
343                    }
344                }
345            }
346        });
347
348        quote! {
349            /// Record from Leo.
350            #[derive(Debug, Clone)]
351            pub struct #record_name<N: Network> {
352                #(#member_definitions),*,
353                #extra_record_fields
354            }
355
356            /// Convert to a SnarkVM Value.
357            impl<N: Network> ToValue<N> for #record_name<N> {
358                fn to_value(&self) -> Value<N> {
359                    match self.to_record() {
360                        Ok(rec) => Value::Record(rec),
361                        Err(e) => panic!("Failed to convert to Record: {}", e),
362                    }
363                }
364            }
365
366            /// Create from a SnarkVM Value
367            impl<N: Network> FromValue<N> for #record_name<N> {
368                fn from_value(value: Value<N>) -> Self {
369                    match value {
370                        Value::Record(record) => {
371                            #(#member_extractions)*
372                            Self {
373                                #(#member_names),*,
374                                #extra_member_inits
375                            }
376                        },
377                        // Interpreter compatibility: records represented as structs
378                        Value::Plaintext(Plaintext::Struct(struct_members, _)) => {
379                            #(#struct_member_extractions)*
380
381                            Self {
382                                #(#member_names),*,
383                                __nonce: Group::zero(),
384                                __version: U8::new(0)
385                            }
386                        },
387                        _ => panic!("Expected record or struct value"),
388                    }
389                }
390            }
391
392            impl<N: Network> #record_name<N> {
393                /// Convert to a SnarkVM Record.
394                pub fn to_record(&self) -> Result<Record<N, Plaintext<N>>, anyhow::Error> {
395                    let data = IndexMap::from([
396                        #(#member_conversions),*
397                    ]);
398                    let owner = self.owner.clone();
399                    let nonce = self.__nonce.clone();
400                    let version = self.__version.clone();
401
402                    Record::<N, Plaintext<N>>::from_plaintext(
403                        owner,
404                        data,
405                        nonce,
406                        version
407                    ).map_err(|e| anyhow::anyhow!("Failed to create record: {}", e))
408                }
409
410                #(#getter_methods)*
411            }
412        }
413    }).collect()
414}
415
416pub fn generate_structs(structs: &[crate::signature::StructBinding]) -> Vec<TokenStream> {
417    structs
418        .iter()
419        .map(|struct_def| {
420            let struct_name = Ident::new(&struct_def.name.to_case(Pascal), Span::call_site());
421            let (definitions, extractions, names, constructor_definitions, conversions): (Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>) = struct_def
422                .members
423                .iter()
424                .map(|member| {
425                    let member_name = Ident::new(&member.name, Span::call_site());
426                    let member_type = get_rust_type(&member.type_name);
427
428                    let definition = quote! { pub #member_name: #member_type, };
429
430                    let extraction = quote! {
431                        let #member_name = {
432                            let member_id = &Identifier::try_from(stringify!(#member_name)).unwrap();
433                            let entry = struct_members.get(member_id).unwrap();
434                            <#member_type>::from_value(Value::Plaintext(entry.clone()))
435                        };
436                    };
437
438                    let name = member_name.clone();
439
440                    let constructor_definition = quote! { #member_name: #member_type, };
441
442                    let conversion = quote! {
443                        (
444                            Identifier::try_from(stringify!(#member_name)).unwrap(),
445                            match self.#member_name.to_value() {
446                                Value::Plaintext(p) => p,
447                                _ => panic!("Expected plaintext value"),
448                            }
449                        )
450                    };
451
452                    (definition, extraction, name, constructor_definition, conversion)
453                })
454                .multiunzip();
455
456            quote! {
457                /// Struct from Leo.
458                #[derive(Debug, Clone, Copy)]
459                pub struct #struct_name<N: Network> {
460                    #(#definitions)*
461                    _network: std::marker::PhantomData<N>
462                }
463
464                /// Convert to a SnarkVM Value.
465                impl<N: Network> ToValue<N> for #struct_name<N> {
466                    fn to_value(&self) -> Value<N> {
467                        let members = IndexMap::from([
468                            #(#conversions),*
469                        ]);
470                        Value::Plaintext(Plaintext::Struct(members, std::sync::OnceLock::new()))
471                    }
472                }
473
474                /// Create from a SnarkVM Value.
475                impl<N: Network> FromValue<N> for #struct_name<N> {
476                    fn from_value(value: Value<N>) -> Self {
477                        match value {
478                            Value::Plaintext(Plaintext::Struct(struct_members, _)) => {
479                                #(#extractions)*
480                                Self {
481                                    #(#names,)*
482                                    _network: std::marker::PhantomData
483                                }
484                            },
485                            _ => panic!("Expected struct type"),
486                        }
487                    }
488                }
489
490                impl<N: Network> #struct_name<N> {
491                    pub fn new(#(#constructor_definitions)*) -> Self {
492                        Self {
493                            #(#names,)*
494                            _network: std::marker::PhantomData
495                        }
496                    }
497                }
498            }
499        })
500        .collect()
501}
502
503pub(crate) struct FunctionTypes {
504    pub(crate) name: Ident,
505    pub(crate) input_params: TokenStream,
506    pub(crate) input_conversions: TokenStream,
507    pub(crate) return_type: TokenStream,
508    pub(crate) return_conversions: TokenStream,
509}
510
511pub(crate) struct MappingTypes {
512    pub(crate) getter_name: Ident,
513    pub(crate) mapping_name_literal: String,
514    pub(crate) key_type: TokenStream,
515    pub(crate) value_type: TokenStream,
516}
517
518fn generate_function_types(functions: &[FunctionBinding]) -> Vec<FunctionTypes> {
519    functions.iter().map(|function| {
520        let name = Ident::new(&function.name, Span::call_site());
521
522        let (input_params, input_conversions): (Vec<_>, Vec<_>) = function.inputs.iter().map(|input| {
523            let param_name = Ident::new(&input.name, Span::call_site());
524            let param_type = get_rust_type(&input.type_name);
525            let param = quote! { #param_name: #param_type };
526            let conversion = quote! { (#param_name).to_value() };
527            (param, conversion)
528        }).unzip();
529        let input_params = quote! { #(#input_params),* };
530        let input_conversions = quote! { #(#input_conversions),* };
531
532        let (return_type, return_conversions) = match function.outputs.len() {
533            0 => (
534                quote! { Result<(), anyhow::Error> },
535                quote! { Ok(()) }
536            ),
537            1 => {
538                let output_type = get_rust_type(&function.outputs[0].type_name);
539                let conversion = quote! {
540                    match function_outputs.get(0) {
541                        Some(snarkvm_value) => <#output_type>::from_value(snarkvm_value.clone()),
542                        None => return Err(anyhow!("Missing output at index 0")),
543                    }
544                };
545                (
546                    quote! { Result<#output_type, anyhow::Error> },
547                    quote! { Ok(#conversion) }
548                )
549            },
550            _ => {
551                let (output_types, output_conversions): (Vec<_>, Vec<_>) = function.outputs.iter()
552                    .enumerate()
553                    .map(|(i, output)| {
554                        let output_type = get_rust_type(&output.type_name);
555                        let conversion = quote! {
556                            match function_outputs.get(#i) {
557                                Some(snarkvm_value) => <#output_type>::from_value(snarkvm_value.clone()),
558                                None => return Err(anyhow!("Missing output at index {}", #i)),
559                            }
560                        };
561                        (output_type, conversion)
562                    })
563                    .unzip();
564                (
565                    quote! { Result<(#(#output_types),*), anyhow::Error> },
566                    quote! { Ok((#(#output_conversions),*)) }
567                )
568            }
569        };
570        FunctionTypes {
571            name,
572            input_params,
573            input_conversions,
574            return_type,
575            return_conversions,
576        }
577    }).collect()
578}
579
580fn generate_mapping_types(mappings: &[crate::signature::MappingBinding]) -> Vec<MappingTypes> {
581    mappings
582        .iter()
583        .map(|mapping| {
584            let getter_name = Ident::new(&format!("get_{}", mapping.name), Span::call_site());
585            let mapping_name_literal = mapping.name.clone();
586            let key_type = get_rust_type(&mapping.key_type);
587            let value_type = get_rust_type(&mapping.value_type);
588
589            MappingTypes {
590                getter_name,
591                mapping_name_literal,
592                key_type,
593                value_type,
594            }
595        })
596        .collect()
597}
598
599fn generate_new(
600    deployment_calls: &[TokenStream],
601    dependency_additions: &TokenStream,
602    trait_imports: &[TokenStream],
603    program_name: &str,
604) -> TokenStream {
605    quote! {
606        fn new(deployer: &Account<N>, endpoint: &str) -> Result<Self, anyhow::Error> {
607            use leo_package::Package;
608            use leo_span::create_session_if_not_set_then;
609            use std::path::Path;
610            #(#trait_imports)*
611
612            let result = create_session_if_not_set_then(|_| {
613                let crate_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
614
615                let package = Package::from_directory(
616                    crate_dir,
617                    crate_dir,
618                    false,
619                    false,
620                    Some(NetworkName::from_str(N::SHORT_NAME).unwrap()),
621                    Some(endpoint),
622                )?;
623
624                let program_id = ProgramID::<N>::from_str(concat!(#program_name, ".aleo"))?;
625                let api_endpoint = format!("{}/v2", endpoint);
626
627                #(#deployment_calls)*
628
629                let program_exists = {
630                    let check_response = ureq::get(&format!("{}/{}/program/{}", api_endpoint, N::SHORT_NAME, program_id))
631                        .call();
632                    match check_response {
633                        Ok(_) => {
634                            log::info!("✅ Found '{}', skipping deployment", program_id);
635                            true
636                        },
637                        Err(_) => {
638                            log::info!("đŸ“Ļ Deploying '{}'", program_id);
639                            false
640                        }
641                    }
642                };
643
644                if !program_exists {
645                    let program_symbol = leo_span::Symbol::intern(#program_name);
646                    let target_program = package.programs.iter()
647                        .find(|p| p.name == program_symbol)
648                        .ok_or_else(|| anyhow!("Program '{}' not found in package", #program_name))?;
649
650                    let bytecode = match &target_program.data {
651                        leo_package::ProgramData::Bytecode(bytecode) => {
652                            bytecode.clone()
653                        },
654                        leo_package::ProgramData::SourcePath { directory, source: _ } => {
655                            let aleo_path = directory.join("build").join("main.aleo");
656                            std::fs::read_to_string(&aleo_path)
657                                .map_err(|e| anyhow!("Failed to read bytecode from {}: {}", aleo_path.display(), e))?
658                        }
659                    };
660
661                    let program: Program<N> = bytecode.parse()
662                        .map_err(|e| anyhow!("Failed to parse program: {}", e))?;
663
664                    log::info!("đŸ“Ļ Creating deployment tx for '{}'...", program_id);
665                    let rng = &mut rand::thread_rng();
666                    let vm = VM::from(ConsensusStore::<N, ConsensusMemory<N>>::open(StorageMode::Production)?)?;
667                    let query = Query::<N, BlockMemory<N>>::from(endpoint.parse::<http::uri::Uri>()?);
668
669                     #dependency_additions
670
671                    let transaction = vm.deploy(
672                        deployer.private_key(),
673                        &program,
674                        None,
675                        0,
676                        Some(&query),
677                        rng,
678                    ).map_err(|e| anyhow!("Failed to generate deployment transaction: {}", e))?;
679
680                    match &transaction {
681                        Transaction::Deploy(_, _, _, deployment, fee) => {
682                            print_deployment_stats(&vm, &program_id.to_string(), deployment, None, ConsensusVersion::V10)?;
683                        },
684                        _ => panic!("Expected a deployment transaction."),
685                    };
686
687                    log::info!("📡 Broadcasting deployment tx: {} to {}",transaction.id(), endpoint);
688
689                    broadcast_transaction(transaction.clone(), &api_endpoint, N::SHORT_NAME)?;
690
691                    wait_for_transaction_confirmation::<N>(&transaction.id(), &api_endpoint, N::SHORT_NAME, 120)?;
692                    wait_for_program_availability(&program_id.to_string(), &api_endpoint, N::SHORT_NAME, 60).map_err(|e| anyhow!(e.to_string()))?;
693                }
694
695                Ok(Self {
696                    package,
697                    endpoint: endpoint.to_string(),
698                    delegated_proving_config: None,
699                    _network: std::marker::PhantomData,
700                })
701            });
702            result
703        }
704    }
705}
706
707fn generate_function(
708    dependency_additions: &TokenStream,
709    types: &FunctionTypes,
710    program_id: &Literal,
711) -> TokenStream {
712    let FunctionTypes {
713        name,
714        input_params,
715        input_conversions,
716        return_type,
717        return_conversions,
718    } = types;
719
720    quote! {
721        fn #name(&self, account: &Account<N>, #input_params) -> #return_type {
722            let endpoint = &self.endpoint;
723            let api_endpoint = format!("{}/v2", endpoint);
724            let program_id = ProgramID::try_from(#program_id).unwrap();
725            let function_id = Identifier::try_from(stringify!(#name)).unwrap();
726            let function_args: Vec<Value<N>> = vec![#input_conversions];
727
728            let rng = &mut rand::thread_rng();
729            let locator = Locator::<N>::new(program_id, function_id);
730
731            log::info!("Creating tx: {}.{}({})", #program_id, stringify!(#name), stringify!(#input_params));
732            let vm = VM::from(ConsensusStore::<N, ConsensusMemory<N>>::open(StorageMode::Production)?)?;
733            let query = Query::<N, BlockMemory<N>>::from(endpoint.parse::<http::uri::Uri>()?);
734
735            wait_for_program_availability(&program_id.to_string(), &api_endpoint, N::SHORT_NAME, 60).map_err(|e| anyhow!(e.to_string()))?;
736            let program: Program<N> = {
737                let mut response = ureq::get(&format!("{}/{}/program/{}", api_endpoint, N::SHORT_NAME, program_id))
738                    .call().unwrap();
739                let json_text = response.body_mut().read_to_string().unwrap();
740                let json_response: serde_json::Value = serde_json::from_str(&json_text).unwrap();
741                json_response.as_str().unwrap().parse().unwrap()
742            };
743
744            #dependency_additions
745
746            vm.process().write().add_programs_with_editions(&vec![(program, 1u16)])
747                .map_err(|e| anyhow!("Failed to add program '{}' to VM: {}", program_id, e))?;
748
749            let delegated_result = self.delegated_proving_config.as_ref()
750                .filter(|config| config.enabled)
751                .and_then(|config| {
752                    let authorization = vm
753                        .authorize(account.private_key(), program_id, function_id, function_args.iter(), rng)
754                        .map_err(|e| log::warn!("Failed to create authorization: {}", e))
755                        .ok()?;
756
757                    let function_outputs = extract_outputs_from_authorization(&authorization, account.view_key())
758                        .map_err(|e| log::warn!("Failed to extract outputs: {}", e))
759                        .ok()?;
760
761                    let transaction = execute_with_delegated_proving(config, authorization).ok()?;
762
763                    Some((transaction, function_outputs))
764                });
765
766            let (transaction, function_outputs): (Transaction<N>, Vec<Value<N>>) = match delegated_result {
767                Some(result) => result,
768                None => {
769                    let (transaction, response) = vm.execute_with_response(
770                        account.private_key(),
771                        (program_id, function_id),
772                        function_args.iter(),
773                        None,
774                        0,
775                        Some(&query as &dyn QueryTrait<N>),
776                        rng,
777                    ).map_err(|e| anyhow!("Failed to execute function '{}' in program '{}': {}", function_id, program_id, e))?;
778                    (transaction, response.outputs().to_vec())
779                }
780            };
781
782            let public_balance = get_public_balance(&account.address(), &api_endpoint, N::SHORT_NAME);
783            let execution = transaction.execution().ok_or_else(|| anyhow!("Missing execution"))?;
784            let (total_cost, _) = execution_cost(&vm.process().read(), execution, ConsensusVersion::V10)?;
785
786            match &transaction {
787                Transaction::Execute(_, _, execution, fee) => {
788                    print_execution_stats(&vm, &program_id.to_string(), &execution, None, ConsensusVersion::V10)?;
789                },
790                _ => panic!("Expected an execution transaction."),
791            };
792
793            ensure!(public_balance >= total_cost,
794                "❌ Insufficient balance {} for total cost {} on `{}`", public_balance, total_cost, locator);
795
796            log::info!("📡 Broadcasting tx: {}",transaction.id());
797            broadcast_transaction(transaction.clone(), &api_endpoint, N::SHORT_NAME)?;
798            wait_for_transaction_confirmation::<N>(&transaction.id(), &api_endpoint, N::SHORT_NAME, 30)?;
799
800            #return_conversions
801        }
802    }
803}
804
805fn generate_mapping(types: &MappingTypes, program_id: &Literal) -> TokenStream {
806    let MappingTypes {
807        getter_name,
808        mapping_name_literal,
809        key_type,
810        value_type,
811    } = types;
812
813    quote! {
814        fn #getter_name(&self, key: #key_type) -> Option<#value_type> {
815            let program_id = #program_id;
816            let mapping_name = #mapping_name_literal;
817            let api_endpoint = format!("{}/v2", self.endpoint);
818
819            let key_value: Value<N> = key.to_value();
820            let url = format!("{}/{}/program/{}/mapping/{}/{}",
821                &api_endpoint, N::SHORT_NAME, program_id, mapping_name,
822                key_value.to_string().replace("\"", ""));
823
824            match leo_bindings::utils::fetch_mapping_value(&url) {
825                Ok(Some(json_text)) => {
826                    let value: Option<Value<N>> = serde_json::from_str(&json_text)
827                        .expect("Failed to parse mapping value JSON");
828                    value.map(|val| <#value_type>::from_value(val))
829                }
830                Ok(None) => None,
831                Err(e) => panic!("Failed to fetch mapping value: {}", e),
832            }
833        }
834    }
835}
836
837fn generate_network_aliases(program_name_pascal: &str, program_struct: &Ident) -> TokenStream {
838    let testnet_struct = Ident::new(
839        &format!("{}Testnet", program_name_pascal),
840        Span::call_site(),
841    );
842    let mainnet_struct = Ident::new(
843        &format!("{}Mainnet", program_name_pascal),
844        Span::call_site(),
845    );
846    let canary_struct = Ident::new(&format!("{}Canary", program_name_pascal), Span::call_site());
847    let interpreter_struct = Ident::new(
848        &format!("{}Interpreter", program_name_pascal),
849        Span::call_site(),
850    );
851
852    quote! {
853        pub type #testnet_struct = network::#program_struct<snarkvm::prelude::TestnetV0>;
854
855        pub type #mainnet_struct = network::#program_struct<snarkvm::prelude::MainnetV0>;
856
857        pub type #canary_struct = network::#program_struct<snarkvm::prelude::CanaryV0>;
858
859        pub type #interpreter_struct = interpreter::#interpreter_struct<snarkvm::prelude::TestnetV0>;
860    }
861}
862
863fn generate_type_imports(imports: &[String]) -> TokenStream {
864    let import_statements: Vec<TokenStream> = imports
865        .iter()
866        .map(|import| {
867            let import_crate_name = Ident::new(&format!("{}_bindings", import), Span::call_site());
868            let import_module = Ident::new(import, Span::call_site());
869            quote! {
870                pub use #import_crate_name::#import_module::*;
871            }
872        })
873        .collect();
874
875    quote! {
876        #(#import_statements)*
877    }
878}