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, ObjectId, Version, move_package::MovePackage};
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    pub fn with_allow_view_function(mut self) -> Self {
170        self.protocol_build_config.allow_view_function = true;
171        self
172    }
173
174    fn fn_info(units: &[AnnotatedCompiledModule]) -> FnInfoMap {
175        let mut fn_info_map = BTreeMap::new();
176        for u in units {
177            let mod_addr = Address::new(u.named_module.address.into_bytes());
178            let mod_name = u.named_module.module.name().to_string();
179            let mod_is_test = u.attributes.is_test_or_test_only();
180            for (_, s, info) in &u.function_infos {
181                let fn_name = s.as_str().to_string();
182                let is_test = mod_is_test || info.attributes.is_test_or_test_only();
183                let authenticator_version = info.attributes.get_authenticator();
184                let is_view = info.attributes.is_view();
185                fn_info_map.insert(
186                    FnInfoKey {
187                        fn_name,
188                        mod_name: mod_name.clone(),
189                        mod_addr,
190                    },
191                    FnInfo {
192                        is_test,
193                        authenticator_version,
194                        is_view,
195                    },
196                );
197            }
198        }
199
200        fn_info_map
201    }
202
203    fn compile_package<W: Write>(
204        resolution_graph: &ResolvedGraph,
205        writer: &mut W,
206    ) -> anyhow::Result<(MoveCompiledPackage, FnInfoMap)> {
207        let build_plan = BuildPlan::create(resolution_graph)?;
208        let mut fn_info = None;
209        let compiled_pkg = build_plan.compile_with_driver(writer, |compiler| {
210            let (files, units_res) = compiler.build()?;
211            match units_res {
212                Ok((units, warning_diags)) => {
213                    decorate_warnings(warning_diags, Some(&files));
214                    fn_info = Some(Self::fn_info(&units));
215                    Ok((files, units))
216                }
217                Err(error_diags) => {
218                    // with errors present don't even try decorating warnings output to avoid
219                    // clutter
220                    assert!(!error_diags.is_empty());
221                    let diags_buf =
222                        report_diagnostics_to_buffer(&files, error_diags, /* color */ true);
223                    if let Err(err) = std::io::stderr().write_all(&diags_buf) {
224                        anyhow::bail!("Cannot output compiler diagnostics: {err}");
225                    }
226                    anyhow::bail!("Compilation error");
227                }
228            }
229        })?;
230        Ok((compiled_pkg, fn_info.unwrap()))
231    }
232
233    /// Given a `path` and a `build_config`, build the package in that path,
234    /// including its dependencies. If we are building the IOTA framework,
235    /// we skip the check that the addresses should be 0
236    pub fn build(self, path: &Path) -> IotaResult<CompiledPackage> {
237        let print_diags_to_stderr = self.print_diags_to_stderr;
238        let run_bytecode_verifier = self.run_bytecode_verifier;
239        let chain_id = self.chain_id.clone();
240        let protocol_build_config = self.protocol_build_config;
241        let resolution_graph = self.resolution_graph(path, chain_id.clone())?;
242        build_from_resolution_graph(
243            resolution_graph,
244            run_bytecode_verifier,
245            print_diags_to_stderr,
246            chain_id,
247            &protocol_build_config,
248        )
249    }
250
251    pub fn resolution_graph(
252        mut self,
253        path: &Path,
254        chain_id: Option<String>,
255    ) -> IotaResult<ResolvedGraph> {
256        if let Some(err_msg) = set_iota_flavor(&mut self.config) {
257            return Err(IotaError::ModuleBuildFailure { error: err_msg });
258        }
259
260        if self.print_diags_to_stderr {
261            self.config
262                .resolution_graph_for_package(path, chain_id, &mut std::io::stderr())
263        } else {
264            self.config
265                .resolution_graph_for_package(path, chain_id, &mut std::io::sink())
266        }
267        .map_err(|err| IotaError::ModuleBuildFailure {
268            error: format!("{err:?}"),
269        })
270    }
271}
272
273/// There may be additional information that needs to be displayed after
274/// diagnostics are reported (optionally report diagnostics themselves if files
275/// argument is provided).
276pub fn decorate_warnings(warning_diags: Diagnostics, files: Option<&MappedFiles>) {
277    let any_linter_warnings = warning_diags.any_with_prefix(LINT_WARNING_PREFIX);
278    let (filtered_diags_num, unique) =
279        warning_diags.filtered_source_diags_with_prefix(LINT_WARNING_PREFIX);
280    if let Some(f) = files {
281        report_warnings(f, warning_diags);
282    }
283    if any_linter_warnings {
284        eprintln!(
285            "Please report feedback on the linter warnings at https://github.com/iotaledger/iota/issues\n"
286        );
287    }
288    if filtered_diags_num > 0 {
289        eprintln!(
290            "Total number of linter warnings suppressed: {filtered_diags_num} (unique lints: {unique})"
291        );
292    }
293}
294
295/// Sets build config's default flavor to `Flavor::Iota`. Returns error message
296/// if the flavor was previously set to something else than `Flavor::Iota`.
297pub fn set_iota_flavor(build_config: &mut MoveBuildConfig) -> Option<String> {
298    use move_compiler::editions::Flavor;
299
300    let flavor = build_config.default_flavor.get_or_insert(Flavor::Iota);
301    if flavor != &Flavor::Iota {
302        return Some(format!(
303            "The flavor of the Move compiler cannot be overridden with anything but \
304                 \"{}\", but the default override was set to: \"{flavor}\"",
305            Flavor::Iota,
306        ));
307    }
308    None
309}
310
311pub fn build_from_resolution_graph(
312    resolution_graph: ResolvedGraph,
313    run_bytecode_verifier: bool,
314    print_diags_to_stderr: bool,
315    chain_id: Option<String>,
316    protocol_build_config: &ProtocolBuildConfig,
317) -> IotaResult<CompiledPackage> {
318    let (published_at, dependency_ids) = gather_published_ids(&resolution_graph, chain_id);
319
320    // collect bytecode dependencies as these are not returned as part of core
321    // `CompiledPackage`
322    let bytecode_deps = collect_bytecode_deps(&resolution_graph)?;
323
324    // compile!
325    let result = if print_diags_to_stderr {
326        BuildConfig::compile_package(&resolution_graph, &mut std::io::stderr())
327    } else {
328        BuildConfig::compile_package(&resolution_graph, &mut std::io::sink())
329    };
330
331    let (mut package, fn_info) = result.map_err(|error| IotaError::ModuleBuildFailure {
332        // Use [Debug] formatting to capture [anyhow] error context
333        error: format!("{error:?}"),
334    })?;
335
336    // Based on the information found in `fn_info`, fill in the metadata for each
337    // compiled module
338    fill_metadata(&mut package, &fn_info, protocol_build_config)?;
339
340    if run_bytecode_verifier {
341        verify_bytecode(&package, &fn_info, protocol_build_config)?;
342    }
343
344    Ok(CompiledPackage {
345        package,
346        published_at,
347        dependency_ids,
348        bytecode_deps,
349        dependency_graph: resolution_graph.graph,
350    })
351}
352
353/// Returns the deps from `resolution_graph` that have no source code
354fn collect_bytecode_deps(
355    resolution_graph: &ResolvedGraph,
356) -> IotaResult<Vec<(Symbol, CompiledModule)>> {
357    let mut bytecode_deps = vec![];
358    for (name, pkg) in resolution_graph.package_table.iter() {
359        if !pkg
360            .get_sources(&resolution_graph.build_options)
361            .unwrap()
362            .is_empty()
363        {
364            continue;
365        }
366        let modules =
367            pkg.get_bytecodes_bytes()
368                .map_err(|error| IotaError::ModuleDeserializationFailure {
369                    error: format!(
370                        "Deserializing bytecode dependency for package {name}: {error:?}"
371                    ),
372                })?;
373        for module in modules {
374            let module =
375                CompiledModule::deserialize_with_defaults(module.as_ref()).map_err(|error| {
376                    IotaError::ModuleDeserializationFailure {
377                        error: format!(
378                            "Deserializing bytecode dependency for package {name}: {error:?}"
379                        ),
380                    }
381                })?;
382            bytecode_deps.push((*name, module));
383        }
384    }
385
386    Ok(bytecode_deps)
387}
388
389/// Fill metadata
390fn fill_metadata(
391    package: &mut MoveCompiledPackage,
392    fn_info_map: &FnInfoMap,
393    protocol_build_config: &ProtocolBuildConfig,
394) -> IotaResult<()> {
395    for module in package
396        .root_compiled_units
397        .iter_mut()
398        .map(|unit| &mut unit.unit.module)
399    {
400        // View functions are only representable in V2 (dynamic) runtime metadata,
401        // which is gated behind the `package_metadata_with_dynamic_module_metadata`
402        // protocol feature.
403        let mut runtime_metadata = if protocol_build_config.allow_view_function {
404            RuntimeModuleMetadata::v2()
405        } else {
406            RuntimeModuleMetadata::v1()
407        };
408        for fn_def in &module.function_defs {
409            let fn_handle = module.function_handle_at(fn_def.function);
410            let fn_name = module.identifier_at(fn_handle.name);
411            if let Some(version) =
412                get_authenticator_version_from_fun(fn_name.as_str(), module, fn_info_map)
413            {
414                let attribute = if protocol_build_config.allow_view_function {
415                    IotaAttribute::V2(IotaAttributeV2::authenticator_attribute(version))
416                } else {
417                    IotaAttribute::V1(IotaAttributeV1::authenticator_attribute(version))
418                };
419                runtime_metadata.add_function_attribute(fn_name.to_string(), attribute);
420            };
421            if is_view_function_from_fn_info(fn_name, module, fn_info_map) {
422                if protocol_build_config.allow_view_function {
423                    runtime_metadata.add_function_attribute(
424                        fn_name.to_string(),
425                        IotaAttribute::V2(IotaAttributeV2::view_attribute()),
426                    );
427                } else {
428                    // The `View` attribute only exists in V2 (dynamic) runtime
429                    // metadata, gated behind the
430                    // `package_metadata_with_dynamic_module_metadata` protocol
431                    // feature. When it is off we drop the attribute rather than emit
432                    // metadata a not-yet-upgraded validator cannot deserialize, but
433                    // warn since the function will not be recorded as a view function.
434                    eprintln!(
435                        "warning: function '{}::{}' is marked `#[view]`, but the target \
436                        network protocol does not support view-function metadata; the `View` \
437                        attribute will not be published",
438                        module.name(),
439                        fn_name,
440                    );
441                }
442            }
443        }
444        if !runtime_metadata.is_empty() {
445            module.metadata.push(move_core_types::metadata::Metadata {
446                key: IOTA_METADATA_KEY.to_vec(),
447                value: RuntimeModuleMetadataWrapper::from(runtime_metadata).to_bcs_bytes(),
448            });
449        }
450    }
451    Ok(())
452}
453
454/// Check that the compiled modules in `package` are valid
455fn verify_bytecode(
456    package: &MoveCompiledPackage,
457    fn_info: &FnInfoMap,
458    protocol_build_config: &ProtocolBuildConfig,
459) -> IotaResult<()> {
460    let compiled_modules = package.root_modules_map();
461    for m in compiled_modules.iter_modules() {
462        move_bytecode_verifier::verify_module_unmetered(m).map_err(|err| {
463            IotaError::ModuleVerificationFailure {
464                error: err.to_string(),
465            }
466        })?;
467        // The client build only sanity-checks the bytecode; whether the `View`
468        // attribute may actually be published is decided by the target network's
469        // protocol at publish time, so accept it here.
470        iota_bytecode_verifier::iota_verify_module_unmetered(m, fn_info, protocol_build_config)?;
471    }
472    // Don't change the link components to iota. It is correct as it is.
473    // TODO(https://github.com/MystenLabs/sui/issues/69): Run Move linker
474    Ok(())
475}
476
477impl CompiledPackage {
478    /// Return all of the bytecode modules in this package (not including direct
479    /// or transitive deps) Note: these are not topologically sorted by
480    /// dependency--use `get_dependency_sorted_modules` to produce a list of
481    /// modules suitable for publishing or static analysis
482    pub fn get_modules(&self) -> impl Iterator<Item = &CompiledModule> {
483        self.package.root_modules().map(|m| &m.unit.module)
484    }
485
486    /// Return all of the bytecode modules in this package (not including direct
487    /// or transitive deps) Note: these are not topologically sorted by
488    /// dependency--use `get_dependency_sorted_modules` to produce a list of
489    /// modules suitable for publishing or static analysis
490    pub fn into_modules(self) -> Vec<CompiledModule> {
491        self.package
492            .root_compiled_units
493            .into_iter()
494            .map(|m| m.unit.module)
495            .collect()
496    }
497
498    /// Return all of the bytecode modules that this package depends on (both
499    /// directly and transitively) Note: these are not topologically sorted
500    /// by dependency.
501    pub fn get_dependent_modules(&self) -> impl Iterator<Item = &CompiledModule> {
502        self.package
503            .deps_compiled_units
504            .iter()
505            .map(|(_, m)| &m.unit.module)
506            .chain(self.bytecode_deps.iter().map(|(_, m)| m))
507    }
508
509    /// Return all of the bytecode modules in this package and the modules of
510    /// its direct and transitive dependencies. Note: these are not
511    /// topologically sorted by dependency.
512    pub fn get_modules_and_deps(&self) -> impl Iterator<Item = &CompiledModule> {
513        self.package
514            .all_modules()
515            .map(|m| &m.unit.module)
516            .chain(self.bytecode_deps.iter().map(|(_, m)| m))
517    }
518
519    /// Return the bytecode modules in this package, topologically sorted in
520    /// dependency order. Optionally include dependencies that have not been
521    /// published (are at address 0x0), if `with_unpublished_deps` is true.
522    /// This is the function to call if you would like to publish
523    /// or statically analyze the modules.
524    pub fn get_dependency_sorted_modules(
525        &self,
526        with_unpublished_deps: bool,
527    ) -> Vec<CompiledModule> {
528        let all_modules = Modules::new(self.get_modules_and_deps());
529
530        // SAFETY: package built successfully
531        let modules = all_modules.compute_topological_order().unwrap();
532
533        if with_unpublished_deps {
534            // For each transitive dependent module, if they are not to be published, they
535            // must have a non-zero address (meaning they are already published
536            // on-chain).
537            modules
538                .filter(|module| module.address() == &AccountAddress::ZERO)
539                .cloned()
540                .collect()
541        } else {
542            // Collect all module IDs from the current package to be published (module names
543            // are not sufficient as we may have modules with the same names in
544            // user code and in IOTA framework which would result in the latter
545            // being pulled into a set of modules to be published).
546            let self_modules: HashSet<_> = self
547                .package
548                .root_modules_map()
549                .iter_modules()
550                .iter()
551                .map(|m| m.self_id())
552                .collect();
553
554            modules
555                .filter(|module| self_modules.contains(&module.self_id()))
556                .cloned()
557                .collect()
558        }
559    }
560
561    /// Return the set of Object IDs corresponding to this package's transitive
562    /// dependencies' storage package IDs (where to load those packages
563    /// on-chain).
564    pub fn get_dependency_storage_package_ids(&self) -> Vec<ObjectId> {
565        self.dependency_ids.published.values().copied().collect()
566    }
567
568    /// Return a digest of the bytecode modules in this package.
569    pub fn get_package_digest(&self, with_unpublished_deps: bool) -> [u8; 32] {
570        MovePackage::compute_digest_for_modules_and_deps(
571            &self.get_package_bytes(with_unpublished_deps),
572            self.dependency_ids.published.values(),
573        )
574        .into_inner()
575    }
576
577    /// Return a serialized representation of the bytecode modules in this
578    /// package, topologically sorted in dependency order
579    pub fn get_package_bytes(&self, with_unpublished_deps: bool) -> Vec<Vec<u8>> {
580        self.get_dependency_sorted_modules(with_unpublished_deps)
581            .iter()
582            .map(|m| {
583                let mut bytes = Vec::new();
584                m.serialize_with_version(m.version, &mut bytes).unwrap(); // safe because package built successfully
585                bytes
586            })
587            .collect()
588    }
589
590    /// Return the base64-encoded representation of the bytecode modules in this
591    /// package, topologically sorted in dependency order
592    pub fn get_package_base64(&self, with_unpublished_deps: bool) -> Vec<Base64> {
593        self.get_package_bytes(with_unpublished_deps)
594            .iter()
595            .map(|b| Base64::from_bytes(b))
596            .collect()
597    }
598
599    /// Size in bytes this package would occupy on-chain once published.
600    ///
601    /// Mirrors [`MovePackage::size`]: it sums the version tag, the serialized
602    /// module map (module names and bytecode), the type origin table (one entry
603    /// per struct and enum), and the linkage table (`dep_count` fixed-size
604    /// entries). This is the value the protocol checks against
605    /// `max_move_package_size`, and is larger than the sum of the `.mv` files
606    /// alone because of the metadata terms.
607    ///
608    /// `dep_count` is the number of linkage-table entries the published package
609    /// will have. Pass the count of tree-shaken transitive dependencies for an
610    /// exact result, or [`Self::get_published_dependencies_ids`]`().len()` for
611    /// an offline upper-bound estimate.
612    pub fn published_size(&self, with_unpublished_deps: bool, dep_count: usize) -> u64 {
613        // Per-entry cost of the linkage table: original ID, upgraded ID, and
614        // upgraded version, matching `MovePackage::size`.
615        const LINKAGE_ENTRY_SIZE: usize =
616            ObjectId::LENGTH + ObjectId::LENGTH + std::mem::size_of::<Version>();
617
618        let mut size = std::mem::size_of::<Version>();
619
620        for module in self.get_dependency_sorted_modules(with_unpublished_deps) {
621            let module_name_len = module.name().as_str().len();
622
623            let mut bytes = Vec::new();
624            // Safe because the package built successfully.
625            module
626                .serialize_with_version(module.version, &mut bytes)
627                .unwrap();
628            size += module_name_len + bytes.len();
629
630            // Type origin table: one entry per struct and enum defined here.
631            for struct_def in module.struct_defs() {
632                let handle = module.datatype_handle_at(struct_def.struct_handle);
633                size += module_name_len
634                    + module.identifier_at(handle.name).as_str().len()
635                    + ObjectId::LENGTH;
636            }
637            for enum_def in module.enum_defs() {
638                let handle = module.datatype_handle_at(enum_def.enum_handle);
639                size += module_name_len
640                    + module.identifier_at(handle.name).as_str().len()
641                    + ObjectId::LENGTH;
642            }
643        }
644
645        size += dep_count * LINKAGE_ENTRY_SIZE;
646        size as u64
647    }
648
649    /// Number of entries the on-chain linkage table would have: the published
650    /// dependency packages reachable from this package's modules, following
651    /// module dependencies through the locally built modules (no network).
652    ///
653    /// This mirrors the tree shaking the publish flow performs, so the offline
654    /// size estimate ignores published dependencies the code does not actually
655    /// use. It can still differ from the exact on-chain linkage when a
656    /// dependency's on-chain linkage lists packages its bytecode does not
657    /// reference; the exact count comes from the tree-shaken publish flow.
658    pub fn linkage_dependency_count(&self) -> usize {
659        // Package of every locally available module.
660        let mut module_pkg: BTreeMap<ModuleId, PackageName> = BTreeMap::new();
661        for unit in self.package.all_modules() {
662            if let Some(pkg) = unit.unit.package_name {
663                module_pkg.insert(unit.unit.module.self_id(), pkg);
664            }
665        }
666        for (pkg, module) in &self.bytecode_deps {
667            module_pkg.insert(module.self_id(), *pkg);
668        }
669
670        // Package-level dependency edges: a package points at every package any
671        // of its modules reference. On-chain linkage is resolved at package
672        // granularity (a dependency contributes its whole linkage table), so the
673        // graph is walked the same way rather than by individual module.
674        let mut edges: BTreeMap<PackageName, BTreeSet<PackageName>> = BTreeMap::new();
675        let mut add_edges = |owner: PackageName, module: &CompiledModule| {
676            for dep in module.immediate_dependencies() {
677                if let Some(dep_pkg) = module_pkg.get(&dep) {
678                    if *dep_pkg != owner {
679                        edges.entry(owner).or_default().insert(*dep_pkg);
680                    }
681                }
682            }
683        };
684        for unit in self.package.all_modules() {
685            if let Some(owner) = unit.unit.package_name {
686                add_edges(owner, &unit.unit.module);
687            }
688        }
689        for (owner, module) in &self.bytecode_deps {
690            add_edges(*owner, module);
691        }
692
693        // Transitive closure of packages reachable from the package(s) being
694        // published.
695        let roots: BTreeSet<PackageName> = self
696            .package
697            .root_modules()
698            .filter_map(|unit| unit.unit.package_name)
699            .collect();
700        let mut reached: BTreeSet<PackageName> = BTreeSet::new();
701        let mut queue: VecDeque<PackageName> = roots.iter().copied().collect();
702        while let Some(pkg) = queue.pop_front() {
703            if let Some(deps) = edges.get(&pkg) {
704                for dep in deps {
705                    if reached.insert(*dep) {
706                        queue.push_back(*dep);
707                    }
708                }
709            }
710        }
711        for root in &roots {
712            reached.remove(root);
713        }
714
715        // The linkage table holds only published dependency packages.
716        self.dependency_ids
717            .published
718            .keys()
719            .filter(|pkg| reached.contains(pkg))
720            .count()
721    }
722
723    /// Get bytecode modules from the IOTA System that are used by this package
724    pub fn get_iota_system_modules(&self) -> impl Iterator<Item = &CompiledModule> {
725        self.get_modules_and_deps()
726            .filter(|m| m.self_id().address().as_ref() == Address::SYSTEM.as_bytes())
727    }
728
729    /// Get bytecode modules from the IOTA Framework that are used by this
730    /// package
731    pub fn get_iota_framework_modules(&self) -> impl Iterator<Item = &CompiledModule> {
732        self.get_modules_and_deps()
733            .filter(|m| m.self_id().address().as_ref() == Address::FRAMEWORK.as_bytes())
734    }
735
736    /// Get bytecode modules from the Move stdlib that are used by this package
737    pub fn get_stdlib_modules(&self) -> impl Iterator<Item = &CompiledModule> {
738        self.get_modules_and_deps()
739            .filter(|m| m.self_id().address().as_ref() == Address::STD.as_bytes())
740    }
741
742    /// Get bytecode modules from Stardust that are used by this package
743    pub fn get_stardust_modules(&self) -> impl Iterator<Item = &CompiledModule> {
744        self.get_modules_and_deps()
745            .filter(|m| m.self_id().address().as_ref() == Address::STARDUST.as_bytes())
746    }
747
748    /// Generate layout schemas for all types declared by this package, as well
749    /// as all struct types passed into `entry` functions declared by
750    /// modules in this package (either directly or by reference).
751    /// These layout schemas can be consumed by clients (e.g., the TypeScript
752    /// SDK) to enable BCS serialization/deserialization of the package's
753    /// objects, tx arguments, and events.
754    pub fn generate_struct_layouts(&self) -> Registry {
755        let pool = &mut normalized::RcPool::new();
756        let mut package_types = BTreeSet::new();
757        for m in self.get_modules() {
758            let normalized_m = normalized::Module::new(pool, m, /* include code */ false);
759            // 1. generate struct layouts for all declared types
760            'structs: for (name, s) in normalized_m.structs {
761                let mut dummy_type_parameters = Vec::new();
762                for t in &s.type_parameters {
763                    if t.is_phantom {
764                        // if all of t's type parameters are phantom, we can generate a type layout
765                        // we make this happen by creating a StructTag with dummy `type_params`,
766                        // since the layout generator won't look at them. we
767                        // need to do this because SerdeLayoutBuilder will refuse to generate a
768                        // layout for any open StructTag, but phantom types
769                        // cannot affect the layout of a struct, so we just use dummy values
770                        dummy_type_parameters.push(TypeTag::Signer)
771                    } else {
772                        // open type--do not attempt to generate a layout
773                        // TODO: handle generating layouts for open types?
774                        continue 'structs;
775                    }
776                }
777                debug_assert!(dummy_type_parameters.len() == s.type_parameters.len());
778                package_types.insert(StructTag {
779                    address: *m.address(),
780                    module: m.name().to_owned(),
781                    name: name.as_ident_str().to_owned(),
782                    type_params: dummy_type_parameters,
783                });
784            }
785            // 2. generate struct layouts for all parameters of `entry` funs
786            for (_name, f) in normalized_m.functions {
787                if f.is_entry {
788                    for t in &*f.parameters {
789                        let tag_opt = match &**t {
790                            Type::Address
791                            | Type::Bool
792                            | Type::Signer
793                            | Type::TypeParameter(_)
794                            | Type::U8
795                            | Type::U16
796                            | Type::U32
797                            | Type::U64
798                            | Type::U128
799                            | Type::U256
800                            | Type::Vector(_) => continue,
801                            Type::Reference(_, inner) => inner.to_struct_tag(pool),
802                            Type::Datatype(_) => t.to_struct_tag(pool),
803                        };
804                        if let Some(tag) = tag_opt {
805                            package_types.insert(tag);
806                        }
807                    }
808                }
809            }
810        }
811        let mut layout_builder = SerdeLayoutBuilder::new(self);
812        for tag in &package_types {
813            layout_builder.build_data_layout(tag).unwrap();
814        }
815        layout_builder.into_registry()
816    }
817
818    /// Checks whether this package corresponds to a built-in framework
819    pub fn is_system_package(&self) -> bool {
820        // System packages always have "published-at" addresses
821        let Ok(published_at) = self.published_at else {
822            return false;
823        };
824
825        published_at.is_system_package()
826    }
827
828    /// Checks for root modules with non-zero package addresses.  Returns an
829    /// arbitrary one, if one can be found, otherwise returns `None`.
830    pub fn published_root_module(&self) -> Option<&CompiledModule> {
831        self.package.root_compiled_units.iter().find_map(|unit| {
832            if unit.unit.module.self_id().address() != &AccountAddress::ZERO {
833                Some(&unit.unit.module)
834            } else {
835                None
836            }
837        })
838    }
839
840    pub fn verify_unpublished_dependencies(
841        &self,
842        unpublished_deps: &BTreeSet<Symbol>,
843    ) -> IotaResult<()> {
844        if unpublished_deps.is_empty() {
845            return Ok(());
846        }
847
848        let errors = self
849            .package
850            .deps_compiled_units
851            .iter()
852            .filter_map(|(p, m)| {
853                if !unpublished_deps.contains(p) || m.unit.module.address() == &AccountAddress::ZERO
854                {
855                    return None;
856                }
857                Some(format!(
858                    " - {}::{} in dependency {}",
859                    m.unit.module.address(),
860                    m.unit.name,
861                    p
862                ))
863            })
864            .collect::<Vec<String>>();
865
866        if errors.is_empty() {
867            return Ok(());
868        }
869
870        let mut error_message = vec![];
871        error_message.push(
872            "The following modules in package dependencies set a non-zero self-address:".into(),
873        );
874        error_message.extend(errors);
875        error_message.push(
876            "If these packages really are unpublished, their self-addresses should be set \
877	     to \"0x0\" in the [addresses] section of the manifest when publishing. If they \
878	     are already published, ensure they specify the address in the `published-at` of \
879	     their Move.toml manifest."
880                .into(),
881        );
882
883        Err(IotaError::ModulePublishFailure {
884            error: error_message.join("\n"),
885        })
886    }
887
888    pub fn get_published_dependencies_ids(&self) -> Vec<ObjectId> {
889        self.dependency_ids.published.values().cloned().collect()
890    }
891
892    /// Find the map of packages that are immediate dependencies of the root
893    /// modules, joined with the set of bytecode dependencies.
894    pub fn find_immediate_deps_pkgs_to_keep(
895        &self,
896        with_unpublished_deps: bool,
897    ) -> Result<BTreeMap<Symbol, ObjectId>, anyhow::Error> {
898        // Start from the root modules (or all modules if with_unpublished_deps is true
899        // as we need to include modules with 0x0 address)
900        let root_modules: Vec<_> = if with_unpublished_deps {
901            self.package
902                .all_compiled_units_with_source()
903                .filter(|m| m.unit.address.into_inner() == AccountAddress::ZERO)
904                .map(|x| x.unit.clone())
905                .collect()
906        } else {
907            self.package
908                .root_modules()
909                .map(|x| x.unit.clone())
910                .collect()
911        };
912
913        // Find the immediate dependencies for each root module and store the package
914        // name in the pkgs_to_keep set. This basically prunes the packages that
915        // are not used based on the modules information.
916        let mut pkgs_to_keep: BTreeSet<Symbol> = BTreeSet::new();
917        let module_to_pkg_name: BTreeMap<_, _> = self
918            .package
919            .all_modules()
920            .map(|m| (m.unit.module.self_id(), m.unit.package_name))
921            .collect();
922
923        for module in &root_modules {
924            let immediate_deps = module.module.immediate_dependencies();
925            for dep in immediate_deps {
926                if let Some(pkg_name) = module_to_pkg_name.get(&dep) {
927                    let Some(pkg_name) = pkg_name else {
928                        bail!("Expected a package name but it's None")
929                    };
930                    pkgs_to_keep.insert(*pkg_name);
931                }
932            }
933        }
934
935        // If a package depends on another published package that has only bytecode
936        // without source code available, we need to include also that package
937        // as dep.
938        pkgs_to_keep.extend(self.bytecode_deps.iter().map(|(name, _)| *name));
939
940        // Finally, filter out packages that are published and exist in the manifest at
941        // the compilation time but are not referenced in the source code.
942        Ok(self
943            .dependency_ids
944            .clone()
945            .published
946            .into_iter()
947            .filter(|(pkg_name, _)| pkgs_to_keep.contains(pkg_name))
948            .collect())
949    }
950}
951
952/// Create a set of [Dependencies] from a [SystemPackagesVersion]; the
953/// dependencies are override git dependencies to the specific revision given by
954/// the [SystemPackagesVersion]
955pub fn implicit_deps(packages: &SystemPackagesVersion) -> Dependencies {
956    packages
957        .packages
958        .iter()
959        .map(|package| {
960            (
961                package.package_name.clone().into(),
962                Dependency::Internal(InternalDependency {
963                    kind: DependencyKind::Git(GitInfo {
964                        git_url: SYSTEM_GIT_REPO.into(),
965                        git_rev: packages.git_revision.clone().into(),
966                        subdir: package.repo_path.clone().into(),
967                    }),
968                    subst: None,
969                    digest: None,
970                    dep_override: true,
971                }),
972            )
973        })
974        .collect()
975}
976
977impl GetModule for CompiledPackage {
978    type Error = anyhow::Error;
979    // TODO: return ref here for better efficiency? Borrow checker +
980    // all_modules_map() make it hard to do this
981    type Item = CompiledModule;
982
983    fn get_module_by_id(&self, id: &ModuleId) -> Result<Option<Self::Item>, Self::Error> {
984        Ok(self.package.all_modules_map().get_module(id).ok().cloned())
985    }
986}
987
988pub const PUBLISHED_AT_MANIFEST_FIELD: &str = "published-at";
989
990pub struct IotaPackageHooks;
991
992impl PackageHooks for IotaPackageHooks {
993    fn custom_package_info_fields(&self) -> Vec<String> {
994        vec![
995            PUBLISHED_AT_MANIFEST_FIELD.to_string(),
996            // TODO: remove this once version fields are removed from all manifests
997            "version".to_string(),
998        ]
999    }
1000
1001    fn resolve_on_chain_dependency(
1002        &self,
1003        _dep_name: move_symbol_pool::Symbol,
1004        _info: &OnChainInfo,
1005    ) -> anyhow::Result<()> {
1006        Ok(())
1007    }
1008
1009    fn custom_resolve_pkg_id(
1010        &self,
1011        manifest: &SourceManifest,
1012    ) -> anyhow::Result<PackageIdentifier> {
1013        if (!cfg!(debug_assertions) || cfg!(test))
1014            && manifest.package.edition == Some(Edition::DEVELOPMENT)
1015        {
1016            return Err(Edition::DEVELOPMENT.unknown_edition_error());
1017        }
1018        Ok(manifest.package.name)
1019    }
1020
1021    fn resolve_version(&self, _: &SourceManifest) -> anyhow::Result<Option<Symbol>> {
1022        Ok(None)
1023    }
1024}
1025
1026#[derive(Debug, Clone)]
1027pub struct PackageDependencies {
1028    /// Set of published dependencies (name and address).
1029    pub published: BTreeMap<Symbol, ObjectId>,
1030    /// Set of unpublished dependencies (name).
1031    pub unpublished: BTreeSet<Symbol>,
1032    /// Set of dependencies with invalid `published-at` addresses.
1033    pub invalid: BTreeMap<Symbol, String>,
1034    /// Set of dependencies that have conflicting `published-at` addresses. The
1035    /// key refers to the package, and the tuple refers to the address in
1036    /// the (Move.lock, Move.toml) respectively.
1037    pub conflicting: BTreeMap<Symbol, (ObjectId, ObjectId)>,
1038}
1039
1040/// Partition packages in `resolution_graph` into one of four groups:
1041/// - The ID that the package itself is published at (if it is published)
1042/// - The IDs of dependencies that have been published
1043/// - The names of packages that have not been published on chain.
1044/// - The names of packages that have a `published-at` field that isn't filled
1045///   with a valid address.
1046pub fn gather_published_ids(
1047    resolution_graph: &ResolvedGraph,
1048    chain_id: Option<String>,
1049) -> (Result<ObjectId, PublishedAtError>, PackageDependencies) {
1050    let root = resolution_graph.root_package();
1051
1052    let mut published = BTreeMap::new();
1053    let mut unpublished = BTreeSet::new();
1054    let mut invalid = BTreeMap::new();
1055    let mut conflicting = BTreeMap::new();
1056    let mut published_at = Err(PublishedAtError::NotPresent);
1057
1058    for (name, package) in &resolution_graph.package_table {
1059        let property = resolve_published_id(package, chain_id.clone());
1060        if name == &root {
1061            // Separate out the root package as a special case
1062            published_at = property;
1063            continue;
1064        }
1065
1066        match property {
1067            Ok(id) => {
1068                published.insert(*name, id);
1069            }
1070            Err(PublishedAtError::NotPresent) => {
1071                unpublished.insert(*name);
1072            }
1073            Err(PublishedAtError::Invalid(value)) => {
1074                invalid.insert(*name, value);
1075            }
1076            Err(PublishedAtError::Conflict {
1077                id_lock,
1078                id_manifest,
1079            }) => {
1080                conflicting.insert(*name, (id_lock, id_manifest));
1081            }
1082        };
1083    }
1084
1085    (
1086        published_at,
1087        PackageDependencies {
1088            published,
1089            unpublished,
1090            invalid,
1091            conflicting,
1092        },
1093    )
1094}
1095
1096pub fn published_at_property(manifest: &SourceManifest) -> Result<ObjectId, PublishedAtError> {
1097    let Some(value) = manifest
1098        .package
1099        .custom_properties
1100        .get(&Symbol::from(PUBLISHED_AT_MANIFEST_FIELD))
1101    else {
1102        return Err(PublishedAtError::NotPresent);
1103    };
1104
1105    ObjectId::from_str(value.as_str()).map_err(|_| PublishedAtError::Invalid(value.to_owned()))
1106}
1107
1108pub fn check_unpublished_dependencies(unpublished: &BTreeSet<Symbol>) -> Result<(), IotaError> {
1109    if unpublished.is_empty() {
1110        return Ok(());
1111    };
1112
1113    let mut error_messages = unpublished
1114        .iter()
1115        .map(|name| {
1116            format!(
1117                "Package dependency \"{name}\" does not specify a published address \
1118		 (the Move.toml manifest for \"{name}\" does not contain a 'published-at' field, \
1119		 nor is there a 'published-id' in the Move.lock). \
1120		 You can use `iota move manage-package` to record the on-chain address for \"{name}\".",
1121            )
1122        })
1123        .collect::<Vec<_>>();
1124
1125    error_messages.push(
1126        "If this is intentional, you may use the --with-unpublished-dependencies flag to \
1127             continue publishing these dependencies as part of your package (they won't be \
1128             linked against existing packages on-chain)."
1129            .into(),
1130    );
1131
1132    Err(IotaError::ModulePublishFailure {
1133        error: error_messages.join("\n"),
1134    })
1135}
1136
1137pub fn check_invalid_dependencies(invalid: &BTreeMap<Symbol, String>) -> Result<(), IotaError> {
1138    if invalid.is_empty() {
1139        return Ok(());
1140    }
1141
1142    let error_messages = invalid
1143        .iter()
1144        .map(|(name, value)| {
1145            format!(
1146                "Package dependency \"{name}\" does not specify a valid published \
1147		 address: could not parse value \"{value}\" for 'published-at' field in Move.toml \
1148                 or 'published-id' in Move.lock file."
1149            )
1150        })
1151        .collect::<Vec<_>>();
1152
1153    Err(IotaError::ModulePublishFailure {
1154        error: error_messages.join("\n"),
1155    })
1156}
1157
1158pub fn check_conflicting_addresses(
1159    conflicting: &BTreeMap<Symbol, (ObjectId, ObjectId)>,
1160    dump_bytecode_base64: bool,
1161) -> Result<(), IotaError> {
1162    if conflicting.is_empty() {
1163        return Ok(());
1164    }
1165
1166    let suffix = if conflicting.len() == 1 { "" } else { "es" };
1167
1168    let err_msg = format!("found the following conflicting published package address{suffix}:");
1169    let suggestion_message =
1170        "You may want to:
1171 - delete the published-at address in the `Move.toml` if the `Move.lock` address is correct; OR
1172 - update the `Move.lock` address to be the same as the `Move.toml`; OR
1173 - check that your `iota active-env` corresponds to the chain on which the package is published (i.e., devnet, testnet, mainnet); OR
1174 - contact the maintainer if this package is a dependency and request resolving the conflict.";
1175
1176    let conflicting_addresses_msg = conflicting
1177        .iter()
1178        .map(|(_, (id_lock, id_manifest))| {
1179            format!(
1180                "  `Move.toml` contains published-at address \
1181                 {id_manifest} but `Move.lock` file contains published-at address {id_lock}."
1182            )
1183        })
1184        .collect::<Vec<_>>()
1185        .join("\n");
1186
1187    let error = format!("{err_msg}\n{conflicting_addresses_msg}\n{suggestion_message}");
1188
1189    let err = if dump_bytecode_base64 {
1190        IotaError::ModuleBuildFailure { error }
1191    } else {
1192        IotaError::ModulePublishFailure { error }
1193    };
1194
1195    Err(err)
1196}
1197
1198/// Create a set of [Dependencies] from a [SystemPackagesVersion] that resolve
1199/// to the system package sources on the local filesystem, relative to this
1200/// crate's location in the iota source tree.
1201pub fn local_implicit_deps(packages: &SystemPackagesVersion) -> Dependencies {
1202    // This relies on the compile-time `CARGO_MANIFEST_DIR` still existing on disk
1203    // at runtime, which holds when the binary is built and run on the same host.
1204    // `CARGO_MANIFEST_DIR` is `<repo>/crates/iota-move-build`, so the repo root is
1205    // two levels up and each system package lives at `<repo>/<repo_path>`.
1206    let repo_root = Path::new(env!("CARGO_MANIFEST_DIR"))
1207        .parent()
1208        .and_then(Path::parent)
1209        .expect("iota-move-build manifest dir should have a grandparent");
1210    packages
1211        .packages
1212        .iter()
1213        .map(|package| {
1214            (
1215                package.package_name.clone().into(),
1216                Dependency::Internal(InternalDependency {
1217                    kind: DependencyKind::Local(repo_root.join(&package.repo_path)),
1218                    subst: None,
1219                    digest: None,
1220                    dep_override: true,
1221                }),
1222            )
1223        })
1224        .collect()
1225}
1226
1227/// Useful for callers (e.g. the `#[sim_test]` static initializer) that don't
1228/// have a [SystemPackagesVersion] on hand and just want the framework resolved
1229/// from the local checkout.
1230pub fn local_implicit_deps_latest() -> Dependencies {
1231    local_implicit_deps(latest_system_packages())
1232}