1use core::panic;
2use multiversx_sc::abi::{ContractAbi, EndpointAbi};
3use std::{
4 collections::{BTreeSet, HashMap, HashSet},
5 fs,
6 path::{Path, PathBuf},
7};
8
9use crate::{
10 ei::parse_check_ei,
11 print_util::print_sc_config_main_deprecated,
12 tools::{self, OpcodeVersion, RustcVersion},
13};
14
15use super::{
16 contract_variant_settings::{parse_allocator, parse_stack_size},
17 proxy_config::ProxyConfig,
18 sc_config_model::SC_CONFIG_FILE_NAMES,
19 ContractVariant, ContractVariantProfile, ContractVariantSerde, ContractVariantSettings,
20 ProxyConfigSerde, ScConfig, ScConfigSerde,
21};
22
23struct ContractVariantBuilder {
25 pub contract_id: String,
26 pub explicit_name: String,
27 pub add_unlabelled: bool,
28 pub add_labels: BTreeSet<String>,
29 pub add_endpoints: BTreeSet<String>,
30 pub collected_endpoints: Vec<EndpointAbi>,
31 endpoint_names: HashSet<String>, pub settings: ContractVariantSettings,
33}
34
35impl Default for ContractVariantBuilder {
36 fn default() -> Self {
37 Self {
38 contract_id: Default::default(),
39 explicit_name: Default::default(),
40 add_unlabelled: true,
41 add_labels: Default::default(),
42 add_endpoints: Default::default(),
43 collected_endpoints: Default::default(),
44 endpoint_names: Default::default(),
45 settings: Default::default(),
46 }
47 }
48}
49
50impl ContractVariantBuilder {
51 fn new(id: String) -> Self {
52 ContractVariantBuilder {
53 contract_id: id.clone(),
54 explicit_name: id,
55 ..Default::default()
56 }
57 }
58
59 fn map_from_config(kvp: (&String, &ContractVariantSerde)) -> (String, ContractVariantBuilder) {
60 let (contract_id, cms) = kvp;
61 let external_view = cms.external_view.unwrap_or_default();
62 let mut collected_endpoints = Vec::new();
63 if external_view {
64 collected_endpoints.push(
65 multiversx_sc::external_view_contract::external_view_contract_constructor_abi(),
66 )
67 }
68 let default = ContractVariantBuilder::default();
69 (
70 contract_id.clone(),
71 ContractVariantBuilder {
72 contract_id: contract_id.clone(),
73 explicit_name: cms.name.clone().unwrap_or(default.explicit_name),
74 add_unlabelled: cms.add_unlabelled.unwrap_or(default.add_unlabelled),
75 add_labels: cms.add_labels.iter().cloned().collect(),
76 add_endpoints: cms.add_endpoints.iter().cloned().collect(),
77 collected_endpoints,
78 settings: ContractVariantSettings {
79 external_view: cms.external_view.unwrap_or(default.settings.external_view),
80 panic_message: cms.panic_message.unwrap_or(default.settings.panic_message),
81 check_ei: parse_check_ei(&cms.ei),
82 allocator: parse_allocator(&cms.allocator),
83 stack_size: parse_stack_size(&cms.stack_size),
84 features: cms.features.clone(),
85 default_features: cms.default_features,
86 kill_legacy_callback: cms.kill_legacy_callback,
87 profile: ContractVariantProfile::from_serde(&cms.profile),
88 std: cms.std.unwrap_or(default.settings.std),
89 rustc_version: RustcVersion::from_opt_sc_config_serde(&cms.rustc_version),
90 rustc_target: cms
91 .rustc_target
92 .clone()
93 .unwrap_or_else(|| tools::build_target::default_target().to_owned()),
94 opcode_version: cms.opcode_version.as_ref().map_or(
95 default.settings.opcode_version,
96 |v| OpcodeVersion::from_settings_str(v).expect("Invalid opcode version in contract variant settings; allowed values are '1' and '2'")
97 ),
98 wasm_opt_version: cms.wasm_opt_version.clone(),
99 },
100 ..default
101 },
102 )
103 }
104
105 fn wasm_name(&self) -> &String {
106 if !self.explicit_name.is_empty() {
107 &self.explicit_name
108 } else {
109 &self.contract_id
110 }
111 }
112
113 fn collect_endpoint(&mut self, endpoint_abi: &EndpointAbi) {
114 if !self.endpoint_names.contains(&endpoint_abi.name) {
115 self.endpoint_names.insert(endpoint_abi.name.clone());
116 self.collected_endpoints.push(endpoint_abi.clone());
117 }
118 }
119}
120
121fn process_labels_for_contracts(
122 contract_builders: &mut HashMap<String, ContractVariantBuilder>,
123 labels_for_contracts: &HashMap<String, Vec<String>>,
124) {
125 for (label, targets) in labels_for_contracts {
126 for target in targets {
127 contract_builders
128 .entry(target.clone())
129 .or_insert_with(|| ContractVariantBuilder::new(target.clone()))
130 .add_labels
131 .insert(label.clone());
132 }
133 }
134}
135
136fn endpoint_unlabelled(endpoint_abi: &EndpointAbi) -> bool {
137 endpoint_abi.labels.is_empty()
138}
139
140fn endpoint_matches_labels(endpoint_abi: &EndpointAbi, labels: &BTreeSet<String>) -> bool {
141 endpoint_abi
142 .labels
143 .iter()
144 .any(|endpoint_label| labels.contains(endpoint_label))
145}
146
147fn collect_unlabelled_endpoints(
148 contract_builders: &mut HashMap<String, ContractVariantBuilder>,
149 original_abi: &ContractAbi,
150) {
151 for builder in contract_builders.values_mut() {
152 if builder.add_unlabelled {
153 for endpoint_abi in original_abi.iter_all_exports() {
154 if endpoint_unlabelled(endpoint_abi) {
155 builder.collect_endpoint(endpoint_abi);
156 }
157 }
158 }
159 }
160}
161
162fn collect_labelled_endpoints(
163 contract_builders: &mut HashMap<String, ContractVariantBuilder>,
164 original_abi: &ContractAbi,
165) {
166 for builder in contract_builders.values_mut() {
167 for endpoint_abi in original_abi.iter_all_exports() {
168 if endpoint_matches_labels(endpoint_abi, &builder.add_labels) {
169 builder.collect_endpoint(endpoint_abi);
170 }
171 }
172 }
173}
174
175fn collect_add_endpoints(
176 contract_builders: &mut HashMap<String, ContractVariantBuilder>,
177 original_abi: &ContractAbi,
178) {
179 for builder in contract_builders.values_mut() {
180 for endpoint_abi in original_abi.iter_all_exports() {
181 if builder.add_endpoints.contains(&endpoint_abi.name) {
182 builder.collect_endpoint(endpoint_abi);
183 }
184 }
185 }
186}
187
188fn build_contract_abi(builder: ContractVariantBuilder, original_abi: &ContractAbi) -> ContractAbi {
189 let mut build_info = original_abi.build_info.clone();
190 build_info.rustc = Some(builder.settings.rustc_version.to_abi());
191 let mut constructors = Vec::new();
192 let mut upgrade_constructors = Vec::new();
193 let mut endpoints = Vec::new();
194 let mut promise_callbacks = Vec::new();
195 for endpoint_abi in builder.collected_endpoints {
196 match endpoint_abi.endpoint_type {
197 multiversx_sc::abi::EndpointTypeAbi::Init => constructors.push(endpoint_abi),
198 multiversx_sc::abi::EndpointTypeAbi::Upgrade => upgrade_constructors.push(endpoint_abi),
199 multiversx_sc::abi::EndpointTypeAbi::Endpoint => endpoints.push(endpoint_abi),
200 multiversx_sc::abi::EndpointTypeAbi::PromisesCallback => {
201 promise_callbacks.push(endpoint_abi)
202 }
203 }
204 }
205 let has_callback = original_abi.has_callback
206 && !builder.settings.external_view
207 && !builder.settings.kill_legacy_callback;
208 ContractAbi {
209 build_info,
210 docs: original_abi.docs.clone(),
211 name: original_abi.name.clone(),
212 constructors,
213 upgrade_constructors,
214 endpoints,
215 promise_callbacks,
216 events: original_abi.events.clone(),
217 has_callback,
218 type_descriptions: original_abi.type_descriptions.clone(),
219 esdt_attributes: original_abi.esdt_attributes.clone(),
220 }
221}
222
223pub(crate) fn default_wasm_crate_name(contract_name: &str) -> String {
224 format!("{contract_name}-wasm")
225}
226
227fn build_contract(builder: ContractVariantBuilder, original_abi: &ContractAbi) -> ContractVariant {
228 let contract_name = builder.wasm_name().clone();
229 let wasm_crate_name = default_wasm_crate_name(&contract_name);
230 ContractVariant {
231 wasm_dir_short_name: false,
232 settings: builder.settings.clone(),
233 contract_id: builder.contract_id.clone(),
234 contract_name,
235 wasm_crate_name,
236 abi: build_contract_abi(builder, original_abi),
237 }
238}
239
240fn set_wasm_dir_short_name(contracts: &mut [ContractVariant]) {
241 if contracts.len() != 1 {
242 return;
243 }
244 if let Some(first_contract) = contracts.first_mut() {
245 first_contract.wasm_dir_short_name = true;
246 }
247}
248
249fn process_contracts(config: &ScConfigSerde, original_abi: &ContractAbi) -> Vec<ContractVariant> {
250 let mut contract_builders: HashMap<String, ContractVariantBuilder> = config
251 .contracts
252 .iter()
253 .map(ContractVariantBuilder::map_from_config)
254 .collect();
255
256 collect_and_process_endpoints(
257 &mut contract_builders,
258 original_abi,
259 &config.labels_for_contracts,
260 );
261
262 let mut contracts: Vec<ContractVariant> = contract_builders
263 .into_values()
264 .map(|builder| build_contract(builder, original_abi))
265 .collect();
266
267 if contracts.is_empty() {
268 contracts.push(ContractVariant::default_from_abi(original_abi));
269 }
270 set_wasm_dir_short_name(&mut contracts);
271
272 contracts
273}
274
275fn process_proxy_contracts(config: &ScConfigSerde, original_abi: &ContractAbi) -> Vec<ProxyConfig> {
276 let mut proxy_contracts = Vec::new();
277
278 proxy_contracts.push(ProxyConfig::output_dir_proxy_config(original_abi.clone()));
279
280 for proxy_config in &config.proxy {
281 let mut contract_builders = HashMap::new();
282
283 match &proxy_config.variant {
284 Some(variant) => {
285 let setting_contract = config
286 .contracts
287 .iter()
288 .find(|setting| setting.0.eq(variant))
289 .unwrap_or_else(|| panic!("No contact with this name"));
290 let (contract_id, mut contract_builder) =
291 ContractVariantBuilder::map_from_config(setting_contract);
292 alter_builder_with_proxy_config(proxy_config, &mut contract_builder);
293
294 contract_builders = HashMap::from([(contract_id, contract_builder)]);
295 }
296 None => {
297 let mut contract_builder = ContractVariantBuilder::default();
298 alter_builder_with_proxy_config(proxy_config, &mut contract_builder);
299
300 contract_builders.insert(
301 proxy_config.path.to_string_lossy().to_string(),
302 contract_builder,
303 );
304 }
305 }
306
307 collect_and_process_endpoints(
308 &mut contract_builders,
309 original_abi,
310 &config.labels_for_contracts,
311 );
312 if let Some((_, builder)) = contract_builders.into_iter().next() {
313 let contract = build_contract(builder, original_abi);
314
315 proxy_contracts.push(ProxyConfig::new(
316 PathBuf::from(&proxy_config.path),
317 proxy_config.override_import.to_owned(),
318 proxy_config.path_rename.to_owned(),
319 contract.abi,
320 ));
321 }
322 }
323
324 proxy_contracts
325}
326
327impl ScConfig {
328 pub fn load_from_config(
332 path: &Path,
333 config: &ScConfigSerde,
334 original_abi: &ContractAbi,
335 ) -> Self {
336 if config.settings.main.is_some() {
337 print_sc_config_main_deprecated(path);
338 }
339
340 ScConfig {
341 contracts: process_contracts(config, original_abi),
342 proxy_configs: process_proxy_contracts(config, original_abi),
343 }
344 }
345}
346
347fn alter_builder_with_proxy_config(
348 proxy_config: &ProxyConfigSerde,
349 contract_builder: &mut ContractVariantBuilder,
350) {
351 let default = ContractVariantBuilder::default();
352
353 contract_builder.add_unlabelled = proxy_config
354 .add_unlabelled
355 .unwrap_or(default.add_unlabelled);
356 contract_builder.add_endpoints = proxy_config.add_endpoints.iter().cloned().collect();
357 contract_builder.add_labels = proxy_config.add_labels.iter().cloned().collect();
358}
359
360fn collect_and_process_endpoints(
361 contract_builders: &mut HashMap<String, ContractVariantBuilder>,
362 original_abi: &ContractAbi,
363 labels_for_contracts: &HashMap<String, Vec<String>>,
364) {
365 collect_unlabelled_endpoints(contract_builders, original_abi);
366 collect_labelled_endpoints(contract_builders, original_abi);
367 collect_add_endpoints(contract_builders, original_abi);
368 process_labels_for_contracts(contract_builders, labels_for_contracts);
369}
370
371impl ScConfig {
372 pub fn default_config(original_abi: &ContractAbi) -> Self {
376 let default_contract_config_name = original_abi.build_info.contract_crate.name.to_string();
377 let wasm_crate_name = default_wasm_crate_name(&default_contract_config_name);
378 ScConfig {
379 contracts: vec![ContractVariant {
380 wasm_dir_short_name: true,
381 settings: ContractVariantSettings::default(),
382 contract_id: default_contract_config_name.clone(),
383 contract_name: default_contract_config_name,
384 wasm_crate_name,
385 abi: original_abi.clone(),
386 }],
387 proxy_configs: Vec::new(),
388 }
389 }
390
391 pub fn load_from_file<P: AsRef<Path>>(path: P, original_abi: &ContractAbi) -> Option<Self> {
393 match fs::read_to_string(path.as_ref()) {
394 Ok(raw_contents) => {
395 let config_serde: ScConfigSerde = toml::from_str(&raw_contents)
396 .unwrap_or_else(|error| panic!("error parsing multicontract.toml: {error}"));
397 Some(Self::load_from_config(
398 path.as_ref(),
399 &config_serde,
400 original_abi,
401 ))
402 }
403 Err(_) => None,
404 }
405 }
406
407 pub fn load_from_files_or_default<I, P>(paths: I, original_abi: &ContractAbi) -> Self
409 where
410 P: AsRef<Path>,
411 I: Iterator<Item = P>,
412 {
413 for path in paths {
414 if let Some(config) = Self::load_from_file(path.as_ref(), original_abi) {
415 return config;
416 }
417 }
418
419 Self::default_config(original_abi)
420 }
421
422 pub fn load_from_crate_or_default<P>(contract_crate_path: P, original_abi: &ContractAbi) -> Self
424 where
425 P: AsRef<Path>,
426 {
427 Self::load_from_files_or_default(
428 SC_CONFIG_FILE_NAMES
429 .iter()
430 .map(|name| PathBuf::from(contract_crate_path.as_ref()).join(name)),
431 original_abi,
432 )
433 }
434}