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