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::{FnInfo, FnInfoKey, FnInfoMap, MovePackage},
26};
27use iota_verifier::verifier as iota_bytecode_verifier;
28use move_binary_format::{
29 CompiledModule,
30 normalized::{self, Type},
31};
32use move_bytecode_utils::{Modules, layout::SerdeLayoutBuilder, module_cache::GetModule};
33use move_compiler::{
34 compiled_unit::AnnotatedCompiledModule,
35 diagnostics::{Diagnostics, report_diagnostics_to_buffer, report_warnings},
36 editions::Edition,
37 linters::LINT_WARNING_PREFIX,
38 shared::files::MappedFiles,
39};
40use move_core_types::{
41 account_address::AccountAddress,
42 language_storage::{ModuleId, StructTag, TypeTag},
43};
44use move_package::{
45 BuildConfig as MoveBuildConfig, LintFlag,
46 compilation::{
47 build_plan::BuildPlan, compiled_package::CompiledPackage as MoveCompiledPackage,
48 },
49 package_hooks::{PackageHooks, PackageIdentifier},
50 resolution::{dependency_graph::DependencyGraph, resolution_graph::ResolvedGraph},
51 source_package::parsed_manifest::{
52 Dependencies, Dependency, DependencyKind, GitInfo, InternalDependency, OnChainInfo,
53 PackageName, SourceManifest,
54 },
55};
56use move_symbol_pool::Symbol;
57use serde_reflection::Registry;
58
59#[cfg(test)]
60#[path = "unit_tests/build_tests.rs"]
61mod build_tests;
62
63pub mod test_utils {
64 use std::path::PathBuf;
65
66 use crate::{BuildConfig, CompiledPackage, IotaPackageHooks};
67
68 pub fn compile_basics_package() -> CompiledPackage {
69 compile_example_package("../../examples/move/basics")
70 }
71
72 pub fn compile_managed_coin_package() -> CompiledPackage {
73 compile_example_package("../../crates/iota-core/src/unit_tests/data/managed_coin")
74 }
75
76 pub fn compile_example_package(relative_path: &str) -> CompiledPackage {
77 move_package::package_hooks::register_package_hooks(Box::new(IotaPackageHooks));
78 let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
79 path.push(relative_path);
80
81 BuildConfig::new_for_testing().build(&path).unwrap()
82 }
83}
84
85#[derive(Debug, Clone)]
88pub struct CompiledPackage {
89 pub package: MoveCompiledPackage,
90 pub published_at: Result<ObjectID, PublishedAtError>,
92 pub dependency_ids: PackageDependencies,
94 pub bytecode_deps: Vec<(PackageName, CompiledModule)>,
97 pub dependency_graph: DependencyGraph,
99}
100
101#[derive(Clone)]
103pub struct BuildConfig {
104 pub config: MoveBuildConfig,
105 pub run_bytecode_verifier: bool,
108 pub print_diags_to_stderr: bool,
110 pub chain_id: Option<String>,
113}
114
115impl BuildConfig {
116 pub fn new_for_testing() -> Self {
117 move_package::package_hooks::register_package_hooks(Box::new(IotaPackageHooks));
118
119 let install_dir = tempfile::tempdir().unwrap().keep();
120 let config = MoveBuildConfig {
121 default_flavor: Some(move_compiler::editions::Flavor::Iota),
122 lock_file: Some(install_dir.join("Move.lock")),
123 install_dir: Some(install_dir),
124 lint_flag: LintFlag::LEVEL_NONE,
125 implicit_dependencies: Dependencies::new(),
127 silence_warnings: true,
128 ..MoveBuildConfig::default()
129 };
130 BuildConfig {
131 config,
132 run_bytecode_verifier: true,
133 print_diags_to_stderr: false,
134 chain_id: None,
135 }
136 }
137
138 pub fn new_for_testing_replace_addresses<I, S>(dep_original_addresses: I) -> Self
139 where
140 I: IntoIterator<Item = (S, ObjectID)>,
141 S: Into<String>,
142 {
143 let mut build_config = Self::new_for_testing();
144 for (addr_name, obj_id) in dep_original_addresses {
145 build_config
146 .config
147 .additional_named_addresses
148 .insert(addr_name.into(), AccountAddress::from(obj_id));
149 }
150 build_config
151 }
152
153 fn fn_info(units: &[AnnotatedCompiledModule]) -> FnInfoMap {
154 let mut fn_info_map = BTreeMap::new();
155 for u in units {
156 let mod_addr = u.named_module.address.into_inner();
157 let mod_is_test = u.attributes.is_test_or_test_only();
158 for (_, s, info) in &u.function_infos {
159 let fn_name = s.as_str().to_string();
160 let is_test = mod_is_test || info.attributes.is_test_or_test_only();
161 fn_info_map.insert(FnInfoKey { fn_name, mod_addr }, FnInfo { is_test });
162 }
163 }
164
165 fn_info_map
166 }
167
168 fn compile_package<W: Write>(
169 resolution_graph: &ResolvedGraph,
170 writer: &mut W,
171 ) -> anyhow::Result<(MoveCompiledPackage, FnInfoMap)> {
172 let build_plan = BuildPlan::create(resolution_graph)?;
173 let mut fn_info = None;
174 let compiled_pkg = build_plan.compile_with_driver(writer, |compiler| {
175 let (files, units_res) = compiler.build()?;
176 match units_res {
177 Ok((units, warning_diags)) => {
178 decorate_warnings(warning_diags, Some(&files));
179 fn_info = Some(Self::fn_info(&units));
180 Ok((files, units))
181 }
182 Err(error_diags) => {
183 assert!(!error_diags.is_empty());
186 let diags_buf =
187 report_diagnostics_to_buffer(&files, error_diags, true);
188 if let Err(err) = std::io::stderr().write_all(&diags_buf) {
189 anyhow::bail!("Cannot output compiler diagnostics: {}", err);
190 }
191 anyhow::bail!("Compilation error");
192 }
193 }
194 })?;
195 Ok((compiled_pkg, fn_info.unwrap()))
196 }
197
198 pub fn build(self, path: &Path) -> IotaResult<CompiledPackage> {
202 let print_diags_to_stderr = self.print_diags_to_stderr;
203 let run_bytecode_verifier = self.run_bytecode_verifier;
204 let chain_id = self.chain_id.clone();
205 let resolution_graph = self.resolution_graph(path, chain_id.clone())?;
206 build_from_resolution_graph(
207 resolution_graph,
208 run_bytecode_verifier,
209 print_diags_to_stderr,
210 chain_id,
211 )
212 }
213
214 pub fn resolution_graph(
215 mut self,
216 path: &Path,
217 chain_id: Option<String>,
218 ) -> IotaResult<ResolvedGraph> {
219 if let Some(err_msg) = set_iota_flavor(&mut self.config) {
220 return Err(IotaError::ModuleBuildFailure { error: err_msg });
221 }
222
223 if self.print_diags_to_stderr {
224 self.config
225 .resolution_graph_for_package(path, chain_id, &mut std::io::stderr())
226 } else {
227 self.config
228 .resolution_graph_for_package(path, chain_id, &mut std::io::sink())
229 }
230 .map_err(|err| IotaError::ModuleBuildFailure {
231 error: format!("{err:?}"),
232 })
233 }
234}
235
236pub fn decorate_warnings(warning_diags: Diagnostics, files: Option<&MappedFiles>) {
240 let any_linter_warnings = warning_diags.any_with_prefix(LINT_WARNING_PREFIX);
241 let (filtered_diags_num, unique) =
242 warning_diags.filtered_source_diags_with_prefix(LINT_WARNING_PREFIX);
243 if let Some(f) = files {
244 report_warnings(f, warning_diags);
245 }
246 if any_linter_warnings {
247 eprintln!(
248 "Please report feedback on the linter warnings at https://github.com/iotaledger/iota/issues\n"
249 );
250 }
251 if filtered_diags_num > 0 {
252 eprintln!(
253 "Total number of linter warnings suppressed: {filtered_diags_num} (unique lints: {unique})"
254 );
255 }
256}
257
258pub fn set_iota_flavor(build_config: &mut MoveBuildConfig) -> Option<String> {
261 use move_compiler::editions::Flavor;
262
263 let flavor = build_config.default_flavor.get_or_insert(Flavor::Iota);
264 if flavor != &Flavor::Iota {
265 return Some(format!(
266 "The flavor of the Move compiler cannot be overridden with anything but \
267 \"{}\", but the default override was set to: \"{flavor}\"",
268 Flavor::Iota,
269 ));
270 }
271 None
272}
273
274pub fn build_from_resolution_graph(
275 resolution_graph: ResolvedGraph,
276 run_bytecode_verifier: bool,
277 print_diags_to_stderr: bool,
278 chain_id: Option<String>,
279) -> IotaResult<CompiledPackage> {
280 let (published_at, dependency_ids) = gather_published_ids(&resolution_graph, chain_id);
281
282 let bytecode_deps = collect_bytecode_deps(&resolution_graph)?;
285
286 let result = if print_diags_to_stderr {
288 BuildConfig::compile_package(&resolution_graph, &mut std::io::stderr())
289 } else {
290 BuildConfig::compile_package(&resolution_graph, &mut std::io::sink())
291 };
292
293 let (package, fn_info) = result.map_err(|error| IotaError::ModuleBuildFailure {
294 error: format!("{error:?}"),
296 })?;
297
298 if run_bytecode_verifier {
299 verify_bytecode(&package, &fn_info)?;
300 }
301
302 Ok(CompiledPackage {
303 package,
304 published_at,
305 dependency_ids,
306 bytecode_deps,
307 dependency_graph: resolution_graph.graph,
308 })
309}
310
311fn collect_bytecode_deps(
313 resolution_graph: &ResolvedGraph,
314) -> IotaResult<Vec<(Symbol, CompiledModule)>> {
315 let mut bytecode_deps = vec![];
316 for (name, pkg) in resolution_graph.package_table.iter() {
317 if !pkg
318 .get_sources(&resolution_graph.build_options)
319 .unwrap()
320 .is_empty()
321 {
322 continue;
323 }
324 let modules =
325 pkg.get_bytecodes_bytes()
326 .map_err(|error| IotaError::ModuleDeserializationFailure {
327 error: format!(
328 "Deserializing bytecode dependency for package {name}: {error:?}"
329 ),
330 })?;
331 for module in modules {
332 let module =
333 CompiledModule::deserialize_with_defaults(module.as_ref()).map_err(|error| {
334 IotaError::ModuleDeserializationFailure {
335 error: format!(
336 "Deserializing bytecode dependency for package {name}: {error:?}"
337 ),
338 }
339 })?;
340 bytecode_deps.push((*name, module));
341 }
342 }
343
344 Ok(bytecode_deps)
345}
346
347fn verify_bytecode(package: &MoveCompiledPackage, fn_info: &FnInfoMap) -> IotaResult<()> {
349 let compiled_modules = package.root_modules_map();
350 for m in compiled_modules.iter_modules() {
351 move_bytecode_verifier::verify_module_unmetered(m).map_err(|err| {
352 IotaError::ModuleVerificationFailure {
353 error: err.to_string(),
354 }
355 })?;
356 iota_bytecode_verifier::iota_verify_module_unmetered(m, fn_info)?;
357 }
358 Ok(())
361}
362
363impl CompiledPackage {
364 pub fn get_modules(&self) -> impl Iterator<Item = &CompiledModule> {
369 self.package.root_modules().map(|m| &m.unit.module)
370 }
371
372 pub fn into_modules(self) -> Vec<CompiledModule> {
377 self.package
378 .root_compiled_units
379 .into_iter()
380 .map(|m| m.unit.module)
381 .collect()
382 }
383
384 pub fn get_dependent_modules(&self) -> impl Iterator<Item = &CompiledModule> {
388 self.package
389 .deps_compiled_units
390 .iter()
391 .map(|(_, m)| &m.unit.module)
392 .chain(self.bytecode_deps.iter().map(|(_, m)| m))
393 }
394
395 pub fn get_modules_and_deps(&self) -> impl Iterator<Item = &CompiledModule> {
399 self.package
400 .all_modules()
401 .map(|m| &m.unit.module)
402 .chain(self.bytecode_deps.iter().map(|(_, m)| m))
403 }
404
405 pub fn get_dependency_sorted_modules(
411 &self,
412 with_unpublished_deps: bool,
413 ) -> Vec<CompiledModule> {
414 let all_modules = Modules::new(self.get_modules_and_deps());
415
416 let modules = all_modules.compute_topological_order().unwrap();
418
419 if with_unpublished_deps {
420 modules
424 .filter(|module| module.address() == &AccountAddress::ZERO)
425 .cloned()
426 .collect()
427 } else {
428 let self_modules: HashSet<_> = self
433 .package
434 .root_modules_map()
435 .iter_modules()
436 .iter()
437 .map(|m| m.self_id())
438 .collect();
439
440 modules
441 .filter(|module| self_modules.contains(&module.self_id()))
442 .cloned()
443 .collect()
444 }
445 }
446
447 pub fn get_dependency_storage_package_ids(&self) -> Vec<ObjectID> {
451 self.dependency_ids.published.values().copied().collect()
452 }
453
454 pub fn get_package_digest(&self, with_unpublished_deps: bool) -> [u8; 32] {
456 MovePackage::compute_digest_for_modules_and_deps(
457 &self.get_package_bytes(with_unpublished_deps),
458 self.dependency_ids.published.values(),
459 )
460 }
461
462 pub fn get_package_bytes(&self, with_unpublished_deps: bool) -> Vec<Vec<u8>> {
465 self.get_dependency_sorted_modules(with_unpublished_deps)
466 .iter()
467 .map(|m| {
468 let mut bytes = Vec::new();
469 m.serialize_with_version(m.version, &mut bytes).unwrap(); bytes
471 })
472 .collect()
473 }
474
475 pub fn get_package_base64(&self, with_unpublished_deps: bool) -> Vec<Base64> {
478 self.get_package_bytes(with_unpublished_deps)
479 .iter()
480 .map(|b| Base64::from_bytes(b))
481 .collect()
482 }
483
484 pub fn get_iota_system_modules(&self) -> impl Iterator<Item = &CompiledModule> {
486 self.get_modules_and_deps()
487 .filter(|m| *m.self_id().address() == IOTA_SYSTEM_ADDRESS)
488 }
489
490 pub fn get_iota_framework_modules(&self) -> impl Iterator<Item = &CompiledModule> {
493 self.get_modules_and_deps()
494 .filter(|m| *m.self_id().address() == IOTA_FRAMEWORK_ADDRESS)
495 }
496
497 pub fn get_stdlib_modules(&self) -> impl Iterator<Item = &CompiledModule> {
499 self.get_modules_and_deps()
500 .filter(|m| *m.self_id().address() == MOVE_STDLIB_ADDRESS)
501 }
502
503 pub fn get_stardust_modules(&self) -> impl Iterator<Item = &CompiledModule> {
505 self.get_modules_and_deps()
506 .filter(|m| *m.self_id().address() == STARDUST_ADDRESS)
507 }
508
509 pub fn generate_struct_layouts(&self) -> Registry {
516 let mut package_types = BTreeSet::new();
517 for m in self.get_modules() {
518 let normalized_m = normalized::Module::new(m);
519 'structs: for (name, s) in normalized_m.structs {
521 let mut dummy_type_parameters = Vec::new();
522 for t in &s.type_parameters {
523 if t.is_phantom {
524 dummy_type_parameters.push(TypeTag::Signer)
531 } else {
532 continue 'structs;
535 }
536 }
537 debug_assert!(dummy_type_parameters.len() == s.type_parameters.len());
538 package_types.insert(StructTag {
539 address: *m.address(),
540 module: m.name().to_owned(),
541 name,
542 type_params: dummy_type_parameters,
543 });
544 }
545 for (_name, f) in normalized_m.functions {
547 if f.is_entry {
548 for t in f.parameters {
549 let tag_opt = match t.clone() {
550 Type::Address
551 | Type::Bool
552 | Type::Signer
553 | Type::TypeParameter(_)
554 | Type::U8
555 | Type::U16
556 | Type::U32
557 | Type::U64
558 | Type::U128
559 | Type::U256
560 | Type::Vector(_) => continue,
561 Type::Reference(t) | Type::MutableReference(t) => t.into_struct_tag(),
562 s @ Type::Struct { .. } => s.into_struct_tag(),
563 };
564 if let Some(tag) = tag_opt {
565 package_types.insert(tag);
566 }
567 }
568 }
569 }
570 }
571 let mut layout_builder = SerdeLayoutBuilder::new(self);
572 for typ in &package_types {
573 layout_builder.build_data_layout(typ).unwrap();
574 }
575 layout_builder.into_registry()
576 }
577
578 pub fn is_system_package(&self) -> bool {
580 let Ok(published_at) = self.published_at else {
582 return false;
583 };
584
585 is_system_package(published_at)
586 }
587
588 pub fn published_root_module(&self) -> Option<&CompiledModule> {
591 self.package.root_compiled_units.iter().find_map(|unit| {
592 if unit.unit.module.self_id().address() != &AccountAddress::ZERO {
593 Some(&unit.unit.module)
594 } else {
595 None
596 }
597 })
598 }
599
600 pub fn verify_unpublished_dependencies(
601 &self,
602 unpublished_deps: &BTreeSet<Symbol>,
603 ) -> IotaResult<()> {
604 if unpublished_deps.is_empty() {
605 return Ok(());
606 }
607
608 let errors = self
609 .package
610 .deps_compiled_units
611 .iter()
612 .filter_map(|(p, m)| {
613 if !unpublished_deps.contains(p) || m.unit.module.address() == &AccountAddress::ZERO
614 {
615 return None;
616 }
617 Some(format!(
618 " - {}::{} in dependency {}",
619 m.unit.module.address(),
620 m.unit.name,
621 p
622 ))
623 })
624 .collect::<Vec<String>>();
625
626 if errors.is_empty() {
627 return Ok(());
628 }
629
630 let mut error_message = vec![];
631 error_message.push(
632 "The following modules in package dependencies set a non-zero self-address:".into(),
633 );
634 error_message.extend(errors);
635 error_message.push(
636 "If these packages really are unpublished, their self-addresses should be set \
637 to \"0x0\" in the [addresses] section of the manifest when publishing. If they \
638 are already published, ensure they specify the address in the `published-at` of \
639 their Move.toml manifest."
640 .into(),
641 );
642
643 Err(IotaError::ModulePublishFailure {
644 error: error_message.join("\n"),
645 })
646 }
647
648 pub fn get_published_dependencies_ids(&self) -> Vec<ObjectID> {
649 self.dependency_ids.published.values().cloned().collect()
650 }
651
652 pub fn find_immediate_deps_pkgs_to_keep(
655 &self,
656 with_unpublished_deps: bool,
657 ) -> Result<BTreeMap<Symbol, ObjectID>, anyhow::Error> {
658 let root_modules: Vec<_> = if with_unpublished_deps {
661 self.package
662 .all_compiled_units_with_source()
663 .filter(|m| m.unit.address.into_inner() == AccountAddress::ZERO)
664 .map(|x| x.unit.clone())
665 .collect()
666 } else {
667 self.package
668 .root_modules()
669 .map(|x| x.unit.clone())
670 .collect()
671 };
672
673 let mut pkgs_to_keep: BTreeSet<Symbol> = BTreeSet::new();
677 let module_to_pkg_name: BTreeMap<_, _> = self
678 .package
679 .all_modules()
680 .map(|m| (m.unit.module.self_id(), m.unit.package_name))
681 .collect();
682
683 for module in &root_modules {
684 let immediate_deps = module.module.immediate_dependencies();
685 for dep in immediate_deps {
686 if let Some(pkg_name) = module_to_pkg_name.get(&dep) {
687 let Some(pkg_name) = pkg_name else {
688 bail!("Expected a package name but it's None")
689 };
690 pkgs_to_keep.insert(*pkg_name);
691 }
692 }
693 }
694
695 pkgs_to_keep.extend(self.bytecode_deps.iter().map(|(name, _)| *name));
699
700 Ok(self
703 .dependency_ids
704 .clone()
705 .published
706 .into_iter()
707 .filter(|(pkg_name, _)| pkgs_to_keep.contains(pkg_name))
708 .collect())
709 }
710}
711
712pub fn implicit_deps(packages: &SystemPackagesVersion) -> Dependencies {
716 packages
717 .packages
718 .iter()
719 .map(|package| {
720 (
721 package.package_name.clone().into(),
722 Dependency::Internal(InternalDependency {
723 kind: DependencyKind::Git(GitInfo {
724 git_url: SYSTEM_GIT_REPO.into(),
725 git_rev: packages.git_revision.clone().into(),
726 subdir: package.repo_path.clone().into(),
727 }),
728 subst: None,
729 digest: None,
730 dep_override: true,
731 }),
732 )
733 })
734 .collect()
735}
736
737impl GetModule for CompiledPackage {
738 type Error = anyhow::Error;
739 type Item = CompiledModule;
742
743 fn get_module_by_id(&self, id: &ModuleId) -> Result<Option<Self::Item>, Self::Error> {
744 Ok(self.package.all_modules_map().get_module(id).ok().cloned())
745 }
746}
747
748pub const PUBLISHED_AT_MANIFEST_FIELD: &str = "published-at";
749
750pub struct IotaPackageHooks;
751
752impl PackageHooks for IotaPackageHooks {
753 fn custom_package_info_fields(&self) -> Vec<String> {
754 vec![
755 PUBLISHED_AT_MANIFEST_FIELD.to_string(),
756 "version".to_string(),
758 ]
759 }
760
761 fn resolve_on_chain_dependency(
762 &self,
763 _dep_name: move_symbol_pool::Symbol,
764 _info: &OnChainInfo,
765 ) -> anyhow::Result<()> {
766 Ok(())
767 }
768
769 fn custom_resolve_pkg_id(
770 &self,
771 manifest: &SourceManifest,
772 ) -> anyhow::Result<PackageIdentifier> {
773 if (!cfg!(debug_assertions) || cfg!(test))
774 && manifest.package.edition == Some(Edition::DEVELOPMENT)
775 {
776 return Err(Edition::DEVELOPMENT.unknown_edition_error());
777 }
778 Ok(manifest.package.name)
779 }
780
781 fn resolve_version(&self, _: &SourceManifest) -> anyhow::Result<Option<Symbol>> {
782 Ok(None)
783 }
784}
785
786#[derive(Debug, Clone)]
787pub struct PackageDependencies {
788 pub published: BTreeMap<Symbol, ObjectID>,
790 pub unpublished: BTreeSet<Symbol>,
792 pub invalid: BTreeMap<Symbol, String>,
794 pub conflicting: BTreeMap<Symbol, (ObjectID, ObjectID)>,
798}
799
800pub fn gather_published_ids(
807 resolution_graph: &ResolvedGraph,
808 chain_id: Option<String>,
809) -> (Result<ObjectID, PublishedAtError>, PackageDependencies) {
810 let root = resolution_graph.root_package();
811
812 let mut published = BTreeMap::new();
813 let mut unpublished = BTreeSet::new();
814 let mut invalid = BTreeMap::new();
815 let mut conflicting = BTreeMap::new();
816 let mut published_at = Err(PublishedAtError::NotPresent);
817
818 for (name, package) in &resolution_graph.package_table {
819 let property = resolve_published_id(package, chain_id.clone());
820 if name == &root {
821 published_at = property;
823 continue;
824 }
825
826 match property {
827 Ok(id) => {
828 published.insert(*name, id);
829 }
830 Err(PublishedAtError::NotPresent) => {
831 unpublished.insert(*name);
832 }
833 Err(PublishedAtError::Invalid(value)) => {
834 invalid.insert(*name, value);
835 }
836 Err(PublishedAtError::Conflict {
837 id_lock,
838 id_manifest,
839 }) => {
840 conflicting.insert(*name, (id_lock, id_manifest));
841 }
842 };
843 }
844
845 (
846 published_at,
847 PackageDependencies {
848 published,
849 unpublished,
850 invalid,
851 conflicting,
852 },
853 )
854}
855
856pub fn published_at_property(manifest: &SourceManifest) -> Result<ObjectID, PublishedAtError> {
857 let Some(value) = manifest
858 .package
859 .custom_properties
860 .get(&Symbol::from(PUBLISHED_AT_MANIFEST_FIELD))
861 else {
862 return Err(PublishedAtError::NotPresent);
863 };
864
865 ObjectID::from_str(value.as_str()).map_err(|_| PublishedAtError::Invalid(value.to_owned()))
866}
867
868pub fn check_unpublished_dependencies(unpublished: &BTreeSet<Symbol>) -> Result<(), IotaError> {
869 if unpublished.is_empty() {
870 return Ok(());
871 };
872
873 let mut error_messages = unpublished
874 .iter()
875 .map(|name| {
876 format!(
877 "Package dependency \"{name}\" does not specify a published address \
878 (the Move.toml manifest for \"{name}\" does not contain a 'published-at' field, nor is there a 'published-id' in the Move.lock).",
879 )
880 })
881 .collect::<Vec<_>>();
882
883 error_messages.push(
884 "If this is intentional, you may use the --with-unpublished-dependencies flag to \
885 continue publishing these dependencies as part of your package (they won't be \
886 linked against existing packages on-chain)."
887 .into(),
888 );
889
890 Err(IotaError::ModulePublishFailure {
891 error: error_messages.join("\n"),
892 })
893}
894
895pub fn check_invalid_dependencies(invalid: &BTreeMap<Symbol, String>) -> Result<(), IotaError> {
896 if invalid.is_empty() {
897 return Ok(());
898 }
899
900 let error_messages = invalid
901 .iter()
902 .map(|(name, value)| {
903 format!(
904 "Package dependency \"{name}\" does not specify a valid published \
905 address: could not parse value \"{value}\" for 'published-at' field in Move.toml \
906 or 'published-id' in Move.lock file."
907 )
908 })
909 .collect::<Vec<_>>();
910
911 Err(IotaError::ModulePublishFailure {
912 error: error_messages.join("\n"),
913 })
914}