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: MuxedAddress) {
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
52#[cfg(test)]
53mod test;
54
55use soroban_sdk::{contracterror, contractevent, Address, Env};
56
57pub use crate::pausable::storage::{pause, paused, unpause, when_not_paused, when_paused};
58
59pub trait Pausable {
60 /// Returns true if the contract is paused, and false otherwise.
61 ///
62 /// # Arguments
63 ///
64 /// * `e` - Access to Soroban environment.
65 fn paused(e: &Env) -> bool {
66 crate::pausable::paused(e)
67 }
68
69 /// Triggers `Paused` state.
70 ///
71 /// # Arguments
72 ///
73 /// * `e` - Access to Soroban environment.
74 /// * `caller` - The address of the caller.
75 ///
76 /// # Errors
77 ///
78 /// * [`PausableError::EnforcedPause`] - Occurs when the contract is already
79 /// in `Paused` state.
80 ///
81 /// # Events
82 ///
83 /// * topics - `["paused"]`
84 /// * data - `[]`
85 ///
86 /// # Notes
87 ///
88 /// We recommend using [`pause`] when implementing this function.
89 ///
90 /// # Security Warning
91 ///
92 /// **IMPORTANT**: The base implementation of [`pause`]
93 /// intentionally lacks authorization controls. If you want to restrict
94 /// who can `pause` the contract, you MUST implement proper
95 /// authorization in your contract.
96 fn pause(e: &Env, caller: Address);
97
98 /// Triggers `Unpaused` state.
99 ///
100 /// # Arguments
101 ///
102 /// * `e` - Access to Soroban environment.
103 /// * `caller` - The address of the caller.
104 ///
105 /// # Errors
106 ///
107 /// * [`PausableError::ExpectedPause`] - Occurs when the contract is already
108 /// in `Unpaused` state.
109 ///
110 /// # Events
111 ///
112 /// * topics - `["unpaused"]`
113 /// * data - `[]`
114 ///
115 /// # Notes
116 ///
117 /// We recommend using [`unpause`] when implementing this function.
118 ///
119 /// # Security Warning
120 ///
121 /// **IMPORTANT**: The base implementation of [`unpause`]
122 /// intentionally lacks authorization controls. If you want to restrict
123 /// who can `unpause` the contract, you MUST implement proper
124 /// authorization in your contract.
125 fn unpause(e: &Env, caller: Address);
126}
127
128// ################## ERRORS ##################
129
130#[contracterror]
131#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
132#[repr(u32)]
133pub enum PausableError {
134 /// The operation failed because the contract is paused.
135 EnforcedPause = 1000,
136 /// The operation failed because the contract is not paused.
137 ExpectedPause = 1001,
138}
139
140// ################## EVENTS ##################
141
142/// Event emitted when the contract is paused.
143#[contractevent]
144#[derive(Clone, Debug, Eq, PartialEq)]
145pub struct Paused {}
146
147/// Emits an event when `Paused` state is triggered.
148///
149/// # Arguments
150///
151/// * `e` - The Soroban environment.
152pub fn emit_paused(e: &Env) {
153 Paused {}.publish(e);
154}
155
156/// Event emitted when the contract is unpaused.
157#[contractevent]
158#[derive(Clone, Debug, Eq, PartialEq)]
159pub struct Unpaused {}
160
161/// Emits an event when `Unpaused` state is triggered.
162///
163/// # Arguments
164///
165/// * `e` - The Soroban environment.
166pub fn emit_unpaused(e: &Env) {
167 Unpaused {}.publish(e);
168}