1use std::{
5 collections::{BTreeMap, BTreeSet, HashSet},
6 io::Write,
7 path::Path,
8};
9
10use fastcrypto::encoding::Base64;
11use serde_reflection::Registry;
12
13use move_binary_format::{
14 CompiledModule,
15 normalized::{self, Type},
16};
17use move_bytecode_utils::{Modules, layout::SerdeLayoutBuilder, module_cache::GetModule};
18use move_compiler::{
19 compiled_unit::AnnotatedCompiledModule,
20 diagnostics::{Diagnostics, report_diagnostics_to_buffer, report_warnings},
21 linters::LINT_WARNING_PREFIX,
22 shared::files::MappedFiles,
23};
24use move_core_types::{
25 account_address::AccountAddress,
26 language_storage::{ModuleId, StructTag},
27};
28use move_package_alt::{MoveFlavor, RootPackage, schema::Environment};
29use move_package_alt_compilation::compiled_package::CompiledPackage as MoveCompiledPackage;
30use move_package_alt_compilation::{
31 build_config::BuildConfig as MoveBuildConfig, build_plan::BuildPlan,
32};
33use move_symbol_pool::Symbol;
34
35use sui_package_alt::{SuiFlavor, testnet_environment};
36use sui_protocol_config::{Chain, ProtocolConfig, ProtocolVersion};
37use sui_types::{
38 BRIDGE_ADDRESS, DEEPBOOK_ADDRESS, MOVE_STDLIB_ADDRESS, SUI_FRAMEWORK_ADDRESS,
39 SUI_SYSTEM_ADDRESS, TypeTag,
40 base_types::ObjectID,
41 error::{SuiError, SuiErrorKind, SuiResult},
42 is_system_package,
43 move_package::{FnInfo, FnInfoKey, FnInfoMap, MovePackage},
44};
45use sui_verifier::verifier as sui_bytecode_verifier;
46
47#[cfg(test)]
48#[path = "unit_tests/build_tests.rs"]
49mod build_tests;
50
51pub mod test_utils {
52 use crate::{BuildConfig, CompiledPackage};
53 use std::path::PathBuf;
54
55 pub async fn compile_basics_package() -> CompiledPackage {
56 compile_example_package("../../examples/move/basics").await
57 }
58
59 pub async fn compile_managed_coin_package() -> CompiledPackage {
60 compile_example_package("../../crates/sui-core/src/unit_tests/data/managed_coin").await
61 }
62
63 pub async fn compile_example_package(relative_path: &str) -> CompiledPackage {
64 let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
65 path.push(relative_path);
66
67 BuildConfig::new_for_testing()
68 .build_async(&path)
69 .await
70 .unwrap()
71 }
72}
73
74#[derive(Debug, Clone)]
76pub struct CompiledPackage {
77 pub package: MoveCompiledPackage,
78 pub published_at: Option<ObjectID>,
80 pub dependency_ids: PackageDependencies,
82}
83
84#[derive(Clone)]
86pub struct BuildConfig {
87 pub config: MoveBuildConfig,
88 pub run_bytecode_verifier: bool,
90 pub print_diags_to_stderr: bool,
92 pub environment: Environment,
95}
96
97impl BuildConfig {
98 pub fn new_for_testing() -> Self {
99 let install_dir = mysten_common::tempdir().unwrap().keep();
100 let config = MoveBuildConfig {
101 default_flavor: Some(move_compiler::editions::Flavor::Sui),
102 install_dir: Some(install_dir),
103 silence_warnings: true,
104 lint_flag: move_package_alt_compilation::lint_flag::LintFlag::LEVEL_NONE,
105 ..MoveBuildConfig::default()
106 };
107 BuildConfig {
108 config,
109 run_bytecode_verifier: true,
110 print_diags_to_stderr: false,
111 environment: testnet_environment(),
112 }
113 }
114
115 pub fn new_for_testing_replace_addresses<I, S>(dep_original_addresses: I) -> Self
116 where
117 I: IntoIterator<Item = (S, ObjectID)>,
118 S: Into<String>,
119 {
120 let mut build_config = Self::new_for_testing();
121 for (addr_name, obj_id) in dep_original_addresses {
122 build_config
123 .config
124 .additional_named_addresses
125 .insert(addr_name.into(), AccountAddress::from(obj_id));
126 }
127 build_config
128 }
129
130 fn fn_info(units: &[AnnotatedCompiledModule]) -> FnInfoMap {
131 let mut fn_info_map = BTreeMap::new();
132 for u in units {
133 let mod_addr = u.named_module.address.into_inner();
134 let mod_is_test = u.attributes.is_test_or_test_only();
135 for (_, s, info) in &u.function_infos {
136 let fn_name = s.as_str().to_string();
137 let is_test = mod_is_test || info.attributes.is_test_or_test_only();
138 fn_info_map.insert(FnInfoKey { fn_name, mod_addr }, FnInfo { is_test });
139 }
140 }
141
142 fn_info_map
143 }
144
145 fn compile_package<W: Write + Send, F: MoveFlavor>(
146 &self,
147 root_pkg: &RootPackage<F>,
148 writer: &mut W,
149 ) -> anyhow::Result<(MoveCompiledPackage, FnInfoMap)> {
150 let mut config = self.config.clone();
151 if config.default_flavor.is_none() {
153 config.default_flavor = Some(move_compiler::editions::Flavor::Sui);
154 }
155 let build_plan = BuildPlan::create(root_pkg, &config)?;
156 let mut fn_info = None;
157 let compiled_pkg = build_plan.compile_with_driver(writer, |compiler| {
158 let (files, units_res) = compiler.build()?;
159 match units_res {
160 Ok((units, warning_diags)) => {
161 decorate_warnings(warning_diags, Some(&files));
162 fn_info = Some(Self::fn_info(&units));
163 Ok((files, units))
164 }
165 Err(error_diags) => {
166 assert!(!error_diags.is_empty());
169 let diags_buf =
170 report_diagnostics_to_buffer(&files, error_diags, true);
171 if let Err(err) = std::io::stderr().write_all(&diags_buf) {
172 anyhow::bail!("Cannot output compiler diagnostics: {}", err);
173 }
174 anyhow::bail!("Compilation error");
175 }
176 }
177 })?;
178 Ok((compiled_pkg, fn_info.unwrap()))
179 }
180
181 pub async fn build_async(self, path: &Path) -> anyhow::Result<CompiledPackage> {
182 let mut root_pkg = self
183 .config
184 .package_loader(path, &self.environment)
185 .load()
186 .await?;
187
188 self.internal_build(&mut root_pkg)
189 }
190
191 pub async fn build_async_from_root_pkg(
192 self,
193 root_pkg: &mut RootPackage<SuiFlavor>,
194 ) -> anyhow::Result<CompiledPackage> {
195 self.internal_build(root_pkg)
196 }
197
198 pub fn build(self, path: &Path) -> anyhow::Result<CompiledPackage> {
201 let mut root_pkg = self
203 .config
204 .package_loader(path, &self.environment)
205 .load_sync()?;
206
207 self.internal_build(&mut root_pkg)
208 }
209
210 fn internal_build(
211 self,
212 root_pkg: &mut RootPackage<SuiFlavor>,
213 ) -> anyhow::Result<CompiledPackage> {
214 let result = if self.print_diags_to_stderr {
215 self.compile_package(root_pkg, &mut std::io::stderr())
216 } else {
217 self.compile_package(root_pkg, &mut std::io::sink())
218 };
219
220 let (package, fn_info) = result.map_err(|error| {
221 SuiError::from(SuiErrorKind::ModuleBuildFailure {
222 error: format!("{:?}", error),
224 })
225 })?;
226
227 if self.run_bytecode_verifier {
228 verify_bytecode(&package, &fn_info)?;
229 }
230
231 let dependency_ids = PackageDependencies::new(root_pkg)?;
232 let published_at = root_pkg
233 .publication()
234 .map(|p| ObjectID::from_address(p.addresses.published_at.0));
235
236 root_pkg.save_lockfile_to_disk()?;
237
238 Ok(CompiledPackage {
239 package,
240 dependency_ids,
241 published_at,
242 })
243 }
244}
245
246pub fn decorate_warnings(warning_diags: Diagnostics, files: Option<&MappedFiles>) {
249 let any_linter_warnings = warning_diags.any_with_prefix(LINT_WARNING_PREFIX);
250 let (filtered_diags_num, unique) =
251 warning_diags.filtered_source_diags_with_prefix(LINT_WARNING_PREFIX);
252 if let Some(f) = files {
253 report_warnings(f, warning_diags);
254 }
255 if any_linter_warnings {
256 eprintln!("Please report feedback on the linter warnings at https://forums.sui.io\n");
257 }
258 if filtered_diags_num > 0 {
259 eprintln!(
260 "Total number of linter warnings suppressed: {filtered_diags_num} (unique lints: {unique})"
261 );
262 }
263}
264
265fn verify_bytecode(package: &MoveCompiledPackage, fn_info: &FnInfoMap) -> SuiResult<()> {
267 let compiled_modules = package.root_modules_map();
268 let verifier_config = ProtocolConfig::get_for_version(ProtocolVersion::MAX, Chain::Unknown)
269 .verifier_config(None);
270
271 for m in compiled_modules.iter_modules() {
272 move_bytecode_verifier::verify_module_unmetered(m).map_err(|err| {
273 SuiErrorKind::ModuleVerificationFailure {
274 error: err.to_string(),
275 }
276 })?;
277 sui_bytecode_verifier::sui_verify_module_unmetered(m, fn_info, &verifier_config)?;
278 }
279 Ok(())
282}
283
284impl CompiledPackage {
285 pub fn get_modules(&self) -> impl Iterator<Item = &CompiledModule> {
289 self.package.root_modules().map(|m| &m.unit.module)
290 }
291
292 pub fn into_modules(self) -> Vec<CompiledModule> {
296 self.package
297 .root_compiled_units
298 .into_iter()
299 .map(|m| m.unit.module)
300 .collect()
301 }
302
303 pub fn get_dependent_modules(&self) -> impl Iterator<Item = &CompiledModule> {
306 self.package
307 .deps_compiled_units
308 .iter()
309 .map(|(_, m)| &m.unit.module)
310 }
311
312 pub fn get_modules_and_deps(&self) -> impl Iterator<Item = &CompiledModule> {
315 self.package
316 .all_compiled_units_with_source()
317 .map(|m| &m.unit.module)
318 }
319
320 pub fn get_dependency_sorted_modules(
325 &self,
326 with_unpublished_deps: bool,
327 ) -> Vec<CompiledModule> {
328 let all_modules = Modules::new(self.get_modules_and_deps());
329
330 let modules = all_modules.compute_topological_order().unwrap();
332
333 if with_unpublished_deps {
334 modules
337 .filter(|module| module.address() == &AccountAddress::ZERO)
338 .cloned()
339 .collect()
340 } else {
341 let self_modules: HashSet<_> = self
346 .package
347 .root_modules_map()
348 .iter_modules()
349 .iter()
350 .map(|m| m.self_id())
351 .collect();
352
353 modules
354 .filter(|module| self_modules.contains(&module.self_id()))
355 .cloned()
356 .collect()
357 }
358 }
359
360 pub fn get_dependency_storage_package_ids(&self) -> Vec<ObjectID> {
363 self.dependency_ids.published.values().cloned().collect()
364 }
365
366 pub fn get_package_digest(&self, with_unpublished_deps: bool) -> [u8; 32] {
368 let hash_modules = true;
369 MovePackage::compute_digest_for_modules_and_deps(
370 &self.get_package_bytes(with_unpublished_deps),
371 &self.get_dependency_storage_package_ids(),
372 hash_modules,
373 )
374 }
375
376 pub fn get_package_bytes(&self, with_unpublished_deps: bool) -> Vec<Vec<u8>> {
378 self.get_dependency_sorted_modules(with_unpublished_deps)
379 .iter()
380 .map(|m| {
381 let mut bytes = Vec::new();
382 m.serialize_with_version(m.version, &mut bytes).unwrap(); bytes
384 })
385 .collect()
386 }
387
388 pub fn get_package_base64(&self, with_unpublished_deps: bool) -> Vec<Base64> {
390 self.get_package_bytes(with_unpublished_deps)
391 .iter()
392 .map(|b| Base64::from_bytes(b))
393 .collect()
394 }
395
396 pub fn get_deepbook_modules(&self) -> impl Iterator<Item = &CompiledModule> {
398 self.get_modules_and_deps()
399 .filter(|m| *m.self_id().address() == DEEPBOOK_ADDRESS)
400 }
401
402 pub fn get_bridge_modules(&self) -> impl Iterator<Item = &CompiledModule> {
404 self.get_modules_and_deps()
405 .filter(|m| *m.self_id().address() == BRIDGE_ADDRESS)
406 }
407
408 pub fn get_sui_system_modules(&self) -> impl Iterator<Item = &CompiledModule> {
410 self.get_modules_and_deps()
411 .filter(|m| *m.self_id().address() == SUI_SYSTEM_ADDRESS)
412 }
413
414 pub fn get_sui_framework_modules(&self) -> impl Iterator<Item = &CompiledModule> {
416 self.get_modules_and_deps()
417 .filter(|m| *m.self_id().address() == SUI_FRAMEWORK_ADDRESS)
418 }
419
420 pub fn get_stdlib_modules(&self) -> impl Iterator<Item = &CompiledModule> {
422 self.get_modules_and_deps()
423 .filter(|m| *m.self_id().address() == MOVE_STDLIB_ADDRESS)
424 }
425
426 pub fn generate_struct_layouts(&self) -> Registry {
432 let pool = &mut normalized::RcPool::new();
433 let mut package_types = BTreeSet::new();
434 for m in self.get_modules() {
435 let normalized_m = normalized::Module::new(pool, m, false);
436 'structs: for (name, s) in normalized_m.structs {
438 let mut dummy_type_parameters = Vec::new();
439 for t in &s.type_parameters {
440 if t.is_phantom {
441 dummy_type_parameters.push(TypeTag::Signer)
446 } else {
447 continue 'structs;
450 }
451 }
452 debug_assert!(dummy_type_parameters.len() == s.type_parameters.len());
453 package_types.insert(StructTag {
454 address: *m.address(),
455 module: m.name().to_owned(),
456 name: name.as_ident_str().to_owned(),
457 type_params: dummy_type_parameters,
458 });
459 }
460 for (_name, f) in normalized_m.functions {
462 if f.is_entry {
463 for t in &*f.parameters {
464 let tag_opt = match &**t {
465 Type::Address
466 | Type::Bool
467 | Type::Signer
468 | Type::TypeParameter(_)
469 | Type::U8
470 | Type::U16
471 | Type::U32
472 | Type::U64
473 | Type::U128
474 | Type::U256
475 | Type::Vector(_) => continue,
476 Type::Reference(_, inner) => inner.to_struct_tag(pool),
477 Type::Datatype(_) => t.to_struct_tag(pool),
478 };
479 if let Some(tag) = tag_opt {
480 package_types.insert(tag);
481 }
482 }
483 }
484 }
485 }
486 let mut layout_builder = SerdeLayoutBuilder::new(self);
487 for typ in &package_types {
488 layout_builder.build_data_layout(typ).unwrap();
489 }
490 layout_builder.into_registry()
491 }
492
493 pub fn is_system_package(&self) -> bool {
495 let Some(published_at) = self.published_at else {
497 return false;
498 };
499
500 is_system_package(published_at)
501 }
502
503 pub fn published_root_module(&self) -> Option<&CompiledModule> {
506 self.package.root_compiled_units.iter().find_map(|unit| {
507 if unit.unit.module.self_id().address() != &AccountAddress::ZERO {
508 Some(&unit.unit.module)
509 } else {
510 None
511 }
512 })
513 }
514
515 pub fn verify_unpublished_dependencies(
516 &self,
517 unpublished_deps: &BTreeSet<Symbol>,
518 ) -> SuiResult<()> {
519 if unpublished_deps.is_empty() {
520 return Ok(());
521 }
522
523 let errors = self
524 .package
525 .deps_compiled_units
526 .iter()
527 .filter_map(|(p, m)| {
528 if !unpublished_deps.contains(p) || m.unit.module.address() == &AccountAddress::ZERO
529 {
530 return None;
531 }
532 Some(format!(
533 " - {}::{} in dependency {}",
534 m.unit.module.address(),
535 m.unit.name,
536 p
537 ))
538 })
539 .collect::<Vec<String>>();
540
541 if errors.is_empty() {
542 return Ok(());
543 }
544
545 let mut error_message = vec![];
546 error_message.push(
547 "The following modules in package dependencies set a non-zero self-address:".into(),
548 );
549 error_message.extend(errors);
550 error_message.push(
551 "If these packages really are unpublished, their self-addresses should not be \
552 explicitly set when publishing. If they are already published, ensure they specify the \
553 address in the `published-at` of their Published.toml file."
554 .into(),
555 );
556
557 Err(SuiErrorKind::ModulePublishFailure {
558 error: error_message.join("\n"),
559 }
560 .into())
561 }
562
563 pub fn get_published_dependencies_ids(&self) -> Vec<ObjectID> {
564 self.dependency_ids.published.values().cloned().collect()
565 }
566}
567
568impl GetModule for CompiledPackage {
569 type Error = anyhow::Error;
570 type Item = CompiledModule;
572
573 fn get_module_by_id(&self, id: &ModuleId) -> Result<Option<Self::Item>, Self::Error> {
574 Ok(self.package.all_modules_map().get_module(id).ok().cloned())
575 }
576}
577
578#[derive(thiserror::Error, Debug, Clone)]
579pub enum PublishedAtError {
580 #[error("The 'published-at' field in Move.toml or Move.lock is invalid: {0:?}")]
581 Invalid(String),
582 #[error("The 'published-at' field is not present in Move.toml or Move.lock")]
583 NotPresent,
584}
585
586#[derive(Debug, Clone)]
587pub struct PackageDependencies {
588 pub published: BTreeMap<Symbol, ObjectID>,
590 pub unpublished: BTreeSet<Symbol>,
592 pub invalid: BTreeMap<Symbol, String>,
594 pub conflicting: BTreeMap<Symbol, (ObjectID, ObjectID)>,
597}
598
599impl PackageDependencies {
600 pub fn new<F: MoveFlavor>(root_pkg: &RootPackage<F>) -> anyhow::Result<Self> {
601 let mut published = BTreeMap::new();
602 let mut unpublished = BTreeSet::new();
603
604 let packages = root_pkg.packages();
605
606 for p in packages {
607 if p.is_root() {
608 continue;
609 }
610 if let Some(addresses) = p.published() {
611 published.insert(
612 p.display_name().into(),
613 ObjectID::from_address(addresses.published_at.0),
614 );
615 } else {
616 unpublished.insert(p.display_name().into());
617 }
618 }
619
620 Ok(Self {
621 published,
622 unpublished,
623 invalid: BTreeMap::new(),
624 conflicting: BTreeMap::new(),
625 })
626 }
627}