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
23 changes: 21 additions & 2 deletions contracts/privacy_pool/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,15 +94,34 @@ impl PrivacyPool {
// ──────────────────────────────────────────────────────────

/// Pause the pool (admin only).
pub fn pause(env: Env, admin: Address) -> Result<(), Error> {
admin::pause(env, admin)
/// Stores pause reason and timestamp for audit trail.
pub fn pause(env: Env, admin: Address, pause_reason: soroban_sdk::String) -> Result<(), Error> {
admin::pause(env, admin, pause_reason)
}

/// Unpause the pool (admin only).
pub fn unpause(env: Env, admin: Address) -> Result<(), Error> {
admin::unpause(env, admin)
}

/// Check if the pool is paused.
/// Returns (is_paused, Option<PauseInfo>) with audit trail.
pub fn is_paused(env: Env) -> Result<(bool, Option<crate::types::state::PauseInfo>), Error> {
admin::is_paused(env)
}

/// Emergency withdraw - admin can recover funds during security incidents.
/// Pool MUST be paused before calling.
pub fn emergency_withdraw(
env: Env,
admin: Address,
recipient: Address,
amount: i128,
reason: soroban_sdk::String,
) -> Result<(), Error> {
admin::emergency_withdraw(env, admin, recipient, amount, reason)
}

/// Update the Groth16 verifying key (admin only).
pub fn set_verifying_key(
env: Env,
Expand Down
133 changes: 126 additions & 7 deletions contracts/privacy_pool/src/core/admin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,160 @@
// Admin Functions - Pool management
// ============================================================

use soroban_sdk::{Address, Env};
use soroban_sdk::{token, Address, Env, String};

use crate::storage::config;
use crate::types::errors::Error;
use crate::types::events::{emit_pool_paused, emit_pool_unpaused, emit_vk_updated};
use crate::types::state::VerifyingKey;
use crate::types::events::{emit_emergency_withdraw, emit_pool_paused, emit_pool_unpaused, emit_vk_updated};
use crate::types::state::{PauseInfo, VerifyingKey};
use crate::utils::validation;

/// Pause the pool - blocks deposits and withdrawals.
/// Only callable by admin.
pub fn pause(env: Env, admin: Address) -> Result<(), Error> {
///
/// # Arguments
/// - `env` - Soroban environment
/// - `admin` - Admin address (must authorize)
/// - `pause_reason` - Human-readable reason for pause (for audit trail)
///
/// # Events
/// Emits `PoolPausedEvent` with admin, reason, and timestamp.
pub fn pause(env: Env, admin: Address, pause_reason: String) -> Result<(), Error> {
admin.require_auth();

let mut pool_config = config::load(&env)?;
validation::require_admin(&admin, &pool_config)?;

// Check if already paused
if pool_config.paused {
return Ok(()); // Idempotent - already paused
}

// Get current timestamp
let timestamp = env.ledger().timestamp();

// Update pool state
pool_config.paused = true;
config::save(&env, &pool_config);

// Save pause info for audit trail
let pause_info = PauseInfo {
pause_timestamp: timestamp,
pause_reason: pause_reason.clone(),
paused_by: admin.clone(),
};
config::save_pause_info(&env, &pause_info);

emit_pool_paused(&env, admin);
emit_pool_paused(&env, admin, pause_reason, timestamp);
Ok(())
}

/// Unpause the pool.
/// Only callable by admin.
///
/// # Events
/// Emits `PoolUnpausedEvent` with admin and timestamp.
pub fn unpause(env: Env, admin: Address) -> Result<(), Error> {
admin.require_auth();

let mut pool_config = config::load(&env)?;
validation::require_admin(&admin, &pool_config)?;

// Check if already unpaused
if !pool_config.paused {
return Ok(()); // Idempotent - already unpaused
}

// Get current timestamp
let timestamp = env.ledger().timestamp();

// Update pool state
pool_config.paused = false;
config::save(&env, &pool_config);

// Clear pause info
config::clear_pause_info(&env);

emit_pool_unpaused(&env, admin);
emit_pool_unpaused(&env, admin, timestamp);
Ok(())
}

/// Check if the pool is currently paused.
/// Returns the paused state and pause info if paused.
pub fn is_paused(env: Env) -> Result<(bool, Option<PauseInfo>), Error> {
let pool_config = config::load(&env)?;

if pool_config.paused {
let pause_info = config::load_pause_info(&env);
Ok((true, Some(pause_info)))
} else {
Ok((false, None))
}
}

/// Emergency withdraw - allows admin to recover funds during security incidents.
/// Pool MUST be paused before calling this function.
///
/// # Security Considerations
/// - This function bypasses all privacy guarantees
/// - Should only be used in genuine emergencies
/// - Emits event for transparency and audit
///
/// # Arguments
/// - `env` - Soroban environment
/// - `admin` - Admin address (must authorize)
/// - `recipient` - Address to receive the funds
/// - `amount` - Amount to withdraw (in stroops/microunits)
/// - `reason` - Human-readable reason for emergency withdrawal
///
/// # Errors
/// - `Error::UnauthorizedAdmin` if caller is not admin
/// - `Error::PoolNotPaused` if pool is not paused
/// - `Error::InvalidEmergencyAmount` if amount is 0
/// - `Error::EmergencyWithdrawExceedsBalance` if amount > contract balance
pub fn emergency_withdraw(
env: Env,
admin: Address,
recipient: Address,
amount: i128,
reason: String,
) -> Result<(), Error> {
admin.require_auth();

let pool_config = config::load(&env)?;
validation::require_admin(&admin, &pool_config)?;

// Security check: pool must be paused
if !pool_config.paused {
return Err(Error::PoolNotPaused);
}

// Validate amount
if amount <= 0 {
return Err(Error::InvalidEmergencyAmount);
}

// Check contract balance
let token_client = token::Client::new(&env, &pool_config.token);
let contract_balance = token_client.balance(&env.current_contract_address());

if amount > contract_balance {
return Err(Error::EmergencyWithdrawExceedsBalance);
}

// Get timestamp for event
let timestamp = env.ledger().timestamp();

// Execute transfer
token_client.transfer(
&env.current_contract_address(),
&recipient,
&amount,
);

// Emit event for audit trail
emit_emergency_withdraw(&env, admin, recipient, amount, timestamp, reason);

Ok(())
}

Expand All @@ -56,4 +175,4 @@ pub fn set_verifying_key(

emit_vk_updated(&env, admin);
Ok(())
}
}
25 changes: 24 additions & 1 deletion contracts/privacy_pool/src/storage/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
use soroban_sdk::Env;

use crate::types::errors::Error;
use crate::types::state::{DataKey, PoolConfig, VerifyingKey};
use crate::types::state::{DataKey, PoolConfig, PauseInfo, VerifyingKey};

/// Check if the pool has been initialized.
pub fn exists(env: &Env) -> bool {
Expand Down Expand Up @@ -45,3 +45,26 @@ pub fn load_verifying_key(env: &Env) -> Result<VerifyingKey, Error> {
pub fn save_verifying_key(env: &Env, vk: &VerifyingKey) {
env.storage().persistent().set(&DataKey::VerifyingKey, vk);
}

// ──────────────────────────────────────────────────────────────
// Pause Info Storage
// ──────────────────────────────────────────────────────────────

/// Load the pause information.
/// Returns default (empty) PauseInfo if not set.
pub fn load_pause_info(env: &Env) -> PauseInfo {
env.storage()
.persistent()
.get(&DataKey::PauseInfo)
.unwrap_or_default()
}

/// Save the pause information.
pub fn save_pause_info(env: &Env, pause_info: &PauseInfo) {
env.storage().persistent().set(&DataKey::PauseInfo, pause_info);
}

/// Clear the pause information (on unpause).
pub fn clear_pause_info(env: &Env) {
env.storage().persistent().remove(&DataKey::PauseInfo);
}
Loading