1use std::collections::BTreeMap;
5
6#[cfg(feature = "hash")]
7use crate::hash::Hasher;
8use crate::{
9 Digest, ExecutionError, Identifier, ObjectId,
10 version::{Version, VersionError},
11};
12
13#[repr(u8)]
15#[derive(Clone, Copy, Debug, Eq, PartialEq, strum::Display)]
16#[non_exhaustive]
17pub enum UpgradePolicy {
18 Compatible = 0,
24 Additive = 128,
27 DepOnly = 192,
29}
30
31impl UpgradePolicy {
32 pub const COMPATIBLE: u8 = Self::Compatible as u8;
33 pub const ADDITIVE: u8 = Self::Additive as u8;
34 pub const DEP_ONLY: u8 = Self::DepOnly as u8;
35
36 pub fn is_valid_policy(policy: &u8) -> bool {
37 Self::try_from(*policy).is_ok()
38 }
39}
40
41impl TryFrom<u8> for UpgradePolicy {
42 type Error = ();
43 fn try_from(value: u8) -> Result<Self, Self::Error> {
44 match value {
45 x if x == Self::Compatible as u8 => Ok(Self::Compatible),
46 x if x == Self::Additive as u8 => Ok(Self::Additive),
47 x if x == Self::DepOnly as u8 => Ok(Self::DepOnly),
48 _ => Err(()),
49 }
50 }
51}
52
53#[derive(Clone, derive_more::Debug)]
56#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
57pub struct MovePackageData {
58 #[cfg_attr(feature = "serde", serde(with = "serialization::modules"))]
60 #[debug(
61 "{:?}",
62 modules
63 .iter()
64 .map(|m| <base64ct::Base64 as base64ct::Encoding>::encode_string(m))
65 .collect::<Vec<_>>()
66 )]
67 pub modules: Vec<Vec<u8>>,
68 pub dependencies: Vec<ObjectId>,
70 #[cfg_attr(feature = "serde", serde(with = "serialization::digest"))]
72 pub digest: Digest,
73}
74
75impl MovePackageData {
76 #[cfg(feature = "hash")]
77 pub fn new(modules: Vec<Vec<u8>>, dependencies: Vec<ObjectId>) -> Self {
78 let digest = MovePackage::compute_digest_for_modules_and_deps(&modules, &dependencies);
79
80 Self {
81 modules,
82 dependencies,
83 digest,
84 }
85 }
86}
87
88impl crate::TreeDisplay for MovePackageData {
89 fn fmt_tree(&self, w: &mut crate::TreeWriter<'_, '_>) -> std::fmt::Result {
90 w.header("Move Package Data")?;
91 w.base64_leaves("Modules", &self.modules, false)?;
92 w.leaves("Dependencies", &self.dependencies, false)?;
93 w.leaf("Digest", &self.digest, true)
94 }
95}
96
97#[derive(Clone, Debug, Eq, Hash, PartialEq)]
107#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
108#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
109#[cfg_attr(feature = "bcs-schema", derive(iota_bcs_schema::BcsSchema))]
110pub struct UpgradeInfo {
111 pub upgraded_id: ObjectId,
113 pub upgraded_version: Version,
115}
116
117impl crate::TreeDisplay for UpgradeInfo {
118 fn fmt_tree(&self, w: &mut crate::TreeWriter<'_, '_>) -> std::fmt::Result {
119 w.header("Upgrade Info")?;
120 w.leaf("Upgraded ID", &self.upgraded_id, false)?;
121 w.leaf("Upgraded Version", &self.upgraded_version, true)
122 }
123}
124
125#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
137#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
138#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
139#[cfg_attr(feature = "bcs-schema", derive(iota_bcs_schema::BcsSchema))]
140pub struct TypeOrigin {
141 pub module_name: Identifier,
143 pub datatype_name: Identifier,
146 pub package: ObjectId,
148}
149
150impl crate::TreeDisplay for TypeOrigin {
151 fn fmt_tree(&self, w: &mut crate::TreeWriter<'_, '_>) -> std::fmt::Result {
152 w.header("Type Origin")?;
153 w.leaf("Module", &self.module_name, false)?;
154 w.leaf("Struct", &self.datatype_name, false)?;
155 w.leaf("Package", &self.package, true)
156 }
157}
158
159#[derive(Clone, derive_more::Debug, Eq, Hash, PartialEq)]
173#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
174#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
175#[cfg_attr(feature = "bcs-schema", derive(iota_bcs_schema::BcsSchema))]
176pub struct MovePackage {
177 pub id: ObjectId,
179 pub version: Version,
191 #[cfg_attr(
193 feature = "serde",
194 serde(
195 with = "::serde_with::As::<BTreeMap<::serde_with::Same, ::serde_with::IfIsHumanReadable<crate::_serde::Base64Encoded, ::serde_with::Bytes>>>"
196 )
197 )]
198 #[cfg_attr(
199 feature = "proptest",
200 strategy(
201 proptest::collection::btree_map(proptest::arbitrary::any::<Identifier>(), proptest::collection::vec(proptest::arbitrary::any::<u8>(), 0..=1024), 0..=5)
202 )
203 )]
204 #[debug(
205 "{:?}",
206 modules
207 .iter()
208 .map(|(k, v)| (k, <base64ct::Base64 as base64ct::Encoding>::encode_string(v)))
209 .collect::<BTreeMap<_, _>>()
210 )]
211 pub modules: BTreeMap<Identifier, Vec<u8>>,
212 pub type_origin_table: Vec<TypeOrigin>,
216 #[cfg_attr(
219 feature = "proptest",
220 strategy(
221 proptest::collection::btree_map(proptest::arbitrary::any::<ObjectId>(), proptest::arbitrary::any::<UpgradeInfo>(), 0..=5)
222 )
223 )]
224 pub linkage_table: BTreeMap<ObjectId, UpgradeInfo>,
225}
226
227impl MovePackage {
228 pub fn new(
234 id: ObjectId,
235 version: Version,
236 modules: BTreeMap<Identifier, Vec<u8>>,
237 max_move_package_size: u64,
238 type_origin_table: Vec<TypeOrigin>,
239 linkage_table: BTreeMap<ObjectId, UpgradeInfo>,
240 ) -> Result<Self, ExecutionError> {
241 let pkg = Self {
242 id,
243 version,
244 modules,
245 type_origin_table,
246 linkage_table,
247 };
248 let object_size = pkg.size() as u64;
249 if object_size > max_move_package_size {
250 return Err(ExecutionError::PackageTooBig {
251 object_size,
252 max_object_size: max_move_package_size,
253 });
254 }
255 Ok(pkg)
256 }
257
258 #[cfg(feature = "hash")]
260 pub fn digest(&self) -> Digest {
261 Self::compute_digest_for_modules_and_deps(
262 self.modules.values(),
263 self.linkage_table
264 .values()
265 .map(|UpgradeInfo { upgraded_id, .. }| upgraded_id),
266 )
267 }
268
269 #[cfg(feature = "hash")]
273 pub fn compute_digest_for_modules_and_deps<'a>(
274 modules: impl IntoIterator<Item = &'a Vec<u8>>,
275 object_ids: impl IntoIterator<Item = &'a ObjectId>,
276 ) -> Digest {
277 let mut components = object_ids
278 .into_iter()
279 .map(|o| o.into_bytes())
280 .chain(
281 modules
282 .into_iter()
283 .map(|module| Hasher::digest(module).into_inner()),
284 )
285 .collect::<Vec<_>>();
286
287 components.sort();
290
291 let mut digest = Hasher::new();
292 for c in components {
293 digest.update(c);
294 }
295 digest.finalize()
296 }
297
298 pub fn get_module(&self, name: &Identifier) -> Option<&Vec<u8>> {
300 self.modules.get(name)
301 }
302
303 pub fn size(&self) -> usize {
305 let module_map_size = self
306 .modules
307 .iter()
308 .map(|(name, module)| name.len() + module.len())
309 .sum::<usize>();
310 let type_origin_table_size = self
311 .type_origin_table
312 .iter()
313 .map(
314 |TypeOrigin {
315 module_name,
316 datatype_name,
317 ..
318 }| module_name.len() + datatype_name.len() + ObjectId::LENGTH,
319 )
320 .sum::<usize>();
321
322 let linkage_table_size = self.linkage_table.len()
323 * (ObjectId::LENGTH + (ObjectId::LENGTH + std::mem::size_of::<Version>()));
324
325 std::mem::size_of::<Version>()
326 + module_map_size
327 + type_origin_table_size
328 + linkage_table_size
329 }
330
331 pub fn id(&self) -> ObjectId {
333 self.id
334 }
335
336 pub fn version(&self) -> Version {
337 self.version
338 }
339
340 pub fn increment_version(&mut self) -> Result<(), VersionError> {
341 self.version.increment()
342 }
343
344 pub fn decrement_version(&mut self) -> Result<(), VersionError> {
345 self.version.decrement()
346 }
347
348 pub fn serialized_module_map(&self) -> &BTreeMap<Identifier, Vec<u8>> {
349 &self.modules
350 }
351
352 pub fn type_origin_table(&self) -> &Vec<TypeOrigin> {
353 &self.type_origin_table
354 }
355
356 pub fn type_origin_map(&self) -> BTreeMap<(Identifier, Identifier), ObjectId> {
357 self.type_origin_table
358 .iter()
359 .map(
360 |TypeOrigin {
361 module_name,
362 datatype_name,
363 package,
364 }| { ((module_name.clone(), datatype_name.clone()), *package) },
365 )
366 .collect()
367 }
368
369 pub fn linkage_table(&self) -> &BTreeMap<ObjectId, UpgradeInfo> {
370 &self.linkage_table
371 }
372}
373
374impl crate::TreeDisplay for MovePackage {
375 fn fmt_tree(&self, w: &mut crate::TreeWriter<'_, '_>) -> std::fmt::Result {
376 w.header("Move Package")?;
377 w.leaf("ID", &self.id, false)?;
378 w.leaf("Version", &self.version, false)?;
379 w.leaves("Modules", self.modules.keys(), true)
380 }
381}
382
383crate::impl_tree_display!(MovePackageData, UpgradeInfo, TypeOrigin, MovePackage);
384
385#[cfg(feature = "serde")]
386mod serialization {
387 use base64ct::Encoding;
388 use serde::{Deserialize, Deserializer, Serialize, Serializer};
389
390 use super::*;
391
392 impl MovePackageData {
393 pub fn to_base64(&self) -> String {
394 base64ct::Base64::encode_string(&bcs::to_bytes(self).expect("bcs encoding failed"))
395 }
396
397 pub fn from_base64(base64: &str) -> Result<Self, bcs::Error> {
398 use serde::de::Error;
399 bcs::from_bytes(&base64ct::Base64::decode_vec(base64).map_err(bcs::Error::custom)?)
400 }
401 }
402
403 pub mod modules {
404 use super::*;
405
406 pub fn serialize<S: Serializer>(
407 value: &[Vec<u8>],
408 serializer: S,
409 ) -> Result<S::Ok, S::Error> {
410 value
411 .iter()
412 .map(|v| base64ct::Base64::encode_string(v))
413 .collect::<Vec<_>>()
414 .serialize(serializer)
415 }
416
417 pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<Vec<u8>>, D::Error>
418 where
419 D: Deserializer<'de>,
420 {
421 let bcs = Vec::<String>::deserialize(deserializer)?;
422 bcs.into_iter()
423 .map(|s| base64ct::Base64::decode_vec(&s).map_err(serde::de::Error::custom))
424 .collect()
425 }
426 }
427
428 pub mod digest {
429 use super::*;
430
431 pub fn serialize<S: Serializer>(value: &Digest, serializer: S) -> Result<S::Ok, S::Error> {
432 value.as_bytes().serialize(serializer)
433 }
434
435 pub fn deserialize<'de, D>(deserializer: D) -> Result<Digest, D::Error>
436 where
437 D: Deserializer<'de>,
438 {
439 let bytes = Vec::<u8>::deserialize(deserializer)?;
440 Digest::from_bytes(bytes).map_err(|e| serde::de::Error::custom(format!("{e}")))
441 }
442 }
443}
444
445#[cfg(test)]
446mod tests {
447 use super::*;
448
449 #[cfg(feature = "serde")]
450 const PACKAGE: &str = r#"{"modules":["oRzrCwYAAAAKAQAIAggUAxw+BFoGBWBBB6EBwQEI4gJACqIDGgy8A5cBDdMEBgAKAQ0BEwEUAAIMAAABCAAAAAgAAQQEAAMDAgAACAABAAAJAgMAABACAwAAEgQDAAAMBQYAAAYHAQAAEQgBAAAFCQoAAQsACwACDg8BAQwCEw8BAQgDDwwNAAoOCgYJBgEHCAQAAQYIAAEDAQYIAQQHCAEDAwcIBAEIAAQDAwUHCAQDCAAFBwgEAgMHCAQBCAIBCAMBBggEAQUBCAECCQAFBkNvbmZpZwVGb3JnZQVTd29yZAlUeENvbnRleHQDVUlEDWNyZWF0ZV9jb25maWcMY3JlYXRlX3N3b3JkAmlkBGluaXQFbWFnaWMJbXlfbW9kdWxlA25ldwluZXdfc3dvcmQGb2JqZWN0D3B1YmxpY190cmFuc2ZlcgZzZW5kZXIIc3RyZW5ndGgOc3dvcmRfdHJhbnNmZXIOc3dvcmRzX2NyZWF0ZWQIdHJhbnNmZXIKdHhfY29udGV4dAV2YWx1ZQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAgMHCAMJAxADAQICBwgDEgMCAgIHCAMVAwAAAAABCQoAEQgGAAAAAAAAAAASAQsALhELOAACAQEAAAEECwAQABQCAgEAAAEECwAQARQCAwEAAAEECwAQAhQCBAEAAAEOCgAQAhQGAQAAAAAAAAAWCwAPAhULAxEICwELAhIAAgUBAAABCAsDEQgLAAsBEgALAjgBAgYBAAABBAsACwE4AgIHAQAAAQULAREICwASAgIAAQACAQEA"],"dependencies":["0x0000000000000000000000000000000000000000000000000000000000000002","0x0000000000000000000000000000000000000000000000000000000000000001"],"digest":[246,127,102,77,186,19,68,12,161,181,56,248,210,0,91,211,245,251,165,152,0,197,250,135,171,37,177,240,133,76,122,124]}"#;
451
452 #[cfg(feature = "serde")]
453 #[test]
454 fn test_serialization() {
455 let package: MovePackageData = serde_json::from_str(PACKAGE).unwrap();
456 let new_json = serde_json::to_string(&package).unwrap();
457 assert_eq!(new_json, PACKAGE);
458 }
459
460 #[cfg(all(feature = "serde", feature = "hash"))]
461 #[test]
462 fn test_digest() {
463 let json_package: MovePackageData = serde_json::from_str(PACKAGE).unwrap();
464 let package = MovePackageData::new(json_package.modules, json_package.dependencies);
465 assert_eq!(json_package.digest, package.digest);
466 }
467
468 fn module(name: &str, bytes: &[u8]) -> (Identifier, Vec<u8>) {
469 (Identifier::new(name).unwrap(), bytes.to_vec())
470 }
471
472 fn upgrade_info(upgraded_id: ObjectId, version: u64) -> UpgradeInfo {
473 UpgradeInfo {
474 upgraded_id,
475 upgraded_version: Version::from_u64(version),
476 }
477 }
478
479 fn package(
480 modules: impl IntoIterator<Item = (Identifier, Vec<u8>)>,
481 type_origin_table: Vec<TypeOrigin>,
482 linkage_table: impl IntoIterator<Item = (ObjectId, UpgradeInfo)>,
483 ) -> MovePackage {
484 MovePackage {
485 id: ObjectId::ZERO,
486 version: Version::OBJECT_START,
487 modules: modules.into_iter().collect(),
488 type_origin_table,
489 linkage_table: linkage_table.into_iter().collect(),
490 }
491 }
492
493 #[test]
494 fn size_all_components() {
495 let mod_a_name = "m1";
496 let mod_a_bytes: &[u8] = &[0; 10];
497 let mod_b_name = "m2";
498 let mod_b_bytes: &[u8] = &[0; 20];
499 let type_module_name = "t_module";
500 let type_datatype_name = "T";
501
502 let pkg = package(
503 [
504 module(mod_a_name, mod_a_bytes),
505 module(mod_b_name, mod_b_bytes),
506 ],
507 vec![TypeOrigin {
508 module_name: Identifier::new(type_module_name).unwrap(),
509 datatype_name: Identifier::new(type_datatype_name).unwrap(),
510 package: ObjectId::ZERO,
511 }],
512 [(
513 ObjectId::from_u16(1),
514 upgrade_info(ObjectId::from_u16(2), 1),
515 )],
516 );
517 let modules_size =
518 (mod_a_name.len() + mod_a_bytes.len()) + (mod_b_name.len() + mod_b_bytes.len());
519 let type_origin_size = type_module_name.len() + type_datatype_name.len() + ObjectId::LENGTH;
520 let linkage_size = ObjectId::LENGTH + ObjectId::LENGTH + std::mem::size_of::<Version>();
521 let expected =
522 std::mem::size_of::<Version>() + modules_size + type_origin_size + linkage_size;
523 assert_eq!(pkg.size(), expected);
524 }
525
526 #[test]
527 fn new_exceeding_max_size_fails() {
528 let modules = [module("m", &[0; 100])];
529 let max = 10_u64;
530 let err = MovePackage::new(
531 ObjectId::ZERO,
532 Version::OBJECT_START,
533 modules.into_iter().collect(),
534 max,
535 vec![],
536 BTreeMap::new(),
537 )
538 .unwrap_err();
539 match err {
540 ExecutionError::PackageTooBig {
541 object_size,
542 max_object_size,
543 } => {
544 assert!(object_size > max);
545 assert_eq!(max_object_size, max);
546 }
547 other => panic!("expected PackageTooBig, got {other:?}"),
548 }
549 }
550
551 #[cfg(feature = "hash")]
552 mod digest {
553 use super::*;
554
555 #[test]
556 fn digest_matches_pinned_hex() {
557 const EXPECTED_HEX: &str =
558 "087322ae613945320a83dce5d45626eb3a112a0d98ed6885788635194f2d566f";
559
560 let pkg = package(
561 [module("a", b"first"), module("b", b"second")],
562 vec![],
563 [(
564 ObjectId::from_u16(0x01),
565 upgrade_info(ObjectId::from_u16(0xdead), 1),
566 )],
567 );
568
569 let expected = Digest::new(hex::decode(EXPECTED_HEX).unwrap().try_into().unwrap());
570 assert_eq!(pkg.digest(), expected);
571 }
572
573 #[test]
574 fn digest_ignores_module_names() {
575 let bytes = b"module_bytes".to_vec();
578 let pkg1 = package(
579 [(Identifier::new("foo").unwrap(), bytes.clone())],
580 vec![],
581 [],
582 );
583 let pkg2 = package([(Identifier::new("bar").unwrap(), bytes)], vec![], []);
584 assert_eq!(pkg1.digest(), pkg2.digest());
585 }
586
587 #[test]
588 fn digest_changes_with_module_bytes() {
589 let pkg1 = package([module("m", b"version_one")], vec![], []);
590 let pkg2 = package([module("m", b"version_two")], vec![], []);
591 assert_ne!(pkg1.digest(), pkg2.digest());
592 }
593
594 #[test]
595 fn digest_changes_with_dependencies() {
596 let modules = [module("m", b"same_bytes")];
597 let pkg1 = package(modules.clone(), vec![], []);
598 let pkg2 = package(
599 modules,
600 vec![],
601 [(
602 ObjectId::from_u16(0x01),
603 upgrade_info(ObjectId::from_u16(0x99), 1),
604 )],
605 );
606 assert_ne!(pkg1.digest(), pkg2.digest());
607 }
608
609 #[test]
610 fn digest_uses_only_upgraded_id_from_linkage() {
611 let modules = [module("m", b"bytes")];
612 let upgraded = ObjectId::from_u16(0xaa);
613
614 let pkg1 = package(
617 modules.clone(),
618 vec![],
619 [(ObjectId::from_u16(0x01), upgrade_info(upgraded, 1))],
620 );
621 let pkg2 = package(
622 modules.clone(),
623 vec![],
624 [(ObjectId::from_u16(0x02), upgrade_info(upgraded, 999))],
625 );
626 assert_eq!(pkg1.digest(), pkg2.digest());
627
628 let pkg3 = package(
630 modules,
631 vec![],
632 [(
633 ObjectId::from_u16(0x01),
634 upgrade_info(ObjectId::from_u16(0xbb), 1),
635 )],
636 );
637 assert_ne!(pkg1.digest(), pkg3.digest());
638 }
639 }
640}