Skip to content

Latest commit

 

History

History
228 lines (184 loc) · 7.78 KB

File metadata and controls

228 lines (184 loc) · 7.78 KB

Pause Notification Feature - Code Changes Summary

Files Modified

1. contracts/stream/src/events.rs

Added new event functions:

/// Emitted when a stream is paused by the employer.
/// Includes employee address for notification purposes.
pub fn stream_paused(env: &Env, id: u64, employer: &Address, employee: &Address, paused_at: u64) {
    env.events().publish((symbol_short!("paused"), id), (employer.clone(), employee.clone(), paused_at));
}

/// Emitted when a stream is resumed by the employer.
pub fn stream_resumed(env: &Env, id: u64, employer: &Address, employee: &Address, resumed_at: u64) {
    env.events().publish((symbol_short!("resumed"), id), (employer.clone(), employee.clone(), resumed_at));
}

2. contracts/stream/src/types.rs

Added PauseEvent struct:

/// Record of a pause/resume event for history tracking.
#[contracttype]
#[derive(Clone, Debug)]
pub struct PauseEvent {
    pub stream_id: u64,
    pub timestamp: u64,
    pub is_pause: bool, // true for pause, false for resume
}

Added storage key:

pub enum DataKey {
    // ... existing keys ...
    /// Pause history for a stream.
    PauseHistory(u64),
    // ... rest of keys ...
}

3. contracts/stream/src/storage.rs

Added import:

use crate::types::{DataKey, PauseEvent, Proposal, ProposalStatus, Stream, StreamStatus, ERR_OVERFLOW, ERR_BAD_NONCE};

Added storage functions:

// ---------------------------------------------------------------------------
// Pause history helpers
// ---------------------------------------------------------------------------

pub fn add_pause_event(env: &Env, stream_id: u64, timestamp: u64, is_pause: bool) {
    let key = DataKey::PauseHistory(stream_id);
    let mut history: Vec<PauseEvent> = env.storage().persistent().get(&key).unwrap_or_else(|| Vec::new(env));
    history.push_back(PauseEvent {
        stream_id,
        timestamp,
        is_pause,
    });
    env.storage().persistent().set(&key, &history);
    env.storage().persistent().extend_ttl(&key, TTL_THRESHOLD, TTL_EXTEND_TO);
}

pub fn get_pause_history(env: &Env, stream_id: u64) -> Vec<PauseEvent> {
    let key = DataKey::PauseHistory(stream_id);
    env.storage().persistent().get(&key).unwrap_or_else(|| Vec::new(env))
}

4. contracts/stream/src/lib.rs

Updated imports:

use storage::{
    add_pause_event, apply_proposal, claimable_amount, clear_pending_admin, clear_pending_employer,
    consume_admin_nonce, get_admin, get_admin_nonce, get_employee_streams, get_employer_streams,
    get_fee_bps, get_fee_recipient, get_max_streams_per_employer, get_min_deposit,
    get_pause_history, get_pending_admin, get_pending_employer, has_voted, index_employee_stream,
    index_employer_stream, load_proposal, load_stream, mark_voted, next_id, next_proposal_id,
    save_proposal, save_stream, set_admin, set_fee_bps, set_fee_recipient,
    set_max_streams_per_employer, set_min_deposit, set_pending_admin, set_pending_employer,
    tally_proposal,
};
use types::{
    DataKey, GovParam, PauseEvent, Proposal, ProposalStatus, Stream, StreamParams, StreamStatus,
    ERR_FEE_TOO_HIGH, ERR_INVALID_TOKEN, ERR_OVERFLOW, ERR_REENTRANT, ERR_STREAM_CANCELLED,
    ERR_STREAM_EXHAUSTED, ERR_UNAUTHORIZED_TRANSFER, ERR_WITHDRAW_COOLDOWN, ERR_ZERO_DEPOSIT,
    ERR_ZERO_RATE,
};

Updated pause_stream function:

pub fn pause_stream(env: Env, employer: Address, stream_id: u64) {
    employer.require_auth();
    let mut stream = load_stream(&env, stream_id).expect("stream not found");
    assert_eq!(stream.employer, employer, "not the employer");
    assert_eq!(stream.status, StreamStatus::Active, "stream not active");
    let now = env.ledger().timestamp();
    stream.paused_at = now;
    stream.status = StreamStatus::Paused;
    save_stream(&env, &stream);
    add_pause_event(&env, stream_id, now, true);  // NEW: Record pause event
    events::stream_paused(&env, stream_id, &employer, &stream.employee, now);  // CHANGED: Use new event
}

Updated resume_stream function:

pub fn resume_stream(env: Env, employer: Address, stream_id: u64) {
    employer.require_auth();
    let mut stream = load_stream(&env, stream_id).expect("stream not found");
    assert_eq!(stream.employer, employer, "not the employer");
    assert_eq!(stream.status, StreamStatus::Paused, "stream not paused");
    let now = env.ledger().timestamp();
    // Advance last_withdraw_time by the paused duration to exclude it while
    // preserving pre-pause accrued earnings.
    let paused_duration = now.saturating_sub(stream.paused_at);
    stream.last_withdraw_time = stream.last_withdraw_time.saturating_add(paused_duration);
    stream.paused_at = 0;
    stream.status = StreamStatus::Active;
    save_stream(&env, &stream);
    add_pause_event(&env, stream_id, now, false);  // NEW: Record resume event
    events::stream_resumed(&env, stream_id, &employer, &stream.employee, now);  // CHANGED: Use new event
}

Added new public query function:

pub fn pause_history(env: Env, stream_id: u64) -> Vec<PauseEvent> {
    get_pause_history(&env, stream_id)
}

5. contracts/stream/src/test.rs

Added comprehensive tests:

  • test_pause_event_includes_employee: Verifies pause events are emitted with employee address
  • test_pause_history_tracking: Tests basic pause/resume history recording
  • test_multiple_pause_resume_cycles: Validates multiple pause/resume cycles are tracked correctly
  • test_resume_event_includes_employee: Verifies resume events are emitted with employee address

Event Structure Changes

Before:

// Generic status change event
events::stream_status_changed(&env, stream_id, &StreamStatus::Paused);
// Topic: ("status", stream_id)
// Data: StreamStatus::Paused

After:

// Specific pause event with employee address
events::stream_paused(&env, stream_id, &employer, &stream.employee, now);
// Topic: ("paused", stream_id)
// Data: (employer_address, employee_address, paused_at_timestamp)

// Specific resume event with employee address
events::stream_resumed(&env, stream_id, &employer, &stream.employee, now);
// Topic: ("resumed", stream_id)
// Data: (employer_address, employee_address, resumed_at_timestamp)

API Changes

New Public Function:

pub fn pause_history(env: Env, stream_id: u64) -> Vec<PauseEvent>

Returns: A vector of PauseEvent structs containing:

  • stream_id: The stream identifier
  • timestamp: When the pause/resume occurred
  • is_pause: true for pause events, false for resume events

Usage Example:

let history = client.pause_history(&stream_id);
for event in history.iter() {
    println!("Stream {} was {} at timestamp {}", 
        event.stream_id,
        if event.is_pause { "paused" } else { "resumed" },
        event.timestamp
    );
}

Migration Notes

  • No breaking changes: All existing functionality remains intact
  • Event listeners: Services listening for stream_status_changed events should be updated to listen for stream_paused and stream_resumed events for better notification handling
  • Storage: New pause history data is stored separately and doesn't affect existing stream data
  • Backward compatibility: Old streams without pause history will return empty vectors from pause_history()

Testing Commands

# Run all stream contract tests
cargo test --package paystream-stream

# Run only pause notification tests
cargo test --package paystream-stream test_pause_event_includes_employee
cargo test --package paystream-stream test_pause_history_tracking
cargo test --package paystream-stream test_multiple_pause_resume_cycles
cargo test --package paystream-stream test_resume_event_includes_employee

# Run with output to see event details
cargo test --package paystream-stream test_pause_event_includes_employee -- --nocapture