1extern crate move_ir_types;
6
7use std::{
8 collections::{BTreeMap, BTreeSet, HashSet},
9 io::Write,
10 path::Path,
11 str::FromStr,
12};
13
14use anyhow::bail;
15use fastcrypto::encoding::Base64;
16use iota_package_management::{
17 PublishedAtError, resolve_published_id,
18 system_package_versions::{SYSTEM_GIT_REPO, SystemPackagesVersion},
19};
20use iota_types::{
21 IOTA_FRAMEWORK_ADDRESS, IOTA_SYSTEM_ADDRESS, MOVE_STDLIB_ADDRESS, STARDUST_ADDRESS,
22 base_types::ObjectID,
23 error::{IotaError, IotaResult},
24 is_system_package,
25 move_package::{
26 FnInfo, FnInfoKey, FnInfoMap, IotaAttribute, MovePackage, RuntimeModuleMetadata,
27 RuntimeModuleMetadataWrapper, get_authenticator_version_from_fun,
28 },
29};
30use iota_verifier::verifier as iota_bytecode_verifier;
31use move_binary_format::{
32 CompiledModule,
33 file_format_common::IOTA_METADATA_KEY,
34 normalized::{self, Type},
35};
36use move_bytecode_utils::{Modules, layout::SerdeLayoutBuilder, module_cache::GetModule};
37use move_compiler::{
38 compiled_unit::AnnotatedCompiledModule,
39 diagnostics::{Diagnostics, report_diagnostics_to_buffer, report_warnings},
40 editions::Edition,
41 linters::LINT_WARNING_PREFIX,
42 shared::files::MappedFiles,
43};
44use move_core_types::{
45 account_address::AccountAddress,
46 language_storage::{ModuleId, StructTag, TypeTag},
47};
48use move_package::{
49 BuildConfig as MoveBuildConfig, LintFlag,
50 compilation::{
51 build_plan::BuildPlan, compiled_package::CompiledPackage as MoveCompiledPackage,
52 },
53 package_hooks::{PackageHooks, PackageIdentifier},
54 resolution::{dependency_graph::DependencyGraph, resolution_graph::ResolvedGraph},
55 source_package::parsed_manifest::{
56 Dependencies, Dependency, DependencyKind, GitInfo, InternalDependency, OnChainInfo,
57 PackageName, SourceManifest,
58 },
59};
60use move_symbol_pool::Symbol;
61use serde_reflection::Registry;
62
63#[cfg(test)]
64#[path = "unit_tests/build_tests.rs"]
65mod build_tests;
66
67pub mod test_utils {
68 use std::path::PathBuf;
69
70 use crate::{BuildConfig, CompiledPackage, IotaPackageHooks};
71
72 pub fn compile_basics_package() -> CompiledPackage {
73 compile_example_package("../../examples/move/basics")
74 }
75
76 pub fn compile_managed_coin_package() -> CompiledPackage {
77 compile_example_package("../../crates/iota-core/src/unit_tests/data/managed_coin")
78 }
79
80 pub fn compile_example_package(relative_path: &str) -> CompiledPackage {
81 move_package::package_hooks::register_package_hooks(Box::new(IotaPackageHooks));
82 let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
83 path.push(relative_path);
84
85 BuildConfig::new_for_testing().build(&path).unwrap()
86 }
87}
88
89#[derive(Debug, Clone)]
92pub struct CompiledPackage {
93 pub package: MoveCompiledPackage,
94 pub published_at: Result<ObjectID, PublishedAtError>,
96 pub dependency_ids: PackageDependencies,
98 pub bytecode_deps: Vec<(PackageName, CompiledModule)>,
101 pub dependency_graph: DependencyGraph,
103}
104
105#[derive(Clone)]
107pub struct BuildConfig {
108 pub config: MoveBuildConfig,
109 pub run_bytecode_verifier: bool,
112 pub print_diags_to_stderr: bool,
114 pub chain_id: Option<String>,
117}
118
119impl BuildConfig {
120 pub fn new_for_testing() -> Self {
121 move_package::package_hooks::register_package_hooks(Box::new(IotaPackageHooks));
122
123 let install_dir = tempfile::tempdir().unwrap().keep();
124 let config = MoveBuildConfig {
125 default_flavor: Some(move_compiler::editions::Flavor::Iota),
126 lock_file: Some(install_dir.join("Move.lock")),
127 install_dir: Some(install_dir),
128 lint_flag: LintFlag::LEVEL_NONE,
129 implicit_dependencies: Dependencies::new(),
131 silence_warnings: true,
132 ..MoveBuildConfig::default()
133 };
134 BuildConfig {
135 config,
136 run_bytecode_verifier: true,
137 print_diags_to_stderr: false,
138 chain_id: None,
139 }
140 }
141
142 pub fn new_for_testing_replace_addresses<I, S>(dep_original_addresses: I) -> Self
143 where
144 I: IntoIterator<Item = (S, ObjectID)>,
145 S: Into<String>,
146 {
147 let mut build_config = Self::new_for_testing();
148 for (addr_name, obj_id) in dep_original_addresses {
149 build_config
150 .config
151 .additional_named_addresses
152 .insert(addr_name.into(), AccountAddress::from(obj_id));
153 }
154 build_config
155 }
156
157 fn fn_info(units: &[AnnotatedCompiledModule]) -> FnInfoMap {
158 let mut fn_info_map = BTreeMap::new();
159 for u in units {
160 let mod_addr = u.named_module.address.into_inner();
161 let mod_name = u.named_module.module.name().to_string();
162 let mod_is_test = u.attributes.is_test_or_test_only();
163 for (_, s, info) in &u.function_infos {
164 let fn_name = s.as_str().to_string();
165 let is_test = mod_is_test || info.attributes.is_test_or_test_only();
166 let authenticator_version = info.attributes.get_authenticator();
167 fn_info_map.insert(
168 FnInfoKey {
169 fn_name,
170 mod_name: mod_name.clone(),
171 mod_addr,
172 },
173 FnInfo {
174 is_test,
175 authenticator_version,
176 },
177 );
178 }
179 }
180
181 fn_info_map
182 }
183
184 fn compile_package<W: Write>(
185 resolution_graph: &ResolvedGraph,
186 writer: &mut W,
187 ) -> anyhow::Result<(MoveCompiledPackage, FnInfoMap)> {
188 let build_plan = BuildPlan::create(resolution_graph)?;
189 let mut fn_info = None;
190 let compiled_pkg = build_plan.compile_with_driver(writer, |compiler| {
191 let (files, units_res) = compiler.build()?;
192 match units_res {
193 Ok((units, warning_diags)) => {
194 decorate_warnings(warning_diags, Some(&files));
195 fn_info = Some(Self::fn_info(&units));
196 Ok((files, units))
197 }
198 Err(error_diags) => {
199 assert!(!error_diags.is_empty());
202 let diags_buf =
203 report_diagnostics_to_buffer(&files, error_diags, true);
204 if let Err(err) = std::io::stderr().write_all(&diags_buf) {
205 anyhow::bail!("Cannot output compiler diagnostics: {}", err);
206 }
207 anyhow::bail!("Compilation error");
208 }
209 }
210 })?;
211 Ok((compiled_pkg, fn_info.unwrap()))
212 }
213
214 pub fn build(self, path: &Path) -> IotaResult<CompiledPackage> {
218 let print_diags_to_stderr = self.print_diags_to_stderr;
219 let run_bytecode_verifier = self.run_bytecode_verifier;
220 let chain_id = self.chain_id.clone();
221 let resolution_graph = self.resolution_graph(path, chain_id.clone())?;
222 build_from_resolution_graph(
223 resolution_graph,
224 run_bytecode_verifier,
225 print_diags_to_stderr,
226 chain_id,
227 )
228 }
229
230 pub fn resolution_graph(
231 mut self,
232 path: &Path,
233 chain_id: Option<String>,
234 ) -> IotaResult<ResolvedGraph> {
235 if let Some(err_msg) = set_iota_flavor(&mut self.config) {
236 return Err(IotaError::ModuleBuildFailure { error: err_msg });
237 }
238
239 if self.print_diags_to_stderr {
240 self.config
241 .resolution_graph_for_package(path, chain_id, &mut std::io::stderr())
242 } else {
243 self.config
244 .resolution_graph_for_package(path, chain_id, &mut std::io::sink())
245 }
246 .map_err(|err| IotaError::ModuleBuildFailure {
247 error: format!("{err:?}"),
248 })
249 }
250}
251
252pub fn decorate_warnings(warning_diags: Diagnostics, files: Option<&MappedFiles>) {
256 let any_linter_warnings = warning_diags.any_with_prefix(LINT_WARNING_PREFIX);
257 let (filtered_diags_num, unique) =
258 warning_diags.filtered_source_diags_with_prefix(LINT_WARNING_PREFIX);
259 if let Some(f) = files {
260 report_warnings(f, warning_diags);
261 }
262 if any_linter_warnings {
263 eprintln!(
264 "Please report feedback on the linter warnings at https://github.com/iotaledger/iota/issues\n"
265 );
266 }
267 if filtered_diags_num > 0 {
268 eprintln!(
269 "Total number of linter warnings suppressed: {filtered_diags_num} (unique lints: {unique})"
270 );
271 }
272}
273
274pub fn set_iota_flavor(build_config: &mut MoveBuildConfig) -> Option<String> {
277 use move_compiler::editions::Flavor;
278
279 let flavor = build_config.default_flavor.get_or_insert(Flavor::Iota);
280 if flavor != &Flavor::Iota {
281 return Some(format!(
282 "The flavor of the Move compiler cannot be overridden with anything but \
283 \"{}\", but the default override was set to: \"{flavor}\"",
284 Flavor::Iota,
285 ));
286 }
287 None
288}
289
290pub fn build_from_resolution_graph(
291 resolution_graph: ResolvedGraph,
292 run_bytecode_verifier: bool,
293 print_diags_to_stderr: bool,
294 chain_id: Option<String>,
295) -> IotaResult<CompiledPackage> {
296 let (published_at, dependency_ids) = gather_published_ids(&resolution_graph, chain_id);
297
298 let bytecode_deps = collect_bytecode_deps(&resolution_graph)?;
301
302 let result = if print_diags_to_stderr {
304 BuildConfig::compile_package(&resolution_graph, &mut std::io::stderr())
305 } else {
306 BuildConfig::compile_package(&resolution_graph, &mut std::io::sink())
307 };
308
309 let (mut package, fn_info) = result.map_err(|error| IotaError::ModuleBuildFailure {
310 error: format!("{error:?}"),
312 })?;
313
314 fill_metadata(&mut package, &fn_info)?;
317
318 if run_bytecode_verifier {
319 verify_bytecode(&package, &fn_info)?;
320 }
321
322 Ok(CompiledPackage {
323 package,
324 published_at,
325 dependency_ids,
326 bytecode_deps,
327 dependency_graph: resolution_graph.graph,
328 })
329}
330
331fn collect_bytecode_deps(
333 resolution_graph: &ResolvedGraph,
334) -> IotaResult<Vec<(Symbol, CompiledModule)>> {
335 let mut bytecode_deps = vec![];
336 for (name, pkg) in resolution_graph.package_table.iter() {
337 if !pkg
338 .get_sources(&resolution_graph.build_options)
339 .unwrap()
340 .is_empty()
341 {
342 continue;
343 }
344 let modules =
345 pkg.get_bytecodes_bytes()
346 .map_err(|error| IotaError::ModuleDeserializationFailure {
347 error: format!(
348 "Deserializing bytecode dependency for package {name}: {error:?}"
349 ),
350 })?;
351 for module in modules {
352 let module =
353 CompiledModule::deserialize_with_defaults(module.as_ref()).map_err(|error| {
354 IotaError::ModuleDeserializationFailure {
355 error: format!(
356 "Deserializing bytecode dependency for package {name}: {error:?}"
357 ),
358 }
359 })?;
360 bytecode_deps.push((*name, module));
361 }
362 }
363
364 Ok(bytecode_deps)
365}
366
367fn fill_metadata(package: &mut MoveCompiledPackage, fn_info_map: &FnInfoMap) -> IotaResult<()> {
369 for module in package
370 .root_compiled_units
371 .iter_mut()
372 .map(|unit| &mut unit.unit.module)
373 {
374 let mut runtime_metadata = RuntimeModuleMetadata::default();
375 for fn_def in &module.function_defs {
376 let fn_handle = module.function_handle_at(fn_def.function);
377 let fn_name = module.identifier_at(fn_handle.name);
378 if let Some(version) = get_authenticator_version_from_fun(fn_name, module, fn_info_map)
379 {
380 runtime_metadata.add_function_attribute(
381 fn_name.to_string(),
382 IotaAttribute::authenticator_attribute(version),
383 );
384 };
385 }
386 if !runtime_metadata.is_empty() {
387 module.metadata.push(move_core_types::metadata::Metadata {
388 key: IOTA_METADATA_KEY.to_vec(),
389 value: RuntimeModuleMetadataWrapper::from(runtime_metadata).to_bcs_bytes(),
390 });
391 }
392 }
393 Ok(())
394}
395
396fn verify_bytecode(package: &MoveCompiledPackage, fn_info: &FnInfoMap) -> IotaResult<()> {
398 let compiled_modules = package.root_modules_map();
399 for m in compiled_modules.iter_modules() {
400 move_bytecode_verifier::verify_module_unmetered(m).map_err(|err| {
401 IotaError::ModuleVerificationFailure {
402 error: err.to_string(),
403 }
404 })?;
405 iota_bytecode_verifier::iota_verify_module_unmetered(m, fn_info)?;
406 }
407 Ok(())
410}
411
412impl CompiledPackage {
413 pub fn get_modules(&self) -> impl Iterator<Item = &CompiledModule> {
418 self.package.root_modules().map(|m| &m.unit.module)
419 }
420
421 pub fn into_modules(self) -> Vec<CompiledModule> {
426 self.package
427 .root_compiled_units
428 .into_iter()
429 .map(|m| m.unit.module)
430 .collect()
431 }
432
433 pub fn get_dependent_modules(&self) -> impl Iterator<Item = &CompiledModule> {
437 self.package
438 .deps_compiled_units
439 .iter()
440 .map(|(_, m)| &m.unit.module)
441 .chain(self.bytecode_deps.iter().map(|(_, m)| m))
442 }
443
444 pub fn get_modules_and_deps(&self) -> impl Iterator<Item = &CompiledModule> {
448 self.package
449 .all_modules()
450 .map(|m| &m.unit.module)
451 .chain(self.bytecode_deps.iter().map(|(_, m)| m))
452 }
453
454 pub fn get_dependency_sorted_modules(
460 &self,
461 with_unpublished_deps: bool,
462 ) -> Vec<CompiledModule> {
463 let all_modules = Modules::new(self.get_modules_and_deps());
464
465 let modules = all_modules.compute_topological_order().unwrap();
467
468 if with_unpublished_deps {
469 modules
473 .filter(|module| module.address() == &AccountAddress::ZERO)
474 .cloned()
475 .collect()
476 } else {
477 let self_modules: HashSet<_> = self
482 .package
483 .root_modules_map()
484 .iter_modules()
485 .iter()
486 .map(|m| m.self_id())
487 .collect();
488
489 modules
490 .filter(|module| self_modules.contains(&module.self_id()))
491 .cloned()
492 .collect()
493 }
494 }
495
496 pub fn get_dependency_storage_package_ids(&self) -> Vec<ObjectID> {
500 self.dependency_ids.published.values().copied().collect()
501 }
502
503 pub fn get_package_digest(&self, with_unpublished_deps: bool) -> [u8; 32] {
505 MovePackage::compute_digest_for_modules_and_deps(
506 &self.get_package_bytes(with_unpublished_deps),
507 self.dependency_ids.published.values(),
508 )
509 }
510
511 pub fn get_package_bytes(&self, with_unpublished_deps: bool) -> Vec<Vec<u8>> {
514 self.get_dependency_sorted_modules(with_unpublished_deps)
515 .iter()
516 .map(|m| {
517 let mut bytes = Vec::new();
518 m.serialize_with_version(m.version, &mut bytes).unwrap(); bytes
520 })
521 .collect()
522 }
523
524 pub fn get_package_base64(&self, with_unpublished_deps: bool) -> Vec<Base64> {
527 self.get_package_bytes(with_unpublished_deps)
528 .iter()
529 .map(|b| Base64::from_bytes(b))
530 .collect()
531 }
532
533 pub fn get_iota_system_modules(&self) -> impl Iterator<Item = &CompiledModule> {
535 self.get_modules_and_deps()
536 .filter(|m| *m.self_id().address() == IOTA_SYSTEM_ADDRESS)
537 }
538
539 pub fn get_iota_framework_modules(&self) -> impl Iterator<Item = &CompiledModule> {
542 self.get_modules_and_deps()
543 .filter(|m| *m.self_id().address() == IOTA_FRAMEWORK_ADDRESS)
544 }
545
546 pub fn get_stdlib_modules(&self) -> impl Iterator<Item = &CompiledModule> {
548 self.get_modules_and_deps()
549 .filter(|m| *m.self_id().address() == MOVE_STDLIB_ADDRESS)
550 }
551
552 pub fn get_stardust_modules(&self) -> impl Iterator<Item = &CompiledModule> {
554 self.get_modules_and_deps()
555 .filter(|m| *m.self_id().address() == STARDUST_ADDRESS)
556 }
557
558 pub fn generate_struct_layouts(&self) -> Registry {
565 let pool = &mut normalized::RcPool::new();
566 let mut package_types = BTreeSet::new();
567 for m in self.get_modules() {
568 let normalized_m = normalized::Module::new(pool, m, false);
569 'structs: for (name, s) in normalized_m.structs {
571 let mut dummy_type_parameters = Vec::new();
572 for t in &s.type_parameters {
573 if t.is_phantom {
574 dummy_type_parameters.push(TypeTag::Signer)
581 } else {
582 continue 'structs;
585 }
586 }
587 debug_assert!(dummy_type_parameters.len() == s.type_parameters.len());
588 package_types.insert(StructTag {
589 address: *m.address(),
590 module: m.name().to_owned(),
591 name: name.as_ident_str().to_owned(),
592 type_params: dummy_type_parameters,
593 });
594 }
595 for (_name, f) in normalized_m.functions {
597 if f.is_entry {
598 for t in &*f.parameters {
599 let tag_opt = match &**t {
600 Type::Address
601 | Type::Bool
602 | Type::Signer
603 | Type::TypeParameter(_)
604 | Type::U8
605 | Type::U16
606 | Type::U32
607 | Type::U64
608 | Type::U128
609 | Type::U256
610 | Type::Vector(_) => continue,
611 Type::Reference(_, inner) => inner.to_struct_tag(pool),
612 Type::Datatype(_) => t.to_struct_tag(pool),
613 };
614 if let Some(tag) = tag_opt {
615 package_types.insert(tag);
616 }
617 }
618 }
619 }
620 }
621 let mut layout_builder = SerdeLayoutBuilder::new(self);
622 for tag in &package_types {
623 layout_builder.build_data_layout(tag).unwrap();
624 }
625 layout_builder.into_registry()
626 }
627
628 pub fn is_system_package(&self) -> bool {
630 let Ok(published_at) = self.published_at else {
632 return false;
633 };
634
635 is_system_package(published_at)
636 }
637
638 pub fn published_root_module(&self) -> Option<&CompiledModule> {
641 self.package.root_compiled_units.iter().find_map(|unit| {
642 if unit.unit.module.self_id().address() != &AccountAddress::ZERO {
643 Some(&unit.unit.module)
644 } else {
645 None
646 }
647 })
648 }
649
650 pub fn verify_unpublished_dependencies(
651 &self,
652 unpublished_deps: &BTreeSet<Symbol>,
653 ) -> IotaResult<()> {
654 if unpublished_deps.is_empty() {
655 return Ok(());
656 }
657
658 let errors = self
659 .package
660 .deps_compiled_units
661 .iter()
662 .filter_map(|(p, m)| {
663 if !unpublished_deps.contains(p) || m.unit.module.address() == &AccountAddress::ZERO
664 {
665 return None;
666 }
667 Some(format!(
668 " - {}::{} in dependency {}",
669 m.unit.module.address(),
670 m.unit.name,
671 p
672 ))
673 })
674 .collect::<Vec<String>>();
675
676 if errors.is_empty() {
677 return Ok(());
678 }
679
680 let mut error_message = vec![];
681 error_message.push(
682 "The following modules in package dependencies set a non-zero self-address:".into(),
683 );
684 error_message.extend(errors);
685 error_message.push(
686 "If these packages really are unpublished, their self-addresses should be set \
687 to \"0x0\" in the [addresses] section of the manifest when publishing. If they \
688 are already published, ensure they specify the address in the `published-at` of \
689 their Move.toml manifest."
690 .into(),
691 );
692
693 Err(IotaError::ModulePublishFailure {
694 error: error_message.join("\n"),
695 })
696 }
697
698 pub fn get_published_dependencies_ids(&self) -> Vec<ObjectID> {
699 self.dependency_ids.published.values().cloned().collect()
700 }
701
702 pub fn find_immediate_deps_pkgs_to_keep(
705 &self,
706 with_unpublished_deps: bool,
707 ) -> Result<BTreeMap<Symbol, ObjectID>, anyhow::Error> {
708 let root_modules: Vec<_> = if with_unpublished_deps {
711 self.package
712 .all_compiled_units_with_source()
713 .filter(|m| m.unit.address.into_inner() == AccountAddress::ZERO)
714 .map(|x| x.unit.clone())
715 .collect()
716 } else {
717 self.package
718 .root_modules()
719 .map(|x| x.unit.clone())
720 .collect()
721 };
722
723 let mut pkgs_to_keep: BTreeSet<Symbol> = BTreeSet::new();
727 let module_to_pkg_name: BTreeMap<_, _> = self
728 .package
729 .all_modules()
730 .map(|m| (m.unit.module.self_id(), m.unit.package_name))
731 .collect();
732
733 for module in &root_modules {
734 let immediate_deps = module.module.immediate_dependencies();
735 for dep in immediate_deps {
736 if let Some(pkg_name) = module_to_pkg_name.get(&dep) {
737 let Some(pkg_name) = pkg_name else {
738 bail!("Expected a package name but it's None")
739 };
740 pkgs_to_keep.insert(*pkg_name);
741 }
742 }
743 }
744
745 pkgs_to_keep.extend(self.bytecode_deps.iter().map(|(name, _)| *name));
749
750 Ok(self
753 .dependency_ids
754 .clone()
755 .published
756 .into_iter()
757 .filter(|(pkg_name, _)| pkgs_to_keep.contains(pkg_name))
758 .collect())
759 }
760}
761
762pub fn implicit_deps(packages: &SystemPackagesVersion) -> Dependencies {
766 packages
767 .packages
768 .iter()
769 .map(|package| {
770 (
771 package.package_name.clone().into(),
772 Dependency::Internal(InternalDependency {
773 kind: DependencyKind::Git(GitInfo {
774 git_url: SYSTEM_GIT_REPO.into(),
775 git_rev: packages.git_revision.clone().into(),
776 subdir: package.repo_path.clone().into(),
777 }),
778 subst: None,
779 digest: None,
780 dep_override: true,
781 }),
782 )
783 })
784 .collect()
785}
786
787impl GetModule for CompiledPackage {
788 type Error = anyhow::Error;
789 type Item = CompiledModule;
792
793 fn get_module_by_id(&self, id: &ModuleId) -> Result<Option<Self::Item>, Self::Error> {
794 Ok(self.package.all_modules_map().get_module(id).ok().cloned())
795 }
796}
797
798pub const PUBLISHED_AT_MANIFEST_FIELD: &str = "published-at";
799
800pub struct IotaPackageHooks;
801
802impl PackageHooks for IotaPackageHooks {
803 fn custom_package_info_fields(&self) -> Vec<String> {
804 vec![
805 PUBLISHED_AT_MANIFEST_FIELD.to_string(),
806 "version".to_string(),
808 ]
809 }
810
811 fn resolve_on_chain_dependency(
812 &self,
813 _dep_name: move_symbol_pool::Symbol,
814 _info: &OnChainInfo,
815 ) -> anyhow::Result<()> {
816 Ok(())
817 }
818
819 fn custom_resolve_pkg_id(
820 &self,
821 manifest: &SourceManifest,
822 ) -> anyhow::Result<PackageIdentifier> {
823 if (!cfg!(debug_assertions) || cfg!(test))
824 && manifest.package.edition == Some(Edition::DEVELOPMENT)
825 {
826 return Err(Edition::DEVELOPMENT.unknown_edition_error());
827 }
828 Ok(manifest.package.name)
829 }
830
831 fn resolve_version(&self, _: &SourceManifest) -> anyhow::Result<Option<Symbol>> {
832 Ok(None)
833 }
834}
835
836#[derive(Debug, Clone)]
837pub struct PackageDependencies {
838 pub published: BTreeMap<Symbol, ObjectID>,
840 pub unpublished: BTreeSet<Symbol>,
842 pub invalid: BTreeMap<Symbol, String>,
844 pub conflicting: BTreeMap<Symbol, (ObjectID, ObjectID)>,
848}
849
850pub fn gather_published_ids(
857 resolution_graph: &ResolvedGraph,
858 chain_id: Option<String>,
859) -> (Result<ObjectID, PublishedAtError>, PackageDependencies) {
860 let root = resolution_graph.root_package();
861
862 let mut published = BTreeMap::new();
863 let mut unpublished = BTreeSet::new();
864 let mut invalid = BTreeMap::new();
865 let mut conflicting = BTreeMap::new();
866 let mut published_at = Err(PublishedAtError::NotPresent);
867
868 for (name, package) in &resolution_graph.package_table {
869 let property = resolve_published_id(package, chain_id.clone());
870 if name == &root {
871 published_at = property;
873 continue;
874 }
875
876 match property {
877 Ok(id) => {
878 published.insert(*name, id);
879 }
880 Err(PublishedAtError::NotPresent) => {
881 unpublished.insert(*name);
882 }
883 Err(PublishedAtError::Invalid(value)) => {
884 invalid.insert(*name, value);
885 }
886 Err(PublishedAtError::Conflict {
887 id_lock,
888 id_manifest,
889 }) => {
890 conflicting.insert(*name, (id_lock, id_manifest));
891 }
892 };
893 }
894
895 (
896 published_at,
897 PackageDependencies {
898 published,
899 unpublished,
900 invalid,
901 conflicting,
902 },
903 )
904}
905
906pub fn published_at_property(manifest: &SourceManifest) -> Result<ObjectID, PublishedAtError> {
907 let Some(value) = manifest
908 .package
909 .custom_properties
910 .get(&Symbol::from(PUBLISHED_AT_MANIFEST_FIELD))
911 else {
912 return Err(PublishedAtError::NotPresent);
913 };
914
915 ObjectID::from_str(value.as_str()).map_err(|_| PublishedAtError::Invalid(value.to_owned()))
916}
917
918pub fn check_unpublished_dependencies(unpublished: &BTreeSet<Symbol>) -> Result<(), IotaError> {
919 if unpublished.is_empty() {
920 return Ok(());
921 };
922
923 let mut error_messages = unpublished
924 .iter()
925 .map(|name| {
926 format!(
927 "Package dependency \"{name}\" does not specify a published address \
928 (the Move.toml manifest for \"{name}\" does not contain a 'published-at' field, \
929 nor is there a 'published-id' in the Move.lock). \
930 You can use `iota move manage-package` to record the on-chain address for \"{name}\".",
931 )
932 })
933 .collect::<Vec<_>>();
934
935 error_messages.push(
936 "If this is intentional, you may use the --with-unpublished-dependencies flag to \
937 continue publishing these dependencies as part of your package (they won't be \
938 linked against existing packages on-chain)."
939 .into(),
940 );
941
942 Err(IotaError::ModulePublishFailure {
943 error: error_messages.join("\n"),
944 })
945}
946
947pub fn check_invalid_dependencies(invalid: &BTreeMap<Symbol, String>) -> Result<(), IotaError> {
948 if invalid.is_empty() {
949 return Ok(());
950 }
951
952 let error_messages = invalid
953 .iter()
954 .map(|(name, value)| {
955 format!(
956 "Package dependency \"{name}\" does not specify a valid published \
957 address: could not parse value \"{value}\" for 'published-at' field in Move.toml \
958 or 'published-id' in Move.lock file."
959 )
960 })
961 .collect::<Vec<_>>();
962
963 Err(IotaError::ModulePublishFailure {
964 error: error_messages.join("\n"),
965 })
966}
967
968pub fn check_conflicting_addresses(
969 conflicting: &BTreeMap<Symbol, (ObjectID, ObjectID)>,
970 dump_bytecode_base64: bool,
971) -> Result<(), IotaError> {
972 if conflicting.is_empty() {
973 return Ok(());
974 }
975
976 let suffix = if conflicting.len() == 1 { "" } else { "es" };
977
978 let err_msg = format!("found the following conflicting published package address{suffix}:");
979 let suggestion_message =
980 "You may want to:
981 - delete the published-at address in the `Move.toml` if the `Move.lock` address is correct; OR
982 - update the `Move.lock` address to be the same as the `Move.toml`; OR
983 - check that your `iota active-env` corresponds to the chain on which the package is published (i.e., devnet, testnet, mainnet); OR
984 - contact the maintainer if this package is a dependency and request resolving the conflict.";
985
986 let conflicting_addresses_msg = conflicting
987 .iter()
988 .map(|(_, (id_lock, id_manifest))| {
989 format!(
990 " `Move.toml` contains published-at address \
991 {id_manifest} but `Move.lock` file contains published-at address {id_lock}."
992 )
993 })
994 .collect::<Vec<_>>()
995 .join("\n");
996
997 let error = format!("{err_msg}\n{conflicting_addresses_msg}\n{suggestion_message}");
998
999 let err = if dump_bytecode_base64 {
1000 IotaError::ModuleBuildFailure { error }
1001 } else {
1002 IotaError::ModulePublishFailure { error }
1003 };
1004
1005 Err(err)
1006}