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;
}