Skip to main content

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, 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};
21// `ProtocolBuildConfig` lives in `iota-types` (both this crate and the verifier
22// depend on it); re-export it here so callers that build a `BuildConfig` can
23// reach it without a separate `iota-types` dependency.
24pub 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/// Wrapper around the core Move `CompiledPackage` with some IOTA-specific
98/// traits and info
99#[derive(Debug, Clone)]
100pub struct CompiledPackage {
101    pub package: MoveCompiledPackage,
102    /// Address the package is recorded as being published at.
103    pub published_at: Result<ObjectId, PublishedAtError>,
104    /// The dependency IDs of this package
105    pub dependency_ids: PackageDependencies,
106    /// The bytecode modules that this package depends on (both directly and
107    /// transitively), i.e. on-chain dependencies.
108    pub bytecode_deps: Vec<(PackageName, CompiledModule)>,
109    /// Transitive dependency graph of a Move package
110    pub dependency_graph: DependencyGraph,
111}
112
113/// Wrapper around the core Move `BuildConfig` with some IOTA-specific info
114#[derive(Clone)]
115pub struct BuildConfig {
116    pub config: MoveBuildConfig,
117    /// If true, run the Move bytecode verifier on the bytecode from a
118    /// successful build
119    pub run_bytecode_verifier: bool,
120    /// If true, print build diagnostics to stderr--no printing if false
121    pub print_diags_to_stderr: bool,
122    /// The chain ID that compilation is with respect to (e.g., required to
123    /// resolve published dependency IDs from the `Move.lock`).
124    pub chain_id: Option<String>,
125    /// The build config for the protocol config of the network that the package
126    /// is being built (and verified) for.
127    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            // TODO: in the future, we may want to provide local implicit dependencies to tests
141            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                    // with errors present don't even try decorating warnings output to avoid
214                    // clutter
215                    assert!(!error_diags.is_empty());
216                    let diags_buf =
217                        report_diagnostics_to_buffer(&files, error_diags, /* color */ 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    /// Given a `path` and a `build_config`, build the package in that path,
229    /// including its dependencies. If we are building the IOTA framework,
230    /// we skip the check that the addresses should be 0
231    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
268/// There may be additional information that needs to be displayed after
269/// diagnostics are reported (optionally report diagnostics themselves if files
270/// argument is provided).
271pub 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
290/// Sets build config's default flavor to `Flavor::Iota`. Returns error message
291/// if the flavor was previously set to something else than `Flavor::Iota`.
292pub 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    // collect bytecode dependencies as these are not returned as part of core
316    // `CompiledPackage`
317    let bytecode_deps = collect_bytecode_deps(&resolution_graph)?;
318
319    // compile!
320    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        // Use [Debug] formatting to capture [anyhow] error context
328        error: format!("{error:?}"),
329    })?;
330
331    // Based on the information found in `fn_info`, fill in the metadata for each
332    // compiled module
333    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
348/// Returns the deps from `resolution_graph` that have no source code
349fn 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
384/// Fill metadata
385fn 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        // View functions are only representable in V2 (dynamic) runtime metadata,
396        // which is gated behind the `package_metadata_with_dynamic_module_metadata`
397        // protocol feature.
398        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                    // The `View` attribute only exists in V2 (dynamic) runtime
424                    // metadata, gated behind the
425                    // `package_metadata_with_dynamic_module_metadata` protocol
426                    // feature. When it is off we drop the attribute rather than emit
427                    // metadata a not-yet-upgraded validator cannot deserialize, but
428                    // warn since the function will not be recorded as a view function.
429                    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
449/// Check that the compiled modules in `package` are valid
450fn 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        // The client build only sanity-checks the bytecode; whether the `View`
463        // attribute may actually be published is decided by the target network's
464        // protocol at publish time, so accept it here.
465        iota_bytecode_verifier::iota_verify_module_unmetered(m, fn_info, protocol_build_config)?;
466    }
467    // Don't change the link components to iota. It is correct as it is.
468    // TODO(https://github.com/MystenLabs/sui/issues/69): Run Move linker
469    Ok(())
470}
471
472impl CompiledPackage {
473    /// Return all of the bytecode modules in this package (not including direct
474    /// or transitive deps) Note: these are not topologically sorted by
475    /// dependency--use `get_dependency_sorted_modules` to produce a list of
476    /// modules suitable for publishing or static analysis
477    pub fn get_modules(&self) -> impl Iterator<Item = &CompiledModule> {
478        self.package.root_modules().map(|m| &m.unit.module)
479    }
480
481    /// Return all of the bytecode modules in this package (not including direct
482    /// or transitive deps) Note: these are not topologically sorted by
483    /// dependency--use `get_dependency_sorted_modules` to produce a list of
484    /// modules suitable for publishing or static analysis
485    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    /// Return all of the bytecode modules that this package depends on (both
494    /// directly and transitively) Note: these are not topologically sorted
495    /// by dependency.
496    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    /// Return all of the bytecode modules in this package and the modules of
505    /// its direct and transitive dependencies. Note: these are not
506    /// topologically sorted by dependency.
507    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    /// Return the bytecode modules in this package, topologically sorted in
515    /// dependency order. Optionally include dependencies that have not been
516    /// published (are at address 0x0), if `with_unpublished_deps` is true.
517    /// This is the function to call if you would like to publish
518    /// or statically analyze the modules.
519    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        // SAFETY: package built successfully
526        let modules = all_modules.compute_topological_order().unwrap();
527
528        if with_unpublished_deps {
529            // For each transitive dependent module, if they are not to be published, they
530            // must have a non-zero address (meaning they are already published
531            // on-chain).
532            modules
533                .filter(|module| module.address() == &AccountAddress::ZERO)
534                .cloned()
535                .collect()
536        } else {
537            // Collect all module IDs from the current package to be published (module names
538            // are not sufficient as we may have modules with the same names in
539            // user code and in IOTA framework which would result in the latter
540            // being pulled into a set of modules to be published).
541            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    /// Return the set of Object IDs corresponding to this package's transitive
557    /// dependencies' storage package IDs (where to load those packages
558    /// on-chain).
559    pub fn get_dependency_storage_package_ids(&self) -> Vec<ObjectId> {
560        self.dependency_ids.published.values().copied().collect()
561    }
562
563    /// Return a digest of the bytecode modules in this package.
564    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    /// Return a serialized representation of the bytecode modules in this
573    /// package, topologically sorted in dependency order
574    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(); // safe because package built successfully
580                bytes
581            })
582            .collect()
583    }
584
585    /// Return the base64-encoded representation of the bytecode modules in this
586    /// package, topologically sorted in dependency order
587    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    /// Size in bytes this package would occupy on-chain once published.
595    ///
596    /// Mirrors [`MovePackage::size`]: it sums the version tag, the serialized
597    /// module map (module names and bytecode), the type origin table (one entry
598    /// per struct and enum), and the linkage table (`dep_count` fixed-size
599    /// entries). This is the value the protocol checks against
600    /// `max_move_package_size`, and is larger than the sum of the `.mv` files
601    /// alone because of the metadata terms.
602    ///
603    /// `dep_count` is the number of linkage-table entries the published package
604    /// will have. Pass the count of tree-shaken transitive dependencies for an
605    /// exact result, or [`Self::get_published_dependencies_ids`]`().len()` for
606    /// an offline upper-bound estimate.
607    pub fn published_size(&self, with_unpublished_deps: bool, dep_count: usize) -> u64 {
608        // Per-entry cost of the linkage table: original ID, upgraded ID, and
609        // upgraded version, matching `MovePackage::size`.
610        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            // Safe because the package built successfully.
620            module
621                .serialize_with_version(module.version, &mut bytes)
622                .unwrap();
623            size += module_name_len + bytes.len();
624
625            // Type origin table: one entry per struct and enum defined here.
626            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    /// Number of entries the on-chain linkage table would have: the published
645    /// dependency packages reachable from this package's modules, following
646    /// module dependencies through the locally built modules (no network).
647    ///
648    /// This mirrors the tree shaking the publish flow performs, so the offline
649    /// size estimate ignores published dependencies the code does not actually
650    /// use. It can still differ from the exact on-chain linkage when a
651    /// dependency's on-chain linkage lists packages its bytecode does not
652    /// reference; the exact count comes from the tree-shaken publish flow.
653    pub fn linkage_dependency_count(&self) -> usize {
654        // Package of every locally available module.
655        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        // Package-level dependency edges: a package points at every package any
666        // of its modules reference. On-chain linkage is resolved at package
667        // granularity (a dependency contributes its whole linkage table), so the
668        // graph is walked the same way rather than by individual module.
669        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        // Transitive closure of packages reachable from the package(s) being
689        // published.
690        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        // The linkage table holds only published dependency packages.
711        self.dependency_ids
712            .published
713            .keys()
714            .filter(|pkg| reached.contains(pkg))
715            .count()
716    }
717
718    /// Get bytecode modules from the IOTA System that are used by this package
719    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    /// Get bytecode modules from the IOTA Framework that are used by this
725    /// package
726    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    /// Get bytecode modules from the Move stdlib that are used by this package
732    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    /// Get bytecode modules from Stardust that are used by this package
738    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    /// Generate layout schemas for all types declared by this package, as well
744    /// as all struct types passed into `entry` functions declared by
745    /// modules in this package (either directly or by reference).
746    /// These layout schemas can be consumed by clients (e.g., the TypeScript
747    /// SDK) to enable BCS serialization/deserialization of the package's
748    /// objects, tx arguments, and events.
749    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, /* include code */ false);
754            // 1. generate struct layouts for all declared types
755            '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                        // if all of t's type parameters are phantom, we can generate a type layout
760                        // we make this happen by creating a StructTag with dummy `type_params`,
761                        // since the layout generator won't look at them. we
762                        // need to do this because SerdeLayoutBuilder will refuse to generate a
763                        // layout for any open StructTag, but phantom types
764                        // cannot affect the layout of a struct, so we just use dummy values
765                        dummy_type_parameters.push(TypeTag::Signer)
766                    } else {
767                        // open type--do not attempt to generate a layout
768                        // TODO: handle generating layouts for open types?
769                        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            // 2. generate struct layouts for all parameters of `entry` funs
781            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    /// Checks whether this package corresponds to a built-in framework
814    pub fn is_system_package(&self) -> bool {
815        // System packages always have "published-at" addresses
816        let Ok(published_at) = self.published_at else {
817            return false;
818        };
819
820        published_at.is_system_package()
821    }
822
823    /// Checks for root modules with non-zero package addresses.  Returns an
824    /// arbitrary one, if one can be found, otherwise returns `None`.
825    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    /// Find the map of packages that are immediate dependencies of the root
888    /// modules, joined with the set of bytecode dependencies.
889    pub fn find_immediate_deps_pkgs_to_keep(
890        &self,
891        with_unpublished_deps: bool,
892    ) -> Result<BTreeMap<Symbol, ObjectId>, anyhow::Error> {
893        // Start from the root modules (or all modules if with_unpublished_deps is true
894        // as we need to include modules with 0x0 address)
895        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        // Find the immediate dependencies for each root module and store the package
909        // name in the pkgs_to_keep set. This basically prunes the packages that
910        // are not used based on the modules information.
911        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        // If a package depends on another published package that has only bytecode
931        // without source code available, we need to include also that package
932        // as dep.
933        pkgs_to_keep.extend(self.bytecode_deps.iter().map(|(name, _)| *name));
934
935        // Finally, filter out packages that are published and exist in the manifest at
936        // the compilation time but are not referenced in the source code.
937        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
947/// Create a set of [Dependencies] from a [SystemPackagesVersion]; the
948/// dependencies are override git dependencies to the specific revision given by
949/// the [SystemPackagesVersion]
950pub 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    // TODO: return ref here for better efficiency? Borrow checker +
975    // all_modules_map() make it hard to do this
976    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            // TODO: remove this once version fields are removed from all manifests
992            "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    /// Set of published dependencies (name and address).
1024    pub published: BTreeMap<Symbol, ObjectId>,
1025    /// Set of unpublished dependencies (name).
1026    pub unpublished: BTreeSet<Symbol>,
1027    /// Set of dependencies with invalid `published-at` addresses.
1028    pub invalid: BTreeMap<Symbol, String>,
1029    /// Set of dependencies that have conflicting `published-at` addresses. The
1030    /// key refers to the package, and the tuple refers to the address in
1031    /// the (Move.lock, Move.toml) respectively.
1032    pub conflicting: BTreeMap<Symbol, (ObjectId, ObjectId)>,
1033}
1034
1035/// Partition packages in `resolution_graph` into one of four groups:
1036/// - The ID that the package itself is published at (if it is published)
1037/// - The IDs of dependencies that have been published
1038/// - The names of packages that have not been published on chain.
1039/// - The names of packages that have a `published-at` field that isn't filled with a valid address.
1040pub 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            // Separate out the root package as a special case
1056            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
1192/// Create a set of [Dependencies] from a [SystemPackagesVersion] that resolve
1193/// to the system package sources on the local filesystem, relative to this
1194/// crate's location in the iota source tree.
1195pub fn local_implicit_deps(packages: &SystemPackagesVersion) -> Dependencies {
1196    // This relies on the compile-time `CARGO_MANIFEST_DIR` still existing on disk
1197    // at runtime, which holds when the binary is built and run on the same host.
1198    // `CARGO_MANIFEST_DIR` is `<repo>/crates/iota-move-build`, so the repo root is
1199    // two levels up and each system package lives at `<repo>/<repo_path>`.
1200    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
1221/// Useful for callers (e.g. the `#[sim_test]` static initializer) that don't
1222/// have a [SystemPackagesVersion] on hand and just want the framework resolved
1223/// from the local checkout.
1224pub fn local_implicit_deps_latest() -> Dependencies {
1225    local_implicit_deps(latest_system_packages())
1226}