stellar_contract_utils/pausable/
mod.rs

1//! Pausable Contract Module.
2//!
3//! This contract module allows implementing a configurable stop mechanism for
4//! your contract.
5//!
6//! By implementing the trait [`Pausable`] for your contract, you can safely
7//! integrate the Pausable functionality. The trait [`Pausable`] has the
8//! following methods:
9//! - [`paused()`]
10//! - [`pause()`]
11//! - [`unpause()`]
12//!
13//! The trait ensures all the required methods are implemented for your
14//! contract, and nothing is forgotten. Additionally, if you are to implement
15//! multiple extensions or utilities for your contract, the code will be better
16//! organized.
17//!
18//! We also provide two macros `when_paused` and `when_not_paused`. These macros
19//! act as guards for your functions. For example:
20//!
21//! ```ignore
22//! #[when_not_paused]
23//! fn transfer(e: &env, from: Address, to: Address) {
24//!     /* this body will execute ONLY when NOT_PAUSED */
25//! }
26//! ```
27//!
28//! For a safe pause/unpause implementation, we expose the underlying functions
29//! required for the pausing. These functions work with the Soroban environment
30//! required for the Smart Contracts `e: &Env`, and take advantage of the
31//! storage by storing a flag for the pause mechanism.
32//!
33//! We expect you to utilize these functions (`storage::*`) for implementing the
34//! methods of the `Pausable` trait, along with your custom business logic
35//! (authentication, etc.)
36//!
37//! You can opt-out of [`Pausable`] trait, and use `storage::*` functions
38//! directly in your contract if you want more customizability. But we encourage
39//! the use of [`Pausable`] trait instead, due to the following reasons:
40//! - there is no additional cost
41//! - standardization
42//! - you cannot mistakenly forget one of the methods
43//! - your code will be better organized, especially if you implement multiple
44//!   extensions/utils
45//!
46//! TL;DR
47//! to see it all in action, check out the `examples/pausable/src/contract.rs`
48//! file.
49
50mod storage;
51
52mod test;
53
54use soroban_sdk::{contracterror, symbol_short, Address, Env};
55
56pub use crate::pausable::storage::{pause, paused, unpause, when_not_paused, when_paused};
57
58pub trait Pausable {
59    /// Returns true if the contract is paused, and false otherwise.
60    ///
61    /// # Arguments
62    ///
63    /// * `e` - Access to Soroban environment.
64    fn paused(e: &Env) -> bool {
65        crate::pausable::paused(e)
66    }
67
68    /// Triggers `Paused` state.
69    ///
70    /// # Arguments
71    ///
72    /// * `e` - Access to Soroban environment.
73    /// * `caller` - The address of the caller.
74    ///
75    /// # Errors
76    ///
77    /// * [`PausableError::EnforcedPause`] - Occurs when the contract is already
78    ///   in `Paused` state.
79    ///
80    /// # Events
81    ///
82    /// * topics - `["paused"]`
83    /// * data - `[]`
84    ///
85    /// # Notes
86    ///
87    /// We recommend using [`pause`] when implementing this function.
88    ///
89    /// # Security Warning
90    ///
91    /// **IMPORTANT**: The base implementation of [`pause`]
92    /// intentionally lacks authorization controls. If you want to restrict
93    /// who can `pause` the contract, you MUST implement proper
94    /// authorization in your contract.
95    fn pause(e: &Env, caller: Address);
96
97    /// Triggers `Unpaused` state.
98    ///
99    /// # Arguments
100    ///
101    /// * `e` - Access to Soroban environment.
102    /// * `caller` - The address of the caller.
103    ///
104    /// # Errors
105    ///
106    /// * [`PausableError::ExpectedPause`] - Occurs when the contract is already
107    ///   in `Unpaused` state.
108    ///
109    /// # Events
110    ///
111    /// * topics - `["unpaused"]`
112    /// * data - `[]`
113    ///
114    /// # Notes
115    ///
116    /// We recommend using [`unpause`] when implementing this function.
117    ///
118    /// # Security Warning
119    ///
120    /// **IMPORTANT**: The base implementation of [`unpause`]
121    /// intentionally lacks authorization controls. If you want to restrict
122    /// who can `unpause` the contract, you MUST implement proper
123    /// authorization in your contract.
124    fn unpause(e: &Env, caller: Address);
125}
126
127// ################## ERRORS ##################
128
129#[contracterror]
130#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
131#[repr(u32)]
132pub enum PausableError {
133    /// The operation failed because the contract is paused.
134    EnforcedPause = 1000,
135    /// The operation failed because the contract is not paused.
136    ExpectedPause = 1001,
137}
138
139// ################## EVENTS ##################
140
141/// Emits an event when `Paused` state is triggered.
142///
143/// # Arguments
144///
145/// * `e` - Access to Soroban environment.
146///
147/// # Events
148///
149/// * topics - `["paused"]`
150/// * data - `[]`
151pub fn emit_paused(e: &Env) {
152    let topics = (symbol_short!("paused"),);
153    e.events().publish(topics, ())
154}
155
156/// Emits an event when `Unpaused` state is triggered.
157///
158/// # Arguments
159///
160/// * `e` - Access to Soroban environment.
161///
162/// # Events
163///
164/// * topics - `["unpaused"]`
165/// * data - `[]`
166pub fn emit_unpaused(e: &Env) {
167    let topics = (symbol_short!("unpaused"),);
168    e.events().publish(topics, ())
169}