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