multiversx_sc_derive/generate/
proxy_gen.rs

1use quote::ToTokens;
2
3use super::util::*;
4use crate::{
5    generate::{convert_to_owned_type::convert_to_owned_type, snippets, supertrait_gen},
6    model::{ArgPaymentMetadata, ContractTrait, Method, MethodArgument, PublicRole},
7};
8
9pub fn proxy_arg_gen(
10    method_args: &[MethodArgument],
11    generics: &mut syn::Generics,
12) -> Vec<proc_macro2::TokenStream> {
13    let mut args_decl = Vec::new();
14    for (arg_index, arg) in method_args.iter().enumerate() {
15        let unprocessed_attributes = &arg.unprocessed_attributes;
16        let pat = &arg.pat;
17        if arg.is_endpoint_arg() {
18            // let ty = &arg.ty;
19            let mut bounds = syn::punctuated::Punctuated::new();
20            bounds.push(syn::TypeParamBound::Trait(syn::TraitBound {
21                paren_token: None,
22                modifier: syn::TraitBoundModifier::None,
23                lifetimes: None,
24                path: equivalent_encode_path_gen(&arg.ty),
25            }));
26            let arg_type_generated_ident = generate_proxy_type_generic(arg_index);
27            generics
28                .params
29                .push(syn::GenericParam::Type(syn::TypeParam {
30                    attrs: Vec::new(),
31                    ident: arg_type_generated_ident.clone(),
32                    colon_token: Some(syn::token::Colon(proc_macro2::Span::call_site())),
33                    bounds,
34                    eq_token: None,
35                    default: None,
36                }));
37            args_decl.push(quote! { #(#unprocessed_attributes)* #pat : #arg_type_generated_ident });
38        } else {
39            let ty = &arg.ty;
40            args_decl.push(quote! { #(#unprocessed_attributes)* #pat : #ty });
41        }
42    }
43
44    args_decl
45}
46
47pub fn generate_proxy_method_sig(
48    method: &Method,
49    return_type: proc_macro2::TokenStream,
50) -> proc_macro2::TokenStream {
51    let method_name = &method.name;
52    let mut generics = method.generics.clone();
53    let generics_where = &method.generics.where_clause;
54    let arg_decl = proxy_arg_gen(&method.method_args, &mut generics);
55    let result = quote! {
56        fn #method_name #generics (
57            &mut self,
58            #(#arg_decl),*
59        ) -> #return_type
60        #generics_where
61    };
62    result
63}
64
65fn original_type_tokens(m: &Method) -> proc_macro2::TokenStream {
66    match &m.return_type {
67        syn::ReturnType::Default => quote! { () },
68        syn::ReturnType::Type(_, ty) => quote! { #ty },
69    }
70}
71
72pub fn generate_proxy_endpoint(m: &Method, endpoint_name: String) -> proc_macro2::TokenStream {
73    let mut token_count = 0;
74    let mut token_expr =
75        quote! { multiversx_sc::types::EgldOrEsdtTokenIdentifier::<Self::Api>::egld() };
76    let mut nonce_count = 0;
77    let mut nonce_expr = quote! { 0u64 };
78    let mut payment_count = 0;
79    let mut payment_expr = quote! { multiversx_sc::types::BigUint::<Self::Api>::zero() };
80    let mut multi_count = 0;
81    let mut multi_expr_opt = None;
82
83    let mut arg_push_snippets = Vec::<proc_macro2::TokenStream>::new();
84
85    for arg in &m.method_args {
86        match &arg.metadata.payment {
87            ArgPaymentMetadata::NotPayment => {
88                let pat = &arg.pat;
89                arg_push_snippets.push(quote! {
90                    .argument(&#pat)
91                });
92            }
93            ArgPaymentMetadata::PaymentToken => {
94                token_count += 1;
95                let pat = &arg.pat;
96                token_expr = quote! { #pat };
97            }
98            ArgPaymentMetadata::PaymentNonce => {
99                nonce_count += 1;
100                let pat = &arg.pat;
101                nonce_expr = quote! { #pat };
102            }
103            ArgPaymentMetadata::PaymentAmount => {
104                payment_count += 1;
105                let pat = &arg.pat;
106                payment_expr = quote! { #pat };
107            }
108            ArgPaymentMetadata::PaymentMulti => {
109                multi_count += 1;
110                let pat = &arg.pat;
111                multi_expr_opt = Some(quote! { #pat });
112            }
113        }
114    }
115
116    assert!(
117        payment_count <= 1,
118        "No more than one payment argument allowed in call proxy"
119    );
120    assert!(
121        token_count <= 1,
122        "No more than one payment token argument allowed in call proxy"
123    );
124    assert!(
125        nonce_count <= 1,
126        "No more than one payment nonce argument allowed in call proxy"
127    );
128    assert!(
129        multi_count <= 1,
130        "No more than one payment multi argument allowed in call proxy"
131    );
132
133    let payment_type;
134    let payment_init;
135    if token_count > 0 || nonce_count > 0 || payment_count > 0 {
136        assert!(multi_count == 0, "#[payment_multi] cannot coexist with any other payment annotation in the same endpoint");
137
138        if token_count == 0 && nonce_count == 0 {
139            payment_type = quote! { multiversx_sc::types::EgldPayment<Self::Api> };
140            payment_init = quote! { .egld(#payment_expr) };
141        } else {
142            payment_type = quote! { multiversx_sc::types::EgldOrEsdtTokenPayment<Self::Api> };
143            payment_init = quote! { .payment(
144                multiversx_sc::types::EgldOrEsdtTokenPayment::new(
145                    #token_expr,
146                    #nonce_expr,
147                    #payment_expr,
148                )
149            )};
150        }
151    } else if multi_count > 0 {
152        let multi_expr = multi_expr_opt.unwrap();
153        payment_type = quote! { MultiEsdtPayment<Self::Api> };
154        payment_init = quote! { .multi_esdt(#multi_expr.clone_value()) };
155    } else {
156        payment_type = quote! { () };
157        payment_init = quote! {};
158    }
159
160    let original_type = original_type_tokens(m);
161    let return_type = quote! {
162        multiversx_sc::types::Tx<
163            multiversx_sc::types::TxScEnv<Self::Api>,
164            (),
165            Self::To,
166            #payment_type,
167            (),
168            multiversx_sc::types::FunctionCall<Self::Api>,
169            multiversx_sc::types::OriginalResultMarker<#original_type>,
170        >
171    };
172
173    let msig = generate_proxy_method_sig(m, return_type);
174
175    let sig = quote! {
176        #[allow(clippy::too_many_arguments)]
177        #[allow(clippy::type_complexity)]
178        #msig {
179            multiversx_sc::types::TxBaseWithEnv::new_tx_from_sc()
180                .to(self.extract_proxy_to())
181                .original_result()
182                .raw_call(#endpoint_name)
183                #payment_init
184                #(#arg_push_snippets)*
185        }
186    };
187
188    sig
189}
190
191pub fn generate_proxy_deploy(init_method: &Method) -> proc_macro2::TokenStream {
192    let mut payment_count = 0;
193    let mut multi_count = 0;
194    let mut token_count = 0;
195    let mut nonce_count = 0;
196
197    let mut payment_type = quote! { () };
198    let mut payment_init = quote! {};
199
200    let mut arg_push_snippets = Vec::<proc_macro2::TokenStream>::new();
201
202    for arg in &init_method.method_args {
203        match &arg.metadata.payment {
204            ArgPaymentMetadata::NotPayment => {
205                let pat = &arg.pat;
206                arg_push_snippets.push(quote! {
207                    .argument(&#pat)
208                });
209            }
210            ArgPaymentMetadata::PaymentToken => {
211                token_count += 1;
212            }
213            ArgPaymentMetadata::PaymentNonce => {
214                nonce_count += 1;
215            }
216            ArgPaymentMetadata::PaymentAmount => {
217                payment_count += 1;
218                let payment_expr = &arg.pat;
219                payment_type = quote! { multiversx_sc::types::EgldPayment<Self::Api> };
220                payment_init = quote! { .egld(#payment_expr) };
221            }
222            ArgPaymentMetadata::PaymentMulti => {
223                multi_count += 1;
224            }
225        }
226    }
227
228    assert!(
229        payment_count <= 1,
230        "No more than one payment argument allowed in call proxy"
231    );
232    assert!(token_count == 0, "No ESDT payment allowed in #[init]");
233    assert!(nonce_count == 0, "No SFT/NFT payment allowed in #[init]");
234    assert!(
235        multi_count == 0,
236        "No multi ESDT payments allowed in #[init]"
237    );
238
239    let original_type = original_type_tokens(init_method);
240    let return_type = quote! {
241        multiversx_sc::types::Tx<
242            multiversx_sc::types::TxScEnv<Self::Api>,
243            (),
244            Self::To, // still accepted, until we separate the upgrade constructor completely
245            #payment_type,
246            (),
247            multiversx_sc::types::DeployCall<multiversx_sc::types::TxScEnv<Self::Api>, ()>,
248            multiversx_sc::types::OriginalResultMarker<#original_type>,
249        >
250    };
251
252    let msig = generate_proxy_method_sig(init_method, return_type);
253
254    let sig = quote! {
255        #[allow(clippy::too_many_arguments)]
256        #[allow(clippy::type_complexity)]
257        #msig {
258            multiversx_sc::types::TxBaseWithEnv::new_tx_from_sc()
259                .raw_deploy()
260                #payment_init
261                #(#arg_push_snippets)*
262                .original_result()
263                .to(self.extract_proxy_to()) // still accepted, until we separate the upgrade constructor completely
264        }
265    };
266
267    sig
268}
269
270pub fn generate_method_impl(contract_trait: &ContractTrait) -> Vec<proc_macro2::TokenStream> {
271    contract_trait
272        .methods
273        .iter()
274        .filter_map(|m| match &m.public_role {
275            PublicRole::Init(_) => Some(generate_proxy_deploy(m)),
276            PublicRole::Upgrade(_) => Some(generate_proxy_endpoint(m, "upgrade".to_string())),
277            PublicRole::Endpoint(endpoint_metadata) => Some(generate_proxy_endpoint(
278                m,
279                endpoint_metadata.public_name.to_string(),
280            )),
281            _ => None,
282        })
283        .collect()
284}
285
286pub fn proxy_trait(contract: &ContractTrait) -> proc_macro2::TokenStream {
287    let proxy_supertrait_decl =
288        supertrait_gen::proxy_supertrait_decl(contract.supertraits.as_slice());
289    let proxy_methods_impl = generate_method_impl(contract);
290    quote! {
291        pub trait ProxyTrait:
292            multiversx_sc::contract_base::ProxyObjBase
293            + Sized
294            #(#proxy_supertrait_decl)*
295        {
296            #(#proxy_methods_impl)*
297        }
298    }
299}
300
301pub fn proxy_obj_code(contract: &ContractTrait) -> proc_macro2::TokenStream {
302    let proxy_object_def = snippets::proxy_object_def();
303    let impl_all_proxy_traits =
304        supertrait_gen::impl_all_proxy_traits(contract.supertraits.as_slice());
305    quote! {
306        #proxy_object_def
307
308        #(#impl_all_proxy_traits)*
309    }
310}
311
312fn equivalent_encode_path_gen(ty: &syn::Type) -> syn::Path {
313    let owned_type = convert_to_owned_type(ty);
314    syn::parse_str(
315        format!(
316            "multiversx_sc::types::ProxyArg<{}>",
317            owned_type.to_token_stream()
318        )
319        .as_str(),
320    )
321    .unwrap()
322}