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
61 changes: 61 additions & 0 deletions contracts/reward-pool/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,15 @@ pub struct PoolFunded {
pub amount: i128,
}

#[contractevent]
pub struct EmergencySweep {
#[topic]
pub admin: Address,
#[topic]
pub recovery_wallet: Address,
pub amount: i128,
}

#[contractimpl]
impl RewardPool {
/// Initializes the RewardPool contract with admin and token addresses.
Expand Down Expand Up @@ -187,6 +196,58 @@ impl RewardPool {
// 5. Emit PoolFunded event
PoolFunded { donor, amount }.publish(&env);
}

/// Emergency sweep function allowing admin to transfer all tokens from the contract
/// to a recovery wallet in case of a critical vulnerability.
///
/// # Arguments
/// * `admin` - The admin address (must match stored admin)
/// * `recovery_wallet` - The address to receive the swept tokens
///
/// # Panics
/// * If contract is not initialized
/// * If admin does not match stored admin
/// * If admin authentication fails
pub fn emergency_sweep(env: Env, admin: Address, recovery_wallet: Address) {
// 1. admin.require_auth()
admin.require_auth();

// 2. Fetch stored admin from Instance storage
let stored_admin: Address = env
.storage()
.instance()
.get(&DataKey::Admin)
.expect("Not initialized");

// 3. Assert admin == stored_admin
if admin != stored_admin {
panic!("Unauthorized");
}

// 4. Fetch token address from Instance storage
let token_id: Address = env
.storage()
.instance()
.get(&DataKey::Token)
.expect("Not initialized");

// 5. Initialize token client
let token_client = token::Client::new(&env, &token_id);

// 6. Fetch full contract token balance
let balance = token_client.balance(&env.current_contract_address());

// 7. Transfer full balance to recovery wallet
token_client.transfer(&env.current_contract_address(), &recovery_wallet, &balance);

// 8. Emit EmergencySweep event
EmergencySweep {
admin,
recovery_wallet,
amount: balance,
}
.publish(&env);
}
}

mod test;
102 changes: 102 additions & 0 deletions contracts/reward-pool/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -446,3 +446,105 @@ fn test_fund_pool_zero_amount() {
assert_eq!(token_client.balance(&donor), 1000);
assert_eq!(token_client.balance(&client.address), 0);
}

// ── emergency_sweep Tests ─────────────────────────────────────────────────────

#[test]
fn test_emergency_sweep_success() {
let (env, client) = setup();
let admin = Address::generate(&env);
let recovery_wallet = Address::generate(&env);
let token_id = env.register_stellar_asset_contract_v2(admin.clone());

// Initialize the reward pool
client.initialize(&admin, &token_id.address());

// Fund the pool with tokens
let token_client = token::StellarAssetClient::new(&env, &token_id.address());
token_client.mint(&client.address, &1000);

// Verify initial balance
assert_eq!(token_client.balance(&client.address), 1000);
assert_eq!(token_client.balance(&recovery_wallet), 0);

// Perform emergency sweep
client.emergency_sweep(&admin, &recovery_wallet);

// Verify contract balance is 0
assert_eq!(token_client.balance(&client.address), 0);

// Verify recovery wallet received full balance
assert_eq!(token_client.balance(&recovery_wallet), 1000);
}

#[test]
#[should_panic(expected = "Not initialized")]
fn test_emergency_sweep_not_initialized() {
let (env, client) = setup();
let admin = Address::generate(&env);
let recovery_wallet = Address::generate(&env);

// Try to sweep without initializing - should panic
client.emergency_sweep(&admin, &recovery_wallet);
}

#[test]
#[should_panic(expected = "Unauthorized")]
fn test_emergency_sweep_wrong_admin() {
let (env, client) = setup();
let admin = Address::generate(&env);
let wrong_admin = Address::generate(&env);
let recovery_wallet = Address::generate(&env);
let token_id = env.register_stellar_asset_contract_v2(admin.clone());

// Initialize the reward pool
client.initialize(&admin, &token_id.address());

// Try to sweep with wrong admin - should panic
client.emergency_sweep(&wrong_admin, &recovery_wallet);
}

#[test]
fn test_emergency_sweep_zero_balance() {
let (env, client) = setup();
let admin = Address::generate(&env);
let recovery_wallet = Address::generate(&env);
let token_id = env.register_stellar_asset_contract_v2(admin.clone());

// Initialize the reward pool
client.initialize(&admin, &token_id.address());

let token_client = token::StellarAssetClient::new(&env, &token_id.address());

// Verify initial balance is 0
assert_eq!(token_client.balance(&client.address), 0);

// Perform emergency sweep with zero balance (should succeed)
client.emergency_sweep(&admin, &recovery_wallet);

// Verify balances remain 0
assert_eq!(token_client.balance(&client.address), 0);
assert_eq!(token_client.balance(&recovery_wallet), 0);
}

#[test]
fn test_emergency_sweep_large_balance() {
let (env, client) = setup();
let admin = Address::generate(&env);
let recovery_wallet = Address::generate(&env);
let token_id = env.register_stellar_asset_contract_v2(admin.clone());

// Initialize the reward pool
client.initialize(&admin, &token_id.address());

// Fund with large amount
let token_client = token::StellarAssetClient::new(&env, &token_id.address());
token_client.mint(&client.address, &1_000_000);

// Perform emergency sweep
client.emergency_sweep(&admin, &recovery_wallet);

// Verify full balance transferred
assert_eq!(token_client.balance(&client.address), 0);
assert_eq!(token_client.balance(&recovery_wallet), 1_000_000);
}
Loading
Loading