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