1extern crate move_ir_types;
6
7use std::{
8 collections::{BTreeMap, BTreeSet, HashSet},
9 io::Write,
10 path::Path,
11 str::FromStr,
12};
13
14use fastcrypto::encoding::Base64;
15use iota_package_management::{PublishedAtError, resolve_published_id};
16use iota_types::{
17 BRIDGE_ADDRESS, IOTA_FRAMEWORK_ADDRESS, IOTA_SYSTEM_ADDRESS, MOVE_STDLIB_ADDRESS,
18 STARDUST_ADDRESS,
19 base_types::ObjectID,
20 error::{IotaError, IotaResult},
21 is_system_package,
22 move_package::{FnInfo, FnInfoKey, FnInfoMap, MovePackage},
23};
24use iota_verifier::verifier as iota_bytecode_verifier;
25use move_binary_format::{
26 CompiledModule,
27 normalized::{self, Type},
28};
29use move_bytecode_utils::{Modules, layout::SerdeLayoutBuilder, module_cache::GetModule};
30use move_compiler::{
31 compiled_unit::AnnotatedCompiledModule,
32 diagnostics::{Diagnostics, report_diagnostics_to_buffer, report_warnings},
33 editions::Edition,
34 linters::LINT_WARNING_PREFIX,
35 shared::files::MappedFiles,
36};
37use move_core_types::{
38 account_address::AccountAddress,
39 language_storage::{ModuleId, StructTag, TypeTag},
40};
41use move_package::{
42 BuildConfig as MoveBuildConfig,
43 compilation::{
44 build_plan::BuildPlan, compiled_package::CompiledPackage as MoveCompiledPackage,
45 },
46 package_hooks::{PackageHooks, PackageIdentifier},
47 resolution::resolution_graph::ResolvedGraph,
48 source_package::parsed_manifest::{OnChainInfo, PackageName, SourceManifest},
49};
50use move_symbol_pool::Symbol;
51use serde_reflection::Registry;
52
53#[cfg(test)]
54#[path = "unit_tests/build_tests.rs"]
55mod build_tests;
56
57#[derive(Debug, Clone)]
60pub struct CompiledPackage {
61 pub package: MoveCompiledPackage,
62 pub published_at: Result<ObjectID, PublishedAtError>,
64 pub dependency_ids: PackageDependencies,
66 pub bytecode_deps: Vec<(PackageName, CompiledModule)>,
69}
70
71#[derive(Clone)]
73pub struct BuildConfig {
74 pub config: MoveBuildConfig,
75 pub run_bytecode_verifier: bool,
78 pub print_diags_to_stderr: bool,
80 pub chain_id: Option<String>,
83}
84
85impl BuildConfig {
86 pub fn new_for_testing() -> Self {
87 move_package::package_hooks::register_package_hooks(Box::new(IotaPackageHooks));
88 let mut build_config: Self = Default::default();
89 let install_dir = tempfile::tempdir().unwrap().into_path();
90 let lock_file = install_dir.join("Move.lock");
91 build_config.config.install_dir = Some(install_dir);
92 build_config.config.lock_file = Some(lock_file);
93 build_config
94 .config
95 .lint_flag
96 .set(move_compiler::linters::LintLevel::None);
97 build_config.config.silence_warnings = true;
98 build_config
99 }
100
101 pub fn new_for_testing_replace_addresses<I, S>(dep_original_addresses: I) -> Self
102 where
103 I: IntoIterator<Item = (S, ObjectID)>,
104 S: Into<String>,
105 {
106 let mut build_config = Self::new_for_testing();
107 for (addr_name, obj_id) in dep_original_addresses {
108 build_config
109 .config
110 .additional_named_addresses
111 .insert(addr_name.into(), AccountAddress::from(obj_id));
112 }
113 build_config
114 }
115
116 fn fn_info(units: &[AnnotatedCompiledModule]) -> FnInfoMap {
117 let mut fn_info_map = BTreeMap::new();
118 for u in units {
119 let mod_addr = u.named_module.address.into_inner();
120 let mod_is_test = u.attributes.is_test_or_test_only();
121 for (_, s, info) in &u.function_infos {
122 let fn_name = s.as_str().to_string();
123 let is_test = mod_is_test || info.attributes.is_test_or_test_only();
124 fn_info_map.insert(FnInfoKey { fn_name, mod_addr }, FnInfo { is_test });
125 }
126 }
127
128 fn_info_map
129 }
130
131 fn compile_package<W: Write>(
132 resolution_graph: ResolvedGraph,
133 writer: &mut W,
134 ) -> anyhow::Result<(MoveCompiledPackage, FnInfoMap)> {
135 let build_plan = BuildPlan::create(resolution_graph)?;
136 let mut fn_info = None;
137 let compiled_pkg = build_plan.compile_with_driver(writer, |compiler| {
138 let (files, units_res) = compiler.build()?;
139 match units_res {
140 Ok((units, warning_diags)) => {
141 decorate_warnings(warning_diags, Some(&files));
142 fn_info = Some(Self::fn_info(&units));
143 Ok((files, units))
144 }
145 Err(error_diags) => {
146 assert!(!error_diags.is_empty());
149 let diags_buf =
150 report_diagnostics_to_buffer(&files, error_diags, true);
151 if let Err(err) = std::io::stderr().write_all(&diags_buf) {
152 anyhow::bail!("Cannot output compiler diagnostics: {}", err);
153 }
154 anyhow::bail!("Compilation error");
155 }
156 }
157 })?;
158 Ok((compiled_pkg, fn_info.unwrap()))
159 }
160
161 pub fn build(self, path: &Path) -> IotaResult<CompiledPackage> {
165 let print_diags_to_stderr = self.print_diags_to_stderr;
166 let run_bytecode_verifier = self.run_bytecode_verifier;
167 let chain_id = self.chain_id.clone();
168 let resolution_graph = self.resolution_graph(path, chain_id.clone())?;
169 build_from_resolution_graph(
170 resolution_graph,
171 run_bytecode_verifier,
172 print_diags_to_stderr,
173 chain_id,
174 )
175 }
176
177 pub fn resolution_graph(
178 mut self,
179 path: &Path,
180 chain_id: Option<String>,
181 ) -> IotaResult<ResolvedGraph> {
182 if let Some(err_msg) = set_iota_flavor(&mut self.config) {
183 return Err(IotaError::ModuleBuildFailure { error: err_msg });
184 }
185
186 if self.print_diags_to_stderr {
187 self.config
188 .resolution_graph_for_package(path, chain_id, &mut std::io::stderr())
189 } else {
190 self.config
191 .resolution_graph_for_package(path, chain_id, &mut std::io::sink())
192 }
193 .map_err(|err| IotaError::ModuleBuildFailure {
194 error: format!("{:?}", err),
195 })
196 }
197}
198
199pub fn decorate_warnings(warning_diags: Diagnostics, files: Option<&MappedFiles>) {
203 let any_linter_warnings = warning_diags.any_with_prefix(LINT_WARNING_PREFIX);
204 let (filtered_diags_num, unique) =
205 warning_diags.filtered_source_diags_with_prefix(LINT_WARNING_PREFIX);
206 if let Some(f) = files {
207 report_warnings(f, warning_diags);
208 }
209 if any_linter_warnings {
210 eprintln!(
211 "Please report feedback on the linter warnings at https://github.com/iotaledger/iota/issues\n"
212 );
213 }
214 if filtered_diags_num > 0 {
215 eprintln!(
216 "Total number of linter warnings suppressed: {filtered_diags_num} (unique lints: {unique})"
217 );
218 }
219}
220
221pub fn set_iota_flavor(build_config: &mut MoveBuildConfig) -> Option<String> {
224 use move_compiler::editions::Flavor;
225
226 let flavor = build_config.default_flavor.get_or_insert(Flavor::Iota);
227 if flavor != &Flavor::Iota {
228 return Some(format!(
229 "The flavor of the Move compiler cannot be overridden with anything but \
230 \"{}\", but the default override was set to: \"{flavor}\"",
231 Flavor::Iota,
232 ));
233 }
234 None
235}
236
237pub fn build_from_resolution_graph(
238 resolution_graph: ResolvedGraph,
239 run_bytecode_verifier: bool,
240 print_diags_to_stderr: bool,
241 chain_id: Option<String>,
242) -> IotaResult<CompiledPackage> {
243 let (published_at, dependency_ids) = gather_published_ids(&resolution_graph, chain_id);
244
245 let mut bytecode_deps = vec![];
248 for (name, pkg) in resolution_graph.package_table.iter() {
249 if !pkg
250 .get_sources(&resolution_graph.build_options)
251 .unwrap()
252 .is_empty()
253 {
254 continue;
255 }
256 let modules =
257 pkg.get_bytecodes_bytes()
258 .map_err(|error| IotaError::ModuleDeserializationFailure {
259 error: format!(
260 "Deserializing bytecode dependency for package {}: {:?}",
261 name, error
262 ),
263 })?;
264 for module in modules {
265 let module =
266 CompiledModule::deserialize_with_defaults(module.as_ref()).map_err(|error| {
267 IotaError::ModuleDeserializationFailure {
268 error: format!(
269 "Deserializing bytecode dependency for package {}: {:?}",
270 name, error
271 ),
272 }
273 })?;
274 bytecode_deps.push((*name, module));
275 }
276 }
277
278 let result = if print_diags_to_stderr {
279 BuildConfig::compile_package(resolution_graph, &mut std::io::stderr())
280 } else {
281 BuildConfig::compile_package(resolution_graph, &mut std::io::sink())
282 };
283 let (package, fn_info) = match result {
286 Err(error) => {
287 return Err(IotaError::ModuleBuildFailure {
288 error: format!("{:?}", error),
289 });
290 }
291 Ok((package, fn_info)) => (package, fn_info),
292 };
293 let compiled_modules = package.root_modules_map();
294 if run_bytecode_verifier {
295 for m in compiled_modules.iter_modules() {
296 move_bytecode_verifier::verify_module_unmetered(m).map_err(|err| {
297 IotaError::ModuleVerificationFailure {
298 error: err.to_string(),
299 }
300 })?;
301 iota_bytecode_verifier::iota_verify_module_unmetered(m, &fn_info)?;
302 }
303 }
305 Ok(CompiledPackage {
306 package,
307 published_at,
308 dependency_ids,
309 bytecode_deps,
310 })
311}
312
313impl CompiledPackage {
314 pub fn get_modules(&self) -> impl Iterator<Item = &CompiledModule> {
319 self.package.root_modules().map(|m| &m.unit.module)
320 }
321
322 pub fn into_modules(self) -> Vec<CompiledModule> {
327 self.package
328 .root_compiled_units
329 .into_iter()
330 .map(|m| m.unit.module)
331 .collect()
332 }
333
334 pub fn get_dependent_modules(&self) -> impl Iterator<Item = &CompiledModule> {
338 self.package
339 .deps_compiled_units
340 .iter()
341 .map(|(_, m)| &m.unit.module)
342 .chain(self.bytecode_deps.iter().map(|(_, m)| m))
343 }
344
345 pub fn get_modules_and_deps(&self) -> impl Iterator<Item = &CompiledModule> {
349 self.package
350 .all_modules()
351 .map(|m| &m.unit.module)
352 .chain(self.bytecode_deps.iter().map(|(_, m)| m))
353 }
354
355 pub fn get_dependency_sorted_modules(
361 &self,
362 with_unpublished_deps: bool,
363 ) -> Vec<CompiledModule> {
364 let all_modules = Modules::new(self.get_modules_and_deps());
365
366 let modules = all_modules.compute_topological_order().unwrap();
368
369 if with_unpublished_deps {
370 modules
374 .filter(|module| module.address() == &AccountAddress::ZERO)
375 .cloned()
376 .collect()
377 } else {
378 let self_modules: HashSet<_> = self
383 .package
384 .root_modules_map()
385 .iter_modules()
386 .iter()
387 .map(|m| m.self_id())
388 .collect();
389
390 modules
391 .filter(|module| self_modules.contains(&module.self_id()))
392 .cloned()
393 .collect()
394 }
395 }
396
397 pub fn get_dependency_storage_package_ids(&self) -> Vec<ObjectID> {
401 self.dependency_ids.published.values().copied().collect()
402 }
403
404 pub fn get_package_digest(&self, with_unpublished_deps: bool) -> [u8; 32] {
405 MovePackage::compute_digest_for_modules_and_deps(
406 &self.get_package_bytes(with_unpublished_deps),
407 self.dependency_ids.published.values(),
408 )
409 }
410
411 pub fn get_package_bytes(&self, with_unpublished_deps: bool) -> Vec<Vec<u8>> {
414 self.get_dependency_sorted_modules(with_unpublished_deps)
415 .iter()
416 .map(|m| {
417 let mut bytes = Vec::new();
418 m.serialize_with_version(m.version, &mut bytes).unwrap(); bytes
420 })
421 .collect()
422 }
423
424 pub fn get_package_base64(&self, with_unpublished_deps: bool) -> Vec<Base64> {
427 self.get_package_bytes(with_unpublished_deps)
428 .iter()
429 .map(|b| Base64::from_bytes(b))
430 .collect()
431 }
432
433 pub fn get_bridge_modules(&self) -> impl Iterator<Item = &CompiledModule> {
435 self.get_modules_and_deps()
436 .filter(|m| *m.self_id().address() == BRIDGE_ADDRESS)
437 }
438
439 pub fn get_iota_system_modules(&self) -> impl Iterator<Item = &CompiledModule> {
441 self.get_modules_and_deps()
442 .filter(|m| *m.self_id().address() == IOTA_SYSTEM_ADDRESS)
443 }
444
445 pub fn get_iota_framework_modules(&self) -> impl Iterator<Item = &CompiledModule> {
448 self.get_modules_and_deps()
449 .filter(|m| *m.self_id().address() == IOTA_FRAMEWORK_ADDRESS)
450 }
451
452 pub fn get_stdlib_modules(&self) -> impl Iterator<Item = &CompiledModule> {
454 self.get_modules_and_deps()
455 .filter(|m| *m.self_id().address() == MOVE_STDLIB_ADDRESS)
456 }
457
458 pub fn get_stardust_modules(&self) -> impl Iterator<Item = &CompiledModule> {
460 self.get_modules_and_deps()
461 .filter(|m| *m.self_id().address() == STARDUST_ADDRESS)
462 }
463
464 pub fn generate_struct_layouts(&self) -> Registry {
471 let mut package_types = BTreeSet::new();
472 for m in self.get_modules() {
473 let normalized_m = normalized::Module::new(m);
474 'structs: for (name, s) in normalized_m.structs {
476 let mut dummy_type_parameters = Vec::new();
477 for t in &s.type_parameters {
478 if t.is_phantom {
479 dummy_type_parameters.push(TypeTag::Signer)
486 } else {
487 continue 'structs;
490 }
491 }
492 debug_assert!(dummy_type_parameters.len() == s.type_parameters.len());
493 package_types.insert(StructTag {
494 address: *m.address(),
495 module: m.name().to_owned(),
496 name,
497 type_params: dummy_type_parameters,
498 });
499 }
500 for (_name, f) in normalized_m.functions {
502 if f.is_entry {
503 for t in f.parameters {
504 let tag_opt = match t.clone() {
505 Type::Address
506 | Type::Bool
507 | Type::Signer
508 | Type::TypeParameter(_)
509 | Type::U8
510 | Type::U16
511 | Type::U32
512 | Type::U64
513 | Type::U128
514 | Type::U256
515 | Type::Vector(_) => continue,
516 Type::Reference(t) | Type::MutableReference(t) => t.into_struct_tag(),
517 s @ Type::Struct { .. } => s.into_struct_tag(),
518 };
519 if let Some(tag) = tag_opt {
520 package_types.insert(tag);
521 }
522 }
523 }
524 }
525 }
526 let mut layout_builder = SerdeLayoutBuilder::new(self);
527 for typ in &package_types {
528 layout_builder.build_data_layout(typ).unwrap();
529 }
530 layout_builder.into_registry()
531 }
532
533 pub fn is_system_package(&self) -> bool {
535 let Ok(published_at) = self.published_at else {
537 return false;
538 };
539
540 is_system_package(published_at)
541 }
542
543 pub fn published_root_module(&self) -> Option<&CompiledModule> {
546 self.package.root_compiled_units.iter().find_map(|unit| {
547 if unit.unit.module.self_id().address() != &AccountAddress::ZERO {
548 Some(&unit.unit.module)
549 } else {
550 None
551 }
552 })
553 }
554
555 pub fn verify_unpublished_dependencies(
556 &self,
557 unpublished_deps: &BTreeSet<Symbol>,
558 ) -> IotaResult<()> {
559 if unpublished_deps.is_empty() {
560 return Ok(());
561 }
562
563 let errors = self
564 .package
565 .deps_compiled_units
566 .iter()
567 .filter_map(|(p, m)| {
568 if !unpublished_deps.contains(p) || m.unit.module.address() == &AccountAddress::ZERO
569 {
570 return None;
571 }
572 Some(format!(
573 " - {}::{} in dependency {}",
574 m.unit.module.address(),
575 m.unit.name,
576 p
577 ))
578 })
579 .collect::<Vec<String>>();
580
581 if errors.is_empty() {
582 return Ok(());
583 }
584
585 let mut error_message = vec![];
586 error_message.push(
587 "The following modules in package dependencies set a non-zero self-address:".into(),
588 );
589 error_message.extend(errors);
590 error_message.push(
591 "If these packages really are unpublished, their self-addresses should be set \
592 to \"0x0\" in the [addresses] section of the manifest when publishing. If they \
593 are already published, ensure they specify the address in the `published-at` of \
594 their Move.toml manifest."
595 .into(),
596 );
597
598 Err(IotaError::ModulePublishFailure {
599 error: error_message.join("\n"),
600 })
601 }
602}
603
604impl Default for BuildConfig {
605 fn default() -> Self {
606 let config = MoveBuildConfig {
607 default_flavor: Some(move_compiler::editions::Flavor::Iota),
608 ..MoveBuildConfig::default()
609 };
610 BuildConfig {
611 config,
612 run_bytecode_verifier: true,
613 print_diags_to_stderr: false,
614 chain_id: None,
615 }
616 }
617}
618
619impl GetModule for CompiledPackage {
620 type Error = anyhow::Error;
621 type Item = CompiledModule;
624
625 fn get_module_by_id(&self, id: &ModuleId) -> Result<Option<Self::Item>, Self::Error> {
626 Ok(self.package.all_modules_map().get_module(id).ok().cloned())
627 }
628}
629
630pub const PUBLISHED_AT_MANIFEST_FIELD: &str = "published-at";
631
632pub struct IotaPackageHooks;
633
634impl PackageHooks for IotaPackageHooks {
635 fn custom_package_info_fields(&self) -> Vec<String> {
636 vec![
637 PUBLISHED_AT_MANIFEST_FIELD.to_string(),
638 "version".to_string(),
640 ]
641 }
642
643 fn resolve_on_chain_dependency(
644 &self,
645 _dep_name: move_symbol_pool::Symbol,
646 _info: &OnChainInfo,
647 ) -> anyhow::Result<()> {
648 Ok(())
649 }
650
651 fn custom_resolve_pkg_id(
652 &self,
653 manifest: &SourceManifest,
654 ) -> anyhow::Result<PackageIdentifier> {
655 if (!cfg!(debug_assertions) || cfg!(test))
656 && manifest.package.edition == Some(Edition::DEVELOPMENT)
657 {
658 return Err(Edition::DEVELOPMENT.unknown_edition_error());
659 }
660 Ok(manifest.package.name)
661 }
662
663 fn resolve_version(&self, _: &SourceManifest) -> anyhow::Result<Option<Symbol>> {
664 Ok(None)
665 }
666}
667
668#[derive(Debug, Clone)]
669pub struct PackageDependencies {
670 pub published: BTreeMap<Symbol, ObjectID>,
672 pub unpublished: BTreeSet<Symbol>,
674 pub invalid: BTreeMap<Symbol, String>,
676 pub conflicting: BTreeMap<Symbol, (ObjectID, ObjectID)>,
680}
681
682pub fn gather_published_ids(
689 resolution_graph: &ResolvedGraph,
690 chain_id: Option<String>,
691) -> (Result<ObjectID, PublishedAtError>, PackageDependencies) {
692 let root = resolution_graph.root_package();
693
694 let mut published = BTreeMap::new();
695 let mut unpublished = BTreeSet::new();
696 let mut invalid = BTreeMap::new();
697 let mut conflicting = BTreeMap::new();
698 let mut published_at = Err(PublishedAtError::NotPresent);
699
700 for (name, package) in &resolution_graph.package_table {
701 let property = resolve_published_id(package, chain_id.clone());
702 if name == &root {
703 published_at = property;
705 continue;
706 }
707
708 match property {
709 Ok(id) => {
710 published.insert(*name, id);
711 }
712 Err(PublishedAtError::NotPresent) => {
713 unpublished.insert(*name);
714 }
715 Err(PublishedAtError::Invalid(value)) => {
716 invalid.insert(*name, value);
717 }
718 Err(PublishedAtError::Conflict {
719 id_lock,
720 id_manifest,
721 }) => {
722 conflicting.insert(*name, (id_lock, id_manifest));
723 }
724 };
725 }
726
727 (
728 published_at,
729 PackageDependencies {
730 published,
731 unpublished,
732 invalid,
733 conflicting,
734 },
735 )
736}
737
738pub fn published_at_property(manifest: &SourceManifest) -> Result<ObjectID, PublishedAtError> {
739 let Some(value) = manifest
740 .package
741 .custom_properties
742 .get(&Symbol::from(PUBLISHED_AT_MANIFEST_FIELD))
743 else {
744 return Err(PublishedAtError::NotPresent);
745 };
746
747 ObjectID::from_str(value.as_str()).map_err(|_| PublishedAtError::Invalid(value.to_owned()))
748}
749
750pub fn check_unpublished_dependencies(unpublished: &BTreeSet<Symbol>) -> Result<(), IotaError> {
751 if unpublished.is_empty() {
752 return Ok(());
753 };
754
755 let mut error_messages = unpublished
756 .iter()
757 .map(|name| {
758 format!(
759 "Package dependency \"{name}\" does not specify a published address \
760 (the Move.toml manifest for \"{name}\" does not contain a 'published-at' field, nor is there a 'published-id' in the Move.lock).",
761 )
762 })
763 .collect::<Vec<_>>();
764
765 error_messages.push(
766 "If this is intentional, you may use the --with-unpublished-dependencies flag to \
767 continue publishing these dependencies as part of your package (they won't be \
768 linked against existing packages on-chain)."
769 .into(),
770 );
771
772 Err(IotaError::ModulePublishFailure {
773 error: error_messages.join("\n"),
774 })
775}
776
777pub fn check_invalid_dependencies(invalid: &BTreeMap<Symbol, String>) -> Result<(), IotaError> {
778 if invalid.is_empty() {
779 return Ok(());
780 }
781
782 let error_messages = invalid
783 .iter()
784 .map(|(name, value)| {
785 format!(
786 "Package dependency \"{name}\" does not specify a valid published \
787 address: could not parse value \"{value}\" for 'published-at' field in Move.toml \
788 or 'published-id' in Move.lock file."
789 )
790 })
791 .collect::<Vec<_>>();
792
793 Err(IotaError::ModulePublishFailure {
794 error: error_messages.join("\n"),
795 })
796}