iota_framework/
lib.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::{fmt::Formatter, sync::LazyLock};
6
7use iota_types::{
8    BRIDGE_PACKAGE_ID, IOTA_FRAMEWORK_PACKAGE_ID, IOTA_SYSTEM_PACKAGE_ID, MOVE_STDLIB_PACKAGE_ID,
9    STARDUST_PACKAGE_ID,
10    base_types::{ObjectID, ObjectRef},
11    digests::TransactionDigest,
12    move_package::MovePackage,
13    object::{OBJECT_START_VERSION, Object},
14    storage::ObjectStore,
15};
16use move_binary_format::{
17    CompiledModule, binary_config::BinaryConfig, compatibility::Compatibility,
18};
19use move_core_types::gas_algebra::InternalGas;
20use serde::{Deserialize, Serialize};
21use tracing::error;
22
23/// Encapsulates a system package in the framework.
24pub struct SystemPackageMetadata {
25    /// The name of the package (e.g. "MoveStdLib").
26    pub name: String,
27    /// The path within the repo to the source (e.g.
28    /// "crates/iota-framework/packages/move-stdlib").
29    pub path: String,
30    /// The compiled bytecode and object ID of the package.
31    pub compiled: SystemPackage,
32}
33
34/// Encapsulates the chain-relevant data about a framework package (such as the
35/// id or compiled bytecode).
36#[derive(Clone, Serialize, PartialEq, Eq, Deserialize)]
37pub struct SystemPackage {
38    pub id: ObjectID,
39    pub bytes: Vec<Vec<u8>>,
40    pub dependencies: Vec<ObjectID>,
41}
42
43impl SystemPackageMetadata {
44    pub fn new(
45        name: impl ToString,
46        path: impl ToString,
47        id: ObjectID,
48        raw_bytes: &'static [u8],
49        dependencies: &[ObjectID],
50    ) -> Self {
51        SystemPackageMetadata {
52            name: name.to_string(),
53            path: path.to_string(),
54            compiled: SystemPackage::new(id, raw_bytes, dependencies),
55        }
56    }
57}
58
59impl SystemPackage {
60    pub fn new(id: ObjectID, raw_bytes: &'static [u8], dependencies: &[ObjectID]) -> Self {
61        let bytes: Vec<Vec<u8>> = bcs::from_bytes(raw_bytes).unwrap();
62        Self {
63            id,
64            bytes,
65            dependencies: dependencies.to_vec(),
66        }
67    }
68
69    pub fn modules(&self) -> Vec<CompiledModule> {
70        self.bytes
71            .iter()
72            .map(|b| CompiledModule::deserialize_with_defaults(b).unwrap())
73            .collect()
74    }
75
76    pub fn genesis_move_package(&self) -> MovePackage {
77        MovePackage::new_system(
78            OBJECT_START_VERSION,
79            &self.modules(),
80            self.dependencies.iter().copied(),
81        )
82    }
83
84    pub fn genesis_object(&self) -> Object {
85        Object::new_system_package(
86            &self.modules(),
87            OBJECT_START_VERSION,
88            self.dependencies.to_vec(),
89            TransactionDigest::genesis_marker(),
90        )
91    }
92}
93
94impl std::fmt::Debug for SystemPackage {
95    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
96        writeln!(f, "Object ID: {:?}", self.id)?;
97        writeln!(f, "Size: {}", self.bytes.len())?;
98        writeln!(f, "Dependencies: {:?}", self.dependencies)?;
99        Ok(())
100    }
101}
102
103macro_rules! define_system_package_metadata {
104    ([$(($id:expr, $name: expr, $path:expr, $deps:expr)),* $(,)?]) => {{
105        static PACKAGES: LazyLock<Vec<SystemPackageMetadata>> = LazyLock::new(|| {
106            vec![
107                $(SystemPackageMetadata::new(
108                    $name,
109                    concat!("crates/iota-framework/packages/", $path),
110                    $id,
111                    include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/packages_compiled", "/", $path)),
112                    &$deps,
113                )),*
114            ]
115        });
116        &PACKAGES
117    }}
118}
119
120pub struct BuiltInFramework;
121impl BuiltInFramework {
122    pub fn iter_system_package_metadata() -> impl Iterator<Item = &'static SystemPackageMetadata> {
123        // All system packages in the current build should be registered here, and this
124        // is the only place we need to worry about if any of them changes.
125        // TODO: Is it possible to derive dependencies from the bytecode instead of
126        // manually specifying them?
127        define_system_package_metadata!([
128            (MOVE_STDLIB_PACKAGE_ID, "MoveStdlib", "move-stdlib", []),
129            (
130                IOTA_FRAMEWORK_PACKAGE_ID,
131                "Iota",
132                "iota-framework",
133                [MOVE_STDLIB_PACKAGE_ID]
134            ),
135            (
136                IOTA_SYSTEM_PACKAGE_ID,
137                "IotaSystem",
138                "iota-system",
139                [MOVE_STDLIB_PACKAGE_ID, IOTA_FRAMEWORK_PACKAGE_ID]
140            ),
141            (
142                BRIDGE_PACKAGE_ID,
143                "Bridge",
144                "bridge",
145                [
146                    MOVE_STDLIB_PACKAGE_ID,
147                    IOTA_FRAMEWORK_PACKAGE_ID,
148                    IOTA_SYSTEM_PACKAGE_ID
149                ]
150            ),
151            (
152                STARDUST_PACKAGE_ID,
153                "Stardust",
154                "stardust",
155                [MOVE_STDLIB_PACKAGE_ID, IOTA_FRAMEWORK_PACKAGE_ID]
156            ),
157        ])
158        .iter()
159    }
160
161    pub fn all_package_ids() -> Vec<ObjectID> {
162        Self::iter_system_packages().map(|p| p.id).collect()
163    }
164
165    pub fn get_package_by_id(id: &ObjectID) -> &'static SystemPackage {
166        Self::iter_system_packages().find(|s| &s.id == id).unwrap()
167    }
168
169    pub fn iter_system_packages() -> impl Iterator<Item = &'static SystemPackage> {
170        BuiltInFramework::iter_system_package_metadata().map(|m| &m.compiled)
171    }
172
173    pub fn genesis_move_packages() -> impl Iterator<Item = MovePackage> {
174        Self::iter_system_packages().map(|package| package.genesis_move_package())
175    }
176
177    pub fn genesis_objects() -> impl Iterator<Item = Object> {
178        Self::iter_system_packages().map(|package| package.genesis_object())
179    }
180}
181
182pub const DEFAULT_FRAMEWORK_PATH: &str = env!("CARGO_MANIFEST_DIR");
183
184pub fn legacy_test_cost() -> InternalGas {
185    InternalGas::new(0)
186}
187
188/// Check whether the framework defined by `modules` is compatible with the
189/// framework that is already on-chain (i.e. stored in `object_store`) at `id`.
190///
191/// - Returns `None` if the current package at `id` cannot be loaded, or the
192///   compatibility check fails (This is grounds not to upgrade).
193/// - Panics if the object at `id` can be loaded but is not a package -- this is
194///   an invariant violation.
195/// - Returns the digest of the current framework (and version) if it is
196///   equivalent to the new framework (indicates support for a protocol upgrade
197///   without a framework upgrade).
198/// - Returns the digest of the new framework (and version) if it is compatible
199///   (indicates support for a protocol upgrade with a framework upgrade).
200pub async fn compare_system_package<S: ObjectStore>(
201    object_store: &S,
202    id: &ObjectID,
203    modules: &[CompiledModule],
204    dependencies: Vec<ObjectID>,
205    binary_config: &BinaryConfig,
206) -> Option<ObjectRef> {
207    let cur_object = match object_store.get_object(id) {
208        Ok(Some(cur_object)) => cur_object,
209
210        Ok(None) => {
211            // creating a new framework package--nothing to check
212            return Some(
213                Object::new_system_package(
214                    modules,
215                    // note: execution_engine assumes any system package with version
216                    // OBJECT_START_VERSION is freshly created rather than
217                    // upgraded
218                    OBJECT_START_VERSION,
219                    dependencies,
220                    // Genesis is fine here, we only use it to calculate an object ref that we can
221                    // use for all validators to commit to the same bytes in
222                    // the update
223                    TransactionDigest::genesis_marker(),
224                )
225                .compute_object_reference(),
226            );
227        }
228
229        Err(e) => {
230            error!("Error loading framework object at {id}: {e:?}");
231            return None;
232        }
233    };
234
235    let cur_ref = cur_object.compute_object_reference();
236    let cur_pkg = cur_object
237        .data
238        .try_as_package()
239        .expect("Framework not package");
240
241    let mut new_object = Object::new_system_package(
242        modules,
243        // Start at the same version as the current package, and increment if compatibility is
244        // successful
245        cur_object.version(),
246        dependencies,
247        cur_object.previous_transaction,
248    );
249
250    if cur_ref == new_object.compute_object_reference() {
251        return Some(cur_ref);
252    }
253
254    let compatibility = Compatibility::framework_upgrade_check();
255
256    let new_pkg = new_object
257        .data
258        .try_as_package_mut()
259        .expect("Created as package");
260
261    let cur_normalized = match cur_pkg.normalize(binary_config) {
262        Ok(v) => v,
263        Err(e) => {
264            error!("Could not normalize existing package: {e:?}");
265            return None;
266        }
267    };
268    let mut new_normalized = new_pkg.normalize(binary_config).ok()?;
269
270    for (name, cur_module) in cur_normalized {
271        let new_module = new_normalized.remove(&name)?;
272
273        if let Err(e) = compatibility.check(&cur_module, &new_module) {
274            error!("Compatibility check failed, for new version of {id}::{name}: {e:?}");
275            return None;
276        }
277    }
278
279    new_pkg.increment_version();
280    Some(new_object.compute_object_reference())
281}