-
Notifications
You must be signed in to change notification settings - Fork 129
Pausable #2793
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: next
Are you sure you want to change the base?
Pausable #2793
Changes from all commits
f7ce498
66c9d47
975d515
6be1f65
2915639
6344ca1
2a92f5d
88e6ad3
44d5fde
5b51b0b
131aee2
3c1d069
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| # The MASM code of the Pausable account component. | ||
| # | ||
| # NOTE: This is a temporary no-auth variant of the component for testing purposes. | ||
| # It is intended to be replaced by dedicated owner and role-based access control wrappers. | ||
|
|
||
| pub use ::miden::standards::utils::pausable::is_paused | ||
| pub use ::miden::standards::utils::pausable::pause | ||
| pub use ::miden::standards::utils::pausable::unpause | ||
| pub use ::miden::standards::utils::pausable::on_before_asset_added_to_account | ||
| pub use ::miden::standards::utils::pausable::on_before_asset_added_to_note |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,192 @@ | ||
| # miden::standards::utils::pausable | ||
| # | ||
| # Minimal pause flag storage and helpers. Pause/unpause procedures do not perform authorization. | ||
| # Compose with ownable2step or role-based access control in a higher layer. | ||
| # | ||
| # Asset callbacks enforce "not paused" when the issuing faucet has callbacks enabled on the asset. | ||
|
|
||
| use miden::core::word | ||
| use miden::protocol::active_account | ||
| use miden::protocol::native_account | ||
|
|
||
| # CONSTANTS | ||
| # ================================================================================================ | ||
|
|
||
| # The slot where the paused flag is stored as a single word. | ||
| # Unpaused: [0, 0, 0, 0]. Paused: [1, 0, 0, 0]. | ||
| const IS_PAUSED_SLOT = word("miden::standards::utils::pausable::is_paused") | ||
|
|
||
| const PAUSED_WORD = [1, 0, 0, 0] | ||
|
|
||
| const UNPAUSED_WORD = [0, 0, 0, 0] | ||
|
|
||
| # ERRORS | ||
| # ================================================================================================ | ||
|
|
||
| const ERR_PAUSABLE_ENFORCED_PAUSE = "the contract is paused" | ||
|
|
||
| const ERR_PAUSABLE_EXPECTED_PAUSE = "the contract is not paused" | ||
|
|
||
| # PUBLIC INTERFACE | ||
| # ================================================================================================ | ||
|
|
||
| #! Returns whether the account is currently paused. | ||
| #! | ||
| #! Reads [`IS_PAUSED_SLOT`] on the active account and returns `1` if the stored word is non-zero | ||
| #! (paused) or `0` if it is the zero word (unpaused). | ||
| #! | ||
| #! Inputs: [pad(16)] | ||
| #! Outputs: [is_paused, pad(15)] | ||
| #! | ||
| #! Invocation: call | ||
| pub proc is_paused | ||
| push.IS_PAUSED_SLOT[0..2] | ||
| exec.active_account::get_item | ||
| # => [is_paused, 0, 0, 0, pad(16)] | ||
|
|
||
| exec.word::eqz not | ||
| # => [is_paused, pad(16)] | ||
|
|
||
| swap drop | ||
| # => [is_paused, pad(15)] | ||
| end | ||
|
|
||
| #! Sets the paused flag. Fails if already paused. | ||
| #! | ||
| #! This procedure does not verify the caller. Wrap with access control in the account component | ||
| #! composition if only privileged accounts should pause. | ||
| #! | ||
| #! Inputs: [pad(16)] | ||
| #! Outputs: [pad(16)] | ||
| #! | ||
| #! Invocation: call | ||
| pub proc pause | ||
| exec.assert_not_paused | ||
| # => [pad(16)] | ||
|
|
||
| push.PAUSED_WORD | ||
| # => [1, 0, 0, 0, pad(16)] | ||
|
|
||
| push.IS_PAUSED_SLOT[0..2] | ||
| # => [slot_suffix, slot_prefix, 1, 0, 0, 0, pad(16)] | ||
|
|
||
| exec.native_account::set_item | ||
| # => [OLD_WORD, pad(16)] | ||
|
|
||
| dropw | ||
| # => [pad(16)] | ||
| end | ||
|
|
||
| #! Clears the paused flag. Fails if not paused. | ||
| #! | ||
| #! This procedure does not verify the caller. | ||
| #! | ||
| #! Inputs: [pad(16)] | ||
| #! Outputs: [pad(16)] | ||
| #! | ||
| #! Invocation: call | ||
| pub proc unpause | ||
| exec.assert_paused | ||
| # => [pad(16)] | ||
|
|
||
| push.UNPAUSED_WORD | ||
| # => [0, 0, 0, 0, pad(16)] | ||
|
|
||
| push.IS_PAUSED_SLOT[0..2] | ||
| # => [slot_suffix, slot_prefix, 0, 0, 0, 0, pad(16)] | ||
|
|
||
| exec.native_account::set_item | ||
| # => [OLD_WORD, pad(16)] | ||
|
|
||
| dropw | ||
| # => [pad(16)] | ||
| end | ||
|
|
||
| #! Requires the contract to be unpaused (storage word is the zero word). | ||
| #! | ||
| #! Reads [`IS_PAUSED_SLOT`] on the active account and applies [`word::eqz`]. If the stored word | ||
| #! is non-zero (paused), panics with [`ERR_PAUSABLE_ENFORCED_PAUSE`]. | ||
| #! | ||
| #! Use from other modules or transaction scripts to guard logic that must not run while paused. | ||
| #! In asset callback foreign context, the active account is the issuing faucet. | ||
| #! | ||
| #! Inputs: [] | ||
| #! Outputs: [] | ||
| #! | ||
| #! Panics if: | ||
| #! - the paused-state word is not the zero word. | ||
| #! | ||
| #! Invocation: exec | ||
| pub proc assert_not_paused | ||
|
Comment on lines
+119
to
+120
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not related to this PR, but we now technically have two types of procedures exposed by a component:
The above distinction doesn't matter too much for the components that just re-export functionality from the standard library, but for non-standardized components it would make a difference. And so, it may be a good idea to make this destination explicity. One way to do it is by switching to attributes to identify exported procedures. This would follow the approach we've taken with note scripts (and soon will take with transaction scripts). For example, we could have cc @mmagician, @greenhat, @bitwalker in case you guys have thoughts on this. |
||
| push.IS_PAUSED_SLOT[0..2] | ||
| exec.active_account::get_item | ||
| # => [is_paused, 0, 0, 0] | ||
|
|
||
| exec.word::eqz | ||
| # => [is_unpaused] | ||
|
|
||
| assert.err=ERR_PAUSABLE_ENFORCED_PAUSE | ||
| # => [] | ||
| end | ||
|
|
||
| #! Requires the contract to be paused (storage word is not the zero word). | ||
| #! | ||
| #! Reads [`IS_PAUSED_SLOT`] on the active account, then [`word::eqz`] and inverts. If the | ||
| #! stored word is zero (unpaused), panics with [`ERR_PAUSABLE_EXPECTED_PAUSE`]. | ||
| #! | ||
| #! Typical use: guard `unpause` so clearing the flag only happens from a paused state. | ||
| #! | ||
| #! Inputs: [] | ||
| #! Outputs: [] | ||
| #! | ||
| #! Panics if: | ||
| #! - the paused-state word is the zero word. | ||
| #! | ||
| #! Invocation: exec | ||
| pub proc assert_paused | ||
| push.IS_PAUSED_SLOT[0..2] | ||
| exec.active_account::get_item | ||
| # => [is_paused, 0, 0, 0] | ||
|
|
||
| exec.word::eqz not | ||
| # => [is_paused] | ||
|
|
||
| assert.err=ERR_PAUSABLE_EXPECTED_PAUSE | ||
| # => [] | ||
| end | ||
|
|
||
| # CALLBACKS | ||
| # ================================================================================================ | ||
|
|
||
| #! Callback when a callbacks-enabled asset is added to an account vault. | ||
| #! | ||
| #! Panics if this faucet account is paused (reads `IS_PAUSED_SLOT` on the active account, | ||
| #! which is the issuing faucet in the callback foreign context). | ||
| #! | ||
| #! Inputs: [ASSET_KEY, ASSET_VALUE, pad(8)] | ||
| #! Outputs: [ASSET_VALUE, pad(12)] | ||
| #! | ||
| #! Invocation: call | ||
| pub proc on_before_asset_added_to_account | ||
| exec.assert_not_paused | ||
| # => [ASSET_KEY, ASSET_VALUE, pad(8)] | ||
|
|
||
| dropw | ||
| # => [ASSET_VALUE, pad(12)] | ||
| end | ||
|
|
||
| #! Callback when a callbacks-enabled asset is added to an output note. | ||
| #! | ||
| #! Panics if this faucet account is paused. | ||
| #! | ||
| #! Inputs: [ASSET_KEY, ASSET_VALUE, note_idx, pad(7)] | ||
| #! Outputs: [ASSET_VALUE, pad(12)] | ||
| #! | ||
| #! Invocation: call | ||
| pub proc on_before_asset_added_to_note | ||
|
Comment on lines
+170
to
+186
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we need these callbacks here? I was imagining that they'd be defined in more specific contexts (e.g., in regulated stablecoin). |
||
| exec.assert_not_paused | ||
| # => [ASSET_KEY, ASSET_VALUE, note_idx, pad(7)] | ||
|
|
||
| dropw | ||
| # => [ASSET_VALUE, note_idx, pad(7)] | ||
| end | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will be changed in a follow-up PR, right? (i.e., we'll have owner-based or RBAC-based checks)