From 404b5a8d18910f8c05c6618470437b6fba0ac0b1 Mon Sep 17 00:00:00 2001 From: Yasir Date: Wed, 3 Jun 2026 11:16:52 +0300 Subject: [PATCH 1/5] refactor: remove TfComponents dependency and implement local timelock module --- notarization-move/Move.lock | 12 -- notarization-move/Move.toml | 1 - notarization-move/README.md | 4 +- notarization-move/api_mapping.toml | 3 - .../sources/dynamic_notarization.move | 3 +- .../sources/locked_notarization.move | 3 +- notarization-move/sources/notarization.move | 38 ++-- notarization-move/sources/timelock.move | 127 +++++++++++ .../tests/dynamic_notarization_tests.move | 3 +- .../tests/locked_notarization_tests.move | 3 +- .../tests/notarization_tests.move | 3 +- notarization-move/tests/timelock_tests.move | 202 ++++++++++++++++++ 12 files changed, 352 insertions(+), 50 deletions(-) create mode 100644 notarization-move/sources/timelock.move create mode 100644 notarization-move/tests/timelock_tests.move diff --git a/notarization-move/Move.lock b/notarization-move/Move.lock index a0489408..5bc04d6e 100644 --- a/notarization-move/Move.lock +++ b/notarization-move/Move.lock @@ -9,7 +9,6 @@ dependencies = [ { id = "IotaSystem", name = "IotaSystem" }, { id = "MoveStdlib", name = "MoveStdlib" }, { id = "Stardust", name = "Stardust" }, - { id = "TfComponents", name = "TfComponents" }, ] [[move.package]] @@ -42,17 +41,6 @@ dependencies = [ { id = "MoveStdlib", name = "MoveStdlib" }, ] -[[move.package]] -id = "TfComponents" -source = { git = "https://github.com/iotaledger/product-core.git", rev = "main", subdir = "components_move" } - -dependencies = [ - { id = "Iota", name = "Iota" }, - { id = "IotaSystem", name = "IotaSystem" }, - { id = "MoveStdlib", name = "MoveStdlib" }, - { id = "Stardust", name = "Stardust" }, -] - [move.toolchain-version] compiler-version = "1.16.2-rc" edition = "2024.beta" diff --git a/notarization-move/Move.toml b/notarization-move/Move.toml index 4726d096..93671c18 100644 --- a/notarization-move/Move.toml +++ b/notarization-move/Move.toml @@ -6,7 +6,6 @@ name = "IotaNotarization" edition = "2024.beta" [dependencies] -TfComponents = { git = "https://github.com/iotaledger/product-core.git", subdir = "components_move", rev = "main" } [addresses] iota_notarization = "0x0" diff --git a/notarization-move/README.md b/notarization-move/README.md index 1012e3a4..4c17fbdd 100644 --- a/notarization-move/README.md +++ b/notarization-move/README.md @@ -31,12 +31,12 @@ It defines the core `Notarization` object and the supporting modules for: - state updates, transfer rules, and destruction checks - emitted events for notarization lifecycle changes -The package depends on `TfComponents` for shared timelock primitives. - ## Modules - `iota_notarization::notarization` Core object, state model, metadata, lock metadata, updates, and destruction logic. +- `iota_notarization::timelock` + Package-local timelock primitives used by notarization lock metadata. - `iota_notarization::dynamic_notarization` Dynamic notarization creation and transfer flows. - `iota_notarization::locked_notarization` diff --git a/notarization-move/api_mapping.toml b/notarization-move/api_mapping.toml index 91029adb..7653ea87 100644 --- a/notarization-move/api_mapping.toml +++ b/notarization-move/api_mapping.toml @@ -124,15 +124,12 @@ rust = [ "LockMetadata", "TimeLock", "TimeLock::new_with_ts", - "TimeLock::new_with_ts_ms", ] wasm = [ "WasmLockMetadata", "WasmTimeLock", "WasmTimeLock::with_unlock_at", - "WasmTimeLock::with_unlock_at_ms", "WasmTimeLock::with_until_destroyed", - "WasmTimeLock::with_infinite", "WasmTimeLock::with_none", "WasmTimeLockType", ] diff --git a/notarization-move/sources/dynamic_notarization.move b/notarization-move/sources/dynamic_notarization.move index b11335e4..83f195d7 100644 --- a/notarization-move/sources/dynamic_notarization.move +++ b/notarization-move/sources/dynamic_notarization.move @@ -8,9 +8,8 @@ module iota_notarization::dynamic_notarization; use iota::{clock::Clock, event}; -use iota_notarization::notarization; +use iota_notarization::{notarization, timelock::TimeLock}; use std::string::String; -use tf_components::timelock::TimeLock; // ===== Constants ===== /// Raised when `transfer` is called on a notarization whose `transfer_lock` diff --git a/notarization-move/sources/locked_notarization.move b/notarization-move/sources/locked_notarization.move index 2f8238c3..19fa59a9 100644 --- a/notarization-move/sources/locked_notarization.move +++ b/notarization-move/sources/locked_notarization.move @@ -8,9 +8,8 @@ module iota_notarization::locked_notarization; use iota::{clock::Clock, event}; -use iota_notarization::notarization; +use iota_notarization::{notarization, timelock::TimeLock}; use std::string::String; -use tf_components::timelock::TimeLock; /// Emitted by `create` after a Locked-Notarization is created and /// transferred to the sender. diff --git a/notarization-move/sources/notarization.move b/notarization-move/sources/notarization.move index 9d319347..6643f0ef 100644 --- a/notarization-move/sources/notarization.move +++ b/notarization-move/sources/notarization.move @@ -8,9 +8,11 @@ module iota_notarization::notarization; use iota::{clock::{Self, Clock}, event}; -use iota_notarization::method::{NotarizationMethod, new_dynamic, new_locked}; +use iota_notarization::{ + method::{NotarizationMethod, new_dynamic, new_locked}, + timelock::{Self, TimeLock}, +}; use std::string::String; -use tf_components::timelock::{Self, TimeLock}; // ===== Constants ===== /// Raised when `state` or `updatable_metadata` is updated while the update lock is active. @@ -150,13 +152,12 @@ public fun new_state_from_generic( State { data, metadata } } -/// Constructs a `LockMetadata` from the three component `TimeLock`s. +/// Constructs a `LockMetadata` from the three package-local `TimeLock`s. /// /// Rejects combinations that would let the object be destroyed before its -/// update or transfer locks expire. When `delete_lock` is a `TimeLock::UnlockAt` -/// (or `UnlockAtMs`), its unlock time must be greater than or equal to the -/// unlock time of any `UnlockAt` (or `UnlockAtMs`) `update_lock` or -/// `transfer_lock`. +/// update or transfer locks expire. When `delete_lock` is a `TimeLock::UnlockAt`, +/// its unlock time must be greater than or equal to the unlock time of any +/// `UnlockAt` `update_lock` or `transfer_lock`. /// /// In the current implementation the legal combinations are further narrowed /// by the method-specific invariants enforced in `new_dynamic_notarization` @@ -166,9 +167,8 @@ public fun new_state_from_generic( /// /// Aborts with: /// * `EUntilDestroyedLockNotAllowed` when `delete_lock` is `TimeLock::UntilDestroyed`. -/// * `ELockTimeNotSatisfied` when `delete_lock` is `UnlockAt`/`UnlockAtMs` and -/// its unlock time is earlier than the unlock time of `update_lock` or -/// `transfer_lock`. +/// * `ELockTimeNotSatisfied` when `delete_lock` is `UnlockAt` and its unlock +/// time is earlier than the unlock time of `update_lock` or `transfer_lock`. /// /// Returns the constructed `LockMetadata`. public fun new_lock_metadata( @@ -375,15 +375,14 @@ public fun update_state( /// Destroys `self` and releases the underlying object id. /// -/// All component `TimeLock`s of the optional `LockMetadata` are destroyed in +/// All package-local `TimeLock`s of the optional `LockMetadata` are destroyed in /// the process; the gating check `is_destroy_allowed` ensures that no -/// `UnlockAt`/`UnlockAtMs` lock is still active. `TimeLock::Infinite` is not -/// destructible and therefore always blocks destruction. +/// `UnlockAt` lock is still active. /// /// Aborts with: /// * `EDestroyWhileLocked` when `is_destroy_allowed` is `false`. -/// * `tf_components::timelock::ETimelockNotExpired` when any component -/// `TimeLock` is `TimeLock::Infinite`. +/// * `iota_notarization::timelock::ETimelockNotExpired` when any `UnlockAt` +/// lock is destroyed before it expires. /// /// Emits a `NotarizationDestroyed` event on success. public fun destroy(self: Notarization, clock: &Clock) { @@ -563,15 +562,10 @@ public fun is_transfer_locked(self: &Notarization, cl /// Checks whether `self` is currently eligible for destruction. /// /// The result depends on the Notarization Method: -/// * `Dynamic`: returns `false` when an `UnlockAt`/`UnlockAtMs` +/// * `Dynamic`: returns `false` when an `UnlockAt` /// `transfer_lock` has not yet expired, and `true` otherwise. /// * `Locked`: returns `true` only when none of `update_lock`, `delete_lock`, -/// or `transfer_lock` is currently an unexpired `UnlockAt`/`UnlockAtMs` -/// lock. -/// -/// `TimeLock::Infinite` is treated as not currently `UnlockAt` for this -/// check but will still abort `destroy`, because such locks are not -/// destructible. +/// or `transfer_lock` is currently an unexpired `UnlockAt` lock. public fun is_destroy_allowed(self: &Notarization, clock: &Clock): bool { if (self.method.is_dynamic()) { !option::is_some_and!( diff --git a/notarization-move/sources/timelock.move b/notarization-move/sources/timelock.move new file mode 100644 index 00000000..e287228d --- /dev/null +++ b/notarization-move/sources/timelock.move @@ -0,0 +1,127 @@ +// Copyright (c) 2025 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +/// # Timelock Unlock Condition Module +/// +/// This module implements a timelock mechanism that restricts access to resources +/// until a specified time has passed. It provides functionality to create and validate +/// different types of time-based locks: +/// +/// - Simple time locks that unlock at a specific Unix timestamp +/// - UntilDestroyed lock that never unlocks until the notarization is destroyed +/// - None lock that is not locked +module iota_notarization::timelock; + +use iota::clock::{Self, Clock}; + +// ===== Errors ===== +/// Error when attempting to create a timelock with a timestamp in the past +const EPastTimestamp: u64 = 0; +/// Error when attempting to destroy a timelock that is still locked +const ETimelockNotExpired: u64 = 1; + +/// Represents different types of time-based locks that can be applied to +/// notarizations. +public enum TimeLock has store { + /// A lock that unlocks at a specific Unix timestamp (seconds since epoch) + UnlockAt(u32), + /// A permanent lock that never unlocks until the notarization object is destroyed (can't be used for `delete_lock`) + UntilDestroyed, + /// No lock applied + None, +} + +/// Creates a new time lock that unlocks at a specific Unix timestamp. +public fun unlock_at(unix_time: u32, clock: &Clock): TimeLock { + let now = (clock::timestamp_ms(clock) / 1000) as u32; + + assert!(is_valid_period(unix_time, now), EPastTimestamp); + + TimeLock::UnlockAt(unix_time) +} + +/// Creates a new UntilDestroyed lock that never unlocks until the notarization object is destroyed. +public fun until_destroyed(): TimeLock { + TimeLock::UntilDestroyed +} + +/// Create a new lock that is not locked. +public fun none(): TimeLock { + TimeLock::None +} + +/// Checks if the provided lock time is an UntilDestroyed lock. +public fun is_until_destroyed(lock_time: &TimeLock): bool { + match (lock_time) { + TimeLock::UntilDestroyed => true, + _ => false, + } +} + +/// Checks if the provided lock time is a UnlockAt lock. +public fun is_unlock_at(lock_time: &TimeLock): bool { + match (lock_time) { + TimeLock::UnlockAt(_) => true, + _ => false, + } +} + +/// Checks if the provided lock time is a None lock. +public fun is_none(lock_time: &TimeLock): bool { + match (lock_time) { + TimeLock::None => true, + _ => false, + } +} + +/// Gets the unlock time from a TimeLock if it is a UnixTime lock. +public fun get_unlock_time(lock_time: &TimeLock): Option { + match (lock_time) { + TimeLock::UnlockAt(time) => option::some(*time), + _ => option::none(), + } +} + +/// Destroys a TimeLock if it's either unlocked or an UntilDestroyed lock. +public fun destroy(condition: TimeLock, clock: &Clock) { + // The TimeLock is always destroyed, except of those cases where an assertion is raised + match (condition) { + TimeLock::UnlockAt(time) => { + assert!(!(time > ((clock::timestamp_ms(clock) / 1000) as u32)), ETimelockNotExpired); + }, + TimeLock::UntilDestroyed => {}, + TimeLock::None => {}, + } +} + +/// Checks if a timelock condition is currently active (locked). +/// +/// This function evaluates whether a given TimeLock instance is currently in a locked state +/// by comparing the current time with the lock's parameters. A lock is considered active if: +/// 1. For UnixTime locks: The current time hasn't reached the specified unlock time yet +/// 2. For UntilDestroyed: Always returns true as these locks never unlock until the notarization is destroyed +/// 3. For None: Always returns false as there is no lock +public fun is_timelocked(condition: &TimeLock, clock: &Clock): bool { + match (condition) { + TimeLock::UnlockAt(unix_time) => { + *unix_time > ((clock::timestamp_ms(clock) / 1000) as u32) + }, + TimeLock::UntilDestroyed => true, + TimeLock::None => false, + } +} + +/// Check if a timelock condition is `UnlockAt` +public fun is_timelocked_unlock_at(lock_time: &TimeLock, clock: &Clock): bool { + match (lock_time) { + TimeLock::UnlockAt(time) => { + *time > ((clock::timestamp_ms(clock) / 1000) as u32) + }, + _ => false, + } +} + +/// Validates that a specified unlock time is in the future. +public fun is_valid_period(unix_time: u32, current_time: u32): bool { + unix_time > current_time +} diff --git a/notarization-move/tests/dynamic_notarization_tests.move b/notarization-move/tests/dynamic_notarization_tests.move index 0077bbde..6c929873 100644 --- a/notarization-move/tests/dynamic_notarization_tests.move +++ b/notarization-move/tests/dynamic_notarization_tests.move @@ -6,9 +6,8 @@ module iota_notarization::dynamic_notarization_tests; use iota::{clock, test_scenario::{Self as ts, ctx}}; -use iota_notarization::{dynamic_notarization, notarization}; +use iota_notarization::{dynamic_notarization, notarization, timelock}; use std::string; -use tf_components::timelock; const ADMIN_ADDRESS: address = @0x01; const RECIPIENT_ADDRESS: address = @0x02; diff --git a/notarization-move/tests/locked_notarization_tests.move b/notarization-move/tests/locked_notarization_tests.move index 8f04965d..43a05780 100644 --- a/notarization-move/tests/locked_notarization_tests.move +++ b/notarization-move/tests/locked_notarization_tests.move @@ -6,9 +6,8 @@ module iota_notarization::locked_notarization_tests; use iota::{clock, test_scenario as ts}; -use iota_notarization::{locked_notarization, notarization}; +use iota_notarization::{locked_notarization, notarization, timelock}; use std::string; -use tf_components::timelock; const ADMIN_ADDRESS: address = @0x1; diff --git a/notarization-move/tests/notarization_tests.move b/notarization-move/tests/notarization_tests.move index dc317470..17e37167 100644 --- a/notarization-move/tests/notarization_tests.move +++ b/notarization-move/tests/notarization_tests.move @@ -6,9 +6,8 @@ module iota_notarization::notarization_tests; use iota::{clock, test_scenario as ts}; -use iota_notarization::notarization; +use iota_notarization::{notarization, timelock}; use std::string; -use tf_components::timelock; const ADMIN_ADDRESS: address = @0x1; diff --git a/notarization-move/tests/timelock_tests.move b/notarization-move/tests/timelock_tests.move new file mode 100644 index 00000000..68aa0f13 --- /dev/null +++ b/notarization-move/tests/timelock_tests.move @@ -0,0 +1,202 @@ +// Copyright (c) 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +/// This module provides tests for the timelock module +#[test_only] +module iota_notarization::timelock_tests; + +use iota::{clock, test_scenario::{Self as ts, ctx}}; +use iota_notarization::timelock; + +const ADMIN_ADDRESS: address = @0x01; + +#[test] +public fun test_new_unlock_at() { + let mut ts = ts::begin(ADMIN_ADDRESS); + + let ctx = ts.ctx(); + + let mut clock = clock::create_for_testing(ctx); + clock::set_for_testing(&mut clock, 1000000); + + let lock = timelock::unlock_at(1001, &clock); + + assert!(timelock::is_unlock_at(&lock)); + assert!(timelock::get_unlock_time(&lock) == std::option::some(1001)); + assert!(timelock::is_timelocked(&lock, &clock)); + + // Advance time by setting a new timestamp + clock::increment_for_testing(&mut clock, 1000); + + assert!(!timelock::is_timelocked(&lock, &clock)); + + timelock::destroy(lock, &clock); + clock::destroy_for_testing(clock); + + ts.end(); +} + +#[test] +#[expected_failure(abort_code = timelock::EPastTimestamp)] +public fun test_new_unlock_at_past_time() { + let mut ts = ts::begin(ADMIN_ADDRESS); + let ctx = ts.ctx(); + + let mut clock = clock::create_for_testing(ctx); + clock::set_for_testing(&mut clock, 1000000); + + // Try to create a timelock with a timestamp in the past + let lock = timelock::unlock_at(999, &clock); + + // This should never be reached + timelock::destroy(lock, &clock); + clock::destroy_for_testing(clock); + + ts.end(); +} + +#[test] +public fun test_until_destroyed() { + let mut ts = ts::begin(ADMIN_ADDRESS); + let ctx = ts.ctx(); + + let mut clock = clock::create_for_testing(ctx); + clock::set_for_testing(&mut clock, 1000000); + + let lock = timelock::until_destroyed(); + + assert!(timelock::is_until_destroyed(&lock)); + assert!(!timelock::is_unlock_at(&lock)); + assert!(timelock::get_unlock_time(&lock) == std::option::none()); + + // UntilDestroyed is always timelocked + assert!(timelock::is_timelocked(&lock, &clock)); + + // Even after a long time + clock::increment_for_testing(&mut clock, 1000000); + assert!(timelock::is_timelocked(&lock, &clock)); + + // UntilDestroyed can always be destroyed without error + timelock::destroy(lock, &clock); + clock::destroy_for_testing(clock); + + ts.end(); +} + +#[test] +public fun test_none_lock() { + let mut ts = ts::begin(ADMIN_ADDRESS); + let ctx = ts.ctx(); + + let mut clock = clock::create_for_testing(ctx); + clock::set_for_testing(&mut clock, 1000000); + + let lock = timelock::none(); + + assert!(!timelock::is_until_destroyed(&lock)); + assert!(!timelock::is_unlock_at(&lock)); + assert!(timelock::get_unlock_time(&lock) == std::option::none()); + + // None is never timelocked + assert!(!timelock::is_timelocked(&lock, &clock)); + + // None can always be destroyed without error + timelock::destroy(lock, &clock); + clock::destroy_for_testing(clock); + + ts.end(); +} + +#[test] +#[expected_failure(abort_code = timelock::ETimelockNotExpired)] +public fun test_destroy_locked_timelock() { + let mut ts = ts::begin(ADMIN_ADDRESS); + let ctx = ts.ctx(); + + let mut clock = clock::create_for_testing(ctx); + clock::set_for_testing(&mut clock, 1000000); + + // Create a timelock that unlocks at time 2000 + let lock = timelock::unlock_at(2000, &clock); + + // Try to destroy it before it's unlocked + // This should fail with ETimelockNotExpired + timelock::destroy(lock, &clock); + + // These should never be reached + clock::destroy_for_testing(clock); + ts.end(); +} + +#[test] +public fun test_is_timelocked_unlock_at() { + let mut ts = ts::begin(ADMIN_ADDRESS); + let ctx = ts.ctx(); + + let mut clock = clock::create_for_testing(ctx); + clock::set_for_testing(&mut clock, 1000000); + + // Create different types of locks + let unlock_at_lock = timelock::unlock_at(2000, &clock); + let until_destroyed_lock = timelock::until_destroyed(); + let none_lock = timelock::none(); + + // Test is_timelocked_unlock_at + assert!(timelock::is_timelocked_unlock_at(&unlock_at_lock, &clock)); + assert!(!timelock::is_timelocked_unlock_at(&until_destroyed_lock, &clock)); + assert!(!timelock::is_timelocked_unlock_at(&none_lock, &clock)); + + // Advance time past unlock time + clock::increment_for_testing(&mut clock, 1000000); + + // Now the unlock_at lock should not be timelocked + assert!(!timelock::is_timelocked_unlock_at(&unlock_at_lock, &clock)); + + // Clean up + timelock::destroy(unlock_at_lock, &clock); + timelock::destroy(until_destroyed_lock, &clock); + timelock::destroy(none_lock, &clock); + clock::destroy_for_testing(clock); + + ts.end(); +} + +#[test] +public fun test_is_valid_period() { + // Test valid periods + assert!(timelock::is_valid_period(1001, 1000)); + assert!(timelock::is_valid_period(2000, 1000)); + + // Test invalid periods + assert!(!timelock::is_valid_period(1000, 1000)); // Equal time + assert!(!timelock::is_valid_period(999, 1000)); // Past time +} + +#[test] +public fun test_edge_cases() { + let mut ts = ts::begin(ADMIN_ADDRESS); + let ctx = ts.ctx(); + + let mut clock = clock::create_for_testing(ctx); + clock::set_for_testing(&mut clock, 1000000); + + // Test with time just one second in the future + let one_second_future = timelock::unlock_at(1001, &clock); + assert!(timelock::is_timelocked(&one_second_future, &clock)); + clock::set_for_testing(&mut clock, 1001000); + assert!(!timelock::is_timelocked(&one_second_future, &clock)); + + // Test with time exactly at the current time boundary + clock::set_for_testing(&mut clock, 2000000); + let exact_current_time = timelock::unlock_at(2001, &clock); + assert!(timelock::is_timelocked(&exact_current_time, &clock)); + clock::set_for_testing(&mut clock, 2001000); + assert!(!timelock::is_timelocked(&exact_current_time, &clock)); + + // Clean up + timelock::destroy(one_second_future, &clock); + timelock::destroy(exact_current_time, &clock); + clock::destroy_for_testing(clock); + + ts.end(); +} From 2a23aa19a420eb5eaf07b3924674a31967a2c003 Mon Sep 17 00:00:00 2001 From: Yasir Date: Wed, 3 Jun 2026 11:58:49 +0300 Subject: [PATCH 2/5] refactor: remove unused dependencies from Move.lock and update Iota dependency in Move.toml --- notarization-move/Move.lock | 25 ++----------------------- notarization-move/Move.toml | 1 + 2 files changed, 3 insertions(+), 23 deletions(-) diff --git a/notarization-move/Move.lock b/notarization-move/Move.lock index 5bc04d6e..c4df2d76 100644 --- a/notarization-move/Move.lock +++ b/notarization-move/Move.lock @@ -2,13 +2,10 @@ [move] version = 3 -manifest_digest = "E8F9EAB938F4F4898CB27E88DD059EEA0544D15A08AC9AFC6A0E81D4F3030DAC" -deps_digest = "397E6A9F7A624706DBDFEE056CE88391A15876868FD18A88504DA74EB458D697" +manifest_digest = "F6A54EEF62E080B67E4ACC8AFED64941661C020EFF00A8D68A404001EC85DD35" +deps_digest = "F8BBB0CCB2491CA29A3DF03D6F92277A4F3574266507ACD77214D37ECA3F3082" dependencies = [ { id = "Iota", name = "Iota" }, - { id = "IotaSystem", name = "IotaSystem" }, - { id = "MoveStdlib", name = "MoveStdlib" }, - { id = "Stardust", name = "Stardust" }, ] [[move.package]] @@ -19,28 +16,10 @@ dependencies = [ { id = "MoveStdlib", name = "MoveStdlib" }, ] -[[move.package]] -id = "IotaSystem" -source = { git = "https://github.com/iotaledger/iota.git", rev = "e694e2ee8f2f9f0b9b03b843a24ff0f7bcff2930", subdir = "crates/iota-framework/packages/iota-system" } - -dependencies = [ - { id = "Iota", name = "Iota" }, - { id = "MoveStdlib", name = "MoveStdlib" }, -] - [[move.package]] id = "MoveStdlib" source = { git = "https://github.com/iotaledger/iota.git", rev = "e694e2ee8f2f9f0b9b03b843a24ff0f7bcff2930", subdir = "crates/iota-framework/packages/move-stdlib" } -[[move.package]] -id = "Stardust" -source = { git = "https://github.com/iotaledger/iota.git", rev = "e694e2ee8f2f9f0b9b03b843a24ff0f7bcff2930", subdir = "crates/iota-framework/packages/stardust" } - -dependencies = [ - { id = "Iota", name = "Iota" }, - { id = "MoveStdlib", name = "MoveStdlib" }, -] - [move.toolchain-version] compiler-version = "1.16.2-rc" edition = "2024.beta" diff --git a/notarization-move/Move.toml b/notarization-move/Move.toml index 93671c18..bb87dba8 100644 --- a/notarization-move/Move.toml +++ b/notarization-move/Move.toml @@ -6,6 +6,7 @@ name = "IotaNotarization" edition = "2024.beta" [dependencies] +Iota = { git = "https://github.com/iotaledger/iota.git", subdir = "crates/iota-framework/packages/iota-framework", rev = "e694e2ee8f2f9f0b9b03b843a24ff0f7bcff2930" } [addresses] iota_notarization = "0x0" From bac59b7cc47bb49c6d53eb2c1ec31bcbc6a6d0f1 Mon Sep 17 00:00:00 2001 From: Yasir Date: Wed, 3 Jun 2026 12:37:15 +0300 Subject: [PATCH 3/5] refactor: clean up import statements in notarization module --- notarization-move/sources/notarization.move | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/notarization-move/sources/notarization.move b/notarization-move/sources/notarization.move index 6643f0ef..29ffb970 100644 --- a/notarization-move/sources/notarization.move +++ b/notarization-move/sources/notarization.move @@ -10,7 +10,7 @@ module iota_notarization::notarization; use iota::{clock::{Self, Clock}, event}; use iota_notarization::{ method::{NotarizationMethod, new_dynamic, new_locked}, - timelock::{Self, TimeLock}, + timelock::{Self, TimeLock} }; use std::string::String; From d8fa4afcc5caef3dadd124418354b930a2cb6613 Mon Sep 17 00:00:00 2001 From: Yasir Date: Wed, 3 Jun 2026 14:28:05 +0300 Subject: [PATCH 4/5] refactor: remove TfComponents references and update TimeLock documentation --- .../src/wasm_notarization.rs | 4 +- .../src/wasm_notarization_client.rs | 12 +---- .../src/wasm_notarization_client_read_only.rs | 7 --- .../notarization_wasm/src/wasm_time_lock.rs | 33 ++---------- notarization-move/Move.history.json | 10 ++-- notarization-rs/src/client/full_client.rs | 5 +- notarization-rs/src/client/read_only.rs | 4 -- .../src/core/transactions/destroy.rs | 11 ++-- notarization-rs/src/core/types/timelock.rs | 54 +++---------------- 9 files changed, 23 insertions(+), 117 deletions(-) diff --git a/bindings/wasm/notarization_wasm/src/wasm_notarization.rs b/bindings/wasm/notarization_wasm/src/wasm_notarization.rs index f2f1fdef..6c57e6bd 100644 --- a/bindings/wasm/notarization_wasm/src/wasm_notarization.rs +++ b/bindings/wasm/notarization_wasm/src/wasm_notarization.rs @@ -447,10 +447,8 @@ impl WasmUpdateMetadata { /// @remarks /// The notarization must currently be destroy-allowed (see /// {@link NotarizationClientReadOnly.isDestroyAllowed}); otherwise the -/// on-chain transaction aborts. All component {@link TimeLock}s of the +/// on-chain transaction aborts. All package-local {@link TimeLock}s of the /// attached {@link LockMetadata} are destroyed in the process. -/// A {@link TimeLockType.Infinite} lock is not -/// destructible and therefore always blocks destruction. /// /// Emits a `NotarizationDestroyed` event on success. #[wasm_bindgen(js_name = DestroyNotarization, inspectable)] diff --git a/bindings/wasm/notarization_wasm/src/wasm_notarization_client.rs b/bindings/wasm/notarization_wasm/src/wasm_notarization_client.rs index 59d54d2f..0e5f35cc 100644 --- a/bindings/wasm/notarization_wasm/src/wasm_notarization_client.rs +++ b/bindings/wasm/notarization_wasm/src/wasm_notarization_client.rs @@ -92,13 +92,6 @@ impl WasmNotarizationClient { .collect() } - /// The TF-Components package ID for the current network, when available; - /// `undefined` otherwise. - #[wasm_bindgen(js_name = tfComponentsPackageId)] - pub fn tf_components_package_id(&self) -> Option { - None - } - /// The underlying IOTA client used for ledger queries. #[wasm_bindgen(js_name = iotaClient)] pub fn iota_client(&self) -> WasmIotaClient { @@ -211,12 +204,11 @@ impl WasmNotarizationClient { /// releases its object ID. /// /// @remarks - /// All component {@link TimeLock}s of the attached {@link LockMetadata} + /// All package-local {@link TimeLock}s of the attached {@link LockMetadata} /// are destroyed in the process. The notarization must currently be /// destroy-allowed (see /// {@link NotarizationClientReadOnly.isDestroyAllowed}); otherwise the - /// on-chain transaction aborts. A {@link TimeLockType.Infinite} lock is - /// not destructible and therefore always blocks destruction. + /// on-chain transaction aborts. /// /// @param notarizationId - The notarization object's ID. /// diff --git a/bindings/wasm/notarization_wasm/src/wasm_notarization_client_read_only.rs b/bindings/wasm/notarization_wasm/src/wasm_notarization_client_read_only.rs index 1827cc7c..8fbbac51 100644 --- a/bindings/wasm/notarization_wasm/src/wasm_notarization_client_read_only.rs +++ b/bindings/wasm/notarization_wasm/src/wasm_notarization_client_read_only.rs @@ -91,13 +91,6 @@ impl WasmNotarizationClientReadOnly { .collect() } - /// The TF-Components package ID for the current network, when available; - /// `undefined` otherwise. - #[wasm_bindgen(js_name = tfComponentsPackageId)] - pub fn tf_components_package_id(&self) -> Option { - None - } - /// The underlying IOTA client used for ledger queries. #[wasm_bindgen(js_name = iotaClient)] pub fn iota_client(&self) -> WasmIotaClient { diff --git a/bindings/wasm/notarization_wasm/src/wasm_time_lock.rs b/bindings/wasm/notarization_wasm/src/wasm_time_lock.rs index 6c46ba51..9cc85df9 100644 --- a/bindings/wasm/notarization_wasm/src/wasm_time_lock.rs +++ b/bindings/wasm/notarization_wasm/src/wasm_time_lock.rs @@ -17,21 +17,16 @@ pub enum WasmTimeLockType { None = "None", /// Unlocks at a specific timestamp expressed in seconds since the Unix epoch. UnlockAt = "UnlockAt", - /// Unlocks at a specific timestamp expressed in milliseconds since the Unix epoch. - UnlockAtMs = "UnlockAtMs", /// Stays locked until the notarization is destroyed. /// Cannot be used for the `deleteLock` field of {@link LockMetadata}. UntilDestroyed = "UntilDestroyed", - /// Permanently locked — never unlocks. - Infinite = "Infinite", } /// A time-based lock applied to one of the lock fields of a notarization. /// /// @remarks /// Construct one with the static factory methods ({@link TimeLock.withUnlockAt}, -/// {@link TimeLock.withUnlockAtMs}, {@link TimeLock.withUntilDestroyed}, -/// {@link TimeLock.withInfinite}, {@link TimeLock.withNone}) and inspect it via +/// {@link TimeLock.withUntilDestroyed}, {@link TimeLock.withNone}) and inspect it via /// the {@link TimeLock.type} and {@link TimeLock.args} getters. #[wasm_bindgen(js_name = TimeLock, inspectable)] #[derive(Debug, Clone, Serialize, Deserialize)] @@ -49,16 +44,6 @@ impl WasmTimeLock { Self(TimeLock::UnlockAt(time_sec)) } - /// Creates a lock that releases at a specific timestamp in milliseconds. - /// - /// @param timeMs - Unlock time, in milliseconds since the Unix epoch. - /// - /// @returns A {@link TimeLock} of type {@link TimeLockType.UnlockAtMs}. - #[wasm_bindgen(js_name = withUnlockAtMs)] - pub fn with_unlock_at_ms(time_ms: u64) -> Self { - Self(TimeLock::UnlockAtMs(time_ms)) - } - /// Creates a lock that stays engaged until the notarization is destroyed. /// /// @remarks @@ -72,14 +57,6 @@ impl WasmTimeLock { Self(TimeLock::UntilDestroyed) } - /// Creates a permanent lock that never releases. - /// - /// @returns A {@link TimeLock} of type {@link TimeLockType.Infinite}. - #[wasm_bindgen(js_name = withInfinite)] - pub fn with_infinite() -> Self { - Self(TimeLock::Infinite) - } - /// Creates an absent lock — semantically "no restriction". /// /// @returns A {@link TimeLock} of type {@link TimeLockType.None}. @@ -93,23 +70,19 @@ impl WasmTimeLock { pub fn lock_type(&self) -> WasmTimeLockType { match &self.0 { TimeLock::UnlockAt(_) => WasmTimeLockType::UnlockAt, - TimeLock::UnlockAtMs(_) => WasmTimeLockType::UnlockAtMs, TimeLock::UntilDestroyed => WasmTimeLockType::UntilDestroyed, - TimeLock::Infinite => WasmTimeLockType::Infinite, TimeLock::None => WasmTimeLockType::None, } } /// The argument carried by the lock variant, if any. /// - /// @returns The unlock timestamp (`number`) for `UnlockAt` (seconds) and - /// `UnlockAtMs` (milliseconds); `undefined` for `None`, `UntilDestroyed`, - /// and `Infinite`. + /// @returns The unlock timestamp (`number`) for `UnlockAt` (seconds); + /// `undefined` for `None` and `UntilDestroyed`. #[wasm_bindgen(js_name = "args", getter)] pub fn args(&self) -> JsValue { match &self.0 { TimeLock::UnlockAt(u) => JsValue::from(*u), - TimeLock::UnlockAtMs(u) => JsValue::from(*u), _ => JsValue::UNDEFINED, } } diff --git a/notarization-move/Move.history.json b/notarization-move/Move.history.json index 30d7c448..66d19291 100644 --- a/notarization-move/Move.history.json +++ b/notarization-move/Move.history.json @@ -1,16 +1,16 @@ { "aliases": { "devnet": "daf90477", - "testnet": "2304aa97", - "mainnet": "6364aad5" + "mainnet": "6364aad5", + "testnet": "2304aa97" }, "envs": { - "daf90477": [ - "0x72c8433b88e6bdee0eb02a257fdebd0ec2b6c990043f35b155cb4c5cf727fdca" - ], "2304aa97": [ "0x00412bd469b7f980227c6c574090348239852e43aa07818b315854fdd8a2d25f" ], + "daf90477": [ + "0x72c8433b88e6bdee0eb02a257fdebd0ec2b6c990043f35b155cb4c5cf727fdca" + ], "6364aad5": [ "0x909ce9dcd9a5e97b7b8884fac8e018fad9dece348bf73837379b8694ff684cf3" ] diff --git a/notarization-rs/src/client/full_client.rs b/notarization-rs/src/client/full_client.rs index 09c10e1d..df092462 100644 --- a/notarization-rs/src/client/full_client.rs +++ b/notarization-rs/src/client/full_client.rs @@ -274,12 +274,11 @@ where /// Destroys a notarization permanently and releases its object ID. /// - /// All component `TimeLock`s of the attached `LockMetadata` are + /// All package-local `TimeLock`s of the attached `LockMetadata` are /// destroyed in the process. The notarization must currently be /// destroy-allowed (see /// [`NotarizationClientReadOnly::is_destroy_allowed`]); otherwise the - /// on-chain transaction aborts. A `TimeLock::Infinite` lock is not - /// destructible and therefore always blocks destruction. + /// on-chain transaction aborts. /// /// On success the on-chain transaction emits a `NotarizationDestroyed` /// event. diff --git a/notarization-rs/src/client/read_only.rs b/notarization-rs/src/client/read_only.rs index d3bfa879..22716f96 100644 --- a/notarization-rs/src/client/read_only.rs +++ b/notarization-rs/src/client/read_only.rs @@ -381,10 +381,6 @@ impl NotarizationClientReadOnly { /// * `Locked`: destruction is gated on all of `update_lock`, `delete_lock`, and `transfer_lock` — the object is /// destroy-allowed only when none of them is currently `UnlockAt`-locked. /// - /// `TimeLock::Infinite` is treated as not currently `UnlockAt` for this - /// check but will still abort `destroy`, because such locks are not - /// destructible. - /// /// # Arguments /// /// * `notarized_object_id`: The [`ObjectID`] of the notarized object. diff --git a/notarization-rs/src/core/transactions/destroy.rs b/notarization-rs/src/core/transactions/destroy.rs index 72a03b2e..043f4c87 100644 --- a/notarization-rs/src/core/transactions/destroy.rs +++ b/notarization-rs/src/core/transactions/destroy.rs @@ -8,12 +8,10 @@ //! ## Overview //! //! The destroy-notarization transaction permanently destroys a notarization -//! and releases its object ID. All component [`TimeLock`](super::super::types::TimeLock)s +//! and releases its object ID. All package-local [`TimeLock`](super::super::types::TimeLock)s //! of the attached [`LockMetadata`](super::super::types::LockMetadata) are //! destroyed in the process. The on-chain gating check `is_destroy_allowed` -//! ensures that no `UnlockAt`/`UnlockAtMs` lock is still active. -//! `TimeLock::Infinite` is not destructible and therefore always blocks -//! destruction. +//! ensures that no `UnlockAt` lock is still active. use async_trait::async_trait; use iota_interaction::OptionalSync; @@ -30,13 +28,12 @@ use crate::error::Error; /// A transaction that destroys a notarization on-chain and releases its /// object ID. /// -/// All component [`TimeLock`](super::super::types::TimeLock)s of the +/// All package-local [`TimeLock`](super::super::types::TimeLock)s of the /// attached [`LockMetadata`](super::super::types::LockMetadata) are /// destroyed in the process. The notarization must currently be /// destroy-allowed (see /// [`NotarizationClientReadOnly::is_destroy_allowed`](crate::client::NotarizationClientReadOnly::is_destroy_allowed)); -/// otherwise the on-chain transaction aborts. A `TimeLock::Infinite` lock is -/// not destructible and therefore always blocks destruction. +/// otherwise the on-chain transaction aborts. /// /// Emits a `NotarizationDestroyed` event on success. pub struct DestroyNotarization { diff --git a/notarization-rs/src/core/types/timelock.rs b/notarization-rs/src/core/types/timelock.rs index ffc7e93a..475bf553 100644 --- a/notarization-rs/src/core/types/timelock.rs +++ b/notarization-rs/src/core/types/timelock.rs @@ -8,6 +8,12 @@ //! ## Overview //! //! The time-based locks are used to restrict the access to a notarization. +//! +//! ## Types +//! +//! - `UnlockAt`: The lock is unlocked at a specific time. +//! - `UntilDestroyed`: The lock is locked until the notarization is destroyed. +//! - `None`: The lock is not applied. use std::str::FromStr; use std::time::SystemTime; @@ -58,12 +64,8 @@ pub struct LockMetadata { pub enum TimeLock { /// A lock that unlocks at a specific Unix timestamp (seconds since Unix epoch) UnlockAt(u32), - /// Same as UnlockAt (unlocks at specific timestamp) but using milliseconds since Unix epoch - UnlockAtMs(u64), /// A permanent lock that never unlocks until the locked object is destroyed (can't be used for `delete_lock`) UntilDestroyed, - /// A lock that never unlocks (permanent lock) - Infinite, /// No lock applied None, } @@ -86,32 +88,13 @@ impl TimeLock { Ok(TimeLock::UnlockAt(unlock_time_sec)) } - /// Creates a new `TimeLock::UnlockAtMs` with a specified unlock time.\ - /// - /// The unlock time is the time in milliseconds since the Unix epoch and - /// must be in the future. - pub fn new_with_ts_ms(unlock_time_ms: u64) -> Result { - if unlock_time_ms - <= SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .expect("system time is before the Unix epoch") - .as_millis() as u64 - { - return Err(Error::InvalidArgument("unlock time must be in the future".to_string())); - } - - Ok(TimeLock::UnlockAtMs(unlock_time_ms)) - } - /// Creates a new `Argument` from the `TimeLock`. /// /// To be used when creating a new `Notarization` object on the ledger. pub(in crate::core) fn to_ptb(&self, ptb: &mut Ptb, package_id: ObjectID) -> Result { match self { TimeLock::UnlockAt(unlock_time) => new_unlock_at(ptb, *unlock_time, package_id), - TimeLock::UnlockAtMs(unlock_time) => new_unlock_at_ms(ptb, *unlock_time, package_id), TimeLock::UntilDestroyed => new_until_destroyed(ptb, package_id), - TimeLock::Infinite => new_infinite(ptb, package_id), TimeLock::None => new_none(ptb, package_id), } } @@ -131,20 +114,6 @@ pub(super) fn new_unlock_at(ptb: &mut Ptb, unlock_time_sec: u32, package_id: Obj )) } -/// Creates a new `Argument` for the `unlock_at` function. -pub(super) fn new_unlock_at_ms(ptb: &mut Ptb, unlock_time_ms: u64, package_id: ObjectID) -> Result { - let clock = move_utils::get_clock_ref(ptb); - let unlock_time_ms = move_utils::ptb_pure(ptb, "unlock_time", unlock_time_ms)?; - - Ok(ptb.programmable_move_call( - package_id, - ident_str!("timelock").as_str().into(), - ident_str!("unlock_at").as_str().into(), - vec![], - vec![unlock_time_ms, clock], - )) -} - /// Creates a new `Argument` for the `until_destroyed` function. pub(super) fn new_until_destroyed(ptb: &mut Ptb, package_id: ObjectID) -> Result { Ok(ptb.programmable_move_call( @@ -156,17 +125,6 @@ pub(super) fn new_until_destroyed(ptb: &mut Ptb, package_id: ObjectID) -> Result )) } -/// Creates a new `Argument` for the `until_destroyed` function. -pub(super) fn new_infinite(ptb: &mut Ptb, package_id: ObjectID) -> Result { - Ok(ptb.programmable_move_call( - package_id, - ident_str!("timelock").as_str().into(), - ident_str!("infinite").as_str().into(), - vec![], - vec![], - )) -} - /// Creates a new `Argument` for the `none` function. pub(super) fn new_none(ptb: &mut Ptb, package_id: ObjectID) -> Result { Ok(ptb.programmable_move_call( From 70c5a65e3f38149861dbf04d9565715a5de699d2 Mon Sep 17 00:00:00 2001 From: Yasir Date: Wed, 3 Jun 2026 17:18:04 +0300 Subject: [PATCH 5/5] refactor: add tfComponentsPackageId method for compatibility with product_common --- .../notarization_wasm/src/wasm_notarization_client.rs | 9 +++++++++ .../src/wasm_notarization_client_read_only.rs | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/bindings/wasm/notarization_wasm/src/wasm_notarization_client.rs b/bindings/wasm/notarization_wasm/src/wasm_notarization_client.rs index 0e5f35cc..2b93d826 100644 --- a/bindings/wasm/notarization_wasm/src/wasm_notarization_client.rs +++ b/bindings/wasm/notarization_wasm/src/wasm_notarization_client.rs @@ -92,6 +92,15 @@ impl WasmNotarizationClient { .collect() } + /// The TF-Components package ID for product_common compatibility. + /// + /// Notarization uses the package-local `timelock` module, so this is + /// always `undefined`. + #[wasm_bindgen(js_name = tfComponentsPackageId)] + pub fn tf_components_package_id(&self) -> Option { + None + } + /// The underlying IOTA client used for ledger queries. #[wasm_bindgen(js_name = iotaClient)] pub fn iota_client(&self) -> WasmIotaClient { diff --git a/bindings/wasm/notarization_wasm/src/wasm_notarization_client_read_only.rs b/bindings/wasm/notarization_wasm/src/wasm_notarization_client_read_only.rs index 8fbbac51..3b896b78 100644 --- a/bindings/wasm/notarization_wasm/src/wasm_notarization_client_read_only.rs +++ b/bindings/wasm/notarization_wasm/src/wasm_notarization_client_read_only.rs @@ -91,6 +91,15 @@ impl WasmNotarizationClientReadOnly { .collect() } + /// The TF-Components package ID for product_common compatibility. + /// + /// Notarization uses the package-local `timelock` module, so this is + /// always `undefined`. + #[wasm_bindgen(js_name = tfComponentsPackageId)] + pub fn tf_components_package_id(&self) -> Option { + None + } + /// The underlying IOTA client used for ledger queries. #[wasm_bindgen(js_name = iotaClient)] pub fn iota_client(&self) -> WasmIotaClient {