multiversx_sc_meta_lib/contract/generate_snippets/
snippet_sc_functions_gen.rs

1use std::{fs::File, io::Write, path::Path};
2
3use multiversx_sc::abi::{ContractAbi, EndpointAbi, EndpointMutabilityAbi, InputAbi};
4
5use super::{snippet_gen_common::write_newline, snippet_type_map::map_abi_type_to_rust_type};
6
7const DEFAULT_GAS: &str = "30_000_000u64";
8
9pub(crate) fn write_interact_struct_impl(file: &mut File, abi: &ContractAbi, crate_name: &str) {
10    let crate_path = crate_name.replace("_", "-");
11    let mxsc_file_name = format!("{crate_path}.mxsc.json");
12    let wasm_output_file_path = Path::new("..").join("output").join(mxsc_file_name);
13    let proxy_file_name = format!("{}_proxy", crate_name.replace("-", "_"));
14    let proxy = format!("{}::{}Proxy", proxy_file_name, abi.name);
15
16    let wasm_output_file_path_expr =
17        format!("\"mxsc:{}\"", &wasm_output_file_path.to_string_lossy());
18
19    writeln!(
20        file,
21        r#"impl ContractInteract {{
22    pub async fn new(config: Config) -> Self {{
23        let mut interactor = Interactor::new(config.gateway_uri())
24            .await
25            .use_chain_simulator(config.use_chain_simulator());
26
27        interactor.set_current_dir_from_workspace("{}");
28        let wallet_address = interactor.register_wallet(test_wallets::alice()).await;
29
30        // Useful in the chain simulator setting
31        // generate blocks until ESDTSystemSCAddress is enabled
32        interactor.generate_blocks_until_all_activations().await;
33        
34        let contract_code = BytesValue::interpret_from(
35            {},
36            &InterpreterContext::default(),
37        );
38
39        ContractInteract {{
40            interactor,
41            wallet_address,
42            contract_code,
43            state: State::load_state()
44        }}
45    }}
46"#,
47        crate_path, wasm_output_file_path_expr,
48    )
49    .unwrap();
50
51    write_deploy_method_impl(file, &proxy, &abi.constructors[0]);
52
53    for upgrade_abi in &abi.upgrade_constructors {
54        write_upgrade_endpoint_impl(file, &proxy, upgrade_abi);
55    }
56
57    for endpoint_abi in &abi.endpoints {
58        write_endpoint_impl(file, &proxy, endpoint_abi);
59    }
60
61    // close impl block brackets
62    writeln!(file, "}}").unwrap();
63}
64
65fn write_deploy_method_impl(file: &mut File, proxy: &str, init_abi: &EndpointAbi) {
66    write_method_declaration(file, "deploy");
67    write_endpoint_args_declaration(file, &init_abi.inputs);
68
69    writeln!(
70        file,
71        r#"        let new_address = self
72            .interactor
73            .tx()
74            .from(&self.wallet_address)
75            .gas({DEFAULT_GAS})
76            .typed({proxy})
77            .init({})
78            .code(&self.contract_code)
79            .returns(ReturnsNewAddress)
80            .run()
81            .await;
82        let new_address_bech32 = new_address.to_bech32_default();
83        println!("new address: {{new_address_bech32}}");
84        self.state.set_address(new_address_bech32);"#,
85        endpoint_args_when_called(init_abi.inputs.as_slice()),
86    )
87    .unwrap();
88
89    // close method block brackets
90    writeln!(file, "    }}").unwrap();
91    write_newline(file);
92}
93
94fn write_upgrade_endpoint_impl(file: &mut File, proxy: &str, upgrade_abi: &EndpointAbi) {
95    write_method_declaration(file, "upgrade");
96    write_endpoint_args_declaration(file, &upgrade_abi.inputs);
97
98    writeln!(
99        file,
100        r#"        let response = self
101            .interactor
102            .tx()
103            .to(self.state.current_address())
104            .from(&self.wallet_address)
105            .gas({DEFAULT_GAS})
106            .typed({proxy})
107            .upgrade({})
108            .code(&self.contract_code)
109            .code_metadata(CodeMetadata::UPGRADEABLE)
110            .returns(ReturnsResultUnmanaged)
111            .run()
112            .await;
113
114        println!("Result: {{response:?}}");"#,
115        endpoint_args_when_called(upgrade_abi.inputs.as_slice()),
116    )
117    .unwrap();
118
119    // close method block brackets
120    writeln!(file, "    }}").unwrap();
121    write_newline(file);
122}
123
124fn write_endpoint_impl(file: &mut File, proxy: &str, endpoint_abi: &EndpointAbi) {
125    write_method_declaration(file, &endpoint_abi.rust_method_name);
126    write_payments_declaration(file, &endpoint_abi.payable_in_tokens);
127    write_endpoint_args_declaration(file, &endpoint_abi.inputs);
128    if matches!(endpoint_abi.mutability, EndpointMutabilityAbi::Readonly) {
129        write_contract_query(file, proxy, endpoint_abi);
130    } else {
131        write_contract_call(file, proxy, endpoint_abi);
132    }
133
134    // close method block brackets
135    writeln!(file, "    }}").unwrap();
136    write_newline(file);
137}
138
139fn write_method_declaration(file: &mut File, endpoint_name: &str) {
140    writeln!(file, "    pub async fn {endpoint_name}(&mut self) {{").unwrap();
141}
142
143fn write_payments_declaration(file: &mut File, accepted_tokens: &[String]) {
144    if accepted_tokens.is_empty() {
145        return;
146    }
147
148    // only handle EGLD and "any" case, as they're the most common
149    let biguint_default = map_abi_type_to_rust_type("BigUint".to_string());
150    let first_accepted = &accepted_tokens[0];
151    if first_accepted == "EGLD" {
152        writeln!(
153            file,
154            "        let egld_amount = {};",
155            biguint_default.get_default_value_expr()
156        )
157        .unwrap();
158    } else {
159        writeln!(
160            file,
161            "        let token_id = String::new();
162        let token_nonce = 0u64;
163        let token_amount = {};",
164            biguint_default.get_default_value_expr()
165        )
166        .unwrap();
167    }
168
169    write_newline(file);
170}
171
172fn write_endpoint_args_declaration(file: &mut File, inputs: &[InputAbi]) {
173    if inputs.is_empty() {
174        return;
175    }
176
177    for input in inputs {
178        let rust_type = map_abi_type_to_rust_type(input.type_names.abi.clone());
179        writeln!(
180            file,
181            "        let {} = {};",
182            input.arg_name,
183            rust_type.get_default_value_expr()
184        )
185        .unwrap();
186    }
187
188    write_newline(file);
189}
190
191fn endpoint_args_when_called(inputs: &[InputAbi]) -> String {
192    let mut result = String::new();
193    for input in inputs {
194        if !result.is_empty() {
195            result.push_str(", ");
196        }
197        result.push_str(&input.arg_name);
198    }
199    result
200}
201
202fn write_contract_call(file: &mut File, proxy: &str, endpoint_abi: &EndpointAbi) {
203    let payment_snippet = if endpoint_abi.payable_in_tokens.is_empty() {
204        ""
205    } else if endpoint_abi.payable_in_tokens[0] == "EGLD" {
206        "\n            .egld(egld_amount)"
207    } else {
208        "\n            .payment((EsdtTokenIdentifier::from(token_id.as_str()), token_nonce, token_amount))"
209    };
210
211    writeln!(
212        file,
213        r#"        let response = self
214            .interactor
215            .tx()
216            .from(&self.wallet_address)
217            .to(self.state.current_address())
218            .gas({DEFAULT_GAS})
219            .typed({proxy})
220            .{}({}){}
221            .returns(ReturnsResultUnmanaged)
222            .run()
223            .await;
224
225        println!("Result: {{response:?}}");"#,
226        endpoint_abi.rust_method_name,
227        endpoint_args_when_called(endpoint_abi.inputs.as_slice()),
228        payment_snippet,
229    )
230    .unwrap();
231}
232
233fn write_contract_query(file: &mut File, proxy: &str, endpoint_abi: &EndpointAbi) {
234    writeln!(
235        file,
236        r#"        let result_value = self
237            .interactor
238            .query()
239            .to(self.state.current_address())
240            .typed({proxy})
241            .{}({})
242            .returns(ReturnsResultUnmanaged)
243            .run()
244            .await;
245
246        println!("Result: {{result_value:?}}");"#,
247        endpoint_abi.rust_method_name,
248        endpoint_args_when_called(endpoint_abi.inputs.as_slice()),
249    )
250    .unwrap();
251}