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 pool = &mut normalized::RcPool::new();
517 let mut package_types = BTreeSet::new();
518 for m in self.get_modules() {
519 let normalized_m = normalized::Module::new(pool, m, false);
520 'structs: for (name, s) in normalized_m.structs {
522 let mut dummy_type_parameters = Vec::new();
523 for t in &s.type_parameters {
524 if t.is_phantom {
525 dummy_type_parameters.push(TypeTag::Signer)
532 } else {
533 continue 'structs;
536 }
537 }
538 debug_assert!(dummy_type_parameters.len() == s.type_parameters.len());
539 package_types.insert(StructTag {
540 address: *m.address(),
541 module: m.name().to_owned(),
542 name: name.as_ident_str().to_owned(),
543 type_params: dummy_type_parameters,
544 });
545 }
546 for (_name, f) in normalized_m.functions {
548 if f.is_entry {
549 for t in &*f.parameters {
550 let tag_opt = match &**t {
551 Type::Address
552 | Type::Bool
553 | Type::Signer
554 | Type::TypeParameter(_)
555 | Type::U8
556 | Type::U16
557 | Type::U32
558 | Type::U64
559 | Type::U128
560 | Type::U256
561 | Type::Vector(_) => continue,
562 Type::Reference(_, inner) => inner.to_struct_tag(pool),
563 Type::Datatype(_) => t.to_struct_tag(pool),
564 };
565 if let Some(tag) = tag_opt {
566 package_types.insert(tag);
567 }
568 }
569 }
570 }
571 }
572 let mut layout_builder = SerdeLayoutBuilder::new(self);
573 for tag in &package_types {
574 layout_builder.build_data_layout(tag).unwrap();
575 }
576 layout_builder.into_registry()
577 }
578
579 pub fn is_system_package(&self) -> bool {
581 let Ok(published_at) = self.published_at else {
583 return false;
584 };
585
586 is_system_package(published_at)
587 }
588
589 pub fn published_root_module(&self) -> Option<&CompiledModule> {
592 self.package.root_compiled_units.iter().find_map(|unit| {
593 if unit.unit.module.self_id().address() != &AccountAddress::ZERO {
594 Some(&unit.unit.module)
595 } else {
596 None
597 }
598 })
599 }
600
601 pub fn verify_unpublished_dependencies(
602 &self,
603 unpublished_deps: &BTreeSet<Symbol>,
604 ) -> IotaResult<()> {
605 if unpublished_deps.is_empty() {
606 return Ok(());
607 }
608
609 let errors = self
610 .package
611 .deps_compiled_units
612 .iter()
613 .filter_map(|(p, m)| {
614 if !unpublished_deps.contains(p) || m.unit.module.address() == &AccountAddress::ZERO
615 {
616 return None;
617 }
618 Some(format!(
619 " - {}::{} in dependency {}",
620 m.unit.module.address(),
621 m.unit.name,
622 p
623 ))
624 })
625 .collect::<Vec<String>>();
626
627 if errors.is_empty() {
628 return Ok(());
629 }
630
631 let mut error_message = vec![];
632 error_message.push(
633 "The following modules in package dependencies set a non-zero self-address:".into(),
634 );
635 error_message.extend(errors);
636 error_message.push(
637 "If these packages really are unpublished, their self-addresses should be set \
638 to \"0x0\" in the [addresses] section of the manifest when publishing. If they \
639 are already published, ensure they specify the address in the `published-at` of \
640 their Move.toml manifest."
641 .into(),
642 );
643
644 Err(IotaError::ModulePublishFailure {
645 error: error_message.join("\n"),
646 })
647 }
648
649 pub fn get_published_dependencies_ids(&self) -> Vec<ObjectID> {
650 self.dependency_ids.published.values().cloned().collect()
651 }
652
653 pub fn find_immediate_deps_pkgs_to_keep(
656 &self,
657 with_unpublished_deps: bool,
658 ) -> Result<BTreeMap<Symbol, ObjectID>, anyhow::Error> {
659 let root_modules: Vec<_> = if with_unpublished_deps {
662 self.package
663 .all_compiled_units_with_source()
664 .filter(|m| m.unit.address.into_inner() == AccountAddress::ZERO)
665 .map(|x| x.unit.clone())
666 .collect()
667 } else {
668 self.package
669 .root_modules()
670 .map(|x| x.unit.clone())
671 .collect()
672 };
673
674 let mut pkgs_to_keep: BTreeSet<Symbol> = BTreeSet::new();
678 let module_to_pkg_name: BTreeMap<_, _> = self
679 .package
680 .all_modules()
681 .map(|m| (m.unit.module.self_id(), m.unit.package_name))
682 .collect();
683
684 for module in &root_modules {
685 let immediate_deps = module.module.immediate_dependencies();
686 for dep in immediate_deps {
687 if let Some(pkg_name) = module_to_pkg_name.get(&dep) {
688 let Some(pkg_name) = pkg_name else {
689 bail!("Expected a package name but it's None")
690 };
691 pkgs_to_keep.insert(*pkg_name);
692 }
693 }
694 }
695
696 pkgs_to_keep.extend(self.bytecode_deps.iter().map(|(name, _)| *name));
700
701 Ok(self
704 .dependency_ids
705 .clone()
706 .published
707 .into_iter()
708 .filter(|(pkg_name, _)| pkgs_to_keep.contains(pkg_name))
709 .collect())
710 }
711}
712
713pub fn implicit_deps(packages: &SystemPackagesVersion) -> Dependencies {
717 packages
718 .packages
719 .iter()
720 .map(|package| {
721 (
722 package.package_name.clone().into(),
723 Dependency::Internal(InternalDependency {
724 kind: DependencyKind::Git(GitInfo {
725 git_url: SYSTEM_GIT_REPO.into(),
726 git_rev: packages.git_revision.clone().into(),
727 subdir: package.repo_path.clone().into(),
728 }),
729 subst: None,
730 digest: None,
731 dep_override: true,
732 }),
733 )
734 })
735 .collect()
736}
737
738impl GetModule for CompiledPackage {
739 type Error = anyhow::Error;
740 type Item = CompiledModule;
743
744 fn get_module_by_id(&self, id: &ModuleId) -> Result<Option<Self::Item>, Self::Error> {
745 Ok(self.package.all_modules_map().get_module(id).ok().cloned())
746 }
747}
748
749pub const PUBLISHED_AT_MANIFEST_FIELD: &str = "published-at";
750
751pub struct IotaPackageHooks;
752
753impl PackageHooks for IotaPackageHooks {
754 fn custom_package_info_fields(&self) -> Vec<String> {
755 vec![
756 PUBLISHED_AT_MANIFEST_FIELD.to_string(),
757 "version".to_string(),
759 ]
760 }
761
762 fn resolve_on_chain_dependency(
763 &self,
764 _dep_name: move_symbol_pool::Symbol,
765 _info: &OnChainInfo,
766 ) -> anyhow::Result<()> {
767 Ok(())
768 }
769
770 fn custom_resolve_pkg_id(
771 &self,
772 manifest: &SourceManifest,
773 ) -> anyhow::Result<PackageIdentifier> {
774 if (!cfg!(debug_assertions) || cfg!(test))
775 && manifest.package.edition == Some(Edition::DEVELOPMENT)
776 {
777 return Err(Edition::DEVELOPMENT.unknown_edition_error());
778 }
779 Ok(manifest.package.name)
780 }
781
782 fn resolve_version(&self, _: &SourceManifest) -> anyhow::Result<Option<Symbol>> {
783 Ok(None)
784 }
785}
786
787#[derive(Debug, Clone)]
788pub struct PackageDependencies {
789 pub published: BTreeMap<Symbol, ObjectID>,
791 pub unpublished: BTreeSet<Symbol>,
793 pub invalid: BTreeMap<Symbol, String>,
795 pub conflicting: BTreeMap<Symbol, (ObjectID, ObjectID)>,
799}
800
801pub fn gather_published_ids(
808 resolution_graph: &ResolvedGraph,
809 chain_id: Option<String>,
810) -> (Result<ObjectID, PublishedAtError>, PackageDependencies) {
811 let root = resolution_graph.root_package();
812
813 let mut published = BTreeMap::new();
814 let mut unpublished = BTreeSet::new();
815 let mut invalid = BTreeMap::new();
816 let mut conflicting = BTreeMap::new();
817 let mut published_at = Err(PublishedAtError::NotPresent);
818
819 for (name, package) in &resolution_graph.package_table {
820 let property = resolve_published_id(package, chain_id.clone());
821 if name == &root {
822 published_at = property;
824 continue;
825 }
826
827 match property {
828 Ok(id) => {
829 published.insert(*name, id);
830 }
831 Err(PublishedAtError::NotPresent) => {
832 unpublished.insert(*name);
833 }
834 Err(PublishedAtError::Invalid(value)) => {
835 invalid.insert(*name, value);
836 }
837 Err(PublishedAtError::Conflict {
838 id_lock,
839 id_manifest,
840 }) => {
841 conflicting.insert(*name, (id_lock, id_manifest));
842 }
843 };
844 }
845
846 (
847 published_at,
848 PackageDependencies {
849 published,
850 unpublished,
851 invalid,
852 conflicting,
853 },
854 )
855}
856
857pub fn published_at_property(manifest: &SourceManifest) -> Result<ObjectID, PublishedAtError> {
858 let Some(value) = manifest
859 .package
860 .custom_properties
861 .get(&Symbol::from(PUBLISHED_AT_MANIFEST_FIELD))
862 else {
863 return Err(PublishedAtError::NotPresent);
864 };
865
866 ObjectID::from_str(value.as_str()).map_err(|_| PublishedAtError::Invalid(value.to_owned()))
867}
868
869pub fn check_unpublished_dependencies(unpublished: &BTreeSet<Symbol>) -> Result<(), IotaError> {
870 if unpublished.is_empty() {
871 return Ok(());
872 };
873
874 let mut error_messages = unpublished
875 .iter()
876 .map(|name| {
877 format!(
878 "Package dependency \"{name}\" does not specify a published address \
879 (the Move.toml manifest for \"{name}\" does not contain a 'published-at' field, nor is there a 'published-id' in the Move.lock).",
880 )
881 })
882 .collect::<Vec<_>>();
883
884 error_messages.push(
885 "If this is intentional, you may use the --with-unpublished-dependencies flag to \
886 continue publishing these dependencies as part of your package (they won't be \
887 linked against existing packages on-chain)."
888 .into(),
889 );
890
891 Err(IotaError::ModulePublishFailure {
892 error: error_messages.join("\n"),
893 })
894}
895
896pub fn check_invalid_dependencies(invalid: &BTreeMap<Symbol, String>) -> Result<(), IotaError> {
897 if invalid.is_empty() {
898 return Ok(());
899 }
900
901 let error_messages = invalid
902 .iter()
903 .map(|(name, value)| {
904 format!(
905 "Package dependency \"{name}\" does not specify a valid published \
906 address: could not parse value \"{value}\" for 'published-at' field in Move.toml \
907 or 'published-id' in Move.lock file."
908 )
909 })
910 .collect::<Vec<_>>();
911
912 Err(IotaError::ModulePublishFailure {
913 error: error_messages.join("\n"),
914 })
915}