iota_sdk_types/
move_package.rs

1// Copyright 2025 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::{Digest, ObjectId};
5
6/// Rust representation of upgrade policy constants in `iota::package`.
7#[repr(u8)]
8#[derive(strum::Display, Debug, Clone, Copy, PartialEq, Eq)]
9#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
10pub enum UpgradePolicy {
11    /// The least restrictive policy. Permits changes to all function
12    /// implementations, the removal of ability constraints on generic type
13    /// parameters in function signatures, and modifications to private,
14    /// public(friend), and entry function signatures. However, public function
15    /// signatures and existing types cannot be changed.
16    Compatible = 0,
17    /// Allows adding new functionalities (e.g., new public functions or
18    /// structs) but restricts changes to existing functionalities.
19    Additive = 128,
20    /// Limits modifications to the package’s dependencies only.
21    DepOnly = 192,
22}
23
24impl UpgradePolicy {
25    pub const COMPATIBLE: u8 = Self::Compatible as u8;
26    pub const ADDITIVE: u8 = Self::Additive as u8;
27    pub const DEP_ONLY: u8 = Self::DepOnly as u8;
28
29    pub fn is_valid_policy(policy: &u8) -> bool {
30        Self::try_from(*policy).is_ok()
31    }
32}
33
34impl TryFrom<u8> for UpgradePolicy {
35    type Error = ();
36    fn try_from(value: u8) -> Result<Self, Self::Error> {
37        match value {
38            x if x == Self::Compatible as u8 => Ok(Self::Compatible),
39            x if x == Self::Additive as u8 => Ok(Self::Additive),
40            x if x == Self::DepOnly as u8 => Ok(Self::DepOnly),
41            _ => Err(()),
42        }
43    }
44}
45
46/// Type corresponding to the output of `iota move build
47/// --dump-bytecode-as-base64`
48#[derive(Clone, Debug)]
49#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
50pub struct MovePackageData {
51    /// The package modules as a series of bytes
52    #[cfg_attr(feature = "serde", serde(with = "serialization::modules"))]
53    pub modules: Vec<Vec<u8>>,
54    /// The package dependencies, specified by their object IDs.
55    pub dependencies: Vec<ObjectId>,
56    /// The package digest.
57    #[cfg_attr(feature = "serde", serde(with = "serialization::digest"))]
58    pub digest: Digest,
59}
60
61impl MovePackageData {
62    #[cfg(feature = "hash")]
63    pub fn new(modules: Vec<Vec<u8>>, dependencies: Vec<ObjectId>) -> Self {
64        use crate::hash::Hasher;
65        let mut components = dependencies
66            .iter()
67            .map(|o| o.into_inner())
68            .chain(modules.iter().map(|module| {
69                let mut hasher = Hasher::new();
70                hasher.update(module);
71                hasher.finalize().into_inner()
72            }))
73            .collect::<Vec<_>>();
74
75        // Sort so the order of the modules and the order of the dependencies
76        // does not matter.
77        components.sort();
78
79        let mut hasher = Hasher::new();
80        for c in components {
81            hasher.update(c);
82        }
83
84        Self {
85            modules,
86            dependencies,
87            digest: Digest::from(hasher.finalize().into_inner()),
88        }
89    }
90}
91
92#[cfg(feature = "serde")]
93mod serialization {
94    use base64ct::Encoding;
95    use serde::{Deserialize, Deserializer, Serialize, Serializer};
96
97    use super::*;
98
99    impl MovePackageData {
100        pub fn to_base64(&self) -> String {
101            base64ct::Base64::encode_string(&bcs::to_bytes(self).expect("bcs encoding failed"))
102        }
103
104        pub fn from_base64(base64: &str) -> Result<Self, bcs::Error> {
105            use serde::de::Error;
106            bcs::from_bytes(&base64ct::Base64::decode_vec(base64).map_err(bcs::Error::custom)?)
107        }
108    }
109
110    pub mod modules {
111        use super::*;
112
113        pub fn serialize<S: Serializer>(
114            value: &[Vec<u8>],
115            serializer: S,
116        ) -> Result<S::Ok, S::Error> {
117            value
118                .iter()
119                .map(|v| base64ct::Base64::encode_string(v))
120                .collect::<Vec<_>>()
121                .serialize(serializer)
122        }
123
124        pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<Vec<u8>>, D::Error>
125        where
126            D: Deserializer<'de>,
127        {
128            let bcs = Vec::<String>::deserialize(deserializer)?;
129            bcs.into_iter()
130                .map(|s| base64ct::Base64::decode_vec(&s).map_err(serde::de::Error::custom))
131                .collect()
132        }
133    }
134
135    pub mod digest {
136        use super::*;
137
138        pub fn serialize<S: Serializer>(value: &Digest, serializer: S) -> Result<S::Ok, S::Error> {
139            value.as_bytes().serialize(serializer)
140        }
141
142        pub fn deserialize<'de, D>(deserializer: D) -> Result<Digest, D::Error>
143        where
144            D: Deserializer<'de>,
145        {
146            let bytes = Vec::<u8>::deserialize(deserializer)?;
147            Digest::from_bytes(bytes).map_err(|e| serde::de::Error::custom(format!("{e}")))
148        }
149    }
150}
151
152#[cfg(test)]
153mod tests {
154    use super::*;
155
156    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]}"#;
157
158    #[test]
159    fn test_serialization() {
160        let package: MovePackageData = serde_json::from_str(PACKAGE).unwrap();
161        let new_json = serde_json::to_string(&package).unwrap();
162        assert_eq!(new_json, PACKAGE);
163    }
164
165    #[test]
166    fn test_digest() {
167        let json_package: MovePackageData = serde_json::from_str(PACKAGE).unwrap();
168        let package = MovePackageData::new(json_package.modules, json_package.dependencies);
169        assert_eq!(json_package.digest, package.digest);
170    }
171}