Skip to main content

miden_standards/account/access/pausable/
mod.rs

1use miden_protocol::account::component::{
2    AccountComponentCode,
3    AccountComponentMetadata,
4    StorageSchema,
5    StorageSlotSchema,
6};
7use miden_protocol::account::{
8    AccountComponent,
9    AccountProcedureRoot,
10    StorageSlot,
11    StorageSlotName,
12};
13use miden_protocol::utils::sync::LazyLock;
14use miden_protocol::{Felt, Word};
15
16use crate::account::account_component_code;
17use crate::procedure_root;
18
19mod manager;
20pub use manager::PausableManager;
21
22// IS_PAUSED STORAGE
23// ================================================================================================
24
25account_component_code!(PAUSABLE_CODE, "access/pausable/mod.masl");
26
27static IS_PAUSED_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
28    StorageSlotName::new("miden::standards::access::pausable::is_paused")
29        .expect("storage slot name should be valid")
30});
31
32procedure_root!(
33    PAUSABLE_IS_PAUSED_ROOT,
34    Pausable::NAME,
35    Pausable::IS_PAUSED_PROC_NAME,
36    Pausable::code()
37);
38
39/// Storage helper backing the pause flag for a single account.
40///
41/// `PausableStorage` exposes the slot name and schema for the `is_paused` flag word plus the
42/// captured pause state. It is **not** an installable account component on its own — the
43/// [`Pausable`] component installs the storage slot, and any consumer (TokenPolicyManager
44/// dispatch, asset callbacks, metadata setters) reads via `exec.pausable::assert_not_paused` /
45/// `assert_paused` exec helpers from the standards library at
46/// `miden::standards::access::pausable`.
47///
48/// ## Storage
49///
50/// - [`Self::is_paused_slot()`]: single word; the zero word means unpaused, `[1, 0, 0, 0]` means
51///   paused. Any non-zero word is interpreted as paused by the MASM helpers.
52#[derive(Debug, Clone, Copy, Default)]
53pub struct PausableStorage {
54    state: bool,
55}
56
57impl PausableStorage {
58    /// Creates a [`PausableStorage`] with the given pause state.
59    pub const fn new(state: bool) -> Self {
60        Self { state }
61    }
62
63    /// Creates a [`PausableStorage`] in the paused state.
64    pub const fn paused() -> Self {
65        Self::new(true)
66    }
67
68    /// Creates a [`PausableStorage`] in the unpaused state.
69    pub const fn unpaused() -> Self {
70        Self::new(false)
71    }
72
73    /// Returns the pause state captured in this storage.
74    pub fn state(&self) -> bool {
75        self.state
76    }
77
78    /// Storage slot name for the pause flag word.
79    pub fn is_paused_slot() -> &'static StorageSlotName {
80        &IS_PAUSED_SLOT_NAME
81    }
82
83    /// Schema entry for the pause flag slot (documentation / tooling).
84    pub fn is_paused_slot_schema() -> (StorageSlotName, StorageSlotSchema) {
85        (
86            Self::is_paused_slot().clone(),
87            StorageSlotSchema::value(
88                "Pause flag word; zero is unpaused, canonical paused encoding is [1,0,0,0]",
89                [Felt::ZERO; 4],
90            ),
91        )
92    }
93
94    /// Returns the pause-flag [`Word`] for the captured state.
95    pub fn to_word(&self) -> Word {
96        if self.state {
97            Word::from([1u32, 0, 0, 0])
98        } else {
99            Word::default()
100        }
101    }
102
103    /// Consumes the storage and returns the [`StorageSlot`] it contributes to an account
104    /// component. The slot is initialized with the captured pause state.
105    pub fn into_slot(self) -> StorageSlot {
106        StorageSlot::with_value(Self::is_paused_slot().clone(), self.to_word())
107    }
108}
109
110// PAUSABLE COMPONENT
111// ================================================================================================
112
113/// Account component that installs the [`PausableStorage`] slot and exposes `is_paused`
114/// view procedure.
115///
116/// Pair with [`PausableManager`] to expose `pause` / `unpause` admin procedures gated by the
117/// account-wide [`crate::account::access::Authority`] component.
118#[derive(Debug, Clone, Copy, Default)]
119pub struct Pausable(PausableStorage);
120
121impl Pausable {
122    /// The name of the component.
123    pub const NAME: &'static str = "miden::standards::components::access::pausable";
124
125    pub const IS_PAUSED_PROC_NAME: &'static str = "is_paused";
126
127    /// Creates a [`Pausable`] component with the given pause state.
128    pub const fn new(state: bool) -> Self {
129        Self(PausableStorage::new(state))
130    }
131
132    /// Creates a [`Pausable`] component that starts in the paused state.
133    pub const fn paused() -> Self {
134        Self::new(true)
135    }
136
137    /// Creates a [`Pausable`] component that starts in the unpaused state.
138    pub const fn unpaused() -> Self {
139        Self::new(false)
140    }
141
142    /// Returns the pause state captured in this component.
143    pub fn state(&self) -> bool {
144        self.0.state()
145    }
146
147    /// Returns the underlying [`PausableStorage`] helper.
148    pub fn storage(&self) -> &PausableStorage {
149        &self.0
150    }
151
152    /// Returns the [`AccountComponentCode`] of this component.
153    pub fn code() -> &'static AccountComponentCode {
154        &PAUSABLE_CODE
155    }
156
157    /// Returns the procedure root of the `is_paused` call procedure exposed by this component.
158    pub fn is_paused_root() -> AccountProcedureRoot {
159        *PAUSABLE_IS_PAUSED_ROOT
160    }
161}
162
163impl From<Pausable> for AccountComponent {
164    fn from(pausable: Pausable) -> Self {
165        let storage_schema = StorageSchema::new([PausableStorage::is_paused_slot_schema()])
166            .expect("storage schema should be valid");
167
168        let metadata = AccountComponentMetadata::new(Pausable::NAME)
169            .with_description(
170                "Pausable: installs the `is_paused` storage slot and exposes \
171                 `is_paused` view.",
172            )
173            .with_storage_schema(storage_schema);
174
175        AccountComponent::new(Pausable::code().clone(), vec![pausable.0.into_slot()], metadata)
176            .expect(
177                "pausable component should satisfy the requirements of a valid account component",
178            )
179    }
180}