Skip to content
Open
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
64 changes: 41 additions & 23 deletions contract/contracts/hello-world/src/autoshare_logic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ use crate::base::events::{
ContractPaused, ContractUnpaused, GroupActivated, GroupDeactivated, NotificationAccessed,
NotificationAcknowledged, NotificationCategory, NotificationDelivered, NotificationExpired,
NotificationExtended, NotificationLimitsConfigured, NotificationPriority, NotificationRecalled,
NotificationRevoked, NotificationScheduled, SchemaVersionSet, ScheduledNotificationCancelled,
Withdrawal,
NotificationRevoked, NotificationScheduled, ScheduledNotificationCancelled,
SchemaVersionSet, SubscriptionCancelled, Withdrawal,
AutoshareUpdated, BatchNotificationsCreated, CategoryRegistered, ContractPaused,
Expand Down Expand Up @@ -274,7 +276,7 @@ pub fn add_group_member(

// Add new member
details.members.push_back(GroupMember {
address: member_address.clone(),
address: address.clone(),
percentage,
});

Expand All @@ -291,7 +293,7 @@ pub fn add_group_member(
.get(&members_key)
.unwrap_or(Vec::new(&env));
stored_members.push_back(GroupMember {
address: member_address,
address,
percentage,
});
env.storage()
Expand Down Expand Up @@ -1455,6 +1457,10 @@ pub fn batch_schedule_notifications(
expires_at,
revoked_by: None,
revoked_at: None,
delivered: false,
delivered_at: None,
recalled_by: None,
recalled_at: None,
title,
};
let key = DataKey::ScheduledNotification(id.clone());
Expand Down Expand Up @@ -1795,27 +1801,6 @@ pub fn acknowledge_notifications(
env: Env,
caller: Address,
notification_ids: Vec<BytesN<32>>,
/// Emits a `BatchProcessingCompleted` event for off-chain consumers.
pub fn emit_batch_completed(env: Env, batch_id: BytesN<32>, processed_count: u32) -> Result<(), Error> {
BatchProcessingCompleted {
batch_id,
category: NotificationCategory::Notification,
priority: NotificationPriority::Medium,
processed_count,
}
.publish(&env);
Ok(())
}
/// Extends the expiration period of a scheduled notification by `extension_seconds`.
///
/// Only authorized callers (the notification creator or the contract admin) can
/// extend a notification. The notification must exist, not already be revoked,
/// and not have expired. Emits a [`NotificationExtended`] event.
pub fn extend_notification_expiry(
env: Env,
notification_id: BytesN<32>,
caller: Address,
extension_seconds: u64,
) -> Result<(), Error> {
caller.require_auth();

Expand Down Expand Up @@ -1849,6 +1834,39 @@ pub fn extend_notification_expiry(
}
.publish(&env);
}

Ok(())
}

/// Emits a `BatchProcessingCompleted` event for off-chain consumers.
pub fn emit_batch_completed(env: Env, batch_id: BytesN<32>, processed_count: u32) -> Result<(), Error> {
BatchProcessingCompleted {
batch_id,
category: NotificationCategory::Notification,
priority: NotificationPriority::Medium,
processed_count,
}
.publish(&env);
Ok(())
}

/// Extends the expiration period of a scheduled notification by `extension_seconds`.
///
/// Only authorized callers (the notification creator or the contract admin) can
/// extend a notification. The notification must exist, not already be revoked,
/// and not have expired. Emits a [`NotificationExtended`] event.
pub fn extend_notification_expiry(
env: Env,
notification_id: BytesN<32>,
caller: Address,
extension_seconds: u64,
) -> Result<(), Error> {
caller.require_auth();

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

if extension_seconds == 0 {
return Err(Error::InvalidExpirationDuration);
}
Expand Down
7 changes: 4 additions & 3 deletions contract/contracts/hello-world/src/base/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,10 @@ pub enum Error {
/// Triggered when the caller is not authorized to acknowledge a notification.
NotAuthorizedToAcknowledge = 29,
AlreadyRevoked = 29,
/// Triggered when the caller is not authorized to acknowledge a notification.
NotAuthorizedToAcknowledge = 30,
/// Triggered when an invalid limit configuration is provided.
InvalidLimit = 29,
InvalidLimit = 31,
/// Triggered when a notification has already been delivered and cannot be recalled.
NotificationDelivered = 30,
InvalidLimit = 30,
NotificationDelivered = 32,
}
34 changes: 22 additions & 12 deletions contract/contracts/hello-world/src/base/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,13 @@ pub struct NotificationRevoked {
pub struct BatchProcessingCompleted {
#[topic]
pub batch_id: BytesN<32>,
#[topic]
pub category: NotificationCategory,
#[topic]
pub priority: NotificationPriority,
pub processed_count: u32,
}

/// Emitted when a scheduled notification's expiry period is extended by an authorized sender.
#[contractevent(data_format = "single-value")]
#[derive(Clone)]
Expand All @@ -366,11 +373,20 @@ pub struct NotificationExtended {

/// Emitted when a sender's reputation score is updated.
/// Triggered by successful or failed notification delivery.
#[contractevent(data_format = "single-value")]
#[contractevent]
#[derive(Clone)]
pub struct ReputationUpdated {
#[topic]
pub sender: Address,
#[topic]
pub category: NotificationCategory,
#[topic]
pub priority: NotificationPriority,
pub new_score: i64,
pub successful_count: u32,
pub failed_count: u32,
}

/// Emitted when protocol-level notification limits are configured or updated.
#[contractevent]
#[derive(Clone)]
Expand All @@ -381,13 +397,14 @@ pub struct NotificationLimitsConfigured {
pub category: NotificationCategory,
#[topic]
pub priority: NotificationPriority,
pub new_score: i64,
pub successful_count: u32,
pub failed_count: u32,
pub max_payload_size: u32,
pub max_expiration_seconds: u64,
pub min_expiration_seconds: u64,
pub max_batch_size: u32,
}

/// Emitted when a sender's reputation tier changes (e.g., from Bronze to Silver).
#[contractevent(data_format = "single-value")]
#[contractevent]
#[derive(Clone)]
pub struct ReputationTierChanged {
#[topic]
Expand All @@ -401,13 +418,6 @@ pub struct ReputationTierChanged {
pub reputation_score: i64,
}

pub processed_count: u32,
pub max_payload_size: u32,
pub max_expiration_seconds: u64,
pub min_expiration_seconds: u64,
pub max_batch_size: u32,
}

// ============================================================================
// Schema Version Tracking (Issue #309)
// ============================================================================
Expand Down
12 changes: 9 additions & 3 deletions contract/contracts/hello-world/src/base/reputation.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use soroban_sdk::{contracttype, Address, Env};
use soroban_sdk::{contracttype, Address};

/// Sender reputation score and metrics.
///
Expand Down Expand Up @@ -59,7 +59,11 @@ pub fn calculate_reputation_score(successful: u32, failed: u32) -> i64 {
}

let success_rate = (successful as f64 / total as f64) * 100.0;
let score = (success_rate / 2.0) as i64 + 25;
// Quadratic curve: a sender's score is disproportionately punished for a
// low success rate (e.g. 50% success only yields a score of 25, not 50),
// so the tier system rewards consistent reliability rather than a merely
// average delivery record.
let score = ((success_rate * success_rate) / 100.0) as i64;

// Clamp score to valid range
if score > MAX_REPUTATION_SCORE {
Expand Down Expand Up @@ -126,6 +130,7 @@ impl SenderReputation {
#[cfg(test)]
mod tests {
use super::*;
use soroban_sdk::{testutils::Address as _, Env};

#[test]
fn test_reputation_tier_classification() {
Expand Down Expand Up @@ -156,7 +161,8 @@ mod tests {

#[test]
fn test_sender_reputation_tracking() {
let sender = Address::random(&Default::default());
let env = Env::default();
let sender = Address::generate(&env);
let mut rep = SenderReputation::new(sender.clone(), 1000);

assert_eq!(rep.reputation_score, INITIAL_REPUTATION_SCORE);
Expand Down
13 changes: 11 additions & 2 deletions contract/contracts/hello-world/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,8 @@ impl AutoShareContract {
/// The notification must exist, not already be revoked or expired, and not yet be delivered.
pub fn recall_notification(env: Env, notification_id: BytesN<32>, caller: Address) {
autoshare_logic::recall_notification(env, notification_id, caller).unwrap();
}

/// Emits a `BatchProcessingCompleted` event for off-chain listeners.
pub fn emit_batch_completed(env: Env, batch_id: BytesN<32>, processed_count: u32) {
autoshare_logic::emit_batch_completed(env, batch_id, processed_count).unwrap();
Expand Down Expand Up @@ -527,6 +529,8 @@ impl AutoShareContract {
/// Acknowledges multiple scheduled notifications in a single batch.
pub fn acknowledge_notifications(env: Env, caller: Address, notification_ids: Vec<BytesN<32>>) {
autoshare_logic::acknowledge_notifications(env, caller, notification_ids).unwrap();
}

/// Extends the expiration period of a scheduled notification by `extension_seconds`.
///
/// Only the notification creator or the contract admin can extend it.
Expand Down Expand Up @@ -584,13 +588,13 @@ impl AutoShareContract {

/// Record a successful notification delivery for a sender.
/// Updates the sender's reputation score based on delivery history.
pub fn record_delivery_success(env: Env, sender: Address) {
pub fn record_sender_delivery_success(env: Env, sender: Address) {
reputation_logic::record_successful_delivery(&env, &sender).unwrap();
}

/// Record a failed notification delivery for a sender.
/// Decreases the sender's reputation score based on delivery history.
pub fn record_delivery_failure(env: Env, sender: Address) {
pub fn record_sender_delivery_failure(env: Env, sender: Address) {
reputation_logic::record_failed_delivery(&env, &sender).unwrap();
}

Expand Down Expand Up @@ -723,6 +727,11 @@ mod tests {
#[path = "../tests/access_log_test.rs"]
mod access_log_test;

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

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