Skip to content

Add emergency withdrawal function for admin to return funds during crisis #24

Description

@prodbycorne

Overview

If a critical vulnerability is discovered in the farming-pool contract, the current pause mechanism (pause) prevents new operations but leaves all staked/locked tokens permanently trapped — there is no admin path to return tokens to users.

An emergency withdrawal function allows the admin to return a specific user's tokens during an emergency, after the pool is paused.

Design

/// Admin: return all tokens for `user` in an emergency.
///
/// May only be called while the pool is paused. Does NOT slash credits — the
/// user's credit record is preserved for any future claim mechanism.
/// Emits an `emrg_exit` event with `(admin, user, amount)`.
pub fn emergency_withdraw(env: Env, user: Address) -> Result<i128, PoolError> {
    get_admin(&env).require_auth();
    if !pool_is_paused(&env) {
        return Err(PoolError::NotPaused);
    }
    bump_instance(&env);

    let mut total_returned: i128 = 0;
    let token = token::TokenClient::new(&env, &get_stake_token(&env));

    // Return lock position
    if let Some(pos) = get_position(&env, &user) {
        token.transfer(&env.current_contract_address(), &user, &pos.amount);
        total_returned += pos.amount;
        remove_position(&env, &user);
    }

    // Return stake balance
    if let Some(stake) = get_user_stake(&env, &user) {
        token.transfer(&env.current_contract_address(), &user, &stake.amount);
        total_returned += stake.amount;
        remove_user_stake(&env, &user);
    }

    if total_returned == 0 {
        return Err(PoolError::NoActiveStake);
    }

    env.events().publish(
        (symbol_short!("pool"), symbol_short!("emrg_exit")),
        (get_admin(&env), user, total_returned),
    );
    Ok(total_returned)
}

Add NotPaused = 13 to PoolError.

Safeguards

  • Only callable while paused (prevents routine admin extraction of user funds)
  • Credits are preserved, not zeroed (users lose time but not accrued credits)
  • Event emitted for every withdrawal so the action is fully auditable

Acceptance Criteria

  • emergency_withdraw implemented, admin-only, paused-only
  • Returns both stake and position balance
  • Credits preserved (not zeroed) after emergency withdrawal
  • emrg_exit event emitted
  • NotPaused error returned if pool is not paused
  • Test: emergency withdraw while paused, verify tokens returned and state cleared
  • Test: emergency withdraw while unpaused returns Err(PoolError::NotPaused)

Metadata

Metadata

Assignees

Labels

GrantFox OSSIssue tracked in GrantFox OSSMaybe RewardedIssue may be eligible for a GrantFox rewardOfficial CampaignCampaign: Official Campaignfarming-poolFarmingPool contractfeatureNew feature or capabilitysecuritySecurity vulnerability or hardening

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions