diff --git a/audit-trail-move/sources/audit_trail.move b/audit-trail-move/sources/audit_trail.move index 3da0cd22..ecd08b26 100644 --- a/audit-trail-move/sources/audit_trail.move +++ b/audit-trail-move/sources/audit_trail.move @@ -20,7 +20,12 @@ use audit_trail::{ permission::{Self, Permission}, record::{Self, Record} }; -use iota::{clock::{Self, Clock}, event, linked_table::{Self, LinkedTable}, vec_set::VecSet}; +use iota::{ + clock::{Self, Clock}, + event, + linked_table::{Self, LinkedTable}, + vec_set::VecSet +}; use std::string::String; use tf_components::{capability::Capability, role_map::{Self, RoleMap}, timelock::TimeLock}; @@ -232,13 +237,13 @@ public fun initial_admin_role_name(): String { /// Migrate the trail to the latest package version entry fun migrate( - trail: &mut AuditTrail, + self: &mut AuditTrail, cap: &Capability, clock: &Clock, ctx: &TxContext, ) { - assert!(trail.version < PACKAGE_VERSION, EPackageVersionMismatch); - trail + assert!(self.version < PACKAGE_VERSION, EPackageVersionMismatch); + self .roles .assert_capability_valid( cap, @@ -246,7 +251,7 @@ entry fun migrate( clock, ctx, ); - trail.version = PACKAGE_VERSION; + self.version = PACKAGE_VERSION; } public fun new_record_tags( @@ -263,15 +268,15 @@ public fun new_record_tags( /// /// Records are added sequentially with auto-assigned sequence numbers. public fun add_record( - trail: &mut AuditTrail, + self: &mut AuditTrail, cap: &Capability, stored_data: D, record_metadata: Option, clock: &Clock, ctx: &mut TxContext, ) { - assert!(trail.version == PACKAGE_VERSION, EPackageVersionMismatch); - trail + assert!(self.version == PACKAGE_VERSION, EPackageVersionMismatch); + self .roles .assert_capability_valid( cap, @@ -279,12 +284,12 @@ public fun add_record( clock, ctx, ); - assert!(!locking::is_write_locked(&trail.locking_config, clock), ETrailWriteLocked); + assert!(!locking::is_write_locked(&self.locking_config, clock), ETrailWriteLocked); let caller = ctx.sender(); let timestamp = clock::timestamp_ms(clock); - let trail_id = trail.id(); - let seq = trail.sequence_number; + let trail_id = self.id(); + let seq = self.sequence_number; let record = record::new( stored_data, @@ -295,8 +300,8 @@ public fun add_record( record::new_correction(), ); - linked_table::push_back(&mut trail.records, seq, record); - trail.sequence_number = trail.sequence_number + 1; + linked_table::push_back(&mut self.records, seq, record); + self.sequence_number = self.sequence_number + 1; event::emit(RecordAdded { trail_id, @@ -311,14 +316,14 @@ public fun add_record( /// The record must not be locked (based on the trail's locking configuration). /// Requires the DeleteRecord permission. public fun delete_record( - trail: &mut AuditTrail, + self: &mut AuditTrail, cap: &Capability, sequence_number: u64, clock: &Clock, ctx: &mut TxContext, ) { - assert!(trail.version == PACKAGE_VERSION, EPackageVersionMismatch); - trail + assert!(self.version == PACKAGE_VERSION, EPackageVersionMismatch); + self .roles .assert_capability_valid( cap, @@ -326,14 +331,14 @@ public fun delete_record( clock, ctx, ); - assert!(linked_table::contains(&trail.records, sequence_number), ERecordNotFound); - assert!(!trail.is_record_locked(sequence_number, clock), ERecordLocked); + assert!(linked_table::contains(&self.records, sequence_number), ERecordNotFound); + assert!(!self.is_record_locked(sequence_number, clock), ERecordLocked); let caller = ctx.sender(); let timestamp = clock::timestamp_ms(clock); - let trail_id = trail.id(); + let trail_id = self.id(); - let record = linked_table::remove(&mut trail.records, sequence_number); + let record = linked_table::remove(&mut self.records, sequence_number); record::destroy(record); event::emit(RecordDeleted { @@ -349,14 +354,14 @@ public fun delete_record( /// Requires `DeleteAllRecords` permission. This operation bypasses record locks. /// Returns the number of records deleted in this batch. public fun delete_records_batch( - trail: &mut AuditTrail, + self: &mut AuditTrail, cap: &Capability, limit: u64, clock: &Clock, ctx: &mut TxContext, ): u64 { - assert!(trail.version == PACKAGE_VERSION, EPackageVersionMismatch); - trail + assert!(self.version == PACKAGE_VERSION, EPackageVersionMismatch); + self .roles .assert_capability_valid( cap, @@ -368,10 +373,10 @@ public fun delete_records_batch( let mut deleted = 0; let caller = ctx.sender(); let timestamp = clock.timestamp_ms(); - let trail_id = trail.id(); + let trail_id = self.id(); - while (deleted < limit && !trail.records.is_empty()) { - let (sequence_number, record) = trail.records.pop_front(); + while (deleted < limit && !self.records.is_empty()) { + let (sequence_number, record) = self.records.pop_front(); record.destroy(); @@ -392,13 +397,13 @@ public fun delete_records_batch( /// /// Requires `DeleteAuditTrail` permission and aborts if records still exist. public fun delete_audit_trail( - trail: AuditTrail, + self: AuditTrail, cap: &Capability, clock: &Clock, ctx: &mut TxContext, ) { - assert!(trail.version == PACKAGE_VERSION, EPackageVersionMismatch); - trail + assert!(self.version == PACKAGE_VERSION, EPackageVersionMismatch); + self .roles .assert_capability_valid( cap, @@ -406,10 +411,10 @@ public fun delete_audit_trail( clock, ctx, ); - assert!(!locking::is_delete_trail_locked(&trail.locking_config, clock), ETrailDeleteLocked); - assert!(linked_table::is_empty(&trail.records), ETrailNotEmpty); + assert!(!locking::is_delete_trail_locked(&self.locking_config, clock), ETrailDeleteLocked); + assert!(linked_table::is_empty(&self.records), ETrailNotEmpty); - let trail_id = trail.id(); + let trail_id = self.id(); let timestamp = clock::timestamp_ms(clock); let AuditTrail { @@ -419,11 +424,13 @@ public fun delete_audit_trail( sequence_number: _, records, locking_config: _, - roles: _, + roles, immutable_metadata: _, updatable_metadata: _, version: _, - } = trail; + } = self; + + roles.destroy(); linked_table::destroy_empty(records); object::delete(id); @@ -436,34 +443,34 @@ public fun delete_audit_trail( /// Check if a record is locked based on the trail's locking configuration. /// Aborts with ERecordNotFound if the record doesn't exist. public fun is_record_locked( - trail: &AuditTrail, + self: &AuditTrail, sequence_number: u64, clock: &Clock, ): bool { - assert!(linked_table::contains(&trail.records, sequence_number), ERecordNotFound); + assert!(linked_table::contains(&self.records, sequence_number), ERecordNotFound); - let record = linked_table::borrow(&trail.records, sequence_number); + let record = linked_table::borrow(&self.records, sequence_number); let current_time = clock::timestamp_ms(clock); locking::is_delete_record_locked( - &trail.locking_config, + &self.locking_config, sequence_number, record::added_at(record), - trail.sequence_number, + self.sequence_number, current_time, ) } /// Update the locking configuration. Requires `UpdateLockingConfig` permission. public fun update_locking_config( - trail: &mut AuditTrail, + self: &mut AuditTrail, cap: &Capability, new_config: LockingConfig, clock: &Clock, ctx: &mut TxContext, ) { - assert!(trail.version == PACKAGE_VERSION, EPackageVersionMismatch); - trail + assert!(self.version == PACKAGE_VERSION, EPackageVersionMismatch); + self .roles .assert_capability_valid( cap, @@ -471,19 +478,19 @@ public fun update_locking_config( clock, ctx, ); - set_config(&mut trail.locking_config, new_config); + set_config(&mut self.locking_config, new_config); } /// Update the `delete_record_lock` locking configuration public fun update_delete_record_window( - trail: &mut AuditTrail, + self: &mut AuditTrail, cap: &Capability, new_delete_record_lock: LockingWindow, clock: &Clock, ctx: &mut TxContext, ) { - assert!(trail.version == PACKAGE_VERSION, EPackageVersionMismatch); - trail + assert!(self.version == PACKAGE_VERSION, EPackageVersionMismatch); + self .roles .assert_capability_valid( cap, @@ -491,19 +498,19 @@ public fun update_delete_record_window( clock, ctx, ); - set_delete_record_window(&mut trail.locking_config, new_delete_record_lock); + set_delete_record_window(&mut self.locking_config, new_delete_record_lock); } /// Update the `delete_trail_lock` locking configuration. public fun update_delete_trail_lock( - trail: &mut AuditTrail, + self: &mut AuditTrail, cap: &Capability, new_delete_trail_lock: TimeLock, clock: &Clock, ctx: &mut TxContext, ) { - assert!(trail.version == PACKAGE_VERSION, EPackageVersionMismatch); - trail + assert!(self.version == PACKAGE_VERSION, EPackageVersionMismatch); + self .roles .assert_capability_valid( cap, @@ -511,19 +518,19 @@ public fun update_delete_trail_lock( clock, ctx, ); - set_delete_trail_lock(&mut trail.locking_config, new_delete_trail_lock); + set_delete_trail_lock(&mut self.locking_config, new_delete_trail_lock); } /// Update the `write_lock` locking configuration. public fun update_write_lock( - trail: &mut AuditTrail, + self: &mut AuditTrail, cap: &Capability, new_write_lock: TimeLock, clock: &Clock, ctx: &mut TxContext, ) { - assert!(trail.version == PACKAGE_VERSION, EPackageVersionMismatch); - trail + assert!(self.version == PACKAGE_VERSION, EPackageVersionMismatch); + self .roles .assert_capability_valid( cap, @@ -531,19 +538,19 @@ public fun update_write_lock( clock, ctx, ); - set_write_lock(&mut trail.locking_config, new_write_lock); + set_write_lock(&mut self.locking_config, new_write_lock); } /// Update the trail's mutable metadata public fun update_metadata( - trail: &mut AuditTrail, + self: &mut AuditTrail, cap: &Capability, new_metadata: Option, clock: &Clock, ctx: &mut TxContext, ) { - assert!(trail.version == PACKAGE_VERSION, EPackageVersionMismatch); - trail + assert!(self.version == PACKAGE_VERSION, EPackageVersionMismatch); + self .roles .assert_capability_valid( cap, @@ -551,23 +558,23 @@ public fun update_metadata( clock, ctx, ); - trail.updatable_metadata = new_metadata; + self.updatable_metadata = new_metadata; } // ===== Role and Capability Administration ===== /// Creates a new role with the provided permissions. public fun create_role( - trail: &mut AuditTrail, + self: &mut AuditTrail, cap: &Capability, role: String, permissions: VecSet, clock: &Clock, ctx: &mut TxContext, ) { - assert!(trail.version == PACKAGE_VERSION, EPackageVersionMismatch); + assert!(self.version == PACKAGE_VERSION, EPackageVersionMismatch); role_map::create_role( - trail.access_mut(), + self.access_mut(), cap, role, permissions, @@ -579,16 +586,16 @@ public fun create_role( /// Updates permissions for an existing role. public fun update_role_permissions( - trail: &mut AuditTrail, + self: &mut AuditTrail, cap: &Capability, role: String, new_permissions: VecSet, clock: &Clock, ctx: &mut TxContext, ) { - assert!(trail.version == PACKAGE_VERSION, EPackageVersionMismatch); + assert!(self.version == PACKAGE_VERSION, EPackageVersionMismatch); role_map::update_role( - trail.access_mut(), + self.access_mut(), cap, &role, new_permissions, @@ -600,21 +607,21 @@ public fun update_role_permissions( /// Deletes an existing role. public fun delete_role( - trail: &mut AuditTrail, + self: &mut AuditTrail, cap: &Capability, role: String, clock: &Clock, ctx: &mut TxContext, ) { - assert!(trail.version == PACKAGE_VERSION, EPackageVersionMismatch); - role_map::delete_role(trail.access_mut(), cap, &role, clock, ctx); + assert!(self.version == PACKAGE_VERSION, EPackageVersionMismatch); + role_map::delete_role(self.access_mut(), cap, &role, clock, ctx); } /// Issues a new capability for an existing role. /// /// The capability object is transferred to `issued_to` if provided, otherwise to the caller. public fun new_capability( - trail: &mut AuditTrail, + self: &mut AuditTrail, cap: &Capability, role: String, issued_to: Option
, @@ -623,7 +630,7 @@ public fun new_capability( clock: &Clock, ctx: &mut TxContext, ) { - assert!(trail.version == PACKAGE_VERSION, EPackageVersionMismatch); + assert!(self.version == PACKAGE_VERSION, EPackageVersionMismatch); let recipient = if (issued_to.is_some()) { let address_ref = issued_to.borrow(); @@ -633,7 +640,7 @@ public fun new_capability( }; let new_cap = role_map::new_capability( - trail.access_mut(), + self.access_mut(), cap, &role, issued_to, @@ -647,28 +654,36 @@ public fun new_capability( /// Revokes an issued capability by ID. public fun revoke_capability( - trail: &mut AuditTrail, + self: &mut AuditTrail, cap: &Capability, - capability_id: ID, + cap_to_revoke: ID, + cap_to_revoke_valid_until: Option, clock: &Clock, - ctx: &mut TxContext, + ctx: &TxContext, ) { - assert!(trail.version == PACKAGE_VERSION, EPackageVersionMismatch); - role_map::revoke_capability(trail.access_mut(), cap, capability_id, clock, ctx); + assert!(self.version == PACKAGE_VERSION, EPackageVersionMismatch); + role_map::revoke_capability( + self.access_mut(), + cap, + cap_to_revoke, + cap_to_revoke_valid_until, + clock, + ctx + ); } /// Destroys a capability object. /// /// Requires a capability with `RevokeCapabilities` permission. public fun destroy_capability( - trail: &mut AuditTrail, + self: &mut AuditTrail, cap: &Capability, cap_to_destroy: Capability, clock: &Clock, ctx: &mut TxContext, ) { - assert!(trail.version == PACKAGE_VERSION, EPackageVersionMismatch); - trail + assert!(self.version == PACKAGE_VERSION, EPackageVersionMismatch); + self .roles .assert_capability_valid( cap, @@ -676,7 +691,7 @@ public fun destroy_capability( clock, ctx, ); - role_map::destroy_capability(trail.access_mut(), cap_to_destroy); + role_map::destroy_capability(self.access_mut(), cap_to_destroy); } /// Destroys an initial admin capability. @@ -687,11 +702,11 @@ public fun destroy_capability( /// WARNING: If all initial admin capabilities are destroyed, the trail will be permanently /// sealed with no admin access possible. public fun destroy_initial_admin_capability( - trail: &mut AuditTrail, + self: &mut AuditTrail, cap_to_destroy: Capability, ) { - assert!(trail.version == PACKAGE_VERSION, EPackageVersionMismatch); - role_map::destroy_initial_admin_capability(trail.access_mut(), cap_to_destroy); + assert!(self.version == PACKAGE_VERSION, EPackageVersionMismatch); + role_map::destroy_initial_admin_capability(self.access_mut(), cap_to_destroy); } /// Revokes an initial admin capability by ID. @@ -701,111 +716,151 @@ public fun destroy_initial_admin_capability( /// WARNING: If all initial admin capabilities are revoked, the trail will be permanently /// sealed with no admin access possible. public fun revoke_initial_admin_capability( - trail: &mut AuditTrail, + self: &mut AuditTrail, cap: &Capability, - capability_id: ID, + cap_to_revoke: ID, + cap_to_revoke_valid_until: Option, clock: &Clock, ctx: &mut TxContext, ) { - assert!(trail.version == PACKAGE_VERSION, EPackageVersionMismatch); - role_map::revoke_initial_admin_capability(trail.access_mut(), cap, capability_id, clock, ctx); + assert!(self.version == PACKAGE_VERSION, EPackageVersionMismatch); + role_map::revoke_initial_admin_capability( + self.access_mut(), + cap, + cap_to_revoke, + cap_to_revoke_valid_until, + clock, + ctx); +} + +/// Remove expired entries from the `revoked_capabilities` denylist. +/// +/// Iterates through the revoked capabilities list and removes every entry whose +/// `valid_until` timestamp is **non-zero** and **less than** the current clock time, +/// because those capabilities are already naturally expired and no longer need to +/// occupy space in the denylist. +/// +/// Entries with `valid_until == 0` (i.e. capabilities that had no expiry) are kept, +/// since they remain potentially valid and must stay on the denylist. +/// +/// Parameters +/// ---------- +/// - cap: Reference to the capability used to authorize this operation. +/// Needs to grant the `CapabilityAdminPermissions::revoke` permission. +/// - clock: Reference to a Clock instance for obtaining the current timestamp. +/// - ctx: Reference to the transaction context. +/// +/// Errors: +/// - Aborts with any error documented by `assert_capability_valid` if the provided capability fails authorization checks. +public fun cleanup_revoked_capabilities( + self: &mut AuditTrail, + cap: &Capability, + clock: &Clock, + ctx: &TxContext, +) { + assert!(self.version == PACKAGE_VERSION, EPackageVersionMismatch); + self.access_mut().cleanup_revoked_capabilities( + cap, + clock, + ctx, + ); } // ===== Trail Query Functions ===== /// Get the total number of records currently in the trail -public fun record_count(trail: &AuditTrail): u64 { - linked_table::length(&trail.records) +public fun record_count(self: &AuditTrail): u64 { + linked_table::length(&self.records) } /// Get the next sequence number (monotonic counter, never decrements) -public fun sequence_number(trail: &AuditTrail): u64 { - trail.sequence_number +public fun sequence_number(self: &AuditTrail): u64 { + self.sequence_number } /// Get the trail creator address -public fun creator(trail: &AuditTrail): address { - trail.creator +public fun creator(self: &AuditTrail): address { + self.creator } /// Get the trail creation timestamp -public fun created_at(trail: &AuditTrail): u64 { - trail.created_at +public fun created_at(self: &AuditTrail): u64 { + self.created_at } /// Get the trail's object ID -public fun id(trail: &AuditTrail): ID { - object::uid_to_inner(&trail.id) +public fun id(self: &AuditTrail): ID { + object::uid_to_inner(&self.id) } /// Get the trail name -public fun name(trail: &AuditTrail): Option { - trail.immutable_metadata.map!(|metadata| metadata.name) +public fun name(self: &AuditTrail): Option { + self.immutable_metadata.map!(|metadata| metadata.name) } /// Get the trail description -public fun description(trail: &AuditTrail): Option { - if (trail.immutable_metadata.is_some()) { - option::borrow(&trail.immutable_metadata).description +public fun description(self: &AuditTrail): Option { + if (self.immutable_metadata.is_some()) { + option::borrow(&self.immutable_metadata).description } else { option::none() } } /// Get the updatable metadata -public fun metadata(trail: &AuditTrail): &Option { - &trail.updatable_metadata +public fun metadata(self: &AuditTrail): &Option { + &self.updatable_metadata } /// Get the locking configuration -public fun locking_config(trail: &AuditTrail): &LockingConfig { - &trail.locking_config +public fun locking_config(self: &AuditTrail): &LockingConfig { + &self.locking_config } /// Check if the trail is empty -public fun is_empty(trail: &AuditTrail): bool { - linked_table::is_empty(&trail.records) +public fun is_empty(self: &AuditTrail): bool { + linked_table::is_empty(&self.records) } /// Get the first sequence number -public fun first_sequence(trail: &AuditTrail): Option { - *linked_table::front(&trail.records) +public fun first_sequence(self: &AuditTrail): Option { + *linked_table::front(&self.records) } /// Get the last sequence number -public fun last_sequence(trail: &AuditTrail): Option { - *linked_table::back(&trail.records) +public fun last_sequence(self: &AuditTrail): Option { + *linked_table::back(&self.records) } // ===== Record Query Functions ===== /// Get a record by sequence number -public fun get_record(trail: &AuditTrail, sequence_number: u64): &Record { - assert!(trail.version == PACKAGE_VERSION, EPackageVersionMismatch); - assert!(linked_table::contains(&trail.records, sequence_number), ERecordNotFound); - linked_table::borrow(&trail.records, sequence_number) +public fun get_record(self: &AuditTrail, sequence_number: u64): &Record { + assert!(self.version == PACKAGE_VERSION, EPackageVersionMismatch); + assert!(linked_table::contains(&self.records, sequence_number), ERecordNotFound); + linked_table::borrow(&self.records, sequence_number) } /// Check if a record exists at the given sequence number -public fun has_record(trail: &AuditTrail, sequence_number: u64): bool { - assert!(trail.version == PACKAGE_VERSION, EPackageVersionMismatch); - linked_table::contains(&trail.records, sequence_number) +public fun has_record(self: &AuditTrail, sequence_number: u64): bool { + assert!(self.version == PACKAGE_VERSION, EPackageVersionMismatch); + linked_table::contains(&self.records, sequence_number) } /// Returns all records of the audit trail -public fun records(trail: &AuditTrail): &LinkedTable> { - assert!(trail.version == PACKAGE_VERSION, EPackageVersionMismatch); - &trail.records +public fun records(self: &AuditTrail): &LinkedTable> { + assert!(self.version == PACKAGE_VERSION, EPackageVersionMismatch); + &self.records } // ===== Access Control Functions ===== -/// Returns the RoleMap managing access for the audit trail. +/// Returns a reference to the RoleMap managing access (roles and capabilities) for the audit trail. public fun access(trail: &AuditTrail): &RoleMap { assert!(trail.version == PACKAGE_VERSION, EPackageVersionMismatch); &trail.roles } -/// Returns a mutable reference to the RoleMap managing access for the audit trail. +/// Returns a mutable reference to the RoleMap managing access (roles and capabilities) for the audit trail. public fun access_mut(trail: &mut AuditTrail): &mut RoleMap { assert!(trail.version == PACKAGE_VERSION, EPackageVersionMismatch); &mut trail.roles diff --git a/audit-trail-move/sources/record.move b/audit-trail-move/sources/record.move index 69ead0fd..987490e6 100644 --- a/audit-trail-move/sources/record.move +++ b/audit-trail-move/sources/record.move @@ -50,37 +50,37 @@ public(package) fun new( // ===== Getters ===== /// Get the stored data from a record -public fun data(record: &Record): &D { - &record.data +public fun data(self: &Record): &D { + &self.data } /// Get the record metadata -public fun metadata(record: &Record): &Option { - &record.metadata +public fun metadata(self: &Record): &Option { + &self.metadata } /// Get the record sequence number -public fun sequence_number(record: &Record): u64 { - record.sequence_number +public fun sequence_number(self: &Record): u64 { + self.sequence_number } /// Get who added the record -public fun added_by(record: &Record): address { - record.added_by +public fun added_by(self: &Record): address { + self.added_by } /// Get when the record was added (milliseconds) -public fun added_at(record: &Record): u64 { - record.added_at +public fun added_at(self: &Record): u64 { + self.added_at } /// Get the correction tracker for this record -public fun correction(record: &Record): &RecordCorrection { - &record.correction +public fun correction(self: &Record): &RecordCorrection { + &self.correction } /// Destroy a record -public(package) fun destroy(record: Record) { +public(package) fun destroy(self: Record) { let Record { data: _, metadata: _, @@ -88,7 +88,7 @@ public(package) fun destroy(record: Record) { added_by: _, added_at: _, correction: _, - } = record; + } = self; } /// Bidirectional correction tracking for audit records diff --git a/audit-trail-move/tests/capability_tests.move b/audit-trail-move/tests/capability_tests.move index 43640af9..72440b15 100644 --- a/audit-trail-move/tests/capability_tests.move +++ b/audit-trail-move/tests/capability_tests.move @@ -150,15 +150,11 @@ fun test_new_capability() { cleanup_capability_trail_and_clock(&scenario, admin_cap, trail, clock); }; - // Issue first capability and verify it's tracked + // Issue first capability ts::next_tx(&mut scenario, admin_user); let cap1_id = { let (admin_cap, mut trail, clock) = fetch_capability_trail_and_clock(&mut scenario); - // Verify initial state - only admin capability should be tracked - let initial_cap_count = trail.access().issued_capabilities().size(); - assert!(initial_cap_count == 1, 0); // Only admin cap - let cap1 = test_utils::new_capability_without_restrictions( trail.access_mut(), &admin_cap, @@ -171,23 +167,17 @@ fun test_new_capability() { let cap1_id = object::id(&cap1); - // Verify capability ID is tracked in issued_capabilities - assert!(trail.access().issued_capabilities().size() == initial_cap_count + 1, 3); - assert!(trail.access().issued_capabilities().contains(&cap1_id), 4); - transfer::public_transfer(cap1, user1); cleanup_capability_trail_and_clock(&scenario, admin_cap, trail, clock); cap1_id }; - // Issue second capability and verify both are tracked with unique IDs + // Issue second capability and verify both have unique IDs ts::next_tx(&mut scenario, admin_user); let _cap2_id = { let (admin_cap, mut trail, clock) = fetch_capability_trail_and_clock(&mut scenario); - let previous_cap_count = trail.access().issued_capabilities().size(); - let cap2 = test_utils::new_capability_without_restrictions( trail.access_mut(), &admin_cap, @@ -198,13 +188,8 @@ fun test_new_capability() { let cap2_id = object::id(&cap2); - // Verify both capabilities are tracked - assert!(trail.access().issued_capabilities().size() == previous_cap_count + 1, 5); - assert!(trail.access().issued_capabilities().contains(&cap1_id), 6); - assert!(trail.access().issued_capabilities().contains(&cap2_id), 7); - // Verify capabilities have unique IDs - assert!(cap1_id != cap2_id, 8); + assert!(cap1_id != cap2_id, 3); transfer::public_transfer(cap2, user2); cleanup_capability_trail_and_clock(&scenario, admin_cap, trail, clock); @@ -255,16 +240,15 @@ fun test_revoke_capability() { (cap1_id, cap2_id) }; - // Test: Revoke first capability and verify it's removed from tracking + // Test: Revoke first capability and verify it's tracked in the deny list ts::next_tx(&mut scenario, admin_user); { let (admin_cap, mut trail, clock) = fetch_capability_trail_and_clock(&mut scenario); let cap1 = ts::take_from_address(&scenario, user1); - // Verify both capabilities are tracked before revocation - let cap_count_before = trail.access().issued_capabilities().size(); - assert!(trail.access().issued_capabilities().contains(&cap1_id), 0); - assert!(trail.access().issued_capabilities().contains(&cap2_id), 1); + // Verify the deny list is empty before revocation + let cap_count_before = trail.access().revoked_capabilities().length(); + assert!(cap_count_before == 0, 0); // Revoke the capability trail @@ -272,15 +256,14 @@ fun test_revoke_capability() { .revoke_capability( &admin_cap, cap1.id(), + std::option::none(), &clock, ts::ctx(&mut scenario), ); - // Verify capability was removed from tracking - assert!(trail.access().issued_capabilities().size() == cap_count_before - 1, 2); - assert!(!trail.access().issued_capabilities().contains(&cap1_id), 3); - // Verify other capability is still tracked - assert!(trail.access().issued_capabilities().contains(&cap2_id), 4); + // Verify capability has been added to the deny list + assert!(trail.access().revoked_capabilities().length() == cap_count_before + 1, 1); + assert!(trail.access().revoked_capabilities().contains(cap1_id), 2); ts::return_to_address(user1, cap1); cleanup_capability_trail_and_clock(&scenario, admin_cap, trail, clock); @@ -289,7 +272,7 @@ fun test_revoke_capability() { // Verify revoked capability object still exists (just invalidated) ts::next_tx(&mut scenario, user1); { - assert!(ts::has_most_recent_for_sender(&scenario), 5); + assert!(ts::has_most_recent_for_sender(&scenario), 3); }; // Test: Revoke second capability @@ -298,20 +281,22 @@ fun test_revoke_capability() { let (admin_cap, mut trail, clock) = fetch_capability_trail_and_clock(&mut scenario); let cap2 = ts::take_from_address(&scenario, user2); - let cap_count_before = trail.access().issued_capabilities().size(); + let cap_count_before = trail.access().revoked_capabilities().length(); + assert!(cap_count_before == 1, 4); // only the first revoked capability (cap1) should be in the list trail .access_mut() .revoke_capability( &admin_cap, cap2.id(), + std::option::none(), &clock, ts::ctx(&mut scenario), ); - // Verify capability was removed from tracking - assert!(trail.access().issued_capabilities().size() == cap_count_before - 1, 6); - assert!(!trail.access().issued_capabilities().contains(&cap2_id), 7); + // Verify capability has been added to the deny list + assert!(trail.access().revoked_capabilities().length() == cap_count_before + 1, 5); + assert!(trail.access().revoked_capabilities().contains(cap2_id), 6); ts::return_to_address(user2, cap2); cleanup_capability_trail_and_clock(&scenario, admin_cap, trail, clock); @@ -332,7 +317,7 @@ fun test_destroy_capability() { // Issue two capabilities ts::next_tx(&mut scenario, admin_user); - let (cap1_id, cap2_id) = { + let (_cap1_id, _cap2_id) = { let (admin_cap, mut trail, clock) = fetch_capability_trail_and_clock(&mut scenario); let cap1 = test_utils::new_capability_without_restrictions( @@ -366,28 +351,16 @@ fun test_destroy_capability() { let mut trail = ts::take_shared>(&scenario); let cap1 = ts::take_from_sender(&scenario); - // Verify both capabilities are tracked before destruction - let cap_count_before = trail.access().issued_capabilities().size(); - assert!(trail.access().issued_capabilities().contains(&cap1_id), 0); - assert!(trail.access().issued_capabilities().contains(&cap2_id), 1); - // Destroy the capability trail.access_mut().destroy_capability(cap1); - // Verify capability was removed from tracking - assert!(trail.access().issued_capabilities().size() == cap_count_before - 1, 2); - assert!(!trail.access().issued_capabilities().contains(&cap1_id), 3); - - // Verify other capability is still tracked - assert!(trail.access().issued_capabilities().contains(&cap2_id), 4); - ts::return_shared(trail); }; // Verify destroyed capability no longer exists ts::next_tx(&mut scenario, user1); { - assert!(!ts::has_most_recent_for_sender(&scenario), 5); + assert!(!ts::has_most_recent_for_sender(&scenario), 0); }; // Test: User2 destroys their own capability @@ -396,26 +369,15 @@ fun test_destroy_capability() { let mut trail = ts::take_shared>(&scenario); let cap2 = ts::take_from_sender(&scenario); - let cap_count_before = trail.access().issued_capabilities().size(); - trail.access_mut().destroy_capability(cap2); - // Verify capability was removed from tracking - assert!(trail.access().issued_capabilities().size() == cap_count_before - 1, 6); - assert!(!trail.access().issued_capabilities().contains(&cap2_id), 7); - ts::return_shared(trail); }; - // Verify only admin capability remains - ts::next_tx(&mut scenario, admin_user); + // Verify destroyed capability no longer exists + ts::next_tx(&mut scenario, user2); { - let trail = ts::take_shared>(&scenario); - - // Only the initial admin capability should remain - assert!(trail.access().issued_capabilities().size() == 1, 8); - - ts::return_shared(trail); + assert!(!ts::has_most_recent_for_sender(&scenario), 1); }; ts::end(scenario); @@ -427,7 +389,7 @@ fun test_destroy_capability() { /// - Multiple capabilities can be created for different roles /// - Capabilities can be used to perform authorized actions /// - Capabilities can be revoked or destroyed -/// - issued_capabilities tracking remains accurate throughout the lifecycle +/// - revoked_capabilities tracking remains accurate throughout the lifecycle #[test] fun test_capability_lifecycle() { let admin_user = @0xAD; @@ -444,9 +406,6 @@ fun test_capability_lifecycle() { { let (admin_cap, mut trail, clock) = fetch_capability_trail_and_clock(&mut scenario); - // Initially only admin cap should be tracked - assert!(trail.access().issued_capabilities().size() == 1, 0); - trail .access_mut() .create_role( @@ -463,7 +422,7 @@ fun test_capability_lifecycle() { // Issue capabilities ts::next_tx(&mut scenario, admin_user); - let (record_cap_id, role_cap_id) = { + let (_record_cap_id, role_cap_id) = { let (admin_cap, mut trail, clock) = fetch_capability_trail_and_clock(&mut scenario); let record_cap = test_utils::new_capability_without_restrictions( @@ -486,11 +445,6 @@ fun test_capability_lifecycle() { let role_cap_id = object::id(&role_cap); transfer::public_transfer(role_cap, role_admin_user); - // Verify all capabilities are tracked - assert!(trail.access().issued_capabilities().size() == 3, 1); // admin + record + role - assert!(trail.access().issued_capabilities().contains(&record_cap_id), 2); - assert!(trail.access().issued_capabilities().contains(&role_cap_id), 3); - cleanup_capability_trail_and_clock(&scenario, admin_cap, trail, clock); (record_cap_id, role_cap_id) @@ -523,10 +477,6 @@ fun test_capability_lifecycle() { trail.access_mut().destroy_capability(record_cap); - // Verify capability was removed - assert!(trail.access().issued_capabilities().size() == 2, 4); // admin + role - assert!(!trail.access().issued_capabilities().contains(&record_cap_id), 5); - ts::return_shared(trail); }; @@ -536,18 +486,22 @@ fun test_capability_lifecycle() { let (admin_cap, mut trail, clock) = fetch_capability_trail_and_clock(&mut scenario); let role_cap = ts::take_from_address(&scenario, role_admin_user); + // Initially the deny list should be empty + assert!(trail.access().revoked_capabilities().length() == 0, 0); + trail .access_mut() .revoke_capability( &admin_cap, role_cap.id(), + std::option::none(), &clock, ts::ctx(&mut scenario), ); - // Verify capability was removed - assert!(trail.access().issued_capabilities().size() == 1, 6); // only admin remains - assert!(!trail.access().issued_capabilities().contains(&role_cap_id), 7); + // Verify role_cap is in the deny list now + assert!(trail.access().revoked_capabilities().length() == 1, 1); + assert!(trail.access().revoked_capabilities().contains(role_cap_id), 2); ts::return_to_address(role_admin_user, role_cap); @@ -694,7 +648,13 @@ fun test_revoked_capability_cannot_be_used() { trail .access_mut() - .revoke_capability(&admin_cap, user_cap.id(), &clock, ts::ctx(&mut scenario)); + .revoke_capability( + &admin_cap, + user_cap.id(), + std::option::none(), + &clock, + ts::ctx(&mut scenario), + ); ts::return_to_address(user, user_cap); cleanup_capability_trail_and_clock(&scenario, admin_cap, trail, clock); @@ -843,7 +803,13 @@ fun test_revoke_capability_permission_denied() { trail .access_mut() - .revoke_capability(&user1_cap, user2_cap.id(), &clock, ts::ctx(&mut scenario)); + .revoke_capability( + &user1_cap, + user2_cap.id(), + std::option::none(), + &clock, + ts::ctx(&mut scenario), + ); ts::return_to_address(user2, user2_cap); ts::return_to_sender(&scenario, user1_cap); diff --git a/audit-trail-move/tests/metadata_tests.move b/audit-trail-move/tests/metadata_tests.move index f690efb4..10070682 100644 --- a/audit-trail-move/tests/metadata_tests.move +++ b/audit-trail-move/tests/metadata_tests.move @@ -14,6 +14,7 @@ use audit_trail::{ }; use iota::test_scenario as ts; use std::string; +use std::option::none; use tf_components::{capability::Capability, timelock}; // ===== Success Case Tests ===== @@ -274,7 +275,7 @@ fun test_update_metadata_revoked_capability() { trail .access_mut() - .revoke_capability(&admin_cap, metadata_cap.id(), &clock, ts::ctx(&mut scenario)); + .revoke_capability(&admin_cap, metadata_cap.id(), none(), &clock, ts::ctx(&mut scenario)); ts::return_to_address(metadata_admin_user, metadata_cap); cleanup_capability_trail_and_clock(&scenario, admin_cap, trail, clock);