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, latest_system_packages},
19};
20use iota_sdk_types::{Address, ObjectId, move_package::MovePackage};
21use iota_types::{
22 error::{IotaError, IotaResult},
23 move_package::{
24 FnInfo, FnInfoKey, FnInfoMap, IotaAttribute, RuntimeModuleMetadata,
25 RuntimeModuleMetadataWrapper, get_authenticator_version_from_fun,
26 },
27};
28use iota_verifier::verifier as iota_bytecode_verifier;
29use move_binary_format::{
30 CompiledModule,
31 file_format_common::IOTA_METADATA_KEY,
32 normalized::{self, Type},
33};
34use move_bytecode_utils::{Modules, layout::SerdeLayoutBuilder, module_cache::GetModule};
35use move_compiler::{
36 compiled_unit::AnnotatedCompiledModule,
37 diagnostics::{Diagnostics, report_diagnostics_to_buffer, report_warnings},
38 editions::Edition,
39 linters::LINT_WARNING_PREFIX,
40 shared::files::MappedFiles,
41};
42use move_core_types::{
43 account_address::AccountAddress,
44 language_storage::{ModuleId, StructTag, TypeTag},
45};
46use move_package::{
47 BuildConfig as MoveBuildConfig, LintFlag,
48 compilation::{
49 build_plan::BuildPlan, compiled_package::CompiledPackage as MoveCompiledPackage,
50 },
51 package_hooks::{PackageHooks, PackageIdentifier},
52 resolution::{dependency_graph::DependencyGraph, resolution_graph::ResolvedGraph},
53 source_package::parsed_manifest::{
54 Dependencies, Dependency, DependencyKind, GitInfo, InternalDependency, OnChainInfo,
55 PackageName, SourceManifest,
56 },
57};
58use move_symbol_pool::Symbol;
59use serde_reflection::Registry;
60
61#[cfg(test)]
62#[path = "unit_tests/build_tests.rs"]
63mod build_tests;
64
65pub mod test_utils {
66 use std::path::PathBuf;
67
68 use crate::{BuildConfig, CompiledPackage, IotaPackageHooks};
69
70 pub fn compile_basics_package() -> CompiledPackage {
71 compile_example_package("../../examples/move/basics")
72 }
73
74 pub fn compile_managed_coin_package() -> CompiledPackage {
75 compile_example_package("../../crates/iota-core/src/unit_tests/data/managed_coin")
76 }
77
78 pub fn compile_example_package(relative_path: &str) -> CompiledPackage {
79 move_package::package_hooks::register_package_hooks(Box::new(IotaPackageHooks));
80 let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
81 path.push(relative_path);
82
83 BuildConfig::new_for_testing().build(&path).unwrap()
84 }
85}
86
87#[derive(Debug, Clone)]
90pub struct CompiledPackage {
91 pub package: MoveCompiledPackage,
92 pub published_at: Result<ObjectId, PublishedAtError>,
94 pub dependency_ids: PackageDependencies,
96 pub bytecode_deps: Vec<(PackageName, CompiledModule)>,
99 pub dependency_graph: DependencyGraph,
101}
102
103#[derive(Clone)]
105pub struct BuildConfig {
106 pub config: MoveBuildConfig,
107 pub run_bytecode_verifier: bool,
110 pub print_diags_to_stderr: bool,
112 pub chain_id: Option<String>,
115}
116
117impl BuildConfig {
118 pub fn new_for_testing() -> Self {
119 move_package::package_hooks::register_package_hooks(Box::new(IotaPackageHooks));
120
121 let install_dir = iota_common::tempdir().keep();
122 let config = MoveBuildConfig {
123 default_flavor: Some(move_compiler::editions::Flavor::Iota),
124 lock_file: Some(install_dir.join("Move.lock")),
125 install_dir: Some(install_dir),
126 lint_flag: LintFlag::LEVEL_NONE,
127 implicit_dependencies: Dependencies::new(),
129 silence_warnings: true,
130 ..MoveBuildConfig::default()
131 };
132 BuildConfig {
133 config,
134 run_bytecode_verifier: true,
135 print_diags_to_stderr: false,
136 chain_id: None,
137 }
138 }
139
140 pub fn new_for_testing_replace_addresses<I, S>(dep_original_addresses: I) -> Self
141 where
142 I: IntoIterator<Item = (S, ObjectId)>,
143 S: Into<String>,
144 {
145 let mut build_config = Self::new_for_testing();
146 for (addr_name, obj_id) in dep_original_addresses {
147 build_config
148 .config
149 .additional_named_addresses
150 .insert(addr_name.into(), AccountAddress::new(obj_id.into_bytes()));
151 }
152 build_config
153 }
154
155 fn fn_info(units: &[AnnotatedCompiledModule]) -> FnInfoMap {
156 let mut fn_info_map = BTreeMap::new();
157 for u in units {
158 let mod_addr = Address::new(u.named_module.address.into_bytes());
159 let mod_name = u.named_module.module.name().to_string();
160 let mod_is_test = u.attributes.is_test_or_test_only();
161 for (_, s, info) in &u.function_infos {
162 let fn_name = s.as_str().to_string();
163 let is_test = mod_is_test || info.attributes.is_test_or_test_only();
164 let authenticator_version = info.attributes.get_authenticator();
165 fn_info_map.insert(
166 FnInfoKey {
167 fn_name,
168 mod_name: mod_name.clone(),
169 mod_addr,
170 },
171 FnInfo {
172 is_test,
173 authenticator_version,
174 },
175 );
176 }
177 }
178
179 fn_info_map
180 }
181
182 fn compile_package<W: Write>(
183 resolution_graph: &ResolvedGraph,
184 writer: &mut W,
185 ) -> anyhow::Result<(MoveCompiledPackage, FnInfoMap)> {
186 let build_plan = BuildPlan::create(resolution_graph)?;
187 let mut fn_info = None;
188 let compiled_pkg = build_plan.compile_with_driver(writer, |compiler| {
189 let (files, units_res) = compiler.build()?;
190 match units_res {
191 Ok((units, warning_diags)) => {
192 decorate_warnings(warning_diags, Some(&files));
193 fn_info = Some(Self::fn_info(&units));
194 Ok((files, units))
195 }
196 Err(error_diags) => {
197 assert!(!error_diags.is_empty());
200 let diags_buf =
201 report_diagnostics_to_buffer(&files, error_diags, true);
202 if let Err(err) = std::io::stderr().write_all(&diags_buf) {
203 anyhow::bail!("Cannot output compiler diagnostics: {}", err);
204 }
205 anyhow::bail!("Compilation error");
206 }
207 }
208 })?;
209 Ok((compiled_pkg, fn_info.unwrap()))
210 }
211
212 pub fn build(self, path: &Path) -> IotaResult<CompiledPackage> {
216 let print_diags_to_stderr = self.print_diags_to_stderr;
217 let run_bytecode_verifier = self.run_bytecode_verifier;
218 let chain_id = self.chain_id.clone();
219 let resolution_graph = self.resolution_graph(path, chain_id.clone())?;
220 build_from_resolution_graph(
221 resolution_graph,
222 run_bytecode_verifier,
223 print_diags_to_stderr,
224 chain_id,
225 )
226 }
227
228 pub fn resolution_graph(
229 mut self,
230 path: &Path,
231 chain_id: Option<String>,
232 ) -> IotaResult<ResolvedGraph> {
233 if let Some(err_msg) = set_iota_flavor(&mut self.config) {
234 return Err(IotaError::ModuleBuildFailure { error: err_msg });
235 }
236
237 if self.print_diags_to_stderr {
238 self.config
239 .resolution_graph_for_package(path, chain_id, &mut std::io::stderr())
240 } else {
241 self.config
242 .resolution_graph_for_package(path, chain_id, &mut std::io::sink())
243 }
244 .map_err(|err| IotaError::ModuleBuildFailure {
245 error: format!("{err:?}"),
246 })
247 }
248}
249
250pub fn decorate_warnings(warning_diags: Diagnostics, files: Option<&MappedFiles>) {
254 let any_linter_warnings = warning_diags.any_with_prefix(LINT_WARNING_PREFIX);
255 let (filtered_diags_num, unique) =
256 warning_diags.filtered_source_diags_with_prefix(LINT_WARNING_PREFIX);
257 if let Some(f) = files {
258 report_warnings(f, warning_diags);
259 }
260 if any_linter_warnings {
261 eprintln!(
262 "Please report feedback on the linter warnings at https://github.com/iotaledger/iota/issues\n"
263 );
264 }
265 if filtered_diags_num > 0 {
266 eprintln!(
267 "Total number of linter warnings suppressed: {filtered_diags_num} (unique lints: {unique})"
268 );
269 }
270}
271
272pub fn set_iota_flavor(build_config: &mut MoveBuildConfig) -> Option<String> {
275 use move_compiler::editions::Flavor;
276
277 let flavor = build_config.default_flavor.get_or_insert(Flavor::Iota);
278 if flavor != &Flavor::Iota {
279 return Some(format!(
280 "The flavor of the Move compiler cannot be overridden with anything but \
281 \"{}\", but the default override was set to: \"{flavor}\"",
282 Flavor::Iota,
283 ));
284 }
285 None
286}
287
288pub fn build_from_resolution_graph(
289 resolution_graph: ResolvedGraph,
290 run_bytecode_verifier: bool,
291 print_diags_to_stderr: bool,
292 chain_id: Option<String>,
293) -> IotaResult<CompiledPackage> {
294 let (published_at, dependency_ids) = gather_published_ids(&resolution_graph, chain_id);
295
296 let bytecode_deps = collect_bytecode_deps(&resolution_graph)?;
299
300 let result = if print_diags_to_stderr {
302 BuildConfig::compile_package(&resolution_graph, &mut std::io::stderr())
303 } else {
304 BuildConfig::compile_package(&resolution_graph, &mut std::io::sink())
305 };
306
307 let (mut package, fn_info) = result.map_err(|error| IotaError::ModuleBuildFailure {
308 error: format!("{error:?}"),
310 })?;
311
312 fill_metadata(&mut package, &fn_info)?;
315
316 if run_bytecode_verifier {
317 verify_bytecode(&package, &fn_info)?;
318 }
319
320 Ok(CompiledPackage {
321 package,
322 published_at,
323 dependency_ids,
324 bytecode_deps,
325 dependency_graph: resolution_graph.graph,
326 })
327}
328
329fn collect_bytecode_deps(
331 resolution_graph: &ResolvedGraph,
332) -> IotaResult<Vec<(Symbol, CompiledModule)>> {
333 let mut bytecode_deps = vec![];
334 for (name, pkg) in resolution_graph.package_table.iter() {
335 if !pkg
336 .get_sources(&resolution_graph.build_options)
337 .unwrap()
338 .is_empty()
339 {
340 continue;
341 }
342 let modules =
343 pkg.get_bytecodes_bytes()
344 .map_err(|error| IotaError::ModuleDeserializationFailure {
345 error: format!(
346 "Deserializing bytecode dependency for package {name}: {error:?}"
347 ),
348 })?;
349 for module in modules {
350 let module =
351 CompiledModule::deserialize_with_defaults(module.as_ref()).map_err(|error| {
352 IotaError::ModuleDeserializationFailure {
353 error: format!(
354 "Deserializing bytecode dependency for package {name}: {error:?}"
355 ),
356 }
357 })?;
358 bytecode_deps.push((*name, module));
359 }
360 }
361
362 Ok(bytecode_deps)
363}
364
365fn fill_metadata(package: &mut MoveCompiledPackage, fn_info_map: &FnInfoMap) -> IotaResult<()> {
367 for module in package
368 .root_compiled_units
369 .iter_mut()
370 .map(|unit| &mut unit.unit.module)
371 {
372 let mut runtime_metadata = RuntimeModuleMetadata::default();
373 for fn_def in &module.function_defs {
374 let fn_handle = module.function_handle_at(fn_def.function);
375 let fn_name = module.identifier_at(fn_handle.name);
376 if let Some(version) =
377 get_authenticator_version_from_fun(fn_name.as_str(), module, fn_info_map)
378 {
379 runtime_metadata.add_function_attribute(
380 fn_name.to_string(),
381 IotaAttribute::authenticator_attribute(version),
382 );
383 };
384 }
385 if !runtime_metadata.is_empty() {
386 module.metadata.push(move_core_types::metadata::Metadata {
387 key: IOTA_METADATA_KEY.to_vec(),
388 value: RuntimeModuleMetadataWrapper::from(runtime_metadata).to_bcs_bytes(),
389 });
390 }
391 }
392 Ok(())
393}
394
395fn verify_bytecode(package: &MoveCompiledPackage, fn_info: &FnInfoMap) -> IotaResult<()> {
397 let compiled_modules = package.root_modules_map();
398 for m in compiled_modules.iter_modules() {
399 move_bytecode_verifier::verify_module_unmetered(m).map_err(|err| {
400 IotaError::ModuleVerificationFailure {
401 error: err.to_string(),
402 }
403 })?;
404 iota_bytecode_verifier::iota_verify_module_unmetered(m, fn_info)?;
405 }
406 Ok(())
409}
410
411impl CompiledPackage {
412 pub fn get_modules(&self) -> impl Iterator<Item = &CompiledModule> {
417 self.package.root_modules().map(|m| &m.unit.module)
418 }
419
420 pub fn into_modules(self) -> Vec<CompiledModule> {
425 self.package
426 .root_compiled_units
427 .into_iter()
428 .map(|m| m.unit.module)
429 .collect()
430 }
431
432 pub fn get_dependent_modules(&self) -> impl Iterator<Item = &CompiledModule> {
436 self.package
437 .deps_compiled_units
438 .iter()
439 .map(|(_, m)| &m.unit.module)
440 .chain(self.bytecode_deps.iter().map(|(_, m)| m))
441 }
442
443 pub fn get_modules_and_deps(&self) -> impl Iterator<Item = &CompiledModule> {
447 self.package
448 .all_modules()
449 .map(|m| &m.unit.module)
450 .chain(self.bytecode_deps.iter().map(|(_, m)| m))
451 }
452
453 pub fn get_dependency_sorted_modules(
459 &self,
460 with_unpublished_deps: bool,
461 ) -> Vec<CompiledModule> {
462 let all_modules = Modules::new(self.get_modules_and_deps());
463
464 let modules = all_modules.compute_topological_order().unwrap();
466
467 if with_unpublished_deps {
468 modules
472 .filter(|module| module.address() == &AccountAddress::ZERO)
473 .cloned()
474 .collect()
475 } else {
476 let self_modules: HashSet<_> = self
481 .package
482 .root_modules_map()
483 .iter_modules()
484 .iter()
485 .map(|m| m.self_id())
486 .collect();
487
488 modules
489 .filter(|module| self_modules.contains(&module.self_id()))
490 .cloned()
491 .collect()
492 }
493 }
494
495 pub fn get_dependency_storage_package_ids(&self) -> Vec<ObjectId> {
499 self.dependency_ids.published.values().copied().collect()
500 }
501
502 pub fn get_package_digest(&self, with_unpublished_deps: bool) -> [u8; 32] {
504 MovePackage::compute_digest_for_modules_and_deps(
505 &self.get_package_bytes(with_unpublished_deps),
506 self.dependency_ids.published.values(),
507 )
508 .into_inner()
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().as_ref() == Address::SYSTEM.as_bytes())
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().as_ref() == Address::FRAMEWORK.as_bytes())
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().as_ref() == Address::STD.as_bytes())
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().as_ref() == Address::STARDUST.as_bytes())
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 published_at.is_system_package()
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}
1007
1008pub fn local_implicit_deps(packages: &SystemPackagesVersion) -> Dependencies {
1012 let repo_root = Path::new(env!("CARGO_MANIFEST_DIR"))
1017 .parent()
1018 .and_then(Path::parent)
1019 .expect("iota-move-build manifest dir should have a grandparent");
1020 packages
1021 .packages
1022 .iter()
1023 .map(|package| {
1024 (
1025 package.package_name.clone().into(),
1026 Dependency::Internal(InternalDependency {
1027 kind: DependencyKind::Local(repo_root.join(&package.repo_path)),
1028 subst: None,
1029 digest: None,
1030 dep_override: true,
1031 }),
1032 )
1033 })
1034 .collect()
1035}
1036
1037pub fn local_implicit_deps_latest() -> Dependencies {
1041 local_implicit_deps(latest_system_packages())
1042}