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}