iota_move_build/
lib.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5extern 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::{
26        FnInfo, FnInfoKey, FnInfoMap, IotaAttribute, MovePackage, RuntimeModuleMetadata,
27        RuntimeModuleMetadataWrapper, get_authenticator_version_from_fun,
28    },
29};
30use iota_verifier::verifier as iota_bytecode_verifier;
31use move_binary_format::{
32    CompiledModule,
33    file_format_common::IOTA_METADATA_KEY,
34    normalized::{self, Type},
35};
36use move_bytecode_utils::{Modules, layout::SerdeLayoutBuilder, module_cache::GetModule};
37use move_compiler::{
38    compiled_unit::AnnotatedCompiledModule,
39    diagnostics::{Diagnostics, report_diagnostics_to_buffer, report_warnings},
40    editions::Edition,
41    linters::LINT_WARNING_PREFIX,
42    shared::files::MappedFiles,
43};
44use move_core_types::{
45    account_address::AccountAddress,
46    language_storage::{ModuleId, StructTag, TypeTag},
47};
48use move_package::{
49    BuildConfig as MoveBuildConfig, LintFlag,
50    compilation::{
51        build_plan::BuildPlan, compiled_package::CompiledPackage as MoveCompiledPackage,
52    },
53    package_hooks::{PackageHooks, PackageIdentifier},
54    resolution::{dependency_graph::DependencyGraph, resolution_graph::ResolvedGraph},
55    source_package::parsed_manifest::{
56        Dependencies, Dependency, DependencyKind, GitInfo, InternalDependency, OnChainInfo,
57        PackageName, SourceManifest,
58    },
59};
60use move_symbol_pool::Symbol;
61use serde_reflection::Registry;
62
63#[cfg(test)]
64#[path = "unit_tests/build_tests.rs"]
65mod build_tests;
66
67pub mod test_utils {
68    use std::path::PathBuf;
69
70    use crate::{BuildConfig, CompiledPackage, IotaPackageHooks};
71
72    pub fn compile_basics_package() -> CompiledPackage {
73        compile_example_package("../../examples/move/basics")
74    }
75
76    pub fn compile_managed_coin_package() -> CompiledPackage {
77        compile_example_package("../../crates/iota-core/src/unit_tests/data/managed_coin")
78    }
79
80    pub fn compile_example_package(relative_path: &str) -> CompiledPackage {
81        move_package::package_hooks::register_package_hooks(Box::new(IotaPackageHooks));
82        let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
83        path.push(relative_path);
84
85        BuildConfig::new_for_testing().build(&path).unwrap()
86    }
87}
88
89/// Wrapper around the core Move `CompiledPackage` with some IOTA-specific
90/// traits and info
91#[derive(Debug, Clone)]
92pub struct CompiledPackage {
93    pub package: MoveCompiledPackage,
94    /// Address the package is recorded as being published at.
95    pub published_at: Result<ObjectID, PublishedAtError>,
96    /// The dependency IDs of this package
97    pub dependency_ids: PackageDependencies,
98    /// The bytecode modules that this package depends on (both directly and
99    /// transitively), i.e. on-chain dependencies.
100    pub bytecode_deps: Vec<(PackageName, CompiledModule)>,
101    /// Transitive dependency graph of a Move package
102    pub dependency_graph: DependencyGraph,
103}
104
105/// Wrapper around the core Move `BuildConfig` with some IOTA-specific info
106#[derive(Clone)]
107pub struct BuildConfig {
108    pub config: MoveBuildConfig,
109    /// If true, run the Move bytecode verifier on the bytecode from a
110    /// successful build
111    pub run_bytecode_verifier: bool,
112    /// If true, print build diagnostics to stderr--no printing if false
113    pub print_diags_to_stderr: bool,
114    /// The chain ID that compilation is with respect to (e.g., required to
115    /// resolve published dependency IDs from the `Move.lock`).
116    pub chain_id: Option<String>,
117}
118
119impl BuildConfig {
120    pub fn new_for_testing() -> Self {
121        move_package::package_hooks::register_package_hooks(Box::new(IotaPackageHooks));
122
123        let install_dir = tempfile::tempdir().unwrap().keep();
124        let config = MoveBuildConfig {
125            default_flavor: Some(move_compiler::editions::Flavor::Iota),
126            lock_file: Some(install_dir.join("Move.lock")),
127            install_dir: Some(install_dir),
128            lint_flag: LintFlag::LEVEL_NONE,
129            // TODO: in the future, we may want to provide local implicit dependencies to tests
130            implicit_dependencies: Dependencies::new(),
131            silence_warnings: true,
132            ..MoveBuildConfig::default()
133        };
134        BuildConfig {
135            config,
136            run_bytecode_verifier: true,
137            print_diags_to_stderr: false,
138            chain_id: None,
139        }
140    }
141
142    pub fn new_for_testing_replace_addresses<I, S>(dep_original_addresses: I) -> Self
143    where
144        I: IntoIterator<Item = (S, ObjectID)>,
145        S: Into<String>,
146    {
147        let mut build_config = Self::new_for_testing();
148        for (addr_name, obj_id) in dep_original_addresses {
149            build_config
150                .config
151                .additional_named_addresses
152                .insert(addr_name.into(), AccountAddress::from(obj_id));
153        }
154        build_config
155    }
156
157    fn fn_info(units: &[AnnotatedCompiledModule]) -> FnInfoMap {
158        let mut fn_info_map = BTreeMap::new();
159        for u in units {
160            let mod_addr = u.named_module.address.into_inner();
161            let mod_name = u.named_module.module.name().to_string();
162            let mod_is_test = u.attributes.is_test_or_test_only();
163            for (_, s, info) in &u.function_infos {
164                let fn_name = s.as_str().to_string();
165                let is_test = mod_is_test || info.attributes.is_test_or_test_only();
166                let authenticator_version = info.attributes.get_authenticator();
167                fn_info_map.insert(
168                    FnInfoKey {
169                        fn_name,
170                        mod_name: mod_name.clone(),
171                        mod_addr,
172                    },
173                    FnInfo {
174                        is_test,
175                        authenticator_version,
176                    },
177                );
178            }
179        }
180
181        fn_info_map
182    }
183
184    fn compile_package<W: Write>(
185        resolution_graph: &ResolvedGraph,
186        writer: &mut W,
187    ) -> anyhow::Result<(MoveCompiledPackage, FnInfoMap)> {
188        let build_plan = BuildPlan::create(resolution_graph)?;
189        let mut fn_info = None;
190        let compiled_pkg = build_plan.compile_with_driver(writer, |compiler| {
191            let (files, units_res) = compiler.build()?;
192            match units_res {
193                Ok((units, warning_diags)) => {
194                    decorate_warnings(warning_diags, Some(&files));
195                    fn_info = Some(Self::fn_info(&units));
196                    Ok((files, units))
197                }
198                Err(error_diags) => {
199                    // with errors present don't even try decorating warnings output to avoid
200                    // clutter
201                    assert!(!error_diags.is_empty());
202                    let diags_buf =
203                        report_diagnostics_to_buffer(&files, error_diags, /* color */ true);
204                    if let Err(err) = std::io::stderr().write_all(&diags_buf) {
205                        anyhow::bail!("Cannot output compiler diagnostics: {}", err);
206                    }
207                    anyhow::bail!("Compilation error");
208                }
209            }
210        })?;
211        Ok((compiled_pkg, fn_info.unwrap()))
212    }
213
214    /// Given a `path` and a `build_config`, build the package in that path,
215    /// including its dependencies. If we are building the IOTA framework,
216    /// we skip the check that the addresses should be 0
217    pub fn build(self, path: &Path) -> IotaResult<CompiledPackage> {
218        let print_diags_to_stderr = self.print_diags_to_stderr;
219        let run_bytecode_verifier = self.run_bytecode_verifier;
220        let chain_id = self.chain_id.clone();
221        let resolution_graph = self.resolution_graph(path, chain_id.clone())?;
222        build_from_resolution_graph(
223            resolution_graph,
224            run_bytecode_verifier,
225            print_diags_to_stderr,
226            chain_id,
227        )
228    }
229
230    pub fn resolution_graph(
231        mut self,
232        path: &Path,
233        chain_id: Option<String>,
234    ) -> IotaResult<ResolvedGraph> {
235        if let Some(err_msg) = set_iota_flavor(&mut self.config) {
236            return Err(IotaError::ModuleBuildFailure { error: err_msg });
237        }
238
239        if self.print_diags_to_stderr {
240            self.config
241                .resolution_graph_for_package(path, chain_id, &mut std::io::stderr())
242        } else {
243            self.config
244                .resolution_graph_for_package(path, chain_id, &mut std::io::sink())
245        }
246        .map_err(|err| IotaError::ModuleBuildFailure {
247            error: format!("{err:?}"),
248        })
249    }
250}
251
252/// There may be additional information that needs to be displayed after
253/// diagnostics are reported (optionally report diagnostics themselves if files
254/// argument is provided).
255pub fn decorate_warnings(warning_diags: Diagnostics, files: Option<&MappedFiles>) {
256    let any_linter_warnings = warning_diags.any_with_prefix(LINT_WARNING_PREFIX);
257    let (filtered_diags_num, unique) =
258        warning_diags.filtered_source_diags_with_prefix(LINT_WARNING_PREFIX);
259    if let Some(f) = files {
260        report_warnings(f, warning_diags);
261    }
262    if any_linter_warnings {
263        eprintln!(
264            "Please report feedback on the linter warnings at https://github.com/iotaledger/iota/issues\n"
265        );
266    }
267    if filtered_diags_num > 0 {
268        eprintln!(
269            "Total number of linter warnings suppressed: {filtered_diags_num} (unique lints: {unique})"
270        );
271    }
272}
273
274/// Sets build config's default flavor to `Flavor::Iota`. Returns error message
275/// if the flavor was previously set to something else than `Flavor::Iota`.
276pub fn set_iota_flavor(build_config: &mut MoveBuildConfig) -> Option<String> {
277    use move_compiler::editions::Flavor;
278
279    let flavor = build_config.default_flavor.get_or_insert(Flavor::Iota);
280    if flavor != &Flavor::Iota {
281        return Some(format!(
282            "The flavor of the Move compiler cannot be overridden with anything but \
283                 \"{}\", but the default override was set to: \"{flavor}\"",
284            Flavor::Iota,
285        ));
286    }
287    None
288}
289
290pub fn build_from_resolution_graph(
291    resolution_graph: ResolvedGraph,
292    run_bytecode_verifier: bool,
293    print_diags_to_stderr: bool,
294    chain_id: Option<String>,
295) -> IotaResult<CompiledPackage> {
296    let (published_at, dependency_ids) = gather_published_ids(&resolution_graph, chain_id);
297
298    // collect bytecode dependencies as these are not returned as part of core
299    // `CompiledPackage`
300    let bytecode_deps = collect_bytecode_deps(&resolution_graph)?;
301
302    // compile!
303    let result = if print_diags_to_stderr {
304        BuildConfig::compile_package(&resolution_graph, &mut std::io::stderr())
305    } else {
306        BuildConfig::compile_package(&resolution_graph, &mut std::io::sink())
307    };
308
309    let (mut package, fn_info) = result.map_err(|error| IotaError::ModuleBuildFailure {
310        // Use [Debug] formatting to capture [anyhow] error context
311        error: format!("{error:?}"),
312    })?;
313
314    // Based on the information found in `fn_info`, fill in the metadata for each
315    // compiled module
316    fill_metadata(&mut package, &fn_info)?;
317
318    if run_bytecode_verifier {
319        verify_bytecode(&package, &fn_info)?;
320    }
321
322    Ok(CompiledPackage {
323        package,
324        published_at,
325        dependency_ids,
326        bytecode_deps,
327        dependency_graph: resolution_graph.graph,
328    })
329}
330
331/// Returns the deps from `resolution_graph` that have no source code
332fn collect_bytecode_deps(
333    resolution_graph: &ResolvedGraph,
334) -> IotaResult<Vec<(Symbol, CompiledModule)>> {
335    let mut bytecode_deps = vec![];
336    for (name, pkg) in resolution_graph.package_table.iter() {
337        if !pkg
338            .get_sources(&resolution_graph.build_options)
339            .unwrap()
340            .is_empty()
341        {
342            continue;
343        }
344        let modules =
345            pkg.get_bytecodes_bytes()
346                .map_err(|error| IotaError::ModuleDeserializationFailure {
347                    error: format!(
348                        "Deserializing bytecode dependency for package {name}: {error:?}"
349                    ),
350                })?;
351        for module in modules {
352            let module =
353                CompiledModule::deserialize_with_defaults(module.as_ref()).map_err(|error| {
354                    IotaError::ModuleDeserializationFailure {
355                        error: format!(
356                            "Deserializing bytecode dependency for package {name}: {error:?}"
357                        ),
358                    }
359                })?;
360            bytecode_deps.push((*name, module));
361        }
362    }
363
364    Ok(bytecode_deps)
365}
366
367/// Fill metadata
368fn fill_metadata(package: &mut MoveCompiledPackage, fn_info_map: &FnInfoMap) -> IotaResult<()> {
369    for module in package
370        .root_compiled_units
371        .iter_mut()
372        .map(|unit| &mut unit.unit.module)
373    {
374        let mut runtime_metadata = RuntimeModuleMetadata::default();
375        for fn_def in &module.function_defs {
376            let fn_handle = module.function_handle_at(fn_def.function);
377            let fn_name = module.identifier_at(fn_handle.name);
378            if let Some(version) = get_authenticator_version_from_fun(fn_name, module, fn_info_map)
379            {
380                runtime_metadata.add_function_attribute(
381                    fn_name.to_string(),
382                    IotaAttribute::authenticator_attribute(version),
383                );
384            };
385        }
386        if !runtime_metadata.is_empty() {
387            module.metadata.push(move_core_types::metadata::Metadata {
388                key: IOTA_METADATA_KEY.to_vec(),
389                value: RuntimeModuleMetadataWrapper::from(runtime_metadata).to_bcs_bytes(),
390            });
391        }
392    }
393    Ok(())
394}
395
396/// Check that the compiled modules in `package` are valid
397fn verify_bytecode(package: &MoveCompiledPackage, fn_info: &FnInfoMap) -> IotaResult<()> {
398    let compiled_modules = package.root_modules_map();
399    for m in compiled_modules.iter_modules() {
400        move_bytecode_verifier::verify_module_unmetered(m).map_err(|err| {
401            IotaError::ModuleVerificationFailure {
402                error: err.to_string(),
403            }
404        })?;
405        iota_bytecode_verifier::iota_verify_module_unmetered(m, fn_info)?;
406    }
407    // Don't change the link components to iota. It is correct as it is.
408    // TODO(https://github.com/MystenLabs/sui/issues/69): Run Move linker
409    Ok(())
410}
411
412impl CompiledPackage {
413    /// Return all of the bytecode modules in this package (not including direct
414    /// or transitive deps) Note: these are not topologically sorted by
415    /// dependency--use `get_dependency_sorted_modules` to produce a list of
416    /// modules suitable for publishing or static analysis
417    pub fn get_modules(&self) -> impl Iterator<Item = &CompiledModule> {
418        self.package.root_modules().map(|m| &m.unit.module)
419    }
420
421    /// Return all of the bytecode modules in this package (not including direct
422    /// or transitive deps) Note: these are not topologically sorted by
423    /// dependency--use `get_dependency_sorted_modules` to produce a list of
424    /// modules suitable for publishing or static analysis
425    pub fn into_modules(self) -> Vec<CompiledModule> {
426        self.package
427            .root_compiled_units
428            .into_iter()
429            .map(|m| m.unit.module)
430            .collect()
431    }
432
433    /// Return all of the bytecode modules that this package depends on (both
434    /// directly and transitively) Note: these are not topologically sorted
435    /// by dependency.
436    pub fn get_dependent_modules(&self) -> impl Iterator<Item = &CompiledModule> {
437        self.package
438            .deps_compiled_units
439            .iter()
440            .map(|(_, m)| &m.unit.module)
441            .chain(self.bytecode_deps.iter().map(|(_, m)| m))
442    }
443
444    /// Return all of the bytecode modules in this package and the modules of
445    /// its direct and transitive dependencies. Note: these are not
446    /// topologically sorted by dependency.
447    pub fn get_modules_and_deps(&self) -> impl Iterator<Item = &CompiledModule> {
448        self.package
449            .all_modules()
450            .map(|m| &m.unit.module)
451            .chain(self.bytecode_deps.iter().map(|(_, m)| m))
452    }
453
454    /// Return the bytecode modules in this package, topologically sorted in
455    /// dependency order. Optionally include dependencies that have not been
456    /// published (are at address 0x0), if `with_unpublished_deps` is true.
457    /// This is the function to call if you would like to publish
458    /// or statically analyze the modules.
459    pub fn get_dependency_sorted_modules(
460        &self,
461        with_unpublished_deps: bool,
462    ) -> Vec<CompiledModule> {
463        let all_modules = Modules::new(self.get_modules_and_deps());
464
465        // SAFETY: package built successfully
466        let modules = all_modules.compute_topological_order().unwrap();
467
468        if with_unpublished_deps {
469            // For each transitive dependent module, if they are not to be published, they
470            // must have a non-zero address (meaning they are already published
471            // on-chain).
472            modules
473                .filter(|module| module.address() == &AccountAddress::ZERO)
474                .cloned()
475                .collect()
476        } else {
477            // Collect all module IDs from the current package to be published (module names
478            // are not sufficient as we may have modules with the same names in
479            // user code and in IOTA framework which would result in the latter
480            // being pulled into a set of modules to be published).
481            let self_modules: HashSet<_> = self
482                .package
483                .root_modules_map()
484                .iter_modules()
485                .iter()
486                .map(|m| m.self_id())
487                .collect();
488
489            modules
490                .filter(|module| self_modules.contains(&module.self_id()))
491                .cloned()
492                .collect()
493        }
494    }
495
496    /// Return the set of Object IDs corresponding to this package's transitive
497    /// dependencies' storage package IDs (where to load those packages
498    /// on-chain).
499    pub fn get_dependency_storage_package_ids(&self) -> Vec<ObjectID> {
500        self.dependency_ids.published.values().copied().collect()
501    }
502
503    /// Return a digest of the bytecode modules in this package.
504    pub fn get_package_digest(&self, with_unpublished_deps: bool) -> [u8; 32] {
505        MovePackage::compute_digest_for_modules_and_deps(
506            &self.get_package_bytes(with_unpublished_deps),
507            self.dependency_ids.published.values(),
508        )
509    }
510
511    /// Return a serialized representation of the bytecode modules in this
512    /// package, topologically sorted in dependency order
513    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(); // safe because package built successfully
519                bytes
520            })
521            .collect()
522    }
523
524    /// Return the base64-encoded representation of the bytecode modules in this
525    /// package, topologically sorted in dependency order
526    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    /// Get bytecode modules from the IOTA System that are used by this package
534    pub fn get_iota_system_modules(&self) -> impl Iterator<Item = &CompiledModule> {
535        self.get_modules_and_deps()
536            .filter(|m| *m.self_id().address() == IOTA_SYSTEM_ADDRESS)
537    }
538
539    /// Get bytecode modules from the IOTA Framework that are used by this
540    /// package
541    pub fn get_iota_framework_modules(&self) -> impl Iterator<Item = &CompiledModule> {
542        self.get_modules_and_deps()
543            .filter(|m| *m.self_id().address() == IOTA_FRAMEWORK_ADDRESS)
544    }
545
546    /// Get bytecode modules from the Move stdlib that are used by this package
547    pub fn get_stdlib_modules(&self) -> impl Iterator<Item = &CompiledModule> {
548        self.get_modules_and_deps()
549            .filter(|m| *m.self_id().address() == MOVE_STDLIB_ADDRESS)
550    }
551
552    /// Get bytecode modules from Stardust that are used by this package
553    pub fn get_stardust_modules(&self) -> impl Iterator<Item = &CompiledModule> {
554        self.get_modules_and_deps()
555            .filter(|m| *m.self_id().address() == STARDUST_ADDRESS)
556    }
557
558    /// Generate layout schemas for all types declared by this package, as well
559    /// as all struct types passed into `entry` functions declared by
560    /// modules in this package (either directly or by reference).
561    /// These layout schemas can be consumed by clients (e.g., the TypeScript
562    /// SDK) to enable BCS serialization/deserialization of the package's
563    /// objects, tx arguments, and events.
564    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, /* include code */ false);
569            // 1. generate struct layouts for all declared types
570            '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                        // if all of t's type parameters are phantom, we can generate a type layout
575                        // we make this happen by creating a StructTag with dummy `type_params`,
576                        // since the layout generator won't look at them. we
577                        // need to do this because SerdeLayoutBuilder will refuse to generate a
578                        // layout for any open StructTag, but phantom types
579                        // cannot affect the layout of a struct, so we just use dummy values
580                        dummy_type_parameters.push(TypeTag::Signer)
581                    } else {
582                        // open type--do not attempt to generate a layout
583                        // TODO: handle generating layouts for open types?
584                        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            // 2. generate struct layouts for all parameters of `entry` funs
596            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    /// Checks whether this package corresponds to a built-in framework
629    pub fn is_system_package(&self) -> bool {
630        // System packages always have "published-at" addresses
631        let Ok(published_at) = self.published_at else {
632            return false;
633        };
634
635        is_system_package(published_at)
636    }
637
638    /// Checks for root modules with non-zero package addresses.  Returns an
639    /// arbitrary one, if one can be found, otherwise returns `None`.
640    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    /// Find the map of packages that are immediate dependencies of the root
703    /// modules, joined with the set of bytecode dependencies.
704    pub fn find_immediate_deps_pkgs_to_keep(
705        &self,
706        with_unpublished_deps: bool,
707    ) -> Result<BTreeMap<Symbol, ObjectID>, anyhow::Error> {
708        // Start from the root modules (or all modules if with_unpublished_deps is true
709        // as we need to include modules with 0x0 address)
710        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        // Find the immediate dependencies for each root module and store the package
724        // name in the pkgs_to_keep set. This basically prunes the packages that
725        // are not used based on the modules information.
726        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        // If a package depends on another published package that has only bytecode
746        // without source code available, we need to include also that package
747        // as dep.
748        pkgs_to_keep.extend(self.bytecode_deps.iter().map(|(name, _)| *name));
749
750        // Finally, filter out packages that are published and exist in the manifest at
751        // the compilation time but are not referenced in the source code.
752        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
762/// Create a set of [Dependencies] from a [SystemPackagesVersion]; the
763/// dependencies are override git dependencies to the specific revision given by
764/// the [SystemPackagesVersion]
765pub 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    // TODO: return ref here for better efficiency? Borrow checker +
790    // all_modules_map() make it hard to do this
791    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            // TODO: remove this once version fields are removed from all manifests
807            "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    /// Set of published dependencies (name and address).
839    pub published: BTreeMap<Symbol, ObjectID>,
840    /// Set of unpublished dependencies (name).
841    pub unpublished: BTreeSet<Symbol>,
842    /// Set of dependencies with invalid `published-at` addresses.
843    pub invalid: BTreeMap<Symbol, String>,
844    /// Set of dependencies that have conflicting `published-at` addresses. The
845    /// key refers to the package, and the tuple refers to the address in
846    /// the (Move.lock, Move.toml) respectively.
847    pub conflicting: BTreeMap<Symbol, (ObjectID, ObjectID)>,
848}
849
850/// Partition packages in `resolution_graph` into one of four groups:
851/// - The ID that the package itself is published at (if it is published)
852/// - The IDs of dependencies that have been published
853/// - The names of packages that have not been published on chain.
854/// - The names of packages that have a `published-at` field that isn't filled
855///   with a valid address.
856pub 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            // Separate out the root package as a special case
872            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}