diff --git a/audit-trail-move/Move.toml b/audit-trail-move/Move.toml index a66b870..61ee5a6 100644 --- a/audit-trail-move/Move.toml +++ b/audit-trail-move/Move.toml @@ -3,6 +3,7 @@ name = "audit_trail" edition = "2024.beta" [dependencies] +TfComponents = { git = "https://github.com/iotaledger/product-core.git", subdir = "components_move", rev = "feat/role-map" } [addresses] audit_trail = "0x0" diff --git a/audit-trail-move/sources/audit_trail.move b/audit-trail-move/sources/audit_trail.move index 74cda05..7f492db 100644 --- a/audit-trail-move/sources/audit_trail.move +++ b/audit-trail-move/sources/audit_trail.move @@ -8,15 +8,14 @@ module audit_trail::main; use audit_trail::{ - capability::Capability, locking::{Self, LockingConfig, LockingWindow, set_delete_record_lock}, permission::{Self, Permission}, record::{Self, Record}, record_correction, - role_map::{Self, RoleMap} }; use iota::{clock::{Self, Clock}, event, linked_table::{Self, LinkedTable}}; use std::string::String; +use tf_components::{capability::Capability, role_map::{Self, RoleMap}}; // ===== Errors ===== #[error] diff --git a/audit-trail-move/sources/capability.move b/audit-trail-move/sources/capability.move deleted file mode 100644 index d793a06..0000000 --- a/audit-trail-move/sources/capability.move +++ /dev/null @@ -1,203 +0,0 @@ -// Copyright (c) 2025 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -/// Role-based access control capabilities for audit trails -module audit_trail::capability; - -use iota::clock::{Self, Clock}; -use std::string::String; - -// ===== Errors ===== - -#[error] -const EValidityPeriodInconsistent: vector = - b"Validity period is inconsistent: valid_from must be before valid_until"; - -// ===== Core Structures ===== - -/// Capability granting role-based access to a managed onchain object (i.e. an audit trail) -public struct Capability has key, store { - id: UID, - /// The ID of the onchain object this capability applies to - security_vault_id: ID, - /// The role granted by this capability - /// Arbitrary string specifying a role contained in the `role_map::RoleMap` mapping - role: String, - /// For whom has this capability been issued - /// * If Some(address), the capability is bound to that specific address - /// * If None, the capability is not bound to a specific address - issued_to: Option
, - /// Optional validity period start timestamp (in seconds since Unix epoch) - /// * The specified timestamp is included in the validity period - /// * If None, the capability is valid from creation time - valid_from: Option, - /// Optional validity period end timestamp (in seconds since Unix epoch) - /// * The specified timestamp is excluded in the validity period - /// * If None, the capability does not expire - valid_until: Option, -} - -/// Create a new capability with a specific role and all available optional restrictions -/// -/// Parameters: -/// * role: The role granted by this capability -/// * security_vault_id: The ID of onchain object (i.e. an audit trail) this capability applies to -/// * issued_to: Optional address restriction; if Some(address), the capability is bound to that specific address -/// * valid_from: Optional validity period start timestamp (in seconds since Unix epoch); if Some(ts), the capability is valid from that timestamp onwards -/// * valid_until: Optional validity period end timestamp (in seconds since Unix epoch); if Some(ts), the capability is valid until that timestamp -/// * ctx: The transaction context -/// -/// Returns: The newly created Capability -/// -/// Errors: -/// * EValidityPeriodInconsistent: If both valid_from and valid_until are provided and valid_from >= valid_until -public(package) fun new_capability( - role: String, - security_vault_id: ID, - issued_to: Option
, - valid_from: Option, - valid_until: Option, - ctx: &mut TxContext, -): Capability { - if (valid_from.is_some() && valid_until.is_some()) { - let from = valid_from.borrow(); - let until = valid_until.borrow(); - assert!(*from < *until, EValidityPeriodInconsistent); - }; - Capability { - id: object::new(ctx), - role, - security_vault_id, - issued_to, - valid_from, - valid_until, - } -} - -/// Create a new unrestricted capability with a specific role -public(package) fun new_capability_without_restrictions( - role: String, - security_vault_id: ID, - ctx: &mut TxContext, -): Capability { - Capability { - id: object::new(ctx), - role, - security_vault_id, - issued_to: std::option::none(), - valid_from: std::option::none(), - valid_until: std::option::none(), - } -} - -/// Create a new capability with a specific role and validity period, valid until the given timestamp -public(package) fun new_capability_valid_until( - role: String, - security_vault_id: ID, - valid_until: u64, - ctx: &mut TxContext, -): Capability { - Capability { - id: object::new(ctx), - role, - security_vault_id, - issued_to: std::option::none(), - valid_from: std::option::none(), - valid_until: std::option::some(valid_until), - } -} - -/// Create a new capability with a specific role, exclusively usable by a specific address and an optional -/// validity period, valid until the given timestamp -public(package) fun new_capability_for_address( - role: String, - security_vault_id: ID, - issued_to: address, - valid_until: Option, - ctx: &mut TxContext, -): Capability { - Capability { - id: object::new(ctx), - role, - security_vault_id, - issued_to: std::option::some(issued_to), - valid_from: std::option::none(), - valid_until, - } -} - -/// Get the capability's ID -public fun id(cap: &Capability): ID { - object::uid_to_inner(&cap.id) -} - -/// Get the capability's role -public fun role(cap: &Capability): &String { - &cap.role -} - -/// Get the capability's security_vault_id -public fun security_vault_id(cap: &Capability): ID { - cap.security_vault_id -} - -/// Check if the capability has a specific role -public fun has_role(cap: &Capability, role: &String): bool { - &cap.role == role -} - -// Get the capability's issued_to address -public fun issued_to(cap: &Capability): &Option
{ - &cap.issued_to -} - -// Get the capability's valid_from timestamp -public fun valid_from(cap: &Capability): &Option { - &cap.valid_from -} - -// Get the capability's valid_until timestamp -public fun valid_until(cap: &Capability): &Option { - &cap.valid_until -} - -// Check if the capability is currently valid for `clock::timestamp_ms(clock)` -public fun is_currently_valid(cap: &Capability, clock: &Clock): bool { - let current_ts = clock::timestamp_ms(clock) / 1000; // convert to seconds - cap.is_valid_for_timestamp(current_ts) -} - -// Check if the capability is valid for a specific timestamp (in seconds since Unix epoch) -public fun is_valid_for_timestamp(cap: &Capability, timestamp_secs: u64): bool { - let valid_from_ok = if (cap.valid_from.is_some()) { - let from = cap.valid_from.borrow(); - timestamp_secs >= *from - } else { - true - }; - let valid_until_ok = if (cap.valid_until.is_some()) { - let until = cap.valid_until.borrow(); - timestamp_secs < *until - } else { - true - }; - valid_from_ok && valid_until_ok -} - -/// Destroy a capability -public(package) fun destroy(cap: Capability) { - let Capability { - id, - role: _role, - security_vault_id: _trail_id, - issued_to: _issued_to, - valid_from: _valid_from, - valid_until: _valid_until, - } = cap; - object::delete(id); -} - -#[test_only] -public fun destroy_for_testing(cap: Capability) { - destroy(cap); -} diff --git a/audit-trail-move/sources/role_map.move b/audit-trail-move/sources/role_map.move deleted file mode 100644 index 5416969..0000000 --- a/audit-trail-move/sources/role_map.move +++ /dev/null @@ -1,601 +0,0 @@ -// Copyright (c) 2026 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -/// A role-based access control helper mapping unique role identifiers to their associated permissions. -/// -/// Provides the following functionalities: -/// - Define an initial role with a custom set of permissions (i.e. an Admin role). -/// - Use custom permission types defined by the integrating module using the generic parameter `P`. -/// - Create, delete, and update roles and their permissions -/// - Issue, revoke, and destroy `audit_trail::capability`s associated with a specific role. -/// - Validate `audit_trail::capability`s against the defined roles to facilitate proper access control by other modules -/// (function `RoleMap.is_capability_valid()`) -/// - All functions are access restricted by custom permissions defined during `RoleMap` instantiation. -/// -/// Examples: -/// - audit_trail::main module uses `RoleMap` to manage access to the audit trail records and their operations. - -module audit_trail::role_map; - -use audit_trail::capability::{Self, Capability}; -use iota::{clock::Clock, event, vec_map::{Self, VecMap}, vec_set::{Self, VecSet}}; -use std::string::String; - -// =============== Errors ========================================================== - -#[error] -const EPermissionDenied: vector = - b"The role associated with the provided capability does not have the required permission"; -#[error] -const ERoleDoesNotExist: vector = - b"The specified role, directly specified or specified by a capability, does not exist in the `RoleMap` mapping"; -#[error] -const ECapabilityHasBeenRevoked: vector = - b"The provided capability has been revoked and is no longer valid"; -#[error] -const ECapabilitySecurityVaultIdMismatch: vector = - b"The security_vault_id associated with the provided capability does not match the security_vault_id of the `RoleMap`"; -#[error] -const ECapabilityTimeConstraintsNotMet: vector = - b"The capability's time constraints are not currently met either due to `valid_from` or `valid_until` restrictions"; -#[error] -const ECapabilityIssuedToMismatch: vector = - b"The capability is restricted to a specific address which does not match the caller's address"; -#[error] -const ECapabilityPermissionDenied: vector = - b"The role associated with provided capability does not have the required permission"; - -// =============== Events ========================================================== - -/// Emitted when a capability is issued -public struct CapabilityIssued has copy, drop { - security_vault_id: ID, - capability_id: ID, - role: String, - issued_to: Option
, - valid_from: Option, - valid_until: Option, -} - -/// Emitted when a capability is destroyed -public struct CapabilityDestroyed has copy, drop { - security_vault_id: ID, - capability_id: ID, - role: String, - issued_to: Option
, - valid_from: Option, - valid_until: Option, -} - -/// Emitted when a capability is revoked or destroyed -public struct CapabilityRevoked has copy, drop { - security_vault_id: ID, - capability_id: ID, -} - -// TODO: Add event for Role creation, removing, updating, etc. - -// =============== Core Types ====================================================== - -/// Defines the permissions required to administer roles in this RoleMap -public struct RoleAdminPermissions has copy, drop, store { - /// Permission required to add a new role - add: P, - /// Permission required to delete an existing role - delete: P, - /// Permission required to update permissions associated with an existing role - update: P, -} - -/// Defines the permissions required to administer capabilities in this RoleMap -public struct CapabilityAdminPermissions has copy, drop, store { - /// Permission required to add (issue) a new capability - add: P, - /// Permission required to revoke an existing capability - revoke: P, -} - -/// The RoleMap structure mapping role names to their associated permissions -/// Generic parameter P defines the permission type used by the integrating module -/// (i.e. audit_trail::Permission) -public struct RoleMap has copy, drop, store { - /// The ObjectID of the onchain object integrating this RoleMap - security_vault_id: ID, - /// Mapping of role names to their associated permissions - roles: VecMap>, - /// Whitelist of all issued capability IDs - issued_capabilities: VecSet, - /// Permissions required to administer roles in this RoleMap - role_admin_permissions: RoleAdminPermissions

, - /// Permissions required to administer capabilities in this RoleMap - capability_admin_permissions: CapabilityAdminPermissions

, -} - -// =============== Role & Capability AdminPermissions Functions ==================== - -public fun new_role_admin_permissions( - add: P, - delete: P, - update: P, -): RoleAdminPermissions

{ - RoleAdminPermissions { - add, - delete, - update, - } -} - -public fun new_capability_admin_permissions( - add: P, - revoke: P, -): CapabilityAdminPermissions

{ - CapabilityAdminPermissions { - add, - revoke, - } -} - -// =============== RoleMap Functions =============================================== - -/// Create a new RoleMap with an initial admin role -/// The initial admin role is created with the specified name and permissions -/// An initial admin capability is created and returned alongside the RoleMap -/// The initial admin capability has no restrictions (no address, valid_from, or valid_until) -/// The security_vault_id is associated with both the RoleMap and the initial admin capability -/// Returns the newly created RoleMap and the initial admin capability -/// -/// Parameters -/// ---------- -/// - security_vault_id: -/// The security_vault_id to associate this RoleMap with the initial admin capability -/// and all other created capabilities. Set this to the ID of the onchain object that integrates the RoleMap. -/// - initial_admin_role_name: -/// The name of the initial admin role -/// - initial_admin_role_permissions: -/// The permissions associated with the initial admin role -/// - role_admin_permissions: -/// The permissions required to administer roles in this RoleMap -/// - capability_admin_permissions: -/// The permissions required to administer capabilities in this RoleMap -/// - ctx: -/// The transaction context for capability creation -public fun new( - security_vault_id: ID, - initial_admin_role_name: String, - initial_admin_role_permissions: VecSet

, - role_admin_permissions: RoleAdminPermissions

, - capability_admin_permissions: CapabilityAdminPermissions

, - ctx: &mut TxContext, -): (RoleMap

, Capability) { - let mut roles = vec_map::empty>(); - roles.insert(initial_admin_role_name, initial_admin_role_permissions); - - let admin_cap = capability::new_capability_without_restrictions( - initial_admin_role_name, - security_vault_id, - ctx, - ); - let mut issued_capabilities = vec_set::empty(); - issued_capabilities.insert(admin_cap.id()); - let role_map = RoleMap { - roles, - role_admin_permissions, - capability_admin_permissions, - security_vault_id, - issued_capabilities, - }; - - (role_map, admin_cap) -} - -/// Get the permissions associated with a specific role. -/// Aborts with ERoleDoesNotExist if the role does not exist. -public fun get_role_permissions(role_map: &RoleMap

, role: &String): &VecSet

{ - assert!(vec_map::contains(&role_map.roles, role), ERoleDoesNotExist); - vec_map::get(&role_map.roles, role) -} - -/// Create a new role consisting of a role name and associated permissions -public fun create_role( - role_map: &mut RoleMap

, - cap: &Capability, - role: String, - permissions: VecSet

, - clock: &Clock, - ctx: &TxContext, -) { - assert!( - role_map.is_capability_valid( - cap, - &role_map.role_admin_permissions.add, - clock, - ctx, - ), - EPermissionDenied, - ); - - vec_map::insert(&mut role_map.roles, role, permissions); -} - -/// Delete an existing role -public fun delete_role( - role_map: &mut RoleMap

, - cap: &Capability, - role: &String, - clock: &Clock, - ctx: &TxContext, -) { - assert!( - role_map.is_capability_valid( - cap, - &role_map.role_admin_permissions.delete, - clock, - ctx, - ), - EPermissionDenied, - ); - - vec_map::remove(&mut role_map.roles, role); -} - -/// Update permissions associated with an existing role -public fun update_role_permissions( - role_map: &mut RoleMap

, - cap: &Capability, - role: &String, - new_permissions: VecSet

, - clock: &Clock, - ctx: &TxContext, -) { - assert!( - role_map.is_capability_valid( - cap, - &role_map.role_admin_permissions.update, - clock, - ctx, - ), - EPermissionDenied, - ); - - assert!(vec_map::contains(&role_map.roles, role), ERoleDoesNotExist); - vec_map::remove(&mut role_map.roles, role); - vec_map::insert(&mut role_map.roles, *role, new_permissions); -} - -/// Indicates if the specified role exists in the role_map -public fun has_role(role_map: &RoleMap

, role: &String): bool { - vec_map::contains(&role_map.roles, role) -} - -// =============== Capability related Functions ==================================== - -/// Indicates if a provided capability is valid. -/// -/// A capability is considered valid if: -/// - The capability's security_vault_id matches the RoleMap's security_vault_id. -/// Aborts with ECapabilitySecurityVaultIdMismatch if not matching. -/// - The role value specified by the capability exists in the `RoleMap` mapping. -/// Aborts with ERoleDoesNotExist if the role does not exist. -/// - The role associated with the capability contains the permission specified by the `permission` argument. -/// Aborts with ECapabilityPermissionDenied if the permission is not granted by the role. -/// - The capability has not been revoked (is included in the `issued_capabilities` set). -/// Aborts with ECapabilityHasBeenRevoked if revoked. -/// - The capability is currently active, based on its time restrictions (if any). -/// Aborts with ECapabilityTimeConstraintsNotMet, if the current time is outside the valid_from and valid_until range. -/// - If the capability is restricted to a specific address, the caller's address matches the sender of the transaction. -/// Aborts with ECapabilityIssuedToMismatch if the addresses do not match. -/// -/// Parameters -/// ---------- -/// - role_map: Reference to the `RoleMap` mapping. -/// - cap: Reference to the capability to be validated. -/// - permission: The permission to check against the capability's role. -/// - clock: Reference to a Clock instance for time-based validation. -/// - ctx: Reference to the transaction context for accessing the caller's address. -/// -/// Returns -/// ------- -/// - bool: true if the capability is valid, otherwise aborts with the relevant error. -public fun is_capability_valid( - role_map: &RoleMap

, - cap: &Capability, - permission: &P, - clock: &Clock, - ctx: &TxContext, -): bool { - assert!( - role_map.security_vault_id == cap.security_vault_id(), - ECapabilitySecurityVaultIdMismatch, - ); - - let permissions = role_map.get_role_permissions(cap.role()); - assert!(vec_set::contains(permissions, permission), ECapabilityPermissionDenied); - - assert!(role_map.issued_capabilities.contains(&cap.id()), ECapabilityHasBeenRevoked); - - if (cap.valid_from().is_some() || cap.valid_until().is_some()) { - assert!(cap.is_currently_valid(clock), ECapabilityTimeConstraintsNotMet); - }; - - if (cap.issued_to().is_some()) { - let caller = ctx.sender(); - let issued_to_addr = cap.issued_to().borrow(); - assert!(*issued_to_addr == caller, ECapabilityIssuedToMismatch); - }; - - true -} - -/// Create a new capability -/// -/// Parameters -/// ---------- -/// - role_map: Reference to the `RoleMap` mapping. -/// - cap: Reference to the capability used to authorize the creation of the new capability. -/// - role: The role to be assigned to the new capability. -/// - issued_to: Optional address restriction for the new capability. -/// - valid_from: Optional start time (in seconds since Unix epoch) for the new capability. -/// - valid_until: Optional end time (in seconds since Unix epoch) for the new capability. -/// - clock: Reference to a Clock instance for time-based validation. -/// - ctx: Reference to the transaction context. -/// -/// Returns the newly created capability. -/// -/// Sends a CapabilityIssued event upon successful creation. -/// -/// Errors: -/// - Aborts with EPermissionDenied if the provided capability does not have the permission specified with `CapabilityAdminPermissions::add`. -/// - Aborts with ERoleDoesNotExist if the specified role does not exist in the role_map. -/// - Aborts with audit_trail::capability::EValidityPeriodInconsistent if the provided valid_from and valid_until are inconsistent. -public fun new_capability( - role_map: &mut RoleMap

, - cap: &Capability, - role: &String, - issued_to: Option

, - valid_from: Option, - valid_until: Option, - clock: &Clock, - ctx: &mut TxContext, -): Capability { - assert!( - role_map.is_capability_valid( - cap, - &role_map.capability_admin_permissions.add, - clock, - ctx, - ), - EPermissionDenied, - ); - - assert!(role_map.roles.contains(role), ERoleDoesNotExist); - let new_cap = capability::new_capability( - *role, - role_map.security_vault_id, - issued_to, - valid_from, - valid_until, - ctx, - ); - register_new_capability(role_map, &new_cap); - new_cap -} - -/// Create a new unrestricted capability with a specific role without any -/// address, valid_from, or valid_until restrictions. -/// -/// Returns the newly created capability. -/// -/// Sends a CapabilityIssued event upon successful creation. -/// -/// Errors: -/// - Aborts with EPermissionDenied if the provided capability does not have the permission specified with `CapabilityAdminPermissions::add`. -/// - Aborts with ERoleDoesNotExist if the specified role does not exist in the role_map. -public fun new_capability_without_restrictions( - role_map: &mut RoleMap

, - cap: &Capability, - role: &String, - clock: &Clock, - ctx: &mut TxContext, -): Capability { - assert!( - role_map.is_capability_valid( - cap, - &role_map.capability_admin_permissions.add, - clock, - ctx, - ), - EPermissionDenied, - ); - - assert!(role_map.roles.contains(role), ERoleDoesNotExist); - let new_cap = capability::new_capability_without_restrictions( - *role, - role_map.security_vault_id, - ctx, - ); - - register_new_capability(role_map, &new_cap); - new_cap -} - -/// Create a new capability with a specific role that expires at a given timestamp (seconds since Unix epoch). -/// -/// Returns the newly created capability. -/// -/// Sends a CapabilityIssued event upon successful creation. -/// -/// Errors: -/// - Aborts with EPermissionDenied if the provided capability does not have the permission specified with `CapabilityAdminPermissions::add`. -/// - Aborts with ERoleDoesNotExist if the specified role does not exist in the role_map. -public fun new_capability_valid_until( - role_map: &mut RoleMap

, - cap: &Capability, - role: &String, - valid_until: u64, - clock: &Clock, - ctx: &mut TxContext, -): Capability { - assert!( - role_map.is_capability_valid( - cap, - &role_map.capability_admin_permissions.add, - clock, - ctx, - ), - EPermissionDenied, - ); - - assert!(role_map.roles.contains(role), ERoleDoesNotExist); - let new_cap = capability::new_capability_valid_until( - *role, - role_map.security_vault_id, - valid_until, - ctx, - ); - - register_new_capability(role_map, &new_cap); - new_cap -} - -/// Create a new capability with a specific role restricted to an address. -/// Optionally set an expiration time (seconds since Unix epoch). -/// -/// Returns the newly created capability. -/// -/// Sends a CapabilityIssued event upon successful creation. -/// -/// Errors: -/// - Aborts with EPermissionDenied if the provided capability does not have the permission specified with `CapabilityAdminPermissions::add`. -/// - Aborts with ERoleDoesNotExist if the specified role does not exist in the role_map. -public fun new_capability_for_address( - role_map: &mut RoleMap

, - cap: &Capability, - role: &String, - issued_to: address, - valid_until: Option, - clock: &Clock, - ctx: &mut TxContext, -): Capability { - assert!( - role_map.is_capability_valid( - cap, - &role_map.capability_admin_permissions.add, - clock, - ctx, - ), - EPermissionDenied, - ); - - assert!(role_map.roles.contains(role), ERoleDoesNotExist); - let new_cap = capability::new_capability_for_address( - *role, - role_map.security_vault_id, - issued_to, - valid_until, - ctx, - ); - - register_new_capability(role_map, &new_cap); - new_cap -} - -/// Destroy an existing capability -/// Every owner of a capability is allowed to destroy it when no longer needed. -/// -/// Sends a CapabilityDestroyed event upon successful destruction. -/// -/// TODO: Clarify if we need to restrict access with the `CapabilitiesRevoke` permission here. -/// If yes, we also need a destroy function for Admin capabilities (without the need of another Admin capability). -/// Otherwise the last Admin capability holder will block the role_map forever by not being able to destroy it. -public fun destroy_capability( - role_map: &mut RoleMap

, - cap_to_destroy: Capability, -) { - assert!( - role_map.security_vault_id == cap_to_destroy.security_vault_id(), - ECapabilitySecurityVaultIdMismatch, - ); - - if (role_map.issued_capabilities.contains(&cap_to_destroy.id())) { - // Capability has not been revoked before destroying, so let's remove it now - role_map.issued_capabilities.remove(&cap_to_destroy.id()); - }; - - event::emit(CapabilityDestroyed { - security_vault_id: role_map.security_vault_id, - capability_id: cap_to_destroy.id(), - role: *cap_to_destroy.role(), - issued_to: *cap_to_destroy.issued_to(), - valid_from: *cap_to_destroy.valid_from(), - valid_until: *cap_to_destroy.valid_until(), - }); - - cap_to_destroy.destroy(); -} - -/// Revoke an existing capability -/// -/// Sends a CapabilityRevoked event upon successful revocation. -/// -/// Errors: -/// - Aborts with EPermissionDenied if the provided capability does not have the permission specified with `CapabilityAdminPermissions::revoke`. -/// - Aborts with ERoleDoesNotExist if the specified role does not exist in the `RoleMap.issued_capabilities()` list. -public fun revoke_capability( - role_map: &mut RoleMap

, - cap: &Capability, - cap_to_revoke: ID, - clock: &Clock, - ctx: &TxContext, -) { - assert!( - role_map.is_capability_valid( - cap, - &role_map.capability_admin_permissions.revoke, - clock, - ctx, - ), - EPermissionDenied, - ); - - assert!(role_map.issued_capabilities.contains(&cap_to_revoke), ERoleDoesNotExist); - role_map.issued_capabilities.remove(&cap_to_revoke); - - event::emit(CapabilityRevoked { - security_vault_id: role_map.security_vault_id, - capability_id: cap_to_revoke, - }); -} - -fun register_new_capability(role_map: &mut RoleMap

, new_cap: &Capability) { - role_map.issued_capabilities.insert(new_cap.id()); - - event::emit(CapabilityIssued { - security_vault_id: role_map.security_vault_id, - capability_id: new_cap.id(), - role: *new_cap.role(), - issued_to: *new_cap.issued_to(), - valid_from: *new_cap.valid_from(), - valid_until: *new_cap.valid_until(), - }); -} - -// =============== Getter Functions ================================================ - -/// Returns the size of the role_map, the number of managed roles -public fun size(role_map: &RoleMap

): u64 { - vec_map::size(&role_map.roles) -} - -/// Returns the security_vault_id associated with the role_map -public fun security_vault_id(role_map: &RoleMap

): ID { - role_map.security_vault_id -} - -//Returns the role admin permissions associated with the role_map -public fun role_admin_permissions(role_map: &RoleMap

): &RoleAdminPermissions

{ - &role_map.role_admin_permissions -} - -public fun issued_capabilities(role_map: &RoleMap

): &VecSet { - &role_map.issued_capabilities -} diff --git a/audit-trail-move/tests/capability_tests.move b/audit-trail-move/tests/capability_tests.move index d4bc27c..03a8df0 100644 --- a/audit-trail-move/tests/capability_tests.move +++ b/audit-trail-move/tests/capability_tests.move @@ -3,7 +3,6 @@ module audit_trail::capability_tests; use audit_trail::{ - capability::Capability, locking, main::AuditTrail, permission, @@ -17,6 +16,7 @@ use audit_trail::{ }; use iota::test_scenario::{Self as ts, Scenario}; use std::string; +use tf_components::capability::Capability; /// Helper function to setup an audit trail with a RecordAdmin role and a capability /// with a time window restriction transferred to the record_user. @@ -25,8 +25,8 @@ fun setup_trail_with_record_admin_capability_and_time_window_restriction( scenario: &mut Scenario, admin_user: address, record_user: address, - valid_from_secs: u64, - valid_until_secs: u64, + valid_from_ms: u64, + valid_until_ms: u64, ): ID { // Setup let trail_id = setup_trail_with_record_admin_role(scenario, admin_user); @@ -42,16 +42,16 @@ fun setup_trail_with_record_admin_capability_and_time_window_restriction( &admin_cap, &string::utf8(b"RecordAdmin"), std::option::none(), // no address restriction - std::option::some(valid_from_secs), - std::option::some(valid_until_secs), + std::option::some(valid_from_ms), + std::option::some(valid_until_ms), &clock, ts::ctx(scenario), ); // Verify capability properties assert!(cap.issued_to().is_none(), 0); - assert!(cap.valid_from() == std::option::some(valid_from_secs), 1); - assert!(cap.valid_until() == std::option::some(valid_until_secs), 2); + assert!(cap.valid_from() == std::option::some(valid_from_ms), 1); + assert!(cap.valid_until() == std::option::some(valid_until_ms), 2); transfer::public_transfer(cap, record_user); cleanup_capability_trail_and_clock(scenario, admin_cap, trail, clock); @@ -149,17 +149,15 @@ fun test_new_capability() { let initial_cap_count = trail.roles().issued_capabilities().size(); assert!(initial_cap_count == 1, 0); // Only admin cap - let cap1 = trail - .roles_mut() - .new_capability_without_restrictions( - &admin_cap, - &string::utf8(b"RecordAdmin"), - &clock, - ts::ctx(&mut scenario), - ); - + let cap1 = test_utils::new_capability_without_restrictions( + trail.roles_mut(), + &admin_cap, + &string::utf8(b"RecordAdmin"), + &clock, + ts::ctx(&mut scenario), + ); assert!(cap1.role() == string::utf8(b"RecordAdmin"), 1); - assert!(cap1.security_vault_id() == trail_id, 2); + assert!(cap1.target_key() == trail_id, 2); let cap1_id = object::id(&cap1); @@ -180,14 +178,13 @@ fun test_new_capability() { let previous_cap_count = trail.roles().issued_capabilities().size(); - let cap2 = trail - .roles_mut() - .new_capability_without_restrictions( - &admin_cap, - &string::utf8(b"RecordAdmin"), - &clock, - ts::ctx(&mut scenario), - ); + let cap2 = test_utils::new_capability_without_restrictions( + trail.roles_mut(), + &admin_cap, + &string::utf8(b"RecordAdmin"), + &clock, + ts::ctx(&mut scenario), + ); let cap2_id = object::id(&cap2); @@ -223,25 +220,23 @@ fun test_revoke_capability() { let (cap1_id, cap2_id) = { let (admin_cap, mut trail, clock) = fetch_capability_trail_and_clock(&mut scenario); - let cap1 = trail - .roles_mut() - .new_capability_without_restrictions( - &admin_cap, - &string::utf8(b"RecordAdmin"), - &clock, - ts::ctx(&mut scenario), - ); + let cap1 = test_utils::new_capability_without_restrictions( + trail.roles_mut(), + &admin_cap, + &string::utf8(b"RecordAdmin"), + &clock, + ts::ctx(&mut scenario), + ); let cap1_id = object::id(&cap1); transfer::public_transfer(cap1, user1); - let cap2 = trail - .roles_mut() - .new_capability_without_restrictions( - &admin_cap, - &string::utf8(b"RecordAdmin"), - &clock, - ts::ctx(&mut scenario), - ); + let cap2 = test_utils::new_capability_without_restrictions( + trail.roles_mut(), + &admin_cap, + &string::utf8(b"RecordAdmin"), + &clock, + ts::ctx(&mut scenario), + ); let cap2_id = object::id(&cap2); transfer::public_transfer(cap2, user2); @@ -330,25 +325,23 @@ fun test_destroy_capability() { let (cap1_id, cap2_id) = { let (admin_cap, mut trail, clock) = fetch_capability_trail_and_clock(&mut scenario); - let cap1 = trail - .roles_mut() - .new_capability_without_restrictions( - &admin_cap, - &string::utf8(b"RecordAdmin"), - &clock, - ts::ctx(&mut scenario), - ); + let cap1 = test_utils::new_capability_without_restrictions( + trail.roles_mut(), + &admin_cap, + &string::utf8(b"RecordAdmin"), + &clock, + ts::ctx(&mut scenario), + ); let cap1_id = object::id(&cap1); transfer::public_transfer(cap1, user1); - let cap2 = trail - .roles_mut() - .new_capability_without_restrictions( - &admin_cap, - &string::utf8(b"RecordAdmin"), - &clock, - ts::ctx(&mut scenario), - ); + let cap2 = test_utils::new_capability_without_restrictions( + trail.roles_mut(), + &admin_cap, + &string::utf8(b"RecordAdmin"), + &clock, + ts::ctx(&mut scenario), + ); let cap2_id = object::id(&cap2); transfer::public_transfer(cap2, user2); @@ -462,25 +455,23 @@ fun test_capability_lifecycle() { let (record_cap_id, role_cap_id) = { let (admin_cap, mut trail, clock) = fetch_capability_trail_and_clock(&mut scenario); - let record_cap = trail - .roles_mut() - .new_capability_without_restrictions( - &admin_cap, - &string::utf8(b"RecordAdmin"), - &clock, - ts::ctx(&mut scenario), - ); + let record_cap = test_utils::new_capability_without_restrictions( + trail.roles_mut(), + &admin_cap, + &string::utf8(b"RecordAdmin"), + &clock, + ts::ctx(&mut scenario), + ); let record_cap_id = object::id(&record_cap); transfer::public_transfer(record_cap, record_admin_user); - let role_cap = trail - .roles_mut() - .new_capability_without_restrictions( - &admin_cap, - &string::utf8(b"RoleAdmin"), - &clock, - ts::ctx(&mut scenario), - ); + let role_cap = test_utils::new_capability_without_restrictions( + trail.roles_mut(), + &admin_cap, + &string::utf8(b"RoleAdmin"), + &clock, + ts::ctx(&mut scenario), + ); let role_cap_id = object::id(&role_cap); transfer::public_transfer(role_cap, role_admin_user); @@ -570,16 +561,15 @@ fun test_capability_issued_to_only() { { let (admin_cap, mut trail, clock) = fetch_capability_trail_and_clock(&mut scenario); - let cap = trail - .roles_mut() - .new_capability_for_address( - &admin_cap, - &string::utf8(b"RecordAdmin"), - authorized_user, - std::option::none(), // no time restriction - &clock, - ts::ctx(&mut scenario), - ); + let cap = test_utils::new_capability_for_address( + trail.roles_mut(), + &admin_cap, + &string::utf8(b"RecordAdmin"), + authorized_user, + std::option::none(), // no time restriction + &clock, + ts::ctx(&mut scenario), + ); // Verify capability properties assert!(cap.issued_to() == std::option::some(authorized_user), 0); @@ -668,14 +658,13 @@ fun test_revoked_capability_cannot_be_used() { ts::ctx(&mut scenario), ); - let user_cap = trail - .roles_mut() - .new_capability_without_restrictions( - &admin_cap, - &string::utf8(b"RecordAdmin"), - &clock, - ts::ctx(&mut scenario), - ); + let user_cap = test_utils::new_capability_without_restrictions( + trail.roles_mut(), + &admin_cap, + &string::utf8(b"RecordAdmin"), + &clock, + ts::ctx(&mut scenario), + ); transfer::public_transfer(user_cap, user); cleanup_capability_trail_and_clock(&scenario, admin_cap, trail, clock); @@ -737,14 +726,13 @@ fun test_new_capability_for_nonexistent_role() { { let (admin_cap, mut trail, clock) = fetch_capability_trail_and_clock(&mut scenario); - let bad_cap = trail - .roles_mut() - .new_capability_without_restrictions( - &admin_cap, - &string::utf8(b"NonExistentRole"), - &clock, - ts::ctx(&mut scenario), - ); + let bad_cap = test_utils::new_capability_without_restrictions( + trail.roles_mut(), + &admin_cap, + &string::utf8(b"NonExistentRole"), + &clock, + ts::ctx(&mut scenario), + ); bad_cap.destroy_for_testing(); cleanup_capability_trail_and_clock(&scenario, admin_cap, trail, clock); @@ -798,23 +786,21 @@ fun test_revoke_capability_permission_denied() { ts::ctx(&mut scenario), ); - let user1_cap = trail - .roles_mut() - .new_capability_without_restrictions( - &admin_cap, - &string::utf8(b"NoRevokePerm"), - &clock, - ts::ctx(&mut scenario), - ); + let user1_cap = test_utils::new_capability_without_restrictions( + trail.roles_mut(), + &admin_cap, + &string::utf8(b"NoRevokePerm"), + &clock, + ts::ctx(&mut scenario), + ); - let user2_cap = trail - .roles_mut() - .new_capability_without_restrictions( - &admin_cap, - &string::utf8(b"RecordAdmin"), - &clock, - ts::ctx(&mut scenario), - ); + let user2_cap = test_utils::new_capability_without_restrictions( + trail.roles_mut(), + &admin_cap, + &string::utf8(b"RecordAdmin"), + &clock, + ts::ctx(&mut scenario), + ); transfer::public_transfer(user1_cap, user1); transfer::public_transfer(user2_cap, user2); @@ -886,14 +872,13 @@ fun test_new_capability_permission_denied() { ts::ctx(&mut scenario), ); - let user_cap = trail - .roles_mut() - .new_capability_without_restrictions( - &admin_cap, - &string::utf8(b"NoCapPerm"), - &clock, - ts::ctx(&mut scenario), - ); + let user_cap = test_utils::new_capability_without_restrictions( + trail.roles_mut(), + &admin_cap, + &string::utf8(b"NoCapPerm"), + &clock, + ts::ctx(&mut scenario), + ); transfer::public_transfer(user_cap, user); cleanup_capability_trail_and_clock(&scenario, admin_cap, trail, clock); @@ -904,14 +889,13 @@ fun test_new_capability_permission_denied() { { let (user_cap, mut trail, clock) = fetch_capability_trail_and_clock(&mut scenario); - let new_cap = trail - .roles_mut() - .new_capability_without_restrictions( - &user_cap, - &string::utf8(b"RecordAdmin"), - &clock, - ts::ctx(&mut scenario), - ); + let new_cap = test_utils::new_capability_without_restrictions( + trail.roles_mut(), + &user_cap, + &string::utf8(b"RecordAdmin"), + &clock, + ts::ctx(&mut scenario), + ); new_cap.destroy_for_testing(); cleanup_capability_trail_and_clock(&scenario, user_cap, trail, clock); @@ -1017,7 +1001,7 @@ fun test_capability_valid_until_only() { let mut scenario = ts::begin(admin_user); - let valid_until_time_secs = test_utils::initial_time_for_testing() / 1000 + 10; + let valid_until_time_ms = test_utils::initial_time_for_testing() + 10000; // Setup let _trail_id = setup_trail_with_record_admin_role(&mut scenario, admin_user); @@ -1027,20 +1011,19 @@ fun test_capability_valid_until_only() { { let (admin_cap, mut trail, clock) = fetch_capability_trail_and_clock(&mut scenario); - let cap = trail - .roles_mut() - .new_capability_valid_until( - &admin_cap, - &string::utf8(b"RecordAdmin"), - valid_until_time_secs, - &clock, - ts::ctx(&mut scenario), - ); + let cap = test_utils::new_capability_valid_until( + trail.roles_mut(), + &admin_cap, + &string::utf8(b"RecordAdmin"), + valid_until_time_ms, + &clock, + ts::ctx(&mut scenario), + ); // Verify capability properties assert!(cap.issued_to().is_none(), 0); assert!(cap.valid_from().is_none(), 1); - assert!(cap.valid_until() == std::option::some(valid_until_time_secs), 2); + assert!(cap.valid_until() == std::option::some(valid_until_time_ms), 2); transfer::public_transfer(cap, user); cleanup_capability_trail_and_clock(&scenario, admin_cap, trail, clock); @@ -1050,7 +1033,7 @@ fun test_capability_valid_until_only() { ts::next_tx(&mut scenario, user); { let (cap, mut trail, mut clock) = fetch_capability_trail_and_clock(&mut scenario); - clock.set_for_testing(valid_until_time_secs* 1000 - 1000); + clock.set_for_testing(valid_until_time_ms - 1000000); let test_data = test_utils::new_test_data(1, b"Test record before valid_until"); trail.add_record( @@ -1068,7 +1051,7 @@ fun test_capability_valid_until_only() { ts::next_tx(&mut scenario, user); { let (cap, mut trail, mut clock) = fetch_capability_trail_and_clock(&mut scenario); - clock.set_for_testing(valid_until_time_secs* 1000 + 1000); + clock.set_for_testing(valid_until_time_ms + 100000); // This should fail as the capability has expired let test_data = test_utils::new_test_data(1, b"Test record after valid_until"); @@ -1106,8 +1089,8 @@ fun test_capability_time_window() { &mut scenario, admin_user, user, - valid_from_time / 1000, - valid_until_time / 1000, + valid_from_time, + valid_until_time, ); // Use the capability within the valid time window @@ -1142,23 +1125,23 @@ fun test_capability_time_window_before_valid_from() { let mut scenario = ts::begin(admin_user); - let valid_from_time_secs = test_utils::initial_time_for_testing() / 1000 + 5; - let valid_until_time_secs = test_utils::initial_time_for_testing() / 1000 + 10; + let valid_from_time_ms = test_utils::initial_time_for_testing() + 5000; + let valid_until_time_ms = test_utils::initial_time_for_testing() + 10000; // Setup let _trail_id = setup_trail_with_record_admin_capability_and_time_window_restriction( &mut scenario, admin_user, user, - valid_from_time_secs, - valid_until_time_secs, + valid_from_time_ms, + valid_until_time_ms, ); // Use the capability before valid_from ts::next_tx(&mut scenario, user); { let (cap, mut trail, mut clock) = fetch_capability_trail_and_clock(&mut scenario); - clock.set_for_testing(valid_from_time_secs* 1000 - 1000); + clock.set_for_testing(valid_from_time_ms - 1000); let test_data = test_utils::new_test_data(1, b"Test record before valid_from"); trail.add_record( @@ -1186,23 +1169,23 @@ fun test_capability_time_window_after_valid_until() { let mut scenario = ts::begin(admin_user); - let valid_from_time_secs = test_utils::initial_time_for_testing() / 1000 + 5; - let valid_until_time_secs = test_utils::initial_time_for_testing() / 1000 + 10; + let valid_from_time_ms = test_utils::initial_time_for_testing() + 5000; + let valid_until_time_ms = test_utils::initial_time_for_testing() + 10000; // Setup let _trail_id = setup_trail_with_record_admin_capability_and_time_window_restriction( &mut scenario, admin_user, user, - valid_from_time_secs, - valid_until_time_secs, + valid_from_time_ms, + valid_until_time_ms, ); // Use the capability after valid_until ts::next_tx(&mut scenario, user); { let (cap, mut trail, mut clock) = fetch_capability_trail_and_clock(&mut scenario); - clock.set_for_testing(valid_until_time_secs* 1000 + 1000); + clock.set_for_testing(valid_until_time_ms + 1000); let test_data = test_utils::new_test_data(1, b"Test record after valid_until"); trail.add_record( @@ -1269,8 +1252,8 @@ fun test_is_valid_for_timestamp() { // Before valid_until (exclusive) assert!(cap.is_valid_for_timestamp(valid_until_time - 1), 3); - // At valid_until (exclusive) - assert!(!cap.is_valid_for_timestamp(valid_until_time), 4); + // At valid_until (inclusive) + assert!(cap.is_valid_for_timestamp(valid_until_time), 4); // After valid_until assert!(!cap.is_valid_for_timestamp(valid_until_time + 1), 5); @@ -1284,14 +1267,13 @@ fun test_is_valid_for_timestamp() { { let (admin_cap, mut trail, clock) = fetch_capability_trail_and_clock(&mut scenario); - let unrestricted_cap = trail - .roles_mut() - .new_capability_without_restrictions( - &admin_cap, - &string::utf8(b"RecordAdmin"), - &clock, - ts::ctx(&mut scenario), - ); + let unrestricted_cap = test_utils::new_capability_without_restrictions( + trail.roles_mut(), + &admin_cap, + &string::utf8(b"RecordAdmin"), + &clock, + ts::ctx(&mut scenario), + ); // Should be valid at any timestamp assert!(unrestricted_cap.is_valid_for_timestamp(0), 6); @@ -1336,8 +1318,8 @@ fun test_is_currently_valid() { &admin_cap, &string::utf8(b"RecordAdmin"), std::option::none(), - std::option::some(valid_from_time / 1000), - std::option::some(valid_until_time / 1000), + std::option::some(valid_from_time), + std::option::some(valid_until_time), &clock, ts::ctx(&mut scenario), ); @@ -1387,158 +1369,3 @@ fun test_is_currently_valid() { ts::end(scenario); } - -/// Test Capability::new_capability_without_restrictions function. -/// -/// This test validates: -/// - Creates capability with no restrictions -/// - issued_to, valid_from, and valid_until are all None -/// - Capability can be used by anyone at any time -#[test] -fun test_new_capability_without_restrictions() { - let admin_user = @0xAD; - let any_user = @0xB0B; - - let mut scenario = ts::begin(admin_user); - - // Setup - let trail_id = setup_trail_with_record_admin_role(&mut scenario, admin_user); - - // Create unrestricted capability - ts::next_tx(&mut scenario, admin_user); - { - let (admin_cap, mut trail, clock) = fetch_capability_trail_and_clock(&mut scenario); - - let cap = trail - .roles_mut() - .new_capability_without_restrictions( - &admin_cap, - &string::utf8(b"RecordAdmin"), - &clock, - ts::ctx(&mut scenario), - ); - - // Verify no restrictions - assert!(cap.issued_to().is_none(), 0); - assert!(cap.valid_from().is_none(), 1); - assert!(cap.valid_until().is_none(), 2); - assert!(cap.role() == string::utf8(b"RecordAdmin"), 3); - assert!(cap.security_vault_id() == trail_id, 4); - - transfer::public_transfer(cap, any_user); - cleanup_capability_trail_and_clock(&scenario, admin_cap, trail, clock); - }; - - // Verify any user can use it at any time - ts::next_tx(&mut scenario, any_user); - { - let (cap, mut trail, mut clock) = fetch_capability_trail_and_clock(&mut scenario); - clock.set_for_testing(999999999); - - let test_data = test_utils::new_test_data(1, b"Test"); - trail.add_record( - &cap, - test_data, - std::option::none(), - &clock, - ts::ctx(&mut scenario), - ); - - cleanup_capability_trail_and_clock(&scenario, cap, trail, clock); - }; - - ts::end(scenario); -} - -/// Test Capability::new_capability_valid_until function. -/// -/// This test validates: -/// - Creates capability with only valid_until restriction -/// - issued_to and valid_from are None -/// - Capability expires at the specified timestamp -#[test] -fun test_new_capability_valid_until() { - let admin_user = @0xAD; - let user = @0xB0B; - - let mut scenario = ts::begin(admin_user); - - let valid_until_time = test_utils::initial_time_for_testing() + 10000; - - // Setup - let trail_id = setup_trail_with_record_admin_role(&mut scenario, admin_user); - - // Create capability with valid_until - ts::next_tx(&mut scenario, admin_user); - { - let (admin_cap, mut trail, clock) = fetch_capability_trail_and_clock(&mut scenario); - - let cap = trail - .roles_mut() - .new_capability_valid_until( - &admin_cap, - &string::utf8(b"RecordAdmin"), - valid_until_time, - &clock, - ts::ctx(&mut scenario), - ); - - // Verify restrictions - assert!(cap.issued_to().is_none(), 0); - assert!(cap.valid_from().is_none(), 1); - assert!(cap.valid_until() == std::option::some(valid_until_time), 2); - assert!(cap.role() == string::utf8(b"RecordAdmin"), 3); - assert!(cap.security_vault_id() == trail_id, 4); - - transfer::public_transfer(cap, user); - cleanup_capability_trail_and_clock(&scenario, admin_cap, trail, clock); - }; - - ts::end(scenario); -} - -/// Test Capability::new_capability_for_address with None for valid_until. -/// -/// This test validates: -/// - Creates capability restricted to specific address -/// - valid_until is None (no expiration) -/// - valid_from is None -#[test] -fun test_new_capability_for_address_no_expiration() { - let admin_user = @0xAD; - let authorized_user = @0xB0B; - - let mut scenario = ts::begin(admin_user); - - // Setup - let trail_id = setup_trail_with_record_admin_role(&mut scenario, admin_user); - - // Create capability for address without expiration - ts::next_tx(&mut scenario, admin_user); - { - let (admin_cap, mut trail, clock) = fetch_capability_trail_and_clock(&mut scenario); - - let cap = trail - .roles_mut() - .new_capability_for_address( - &admin_cap, - &string::utf8(b"RecordAdmin"), - authorized_user, - std::option::none(), // no expiration - &clock, - ts::ctx(&mut scenario), - ); - - // Verify restrictions - assert!(cap.issued_to() == std::option::some(authorized_user), 0); - assert!(cap.valid_from().is_none(), 1); - assert!(cap.valid_until().is_none(), 2); - assert!(cap.role() == string::utf8(b"RecordAdmin"), 3); - assert!(cap.security_vault_id() == trail_id, 4); - - transfer::public_transfer(cap, authorized_user); - cleanup_capability_trail_and_clock(&scenario, admin_cap, trail, clock); - }; - - ts::end(scenario); -} diff --git a/audit-trail-move/tests/create_audit_trail_tests.move b/audit-trail-move/tests/create_audit_trail_tests.move index 7c07ce5..5707c08 100644 --- a/audit-trail-move/tests/create_audit_trail_tests.move +++ b/audit-trail-move/tests/create_audit_trail_tests.move @@ -33,7 +33,7 @@ fun test_create_without_initial_record() { // Verify capability was created assert!(admin_cap.role() == initial_admin_role_name(), 0); - assert!(admin_cap.security_vault_id() == trail_id, 1); + assert!(admin_cap.target_key() == trail_id, 1); // Clean up admin_cap.destroy_for_testing(); @@ -71,7 +71,7 @@ fun test_create_with_initial_record() { // Verify capability assert!(admin_cap.role() == initial_admin_role_name(), 0); - assert!(admin_cap.security_vault_id() == trail_id, 1); + assert!(admin_cap.target_key() == trail_id, 1); // Clean up admin_cap.destroy_for_testing(); @@ -230,7 +230,7 @@ fun test_create_metadata_admin_role() { // Verify admin capability was created assert!(admin_cap.role() == initial_admin_role_name(), 0); - assert!(admin_cap.security_vault_id() == trail_id, 1); + assert!(admin_cap.target_key() == trail_id, 1); // Transfer the admin capability to the user transfer::public_transfer(admin_cap, user); diff --git a/audit-trail-move/tests/locking_tests.move b/audit-trail-move/tests/locking_tests.move index 7a81e71..c6d84b5 100644 --- a/audit-trail-move/tests/locking_tests.move +++ b/audit-trail-move/tests/locking_tests.move @@ -3,11 +3,11 @@ module audit_trail::locking_tests; use audit_trail::{ - capability::Capability, locking, main::AuditTrail, permission, test_utils::{ + Self, TestData, setup_test_audit_trail, new_test_data, @@ -19,6 +19,7 @@ use audit_trail::{ }; use iota::{clock, test_scenario as ts}; use std::string; +use tf_components::capability::Capability; // ===== Time-Based Locking Tests ===== @@ -131,14 +132,13 @@ fun test_count_based_locking() { ts::ctx(&mut scenario), ); - let record_cap = trail - .roles_mut() - .new_capability_without_restrictions( - &admin_cap, - &string::utf8(b"RecordAdmin"), - &clock, - ts::ctx(&mut scenario), - ); + let record_cap = test_utils::new_capability_without_restrictions( + trail.roles_mut(), + &admin_cap, + &string::utf8(b"RecordAdmin"), + &clock, + ts::ctx(&mut scenario), + ); transfer::public_transfer(record_cap, admin); admin_cap.destroy_for_testing(); @@ -281,14 +281,13 @@ fun test_update_locking_config() { ts::ctx(&mut scenario), ); - let locking_cap = trail - .roles_mut() - .new_capability_without_restrictions( - &admin_cap, - &string::utf8(b"LockingAdmin"), - &clock, - ts::ctx(&mut scenario), - ); + let locking_cap = test_utils::new_capability_without_restrictions( + trail.roles_mut(), + &admin_cap, + &string::utf8(b"LockingAdmin"), + &clock, + ts::ctx(&mut scenario), + ); transfer::public_transfer(locking_cap, admin); admin_cap.destroy_for_testing(); @@ -354,14 +353,13 @@ fun test_update_locking_config_permission_denied() { ts::ctx(&mut scenario), ); - let no_locking_cap = trail - .roles_mut() - .new_capability_without_restrictions( - &admin_cap, - &string::utf8(b"NoLockingPerm"), - &clock, - ts::ctx(&mut scenario), - ); + let no_locking_cap = test_utils::new_capability_without_restrictions( + trail.roles_mut(), + &admin_cap, + &string::utf8(b"NoLockingPerm"), + &clock, + ts::ctx(&mut scenario), + ); transfer::public_transfer(no_locking_cap, admin); admin_cap.destroy_for_testing(); cleanup_trail_and_clock(trail, clock); @@ -419,14 +417,13 @@ fun test_update_locking_config_for_delete_record() { ts::ctx(&mut scenario), ); - let delete_lock_cap = trail - .roles_mut() - .new_capability_without_restrictions( - &admin_cap, - &string::utf8(b"DeleteLockAdmin"), - &clock, - ts::ctx(&mut scenario), - ); + let delete_lock_cap = test_utils::new_capability_without_restrictions( + trail.roles_mut(), + &admin_cap, + &string::utf8(b"DeleteLockAdmin"), + &clock, + ts::ctx(&mut scenario), + ); transfer::public_transfer(delete_lock_cap, admin); admin_cap.destroy_for_testing(); @@ -493,14 +490,13 @@ fun test_update_locking_config_for_delete_record_permission_denied() { ts::ctx(&mut scenario), ); - let wrong_cap = trail - .roles_mut() - .new_capability_without_restrictions( - &admin_cap, - &string::utf8(b"WrongPerm"), - &clock, - ts::ctx(&mut scenario), - ); + let wrong_cap = test_utils::new_capability_without_restrictions( + trail.roles_mut(), + &admin_cap, + &string::utf8(b"WrongPerm"), + &clock, + ts::ctx(&mut scenario), + ); transfer::public_transfer(wrong_cap, admin); admin_cap.destroy_for_testing(); @@ -556,14 +552,13 @@ fun test_delete_record_after_time_lock_expires() { ts::ctx(&mut scenario), ); - let record_cap = trail - .roles_mut() - .new_capability_without_restrictions( - &admin_cap, - &string::utf8(b"RecordAdmin"), - &clock, - ts::ctx(&mut scenario), - ); + let record_cap = test_utils::new_capability_without_restrictions( + trail.roles_mut(), + &admin_cap, + &string::utf8(b"RecordAdmin"), + &clock, + ts::ctx(&mut scenario), + ); transfer::public_transfer(record_cap, admin); admin_cap.destroy_for_testing(); @@ -681,14 +676,13 @@ fun test_combined_time_and_count_locking_both_lock() { ts::ctx(&mut scenario), ); - let record_cap = trail - .roles_mut() - .new_capability_without_restrictions( - &admin_cap, - &string::utf8(b"RecordAdmin"), - &clock, - ts::ctx(&mut scenario), - ); + let record_cap = test_utils::new_capability_without_restrictions( + trail.roles_mut(), + &admin_cap, + &string::utf8(b"RecordAdmin"), + &clock, + ts::ctx(&mut scenario), + ); // Add 5 records clock.set_for_testing(initial_time_for_testing() + 1000); @@ -766,14 +760,13 @@ fun test_combined_locking_time_expired_but_count_locked() { ts::ctx(&mut scenario), ); - let record_cap = trail - .roles_mut() - .new_capability_without_restrictions( - &admin_cap, - &string::utf8(b"RecordAdmin"), - &clock, - ts::ctx(&mut scenario), - ); + let record_cap = test_utils::new_capability_without_restrictions( + trail.roles_mut(), + &admin_cap, + &string::utf8(b"RecordAdmin"), + &clock, + ts::ctx(&mut scenario), + ); // Add 5 records clock.set_for_testing(initial_time_for_testing() + 1000); @@ -852,14 +845,13 @@ fun test_combined_locking_count_satisfied_but_time_locked() { ts::ctx(&mut scenario), ); - let record_cap = trail - .roles_mut() - .new_capability_without_restrictions( - &admin_cap, - &string::utf8(b"RecordAdmin"), - &clock, - ts::ctx(&mut scenario), - ); + let record_cap = test_utils::new_capability_without_restrictions( + trail.roles_mut(), + &admin_cap, + &string::utf8(b"RecordAdmin"), + &clock, + ts::ctx(&mut scenario), + ); // Add 5 records clock.set_for_testing(initial_time_for_testing() + 1000); @@ -935,14 +927,13 @@ fun test_combined_locking_both_satisfied_can_delete() { ts::ctx(&mut scenario), ); - let record_cap = trail - .roles_mut() - .new_capability_without_restrictions( - &admin_cap, - &string::utf8(b"RecordAdmin"), - &clock, - ts::ctx(&mut scenario), - ); + let record_cap = test_utils::new_capability_without_restrictions( + trail.roles_mut(), + &admin_cap, + &string::utf8(b"RecordAdmin"), + &clock, + ts::ctx(&mut scenario), + ); // Add 5 records clock.set_for_testing(initial_time_for_testing() + 1000); diff --git a/audit-trail-move/tests/metadata_tests.move b/audit-trail-move/tests/metadata_tests.move index 1b619d4..bf091be 100644 --- a/audit-trail-move/tests/metadata_tests.move +++ b/audit-trail-move/tests/metadata_tests.move @@ -3,10 +3,10 @@ module audit_trail::metadata_tests; use audit_trail::{ - capability::Capability, locking, permission, test_utils::{ + Self, setup_test_audit_trail, fetch_capability_trail_and_clock, cleanup_capability_trail_and_clock @@ -14,6 +14,7 @@ use audit_trail::{ }; use iota::test_scenario as ts; use std::string; +use tf_components::capability::Capability; // ===== Success Case Tests ===== @@ -53,14 +54,13 @@ fun test_update_metadata_success() { ); // Issue capability to metadata admin user - let metadata_cap = trail - .roles_mut() - .new_capability_without_restrictions( - &admin_cap, - &string::utf8(b"MetadataAdmin"), - &clock, - ts::ctx(&mut scenario), - ); + let metadata_cap = test_utils::new_capability_without_restrictions( + trail.roles_mut(), + &admin_cap, + &string::utf8(b"MetadataAdmin"), + &clock, + ts::ctx(&mut scenario), + ); transfer::public_transfer(metadata_cap, metadata_admin_user); cleanup_capability_trail_and_clock(&scenario, admin_cap, trail, clock); @@ -171,14 +171,13 @@ fun test_update_metadata_permission_denied() { ts::ctx(&mut scenario), ); - let user_cap = trail - .roles_mut() - .new_capability_without_restrictions( - &admin_cap, - &string::utf8(b"NoMetadataPerm"), - &clock, - ts::ctx(&mut scenario), - ); + let user_cap = test_utils::new_capability_without_restrictions( + trail.roles_mut(), + &admin_cap, + &string::utf8(b"NoMetadataPerm"), + &clock, + ts::ctx(&mut scenario), + ); transfer::public_transfer(user_cap, user); cleanup_capability_trail_and_clock(&scenario, admin_cap, trail, clock); @@ -240,14 +239,13 @@ fun test_update_metadata_revoked_capability() { ); // Issue capability - let metadata_cap = trail - .roles_mut() - .new_capability_without_restrictions( - &admin_cap, - &string::utf8(b"MetadataAdmin"), - &clock, - ts::ctx(&mut scenario), - ); + let metadata_cap = test_utils::new_capability_without_restrictions( + trail.roles_mut(), + &admin_cap, + &string::utf8(b"MetadataAdmin"), + &clock, + ts::ctx(&mut scenario), + ); transfer::public_transfer(metadata_cap, metadata_admin_user); cleanup_capability_trail_and_clock(&scenario, admin_cap, trail, clock); diff --git a/audit-trail-move/tests/record_tests.move b/audit-trail-move/tests/record_tests.move index c89c2ef..d131030 100644 --- a/audit-trail-move/tests/record_tests.move +++ b/audit-trail-move/tests/record_tests.move @@ -7,6 +7,7 @@ use audit_trail::{ main::{Self, AuditTrail}, permission, test_utils::{ + Self, TestData, setup_test_audit_trail, new_test_data, @@ -54,14 +55,13 @@ fun test_add_record_to_empty_trail() { ts::ctx(&mut scenario), ); - let record_cap = trail - .roles_mut() - .new_capability_without_restrictions( - &admin_cap, - &string::utf8(b"RecordAdmin"), - &clock, - ts::ctx(&mut scenario), - ); + let record_cap = test_utils::new_capability_without_restrictions( + trail.roles_mut(), + &admin_cap, + &string::utf8(b"RecordAdmin"), + &clock, + ts::ctx(&mut scenario), + ); transfer::public_transfer(record_cap, admin); admin_cap.destroy_for_testing(); @@ -129,14 +129,13 @@ fun test_add_multiple_records() { ts::ctx(&mut scenario), ); - let record_cap = trail - .roles_mut() - .new_capability_without_restrictions( - &admin_cap, - &string::utf8(b"RecordAdmin"), - &clock, - ts::ctx(&mut scenario), - ); + let record_cap = test_utils::new_capability_without_restrictions( + trail.roles_mut(), + &admin_cap, + &string::utf8(b"RecordAdmin"), + &clock, + ts::ctx(&mut scenario), + ); transfer::public_transfer(record_cap, admin); admin_cap.destroy_for_testing(); @@ -208,14 +207,13 @@ fun test_add_record_permission_denied() { ts::ctx(&mut scenario), ); - let no_add_cap = trail - .roles_mut() - .new_capability_without_restrictions( - &admin_cap, - &string::utf8(b"NoAddPerm"), - &clock, - ts::ctx(&mut scenario), - ); + let no_add_cap = test_utils::new_capability_without_restrictions( + trail.roles_mut(), + &admin_cap, + &string::utf8(b"NoAddPerm"), + &clock, + ts::ctx(&mut scenario), + ); transfer::public_transfer(no_add_cap, admin); admin_cap.destroy_for_testing(); @@ -276,14 +274,13 @@ fun test_delete_record_success() { ts::ctx(&mut scenario), ); - let record_cap = trail - .roles_mut() - .new_capability_without_restrictions( - &admin_cap, - &string::utf8(b"RecordAdmin"), - &clock, - ts::ctx(&mut scenario), - ); + let record_cap = test_utils::new_capability_without_restrictions( + trail.roles_mut(), + &admin_cap, + &string::utf8(b"RecordAdmin"), + &clock, + ts::ctx(&mut scenario), + ); transfer::public_transfer(record_cap, admin); admin_cap.destroy_for_testing(); @@ -347,14 +344,13 @@ fun test_delete_record_permission_denied() { ts::ctx(&mut scenario), ); - let no_delete_cap = trail - .roles_mut() - .new_capability_without_restrictions( - &admin_cap, - &string::utf8(b"NoDeletePerm"), - &clock, - ts::ctx(&mut scenario), - ); + let no_delete_cap = test_utils::new_capability_without_restrictions( + trail.roles_mut(), + &admin_cap, + &string::utf8(b"NoDeletePerm"), + &clock, + ts::ctx(&mut scenario), + ); transfer::public_transfer(no_delete_cap, admin); admin_cap.destroy_for_testing(); @@ -408,14 +404,13 @@ fun test_delete_record_not_found() { ts::ctx(&mut scenario), ); - let record_cap = trail - .roles_mut() - .new_capability_without_restrictions( - &admin_cap, - &string::utf8(b"RecordAdmin"), - &clock, - ts::ctx(&mut scenario), - ); + let record_cap = test_utils::new_capability_without_restrictions( + trail.roles_mut(), + &admin_cap, + &string::utf8(b"RecordAdmin"), + &clock, + ts::ctx(&mut scenario), + ); transfer::public_transfer(record_cap, admin); admin_cap.destroy_for_testing(); @@ -469,14 +464,13 @@ fun test_delete_record_time_locked() { ts::ctx(&mut scenario), ); - let record_cap = trail - .roles_mut() - .new_capability_without_restrictions( - &admin_cap, - &string::utf8(b"RecordAdmin"), - &clock, - ts::ctx(&mut scenario), - ); + let record_cap = test_utils::new_capability_without_restrictions( + trail.roles_mut(), + &admin_cap, + &string::utf8(b"RecordAdmin"), + &clock, + ts::ctx(&mut scenario), + ); transfer::public_transfer(record_cap, admin); admin_cap.destroy_for_testing(); @@ -531,14 +525,13 @@ fun test_delete_record_count_locked() { ts::ctx(&mut scenario), ); - let record_cap = trail - .roles_mut() - .new_capability_without_restrictions( - &admin_cap, - &string::utf8(b"RecordAdmin"), - &clock, - ts::ctx(&mut scenario), - ); + let record_cap = test_utils::new_capability_without_restrictions( + trail.roles_mut(), + &admin_cap, + &string::utf8(b"RecordAdmin"), + &clock, + ts::ctx(&mut scenario), + ); transfer::public_transfer(record_cap, admin); admin_cap.destroy_for_testing(); @@ -660,14 +653,13 @@ fun test_first_last_sequence() { ts::ctx(&mut scenario), ); - let record_cap = trail - .roles_mut() - .new_capability_without_restrictions( - &admin_cap, - &string::utf8(b"RecordAdmin"), - &clock, - ts::ctx(&mut scenario), - ); + let record_cap = test_utils::new_capability_without_restrictions( + trail.roles_mut(), + &admin_cap, + &string::utf8(b"RecordAdmin"), + &clock, + ts::ctx(&mut scenario), + ); clock.set_for_testing(initial_time_for_testing() + 1000); diff --git a/audit-trail-move/tests/role_tests.move b/audit-trail-move/tests/role_tests.move index e4c3263..1501ac7 100644 --- a/audit-trail-move/tests/role_tests.move +++ b/audit-trail-move/tests/role_tests.move @@ -38,7 +38,7 @@ fun test_role_based_permission_delegation() { // Verify admin capability was created with correct role and trail reference assert!(admin_cap.role() == initial_admin_role_name(), 0); - assert!(admin_cap.security_vault_id() == trail_id, 1); + assert!(admin_cap.target_key() == trail_id, 1); // Transfer the admin capability to the user transfer::public_transfer(admin_cap, admin_user); @@ -91,33 +91,31 @@ fun test_role_based_permission_delegation() { { let (admin_cap, mut trail, clock) = fetch_capability_trail_and_clock(&mut scenario); - let role_admin_cap = trail - .roles_mut() - .new_capability_without_restrictions( - &admin_cap, - &string::utf8(b"RoleAdmin"), - &clock, - ts::ctx(&mut scenario), - ); + let role_admin_cap = test_utils::new_capability_without_restrictions( + trail.roles_mut(), + &admin_cap, + &string::utf8(b"RoleAdmin"), + &clock, + ts::ctx(&mut scenario), + ); // Verify the capability was created with correct role and trail ID assert!(role_admin_cap.role() == string::utf8(b"RoleAdmin"), 6); - assert!(role_admin_cap.security_vault_id() == trail_id, 7); + assert!(role_admin_cap.target_key() == trail_id, 7); iota::transfer::public_transfer(role_admin_cap, role_admin_user); - let cap_admin_cap = trail - .roles_mut() - .new_capability_without_restrictions( - &admin_cap, - &string::utf8(b"CapAdmin"), - &clock, - ts::ctx(&mut scenario), - ); + let cap_admin_cap = test_utils::new_capability_without_restrictions( + trail.roles_mut(), + &admin_cap, + &string::utf8(b"CapAdmin"), + &clock, + ts::ctx(&mut scenario), + ); // Verify the capability was created with correct role and trail ID assert!(cap_admin_cap.role() == string::utf8(b"CapAdmin"), 8); - assert!(cap_admin_cap.security_vault_id() == trail_id, 9); + assert!(cap_admin_cap.target_key() == trail_id, 9); iota::transfer::public_transfer(cap_admin_cap, cap_admin_user); @@ -158,18 +156,17 @@ fun test_role_based_permission_delegation() { // Verify CapAdmin has the correct role assert!(cap_admin_cap.role() == string::utf8(b"CapAdmin"), 13); - let record_admin_cap = trail - .roles_mut() - .new_capability_without_restrictions( - &cap_admin_cap, - &string::utf8(b"RecordAdmin"), - &clock, - ts::ctx(&mut scenario), - ); + let record_admin_cap = test_utils::new_capability_without_restrictions( + trail.roles_mut(), + &cap_admin_cap, + &string::utf8(b"RecordAdmin"), + &clock, + ts::ctx(&mut scenario), + ); // Verify the capability was created with correct role and trail ID assert!(record_admin_cap.role() == string::utf8(b"RecordAdmin"), 14); - assert!(record_admin_cap.security_vault_id() == trail_id, 15); + assert!(record_admin_cap.target_key() == trail_id, 15); iota::transfer::public_transfer(record_admin_cap, record_admin_user); @@ -308,14 +305,13 @@ fun test_create_role_permission_denied() { ts::ctx(&mut scenario), ); - let user_cap = trail - .roles_mut() - .new_capability_without_restrictions( - &admin_cap, - &string::utf8(b"NoRolesPerm"), - &clock, - ts::ctx(&mut scenario), - ); + let user_cap = test_utils::new_capability_without_restrictions( + trail.roles_mut(), + &admin_cap, + &string::utf8(b"NoRolesPerm"), + &clock, + ts::ctx(&mut scenario), + ); transfer::public_transfer(user_cap, user); cleanup_capability_trail_and_clock(&scenario, admin_cap, trail, clock); @@ -393,14 +389,13 @@ fun test_delete_role_permission_denied() { ts::ctx(&mut scenario), ); - let user_cap = trail - .roles_mut() - .new_capability_without_restrictions( - &admin_cap, - &string::utf8(b"NoDeleteRolePerm"), - &clock, - ts::ctx(&mut scenario), - ); + let user_cap = test_utils::new_capability_without_restrictions( + trail.roles_mut(), + &admin_cap, + &string::utf8(b"NoDeleteRolePerm"), + &clock, + ts::ctx(&mut scenario), + ); transfer::public_transfer(user_cap, user); cleanup_capability_trail_and_clock(&scenario, admin_cap, trail, clock); @@ -470,14 +465,13 @@ fun test_update_role_permissions_permission_denied() { ts::ctx(&mut scenario), ); - let user_cap = trail - .roles_mut() - .new_capability_without_restrictions( - &admin_cap, - &string::utf8(b"NoUpdateRolePerm"), - &clock, - ts::ctx(&mut scenario), - ); + let user_cap = test_utils::new_capability_without_restrictions( + trail.roles_mut(), + &admin_cap, + &string::utf8(b"NoUpdateRolePerm"), + &clock, + ts::ctx(&mut scenario), + ); transfer::public_transfer(user_cap, user); cleanup_capability_trail_and_clock(&scenario, admin_cap, trail, clock); diff --git a/audit-trail-move/tests/test_utils.move b/audit-trail-move/tests/test_utils.move index e0f5739..f7d9064 100644 --- a/audit-trail-move/tests/test_utils.move +++ b/audit-trail-move/tests/test_utils.move @@ -1,11 +1,12 @@ #[test_only] module audit_trail::test_utils; -use audit_trail::{capability::Capability, locking, main::{Self, AuditTrail}}; +use audit_trail::{locking, main::{Self, AuditTrail}}; use iota::{clock::{Self, Clock}, test_scenario::{Self as ts, Scenario}}; use std::string; +use tf_components::{capability::Capability, role_map::RoleMap}; -const INITIAL_TIME_FOR_TESTING: u64 = 1234; +const INITIAL_TIME_FOR_TESTING: u64 = 1234567; /// Test data type for audit trail records public struct TestData has copy, drop, store { @@ -64,6 +65,92 @@ public(package) fun setup_test_audit_trail( (admin_cap, trail_id) } +/// Create a new unrestricted capability with a specific role without any +/// address, valid_from, or valid_until restrictions. +/// +/// Returns the newly created capability. +/// +/// Sends a CapabilityIssued event upon successful creation. +/// +/// Errors: +/// - Aborts with EPermissionDenied if the provided capability does not have the permission specified with `CapabilityAdminPermissions::add`. +/// - Aborts with ERoleDoesNotExist if the specified role does not exist in the role_map. +public fun new_capability_without_restrictions( + role_map: &mut RoleMap

, + cap: &Capability, + role: &string::String, + clock: &Clock, + ctx: &mut TxContext, +): Capability { + role_map.new_capability( + cap, + role, + std::option::none(), + std::option::none(), + std::option::none(), + clock, + ctx, + ) +} + +/// Create a new capability with a specific role that expires at a given timestamp (milliseconds since Unix epoch). +/// +/// Returns the newly created capability. +/// +/// Sends a CapabilityIssued event upon successful creation. +/// +/// Errors: +/// - Aborts with EPermissionDenied if the provided capability does not have the permission specified with `CapabilityAdminPermissions::add`. +/// - Aborts with ERoleDoesNotExist if the specified role does not exist in the role_map. +public(package) fun new_capability_valid_until( + role_map: &mut RoleMap

, + cap: &Capability, + role: &string::String, + valid_until: u64, + clock: &Clock, + ctx: &mut TxContext, +): Capability { + role_map.new_capability( + cap, + role, + std::option::none(), + std::option::none(), + std::option::some(valid_until), + clock, + ctx, + ) +} + +/// Create a new capability with a specific role restricted to an address. +/// Optionally set an expiration time (milliseconds since Unix epoch). +/// +/// Returns the newly created capability. +/// +/// Sends a CapabilityIssued event upon successful creation. +/// +/// Errors: +/// - Aborts with EPermissionDenied if the provided capability does not have the permission specified with `CapabilityAdminPermissions::add`. +/// - Aborts with ERoleDoesNotExist if the specified role does not exist in the role_map. +public fun new_capability_for_address( + role_map: &mut RoleMap

, + cap: &Capability, + role: &string::String, + issued_to: address, + valid_until: Option, + clock: &Clock, + ctx: &mut TxContext, +): Capability { + role_map.new_capability( + cap, + role, + std::option::some(issued_to), + std::option::none(), + valid_until, + clock, + ctx, + ) +} + public(package) fun fetch_capability_trail_and_clock( scenario: &mut Scenario, ): (Capability, AuditTrail, Clock) { diff --git a/bindings/wasm/notarization_wasm/src/wasm_time_lock.rs b/bindings/wasm/notarization_wasm/src/wasm_time_lock.rs index 9455bd9..cf3e42f 100644 --- a/bindings/wasm/notarization_wasm/src/wasm_time_lock.rs +++ b/bindings/wasm/notarization_wasm/src/wasm_time_lock.rs @@ -9,14 +9,18 @@ use wasm_bindgen::prelude::*; /// /// This enum defines the possible types of time locks that can be applied to a notarization object. /// - `None`: No time lock is applied. -/// - `UnlockAt`: The object will unlock at a specific timestamp. +/// - `UnlockAt`: The object will unlock at a specific timestamp (seconds since Unix epoch). +/// - `UnlockAtMs`: Same as UnlockAt (unlocks at specific timestamp) but using milliseconds since Unix epoch. /// - `UntilDestroyed`: The object remains locked until it is destroyed. Can not be used for `delete_lock`. +/// - `Infinite`: The object is permanently locked and will never unlock. #[wasm_bindgen(js_name = TimeLockType)] #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum WasmTimeLockType { None = "None", UnlockAt = "UnlockAt", + UnlockAtMs = "UnlockAtMs", UntilDestroyed = "UntilDestroyed", + Infinite = "Infinite", } /// Represents a time lock configuration. @@ -28,16 +32,28 @@ pub struct WasmTimeLock(pub(crate) TimeLock); #[wasm_bindgen(js_class = TimeLock)] impl WasmTimeLock { - /// Creates a time lock that unlocks at a specific timestamp. + /// Creates a time lock that unlocks at a specific seconds based timestamp. /// /// # Arguments - /// * `time` - The timestamp in seconds since the Unix epoch at which the object will unlock. + /// * `time_sec` - The timestamp in seconds since the Unix epoch at which the object will unlock. /// /// # Returns /// A new `TimeLock` instance configured to unlock at the specified timestamp. #[wasm_bindgen(js_name = withUnlockAt)] - pub fn with_unlock_at(time: u32) -> Self { - Self(TimeLock::UnlockAt(time)) + pub fn with_unlock_at(time_sec: u32) -> Self { + Self(TimeLock::UnlockAt(time_sec)) + } + + /// Creates a time lock that unlocks at a specific milliseconds based timestamp. + /// + /// # Arguments + /// * `time_ms` - The timestamp in milliseconds since the Unix epoch at which the object will unlock. + /// + /// # Returns + /// A new `TimeLock` instance configured to unlock at the specified timestamp. + #[wasm_bindgen(js_name = withUnlockAtMs)] + pub fn with_unlock_at_ms(time_ms: u64) -> Self { + Self(TimeLock::UnlockAtMs(time_ms)) } /// Creates a time lock that remains locked until the object is destroyed. @@ -49,6 +65,15 @@ impl WasmTimeLock { Self(TimeLock::UntilDestroyed) } + /// Creates a time lock that is locked permanently and will never be unlocked + /// + /// # Returns + /// A new `TimeLock` instance configured to remain locked infinitely. + #[wasm_bindgen(js_name = withInfinite)] + pub fn with_infinite() -> Self { + Self(TimeLock::Infinite) + } + /// Creates a time lock with no restrictions. /// /// # Returns @@ -66,7 +91,9 @@ 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, } } @@ -81,6 +108,7 @@ impl WasmTimeLock { 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.lock b/notarization-move/Move.lock index 920d642..ad00e68 100644 --- a/notarization-move/Move.lock +++ b/notarization-move/Move.lock @@ -2,27 +2,59 @@ [move] version = 3 -manifest_digest = "2FA64AE578ECFADFE6576D98160FEBC1DFF70895E34236A183F6833371359743" -deps_digest = "3C4103934B1E040BB6B23F1D610B4EF9F2F1166A50A104EADCF77467C004C600" +manifest_digest = "8019AAD757782B3104C1350C12F73AA444CDAA9B3E4B40A2468C3DA235715C42" +deps_digest = "397E6A9F7A624706DBDFEE056CE88391A15876868FD18A88504DA74EB458D697" dependencies = [ { id = "Iota", name = "Iota" }, + { id = "IotaSystem", name = "IotaSystem" }, { id = "MoveStdlib", name = "MoveStdlib" }, + { id = "Stardust", name = "Stardust" }, + { id = "tf_components", name = "tf_components" }, ] [[move.package]] id = "Iota" -source = { git = "https://github.com/iotaledger/iota.git", rev = "d9dbd00f5601f20f9d0d8381fc674f70869c7910", subdir = "crates/iota-framework/packages/iota-framework" } +source = { git = "https://github.com/iotaledger/iota.git", rev = "4698c6723208e052a00c74602d2c8dc0efffe5de", subdir = "crates/iota-framework/packages/iota-framework" } dependencies = [ { id = "MoveStdlib", name = "MoveStdlib" }, ] +[[move.package]] +id = "IotaSystem" +source = { git = "https://github.com/iotaledger/iota.git", rev = "4698c6723208e052a00c74602d2c8dc0efffe5de", 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 = "d9dbd00f5601f20f9d0d8381fc674f70869c7910", subdir = "crates/iota-framework/packages/move-stdlib" } +source = { git = "https://github.com/iotaledger/iota.git", rev = "4698c6723208e052a00c74602d2c8dc0efffe5de", subdir = "crates/iota-framework/packages/move-stdlib" } + +[[move.package]] +id = "Stardust" +source = { git = "https://github.com/iotaledger/iota.git", rev = "4698c6723208e052a00c74602d2c8dc0efffe5de", subdir = "crates/iota-framework/packages/stardust" } + +dependencies = [ + { id = "Iota", name = "Iota" }, + { id = "MoveStdlib", name = "MoveStdlib" }, +] + +[[move.package]] +id = "tf_components" +source = { git = "https://github.com/iotaledger/product-core.git", rev = "feat/role-map", 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.2.3" +compiler-version = "1.14.1" edition = "2024.beta" flavor = "iota" diff --git a/notarization-move/Move.toml b/notarization-move/Move.toml index 93671c1..4726d09 100644 --- a/notarization-move/Move.toml +++ b/notarization-move/Move.toml @@ -6,6 +6,7 @@ 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/sources/dynamic_notarization.move b/notarization-move/sources/dynamic_notarization.move index ca5e795..b9edcfa 100644 --- a/notarization-move/sources/dynamic_notarization.move +++ b/notarization-move/sources/dynamic_notarization.move @@ -5,8 +5,9 @@ module iota_notarization::dynamic_notarization; use iota::{clock::Clock, event}; -use iota_notarization::{notarization, timelock::TimeLock}; +use iota_notarization::notarization; use std::string::String; +use tf_components::timelock::TimeLock; // ===== Constants ===== /// Cannot transfer a locked notarization diff --git a/notarization-move/sources/locked_notarization.move b/notarization-move/sources/locked_notarization.move index f1c02fa..e843e9c 100644 --- a/notarization-move/sources/locked_notarization.move +++ b/notarization-move/sources/locked_notarization.move @@ -5,8 +5,9 @@ module iota_notarization::locked_notarization; use iota::{clock::Clock, event}; -use iota_notarization::{notarization, timelock::TimeLock}; +use iota_notarization::notarization; use std::string::String; +use tf_components::timelock::TimeLock; /// Event emitted when a locked notarization is created public struct LockedNotarizationCreated has copy, drop { diff --git a/notarization-move/sources/notarization.move b/notarization-move/sources/notarization.move index 043eeca..757ddef 100644 --- a/notarization-move/sources/notarization.move +++ b/notarization-move/sources/notarization.move @@ -7,11 +7,9 @@ module iota_notarization::notarization; use iota::{clock::{Self, Clock}, event}; -use iota_notarization::{ - method::{NotarizationMethod, new_dynamic, new_locked}, - timelock::{Self, TimeLock} -}; +use iota_notarization::method::{NotarizationMethod, new_dynamic, new_locked}; use std::string::String; +use tf_components::timelock::{Self, TimeLock}; // ===== Constants ===== /// Cannot update state while notarization is locked for updates diff --git a/notarization-move/sources/timelock.move b/notarization-move/sources/timelock.move deleted file mode 100644 index c6eb46f..0000000 --- a/notarization-move/sources/timelock.move +++ /dev/null @@ -1,127 +0,0 @@ -// 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 Unix 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 6c92987..0077bbd 100644 --- a/notarization-move/tests/dynamic_notarization_tests.move +++ b/notarization-move/tests/dynamic_notarization_tests.move @@ -6,8 +6,9 @@ module iota_notarization::dynamic_notarization_tests; use iota::{clock, test_scenario::{Self as ts, ctx}}; -use iota_notarization::{dynamic_notarization, notarization, timelock}; +use iota_notarization::{dynamic_notarization, notarization}; 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 43a0578..8f04965 100644 --- a/notarization-move/tests/locked_notarization_tests.move +++ b/notarization-move/tests/locked_notarization_tests.move @@ -6,8 +6,9 @@ module iota_notarization::locked_notarization_tests; use iota::{clock, test_scenario as ts}; -use iota_notarization::{locked_notarization, notarization, timelock}; +use iota_notarization::{locked_notarization, notarization}; 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 17e3716..dc31747 100644 --- a/notarization-move/tests/notarization_tests.move +++ b/notarization-move/tests/notarization_tests.move @@ -6,8 +6,9 @@ module iota_notarization::notarization_tests; use iota::{clock, test_scenario as ts}; -use iota_notarization::{notarization, timelock}; +use iota_notarization::notarization; 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 deleted file mode 100644 index 68aa0f1..0000000 --- a/notarization-move/tests/timelock_tests.move +++ /dev/null @@ -1,202 +0,0 @@ -// 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(); -} diff --git a/notarization-rs/src/core/types/timelock.rs b/notarization-rs/src/core/types/timelock.rs index 07beba3..2c4ca47 100644 --- a/notarization-rs/src/core/types/timelock.rs +++ b/notarization-rs/src/core/types/timelock.rs @@ -8,12 +8,6 @@ //! ## 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; @@ -40,20 +34,25 @@ pub struct LockMetadata { /// notarizations. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] pub enum TimeLock { - /// A lock that is unlocked at a specific time. + /// A lock that unlocks at a specific Unix timestamp (seconds since Unix epoch) UnlockAt(u32), - /// A lock that is unlocked when the notarization is destroyed. + /// 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, } impl TimeLock { - /// Creates a new `TimeLock` with a specified unlock time.\ + /// Creates a new `TimeLock::UnlockAt` with a specified unlock time.\ /// /// The unlock time is the time in seconds since the Unix epoch and /// must be in the future. - pub fn new_with_ts(unlock_time: u32) -> Result { - if unlock_time + pub fn new_with_ts(unlock_time_sec: u32) -> Result { + if unlock_time_sec <= SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) .expect("system time is before the Unix epoch") @@ -62,7 +61,24 @@ impl TimeLock { return Err(Error::InvalidArgument("unlock time must be in the future".to_string())); } - Ok(TimeLock::UnlockAt(unlock_time)) + 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`. @@ -71,23 +87,39 @@ impl TimeLock { 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), } } } /// Creates a new `Argument` for the `unlock_at` function. -pub(super) fn new_unlock_at(ptb: &mut Ptb, unlock_time: u32, package_id: ObjectID) -> Result { +pub(super) fn new_unlock_at(ptb: &mut Ptb, unlock_time_sec: u32, package_id: ObjectID) -> Result { let clock = move_utils::get_clock_ref(ptb); - let unlock_time = move_utils::ptb_pure(ptb, "unlock_time", unlock_time)?; + let unlock_time_sec = move_utils::ptb_pure(ptb, "unlock_time", unlock_time_sec)?; Ok(ptb.programmable_move_call( package_id, ident_str!("timelock").into(), ident_str!("unlock_at").into(), vec![], - vec![unlock_time, clock], + vec![unlock_time_sec, clock], + )) +} + +/// 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").into(), + ident_str!("unlock_at").into(), + vec![], + vec![unlock_time_ms, clock], )) } @@ -102,6 +134,17 @@ 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").into(), + ident_str!("infinite").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(