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, ObjectId, Version, move_package::MovePackage};
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 pub fn with_allow_view_function(mut self) -> Self {
170 self.protocol_build_config.allow_view_function = true;
171 self
172 }
173
174 fn fn_info(units: &[AnnotatedCompiledModule]) -> FnInfoMap {
175 let mut fn_info_map = BTreeMap::new();
176 for u in units {
177 let mod_addr = Address::new(u.named_module.address.into_bytes());
178 let mod_name = u.named_module.module.name().to_string();
179 let mod_is_test = u.attributes.is_test_or_test_only();
180 for (_, s, info) in &u.function_infos {
181 let fn_name = s.as_str().to_string();
182 let is_test = mod_is_test || info.attributes.is_test_or_test_only();
183 let authenticator_version = info.attributes.get_authenticator();
184 let is_view = info.attributes.is_view();
185 fn_info_map.insert(
186 FnInfoKey {
187 fn_name,
188 mod_name: mod_name.clone(),
189 mod_addr,
190 },
191 FnInfo {
192 is_test,
193 authenticator_version,
194 is_view,
195 },
196 );
197 }
198 }
199
200 fn_info_map
201 }
202
203 fn compile_package<W: Write>(
204 resolution_graph: &ResolvedGraph,
205 writer: &mut W,
206 ) -> anyhow::Result<(MoveCompiledPackage, FnInfoMap)> {
207 let build_plan = BuildPlan::create(resolution_graph)?;
208 let mut fn_info = None;
209 let compiled_pkg = build_plan.compile_with_driver(writer, |compiler| {
210 let (files, units_res) = compiler.build()?;
211 match units_res {
212 Ok((units, warning_diags)) => {
213 decorate_warnings(warning_diags, Some(&files));
214 fn_info = Some(Self::fn_info(&units));
215 Ok((files, units))
216 }
217 Err(error_diags) => {
218 assert!(!error_diags.is_empty());
221 let diags_buf =
222 report_diagnostics_to_buffer(&files, error_diags, true);
223 if let Err(err) = std::io::stderr().write_all(&diags_buf) {
224 anyhow::bail!("Cannot output compiler diagnostics: {err}");
225 }
226 anyhow::bail!("Compilation error");
227 }
228 }
229 })?;
230 Ok((compiled_pkg, fn_info.unwrap()))
231 }
232
233 pub fn build(self, path: &Path) -> IotaResult<CompiledPackage> {
237 let print_diags_to_stderr = self.print_diags_to_stderr;
238 let run_bytecode_verifier = self.run_bytecode_verifier;
239 let chain_id = self.chain_id.clone();
240 let protocol_build_config = self.protocol_build_config;
241 let resolution_graph = self.resolution_graph(path, chain_id.clone())?;
242 build_from_resolution_graph(
243 resolution_graph,
244 run_bytecode_verifier,
245 print_diags_to_stderr,
246 chain_id,
247 &protocol_build_config,
248 )
249 }
250
251 pub fn resolution_graph(
252 mut self,
253 path: &Path,
254 chain_id: Option<String>,
255 ) -> IotaResult<ResolvedGraph> {
256 if let Some(err_msg) = set_iota_flavor(&mut self.config) {
257 return Err(IotaError::ModuleBuildFailure { error: err_msg });
258 }
259
260 if self.print_diags_to_stderr {
261 self.config
262 .resolution_graph_for_package(path, chain_id, &mut std::io::stderr())
263 } else {
264 self.config
265 .resolution_graph_for_package(path, chain_id, &mut std::io::sink())
266 }
267 .map_err(|err| IotaError::ModuleBuildFailure {
268 error: format!("{err:?}"),
269 })
270 }
271}
272
273pub fn decorate_warnings(warning_diags: Diagnostics, files: Option<&MappedFiles>) {
277 let any_linter_warnings = warning_diags.any_with_prefix(LINT_WARNING_PREFIX);
278 let (filtered_diags_num, unique) =
279 warning_diags.filtered_source_diags_with_prefix(LINT_WARNING_PREFIX);
280 if let Some(f) = files {
281 report_warnings(f, warning_diags);
282 }
283 if any_linter_warnings {
284 eprintln!(
285 "Please report feedback on the linter warnings at https://github.com/iotaledger/iota/issues\n"
286 );
287 }
288 if filtered_diags_num > 0 {
289 eprintln!(
290 "Total number of linter warnings suppressed: {filtered_diags_num} (unique lints: {unique})"
291 );
292 }
293}
294
295pub fn set_iota_flavor(build_config: &mut MoveBuildConfig) -> Option<String> {
298 use move_compiler::editions::Flavor;
299
300 let flavor = build_config.default_flavor.get_or_insert(Flavor::Iota);
301 if flavor != &Flavor::Iota {
302 return Some(format!(
303 "The flavor of the Move compiler cannot be overridden with anything but \
304 \"{}\", but the default override was set to: \"{flavor}\"",
305 Flavor::Iota,
306 ));
307 }
308 None
309}
310
311pub fn build_from_resolution_graph(
312 resolution_graph: ResolvedGraph,
313 run_bytecode_verifier: bool,
314 print_diags_to_stderr: bool,
315 chain_id: Option<String>,
316 protocol_build_config: &ProtocolBuildConfig,
317) -> IotaResult<CompiledPackage> {
318 let (published_at, dependency_ids) = gather_published_ids(&resolution_graph, chain_id);
319
320 let bytecode_deps = collect_bytecode_deps(&resolution_graph)?;
323
324 let result = if print_diags_to_stderr {
326 BuildConfig::compile_package(&resolution_graph, &mut std::io::stderr())
327 } else {
328 BuildConfig::compile_package(&resolution_graph, &mut std::io::sink())
329 };
330
331 let (mut package, fn_info) = result.map_err(|error| IotaError::ModuleBuildFailure {
332 error: format!("{error:?}"),
334 })?;
335
336 fill_metadata(&mut package, &fn_info, protocol_build_config)?;
339
340 if run_bytecode_verifier {
341 verify_bytecode(&package, &fn_info, protocol_build_config)?;
342 }
343
344 Ok(CompiledPackage {
345 package,
346 published_at,
347 dependency_ids,
348 bytecode_deps,
349 dependency_graph: resolution_graph.graph,
350 })
351}
352
353fn collect_bytecode_deps(
355 resolution_graph: &ResolvedGraph,
356) -> IotaResult<Vec<(Symbol, CompiledModule)>> {
357 let mut bytecode_deps = vec![];
358 for (name, pkg) in resolution_graph.package_table.iter() {
359 if !pkg
360 .get_sources(&resolution_graph.build_options)
361 .unwrap()
362 .is_empty()
363 {
364 continue;
365 }
366 let modules =
367 pkg.get_bytecodes_bytes()
368 .map_err(|error| IotaError::ModuleDeserializationFailure {
369 error: format!(
370 "Deserializing bytecode dependency for package {name}: {error:?}"
371 ),
372 })?;
373 for module in modules {
374 let module =
375 CompiledModule::deserialize_with_defaults(module.as_ref()).map_err(|error| {
376 IotaError::ModuleDeserializationFailure {
377 error: format!(
378 "Deserializing bytecode dependency for package {name}: {error:?}"
379 ),
380 }
381 })?;
382 bytecode_deps.push((*name, module));
383 }
384 }
385
386 Ok(bytecode_deps)
387}
388
389fn fill_metadata(
391 package: &mut MoveCompiledPackage,
392 fn_info_map: &FnInfoMap,
393 protocol_build_config: &ProtocolBuildConfig,
394) -> IotaResult<()> {
395 for module in package
396 .root_compiled_units
397 .iter_mut()
398 .map(|unit| &mut unit.unit.module)
399 {
400 let mut runtime_metadata = if protocol_build_config.allow_view_function {
404 RuntimeModuleMetadata::v2()
405 } else {
406 RuntimeModuleMetadata::v1()
407 };
408 for fn_def in &module.function_defs {
409 let fn_handle = module.function_handle_at(fn_def.function);
410 let fn_name = module.identifier_at(fn_handle.name);
411 if let Some(version) =
412 get_authenticator_version_from_fun(fn_name.as_str(), module, fn_info_map)
413 {
414 let attribute = if protocol_build_config.allow_view_function {
415 IotaAttribute::V2(IotaAttributeV2::authenticator_attribute(version))
416 } else {
417 IotaAttribute::V1(IotaAttributeV1::authenticator_attribute(version))
418 };
419 runtime_metadata.add_function_attribute(fn_name.to_string(), attribute);
420 };
421 if is_view_function_from_fn_info(fn_name, module, fn_info_map) {
422 if protocol_build_config.allow_view_function {
423 runtime_metadata.add_function_attribute(
424 fn_name.to_string(),
425 IotaAttribute::V2(IotaAttributeV2::view_attribute()),
426 );
427 } else {
428 eprintln!(
435 "warning: function '{}::{}' is marked `#[view]`, but the target \
436 network protocol does not support view-function metadata; the `View` \
437 attribute will not be published",
438 module.name(),
439 fn_name,
440 );
441 }
442 }
443 }
444 if !runtime_metadata.is_empty() {
445 module.metadata.push(move_core_types::metadata::Metadata {
446 key: IOTA_METADATA_KEY.to_vec(),
447 value: RuntimeModuleMetadataWrapper::from(runtime_metadata).to_bcs_bytes(),
448 });
449 }
450 }
451 Ok(())
452}
453
454fn verify_bytecode(
456 package: &MoveCompiledPackage,
457 fn_info: &FnInfoMap,
458 protocol_build_config: &ProtocolBuildConfig,
459) -> IotaResult<()> {
460 let compiled_modules = package.root_modules_map();
461 for m in compiled_modules.iter_modules() {
462 move_bytecode_verifier::verify_module_unmetered(m).map_err(|err| {
463 IotaError::ModuleVerificationFailure {
464 error: err.to_string(),
465 }
466 })?;
467 iota_bytecode_verifier::iota_verify_module_unmetered(m, fn_info, protocol_build_config)?;
471 }
472 Ok(())
475}
476
477impl CompiledPackage {
478 pub fn get_modules(&self) -> impl Iterator<Item = &CompiledModule> {
483 self.package.root_modules().map(|m| &m.unit.module)
484 }
485
486 pub fn into_modules(self) -> Vec<CompiledModule> {
491 self.package
492 .root_compiled_units
493 .into_iter()
494 .map(|m| m.unit.module)
495 .collect()
496 }
497
498 pub fn get_dependent_modules(&self) -> impl Iterator<Item = &CompiledModule> {
502 self.package
503 .deps_compiled_units
504 .iter()
505 .map(|(_, m)| &m.unit.module)
506 .chain(self.bytecode_deps.iter().map(|(_, m)| m))
507 }
508
509 pub fn get_modules_and_deps(&self) -> impl Iterator<Item = &CompiledModule> {
513 self.package
514 .all_modules()
515 .map(|m| &m.unit.module)
516 .chain(self.bytecode_deps.iter().map(|(_, m)| m))
517 }
518
519 pub fn get_dependency_sorted_modules(
525 &self,
526 with_unpublished_deps: bool,
527 ) -> Vec<CompiledModule> {
528 let all_modules = Modules::new(self.get_modules_and_deps());
529
530 let modules = all_modules.compute_topological_order().unwrap();
532
533 if with_unpublished_deps {
534 modules
538 .filter(|module| module.address() == &AccountAddress::ZERO)
539 .cloned()
540 .collect()
541 } else {
542 let self_modules: HashSet<_> = self
547 .package
548 .root_modules_map()
549 .iter_modules()
550 .iter()
551 .map(|m| m.self_id())
552 .collect();
553
554 modules
555 .filter(|module| self_modules.contains(&module.self_id()))
556 .cloned()
557 .collect()
558 }
559 }
560
561 pub fn get_dependency_storage_package_ids(&self) -> Vec<ObjectId> {
565 self.dependency_ids.published.values().copied().collect()
566 }
567
568 pub fn get_package_digest(&self, with_unpublished_deps: bool) -> [u8; 32] {
570 MovePackage::compute_digest_for_modules_and_deps(
571 &self.get_package_bytes(with_unpublished_deps),
572 self.dependency_ids.published.values(),
573 )
574 .into_inner()
575 }
576
577 pub fn get_package_bytes(&self, with_unpublished_deps: bool) -> Vec<Vec<u8>> {
580 self.get_dependency_sorted_modules(with_unpublished_deps)
581 .iter()
582 .map(|m| {
583 let mut bytes = Vec::new();
584 m.serialize_with_version(m.version, &mut bytes).unwrap(); bytes
586 })
587 .collect()
588 }
589
590 pub fn get_package_base64(&self, with_unpublished_deps: bool) -> Vec<Base64> {
593 self.get_package_bytes(with_unpublished_deps)
594 .iter()
595 .map(|b| Base64::from_bytes(b))
596 .collect()
597 }
598
599 pub fn published_size(&self, with_unpublished_deps: bool, dep_count: usize) -> u64 {
613 const LINKAGE_ENTRY_SIZE: usize =
616 ObjectId::LENGTH + ObjectId::LENGTH + std::mem::size_of::<Version>();
617
618 let mut size = std::mem::size_of::<Version>();
619
620 for module in self.get_dependency_sorted_modules(with_unpublished_deps) {
621 let module_name_len = module.name().as_str().len();
622
623 let mut bytes = Vec::new();
624 module
626 .serialize_with_version(module.version, &mut bytes)
627 .unwrap();
628 size += module_name_len + bytes.len();
629
630 for struct_def in module.struct_defs() {
632 let handle = module.datatype_handle_at(struct_def.struct_handle);
633 size += module_name_len
634 + module.identifier_at(handle.name).as_str().len()
635 + ObjectId::LENGTH;
636 }
637 for enum_def in module.enum_defs() {
638 let handle = module.datatype_handle_at(enum_def.enum_handle);
639 size += module_name_len
640 + module.identifier_at(handle.name).as_str().len()
641 + ObjectId::LENGTH;
642 }
643 }
644
645 size += dep_count * LINKAGE_ENTRY_SIZE;
646 size as u64
647 }
648
649 pub fn linkage_dependency_count(&self) -> usize {
659 let mut module_pkg: BTreeMap<ModuleId, PackageName> = BTreeMap::new();
661 for unit in self.package.all_modules() {
662 if let Some(pkg) = unit.unit.package_name {
663 module_pkg.insert(unit.unit.module.self_id(), pkg);
664 }
665 }
666 for (pkg, module) in &self.bytecode_deps {
667 module_pkg.insert(module.self_id(), *pkg);
668 }
669
670 let mut edges: BTreeMap<PackageName, BTreeSet<PackageName>> = BTreeMap::new();
675 let mut add_edges = |owner: PackageName, module: &CompiledModule| {
676 for dep in module.immediate_dependencies() {
677 if let Some(dep_pkg) = module_pkg.get(&dep) {
678 if *dep_pkg != owner {
679 edges.entry(owner).or_default().insert(*dep_pkg);
680 }
681 }
682 }
683 };
684 for unit in self.package.all_modules() {
685 if let Some(owner) = unit.unit.package_name {
686 add_edges(owner, &unit.unit.module);
687 }
688 }
689 for (owner, module) in &self.bytecode_deps {
690 add_edges(*owner, module);
691 }
692
693 let roots: BTreeSet<PackageName> = self
696 .package
697 .root_modules()
698 .filter_map(|unit| unit.unit.package_name)
699 .collect();
700 let mut reached: BTreeSet<PackageName> = BTreeSet::new();
701 let mut queue: VecDeque<PackageName> = roots.iter().copied().collect();
702 while let Some(pkg) = queue.pop_front() {
703 if let Some(deps) = edges.get(&pkg) {
704 for dep in deps {
705 if reached.insert(*dep) {
706 queue.push_back(*dep);
707 }
708 }
709 }
710 }
711 for root in &roots {
712 reached.remove(root);
713 }
714
715 self.dependency_ids
717 .published
718 .keys()
719 .filter(|pkg| reached.contains(pkg))
720 .count()
721 }
722
723 pub fn get_iota_system_modules(&self) -> impl Iterator<Item = &CompiledModule> {
725 self.get_modules_and_deps()
726 .filter(|m| m.self_id().address().as_ref() == Address::SYSTEM.as_bytes())
727 }
728
729 pub fn get_iota_framework_modules(&self) -> impl Iterator<Item = &CompiledModule> {
732 self.get_modules_and_deps()
733 .filter(|m| m.self_id().address().as_ref() == Address::FRAMEWORK.as_bytes())
734 }
735
736 pub fn get_stdlib_modules(&self) -> impl Iterator<Item = &CompiledModule> {
738 self.get_modules_and_deps()
739 .filter(|m| m.self_id().address().as_ref() == Address::STD.as_bytes())
740 }
741
742 pub fn get_stardust_modules(&self) -> impl Iterator<Item = &CompiledModule> {
744 self.get_modules_and_deps()
745 .filter(|m| m.self_id().address().as_ref() == Address::STARDUST.as_bytes())
746 }
747
748 pub fn generate_struct_layouts(&self) -> Registry {
755 let pool = &mut normalized::RcPool::new();
756 let mut package_types = BTreeSet::new();
757 for m in self.get_modules() {
758 let normalized_m = normalized::Module::new(pool, m, false);
759 'structs: for (name, s) in normalized_m.structs {
761 let mut dummy_type_parameters = Vec::new();
762 for t in &s.type_parameters {
763 if t.is_phantom {
764 dummy_type_parameters.push(TypeTag::Signer)
771 } else {
772 continue 'structs;
775 }
776 }
777 debug_assert!(dummy_type_parameters.len() == s.type_parameters.len());
778 package_types.insert(StructTag {
779 address: *m.address(),
780 module: m.name().to_owned(),
781 name: name.as_ident_str().to_owned(),
782 type_params: dummy_type_parameters,
783 });
784 }
785 for (_name, f) in normalized_m.functions {
787 if f.is_entry {
788 for t in &*f.parameters {
789 let tag_opt = match &**t {
790 Type::Address
791 | Type::Bool
792 | Type::Signer
793 | Type::TypeParameter(_)
794 | Type::U8
795 | Type::U16
796 | Type::U32
797 | Type::U64
798 | Type::U128
799 | Type::U256
800 | Type::Vector(_) => continue,
801 Type::Reference(_, inner) => inner.to_struct_tag(pool),
802 Type::Datatype(_) => t.to_struct_tag(pool),
803 };
804 if let Some(tag) = tag_opt {
805 package_types.insert(tag);
806 }
807 }
808 }
809 }
810 }
811 let mut layout_builder = SerdeLayoutBuilder::new(self);
812 for tag in &package_types {
813 layout_builder.build_data_layout(tag).unwrap();
814 }
815 layout_builder.into_registry()
816 }
817
818 pub fn is_system_package(&self) -> bool {
820 let Ok(published_at) = self.published_at else {
822 return false;
823 };
824
825 published_at.is_system_package()
826 }
827
828 pub fn published_root_module(&self) -> Option<&CompiledModule> {
831 self.package.root_compiled_units.iter().find_map(|unit| {
832 if unit.unit.module.self_id().address() != &AccountAddress::ZERO {
833 Some(&unit.unit.module)
834 } else {
835 None
836 }
837 })
838 }
839
840 pub fn verify_unpublished_dependencies(
841 &self,
842 unpublished_deps: &BTreeSet<Symbol>,
843 ) -> IotaResult<()> {
844 if unpublished_deps.is_empty() {
845 return Ok(());
846 }
847
848 let errors = self
849 .package
850 .deps_compiled_units
851 .iter()
852 .filter_map(|(p, m)| {
853 if !unpublished_deps.contains(p) || m.unit.module.address() == &AccountAddress::ZERO
854 {
855 return None;
856 }
857 Some(format!(
858 " - {}::{} in dependency {}",
859 m.unit.module.address(),
860 m.unit.name,
861 p
862 ))
863 })
864 .collect::<Vec<String>>();
865
866 if errors.is_empty() {
867 return Ok(());
868 }
869
870 let mut error_message = vec![];
871 error_message.push(
872 "The following modules in package dependencies set a non-zero self-address:".into(),
873 );
874 error_message.extend(errors);
875 error_message.push(
876 "If these packages really are unpublished, their self-addresses should be set \
877 to \"0x0\" in the [addresses] section of the manifest when publishing. If they \
878 are already published, ensure they specify the address in the `published-at` of \
879 their Move.toml manifest."
880 .into(),
881 );
882
883 Err(IotaError::ModulePublishFailure {
884 error: error_message.join("\n"),
885 })
886 }
887
888 pub fn get_published_dependencies_ids(&self) -> Vec<ObjectId> {
889 self.dependency_ids.published.values().cloned().collect()
890 }
891
892 pub fn find_immediate_deps_pkgs_to_keep(
895 &self,
896 with_unpublished_deps: bool,
897 ) -> Result<BTreeMap<Symbol, ObjectId>, anyhow::Error> {
898 let root_modules: Vec<_> = if with_unpublished_deps {
901 self.package
902 .all_compiled_units_with_source()
903 .filter(|m| m.unit.address.into_inner() == AccountAddress::ZERO)
904 .map(|x| x.unit.clone())
905 .collect()
906 } else {
907 self.package
908 .root_modules()
909 .map(|x| x.unit.clone())
910 .collect()
911 };
912
913 let mut pkgs_to_keep: BTreeSet<Symbol> = BTreeSet::new();
917 let module_to_pkg_name: BTreeMap<_, _> = self
918 .package
919 .all_modules()
920 .map(|m| (m.unit.module.self_id(), m.unit.package_name))
921 .collect();
922
923 for module in &root_modules {
924 let immediate_deps = module.module.immediate_dependencies();
925 for dep in immediate_deps {
926 if let Some(pkg_name) = module_to_pkg_name.get(&dep) {
927 let Some(pkg_name) = pkg_name else {
928 bail!("Expected a package name but it's None")
929 };
930 pkgs_to_keep.insert(*pkg_name);
931 }
932 }
933 }
934
935 pkgs_to_keep.extend(self.bytecode_deps.iter().map(|(name, _)| *name));
939
940 Ok(self
943 .dependency_ids
944 .clone()
945 .published
946 .into_iter()
947 .filter(|(pkg_name, _)| pkgs_to_keep.contains(pkg_name))
948 .collect())
949 }
950}
951
952pub fn implicit_deps(packages: &SystemPackagesVersion) -> Dependencies {
956 packages
957 .packages
958 .iter()
959 .map(|package| {
960 (
961 package.package_name.clone().into(),
962 Dependency::Internal(InternalDependency {
963 kind: DependencyKind::Git(GitInfo {
964 git_url: SYSTEM_GIT_REPO.into(),
965 git_rev: packages.git_revision.clone().into(),
966 subdir: package.repo_path.clone().into(),
967 }),
968 subst: None,
969 digest: None,
970 dep_override: true,
971 }),
972 )
973 })
974 .collect()
975}
976
977impl GetModule for CompiledPackage {
978 type Error = anyhow::Error;
979 type Item = CompiledModule;
982
983 fn get_module_by_id(&self, id: &ModuleId) -> Result<Option<Self::Item>, Self::Error> {
984 Ok(self.package.all_modules_map().get_module(id).ok().cloned())
985 }
986}
987
988pub const PUBLISHED_AT_MANIFEST_FIELD: &str = "published-at";
989
990pub struct IotaPackageHooks;
991
992impl PackageHooks for IotaPackageHooks {
993 fn custom_package_info_fields(&self) -> Vec<String> {
994 vec![
995 PUBLISHED_AT_MANIFEST_FIELD.to_string(),
996 "version".to_string(),
998 ]
999 }
1000
1001 fn resolve_on_chain_dependency(
1002 &self,
1003 _dep_name: move_symbol_pool::Symbol,
1004 _info: &OnChainInfo,
1005 ) -> anyhow::Result<()> {
1006 Ok(())
1007 }
1008
1009 fn custom_resolve_pkg_id(
1010 &self,
1011 manifest: &SourceManifest,
1012 ) -> anyhow::Result<PackageIdentifier> {
1013 if (!cfg!(debug_assertions) || cfg!(test))
1014 && manifest.package.edition == Some(Edition::DEVELOPMENT)
1015 {
1016 return Err(Edition::DEVELOPMENT.unknown_edition_error());
1017 }
1018 Ok(manifest.package.name)
1019 }
1020
1021 fn resolve_version(&self, _: &SourceManifest) -> anyhow::Result<Option<Symbol>> {
1022 Ok(None)
1023 }
1024}
1025
1026#[derive(Debug, Clone)]
1027pub struct PackageDependencies {
1028 pub published: BTreeMap<Symbol, ObjectId>,
1030 pub unpublished: BTreeSet<Symbol>,
1032 pub invalid: BTreeMap<Symbol, String>,
1034 pub conflicting: BTreeMap<Symbol, (ObjectId, ObjectId)>,
1038}
1039
1040pub fn gather_published_ids(
1047 resolution_graph: &ResolvedGraph,
1048 chain_id: Option<String>,
1049) -> (Result<ObjectId, PublishedAtError>, PackageDependencies) {
1050 let root = resolution_graph.root_package();
1051
1052 let mut published = BTreeMap::new();
1053 let mut unpublished = BTreeSet::new();
1054 let mut invalid = BTreeMap::new();
1055 let mut conflicting = BTreeMap::new();
1056 let mut published_at = Err(PublishedAtError::NotPresent);
1057
1058 for (name, package) in &resolution_graph.package_table {
1059 let property = resolve_published_id(package, chain_id.clone());
1060 if name == &root {
1061 published_at = property;
1063 continue;
1064 }
1065
1066 match property {
1067 Ok(id) => {
1068 published.insert(*name, id);
1069 }
1070 Err(PublishedAtError::NotPresent) => {
1071 unpublished.insert(*name);
1072 }
1073 Err(PublishedAtError::Invalid(value)) => {
1074 invalid.insert(*name, value);
1075 }
1076 Err(PublishedAtError::Conflict {
1077 id_lock,
1078 id_manifest,
1079 }) => {
1080 conflicting.insert(*name, (id_lock, id_manifest));
1081 }
1082 };
1083 }
1084
1085 (
1086 published_at,
1087 PackageDependencies {
1088 published,
1089 unpublished,
1090 invalid,
1091 conflicting,
1092 },
1093 )
1094}
1095
1096pub fn published_at_property(manifest: &SourceManifest) -> Result<ObjectId, PublishedAtError> {
1097 let Some(value) = manifest
1098 .package
1099 .custom_properties
1100 .get(&Symbol::from(PUBLISHED_AT_MANIFEST_FIELD))
1101 else {
1102 return Err(PublishedAtError::NotPresent);
1103 };
1104
1105 ObjectId::from_str(value.as_str()).map_err(|_| PublishedAtError::Invalid(value.to_owned()))
1106}
1107
1108pub fn check_unpublished_dependencies(unpublished: &BTreeSet<Symbol>) -> Result<(), IotaError> {
1109 if unpublished.is_empty() {
1110 return Ok(());
1111 };
1112
1113 let mut error_messages = unpublished
1114 .iter()
1115 .map(|name| {
1116 format!(
1117 "Package dependency \"{name}\" does not specify a published address \
1118 (the Move.toml manifest for \"{name}\" does not contain a 'published-at' field, \
1119 nor is there a 'published-id' in the Move.lock). \
1120 You can use `iota move manage-package` to record the on-chain address for \"{name}\".",
1121 )
1122 })
1123 .collect::<Vec<_>>();
1124
1125 error_messages.push(
1126 "If this is intentional, you may use the --with-unpublished-dependencies flag to \
1127 continue publishing these dependencies as part of your package (they won't be \
1128 linked against existing packages on-chain)."
1129 .into(),
1130 );
1131
1132 Err(IotaError::ModulePublishFailure {
1133 error: error_messages.join("\n"),
1134 })
1135}
1136
1137pub fn check_invalid_dependencies(invalid: &BTreeMap<Symbol, String>) -> Result<(), IotaError> {
1138 if invalid.is_empty() {
1139 return Ok(());
1140 }
1141
1142 let error_messages = invalid
1143 .iter()
1144 .map(|(name, value)| {
1145 format!(
1146 "Package dependency \"{name}\" does not specify a valid published \
1147 address: could not parse value \"{value}\" for 'published-at' field in Move.toml \
1148 or 'published-id' in Move.lock file."
1149 )
1150 })
1151 .collect::<Vec<_>>();
1152
1153 Err(IotaError::ModulePublishFailure {
1154 error: error_messages.join("\n"),
1155 })
1156}
1157
1158pub fn check_conflicting_addresses(
1159 conflicting: &BTreeMap<Symbol, (ObjectId, ObjectId)>,
1160 dump_bytecode_base64: bool,
1161) -> Result<(), IotaError> {
1162 if conflicting.is_empty() {
1163 return Ok(());
1164 }
1165
1166 let suffix = if conflicting.len() == 1 { "" } else { "es" };
1167
1168 let err_msg = format!("found the following conflicting published package address{suffix}:");
1169 let suggestion_message =
1170 "You may want to:
1171 - delete the published-at address in the `Move.toml` if the `Move.lock` address is correct; OR
1172 - update the `Move.lock` address to be the same as the `Move.toml`; OR
1173 - check that your `iota active-env` corresponds to the chain on which the package is published (i.e., devnet, testnet, mainnet); OR
1174 - contact the maintainer if this package is a dependency and request resolving the conflict.";
1175
1176 let conflicting_addresses_msg = conflicting
1177 .iter()
1178 .map(|(_, (id_lock, id_manifest))| {
1179 format!(
1180 " `Move.toml` contains published-at address \
1181 {id_manifest} but `Move.lock` file contains published-at address {id_lock}."
1182 )
1183 })
1184 .collect::<Vec<_>>()
1185 .join("\n");
1186
1187 let error = format!("{err_msg}\n{conflicting_addresses_msg}\n{suggestion_message}");
1188
1189 let err = if dump_bytecode_base64 {
1190 IotaError::ModuleBuildFailure { error }
1191 } else {
1192 IotaError::ModulePublishFailure { error }
1193 };
1194
1195 Err(err)
1196}
1197
1198pub fn local_implicit_deps(packages: &SystemPackagesVersion) -> Dependencies {
1202 let repo_root = Path::new(env!("CARGO_MANIFEST_DIR"))
1207 .parent()
1208 .and_then(Path::parent)
1209 .expect("iota-move-build manifest dir should have a grandparent");
1210 packages
1211 .packages
1212 .iter()
1213 .map(|package| {
1214 (
1215 package.package_name.clone().into(),
1216 Dependency::Internal(InternalDependency {
1217 kind: DependencyKind::Local(repo_root.join(&package.repo_path)),
1218 subst: None,
1219 digest: None,
1220 dep_override: true,
1221 }),
1222 )
1223 })
1224 .collect()
1225}
1226
1227pub fn local_implicit_deps_latest() -> Dependencies {
1231 local_implicit_deps(latest_system_packages())
1232}