Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 72 additions & 9 deletions contract/contracts/hello-world/src/autoshare_logic.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
use crate::base::errors::Error;
use crate::base::events::{
AdminTransferred, AuthorizationFailure, AutoshareCreated, AutoshareUpdated, CategoryRegistered,
ContractPaused, ContractUnpaused, GroupActivated, GroupDeactivated, NotificationCategory,
NotificationDelivered, NotificationExpired, NotificationExtended, NotificationLimitsConfigured,
NotificationPriority, NotificationRecalled, NotificationRevoked, NotificationScheduled,
ScheduledNotificationCancelled, Withdrawal,
AdminTransferred, AuthorizationFailure, AutoshareCreated, AutoshareUpdated, ContractPaused,
ContractUnpaused, GroupActivated, GroupDeactivated, NotificationAcknowledged,
NotificationCategory, NotificationExpired, NotificationPriority, NotificationRevoked,
NotificationScheduled, ScheduledNotificationCancelled, Withdrawal,
AdminTransferred, AuditAction, AuditRecordAppended, AuthorizationFailure, AutoshareCreated,
AutoshareUpdated, BatchNotificationsCreated, BatchProcessingCompleted, CategoryRegistered,
ContractPaused, ContractUnpaused, GroupActivated, GroupDeactivated, NotificationAccessed,
NotificationAcknowledged, NotificationCategory, NotificationDelivered, NotificationExpired,
NotificationExtended, NotificationLimitsConfigured, NotificationPriority, NotificationRecalled,
NotificationRevoked, NotificationScheduled, ScheduledNotificationCancelled,
SchemaVersionSet, SubscriptionCancelled, Withdrawal,
AutoshareUpdated, BatchNotificationsCreated, CategoryRegistered, ContractPaused,
ContractUnpaused, GroupActivated, GroupDeactivated, NotificationCategory, NotificationExpired,
NotificationPriority, NotificationRevoked, NotificationScheduled, OwnershipTransferInitiated,
Expand Down Expand Up @@ -751,6 +748,72 @@ pub fn topup_subscription(
Ok(())
}

/// Cancels an active notification subscription for a group and emits a
/// [`SubscriptionCancelled`] event so off-chain consumers can track the full
/// subscription lifecycle.
///
/// The subscriber must be the group's creator or an authorised member of the
/// group. Cancellation marks the group as inactive (deactivates it) and zeroes
/// out the remaining usage count so no further notifications can be dispatched
/// against the subscription.
///
/// # Errors
/// - [`Error::ContractPaused`] – the contract is currently paused.
/// - [`Error::NotFound`] – `id` does not correspond to a known group.
/// - [`Error::Unauthorized`] – `subscriber` is neither the creator nor a
/// member of the group.
/// - [`Error::GroupInactive`] – the group is already inactive (subscription
/// already cancelled or never active).
pub fn cancel_subscription(
env: Env,
id: BytesN<32>,
subscriber: Address,
) -> Result<(), Error> {
subscriber.require_auth();

if get_paused_status(&env) {
return Err(Error::ContractPaused);
}

let key = DataKey::AutoShare(id.clone());
let mut details: AutoShareDetails = env
.storage()
.persistent()
.get(&key)
.ok_or(Error::NotFound)?;

// Only the creator or a current member may cancel the subscription.
let is_creator = details.creator == subscriber;
let is_member = details.members.iter().any(|m| m.address == subscriber);

if !is_creator && !is_member {
publish_authorization_failure(&env, &subscriber, "cancel_subscription");
return Err(Error::Unauthorized);
}

if !details.is_active {
return Err(Error::GroupInactive);
}

// Deactivate the group and clear remaining usages.
details.is_active = false;
details.usage_count = 0;
env.storage().persistent().set(&key, &details);

let cancelled_at = env.ledger().timestamp();

SubscriptionCancelled {
group_id: id,
subscriber,
category: NotificationCategory::Group,
priority: NotificationPriority::Medium,
cancelled_at,
}
.publish(&env);

Ok(())
}

// ============================================================================
// Payment History
// ============================================================================
Expand Down
17 changes: 17 additions & 0 deletions contract/contracts/hello-world/src/base/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,21 @@ pub struct NotificationAcknowledged {
pub timestamp: u64,
}

/// Emitted when a subscriber cancels an active notification subscription.
///
/// Off-chain consumers can key off `(group_id, subscriber)` to track the full
/// subscription lifecycle. The `group_id` identifies the AutoShare group whose
/// subscription was cancelled; `subscriber` is the address that initiated the
/// cancellation.
#[contractevent(data_format = "single-value")]
#[derive(Clone)]
pub struct SubscriptionCancelled {
/// The group whose subscription was cancelled.
#[topic]
pub group_id: BytesN<32>,
/// The address that cancelled the subscription.
#[topic]
pub subscriber: Address,
/// Emitted when the current owner initiates a two-step ownership transfer by
/// nominating a `pending_owner`. The transfer is not final until the pending
/// owner calls `accept_ownership`.
Expand Down Expand Up @@ -499,5 +514,7 @@ pub struct OwnershipTransferred {
pub category: NotificationCategory,
#[topic]
pub priority: NotificationPriority,
/// Ledger timestamp (seconds) when the cancellation occurred.
pub cancelled_at: u64,
pub new_owner: Address,
}
13 changes: 13 additions & 0 deletions contract/contracts/hello-world/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,16 @@ impl AutoShareContract {
.unwrap();
}

/// Cancels an active notification subscription for a group.
///
/// The `subscriber` must be the group's creator or a current member. On
/// success the group is deactivated, its remaining usage count is zeroed, and
/// a `SubscriptionCancelled` event is emitted so off-chain consumers can
/// track the full subscription lifecycle.
pub fn cancel_subscription(env: Env, id: BytesN<32>, subscriber: Address) {
autoshare_logic::cancel_subscription(env, id, subscriber).unwrap();
}

// ============================================================================
// Payment History
// ============================================================================
Expand Down Expand Up @@ -712,4 +722,7 @@ mod tests {

#[path = "../tests/access_log_test.rs"]
mod access_log_test;

#[path = "../tests/subscription_cancellation_test.rs"]
mod subscription_cancellation_test;
}
Loading
Loading