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
16 changes: 14 additions & 2 deletions .github/workflows/preview.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,18 @@ jobs:
VITE_STELLAR_NETWORK: TESTNET
run: npm run build

- name: Check for Cloudflare credentials
id: cf_check
run: |
if [ -n "${{ secrets.CLOUDFLARE_API_TOKEN }}" ] && [ -n "${{ secrets.CLOUDFLARE_ACCOUNT_ID }}" ]; then
echo "has_creds=true" >> "$GITHUB_OUTPUT"
else
echo "has_creds=false" >> "$GITHUB_OUTPUT"
fi

- name: Deploy to Cloudflare Pages
id: deploy
if: steps.cf_check.outputs.has_creds == 'true'
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
Expand All @@ -49,6 +59,7 @@ jobs:
- name: Post preview URL comment
uses: actions/github-script@v7
env:
HAS_CREDS: ${{ steps.cf_check.outputs.has_creds }}
DEPLOYMENT_URL: ${{ steps.deploy.outputs.deployment-url }}
PR_NUMBER: ${{ github.event.pull_request.number }}
COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
Expand All @@ -64,15 +75,16 @@ jobs:
const existing = comments.find(c => c.body.includes(marker));

const short = process.env.COMMIT_SHA.slice(0, 7);
const hasCreds = process.env.HAS_CREDS === 'true';
const body = [
marker,
'## 🚀 Preview Deployment',
'',
`| | |`,
`|---|---|`,
`| **URL** | ${process.env.DEPLOYMENT_URL} |`,
`| **URL** | ${hasCreds ? process.env.DEPLOYMENT_URL : '_unavailable for this PR_'} |`,
`| **Commit** | \`${short}\` |`,
`| **Status** | ✅ Ready |`,
`| **Status** | ${hasCreds ? '✅ Ready' : '⚠️ Skipped — Cloudflare credentials are not available to PRs from forks'} |`,
'',
'_This comment is updated automatically on every push to the PR._',
].join('\n');
Expand Down
15 changes: 6 additions & 9 deletions contract/contracts/hello-world/src/autoshare_logic.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
use crate::base::errors::Error;
use crate::base::events::{
AdminTransferred, AuditAction, AuditRecordAppended, AuthorizationFailure, AutoshareCreated,
AutoshareUpdated, BatchNotificationsCreated, CategoryRegistered, ContractPaused,
ContractUnpaused, GroupActivated, GroupDeactivated, NotificationCategory, NotificationExpired,
AutoshareUpdated, BatchNotificationsCreated, BatchProcessingCompleted, CategoryRegistered,
ContractPaused, ContractUnpaused, GroupActivated, GroupDeactivated, NotificationAccessed,
NotificationCategory, NotificationExpired, NotificationExtended, NotificationLimitsConfigured,
NotificationPriority, NotificationRevoked, NotificationScheduled,
NotificationExtended, NotificationPriority, NotificationRevoked, NotificationScheduled,
ScheduledNotificationCancelled, Withdrawal,
NotificationPriority, NotificationRevoked, NotificationScheduled, ScheduledNotificationCancelled,
Withdrawal, BatchProcessingCompleted,
NotificationExtended, NotificationLimitsConfigured, NotificationPriority, NotificationRevoked,
NotificationScheduled, ScheduledNotificationCancelled, Withdrawal,
SchemaVersionSet, NotificationAccessed,
ScheduledNotificationCancelled, SchemaVersionSet, Withdrawal,
};
use crate::base::types::{
AuditRecord, AutoShareDetails, GroupMember, NotificationLimits, PaymentHistory,
Expand Down Expand Up @@ -59,6 +54,8 @@ pub enum DataKey {
RegisteredCategories,
/// Stores the current on-chain notification schema version.
SchemaVersion,
/// Per-sender reputation record.
Reputation(Address),
}

// ============================================================================
Expand Down
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 @@ -319,6 +319,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 @@ -336,11 +343,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 @@ -351,13 +367,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 @@ -371,13 +388,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
4 changes: 3 additions & 1 deletion contract/contracts/hello-world/src/base/reputation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ impl SenderReputation {
#[cfg(test)]
mod tests {
use super::*;
use soroban_sdk::testutils::Address as _;

#[test]
fn test_reputation_tier_classification() {
Expand Down Expand Up @@ -156,7 +157,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
7 changes: 6 additions & 1 deletion contract/contracts/hello-world/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,8 @@ impl AutoShareContract {
/// 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();
}

// ============================================================================
// Batch Notification Creation
// ============================================================================
Expand Down Expand Up @@ -520,7 +522,7 @@ impl AutoShareContract {

/// 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_reputation_failure(env: Env, sender: Address) {
reputation_logic::record_failed_delivery(&env, &sender).unwrap();
}

Expand Down Expand Up @@ -638,4 +640,7 @@ mod tests {

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

#[path = "../tests/event_emission_test.rs"]
mod event_emission_test;
}
92 changes: 41 additions & 51 deletions contract/contracts/hello-world/src/reputation_logic.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
use crate::autoshare_logic::DataKey;
use crate::base::events::{NotificationCategory, NotificationPriority, ReputationUpdated, ReputationTierChanged};
use crate::base::reputation::{SenderReputation, INITIAL_REPUTATION_SCORE};
use soroban_sdk::{Address, Env, Symbol, storage::Persistent, String as SorobanString, Error};

const REPUTATION_KEY_PREFIX: &str = "reputation_";
use crate::base::reputation::SenderReputation;
use soroban_sdk::{Address, Env, Error};

/// Get the storage key for a sender's reputation.
fn reputation_key(sender: &Address) -> SorobanString {
let key_str = format!("{}{}", REPUTATION_KEY_PREFIX, sender);
SorobanString::from_small_str(&key_str)
fn reputation_key(sender: &Address) -> DataKey {
DataKey::Reputation(sender.clone())
}

/// Initialize or get a sender's reputation record.
Expand Down Expand Up @@ -41,31 +39,27 @@ pub fn record_successful_delivery(
env.storage().persistent().set(&key, &reputation);

// Emit reputation update event
env.events().publish(
("rep_update",),
ReputationUpdated {
sender: sender.clone(),
category: NotificationCategory::Notification,
priority: NotificationPriority::Medium,
new_score: reputation.reputation_score,
successful_count: reputation.successful_deliveries,
failed_count: reputation.failed_deliveries,
},
);
ReputationUpdated {
sender: sender.clone(),
category: NotificationCategory::Notification,
priority: NotificationPriority::Medium,
new_score: reputation.reputation_score,
successful_count: reputation.successful_deliveries,
failed_count: reputation.failed_deliveries,
}
.publish(env);

// Emit tier change event if tier changed
if old_tier != new_tier {
env.events().publish(
("rep_tier_change",),
ReputationTierChanged {
sender: sender.clone(),
category: NotificationCategory::Notification,
priority: NotificationPriority::High,
old_tier: old_tier as u32,
new_tier: new_tier as u32,
reputation_score: reputation.reputation_score,
},
);
ReputationTierChanged {
sender: sender.clone(),
category: NotificationCategory::Notification,
priority: NotificationPriority::High,
old_tier: old_tier as u32,
new_tier: new_tier as u32,
reputation_score: reputation.reputation_score,
}
.publish(env);
}

Ok(())
Expand All @@ -88,31 +82,27 @@ pub fn record_failed_delivery(
env.storage().persistent().set(&key, &reputation);

// Emit reputation update event
env.events().publish(
("rep_update",),
ReputationUpdated {
sender: sender.clone(),
category: NotificationCategory::Notification,
priority: NotificationPriority::Medium,
new_score: reputation.reputation_score,
successful_count: reputation.successful_deliveries,
failed_count: reputation.failed_deliveries,
},
);
ReputationUpdated {
sender: sender.clone(),
category: NotificationCategory::Notification,
priority: NotificationPriority::Medium,
new_score: reputation.reputation_score,
successful_count: reputation.successful_deliveries,
failed_count: reputation.failed_deliveries,
}
.publish(env);

// Emit tier change event if tier changed
if old_tier != new_tier {
env.events().publish(
("rep_tier_change",),
ReputationTierChanged {
sender: sender.clone(),
category: NotificationCategory::Notification,
priority: NotificationPriority::High,
old_tier: old_tier as u32,
new_tier: new_tier as u32,
reputation_score: reputation.reputation_score,
},
);
ReputationTierChanged {
sender: sender.clone(),
category: NotificationCategory::Notification,
priority: NotificationPriority::High,
old_tier: old_tier as u32,
new_tier: new_tier as u32,
reputation_score: reputation.reputation_score,
}
.publish(env);
}

Ok(())
Expand Down
Loading
Loading