multiversx_sc_meta_lib/contract/sc_config/
contract_variant.rs

1use std::path::{Path, PathBuf};
2
3use super::{contract_variant_builder::default_wasm_crate_name, ContractVariantSettings};
4use crate::{cli::BuildArgs, tools::RustcVersion};
5use multiversx_sc::abi::ContractAbi;
6
7/// Represents a contract created by the framework when building.
8///
9/// It might have only some of the endpoints written by the developer and maybe some other function.
10pub struct ContractVariant {
11    /// Initially, there was only 1 wasm crate, called `wasm`.
12    ///
13    /// Then, we got multi-contracts, but the wasm crate became the "main" one.
14    ///
15    /// Then we removed that mechanism completely, and it remained purely cosmetic,
16    /// being named just `wasm`, instead of `wasm-{contract-name}`.
17    ///
18    /// We are still keeping this around for contracts with only one output contract, for the cosmetic bit.
19    pub wasm_dir_short_name: bool,
20
21    /// The contract id is defined in `multicontract.toml`. It has no effect on the produced assets.
22    ///
23    /// It can be the same as the contract name, but it is not necessary.
24    pub contract_id: String,
25
26    /// The name, as seen in the generated contract names.
27    ///
28    /// It is either defined in the multicontract.toml, or is inferred from the main crate name.
29    pub contract_name: String,
30
31    /// The name of the wasm crate, as it appear in Cargo.toml. It is normally the `contract_name` field, followed by the `-wasm` suffix.
32    ///
33    /// However, the main contract Cargo.toml is given explicitly, so this name might differ.
34    pub wasm_crate_name: String,
35
36    /// Collection of flags, specified in the multicontract config.
37    pub settings: ContractVariantSettings,
38
39    /// Filtered and processed ABI of the output contract.
40    pub abi: ContractAbi,
41}
42
43impl ContractVariant {
44    /// Constructor that is called when no contract variants are configured.
45    ///
46    /// This single resulting variant takes all its data from the original ABI, and uses defaults for everything else.
47    pub fn default_from_abi(original_abi: &ContractAbi) -> Self {
48        let default_contract_config_name = original_abi.build_info.contract_crate.name.to_string();
49        let wasm_crate_name = default_wasm_crate_name(&default_contract_config_name);
50
51        // add the rustc version to the original ABI
52        let mut abi = original_abi.clone();
53        abi.build_info.rustc = Some(RustcVersion::current_version().to_abi());
54
55        ContractVariant {
56            wasm_dir_short_name: true,
57            settings: ContractVariantSettings::default(),
58            contract_id: default_contract_config_name.clone(),
59            contract_name: default_contract_config_name,
60            wasm_crate_name,
61            abi,
62        }
63    }
64
65    pub fn public_name_snake_case(&self) -> String {
66        self.contract_name.replace('-', "_")
67    }
68
69    /// The name of the directory of the wasm crate.
70    ///
71    /// Note this does not necessarily have to match the wasm crate name defined in Cargo.toml.
72    pub fn wasm_crate_dir_name(&self) -> String {
73        if self.wasm_dir_short_name {
74            "wasm".to_string()
75        } else {
76            format!("wasm-{}", &self.contract_name)
77        }
78    }
79
80    pub fn wasm_crate_path(&self) -> PathBuf {
81        Path::new("..").join(self.wasm_crate_dir_name())
82    }
83
84    pub fn cargo_toml_path(&self) -> PathBuf {
85        Path::new(&self.wasm_crate_path()).join("Cargo.toml")
86    }
87
88    pub fn some_other_test_path(&self) -> PathBuf {
89        Path::new(&self.wasm_crate_path()).join("test-Cargo.toml")
90    }
91
92    pub fn wasm_crate_name_snake_case(&self) -> String {
93        self.wasm_crate_name.replace('-', "_")
94    }
95
96    pub fn resolve_wasm_target_dir(&self, explicit_target_dir: &Option<String>) -> PathBuf {
97        let wasm_crate_path = self.wasm_crate_path();
98        if let Some(explicit_target_dir) = explicit_target_dir {
99            // usually the explicit_target_dir is absolute,
100            // but if it isn't, we need to take the path of the wasm crate into account
101            Path::new(&wasm_crate_path).join(explicit_target_dir)
102        } else {
103            Path::new(&wasm_crate_path).join("target")
104        }
105    }
106
107    /// This is where Rust will initially compile the WASM binary.
108    pub fn wasm_compilation_output_path(&self, explicit_target_dir: &Option<String>) -> PathBuf {
109        let target_dir = self.resolve_wasm_target_dir(explicit_target_dir);
110        let wasm_file_name = format!("{}.wasm", &self.wasm_crate_name_snake_case());
111
112        Path::new(&target_dir)
113            .join(&self.settings.rustc_target)
114            .join("release")
115            .join(wasm_file_name)
116    }
117
118    pub fn abi_output_name(&self) -> String {
119        format!("{}.abi.json", &self.contract_name)
120    }
121
122    fn output_name_base(&self, build_args: &BuildArgs) -> String {
123        if let Some(wasm_name_override) = &build_args.wasm_name_override {
124            wasm_name_override.clone()
125        } else if let Some(suffix) = &build_args.wasm_name_suffix {
126            format!("{}-{suffix}", &self.contract_name)
127        } else {
128            self.contract_name.clone()
129        }
130    }
131
132    pub fn wasm_output_name(&self, build_args: &BuildArgs) -> String {
133        format!("{}.wasm", self.output_name_base(build_args))
134    }
135
136    pub fn wat_output_name(&self, build_args: &BuildArgs) -> String {
137        format!("{}.wat", self.output_name_base(build_args))
138    }
139
140    pub fn mxsc_file_output_name(&self, build_args: &BuildArgs) -> String {
141        format!("{}.mxsc.json", self.output_name_base(build_args))
142    }
143
144    pub fn imports_json_output_name(&self, build_args: &BuildArgs) -> String {
145        format!("{}.imports.json", self.output_name_base(build_args))
146    }
147
148    pub fn twiggy_top_name(&self, build_args: &BuildArgs) -> String {
149        format!("twiggy-top-{}.txt", self.output_name_base(build_args))
150    }
151
152    pub fn twiggy_paths_name(&self, build_args: &BuildArgs) -> String {
153        format!("twiggy-paths-{}.txt", self.output_name_base(build_args))
154    }
155
156    pub fn twiggy_monos_name(&self, build_args: &BuildArgs) -> String {
157        format!("twiggy-monos-{}.txt", self.output_name_base(build_args))
158    }
159
160    pub fn twiggy_dominators_name(&self, build_args: &BuildArgs) -> String {
161        format!(
162            "twiggy-dominators-{}.txt",
163            self.output_name_base(build_args)
164        )
165    }
166
167    pub fn endpoint_names(&self) -> Vec<String> {
168        self.abi
169            .endpoints
170            .iter()
171            .map(|endpoint| endpoint.name.to_string())
172            .collect()
173    }
174
175    /// Yields "init" + all endpoint names + "callBack" (if it exists).
176    ///
177    /// Should correspond to all wasm exported functions.
178    pub fn all_exported_function_names(&self) -> Vec<String> {
179        let mut result = vec!["init".to_string()];
180        if !self.abi.upgrade_constructors.is_empty() {
181            result.push("upgrade".to_string())
182        }
183        result.append(&mut self.endpoint_names());
184        if self.abi.has_callback {
185            result.push("callBack".to_string());
186        }
187        result
188    }
189}
190
191impl std::fmt::Debug for ContractVariant {
192    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
193        f.debug_struct("ContractVariant")
194            .field("wasm_dir_short_name", &self.wasm_dir_short_name)
195            .field("config_name", &self.contract_id)
196            .field("public_name", &self.contract_name)
197            .field("num-constructors", &self.abi.constructors.len())
198            .field(
199                "num-upgrade-constructors",
200                &self.abi.upgrade_constructors.len(),
201            )
202            .field("num-endpoints", &self.abi.endpoints.len())
203            .field("settings", &self.settings)
204            .finish()
205    }
206}