From d6545e290834fff3703af64cbeb35259acc3b753 Mon Sep 17 00:00:00 2001 From: bitzoic Date: Mon, 23 Oct 2023 10:10:48 +0200 Subject: [PATCH 01/15] Inital commit --- libs/pausable/Forc.toml | 5 +++++ libs/pausable/src/lib.sw | 2 ++ 2 files changed, 7 insertions(+) create mode 100644 libs/pausable/Forc.toml create mode 100644 libs/pausable/src/lib.sw diff --git a/libs/pausable/Forc.toml b/libs/pausable/Forc.toml new file mode 100644 index 00000000..d9bcba2c --- /dev/null +++ b/libs/pausable/Forc.toml @@ -0,0 +1,5 @@ +[project] +authors = ["Fuel Labs "] +entry = "lib.sw" +license = "Apache-2.0" +name = "pausable" diff --git a/libs/pausable/src/lib.sw b/libs/pausable/src/lib.sw new file mode 100644 index 00000000..6523652f --- /dev/null +++ b/libs/pausable/src/lib.sw @@ -0,0 +1,2 @@ +library; + From fa0205424c1ef7c47ea79dded306d52e188c0eb6 Mon Sep 17 00:00:00 2001 From: bitzoic Date: Mon, 23 Oct 2023 10:32:51 +0200 Subject: [PATCH 02/15] Add Pausable library --- libs/pausable/src/lib.sw | 184 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 184 insertions(+) diff --git a/libs/pausable/src/lib.sw b/libs/pausable/src/lib.sw index 6523652f..06f46e42 100644 --- a/libs/pausable/src/lib.sw +++ b/libs/pausable/src/lib.sw @@ -1,2 +1,186 @@ library; +use std::{ + constants::ZERO_B256, + token::{mint, burn}, +}; + +// Precomputed hash of sha256("pausable") +const PAUSABLE = 0xd987cda398e9af257cbcf8a8995c5dccb19833cadc727ba56b0fec60ccf8944c; + +/// Error emitted upon the opposite of the desired pause state. +pub enum PauseError { + /// Emitted when the contract is paused. + Paused: (), + /// Emitted when the contract is not paused. + NotPaused: (), +} + +abi Pausable { + /// Pauses the contract. + /// + /// # Additional Information + /// + /// It is highly encouraged to use the Ownership Library in order to lock this + /// function to a single administrative user. + /// + /// # Examples + /// + /// ```sway + /// use pausable::Pausable; + /// + /// fn foo(contract: ContractId) { + /// let pausable_abi = abi(Pauseable, contract); + /// pausable_abi.pause(); + /// assert(pausable_abi.is_paused() == true); + /// } + /// ``` + fn pause(); + + /// Returns whether the contract is paused. + /// + /// # Returns + /// + /// * [bool] - The pause state for the contract. + /// + /// # Examples + /// + /// ```sway + /// use pausable::Pausable; + /// + /// fn foo(contract: ContractId) { + /// let pausable_abi = abi(Pauseable, contract); + /// assert(pausable_abi.is_paused() == false); + /// } + /// ``` + fn is_paused() -> bool; + + /// Unpauses the contract. + /// + /// # Additional Information + /// + /// It is highly encouraged to use the Ownership Library in order to lock this + /// function to a single administrative user. + /// + /// # Examples + /// + /// ```sway + /// use pausable::Pausable; + /// + /// fn foo(contract: ContractId) { + /// let pausable_abi = abi(Pauseable, contract); + /// pausable_abi.unpause(); + /// assert(pausable_abi.is_paused() == false); + /// } + /// ``` + fn unpause(); +} + +/// Unconditionally sets the contract to the paused state. +/// +/// # Examples +/// +/// ```sway +/// use pausable::{_pause, _is_paused}; +/// +/// fn foo() { +/// _pause(); +/// assert(_is_paused() == true); +/// } +/// ``` +pub fn _pause() { + if balance() == 0 { + mint(PAUSABLE, 1); + } +} + +/// Unconditionally sets the contract to the unpaused state. +/// +/// # Examples +/// +/// ```sway +/// use pausable::{_unpause, _is_paused}; +/// +/// fn foo() { +/// _unpause(); +/// assert(_is_paused() == false); +/// } +/// ``` +pub fn _unpause() { + if balance() == 1 { + burn(PAUSABLE, 1); + } +} + +/// Returns whether the contract is in the paused or unpaused state. +/// +/// # Returns +/// +/// * [bool] - The pause state of the contract. +/// +/// # Examples +/// +/// ```sway +/// use pausable::_is_paused; +/// +/// fn foo() { +/// assert(_is_paused() == false); +/// } +/// ``` +pub fn _is_paused() -> bool { + balance() == 1 +} + +/// Requires that the contract is in the paused state. +/// +/// # Reverts +/// +/// * When the contract is not paused. +/// +/// # Examples +/// +/// ```sway +/// use pausable::{_pause, require_paused}; +/// +/// fn foo() { +/// _pause(); +/// require_paused(); +/// } +/// ``` +pub fn require_paused() { + require(balance() == 1, PauseError::NotPaused); +} + +/// Requires that the contract is in the unpaused state. +/// +/// # Reverts +/// +/// * When the contract is paused. +/// +/// # Examples +/// +/// ```sway +/// use pausable::{_unpause, require_not_paused}; +/// +/// fn foo() { +/// _unpause(); +/// require_not_paused(); +/// } +/// ``` +pub fn require_not_paused() { + require(balance() == 0, PauseError::Paused); +} + +fn balance() -> u64 { + let id = asm() { fp: b256 }; + let result_buffer = ZERO_B256; + + // Hashing in assmeby gives us significant gas savings over using the std::hash::sha256 + // as this does not use std::bytes::Bytes. + // Only possible because of the fixed length of bytes. + asm(balance, token_id: result_buffer, ptr: (id, PAUSABLE), bytes: 64, id: id) { + s256 token_id ptr bytes; + bal balance token_id id; + balance: u64 + } +} From e3b7825926c8696ef445516d21990128c6833e65 Mon Sep 17 00:00:00 2001 From: bitzoic Date: Mon, 23 Oct 2023 10:50:19 +0200 Subject: [PATCH 03/15] Add pausable tests --- tests/Forc.toml | 1 + tests/src/pausable/Forc.toml | 8 +++ tests/src/pausable/src/main.sw | 90 ++++++++++++++++++++++++++++++++++ 3 files changed, 99 insertions(+) create mode 100644 tests/src/pausable/Forc.toml create mode 100644 tests/src/pausable/src/main.sw diff --git a/tests/Forc.toml b/tests/Forc.toml index a68c006c..0f864eb4 100644 --- a/tests/Forc.toml +++ b/tests/Forc.toml @@ -24,6 +24,7 @@ members = [ "./src/fixed_point/ifp256_div_test", "./src/fixed_point/ifp256_test", "./src/merkle_proof", + "./src/pausable", "./src/ownership", "./src/reentrancy/reentrancy_attacker_abi", "./src/reentrancy/reentrancy_attacker_contract", diff --git a/tests/src/pausable/Forc.toml b/tests/src/pausable/Forc.toml new file mode 100644 index 00000000..861d9aa8 --- /dev/null +++ b/tests/src/pausable/Forc.toml @@ -0,0 +1,8 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +name = "pausable_test" + +[dependencies] +pausable = { path = "../../../libs/pausable" } diff --git a/tests/src/pausable/src/main.sw b/tests/src/pausable/src/main.sw new file mode 100644 index 00000000..e432b9fe --- /dev/null +++ b/tests/src/pausable/src/main.sw @@ -0,0 +1,90 @@ +contract; + +use pausable::{_is_paused, _pause, _unpause, Pausable, require_not_paused, require_paused}; + +abi RequireTests { + fn test_require_paused(); + fn test_require_not_paused(); +} + +impl Pausable for Contract { + fn pause() { + _pause(); + } + + fn unpause() { + _unpause(); + } + + fn is_paused() -> bool { + _is_paused() + } +} + +impl RequireTests for Contract { + fn test_require_paused() { + require_paused(); + } + + fn test_require_not_paused() { + require_not_paused(); + } +} + +#[test] +fn test_is_paused() { + let pausable_abi = abi(Pausable, CONTRACT_ID); + + assert(pausable_abi.is_paused() == false); +} + +#[test] +fn test_pause() { + let pausable_abi = abi(Pausable, CONTRACT_ID); + + assert(pausable_abi.is_paused() == false); + pausable_abi.pause(); + assert(pausable_abi.is_paused() == true); +} + +#[test] +fn test_unpause() { + let pausable_abi = abi(Pausable, CONTRACT_ID); + pausable_abi.pause(); + + assert(pausable_abi.is_paused() == true); + pausable_abi.unpause(); + assert(pausable_abi.is_paused() == false); +} + +#[test] +fn test_require_not_paused() { + let require_abi = abi(RequireTests, CONTRACT_ID); + + require_abi.test_require_not_paused(); +} + +#[test(should_revert)] +fn test_revert_require_not_paused() { + let pausable_abi = abi(Pausable, CONTRACT_ID); + let require_abi = abi(RequireTests, CONTRACT_ID); + pausable_abi.pause(); + + require_abi.test_require_not_paused(); +} + +#[test] +fn test_require_paused() { + let pausable_abi = abi(Pausable, CONTRACT_ID); + let require_abi = abi(RequireTests, CONTRACT_ID); + pausable_abi.pause(); + + require_abi.test_require_paused(); +} + +#[test(should_revert)] +fn test_revert_require_paused() { + let require_abi = abi(RequireTests, CONTRACT_ID); + + require_abi.test_require_paused(); +} From 93bab06b24752d09858dae6101ca07f6ac08976a Mon Sep 17 00:00:00 2001 From: bitzoic Date: Mon, 23 Oct 2023 11:25:39 +0200 Subject: [PATCH 04/15] Add README --- libs/pausable/README.md | 82 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 libs/pausable/README.md diff --git a/libs/pausable/README.md b/libs/pausable/README.md new file mode 100644 index 00000000..823a557c --- /dev/null +++ b/libs/pausable/README.md @@ -0,0 +1,82 @@ +

+ + + pausable_logo_light + +

+ +# Overview + +The Pausable library allows contracts to implement an emergency stop mechanism. This can be useful for scenarios such as having an emergency switch to freeze all transactions in the event of a large bug. + +It is highly encouraged to use the [Ownership Library](../ownership/) in combination with the Pausable Library to ensure that only a single administrative user has the ability to pause your contract. + +This library is completely stateless and follows a packet oriented design providing significant gas savings. + +More information can be found in the [specification](./SPECIFICATION.md). + +# Using the Library + +## Getting Started + +In order to use the Pausable library it must be added to the `Forc.toml` file and then imported into your Sway project. To add Pausable as a dependency to the `Forc.toml` file in your project please see the [README.md](../../README.md). + +You may import the Pausable library's functionalities like so: + +```rust +use pausable::*; +``` + +## Basic Functionality + +Once imported, the Pausable library's functions should be available. Using the Pausable Library is as simple as calling your desired function. + +The Pausable Library has two states: + +`- Paused` +`- Unpaused` + +By default, your contract will start in the `Unpaused` state. To pause your contract, you may call the `_pause()` function. The example below provides a basic pausable contract using the Pausable Library's `Pausable` abi without any restrictions such as an administrator. + +```sway +use pausable::{_is_paused, _pause, _unpause, Pausable}; + +impl Pausable for Contract { + fn pause() { + _pause(); + } + + fn unpause() { + _unpause(); + } + + fn is_paused() -> bool { + _is_paused() + } +} +``` + +## Requiring A State + +When developing a contract, you may want to lock functions down to a specific state. To do this, you may call either of the `require_paused()` or `require_not_paused()` functions. The example below shows these functions in use. + +```sway +use pausable::{require_not_paused, require_paused}; + +abi MyAbi { + fn require_paused_state(); + fn require_not_paused_state(); +} + +impl MyAbi for Contract { + fn require_paused_state() { + require_paused(); + // This comment will only ever be reached if the contract is in the paused state + } + + fn require_not_paused_state() { + require_not_paused(); + // This comment will only ever be reached if the contract is in the unpaused state + } +} +``` From 61857ccd82637bf29f2d506708541f63260de917 Mon Sep 17 00:00:00 2001 From: bitzoic Date: Mon, 23 Oct 2023 11:25:52 +0200 Subject: [PATCH 05/15] Add SPECIFICATION --- libs/pausable/SPECIFICATION.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 libs/pausable/SPECIFICATION.md diff --git a/libs/pausable/SPECIFICATION.md b/libs/pausable/SPECIFICATION.md new file mode 100644 index 00000000..1062aa8f --- /dev/null +++ b/libs/pausable/SPECIFICATION.md @@ -0,0 +1,31 @@ +# Overview + +This document provides an overview of the Pausable library. + +It outlines the use cases, i.e. specification, and describes how to implement the library. + +## Use Cases + +The Pausable library can be used anytime a contract needs a basic paused and unpased state, such as in the case of an emergency when a major bug is found. + +## Public Functions + +### `_pause()` + +This function will unconditionally set the contract to the paused state. + +### `_unpause()` + +This function will unconditionally set the contract to the unpaused state. + +### `_is_paused()` + +This function will return whether the contract is in the paused state. + +### `require_paused()` + +This function will ensure the contract is in the paused state before continuing. + +### `require_not_paused()` + +This function will ensure the contract is in the unpaused state before continuing. From 5699797c9c058429b436998e5717b8ed2ba83bcd Mon Sep 17 00:00:00 2001 From: bitzoic Date: Mon, 23 Oct 2023 11:26:09 +0200 Subject: [PATCH 06/15] Add README header images --- .../pausable/.docs/pausable-logo-dark-theme.png | Bin 0 -> 17858 bytes .../.docs/pausable-logo-light-theme.png | Bin 0 -> 20068 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 libs/pausable/.docs/pausable-logo-dark-theme.png create mode 100644 libs/pausable/.docs/pausable-logo-light-theme.png diff --git a/libs/pausable/.docs/pausable-logo-dark-theme.png b/libs/pausable/.docs/pausable-logo-dark-theme.png new file mode 100644 index 0000000000000000000000000000000000000000..d1cb394d42b8ed449d317b253462d355b18c778f GIT binary patch literal 17858 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 literal 0 HcmV?d00001 diff --git a/libs/pausable/.docs/pausable-logo-light-theme.png b/libs/pausable/.docs/pausable-logo-light-theme.png new file mode 100644 index 0000000000000000000000000000000000000000..52e256ed0c30c13589b4607e44c38ccc2aaec445 GIT binary patch literal 20068 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 Date: Mon, 23 Oct 2023 11:28:01 +0200 Subject: [PATCH 07/15] Update src README --- README.md | 1 + libs/pausable/README.md | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6e15f46b..30475f87 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ These libraries contain helper functions and other tools valuable to blockchain - [Fixed Point Number](./libs/fixed_point/) is an interface to implement fixed-point numbers. - [Queue](./libs/queue/) is a linear data structure that provides First-In-First-Out (FIFO) operations. - [Token](./libs/token/) provides helper functions for the [SRC-20](https://github.com/FuelLabs/sway-standards/tree/master/standards/src_20), [SRC-3](https://github.com/FuelLabs/sway-standards/tree/master/standards/src_3), and [SRC-7](https://github.com/FuelLabs/sway-standards/tree/master/standards/src_7) standards. +- [Pausable](./libs/pausable/) allows contracts to implement an emergency stop mechanism. ## Using a library diff --git a/libs/pausable/README.md b/libs/pausable/README.md index 823a557c..5993a439 100644 --- a/libs/pausable/README.md +++ b/libs/pausable/README.md @@ -9,10 +9,10 @@ The Pausable library allows contracts to implement an emergency stop mechanism. This can be useful for scenarios such as having an emergency switch to freeze all transactions in the event of a large bug. -It is highly encouraged to use the [Ownership Library](../ownership/) in combination with the Pausable Library to ensure that only a single administrative user has the ability to pause your contract. - This library is completely stateless and follows a packet oriented design providing significant gas savings. +> **NOTE** It is highly encouraged to use the [Ownership Library](../ownership/) in combination with the Pausable Library to ensure that only a single administrative user has the ability to pause your contract. + More information can be found in the [specification](./SPECIFICATION.md). # Using the Library From a2d6a071b4de25c93fe502a9913f57023958c1d8 Mon Sep 17 00:00:00 2001 From: bitzoic Date: Mon, 23 Oct 2023 11:29:01 +0200 Subject: [PATCH 08/15] Add Pausable to workspace --- libs/Forc.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/libs/Forc.toml b/libs/Forc.toml index 5a7fa837..354c069b 100644 --- a/libs/Forc.toml +++ b/libs/Forc.toml @@ -3,6 +3,7 @@ members = [ "fixed_point", "merkle_proof", "ownership", + "pausable", "queue", "reentrancy", "signed_integers", From 2bed22711efaaa2199831cbc3c590e1082d8a990 Mon Sep 17 00:00:00 2001 From: bitzoic Date: Mon, 23 Oct 2023 11:54:22 +0200 Subject: [PATCH 09/15] Run formatter/ --- libs/pausable/src/lib.sw | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/libs/pausable/src/lib.sw b/libs/pausable/src/lib.sw index 06f46e42..f5516aaa 100644 --- a/libs/pausable/src/lib.sw +++ b/libs/pausable/src/lib.sw @@ -1,9 +1,5 @@ -library; - -use std::{ - constants::ZERO_B256, - token::{mint, burn}, -}; +library; +use std::{constants::ZERO_B256, token::{burn, mint}}; // Precomputed hash of sha256("pausable") const PAUSABLE = 0xd987cda398e9af257cbcf8a8995c5dccb19833cadc727ba56b0fec60ccf8944c; @@ -21,7 +17,7 @@ abi Pausable { /// /// # Additional Information /// - /// It is highly encouraged to use the Ownership Library in order to lock this + /// It is highly encouraged to use the Ownership Library in order to lock this /// function to a single administrative user. /// /// # Examples @@ -59,7 +55,7 @@ abi Pausable { /// /// # Additional Information /// - /// It is highly encouraged to use the Ownership Library in order to lock this + /// It is highly encouraged to use the Ownership Library in order to lock this /// function to a single administrative user. /// /// # Examples @@ -154,7 +150,7 @@ pub fn require_paused() { /// Requires that the contract is in the unpaused state. /// /// # Reverts -/// +/// /// * When the contract is paused. /// /// # Examples @@ -173,14 +169,13 @@ pub fn require_not_paused() { fn balance() -> u64 { let id = asm() { fp: b256 }; - let result_buffer = ZERO_B256; - + let result_buffer = ZERO_B256; // Hashing in assmeby gives us significant gas savings over using the std::hash::sha256 // as this does not use std::bytes::Bytes. // Only possible because of the fixed length of bytes. asm(balance, token_id: result_buffer, ptr: (id, PAUSABLE), bytes: 64, id: id) { s256 token_id ptr bytes; - bal balance token_id id; + bal balance token_id id; balance: u64 } } From 0f0f155c973ede0c2e6b8d815be2f057b4de5192 Mon Sep 17 00:00:00 2001 From: bitzoic Date: Wed, 25 Oct 2023 10:33:30 +0200 Subject: [PATCH 10/15] Review comments --- libs/pausable/README.md | 2 +- libs/pausable/src/lib.sw | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/libs/pausable/README.md b/libs/pausable/README.md index 5993a439..3d806e4e 100644 --- a/libs/pausable/README.md +++ b/libs/pausable/README.md @@ -23,7 +23,7 @@ In order to use the Pausable library it must be added to the `Forc.toml` file an You may import the Pausable library's functionalities like so: -```rust +```sway use pausable::*; ``` diff --git a/libs/pausable/src/lib.sw b/libs/pausable/src/lib.sw index f5516aaa..c5438bc1 100644 --- a/libs/pausable/src/lib.sw +++ b/libs/pausable/src/lib.sw @@ -25,8 +25,8 @@ abi Pausable { /// ```sway /// use pausable::Pausable; /// - /// fn foo(contract: ContractId) { - /// let pausable_abi = abi(Pauseable, contract); + /// fn foo(contract_id: ContractId) { + /// let pausable_abi = abi(Pauseable, contract_id); /// pausable_abi.pause(); /// assert(pausable_abi.is_paused() == true); /// } @@ -44,8 +44,8 @@ abi Pausable { /// ```sway /// use pausable::Pausable; /// - /// fn foo(contract: ContractId) { - /// let pausable_abi = abi(Pauseable, contract); + /// fn foo(contract_id: ContractId) { + /// let pausable_abi = abi(Pauseable, contract_id); /// assert(pausable_abi.is_paused() == false); /// } /// ``` @@ -63,8 +63,8 @@ abi Pausable { /// ```sway /// use pausable::Pausable; /// - /// fn foo(contract: ContractId) { - /// let pausable_abi = abi(Pauseable, contract); + /// fn foo(contract_id: ContractId) { + /// let pausable_abi = abi(Pauseable, contract_id); /// pausable_abi.unpause(); /// assert(pausable_abi.is_paused() == false); /// } @@ -108,7 +108,7 @@ pub fn _unpause() { } } -/// Returns whether the contract is in the paused or unpaused state. +/// Returns whether the contract is in the paused state. /// /// # Returns /// From 3bb0af8a3171d2a55ce697eacaa454ceeba5376b Mon Sep 17 00:00:00 2001 From: bitzoic Date: Fri, 27 Oct 2023 10:16:32 +0200 Subject: [PATCH 11/15] Update pausable library to use storage instead of packet oriented design --- libs/pausable/src/lib.sw | 141 ++++++++++++++++++++++++++++----------- 1 file changed, 101 insertions(+), 40 deletions(-) diff --git a/libs/pausable/src/lib.sw b/libs/pausable/src/lib.sw index c5438bc1..fbef06b1 100644 --- a/libs/pausable/src/lib.sw +++ b/libs/pausable/src/lib.sw @@ -1,8 +1,4 @@ library; -use std::{constants::ZERO_B256, token::{burn, mint}}; - -// Precomputed hash of sha256("pausable") -const PAUSABLE = 0xd987cda398e9af257cbcf8a8995c5dccb19833cadc727ba56b0fec60ccf8944c; /// Error emitted upon the opposite of the desired pause state. pub enum PauseError { @@ -20,6 +16,10 @@ abi Pausable { /// It is highly encouraged to use the Ownership Library in order to lock this /// function to a single administrative user. /// + /// # Number of Storage Accesses + /// + /// * Writes: `1` + /// /// # Examples /// /// ```sway @@ -31,6 +31,7 @@ abi Pausable { /// assert(pausable_abi.is_paused() == true); /// } /// ``` + #[storage(write)] fn pause(); /// Returns whether the contract is paused. @@ -39,6 +40,10 @@ abi Pausable { /// /// * [bool] - The pause state for the contract. /// + /// # Number of Storage Accesses + /// + /// * Reads: `1` + /// /// # Examples /// /// ```sway @@ -49,6 +54,7 @@ abi Pausable { /// assert(pausable_abi.is_paused() == false); /// } /// ``` + #[storage(read)] fn is_paused() -> bool; /// Unpauses the contract. @@ -58,6 +64,10 @@ abi Pausable { /// It is highly encouraged to use the Ownership Library in order to lock this /// function to a single administrative user. /// + /// # Number of Storage Accesses + /// + /// * Writes: `1` + /// /// # Examples /// /// ```sway @@ -69,113 +79,164 @@ abi Pausable { /// assert(pausable_abi.is_paused() == false); /// } /// ``` + #[storage(write)] fn unpause(); } /// Unconditionally sets the contract to the paused state. /// +/// # Arguments +/// +/// * `paused_key`: [StorageKey] - The location in storage at which the paused state is stored. +/// +/// # Number of Storage Accesses +/// +/// * Writes: `1` +/// /// # Examples /// /// ```sway /// use pausable::{_pause, _is_paused}; /// +/// storage { +/// paused: bool = false, +/// } +/// /// fn foo() { -/// _pause(); -/// assert(_is_paused() == true); +/// _pause(storage.paused); +/// assert(_is_paused(storage.paused) == true); /// } /// ``` -pub fn _pause() { - if balance() == 0 { - mint(PAUSABLE, 1); - } +#[storage(write)] +pub fn _pause(paused_key: StorageKey) { + paused_key.write(true); } /// Unconditionally sets the contract to the unpaused state. /// +/// # Arguments +/// +/// * `paused_key`: [StorageKey] - The location in storage at which the paused state is stored. +/// +/// # Number of Storage Accesses +/// +/// * Writes: `1` +/// /// # Examples /// /// ```sway /// use pausable::{_unpause, _is_paused}; /// +/// storage { +/// paused: bool = false, +/// } +/// /// fn foo() { -/// _unpause(); -/// assert(_is_paused() == false); +/// _unpause(storage.paused); +/// assert(_is_paused(storage.paused) == false); /// } /// ``` -pub fn _unpause() { - if balance() == 1 { - burn(PAUSABLE, 1); - } +#[storage(write)] +pub fn _unpause(paused_key: StorageKey) { + paused_key.write(false); } /// Returns whether the contract is in the paused state. /// +/// # Arguments +/// +/// * `paused_key`: [StorageKey] - The location in storage at which the paused state is stored. +/// /// # Returns /// /// * [bool] - The pause state of the contract. /// +/// # Number of Storage Accesses +/// +/// * Reads: `1` +/// /// # Examples /// /// ```sway /// use pausable::_is_paused; /// +/// storage { +/// paused: bool = false, +/// } +/// /// fn foo() { -/// assert(_is_paused() == false); +/// assert(_is_paused(storage.paused) == false); /// } /// ``` -pub fn _is_paused() -> bool { - balance() == 1 +#[storage(read)] +pub fn _is_paused(paused_key: StorageKey) -> bool { + paused_key.read() } /// Requires that the contract is in the paused state. /// +/// # Arguments +/// +/// * `paused_key`: [StorageKey] - The location in storage at which the paused state is stored. +/// /// # Reverts /// /// * When the contract is not paused. /// +/// # Number of Storage Accesses +/// +/// * Reads: `1` +/// /// # Examples /// /// ```sway /// use pausable::{_pause, require_paused}; /// +/// storage { +/// paused: bool = false, +/// } +/// /// fn foo() { -/// _pause(); -/// require_paused(); +/// _pause(storage.paused); +/// require_paused(storage.paused); +/// // Only reachable when paused /// } /// ``` -pub fn require_paused() { - require(balance() == 1, PauseError::NotPaused); +#[storage(read)] +pub fn require_paused(paused_key: StorageKey) { + require(paused_key.read(), PauseError::NotPaused); } /// Requires that the contract is in the unpaused state. /// +/// # Arguments +/// +/// * `paused_key`: [StorageKey] - The location in storage at which the paused state is stored. +/// /// # Reverts /// /// * When the contract is paused. /// +/// # Number of Storage Accesses +/// +/// * Reads: `1` +/// /// # Examples /// /// ```sway /// use pausable::{_unpause, require_not_paused}; /// +/// storage { +/// paused: bool = false, +/// } +/// /// fn foo() { -/// _unpause(); -/// require_not_paused(); +/// _unpause(storage.paused); +/// require_not_paused(storage.paused); +/// // Only reachable when unpaused /// } /// ``` -pub fn require_not_paused() { - require(balance() == 0, PauseError::Paused); -} - -fn balance() -> u64 { - let id = asm() { fp: b256 }; - let result_buffer = ZERO_B256; - // Hashing in assmeby gives us significant gas savings over using the std::hash::sha256 - // as this does not use std::bytes::Bytes. - // Only possible because of the fixed length of bytes. - asm(balance, token_id: result_buffer, ptr: (id, PAUSABLE), bytes: 64, id: id) { - s256 token_id ptr bytes; - bal balance token_id id; - balance: u64 - } +#[storage(read)] +pub fn require_not_paused(paused_key: StorageKey) { + require(!paused_key.read(), PauseError::Paused); } From 90ef5f6c7a3bb60e66c2e4bb90ddbd62bede8472 Mon Sep 17 00:00:00 2001 From: bitzoic Date: Fri, 27 Oct 2023 10:16:43 +0200 Subject: [PATCH 12/15] Update tests for storage design --- tests/src/pausable/src/main.sw | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/tests/src/pausable/src/main.sw b/tests/src/pausable/src/main.sw index e432b9fe..08f764f7 100644 --- a/tests/src/pausable/src/main.sw +++ b/tests/src/pausable/src/main.sw @@ -2,32 +2,43 @@ contract; use pausable::{_is_paused, _pause, _unpause, Pausable, require_not_paused, require_paused}; +storage { + paused: bool = false, +} + abi RequireTests { + #[storage(read)] fn test_require_paused(); + #[storage(read)] fn test_require_not_paused(); } impl Pausable for Contract { + #[storage(write)] fn pause() { - _pause(); + _pause(storage.paused); } + #[storage(write)] fn unpause() { - _unpause(); + _unpause(storage.paused); } + #[storage(read)] fn is_paused() -> bool { - _is_paused() + _is_paused(storage.paused) } } impl RequireTests for Contract { + #[storage(read)] fn test_require_paused() { - require_paused(); + require_paused(storage.paused); } + #[storage(read)] fn test_require_not_paused() { - require_not_paused(); + require_not_paused(storage.paused); } } From d54f83febbf9a6bcb9c0f696654e5d956a6616f2 Mon Sep 17 00:00:00 2001 From: bitzoic Date: Fri, 27 Oct 2023 10:16:53 +0200 Subject: [PATCH 13/15] Update README for storage design --- libs/pausable/README.md | 39 ++++++++++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/libs/pausable/README.md b/libs/pausable/README.md index 3d806e4e..d80e4a43 100644 --- a/libs/pausable/README.md +++ b/libs/pausable/README.md @@ -9,8 +9,6 @@ The Pausable library allows contracts to implement an emergency stop mechanism. This can be useful for scenarios such as having an emergency switch to freeze all transactions in the event of a large bug. -This library is completely stateless and follows a packet oriented design providing significant gas savings. - > **NOTE** It is highly encouraged to use the [Ownership Library](../ownership/) in combination with the Pausable Library to ensure that only a single administrative user has the ability to pause your contract. More information can be found in the [specification](./SPECIFICATION.md). @@ -27,31 +25,46 @@ You may import the Pausable library's functionalities like so: use pausable::*; ``` +Be sure to add the storage block bellow to your contract which enables the library. + +```sway +storage { + paused: bool = false, +} +``` + ## Basic Functionality Once imported, the Pausable library's functions should be available. Using the Pausable Library is as simple as calling your desired function. The Pausable Library has two states: -`- Paused` -`- Unpaused` +- `Paused` +- `Unpaused` By default, your contract will start in the `Unpaused` state. To pause your contract, you may call the `_pause()` function. The example below provides a basic pausable contract using the Pausable Library's `Pausable` abi without any restrictions such as an administrator. ```sway use pausable::{_is_paused, _pause, _unpause, Pausable}; +storage { + paused: bool = false, +} + impl Pausable for Contract { + #[storage(write)] fn pause() { - _pause(); + _pause(storage.paused); } + #[storage(write)] fn unpause() { - _unpause(); + _unpause(storage.paused); } + #[storage(read)] fn is_paused() -> bool { - _is_paused() + _is_paused(storage.paused) } } ``` @@ -63,19 +76,27 @@ When developing a contract, you may want to lock functions down to a specific st ```sway use pausable::{require_not_paused, require_paused}; +storage { + paused: bool = false, +} + abi MyAbi { + #[storage(read)] fn require_paused_state(); + #[storage(read)] fn require_not_paused_state(); } impl MyAbi for Contract { + #[storage(read)] fn require_paused_state() { - require_paused(); + require_paused(storage.paused); // This comment will only ever be reached if the contract is in the paused state } + #[storage(read)] fn require_not_paused_state() { - require_not_paused(); + require_not_paused(storage.paused); // This comment will only ever be reached if the contract is in the unpaused state } } From 70fc2180f4bb4eec9a390e8bf1ab196a5f7b18fa Mon Sep 17 00:00:00 2001 From: bitzoic Date: Fri, 27 Oct 2023 12:49:51 +0200 Subject: [PATCH 14/15] Remove StorageKey arguemnt for pause functions --- libs/pausable/README.md | 26 +++---------- libs/pausable/src/lib.sw | 82 ++++++++++++---------------------------- 2 files changed, 30 insertions(+), 78 deletions(-) diff --git a/libs/pausable/README.md b/libs/pausable/README.md index d80e4a43..03bd8495 100644 --- a/libs/pausable/README.md +++ b/libs/pausable/README.md @@ -25,14 +25,6 @@ You may import the Pausable library's functionalities like so: use pausable::*; ``` -Be sure to add the storage block bellow to your contract which enables the library. - -```sway -storage { - paused: bool = false, -} -``` - ## Basic Functionality Once imported, the Pausable library's functions should be available. Using the Pausable Library is as simple as calling your desired function. @@ -47,24 +39,20 @@ By default, your contract will start in the `Unpaused` state. To pause your cont ```sway use pausable::{_is_paused, _pause, _unpause, Pausable}; -storage { - paused: bool = false, -} - impl Pausable for Contract { #[storage(write)] fn pause() { - _pause(storage.paused); + _pause(); } #[storage(write)] fn unpause() { - _unpause(storage.paused); + _unpause(); } #[storage(read)] fn is_paused() -> bool { - _is_paused(storage.paused) + _is_paused() } } ``` @@ -76,10 +64,6 @@ When developing a contract, you may want to lock functions down to a specific st ```sway use pausable::{require_not_paused, require_paused}; -storage { - paused: bool = false, -} - abi MyAbi { #[storage(read)] fn require_paused_state(); @@ -90,13 +74,13 @@ abi MyAbi { impl MyAbi for Contract { #[storage(read)] fn require_paused_state() { - require_paused(storage.paused); + require_paused(); // This comment will only ever be reached if the contract is in the paused state } #[storage(read)] fn require_not_paused_state() { - require_not_paused(storage.paused); + require_not_paused(); // This comment will only ever be reached if the contract is in the unpaused state } } diff --git a/libs/pausable/src/lib.sw b/libs/pausable/src/lib.sw index fbef06b1..c0f6b0e4 100644 --- a/libs/pausable/src/lib.sw +++ b/libs/pausable/src/lib.sw @@ -1,5 +1,8 @@ library; +// Precomputed hash of sha256("pausable") +const PAUSABLE = 0xd987cda398e9af257cbcf8a8995c5dccb19833cadc727ba56b0fec60ccf8944c; + /// Error emitted upon the opposite of the desired pause state. pub enum PauseError { /// Emitted when the contract is paused. @@ -28,7 +31,7 @@ abi Pausable { /// fn foo(contract_id: ContractId) { /// let pausable_abi = abi(Pauseable, contract_id); /// pausable_abi.pause(); - /// assert(pausable_abi.is_paused() == true); + /// assert(pausable_abi.is_paused()); /// } /// ``` #[storage(write)] @@ -51,7 +54,7 @@ abi Pausable { /// /// fn foo(contract_id: ContractId) { /// let pausable_abi = abi(Pauseable, contract_id); - /// assert(pausable_abi.is_paused() == false); + /// assert(!pausable_abi.is_paused()); /// } /// ``` #[storage(read)] @@ -76,7 +79,7 @@ abi Pausable { /// fn foo(contract_id: ContractId) { /// let pausable_abi = abi(Pauseable, contract_id); /// pausable_abi.unpause(); - /// assert(pausable_abi.is_paused() == false); + /// assert(!pausable_abi.is_paused()); /// } /// ``` #[storage(write)] @@ -85,10 +88,6 @@ abi Pausable { /// Unconditionally sets the contract to the paused state. /// -/// # Arguments -/// -/// * `paused_key`: [StorageKey] - The location in storage at which the paused state is stored. -/// /// # Number of Storage Accesses /// /// * Writes: `1` @@ -98,26 +97,19 @@ abi Pausable { /// ```sway /// use pausable::{_pause, _is_paused}; /// -/// storage { -/// paused: bool = false, -/// } -/// /// fn foo() { -/// _pause(storage.paused); -/// assert(_is_paused(storage.paused) == true); +/// _pause(); +/// assert(_is_paused()); /// } /// ``` #[storage(write)] -pub fn _pause(paused_key: StorageKey) { +pub fn _pause() { + let paused_key = StorageKey::new(PAUSABLE, 0, PAUSABLE); paused_key.write(true); } /// Unconditionally sets the contract to the unpaused state. /// -/// # Arguments -/// -/// * `paused_key`: [StorageKey] - The location in storage at which the paused state is stored. -/// /// # Number of Storage Accesses /// /// * Writes: `1` @@ -127,26 +119,19 @@ pub fn _pause(paused_key: StorageKey) { /// ```sway /// use pausable::{_unpause, _is_paused}; /// -/// storage { -/// paused: bool = false, -/// } -/// /// fn foo() { -/// _unpause(storage.paused); -/// assert(_is_paused(storage.paused) == false); +/// _unpause(); +/// assert(!_is_paused()); /// } /// ``` #[storage(write)] -pub fn _unpause(paused_key: StorageKey) { +pub fn _unpause() { + let paused_key = StorageKey::new(PAUSABLE, 0, PAUSABLE); paused_key.write(false); } /// Returns whether the contract is in the paused state. /// -/// # Arguments -/// -/// * `paused_key`: [StorageKey] - The location in storage at which the paused state is stored. -/// /// # Returns /// /// * [bool] - The pause state of the contract. @@ -160,25 +145,18 @@ pub fn _unpause(paused_key: StorageKey) { /// ```sway /// use pausable::_is_paused; /// -/// storage { -/// paused: bool = false, -/// } -/// /// fn foo() { -/// assert(_is_paused(storage.paused) == false); +/// assert(!_is_paused()); /// } /// ``` #[storage(read)] -pub fn _is_paused(paused_key: StorageKey) -> bool { +pub fn _is_paused() -> bool { + let paused_key = StorageKey::new(PAUSABLE, 0, PAUSABLE); paused_key.read() } /// Requires that the contract is in the paused state. /// -/// # Arguments -/// -/// * `paused_key`: [StorageKey] - The location in storage at which the paused state is stored. -/// /// # Reverts /// /// * When the contract is not paused. @@ -192,27 +170,20 @@ pub fn _is_paused(paused_key: StorageKey) -> bool { /// ```sway /// use pausable::{_pause, require_paused}; /// -/// storage { -/// paused: bool = false, -/// } -/// /// fn foo() { -/// _pause(storage.paused); -/// require_paused(storage.paused); +/// _pause(); +/// require_paused(); /// // Only reachable when paused /// } /// ``` #[storage(read)] -pub fn require_paused(paused_key: StorageKey) { +pub fn require_paused() { + let paused_key = StorageKey::::new(PAUSABLE, 0, PAUSABLE); require(paused_key.read(), PauseError::NotPaused); } /// Requires that the contract is in the unpaused state. /// -/// # Arguments -/// -/// * `paused_key`: [StorageKey] - The location in storage at which the paused state is stored. -/// /// # Reverts /// /// * When the contract is paused. @@ -226,17 +197,14 @@ pub fn require_paused(paused_key: StorageKey) { /// ```sway /// use pausable::{_unpause, require_not_paused}; /// -/// storage { -/// paused: bool = false, -/// } -/// /// fn foo() { -/// _unpause(storage.paused); -/// require_not_paused(storage.paused); +/// _unpause(); +/// require_not_paused(); /// // Only reachable when unpaused /// } /// ``` #[storage(read)] -pub fn require_not_paused(paused_key: StorageKey) { +pub fn require_not_paused() { + let paused_key = StorageKey::::new(PAUSABLE, 0, PAUSABLE); require(!paused_key.read(), PauseError::Paused); } From 548e269010b33d9e56f21bb36d32ca42996fbb6d Mon Sep 17 00:00:00 2001 From: bitzoic Date: Fri, 27 Oct 2023 12:50:47 +0200 Subject: [PATCH 15/15] Update tests for no arguments --- tests/src/pausable/src/main.sw | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/tests/src/pausable/src/main.sw b/tests/src/pausable/src/main.sw index 08f764f7..ef7e1a96 100644 --- a/tests/src/pausable/src/main.sw +++ b/tests/src/pausable/src/main.sw @@ -2,10 +2,6 @@ contract; use pausable::{_is_paused, _pause, _unpause, Pausable, require_not_paused, require_paused}; -storage { - paused: bool = false, -} - abi RequireTests { #[storage(read)] fn test_require_paused(); @@ -16,29 +12,29 @@ abi RequireTests { impl Pausable for Contract { #[storage(write)] fn pause() { - _pause(storage.paused); + _pause(); } #[storage(write)] fn unpause() { - _unpause(storage.paused); + _unpause(); } #[storage(read)] fn is_paused() -> bool { - _is_paused(storage.paused) + _is_paused() } } impl RequireTests for Contract { #[storage(read)] fn test_require_paused() { - require_paused(storage.paused); + require_paused(); } #[storage(read)] fn test_require_not_paused() { - require_not_paused(storage.paused); + require_not_paused(); } } @@ -46,16 +42,16 @@ impl RequireTests for Contract { fn test_is_paused() { let pausable_abi = abi(Pausable, CONTRACT_ID); - assert(pausable_abi.is_paused() == false); + assert(!pausable_abi.is_paused()); } #[test] fn test_pause() { let pausable_abi = abi(Pausable, CONTRACT_ID); - assert(pausable_abi.is_paused() == false); + assert(!pausable_abi.is_paused()); pausable_abi.pause(); - assert(pausable_abi.is_paused() == true); + assert(pausable_abi.is_paused()); } #[test] @@ -63,9 +59,9 @@ fn test_unpause() { let pausable_abi = abi(Pausable, CONTRACT_ID); pausable_abi.pause(); - assert(pausable_abi.is_paused() == true); + assert(pausable_abi.is_paused()); pausable_abi.unpause(); - assert(pausable_abi.is_paused() == false); + assert(!pausable_abi.is_paused()); } #[test]