1extern crate move_ir_types;
6
7use std::{
8 collections::{BTreeMap, BTreeSet, HashSet, VecDeque},
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, MovePackage, ObjectId, Version};
21pub use iota_types::move_package::ProtocolBuildConfig;
25use iota_types::{
26 error::{IotaError, IotaResult},
27 move_package::{
28 FnInfo, FnInfoKey, FnInfoMap, IotaAttribute, IotaAttributeV1, IotaAttributeV2,
29 RuntimeModuleMetadata, RuntimeModuleMetadataWrapper, get_authenticator_version_from_fun,
30 is_view_function_from_fn_info,
31 },
32};
33use iota_verifier::verifier as iota_bytecode_verifier;
34use move_binary_format::{
35 CompiledModule,
36 file_format_common::IOTA_METADATA_KEY,
37 normalized::{self, Type},
38};
39use move_bytecode_utils::{Modules, layout::SerdeLayoutBuilder, module_cache::GetModule};
40use move_compiler::{
41 compiled_unit::AnnotatedCompiledModule,
42 diagnostics::{Diagnostics, report_diagnostics_to_buffer, report_warnings},
43 editions::Edition,
44 linters::LINT_WARNING_PREFIX,
45 shared::files::MappedFiles,
46};
47use move_core_types::{
48 account_address::AccountAddress,
49 language_storage::{ModuleId, StructTag, TypeTag},
50};
51use move_package::{
52 BuildConfig as MoveBuildConfig, LintFlag,
53 compilation::{
54 build_plan::BuildPlan, compiled_package::CompiledPackage as MoveCompiledPackage,
55 },
56 package_hooks::{PackageHooks, PackageIdentifier},
57 resolution::{dependency_graph::DependencyGraph, resolution_graph::ResolvedGraph},
58 source_package::parsed_manifest::{
59 Dependencies, Dependency, DependencyKind, GitInfo, InternalDependency, OnChainInfo,
60 PackageName, SourceManifest,
61 },
62};
63use move_symbol_pool::Symbol;
64use serde_reflection::Registry;
65
66#[cfg(test)]
67#[path = "unit_tests/build_tests.rs"]
68mod build_tests;
69
70pub mod test_utils {
71 use std::path::PathBuf;
72
73 use iota_types::supported_protocol_versions::ProtocolConfig;
74
75 use crate::{BuildConfig, CompiledPackage, IotaPackageHooks, ProtocolBuildConfig};
76
77 pub fn compile_basics_package() -> CompiledPackage {
78 compile_example_package("../../examples/move/basics")
79 }
80
81 pub fn compile_managed_coin_package() -> CompiledPackage {
82 compile_example_package("../../crates/iota-core/src/unit_tests/data/managed_coin")
83 }
84
85 pub fn compile_example_package(relative_path: &str) -> CompiledPackage {
86 move_package::package_hooks::register_package_hooks(Box::new(IotaPackageHooks));
87 let protocol_config = ProtocolConfig::get_for_max_version_UNSAFE();
88 let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
89 path.push(relative_path);
90
91 let mut build_config = BuildConfig::new_for_testing();
92 build_config.protocol_build_config = ProtocolBuildConfig::from(&protocol_config);
93 build_config.build(&path).unwrap()
94 }
95}
96
97#[derive(Debug, Clone)]
100pub struct CompiledPackage {
101 pub package: MoveCompiledPackage,
102 pub published_at: Result<ObjectId, PublishedAtError>,
104 pub dependency_ids: PackageDependencies,
106 pub bytecode_deps: Vec<(PackageName, CompiledModule)>,
109 pub dependency_graph: DependencyGraph,
111}
112
113#[derive(Clone)]
115pub struct BuildConfig {
116 pub config: MoveBuildConfig,
117 pub run_bytecode_verifier: bool,
120 pub print_diags_to_stderr: bool,
122 pub chain_id: Option<String>,
125 pub protocol_build_config: ProtocolBuildConfig,
128}
129
130impl BuildConfig {
131 pub fn new_for_testing() -> Self {
132 move_package::package_hooks::register_package_hooks(Box::new(IotaPackageHooks));
133
134 let install_dir = iota_common::tempdir().keep();
135 let config = MoveBuildConfig {
136 default_flavor: Some(move_compiler::editions::Flavor::Iota),
137 lock_file: Some(install_dir.join("Move.lock")),
138 install_dir: Some(install_dir),
139 lint_flag: LintFlag::LEVEL_NONE,
140 implicit_dependencies: Dependencies::new(),
142 silence_warnings: true,
143 ..MoveBuildConfig::default()
144 };
145 BuildConfig {
146 config,
147 run_bytecode_verifier: true,
148 print_diags_to_stderr: false,
149 chain_id: None,
150 protocol_build_config: ProtocolBuildConfig::default(),
151 }
152 }
153
154 pub fn new_for_testing_replace_addresses<I, S>(dep_original_addresses: I) -> Self
155 where
156 I: IntoIterator<Item = (S, ObjectId)>,
157 S: Into<String>,
158 {
159 let mut build_config = Self::new_for_testing();
160 for (addr_name, obj_id) in dep_original_addresses {
161 build_config
162 .config
163 .additional_named_addresses
164 .insert(addr_name.into(), AccountAddress::new(obj_id.into_bytes()));
165 }
166 build_config
167 }
168
169 fn fn_info(units: &[AnnotatedCompiledModule]) -> FnInfoMap {
170 let mut fn_info_map = BTreeMap::new();
171 for u in units {
172 let mod_addr = Address::new(u.named_module.address.into_bytes());
173 let mod_name = u.named_module.module.name().to_string();
174 let mod_is_test = u.attributes.is_test_or_test_only();
175 for (_, s, info) in &u.function_infos {
176 let fn_name = s.as_str().to_string();
177 let is_test = mod_is_test || info.attributes.is_test_or_test_only();
178 let authenticator_version = info.attributes.get_authenticator();
179 let is_view = info.attributes.is_view();
180 fn_info_map.insert(
181 FnInfoKey {
182 fn_name,
183 mod_name: mod_name.clone(),
184 mod_addr,
185 },
186 FnInfo {
187 is_test,
188 authenticator_version,
189 is_view,
190 },
191 );
192 }
193 }
194
195 fn_info_map
196 }
197
198 fn compile_package<W: Write>(
199 resolution_graph: &ResolvedGraph,
200 writer: &mut W,
201 ) -> anyhow::Result<(MoveCompiledPackage, FnInfoMap)> {
202 let build_plan = BuildPlan::create(resolution_graph)?;
203 let mut fn_info = None;
204 let compiled_pkg = build_plan.compile_with_driver(writer, |compiler| {
205 let (files, units_res) = compiler.build()?;
206 match units_res {
207 Ok((units, warning_diags)) => {
208 decorate_warnings(warning_diags, Some(&files));
209 fn_info = Some(Self::fn_info(&units));
210 Ok((files, units))
211 }
212 Err(error_diags) => {
213 assert!(!error_diags.is_empty());
216 let diags_buf =
217 report_diagnostics_to_buffer(&files, error_diags, true);
218 if let Err(err) = std::io::stderr().write_all(&diags_buf) {
219 anyhow::bail!("Cannot output compiler diagnostics: {err}");
220 }
221 anyhow::bail!("Compilation error");
222 }
223 }
224 })?;
225 Ok((compiled_pkg, fn_info.unwrap()))
226 }
227
228 pub fn build(self, path: &Path) -> IotaResult<CompiledPackage> {
232 let print_diags_to_stderr = self.print_diags_to_stderr;
233 let run_bytecode_verifier = self.run_bytecode_verifier;
234 let chain_id = self.chain_id.clone();
235 let protocol_build_config = self.protocol_build_config;
236 let resolution_graph = self.resolution_graph(path, chain_id.clone())?;
237 build_from_resolution_graph(
238 resolution_graph,
239 run_bytecode_verifier,
240 print_diags_to_stderr,
241 chain_id,
242 &protocol_build_config,
243 )
244 }
245
246 pub fn resolution_graph(
247 mut self,
248 path: &Path,
249 chain_id: Option<String>,
250 ) -> IotaResult<ResolvedGraph> {
251 if let Some(err_msg) = set_iota_flavor(&mut self.config) {
252 return Err(IotaError::ModuleBuildFailure { error: err_msg });
253 }
254
255 if self.print_diags_to_stderr {
256 self.config
257 .resolution_graph_for_package(path, chain_id, &mut std::io::stderr())
258 } else {
259 self.config
260 .resolution_graph_for_package(path, chain_id, &mut std::io::sink())
261 }
262 .map_err(|err| IotaError::ModuleBuildFailure {
263 error: format!("{err:?}"),
264 })
265 }
266}
267
268pub fn decorate_warnings(warning_diags: Diagnostics, files: Option<&MappedFiles>) {
272 let any_linter_warnings = warning_diags.any_with_prefix(LINT_WARNING_PREFIX);
273 let (filtered_diags_num, unique) =
274 warning_diags.filtered_source_diags_with_prefix(LINT_WARNING_PREFIX);
275 if let Some(f) = files {
276 report_warnings(f, warning_diags);
277 }
278 if any_linter_warnings {
279 eprintln!(
280 "Please report feedback on the linter warnings at https://github.com/iotaledger/iota/issues\n"
281 );
282 }
283 if filtered_diags_num > 0 {
284 eprintln!(
285 "Total number of linter warnings suppressed: {filtered_diags_num} (unique lints: {unique})"
286 );
287 }
288}
289
290pub fn set_iota_flavor(build_config: &mut MoveBuildConfig) -> Option<String> {
293 use move_compiler::editions::Flavor;
294
295 let flavor = build_config.default_flavor.get_or_insert(Flavor::Iota);
296 if flavor != &Flavor::Iota {
297 return Some(format!(
298 "The flavor of the Move compiler cannot be overridden with anything but \
299 \"{}\", but the default override was set to: \"{flavor}\"",
300 Flavor::Iota,
301 ));
302 }
303 None
304}
305
306pub fn build_from_resolution_graph(
307 resolution_graph: ResolvedGraph,
308 run_bytecode_verifier: bool,
309 print_diags_to_stderr: bool,
310 chain_id: Option<String>,
311 protocol_build_config: &ProtocolBuildConfig,
312) -> IotaResult<CompiledPackage> {
313 let (published_at, dependency_ids) = gather_published_ids(&resolution_graph, chain_id);
314
315 let bytecode_deps = collect_bytecode_deps(&resolution_graph)?;
318
319 let result = if print_diags_to_stderr {
321 BuildConfig::compile_package(&resolution_graph, &mut std::io::stderr())
322 } else {
323 BuildConfig::compile_package(&resolution_graph, &mut std::io::sink())
324 };
325
326 let (mut package, fn_info) = result.map_err(|error| IotaError::ModuleBuildFailure {
327 error: format!("{error:?}"),
329 })?;
330
331 fill_metadata(&mut package, &fn_info, protocol_build_config)?;
334
335 if run_bytecode_verifier {
336 verify_bytecode(&package, &fn_info, protocol_build_config)?;
337 }
338
339 Ok(CompiledPackage {
340 package,
341 published_at,
342 dependency_ids,
343 bytecode_deps,
344 dependency_graph: resolution_graph.graph,
345 })
346}
347
348fn collect_bytecode_deps(
350 resolution_graph: &ResolvedGraph,
351) -> IotaResult<Vec<(Symbol, CompiledModule)>> {
352 let mut bytecode_deps = vec![];
353 for (name, pkg) in resolution_graph.package_table.iter() {
354 if !pkg
355 .get_sources(&resolution_graph.build_options)
356 .unwrap()
357 .is_empty()
358 {
359 continue;
360 }
361 let modules =
362 pkg.get_bytecodes_bytes()
363 .map_err(|error| IotaError::ModuleDeserializationFailure {
364 error: format!(
365 "Deserializing bytecode dependency for package {name}: {error:?}"
366 ),
367 })?;
368 for module in modules {
369 let module =
370 CompiledModule::deserialize_with_defaults(module.as_ref()).map_err(|error| {
371 IotaError::ModuleDeserializationFailure {
372 error: format!(
373 "Deserializing bytecode dependency for package {name}: {error:?}"
374 ),
375 }
376 })?;
377 bytecode_deps.push((*name, module));
378 }
379 }
380
381 Ok(bytecode_deps)
382}
383
384fn fill_metadata(
386 package: &mut MoveCompiledPackage,
387 fn_info_map: &FnInfoMap,
388 protocol_build_config: &ProtocolBuildConfig,
389) -> IotaResult<()> {
390 for module in package
391 .root_compiled_units
392 .iter_mut()
393 .map(|unit| &mut unit.unit.module)
394 {
395 let mut runtime_metadata = if protocol_build_config.allow_view_function {
399 RuntimeModuleMetadata::v2()
400 } else {
401 RuntimeModuleMetadata::v1()
402 };
403 for fn_def in &module.function_defs {
404 let fn_handle = module.function_handle_at(fn_def.function);
405 let fn_name = module.identifier_at(fn_handle.name);
406 if let Some(version) =
407 get_authenticator_version_from_fun(fn_name.as_str(), module, fn_info_map)
408 {
409 let attribute = if protocol_build_config.allow_view_function {
410 IotaAttribute::V2(IotaAttributeV2::authenticator_attribute(version))
411 } else {
412 IotaAttribute::V1(IotaAttributeV1::authenticator_attribute(version))
413 };
414 runtime_metadata.add_function_attribute(fn_name.to_string(), attribute);
415 };
416 if is_view_function_from_fn_info(fn_name, module, fn_info_map) {
417 if protocol_build_config.allow_view_function {
418 runtime_metadata.add_function_attribute(
419 fn_name.to_string(),
420 IotaAttribute::V2(IotaAttributeV2::view_attribute()),
421 );
422 } else {
423 eprintln!(
430 "warning: function '{}::{}' is marked `#[view]`, but the target \
431 network protocol does not support view-function metadata; the `View` \
432 attribute will not be published",
433 module.name(),
434 fn_name,
435 );
436 }
437 }
438 }
439 if !runtime_metadata.is_empty() {
440 module.metadata.push(move_core_types::metadata::Metadata {
441 key: IOTA_METADATA_KEY.to_vec(),
442 value: RuntimeModuleMetadataWrapper::from(runtime_metadata).to_bcs_bytes(),
443 });
444 }
445 }
446 Ok(())
447}
448
449fn verify_bytecode(
451 package: &MoveCompiledPackage,
452 fn_info: &FnInfoMap,
453 protocol_build_config: &ProtocolBuildConfig,
454) -> IotaResult<()> {
455 let compiled_modules = package.root_modules_map();
456 for m in compiled_modules.iter_modules() {
457 move_bytecode_verifier::verify_module_unmetered(m).map_err(|err| {
458 IotaError::ModuleVerificationFailure {
459 error: err.to_string(),
460 }
461 })?;
462 iota_bytecode_verifier::iota_verify_module_unmetered(m, fn_info, protocol_build_config)?;
466 }
467 Ok(())
470}
471
472impl CompiledPackage {
473 pub fn get_modules(&self) -> impl Iterator<Item = &CompiledModule> {
478 self.package.root_modules().map(|m| &m.unit.module)
479 }
480
481 pub fn into_modules(self) -> Vec<CompiledModule> {
486 self.package
487 .root_compiled_units
488 .into_iter()
489 .map(|m| m.unit.module)
490 .collect()
491 }
492
493 pub fn get_dependent_modules(&self) -> impl Iterator<Item = &CompiledModule> {
497 self.package
498 .deps_compiled_units
499 .iter()
500 .map(|(_, m)| &m.unit.module)
501 .chain(self.bytecode_deps.iter().map(|(_, m)| m))
502 }
503
504 pub fn get_modules_and_deps(&self) -> impl Iterator<Item = &CompiledModule> {
508 self.package
509 .all_modules()
510 .map(|m| &m.unit.module)
511 .chain(self.bytecode_deps.iter().map(|(_, m)| m))
512 }
513
514 pub fn get_dependency_sorted_modules(
520 &self,
521 with_unpublished_deps: bool,
522 ) -> Vec<CompiledModule> {
523 let all_modules = Modules::new(self.get_modules_and_deps());
524
525 let modules = all_modules.compute_topological_order().unwrap();
527
528 if with_unpublished_deps {
529 modules
533 .filter(|module| module.address() == &AccountAddress::ZERO)
534 .cloned()
535 .collect()
536 } else {
537 let self_modules: HashSet<_> = self
542 .package
543 .root_modules_map()
544 .iter_modules()
545 .iter()
546 .map(|m| m.self_id())
547 .collect();
548
549 modules
550 .filter(|module| self_modules.contains(&module.self_id()))
551 .cloned()
552 .collect()
553 }
554 }
555
556 pub fn get_dependency_storage_package_ids(&self) -> Vec<ObjectId> {
560 self.dependency_ids.published.values().copied().collect()
561 }
562
563 pub fn get_package_digest(&self, with_unpublished_deps: bool) -> [u8; 32] {
565 MovePackage::compute_digest_for_modules_and_deps(
566 &self.get_package_bytes(with_unpublished_deps),
567 self.dependency_ids.published.values(),
568 )
569 .into_bytes()
570 }
571
572 pub fn get_package_bytes(&self, with_unpublished_deps: bool) -> Vec<Vec<u8>> {
575 self.get_dependency_sorted_modules(with_unpublished_deps)
576 .iter()
577 .map(|m| {
578 let mut bytes = Vec::new();
579 m.serialize_with_version(m.version, &mut bytes).unwrap(); bytes
581 })
582 .collect()
583 }
584
585 pub fn get_package_base64(&self, with_unpublished_deps: bool) -> Vec<Base64> {
588 self.get_package_bytes(with_unpublished_deps)
589 .iter()
590 .map(|b| Base64::from_bytes(b))
591 .collect()
592 }
593
594 pub fn published_size(&self, with_unpublished_deps: bool, dep_count: usize) -> u64 {
608 const LINKAGE_ENTRY_SIZE: usize =
611 ObjectId::LENGTH + ObjectId::LENGTH + std::mem::size_of::<Version>();
612
613 let mut size = std::mem::size_of::<Version>();
614
615 for module in self.get_dependency_sorted_modules(with_unpublished_deps) {
616 let module_name_len = module.name().as_str().len();
617
618 let mut bytes = Vec::new();
619 module
621 .serialize_with_version(module.version, &mut bytes)
622 .unwrap();
623 size += module_name_len + bytes.len();
624
625 for struct_def in module.struct_defs() {
627 let handle = module.datatype_handle_at(struct_def.struct_handle);
628 size += module_name_len
629 + module.identifier_at(handle.name).as_str().len()
630 + ObjectId::LENGTH;
631 }
632 for enum_def in module.enum_defs() {
633 let handle = module.datatype_handle_at(enum_def.enum_handle);
634 size += module_name_len
635 + module.identifier_at(handle.name).as_str().len()
636 + ObjectId::LENGTH;
637 }
638 }
639
640 size += dep_count * LINKAGE_ENTRY_SIZE;
641 size as u64
642 }
643
644 pub fn linkage_dependency_count(&self) -> usize {
654 let mut module_pkg: BTreeMap<ModuleId, PackageName> = BTreeMap::new();
656 for unit in self.package.all_modules() {
657 if let Some(pkg) = unit.unit.package_name {
658 module_pkg.insert(unit.unit.module.self_id(), pkg);
659 }
660 }
661 for (pkg, module) in &self.bytecode_deps {
662 module_pkg.insert(module.self_id(), *pkg);
663 }
664
665 let mut edges: BTreeMap<PackageName, BTreeSet<PackageName>> = BTreeMap::new();
670 let mut add_edges = |owner: PackageName, module: &CompiledModule| {
671 for dep in module.immediate_dependencies() {
672 if let Some(dep_pkg) = module_pkg.get(&dep) {
673 if *dep_pkg != owner {
674 edges.entry(owner).or_default().insert(*dep_pkg);
675 }
676 }
677 }
678 };
679 for unit in self.package.all_modules() {
680 if let Some(owner) = unit.unit.package_name {
681 add_edges(owner, &unit.unit.module);
682 }
683 }
684 for (owner, module) in &self.bytecode_deps {
685 add_edges(*owner, module);
686 }
687
688 let roots: BTreeSet<PackageName> = self
691 .package
692 .root_modules()
693 .filter_map(|unit| unit.unit.package_name)
694 .collect();
695 let mut reached: BTreeSet<PackageName> = BTreeSet::new();
696 let mut queue: VecDeque<PackageName> = roots.iter().copied().collect();
697 while let Some(pkg) = queue.pop_front() {
698 if let Some(deps) = edges.get(&pkg) {
699 for dep in deps {
700 if reached.insert(*dep) {
701 queue.push_back(*dep);
702 }
703 }
704 }
705 }
706 for root in &roots {
707 reached.remove(root);
708 }
709
710 self.dependency_ids
712 .published
713 .keys()
714 .filter(|pkg| reached.contains(pkg))
715 .count()
716 }
717
718 pub fn get_iota_system_modules(&self) -> impl Iterator<Item = &CompiledModule> {
720 self.get_modules_and_deps()
721 .filter(|m| m.self_id().address().as_ref() == Address::SYSTEM.as_bytes())
722 }
723
724 pub fn get_iota_framework_modules(&self) -> impl Iterator<Item = &CompiledModule> {
727 self.get_modules_and_deps()
728 .filter(|m| m.self_id().address().as_ref() == Address::FRAMEWORK.as_bytes())
729 }
730
731 pub fn get_stdlib_modules(&self) -> impl Iterator<Item = &CompiledModule> {
733 self.get_modules_and_deps()
734 .filter(|m| m.self_id().address().as_ref() == Address::STD.as_bytes())
735 }
736
737 pub fn get_stardust_modules(&self) -> impl Iterator<Item = &CompiledModule> {
739 self.get_modules_and_deps()
740 .filter(|m| m.self_id().address().as_ref() == Address::STARDUST.as_bytes())
741 }
742
743 pub fn generate_struct_layouts(&self) -> Registry {
750 let pool = &mut normalized::RcPool::new();
751 let mut package_types = BTreeSet::new();
752 for m in self.get_modules() {
753 let normalized_m = normalized::Module::new(pool, m, false);
754 'structs: for (name, s) in normalized_m.structs {
756 let mut dummy_type_parameters = Vec::new();
757 for t in &s.type_parameters {
758 if t.is_phantom {
759 dummy_type_parameters.push(TypeTag::Signer)
766 } else {
767 continue 'structs;
770 }
771 }
772 debug_assert!(dummy_type_parameters.len() == s.type_parameters.len());
773 package_types.insert(StructTag {
774 address: *m.address(),
775 module: m.name().to_owned(),
776 name: name.as_ident_str().to_owned(),
777 type_params: dummy_type_parameters,
778 });
779 }
780 for (_name, f) in normalized_m.functions {
782 if f.is_entry {
783 for t in &*f.parameters {
784 let tag_opt = match &**t {
785 Type::Address
786 | Type::Bool
787 | Type::Signer
788 | Type::TypeParameter(_)
789 | Type::U8
790 | Type::U16
791 | Type::U32
792 | Type::U64
793 | Type::U128
794 | Type::U256
795 | Type::Vector(_) => continue,
796 Type::Reference(_, inner) => inner.to_struct_tag(pool),
797 Type::Datatype(_) => t.to_struct_tag(pool),
798 };
799 if let Some(tag) = tag_opt {
800 package_types.insert(tag);
801 }
802 }
803 }
804 }
805 }
806 let mut layout_builder = SerdeLayoutBuilder::new(self);
807 for tag in &package_types {
808 layout_builder.build_data_layout(tag).unwrap();
809 }
810 layout_builder.into_registry()
811 }
812
813 pub fn is_system_package(&self) -> bool {
815 let Ok(published_at) = self.published_at else {
817 return false;
818 };
819
820 published_at.is_system_package()
821 }
822
823 pub fn published_root_module(&self) -> Option<&CompiledModule> {
826 self.package.root_compiled_units.iter().find_map(|unit| {
827 if unit.unit.module.self_id().address() != &AccountAddress::ZERO {
828 Some(&unit.unit.module)
829 } else {
830 None
831 }
832 })
833 }
834
835 pub fn verify_unpublished_dependencies(
836 &self,
837 unpublished_deps: &BTreeSet<Symbol>,
838 ) -> IotaResult<()> {
839 if unpublished_deps.is_empty() {
840 return Ok(());
841 }
842
843 let errors = self
844 .package
845 .deps_compiled_units
846 .iter()
847 .filter_map(|(p, m)| {
848 if !unpublished_deps.contains(p) || m.unit.module.address() == &AccountAddress::ZERO
849 {
850 return None;
851 }
852 Some(format!(
853 " - {}::{} in dependency {}",
854 m.unit.module.address(),
855 m.unit.name,
856 p
857 ))
858 })
859 .collect::<Vec<String>>();
860
861 if errors.is_empty() {
862 return Ok(());
863 }
864
865 let mut error_message = vec![];
866 error_message.push(
867 "The following modules in package dependencies set a non-zero self-address:".into(),
868 );
869 error_message.extend(errors);
870 error_message.push(
871 "If these packages really are unpublished, their self-addresses should be set \
872 to \"0x0\" in the [addresses] section of the manifest when publishing. If they \
873 are already published, ensure they specify the address in the `published-at` of \
874 their Move.toml manifest."
875 .into(),
876 );
877
878 Err(IotaError::ModulePublishFailure {
879 error: error_message.join("\n"),
880 })
881 }
882
883 pub fn get_published_dependencies_ids(&self) -> Vec<ObjectId> {
884 self.dependency_ids.published.values().cloned().collect()
885 }
886
887 pub fn find_immediate_deps_pkgs_to_keep(
890 &self,
891 with_unpublished_deps: bool,
892 ) -> Result<BTreeMap<Symbol, ObjectId>, anyhow::Error> {
893 let root_modules: Vec<_> = if with_unpublished_deps {
896 self.package
897 .all_compiled_units_with_source()
898 .filter(|m| m.unit.address.into_inner() == AccountAddress::ZERO)
899 .map(|x| x.unit.clone())
900 .collect()
901 } else {
902 self.package
903 .root_modules()
904 .map(|x| x.unit.clone())
905 .collect()
906 };
907
908 let mut pkgs_to_keep: BTreeSet<Symbol> = BTreeSet::new();
912 let module_to_pkg_name: BTreeMap<_, _> = self
913 .package
914 .all_modules()
915 .map(|m| (m.unit.module.self_id(), m.unit.package_name))
916 .collect();
917
918 for module in &root_modules {
919 let immediate_deps = module.module.immediate_dependencies();
920 for dep in immediate_deps {
921 if let Some(pkg_name) = module_to_pkg_name.get(&dep) {
922 let Some(pkg_name) = pkg_name else {
923 bail!("Expected a package name but it's None")
924 };
925 pkgs_to_keep.insert(*pkg_name);
926 }
927 }
928 }
929
930 pkgs_to_keep.extend(self.bytecode_deps.iter().map(|(name, _)| *name));
934
935 Ok(self
938 .dependency_ids
939 .clone()
940 .published
941 .into_iter()
942 .filter(|(pkg_name, _)| pkgs_to_keep.contains(pkg_name))
943 .collect())
944 }
945}
946
947pub fn implicit_deps(packages: &SystemPackagesVersion) -> Dependencies {
951 packages
952 .packages
953 .iter()
954 .map(|package| {
955 (
956 package.package_name.clone().into(),
957 Dependency::Internal(InternalDependency {
958 kind: DependencyKind::Git(GitInfo {
959 git_url: SYSTEM_GIT_REPO.into(),
960 git_rev: packages.git_revision.clone().into(),
961 subdir: package.repo_path.clone().into(),
962 }),
963 subst: None,
964 digest: None,
965 dep_override: true,
966 }),
967 )
968 })
969 .collect()
970}
971
972impl GetModule for CompiledPackage {
973 type Error = anyhow::Error;
974 type Item = CompiledModule;
977
978 fn get_module_by_id(&self, id: &ModuleId) -> Result<Option<Self::Item>, Self::Error> {
979 Ok(self.package.all_modules_map().get_module(id).ok().cloned())
980 }
981}
982
983pub const PUBLISHED_AT_MANIFEST_FIELD: &str = "published-at";
984
985pub struct IotaPackageHooks;
986
987impl PackageHooks for IotaPackageHooks {
988 fn custom_package_info_fields(&self) -> Vec<String> {
989 vec![
990 PUBLISHED_AT_MANIFEST_FIELD.to_string(),
991 "version".to_string(),
993 ]
994 }
995
996 fn resolve_on_chain_dependency(
997 &self,
998 _dep_name: move_symbol_pool::Symbol,
999 _info: &OnChainInfo,
1000 ) -> anyhow::Result<()> {
1001 Ok(())
1002 }
1003
1004 fn custom_resolve_pkg_id(
1005 &self,
1006 manifest: &SourceManifest,
1007 ) -> anyhow::Result<PackageIdentifier> {
1008 if (!cfg!(debug_assertions) || cfg!(test))
1009 && manifest.package.edition == Some(Edition::DEVELOPMENT)
1010 {
1011 return Err(Edition::DEVELOPMENT.unknown_edition_error());
1012 }
1013 Ok(manifest.package.name)
1014 }
1015
1016 fn resolve_version(&self, _: &SourceManifest) -> anyhow::Result<Option<Symbol>> {
1017 Ok(None)
1018 }
1019}
1020
1021#[derive(Debug, Clone)]
1022pub struct PackageDependencies {
1023 pub published: BTreeMap<Symbol, ObjectId>,
1025 pub unpublished: BTreeSet<Symbol>,
1027 pub invalid: BTreeMap<Symbol, String>,
1029 pub conflicting: BTreeMap<Symbol, (ObjectId, ObjectId)>,
1033}
1034
1035pub fn gather_published_ids(
1041 resolution_graph: &ResolvedGraph,
1042 chain_id: Option<String>,
1043) -> (Result<ObjectId, PublishedAtError>, PackageDependencies) {
1044 let root = resolution_graph.root_package();
1045
1046 let mut published = BTreeMap::new();
1047 let mut unpublished = BTreeSet::new();
1048 let mut invalid = BTreeMap::new();
1049 let mut conflicting = BTreeMap::new();
1050 let mut published_at = Err(PublishedAtError::NotPresent);
1051
1052 for (name, package) in &resolution_graph.package_table {
1053 let property = resolve_published_id(package, chain_id.clone());
1054 if name == &root {
1055 published_at = property;
1057 continue;
1058 }
1059
1060 match property {
1061 Ok(id) => {
1062 published.insert(*name, id);
1063 }
1064 Err(PublishedAtError::NotPresent) => {
1065 unpublished.insert(*name);
1066 }
1067 Err(PublishedAtError::Invalid(value)) => {
1068 invalid.insert(*name, value);
1069 }
1070 Err(PublishedAtError::Conflict {
1071 id_lock,
1072 id_manifest,
1073 }) => {
1074 conflicting.insert(*name, (id_lock, id_manifest));
1075 }
1076 };
1077 }
1078
1079 (
1080 published_at,
1081 PackageDependencies {
1082 published,
1083 unpublished,
1084 invalid,
1085 conflicting,
1086 },
1087 )
1088}
1089
1090pub fn published_at_property(manifest: &SourceManifest) -> Result<ObjectId, PublishedAtError> {
1091 let Some(value) = manifest
1092 .package
1093 .custom_properties
1094 .get(&Symbol::from(PUBLISHED_AT_MANIFEST_FIELD))
1095 else {
1096 return Err(PublishedAtError::NotPresent);
1097 };
1098
1099 ObjectId::from_str(value.as_str()).map_err(|_| PublishedAtError::Invalid(value.to_owned()))
1100}
1101
1102pub fn check_unpublished_dependencies(unpublished: &BTreeSet<Symbol>) -> Result<(), IotaError> {
1103 if unpublished.is_empty() {
1104 return Ok(());
1105 };
1106
1107 let mut error_messages = unpublished
1108 .iter()
1109 .map(|name| {
1110 format!(
1111 "Package dependency \"{name}\" does not specify a published address \
1112 (the Move.toml manifest for \"{name}\" does not contain a 'published-at' field, \
1113 nor is there a 'published-id' in the Move.lock). \
1114 You can use `iota move manage-package` to record the on-chain address for \"{name}\".",
1115 )
1116 })
1117 .collect::<Vec<_>>();
1118
1119 error_messages.push(
1120 "If this is intentional, you may use the --with-unpublished-dependencies flag to \
1121 continue publishing these dependencies as part of your package (they won't be \
1122 linked against existing packages on-chain)."
1123 .into(),
1124 );
1125
1126 Err(IotaError::ModulePublishFailure {
1127 error: error_messages.join("\n"),
1128 })
1129}
1130
1131pub fn check_invalid_dependencies(invalid: &BTreeMap<Symbol, String>) -> Result<(), IotaError> {
1132 if invalid.is_empty() {
1133 return Ok(());
1134 }
1135
1136 let error_messages = invalid
1137 .iter()
1138 .map(|(name, value)| {
1139 format!(
1140 "Package dependency \"{name}\" does not specify a valid published \
1141 address: could not parse value \"{value}\" for 'published-at' field in Move.toml \
1142 or 'published-id' in Move.lock file."
1143 )
1144 })
1145 .collect::<Vec<_>>();
1146
1147 Err(IotaError::ModulePublishFailure {
1148 error: error_messages.join("\n"),
1149 })
1150}
1151
1152pub fn check_conflicting_addresses(
1153 conflicting: &BTreeMap<Symbol, (ObjectId, ObjectId)>,
1154 dump_bytecode_base64: bool,
1155) -> Result<(), IotaError> {
1156 if conflicting.is_empty() {
1157 return Ok(());
1158 }
1159
1160 let suffix = if conflicting.len() == 1 { "" } else { "es" };
1161
1162 let err_msg = format!("found the following conflicting published package address{suffix}:");
1163 let suggestion_message =
1164 "You may want to:
1165 - delete the published-at address in the `Move.toml` if the `Move.lock` address is correct; OR
1166 - update the `Move.lock` address to be the same as the `Move.toml`; OR
1167 - check that your `iota active-env` corresponds to the chain on which the package is published (i.e., devnet, testnet, mainnet); OR
1168 - contact the maintainer if this package is a dependency and request resolving the conflict.";
1169
1170 let conflicting_addresses_msg = conflicting
1171 .iter()
1172 .map(|(_, (id_lock, id_manifest))| {
1173 format!(
1174 " `Move.toml` contains published-at address \
1175 {id_manifest} but `Move.lock` file contains published-at address {id_lock}."
1176 )
1177 })
1178 .collect::<Vec<_>>()
1179 .join("\n");
1180
1181 let error = format!("{err_msg}\n{conflicting_addresses_msg}\n{suggestion_message}");
1182
1183 let err = if dump_bytecode_base64 {
1184 IotaError::ModuleBuildFailure { error }
1185 } else {
1186 IotaError::ModulePublishFailure { error }
1187 };
1188
1189 Err(err)
1190}
1191
1192pub fn local_implicit_deps(packages: &SystemPackagesVersion) -> Dependencies {
1196 let repo_root = Path::new(env!("CARGO_MANIFEST_DIR"))
1201 .parent()
1202 .and_then(Path::parent)
1203 .expect("iota-move-build manifest dir should have a grandparent");
1204 packages
1205 .packages
1206 .iter()
1207 .map(|package| {
1208 (
1209 package.package_name.clone().into(),
1210 Dependency::Internal(InternalDependency {
1211 kind: DependencyKind::Local(repo_root.join(&package.repo_path)),
1212 subst: None,
1213 digest: None,
1214 dep_override: true,
1215 }),
1216 )
1217 })
1218 .collect()
1219}
1220
1221pub fn local_implicit_deps_latest() -> Dependencies {
1225 local_implicit_deps(latest_system_packages())
1226}