1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
//! Pause pattern implements methods to pause, unpause and get the status of the
//! contract.
//!
//! [`Pause`] implements methods to pause and unpause the contract. When the
//! methods are called the contracts status changes and the respective event
//! is emitted. A contract starts off as "unpaused" by default. [`PauseExternal`]
//! exposes an external function to check the status of the contract.
//!
//! This [derive macro](near_contract_tools_macros::Pause)
//! derives a default implementation for both these traits.
//!
//! # Safety
//! The default implementation assumes or enforces the following invariants.
//! Violating assumed invariants may corrupt contract state and show unexpected
//! behavior (UB). Enforced invariants throw an error (ERR) but contract
//! state remains intact.
//!
//! * Initial state is unpaused.
//! * (UB) The pause root storage slot is not used or modified. The default key is `~p`.
//! * (ERR) Only an "unpaused" contract can call `pause`.
//! * (ERR) Only a "paused" contract can call `unpause`.
//! * (ERR) [`Pause::require_paused`] may only be called when the contract is paused.
//! * (ERR) [`Pause::require_unpaused`] may only be called when the contract is unpaused.
#![allow(missing_docs)] // #[ext_contract(...)] does not play nicely with clippy
use crate::{slot::Slot, standard::nep297::Event, DefaultStorageKey};
use near_contract_tools_macros::event;
use near_sdk::{ext_contract, require};
const UNPAUSED_FAIL_MESSAGE: &str = "Disallowed while contract is unpaused";
const PAUSED_FAIL_MESSAGE: &str = "Disallowed while contract is paused";
/// Events emitted when contract pause state is changed
#[event(
standard = "x-paus",
version = "1.0.0",
crate = "crate",
macros = "near_contract_tools_macros"
)]
#[derive(Debug, Clone)]
pub enum PauseEvent {
/// Emitted when the contract is paused
Pause,
/// Emitted when the contract is unpaused
Unpause,
}
/// Internal-only interactions for a pausable contract
///
/// # Examples
///
/// ```
/// use near_sdk::near_bindgen;
/// use near_contract_tools::{pause::Pause, Pause};
///
/// #[derive(Pause)]
/// #[near_bindgen]
/// struct Contract {
/// // ...
/// }
///
/// #[near_bindgen]
/// impl Contract {
/// pub fn only_when_unpaused(&self) {
/// Self::require_unpaused();
/// }
///
/// pub fn only_when_paused(&self) {
/// Self::require_paused();
/// }
///
/// pub fn emergency_shutdown(&mut self) {
/// self.pause();
/// }
///
/// pub fn emergency_shutdown_end(&mut self) {
/// self.unpause();
/// }
/// }
/// ```
pub trait Pause {
/// Storage root
fn root() -> Slot<()> {
Slot::new(DefaultStorageKey::Pause)
}
/// Storage slot for pause state
fn slot_paused() -> Slot<bool> {
Self::root().transmute()
}
/// Force the contract pause state in a particular direction.
/// Does not emit events or check the current pause state.
fn set_is_paused(&mut self, is_paused: bool) {
Self::slot_paused().write(&is_paused);
}
/// Returns `true` if the contract is paused, `false` otherwise
fn is_paused() -> bool {
Self::slot_paused().read().unwrap_or(false)
}
/// Pauses the contract if it is currently unpaused, panics otherwise.
/// Emits a `PauseEvent::Pause` event.
fn pause(&mut self) {
Self::require_unpaused();
self.set_is_paused(true);
PauseEvent::Pause.emit();
}
/// Unpauses the contract if it is currently paused, panics otherwise.
/// Emits a `PauseEvent::Unpause` event.
fn unpause(&mut self) {
Self::require_paused();
self.set_is_paused(false);
PauseEvent::Unpause.emit();
}
/// Rejects if the contract is unpaused
fn require_paused() {
require!(Self::is_paused(), UNPAUSED_FAIL_MESSAGE);
}
/// Rejects if the contract is paused
fn require_unpaused() {
require!(!Self::is_paused(), PAUSED_FAIL_MESSAGE);
}
}
/// External methods for [Pause]
#[ext_contract(ext_pause)]
pub trait PauseExternal {
/// Returns `true` if the contract is paused, `false` otherwise
fn paus_is_paused(&self) -> bool;
}