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