Skip to main content

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!(
137            multi_count == 0,
138            "#[payment_multi] cannot coexist with any other payment annotation in the same endpoint"
139        );
140
141        if token_count == 0 && nonce_count == 0 {
142            payment_type = quote! { multiversx_sc::types::EgldPayment<Self::Api> };
143            payment_init = quote! { .egld(#payment_expr) };
144        } else {
145            payment_type = quote! { multiversx_sc::types::EgldOrEsdtTokenPayment<Self::Api> };
146            payment_init = quote! { .payment(
147                multiversx_sc::types::EgldOrEsdtTokenPayment::new(
148                    #token_expr,
149                    #nonce_expr,
150                    #payment_expr,
151                )
152            )};
153        }
154    } else if multi_count > 0 {
155        let multi_expr = multi_expr_opt.unwrap();
156        payment_type = quote! { EsdtTokenPaymentVec<Self::Api> };
157        payment_init = quote! { .payment(#multi_expr.clone_value()) };
158    } else {
159        payment_type = quote! { () };
160        payment_init = quote! {};
161    }
162
163    let original_type = original_type_tokens(m);
164    let return_type = quote! {
165        multiversx_sc::types::Tx<
166            multiversx_sc::types::TxScEnv<Self::Api>,
167            (),
168            Self::To,
169            #payment_type,
170            (),
171            multiversx_sc::types::FunctionCall<Self::Api>,
172            multiversx_sc::types::OriginalResultMarker<#original_type>,
173        >
174    };
175
176    let msig = generate_proxy_method_sig(m, return_type);
177
178    let sig = quote! {
179        #[allow(clippy::too_many_arguments)]
180        #[allow(clippy::type_complexity)]
181        #msig {
182            multiversx_sc::types::TxBaseWithEnv::new_tx_from_sc()
183                .to(self.extract_proxy_to())
184                .original_result()
185                .raw_call(#endpoint_name)
186                #payment_init
187                #(#arg_push_snippets)*
188        }
189    };
190
191    sig
192}
193
194pub fn generate_proxy_deploy(init_method: &Method) -> proc_macro2::TokenStream {
195    let mut payment_count = 0;
196    let mut multi_count = 0;
197    let mut token_count = 0;
198    let mut nonce_count = 0;
199
200    let mut payment_type = quote! { () };
201    let mut payment_init = quote! {};
202
203    let mut arg_push_snippets = Vec::<proc_macro2::TokenStream>::new();
204
205    for arg in &init_method.method_args {
206        match &arg.metadata.payment {
207            ArgPaymentMetadata::NotPayment => {
208                let pat = &arg.pat;
209                arg_push_snippets.push(quote! {
210                    .argument(&#pat)
211                });
212            }
213            ArgPaymentMetadata::PaymentToken => {
214                token_count += 1;
215            }
216            ArgPaymentMetadata::PaymentNonce => {
217                nonce_count += 1;
218            }
219            ArgPaymentMetadata::PaymentAmount => {
220                payment_count += 1;
221                let payment_expr = &arg.pat;
222                payment_type = quote! { multiversx_sc::types::EgldPayment<Self::Api> };
223                payment_init = quote! { .egld(#payment_expr) };
224            }
225            ArgPaymentMetadata::PaymentMulti => {
226                multi_count += 1;
227            }
228        }
229    }
230
231    assert!(
232        payment_count <= 1,
233        "No more than one payment argument allowed in call proxy"
234    );
235    assert!(token_count == 0, "No ESDT payment allowed in #[init]");
236    assert!(nonce_count == 0, "No SFT/NFT payment allowed in #[init]");
237    assert!(
238        multi_count == 0,
239        "No multi ESDT payments allowed in #[init]"
240    );
241
242    let original_type = original_type_tokens(init_method);
243    let return_type = quote! {
244        multiversx_sc::types::Tx<
245            multiversx_sc::types::TxScEnv<Self::Api>,
246            (),
247            Self::To, // still accepted, until we separate the upgrade constructor completely
248            #payment_type,
249            (),
250            multiversx_sc::types::DeployCall<multiversx_sc::types::TxScEnv<Self::Api>, ()>,
251            multiversx_sc::types::OriginalResultMarker<#original_type>,
252        >
253    };
254
255    let msig = generate_proxy_method_sig(init_method, return_type);
256
257    let sig = quote! {
258        #[allow(clippy::too_many_arguments)]
259        #[allow(clippy::type_complexity)]
260        #msig {
261            multiversx_sc::types::TxBaseWithEnv::new_tx_from_sc()
262                .raw_deploy()
263                #payment_init
264                #(#arg_push_snippets)*
265                .original_result()
266                .to(self.extract_proxy_to()) // still accepted, until we separate the upgrade constructor completely
267        }
268    };
269
270    sig
271}
272
273pub fn generate_method_impl(contract_trait: &ContractTrait) -> Vec<proc_macro2::TokenStream> {
274    contract_trait
275        .methods
276        .iter()
277        .filter_map(|m| match &m.public_role {
278            PublicRole::Init(_) => Some(generate_proxy_deploy(m)),
279            PublicRole::Upgrade(_) => Some(generate_proxy_endpoint(m, "upgrade".to_string())),
280            PublicRole::Endpoint(endpoint_metadata) => Some(generate_proxy_endpoint(
281                m,
282                endpoint_metadata.public_name.to_string(),
283            )),
284            _ => None,
285        })
286        .collect()
287}
288
289pub fn proxy_trait(contract: &ContractTrait) -> proc_macro2::TokenStream {
290    let proxy_supertrait_decl =
291        supertrait_gen::proxy_supertrait_decl(contract.supertraits.as_slice());
292    let proxy_methods_impl = generate_method_impl(contract);
293    quote! {
294        pub trait ProxyTrait:
295            multiversx_sc::contract_base::ProxyObjBase
296            + Sized
297            #(#proxy_supertrait_decl)*
298        {
299            #(#proxy_methods_impl)*
300        }
301    }
302}
303
304pub fn proxy_obj_code(contract: &ContractTrait) -> proc_macro2::TokenStream {
305    let proxy_object_def = snippets::proxy_object_def();
306    let impl_all_proxy_traits =
307        supertrait_gen::impl_all_proxy_traits(contract.supertraits.as_slice());
308    quote! {
309        #proxy_object_def
310
311        #(#impl_all_proxy_traits)*
312    }
313}
314
315fn equivalent_encode_path_gen(ty: &syn::Type) -> syn::Path {
316    let owned_type = convert_to_owned_type(ty);
317    syn::parse_str(
318        format!(
319            "multiversx_sc::types::ProxyArg<{}>",
320            owned_type.to_token_stream()
321        )
322        .as_str(),
323    )
324    .unwrap()
325}