Emergency Pause Remediation
How to fix missing or vulnerable emergency pause mechanisms in Solana programs.
Emergency Pause Remediation
Overview
Related Detector: Emergency Pause
Missing or flawed emergency pause mechanisms leave programs unable to halt operations when a vulnerability is discovered. The fix involves implementing a pause state stored in an on-chain account, checking that state before every critical operation, and restricting pause authority to verified accounts.
Recommended Fix
Before (Vulnerable)
pub fn transfer(accounts: &[AccountInfo], amount: u64) -> ProgramResult {
let vault = &accounts[0];
let dest = &accounts[1];
// No pause check -- no way to stop exploitation
**vault.try_borrow_mut_lamports()? -= amount;
**dest.try_borrow_mut_lamports()? += amount;
Ok(())
}
After (Fixed)
pub fn transfer(accounts: &[AccountInfo], amount: u64) -> ProgramResult {
let authority = &accounts[0];
let vault = &accounts[1];
let dest = &accounts[2];
let pause_account = &accounts[3];
// Check pause state
let pause_data = pause_account.try_borrow_data()?;
if pause_data[0] == 1 {
return Err(ProgramError::InvalidInstructionData);
}
drop(pause_data);
if !authority.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
**vault.try_borrow_mut_lamports()? -= amount;
**dest.try_borrow_mut_lamports()? += amount;
Ok(())
}
Alternative Mitigations
1. Anchor pause guard
Use a reusable account constraint pattern:
#[derive(Accounts)]
pub struct GuardedTransfer<'info> {
pub authority: Signer<'info>,
#[account(constraint = !global_state.paused @ ErrorCode::Paused)]
pub global_state: Account<'info, GlobalState>,
#[account(mut)]
pub vault: Account<'info, Vault>,
}
#[account]
pub struct GlobalState {
pub paused: bool,
pub pause_authority: Pubkey,
}
2. Guardian multi-sig pause
Require multiple guardians to agree before pausing:
pub fn vote_pause(accounts: &[AccountInfo]) -> ProgramResult {
let guardian = &accounts[0];
let pause_state = &accounts[1];
if !guardian.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
// Increment pause votes
let mut data = pause_state.try_borrow_mut_data()?;
let votes = u8::from_le_bytes([data[1]]) + 1;
data[1] = votes;
// Auto-pause at threshold
if votes >= 2 {
data[0] = 1; // paused
}
Ok(())
}
3. Granular pause (per-operation)
Instead of a global pause, allow pausing specific operations:
#[account]
pub struct PauseConfig {
pub transfers_paused: bool,
pub minting_paused: bool,
pub admin_paused: bool,
pub pause_authority: Pubkey,
}
Common Mistakes
Mistake 1: Not checking pause state in all critical paths
Every function that performs state mutations or fund transfers must check the pause state. A single unchecked path allows continued exploitation.
Mistake 2: Allowing anyone to toggle pause
// WRONG: anyone can pause/unpause
pub fn toggle_pause(accounts: &[AccountInfo]) -> ProgramResult {
let pause = &accounts[0];
let mut data = pause.try_borrow_mut_data()?;
data[0] = if data[0] == 0 { 1 } else { 0 };
Ok(())
}
Always verify the caller is the authorized pause authority.
Mistake 3: Pausing read operations unnecessarily
Read-only operations (balance queries, state views) generally do not need pause protection. Over-pausing degrades user experience without security benefit.