-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(pausable): add Pausable module and example contract
* feat(pausable): add contract module * fix(pausable): lib exports * feat(pausable): add pausable example * fix(pausable): rename unit test and fmt * refactor(pausable): combine clients.rs and errors.rs * fix(pausable): fmt * fix(pausable): missing no_std in examples * refactor(pausable): move events * fmt
- Loading branch information
Showing
34 changed files
with
5,170 additions
and
0 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
[workspace] | ||
resolver = "2" | ||
members = [ | ||
"contracts/utils/*", | ||
"examples/*", | ||
] | ||
|
||
[workspace.package] | ||
authors = ["OpenZeppelin"] | ||
edition = "2021" | ||
license = "MIT" | ||
repository = "https://github.com/OpenZeppelin/soroban-contracts" | ||
version = "0.0.0" | ||
|
||
[workspace.dependencies] | ||
soroban-sdk = "22.0.1" | ||
|
||
# members | ||
openzeppelin-pausable = { path = "contracts/utils/pausable" } | ||
|
||
[profile.release] | ||
opt-level = "z" | ||
overflow-checks = true | ||
debug = 0 | ||
strip = "symbols" | ||
debug-assertions = false | ||
panic = "abort" | ||
codegen-units = 1 | ||
lto = true | ||
|
||
# For more information about this profile see https://soroban.stellar.org/docs/basic-tutorials/logging#cargotoml-profile | ||
[profile.release-with-logs] | ||
inherits = "release" | ||
debug-assertions = true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
[package] | ||
name = "openzeppelin-pausable" | ||
edition.workspace = true | ||
license.workspace = true | ||
repository.workspace = true | ||
publish = false | ||
version.workspace = true | ||
|
||
[lib] | ||
crate-type = ["lib", "cdylib"] | ||
doctest = false | ||
|
||
[dependencies] | ||
soroban-sdk = { workspace = true } | ||
|
||
[dev-dependencies] | ||
soroban-sdk = { workspace = true, features = ["testutils"] } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
default: build | ||
|
||
all: test | ||
|
||
test: build | ||
cargo test | ||
|
||
build: | ||
stellar contract build | ||
@ls -l target/wasm32-unknown-unknown/release/*.wasm | ||
|
||
fmt: | ||
cargo fmt --all | ||
|
||
clean: | ||
cargo clean |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
#![no_std] | ||
|
||
mod pausable; | ||
mod storage; | ||
|
||
pub use crate::{ | ||
pausable::{emit_paused, emit_unpaused, Pausable, PausableClient}, | ||
storage::{pause, paused, unpause, when_not_paused, when_paused}, | ||
}; | ||
|
||
mod test; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
//! Pausable Contract Module. | ||
//! | ||
//! Contract module which allows implementing an emergency stop mechanism | ||
//! that can be triggered by an authorized account. | ||
//! | ||
//! It provides functions [`pausable::when_not_paused`] | ||
//! and [`pausable::when_paused`], | ||
//! which can be added to the functions of your contract. | ||
//! | ||
//! Note that your contract will NOT be pausable by simply including this | ||
//! module only once and where you use [`pausable::when_not_paused`]. | ||
use soroban_sdk::{contractclient, contracterror, symbol_short, Address, Env}; | ||
|
||
#[contractclient(name = "PausableClient")] | ||
pub trait Pausable { | ||
/// Returns true if the contract is paused, and false otherwise. | ||
/// | ||
/// # Arguments | ||
/// | ||
/// * `e` - Access to Soroban environment. | ||
fn paused(e: Env) -> bool; | ||
|
||
/// Triggers `Paused` state. | ||
/// | ||
/// # Arguments | ||
/// | ||
/// * `e` - Access to Soroban environment. | ||
/// * `caller` - The address of the caller. | ||
/// | ||
/// # Errors | ||
/// | ||
/// If the contract is in `Paused` state, then the error | ||
/// [`PausableError::EnforcedPause`] is thrown. | ||
/// | ||
/// # Events | ||
/// | ||
/// * topics - `["paused"]` | ||
/// * data - `[caller: Address]` | ||
fn pause(e: Env, caller: Address); | ||
|
||
/// Triggers `Unpaused` state. | ||
/// | ||
/// # Arguments | ||
/// | ||
/// * `e` - Access to Soroban environment. | ||
/// * `caller` - The address of the caller. | ||
/// | ||
/// # Errors | ||
/// | ||
/// If the contract is in `Unpaused` state, then the error | ||
/// [`PausableError::ExpectedPause`] is thrown. | ||
/// | ||
/// # Events | ||
/// | ||
/// * topics - `["unpaused"]` | ||
/// * data - `[caller: Address]` | ||
fn unpause(e: Env, caller: Address); | ||
} | ||
|
||
// ################## ERRORS ################## | ||
|
||
#[contracterror] | ||
#[repr(u32)] | ||
pub enum PausableError { | ||
/// The operation failed because the contract is paused. | ||
EnforcedPause = 1, | ||
/// The operation failed because the contract is not paused. | ||
ExpectedPause = 2, | ||
} | ||
|
||
// ################## EVENTS ################## | ||
|
||
/// Emits an event when `Paused` state is triggered. | ||
/// | ||
/// # Arguments | ||
/// | ||
/// * `e` - Access to Soroban environment. | ||
/// * `caller` - The address of the caller. | ||
/// | ||
/// # Events | ||
/// | ||
/// * topics - `["paused"]` | ||
/// * data - `[caller: Address]` | ||
pub fn emit_paused(e: &Env, caller: &Address) { | ||
let topics = (symbol_short!("paused"),); | ||
e.events().publish(topics, caller) | ||
} | ||
|
||
/// Emits an event when `Unpaused` state is triggered. | ||
/// | ||
/// # Arguments | ||
/// | ||
/// * `e` - Access to Soroban environment. | ||
/// * `caller` - The address of the caller. | ||
/// | ||
/// # Events | ||
/// | ||
/// * topics - `["unpaused"]` | ||
/// * data - `[caller: Address]` | ||
pub fn emit_unpaused(e: &Env, caller: &Address) { | ||
let topics = (symbol_short!("unpaused"),); | ||
e.events().publish(topics, caller) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
use soroban_sdk::{panic_with_error, symbol_short, Address, Env, Symbol}; | ||
|
||
use crate::{emit_paused, emit_unpaused, pausable::PausableError}; | ||
|
||
/// Indicates whether the contract is in `Paused` state. | ||
pub(crate) const PAUSED: Symbol = symbol_short!("PAUSED"); | ||
|
||
/// Returns true if the contract is paused, and false otherwise. | ||
/// | ||
/// # Arguments | ||
/// | ||
/// * `e` - Access to Soroban environment. | ||
pub fn paused(e: &Env) -> bool { | ||
// if not paused, consider default false (unpaused) | ||
e.storage().instance().get(&PAUSED).unwrap_or(false) | ||
} | ||
|
||
/// Triggers `Paused` state. | ||
/// | ||
/// # Arguments | ||
/// | ||
/// * `e` - Access to Soroban environment. | ||
/// * `caller` - The address of the caller. | ||
/// | ||
/// # Errors | ||
/// | ||
/// If the contract is in `Paused` state, then the error | ||
/// [`PausableError::EnforcedPause`] is thrown. | ||
/// | ||
/// # Events | ||
/// | ||
/// * topics - `["paused"]` | ||
/// * data - `[caller: Address]` | ||
pub fn pause(e: &Env, caller: &Address) { | ||
caller.require_auth(); | ||
when_not_paused(e); | ||
e.storage().instance().set(&PAUSED, &true); | ||
emit_paused(e, caller); | ||
} | ||
|
||
/// Triggers `Unpaused` state. | ||
/// | ||
/// # Arguments | ||
/// | ||
/// * `e` - Access to Soroban environment. | ||
/// * `caller` - The address of the caller. | ||
/// | ||
/// # Errors | ||
/// | ||
/// If the contract is in `Unpaused` state, then the error | ||
/// [`PausableError::ExpectedPause`] is thrown. | ||
/// | ||
/// # Events | ||
/// | ||
/// * topics - `["unpaused"]` | ||
/// * data - `[caller: Address]` | ||
pub fn unpause(e: &Env, caller: &Address) { | ||
caller.require_auth(); | ||
when_paused(e); | ||
e.storage().instance().set(&PAUSED, &false); | ||
emit_unpaused(e, caller); | ||
} | ||
|
||
/// Helper to make a function callable only when the contract is NOT | ||
/// paused. | ||
/// | ||
/// # Arguments | ||
/// | ||
/// * `e` - Access to Soroban environment. | ||
/// | ||
/// # Errors | ||
/// | ||
/// If the contract is in the `Paused` state, then the error | ||
/// [`PausableError::EnforcedPause`] is thrown. | ||
pub fn when_not_paused(e: &Env) { | ||
if paused(e) { | ||
panic_with_error!(e, PausableError::EnforcedPause) | ||
} | ||
} | ||
|
||
/// Helper to make a function callable | ||
/// only when the contract is paused. | ||
/// | ||
/// # Arguments | ||
/// | ||
/// * `e` - Access to Soroban environment. | ||
/// | ||
/// # Errors | ||
/// | ||
/// If the contract is in `Unpaused` state, then the error | ||
/// [`PausableError::ExpectedPause`] is thrown. | ||
pub fn when_paused(e: &Env) { | ||
if !paused(e) { | ||
panic_with_error!(e, PausableError::ExpectedPause) | ||
} | ||
} |
Oops, something went wrong.