Skip to content
Draft
4 changes: 2 additions & 2 deletions bindings/wasm/iota_interaction_ts/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion bindings/wasm/iota_interaction_ts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"author": "IOTA Foundation <[email protected]>",
"description": "WASM bindings importing types from the IOTA Client typescript SDK to be used in Rust",
"homepage": "https://www.iota.org",
"version": "0.8.0",
"version": "0.0.0-experimental-202511201500",
"license": "Apache-2.0",
"repository": {
"type": "git",
Expand Down
11 changes: 9 additions & 2 deletions product_common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ serde.workspace = true
serde_json.workspace = true
strum.workspace = true
thiserror.workspace = true
toml = { workspace = true, optional = true }
toml = { workspace = true } # , optional = true --- TODO see comment for `move-history-manager` feature
url = { version = "2", default-features = false, features = ["serde"], optional = true }

[dev-dependencies]
Expand Down Expand Up @@ -71,7 +71,14 @@ default-http-client = ["dep:reqwest", "http-client"]
gas-station = ["http-client", "transaction"]
http-client = ["dep:url"]
# Management functions to read Move.lock files and create/update Move.history.json files
move-history-manager = ["dep:toml"]
# TODO: Since the Full Package History version of product core (MoveHistoryManager and `Move.history.toml` files are used
# for the PackageRegistry), product core only needs the `toml` dependency if feature `move-history-manager` is
# activated.
# Despite this, to prevent existing projects from having build issues, we need to maintain the static `toml`
# depency. As soon as all products have migrated to use MoveHistoryManager and the legacy PackageRegistry functions
# marked as deprecated have been removed, we can re-add the `optional = true` flag to the `toml` dependency
# and add it to the `move-history-manager` feature flag depencency list.
move-history-manager = [] # ["dep:toml"]
send-sync = ["secret-storage/send-sync-storage"]
test-utils = [
"dep:bcs",
Expand Down
2 changes: 1 addition & 1 deletion product_common/src/move_history_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ impl PackageRegistry {
}

let env = Env::new_with_alias(chain_id, alias.clone());
registry.insert_env(env, metadata);
registry.insert_env_history(env, metadata);

Ok(registry)
})
Expand Down
225 changes: 214 additions & 11 deletions product_common/src/package_registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::collections::HashMap;

use anyhow::Context;
use iota_interaction::types::base_types::ObjectID;
use serde::{Deserialize, Serialize};
use serde::{Deserialize, Deserializer, Serialize};
use serde_json::Value;

pub const MAINNET_CHAIN_ID: &str = "6364aad5";
Expand Down Expand Up @@ -35,6 +35,39 @@ impl Env {
}
}

/// A published package's metadata for a certain environment.
#[deprecated = "Use Vec<ObjectID> with PackageRegistry::insert_env_history() instead."]
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct Metadata {
pub original_published_id: ObjectID,
pub latest_published_id: ObjectID,
#[serde(deserialize_with = "deserialize_u64_from_str")]
pub published_version: u64,
}

#[allow(deprecated)]
impl Metadata {
/// Create a new [Metadata] assuming a newly published package.
pub fn from_package_id(package: ObjectID) -> Self {
Self {
original_published_id: package,
latest_published_id: package,
published_version: 1,
}
}
}

#[deprecated]
fn deserialize_u64_from_str<'de, D>(deserializer: D) -> Result<u64, D::Error>
where
D: Deserializer<'de>,
{
use serde::de::Error;

String::deserialize(deserializer)?.parse().map_err(D::Error::custom)
}

/// A registry that tracks package versions across different blockchain environments.
///
/// The `PackageRegistry` stores:
Expand Down Expand Up @@ -112,13 +145,33 @@ impl PackageRegistry {
}

/// Adds or replaces this package's metadata for a given environment.
pub fn insert_env(&mut self, env: Env, metadata: Vec<ObjectID>) {
#[deprecated = "Use PackageRegistry::insert_env_history() instead."]
#[allow(deprecated)]
pub fn insert_env(&mut self, env: Env, metadata: Metadata) {
let Env { chain_id, alias } = env;

if let Some(alias) = alias {
self.aliases.insert(alias, chain_id.clone());
}
self.envs.insert(chain_id, metadata);

#[allow(deprecated)]
let history = if metadata.original_published_id == metadata.latest_published_id {
vec![metadata.original_published_id]
} else {
vec![metadata.original_published_id, metadata.latest_published_id]
};

self.envs.insert(chain_id, history);
}

/// Adds or replaces this package's id history for a given environment.
pub fn insert_env_history(&mut self, env: Env, history: Vec<ObjectID>) {
let Env { chain_id, alias } = env;

if let Some(alias) = alias {
self.aliases.insert(alias, chain_id.clone());
}
self.envs.insert(chain_id, history);
}

/// Updates or adds an alias for a given chain ID.
Expand Down Expand Up @@ -186,6 +239,40 @@ impl PackageRegistry {
Ok(registry)
})
}

#[cfg(not(feature = "move-history-manager"))]
/// Creates a [PackageRegistry] from a Move.lock file.
#[deprecated = "Use PackageRegistry::from_package_history_json_str() instead."]
#[allow(deprecated)]
pub fn from_move_lock_content(move_lock: &str) -> anyhow::Result<Self> {
let mut move_lock: toml::Table = move_lock.parse()?;

move_lock
.remove("env")
.context("invalid Move.lock file: missing `env` table")?
.as_table_mut()
.map(std::mem::take)
.context("invalid Move.lock file: `env` is not a table")?
.into_iter()
.try_fold(Self::default(), |mut registry, (alias, table)| {
let toml::Value::Table(mut table) = table else {
anyhow::bail!("invalid Move.lock file: invalid `env` table");
};
let chain_id: String = table
.remove("chain-id")
.context(format!("invalid Move.lock file: missing `chain-id` for env {alias}"))?
.try_into()
.context("invalid Move.lock file: invalid `chain-id`")?;

let env = Env::new_with_alias(chain_id, alias.clone());
let metadata = table
.try_into()
.context(format!("invalid Move.lock file: invalid env metadata for {alias}"))?;
registry.insert_env(env, metadata);

Ok(registry)
})
}
}

#[cfg(test)]
Expand Down Expand Up @@ -274,20 +361,20 @@ mod tests {
fn test_serialize_package_registry_to_json() {
let mut registry = PackageRegistry::default();
// Add well-known networks.
registry.insert_env(
registry.insert_env_history(
Env::new_with_alias("6364aad5", "mainnet"),
vec![object_id!(
"0x84cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed2054ad931de08"
)],
);
registry.insert_env(
registry.insert_env_history(
Env::new_with_alias("2304aa97", "testnet"),
vec![
object_id!("0x222741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac864ab4e4cc555"),
object_id!("0x3403da7ec4cd2ff9bdf6f34c0b8df5a2bd62c798089feb0d2ebf1c2e953296dc"),
],
);
registry.insert_env(
registry.insert_env_history(
Env::new_with_alias("e678123a", "devnet"),
vec![
object_id!("0xe6fa03d273131066036f1d2d4c3d919b9abbca93910769f26a924c7a01811103"),
Expand Down Expand Up @@ -315,15 +402,15 @@ mod tests {
}

#[test]
fn insert_env_overwrites_existing_alias() {
fn insert_env_history_overwrites_existing_alias() {
let mut registry = PackageRegistry::default();
registry.insert_env(
registry.insert_env_history(
Env::new_with_alias("6364aad5", "mainnet"),
vec![object_id!(
"0x84cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed2054ad931de08"
)],
);
registry.insert_env(
registry.insert_env_history(
Env::new_with_alias("2304aa97", "mainnet"),
vec![object_id!(
"0x222741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac864ab4e4cc555"
Expand Down Expand Up @@ -354,15 +441,15 @@ mod tests {
#[test]
fn join_merges_aliases_and_envs() {
let mut registry1 = PackageRegistry::default();
registry1.insert_env(
registry1.insert_env_history(
Env::new_with_alias("6364aad5", "mainnet"),
vec![object_id!(
"0x84cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed2054ad931de08"
)],
);

let mut registry2 = PackageRegistry::default();
registry2.insert_env(
registry2.insert_env_history(
Env::new_with_alias("2304aa97", "testnet"),
vec![object_id!(
"0x222741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac864ab4e4cc555"
Expand All @@ -376,4 +463,120 @@ mod tests {
assert!(registry1.envs.contains_key("6364aad5"));
assert!(registry1.envs.contains_key("2304aa97"));
}

// ----------------------------------------------
// Tests for the deprecated `insert_env` function
// ----------------------------------------------

#[test]
fn insert_env_creates_single_entry_history_when_original_equals_latest() {
let mut registry = PackageRegistry::default();
#[allow(deprecated)]
registry.insert_env(
Env::new_with_alias("6364aad5", "mainnet"),
Metadata {
original_published_id: object_id!("0x84cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed2054ad931de08"),
latest_published_id: object_id!("0x84cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed2054ad931de08"),
published_version: 1,
},
);

assert_eq!(registry.history("6364aad5").unwrap().len(), 1);
assert_eq!(
registry.history("6364aad5").unwrap()[0],
object_id!("0x84cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed2054ad931de08")
);
}

#[test]
fn insert_env_creates_two_entry_history_when_original_differs_from_latest() {
let mut registry = PackageRegistry::default();
#[allow(deprecated)]
registry.insert_env(
Env::new_with_alias("2304aa97", "testnet"),
Metadata {
original_published_id: object_id!("0x222741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac864ab4e4cc555"),
latest_published_id: object_id!("0x3403da7ec4cd2ff9bdf6f34c0b8df5a2bd62c798089feb0d2ebf1c2e953296dc"),
published_version: 2,
},
);

assert_eq!(registry.history("2304aa97").unwrap().len(), 2);
assert_eq!(
registry.history("2304aa97").unwrap()[0],
object_id!("0x222741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac864ab4e4cc555")
);
assert_eq!(
registry.history("2304aa97").unwrap()[1],
object_id!("0x3403da7ec4cd2ff9bdf6f34c0b8df5a2bd62c798089feb0d2ebf1c2e953296dc")
);

let package_id = registry.package_id("testnet");
assert_eq!(
package_id,
Some(object_id!(
"0x3403da7ec4cd2ff9bdf6f34c0b8df5a2bd62c798089feb0d2ebf1c2e953296dc"
))
);
}

#[test]
fn insert_env_adds_alias_when_provided() {
let mut registry = PackageRegistry::default();
#[allow(deprecated)]
registry.insert_env(
Env::new_with_alias("6364aad5", "mainnet"),
Metadata::from_package_id(object_id!(
"0x84cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed2054ad931de08"
)),
);

assert_eq!(registry.aliases.get("mainnet"), Some(&"6364aad5".to_string()));
}

#[test]
fn insert_env_does_not_add_alias_when_not_provided() {
let mut registry = PackageRegistry::default();
#[allow(deprecated)]
registry.insert_env(
Env::new("6364aad5"),
Metadata::from_package_id(object_id!(
"0x84cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed2054ad931de08"
)),
);

assert!(registry.aliases.is_empty());
assert!(registry.envs.contains_key("6364aad5"));
}

#[test]
fn insert_env_replaces_existing_environment_with_same_chain_id() {
let mut registry = PackageRegistry::default();
#[allow(deprecated)]
registry.insert_env(
Env::new_with_alias("6364aad5", "mainnet"),
Metadata::from_package_id(object_id!(
"0x84cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed2054ad931de08"
)),
);

#[allow(deprecated)]
registry.insert_env(
Env::new_with_alias("6364aad5", "production"),
Metadata::from_package_id(object_id!(
"0x94cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed2054ad931de09"
)),
);

// Only one env with chain ID "6364aad5" should exist, the second insert replaced the first.
assert_eq!(registry.history("6364aad5").unwrap().len(), 1);
assert_eq!(
registry.history("6364aad5").unwrap()[0],
object_id!("0x94cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed2054ad931de09")
);

// Aliases are managed using the alias name as key, so both should exist.
assert_eq!(registry.aliases.get("production"), Some(&"6364aad5".to_string()));
assert_eq!(registry.aliases.get("mainnet"), Some(&"6364aad5".to_string()));
}
}
Loading