From aad59c2b99b4cc1a0b12ae732f7562a9f0b978bd Mon Sep 17 00:00:00 2001 From: temiport25 Date: Mon, 20 Jul 2026 16:51:34 +0100 Subject: [PATCH 1/2] refactor: decouple legacy boolean privacy storage from PrivacyLevel (#317) Separate legacy boolean privacy storage helpers from new PrivacyLevel storage paths for cleaner maintenance and independent evolution. - Create legacy_privacy.rs with backward-compatible boolean helpers (key construction, fallback reads, typed key cleanup) - Refactor privacy.rs to own PrivacyLevel (numeric) API, history, and a migrate_boolean_to_level() conversion function - Remove privacy-level functions from storage.rs (moved to privacy.rs) - Add migration tests for boolean-to-level conversion - Update storage.rs and UPGRADE_SAFETY_GATE_QUICK_REFERENCE.md docs --- .../UPGRADE_SAFETY_GATE_QUICK_REFERENCE.md | 25 ++ .../contracts/Folder/src/legacy_privacy.rs | 177 +++++++++++++ app/contract/contracts/Folder/src/lib.rs | 2 + app/contract/contracts/Folder/src/privacy.rs | 233 +++++++++++++++--- app/contract/contracts/Folder/src/storage.rs | 64 +---- .../contracts/Folder/src/storage_test.rs | 1 + 6 files changed, 407 insertions(+), 95 deletions(-) create mode 100644 app/contract/contracts/Folder/src/legacy_privacy.rs diff --git a/app/contract/UPGRADE_SAFETY_GATE_QUICK_REFERENCE.md b/app/contract/UPGRADE_SAFETY_GATE_QUICK_REFERENCE.md index df441ebb7..15e61740d 100644 --- a/app/contract/UPGRADE_SAFETY_GATE_QUICK_REFERENCE.md +++ b/app/contract/UPGRADE_SAFETY_GATE_QUICK_REFERENCE.md @@ -223,6 +223,31 @@ assert_eq!(contract.get_version(), 2); --- +## Privacy Storage Decoupling (Issue #317) + +Legacy boolean privacy (`privacy_enabled`) and the new numeric `PrivacyLevel` +storage paths are now maintained independently: + +| Module | Responsibility | +|---------------------|-------------------------------------------------------------| +| `legacy_privacy.rs` | Backward-compatible boolean read/write with key migration | +| `privacy.rs` | `PrivacyLevel` (numeric), history, and boolean→level migration | +| `storage.rs` | All other persistent storage (escrow, fees, admin, etc.) | + +### Migration helper + +```rust +// Convert a legacy boolean flag to the numeric PrivacyLevel API. +// false → 0, true → 1. No-op if no boolean flag exists. +privacy::migrate_boolean_to_level(env, account) +``` + +During a rolling upgrade call `migrate_boolean_to_level` for each account +that may have a legacy boolean flag. Both storage paths coexist until the +migration completes. + +--- + ## Related Documentation - **UPGRADE_SAFETY_GATE.md**: Full spec, usage examples, checklist diff --git a/app/contract/contracts/Folder/src/legacy_privacy.rs b/app/contract/contracts/Folder/src/legacy_privacy.rs new file mode 100644 index 000000000..5e65a8834 --- /dev/null +++ b/app/contract/contracts/Folder/src/legacy_privacy.rs @@ -0,0 +1,177 @@ +//! # Legacy Boolean Privacy Storage +//! +//! Backward-compatible helpers for the original boolean `privacy_enabled` flag. +//! +//! ## Storage layout +//! +//! Two key formats coexist for historical reasons: +//! +//! | Key format | Value type | Era | +//! |------------------------------------|------------|-----------| +//! | `(Symbol::new("privacy_enabled"), Address)` | `bool` | Legacy | +//! | `DataKey::PrivacyEnabled(Address)`| `bool` | Current | +//! +//! On every **write** the legacy symbol key is removed (if present) and the +//! typed key is used instead. On **read**, the typed key is checked first; +//! if absent the legacy key is used as a fallback. +//! +//! For the new `PrivacyLevel` (numeric) API see [`crate::privacy`]. +//! For migration from boolean to level, see +//! [`crate::privacy::migrate_boolean_to_level`]. + +use crate::errors::RustAcademyError; +use crate::events::publish_privacy_toggled; +use crate::storage::{DataKey, PRIVACY_ENABLED_KEY}; +use soroban_sdk::{Address, Env, Symbol}; + +/// Construct the legacy `(Symbol, Address)` storage key. +pub fn legacy_privacy_key(env: &Env, owner: &Address) -> (Symbol, Address) { + (Symbol::new(env, PRIVACY_ENABLED_KEY), owner.clone()) +} + +/// Construct the typed `DataKey::PrivacyEnabled` storage key. +pub fn typed_privacy_key(owner: &Address) -> DataKey { + DataKey::PrivacyEnabled(owner.clone()) +} + +/// Read the boolean privacy flag for `owner`, falling back from the typed +/// key to the legacy symbol key. +/// +/// Returns `false` when neither key is set. +pub fn read_privacy_flag(env: &Env, owner: &Address) -> bool { + let typed_key = typed_privacy_key(owner); + if let Some(enabled) = env.storage().persistent().get(&typed_key) { + return enabled; + } + + env.storage() + .persistent() + .get(&legacy_privacy_key(env, owner)) + .unwrap_or(false) +} + +/// Remove the legacy symbol key if present. +/// +/// Called internally after a write to the typed key so that subsequent reads +/// go through the fast path. +pub fn cleanup_legacy_key(env: &Env, owner: &Address) { + let legacy_key = legacy_privacy_key(env, owner); + if env.storage().persistent().has(&legacy_key) { + env.storage().persistent().remove(&legacy_key); + } +} + +/// Enable or disable privacy for an account (boolean API). +/// +/// Reads the current state first and returns [`RustAcademyError::PrivacyAlreadySet`] +/// if the requested value matches the current value. Otherwise persists the new +/// state via the typed key, cleans up the legacy key, and publishes a +/// [`crate::events::publish_privacy_toggled`] event. +pub fn set_privacy(env: &Env, owner: Address, enabled: bool) -> Result<(), RustAcademyError> { + owner.require_auth(); + + let current = read_privacy_flag(env, &owner); + if current == enabled { + return Err(RustAcademyError::PrivacyAlreadySet); + } + + let typed_key = typed_privacy_key(&owner); + env.storage().persistent().set(&typed_key, &enabled); + + cleanup_legacy_key(env, &owner); + + publish_privacy_toggled(env, owner, enabled); + Ok(()) +} + +/// Return the current boolean privacy state for an account. +/// +/// Defaults to `false` if never set. +pub fn get_privacy(env: &Env, owner: Address) -> bool { + read_privacy_flag(env, &owner) +} + +#[cfg(test)] +pub(crate) mod tests { + use super::*; + use soroban_sdk::testutils::Address as _; + + #[test] + fn test_read_returns_false_when_unset() { + let env = Env::default(); + let contract_id = env.register(crate::RustAcademyContract, ()); + let owner = Address::generate(&env); + env.as_contract(&contract_id, || { + assert!(!read_privacy_flag(&env, &owner)); + }); + } + + #[test] + fn test_read_prefers_typed_key() { + let env = Env::default(); + let contract_id = env.register(crate::RustAcademyContract, ()); + let owner = Address::generate(&env); + env.as_contract(&contract_id, || { + let typed_key = typed_privacy_key(&owner); + env.storage().persistent().set(&typed_key, &true); + assert!(read_privacy_flag(&env, &owner)); + }); + } + + #[test] + fn test_read_falls_back_to_legacy_key() { + let env = Env::default(); + let contract_id = env.register(crate::RustAcademyContract, ()); + let owner = Address::generate(&env); + env.as_contract(&contract_id, || { + let legacy_key = legacy_privacy_key(&env, &owner); + env.storage().persistent().set(&legacy_key, &true); + assert!(read_privacy_flag(&env, &owner)); + }); + } + + #[test] + fn test_typed_key_takes_precedence_over_legacy() { + let env = Env::default(); + let contract_id = env.register(crate::RustAcademyContract, ()); + let owner = Address::generate(&env); + env.as_contract(&contract_id, || { + let typed_key = typed_privacy_key(&owner); + let legacy_key = legacy_privacy_key(&env, &owner); + env.storage().persistent().set(&typed_key, &true); + env.storage().persistent().set(&legacy_key, &false); + assert!(read_privacy_flag(&env, &owner)); + }); + } + + #[test] + fn test_cleanup_removes_legacy_key() { + let env = Env::default(); + let contract_id = env.register(crate::RustAcademyContract, ()); + let owner = Address::generate(&env); + env.as_contract(&contract_id, || { + let legacy_key = legacy_privacy_key(&env, &owner); + env.storage().persistent().set(&legacy_key, &true); + assert!(env.storage().persistent().has(&legacy_key)); + cleanup_legacy_key(&env, &owner); + assert!(!env.storage().persistent().has(&legacy_key)); + }); + } + + #[test] + fn test_cleanup_is_idempotent_when_no_legacy_key() { + let env = Env::default(); + let contract_id = env.register(crate::RustAcademyContract, ()); + let owner = Address::generate(&env); + env.as_contract(&contract_id, || { + cleanup_legacy_key(&env, &owner); + }); + } + + /// Helper: write a raw boolean to the typed key (no auth, no event). + /// Used by migration tests in `privacy::tests`. + pub fn set_raw_privacy(env: &Env, owner: &Address, enabled: bool) { + let typed_key = typed_privacy_key(owner); + env.storage().persistent().set(&typed_key, &enabled); + } +} diff --git a/app/contract/contracts/Folder/src/lib.rs b/app/contract/contracts/Folder/src/lib.rs index ec49c2f89..5b6561b41 100644 --- a/app/contract/contracts/Folder/src/lib.rs +++ b/app/contract/contracts/Folder/src/lib.rs @@ -31,6 +31,7 @@ pub mod nonce; mod nonce_test; mod oracle; mod privacy; +mod legacy_privacy; #[cfg(test)] mod role_test; mod stealth; @@ -49,6 +50,7 @@ mod upgrade_test; use errors::RustAcademyError; use storage::*; +use privacy::{add_privacy_history, get_privacy_history, get_privacy_level, set_privacy_level}; use types::{ ContractHealth, DeploymentMetadata, DisputeExpiryAction, EscrowEntry, EscrowOperationEstimate, EscrowOperationLimits, EscrowStatus, FeatureFlags, FeeConfig, diff --git a/app/contract/contracts/Folder/src/privacy.rs b/app/contract/contracts/Folder/src/privacy.rs index bfc6e2ee5..62efde5b7 100644 --- a/app/contract/contracts/Folder/src/privacy.rs +++ b/app/contract/contracts/Folder/src/privacy.rs @@ -1,56 +1,213 @@ -use crate::errors:: RustAcademyError; -use crate::events::publish_privacy_toggled; -use crate::storage::{DataKey, PRIVACY_ENABLED_KEY}; -use soroban_sdk::{Address, Env, Symbol}; +//! # Privacy Module +//! +//! This module provides two independent privacy APIs: +//! +//! 1. **Legacy boolean** (`set_privacy` / `get_privacy`) — the original on/off +//! flag. Backward-compatible helpers live in [`crate::legacy_privacy`] and +//! are re-exported here for convenience. +//! +//! 2. **PrivacyLevel** (numeric) — a per-account `u32` level with append-only +//! history. Used by the `enable_privacy` contract entry point. +//! +//! ## Migration +//! +//! [`migrate_boolean_to_level`] converts a legacy boolean flag into the +//! equivalent numeric level (off → `0`, on → `1`) so that both storage paths +//! can coexist during a rolling upgrade. +//! +//! ## Storage keys +//! +//! | API | Key variant | Value type | +//! |----------------|-------------------------------------|------------| +//! | Boolean | `DataKey::PrivacyEnabled(Address)` | `bool` | +//! | Level | `DataKey::PrivacyLevel(Address)` | `u32` | +//! | Level history | `DataKey::PrivacyHistory(Address)` | `Vec` | -fn legacy_privacy_key(env: &Env, owner: &Address) -> (Symbol, Address) { - (Symbol::new(env, PRIVACY_ENABLED_KEY), owner.clone()) -} +use crate::storage::{DataKey, RecordType, set_or_extend_ttl, MAX_PRIVACY_HISTORY}; +use soroban_sdk::{Address, Env, Vec}; + +// Re-export legacy boolean helpers so that existing callers +// (`privacy::set_privacy`, `privacy::get_privacy`) continue to work. +pub use crate::legacy_privacy::{get_privacy, set_privacy}; -fn typed_privacy_key(owner: &Address) -> DataKey { - DataKey::PrivacyEnabled(owner.clone()) +// --------------------------------------------------------------------------- +// PrivacyLevel helpers (numeric API) +// --------------------------------------------------------------------------- + +/// Set privacy level for an account. +pub fn set_privacy_level(env: &Env, account: &Address, level: u32) { + let key = DataKey::PrivacyLevel(account.clone()); + env.storage().persistent().set(&key, &level); + set_or_extend_ttl(env, &key, RecordType::Privacy); } -fn read_privacy_flag(env: &Env, owner: &Address) -> bool { - let typed_key = typed_privacy_key(owner); - if let Some(enabled) = env.storage().persistent().get(&typed_key) { - return enabled; +/// Get privacy level for an account. +/// +/// Returns `None` if no level has been set. +pub fn get_privacy_level(env: &Env, account: &Address) -> Option { + let key = DataKey::PrivacyLevel(account.clone()); + let result = env.storage().persistent().get(&key); + if result.is_some() { + set_or_extend_ttl(env, &key, RecordType::Privacy); } + result +} - env.storage() +/// Append a level entry to the per-account privacy history. +/// +/// The new entry is pushed to the front (newest-first). History is capped at +/// [`MAX_PRIVACY_HISTORY`] entries; the oldest entries are evicted when the cap +/// is exceeded so per-account storage stays bounded (Issue #51). +pub fn add_privacy_history(env: &Env, account: &Address, level: u32) { + let key = DataKey::PrivacyHistory(account.clone()); + let mut history: Vec = env + .storage() .persistent() - .get(&legacy_privacy_key(env, owner)) - .unwrap_or(false) + .get(&key) + .unwrap_or(Vec::new(env)); + history.push_front(level); + while history.len() > MAX_PRIVACY_HISTORY { + history.pop_back(); + } + env.storage().persistent().set(&key, &history); + set_or_extend_ttl(env, &key, RecordType::Privacy); } -/// Enable or disable privacy for an account. +/// Get the privacy history for an account. /// -/// Reads the current state first and returns [` RustAcademyError::PrivacyAlreadySet`] -/// if the requested value matches the current value. Otherwise persists the new -/// state and publishes a [`crate::events::publish_privacy_toggled`] event. -pub fn set_privacy(env: &Env, owner: Address, enabled: bool) -> Result<(), RustAcademyError> { - owner.require_auth(); +/// Returns an empty vec if never set. Order is newest-first. +pub fn get_privacy_history(env: &Env, account: &Address) -> Vec { + let key = DataKey::PrivacyHistory(account.clone()); + let result = env.storage().persistent().get(&key); + if result.is_some() { + set_or_extend_ttl(env, &key, RecordType::Privacy); + } + result.unwrap_or(Vec::new(env)) +} + +// --------------------------------------------------------------------------- +// Migration: boolean → PrivacyLevel +// --------------------------------------------------------------------------- + +/// Migrate a legacy boolean privacy flag to the numeric [`PrivacyLevel`] API. +/// +/// - `false` → level `0` (off) +/// - `true` → level `1` (basic privacy) +/// +/// If no boolean flag exists for `account`, this is a no-op. +/// Cleans up both the typed and legacy boolean keys after migration. +/// Returns the resulting privacy level. +pub fn migrate_boolean_to_level(env: &Env, account: &Address) -> u32 { + use crate::legacy_privacy::{read_privacy_flag, cleanup_legacy_key, typed_privacy_key}; + + let enabled = read_privacy_flag(env, account); + let level: u32 = if enabled { 1 } else { 0 }; - let current = read_privacy_flag(env, &owner); - if current == enabled { - return Err( RustAcademyError::PrivacyAlreadySet); + set_privacy_level(env, account, level); + add_privacy_history(env, account, level); + + // Clean up both boolean storage keys so only the level remains. + cleanup_legacy_key(env, account); + let typed_key = typed_privacy_key(account); + env.storage().persistent().remove(&typed_key); + + level +} + +#[cfg(test)] +mod tests { + use super::*; + use soroban_sdk::testutils::Address as _; + + #[test] + fn test_set_and_get_privacy_level() { + let env = Env::default(); + let contract_id = env.register(crate::RustAcademyContract, ()); + let account = Address::generate(&env); + env.as_contract(&contract_id, || { + set_privacy_level(&env, &account, 5); + assert_eq!(get_privacy_level(&env, &account), Some(5)); + }); } - let typed_key = typed_privacy_key(&owner); - env.storage().persistent().set(&typed_key, &enabled); + #[test] + fn test_get_privacy_level_returns_none_when_unset() { + let env = Env::default(); + let contract_id = env.register(crate::RustAcademyContract, ()); + let account = Address::generate(&env); + env.as_contract(&contract_id, || { + assert!(get_privacy_level(&env, &account).is_none()); + }); + } - let legacy_key = legacy_privacy_key(env, &owner); - if env.storage().persistent().has(&legacy_key) { - env.storage().persistent().remove(&legacy_key); + #[test] + fn test_add_privacy_history_newest_first() { + let env = Env::default(); + let contract_id = env.register(crate::RustAcademyContract, ()); + let account = Address::generate(&env); + env.as_contract(&contract_id, || { + add_privacy_history(&env, &account, 1); + add_privacy_history(&env, &account, 2); + add_privacy_history(&env, &account, 3); + let history = get_privacy_history(&env, &account); + assert_eq!(history.len(), 3); + assert_eq!(history.get(0).unwrap(), 3u32); + assert_eq!(history.get(1).unwrap(), 2u32); + assert_eq!(history.get(2).unwrap(), 1u32); + }); } - publish_privacy_toggled(env, owner, enabled); - Ok(()) -} + #[test] + fn test_history_is_bounded() { + let env = Env::default(); + let contract_id = env.register(crate::RustAcademyContract, ()); + let account = Address::generate(&env); + env.as_contract(&contract_id, || { + let total = MAX_PRIVACY_HISTORY + 10; + for i in 0..total { + add_privacy_history(&env, &account, i); + } + let history = get_privacy_history(&env, &account); + assert_eq!(history.len(), MAX_PRIVACY_HISTORY); + }); + } -/// Return the current boolean privacy state for an account. -/// -/// Defaults to `false` if never set. -pub fn get_privacy(env: &Env, owner: Address) -> bool { - read_privacy_flag(env, &owner) + #[test] + fn test_migrate_boolean_true_to_level_1() { + let env = Env::default(); + let contract_id = env.register(crate::RustAcademyContract, ()); + let account = Address::generate(&env); + env.as_contract(&contract_id, || { + crate::legacy_privacy::tests::set_raw_privacy(&env, &account, true); + let level = migrate_boolean_to_level(&env, &account); + assert_eq!(level, 1); + assert_eq!(get_privacy_level(&env, &account), Some(1)); + assert!(!crate::legacy_privacy::read_privacy_flag(&env, &account)); + }); + } + + #[test] + fn test_migrate_boolean_false_to_level_0() { + let env = Env::default(); + let contract_id = env.register(crate::RustAcademyContract, ()); + let account = Address::generate(&env); + env.as_contract(&contract_id, || { + crate::legacy_privacy::tests::set_raw_privacy(&env, &account, false); + let level = migrate_boolean_to_level(&env, &account); + assert_eq!(level, 0); + assert_eq!(get_privacy_level(&env, &account), Some(0)); + }); + } + + #[test] + fn test_migrate_boolean_noop_when_no_flag() { + let env = Env::default(); + let contract_id = env.register(crate::RustAcademyContract, ()); + let account = Address::generate(&env); + env.as_contract(&contract_id, || { + let level = migrate_boolean_to_level(&env, &account); + assert_eq!(level, 0); + assert_eq!(get_privacy_level(&env, &account), Some(0)); + }); + } } diff --git a/app/contract/contracts/Folder/src/storage.rs b/app/contract/contracts/Folder/src/storage.rs index edb786f7f..a46479a3b 100644 --- a/app/contract/contracts/Folder/src/storage.rs +++ b/app/contract/contracts/Folder/src/storage.rs @@ -13,14 +13,14 @@ //! | [`ContractVersion`](DataKey::ContractVersion) | `u32` | Stored schema/version marker for upgrade migrations. | //! | [`Admin`](DataKey::Admin) | `Address` | Contract admin address. Set during initialisation, transferable by admin. | //! | [`Paused`](DataKey::Paused) | `bool` | Global pause flag. When true, critical operations may be blocked. | -//! | [`PrivacyLevel`](DataKey::PrivacyLevel) | `u32` | Numeric privacy level per account (0 = off). Used by `enable_privacy`. | -//! | [`PrivacyHistory`](DataKey::PrivacyHistory) | `Vec` | Per-account history of privacy level changes (chronological). | +//! | [`PrivacyLevel`](DataKey::PrivacyLevel) | `u32` | Numeric privacy level per account (0 = off). Used by `enable_privacy`. Migrated to [`crate::privacy`]. | +//! | [`PrivacyHistory`](DataKey::PrivacyHistory) | `Vec` | Per-account history of privacy level changes (chronological). Migrated to [`crate::privacy`]. | //! //! ## Related Keys (legacy compatibility) //! //! | Key | Format | Value Type | Description | //! |------------------------|---------------------------|------------|-------------| -//! | `privacy_enabled` | `(Symbol, Address)` | `bool` | Legacy boolean privacy on/off key. Read as a fallback and migrated to [`DataKey::PrivacyEnabled`] on write. | +//! | `privacy_enabled` | `(Symbol, Address)` | `bool` | Legacy boolean privacy on/off key. Managed by [`crate::legacy_privacy`]. Read as a fallback and migrated to [`DataKey::PrivacyEnabled`] on write. | //! //! ## Relations //! @@ -611,60 +611,10 @@ pub fn is_paused(env: &Env) -> bool { env.storage().persistent().get(&key).unwrap_or(false) } -// ----------------------------------------------------------------------------- -// Privacy helpers (level-based API) -// ----------------------------------------------------------------------------- - -/// Set privacy level for an account. -pub fn set_privacy_level(env: &Env, account: &Address, level: u32) { - let key = DataKey::PrivacyLevel(account.clone()); - env.storage().persistent().set(&key, &level); - set_or_extend_ttl(env, &key, RecordType::Privacy); -} - -/// Get privacy level for an account. -pub fn get_privacy_level(env: &Env, account: &Address) -> Option { - let key = DataKey::PrivacyLevel(account.clone()); - let result = env.storage().persistent().get(&key); - if result.is_some() { - set_or_extend_ttl(env, &key, RecordType::Privacy); - } - result -} - -/// Add to privacy history for an account. -/// -/// **Contract**: Pushes `level` to the front of the history (newest-first). -/// History is capped at [`MAX_PRIVACY_HISTORY`] entries; the oldest entries -/// are evicted when the cap is exceeded so per-account storage stays bounded. -pub fn add_privacy_history(env: &Env, account: &Address, level: u32) { - let key = DataKey::PrivacyHistory(account.clone()); - let mut history: Vec = env - .storage() - .persistent() - .get(&key) - .unwrap_or(Vec::new(env)); - history.push_front(level); - // Bounded retention: evict the oldest entries beyond the cap so this - // per-account index cannot accumulate unbounded storage (Issue #15). - while history.len() > MAX_PRIVACY_HISTORY { - history.pop_back(); - } - env.storage().persistent().set(&key, &history); - set_or_extend_ttl(env, &key, RecordType::Privacy); -} - -/// Get privacy history for an account. -/// -/// **Contract**: Returns empty vec if never set. Order is newest-first. -pub fn get_privacy_history(env: &Env, account: &Address) -> Vec { - let key = DataKey::PrivacyHistory(account.clone()); - let result = env.storage().persistent().get(&key); - if result.is_some() { - set_or_extend_ttl(env, &key, RecordType::Privacy); - } - result.unwrap_or(Vec::new(env)) -} +// NOTE: Privacy-level helpers (set_privacy_level, get_privacy_level, +// add_privacy_history, get_privacy_history) have been moved to +// crate::privacy to decouple them from legacy boolean privacy storage. +// See Issue #317. // ----------------------------------------------------------------------------- // Fee & Wallet helpers diff --git a/app/contract/contracts/Folder/src/storage_test.rs b/app/contract/contracts/Folder/src/storage_test.rs index 1a416772d..e654fd1a4 100644 --- a/app/contract/contracts/Folder/src/storage_test.rs +++ b/app/contract/contracts/Folder/src/storage_test.rs @@ -102,6 +102,7 @@ use soroban_sdk::{testutils::Address as _, Address, Bytes, Env}; use crate::{ storage::*, + privacy::{add_privacy_history, get_privacy_history, get_privacy_level, set_privacy_level}, types::{EscrowEntry, EscrowStatus}, }; From 1dab4130df3b9dcd24bbd8988d9d232c332c058a Mon Sep 17 00:00:00 2001 From: temiport25 Date: Mon, 20 Jul 2026 21:30:55 +0100 Subject: [PATCH 2/2] fix: update ethnum to v1.5.3 to resolve transmute error on Rust 1.97 --- app/contract/Cargo.lock | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/app/contract/Cargo.lock b/app/contract/Cargo.lock index d36c03a78..52e49599d 100644 --- a/app/contract/Cargo.lock +++ b/app/contract/Cargo.lock @@ -2,17 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "RustAcademy" -version = "0.1.0" -dependencies = [ - "blake3", - "hex", - "proptest", - "serde_json", - "soroban-sdk", -] - [[package]] name = "ahash" version = "0.8.12" @@ -628,9 +617,9 @@ checksum = "2bfcf67fea2815c2fc3b90873fae90957be12ff417335dfadc7f52927feb03b2" [[package]] name = "ethnum" -version = "1.5.2" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca81e6b4777c89fd810c25a4be2b1bd93ea034fbe58e6a75216a34c6b82c539b" +checksum = "40404c3f5f511ec4da6fe866ddf6a717c309fdbb69fbbad7b0f3edab8f2e835f" [[package]] name = "ff" @@ -1154,6 +1143,17 @@ dependencies = [ "subtle", ] +[[package]] +name = "rust_academy" +version = "0.1.0" +dependencies = [ + "blake3", + "hex", + "proptest", + "serde_json", + "soroban-sdk", +] + [[package]] name = "rustc_version" version = "0.4.1"