Utilities

Pausable

Source Code

Purpose

Allows contracts to be paused and unpaused by authorized accounts.

This utility can be used in any contract, including token contracts (e.g. fungible or non-fungible tokens).

Design

To make it easier to spot when inspecting the code, we turned this simple functionality into two macros that can annotate your smart contract functions: #[when_not_paused] and #[when_paused].

An example:

#[when_paused]
pub fn emergency_reset(e: &Env) {
    e.storage().instance().set(&DataKey::Counter, &0);
}

Which will expand into the below code:

pub fn emergency_reset(e: &Env) {
    stellar_contract_utils::pausable::when_paused(e);

    e.storage().instance().set(&DataKey::Counter, &0);
}

#[when_not_paused] works the same way, injecting stellar_contract_utils::pausable::when_not_paused(e). when_paused panics with PausableError::ExpectedPause (1001) when the contract is not paused, and when_not_paused panics with PausableError::EnforcedPause (1000) when it is.

Implementing Pausable Trait

The pausable::pause() and pausable::unpause() helpers don't check authorization, so access control must be added by the implementor. The example below restricts them to the owner with #[only_owner]:

use soroban_sdk::{contract, contractimpl, contracttype, Address, Env};
use stellar_access::ownable::{self as ownable, Ownable};
use stellar_contract_utils::pausable::{self as pausable, Pausable};
use stellar_macros::{only_owner, when_not_paused, when_paused};

#[contracttype]
pub enum DataKey {
    Counter,
}

#[contract]
pub struct ExampleContract;

#[contractimpl]
impl ExampleContract {
    pub fn __constructor(e: &Env, owner: Address) {
        ownable::set_owner(e, &owner);
        e.storage().instance().set(&DataKey::Counter, &0);
    }

    #[when_not_paused]
    pub fn increment(e: &Env) -> i32 {
        let counter: i32 = e.storage().instance().get(&DataKey::Counter).unwrap();
        e.storage().instance().set(&DataKey::Counter, &(counter + 1));
        counter + 1
    }

    #[when_paused]
    pub fn emergency_reset(e: &Env) {
        e.storage().instance().set(&DataKey::Counter, &0);
    }
}

#[contractimpl]
impl Pausable for ExampleContract {
    fn paused(e: &Env) -> bool {
        pausable::paused(e)
    }

    #[only_owner]
    fn pause(e: &Env, _caller: Address) {
        pausable::pause(e);
    }

    #[only_owner]
    fn unpause(e: &Env, _caller: Address) {
        pausable::unpause(e);
    }
}

#[contractimpl(contracttrait)]
impl Ownable for ExampleContract {}

See the pausable example for a complete contract, and the fungible-pausable example for #[when_not_paused] applied to the transfer, transfer_from, burn and burn_from overrides of a fungible token.