multiversx_sc_meta_lib/contract/generate_snippets/
snippet_sc_functions_gen.rs1use 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 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 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 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 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 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}