From 03991cc13889a5b4f5632efbbee0cf41cc66254c Mon Sep 17 00:00:00 2001 From: bitzoic Date: Thu, 16 Nov 2023 11:30:24 +0300 Subject: [PATCH 1/5] Refactor Ownership Library --- libs/ownership/src/errors.sw | 4 +- libs/ownership/src/ownable.sw | 418 +++++++++++++--------------------- 2 files changed, 156 insertions(+), 266 deletions(-) diff --git a/libs/ownership/src/errors.sw b/libs/ownership/src/errors.sw index 63d570c9..c043b06e 100644 --- a/libs/ownership/src/errors.sw +++ b/libs/ownership/src/errors.sw @@ -1,9 +1,7 @@ library; /// Error log for when access is denied. -pub enum AccessError { +pub enum InitializationError { /// Emiited when an owner has already been set. CannotReinitialized: (), - /// Emitted when the caller is not the owner of the contract. - NotOwner: (), } diff --git a/libs/ownership/src/ownable.sw b/libs/ownership/src/ownable.sw index 1d17323d..95f848bf 100644 --- a/libs/ownership/src/ownable.sw +++ b/libs/ownership/src/ownable.sw @@ -3,277 +3,169 @@ library; pub mod errors; pub mod events; -use errors::AccessError; +use errors::InitializationError; use events::{OwnershipRenounced, OwnershipSet, OwnershipTransferred}; use std::{auth::msg_sender, hash::sha256, storage::storage_api::{read, write}}; -use src_5::State; - -pub struct Ownership { - state: State, +use src_5::{AccessError, State}; + +// Pre-computed hash digest of sha256("owner") +const OWNER = 0x4c1029697ee358715d3a14a2add817c4b01651440de808371f78165ac90dc581; + +/// Returns the owner. +/// +/// # Returns +/// +/// * [State] - The state of the ownership. +/// +/// # Number of Storage Accesses +/// +/// * Reads: `1` +/// +/// # Examples +/// +/// ```sway +/// use ownable::*; +/// +/// fn foo() { +/// let stored_owner = _owner(); +/// } +/// ``` +#[storage(read)] +pub fn _owner() -> State { + let owner_key = StorageKey::new(OWNER, 0, OWNER); + owner_key.try_read().unwrap_or(State::Uninitialized) } -impl Ownership { - /// Returns the `Ownership` struct in the `Uninitalized` state. - /// - /// # Returns - /// - /// * [Ownership] - The SRC-5 Ownership Standard struct. - /// - /// # Examples - /// - /// ```sway - /// use ownership::Ownership; - /// - /// fn foo() { - /// let ownership = Ownership::uninitalized(); - /// assert(ownership.state == State::Uninitalized); - /// } - /// ``` - pub fn uninitialized() -> Self { - Self { - state: State::Uninitialized, - } - } - - /// Returns the `Ownership` struct in the `Initalized` state. - /// - /// # Arguments - /// - /// * `identity`: [Identity] - The `Identity` which ownership is set to. - /// - /// # Returns - /// - /// * [Ownership] - The SRC-5 Ownership Standard struct. - /// - /// # Examples - /// - /// ```sway - /// use ownership::Ownership; - /// use std::constants::ZERO_B256; - /// - /// fn foo() { - /// let identity = Identity::Address(Address::from(ZERO_B256)); - /// let ownership = Ownership::initialized(); - /// assert(ownership.state == State::Initialized(identity)); - /// } - /// ``` - pub fn initialized(identity: Identity) -> Self { - Self { - state: State::Initialized(identity), - } - } - - /// Returns the `Ownership` struct in the `Revoked` state. - /// - /// # Additional Information - /// - /// Any ownership that is revoked is forever locked in that state. The ownership cannot be reset. - /// - /// # Returns - /// - /// * [Ownership] - The SRC-5 Ownership Standard struct. - /// - /// # Examples - /// - /// ```sway - /// use ownership::Ownership; - /// - /// fn foo() { - /// let ownership = Ownership::revoked(); - /// assert(ownership.state == State::Revoked); - /// } - /// ``` - pub fn revoked() -> Self { - Self { - state: State::Revoked, - } - } +/// Ensures that the sender is the owner. +/// +/// # Reverts +/// +/// * When the sender is not the owner. +/// +/// # Number of Storage Accesses +/// +/// * Reads: `1` +/// +/// # Examples +/// +/// ```sway +/// use ownable::*; +/// +/// fn foo() { +/// only_owner(); +/// // Do stuff here +/// } +/// ``` +#[storage(read)] +pub fn only_owner() { + require(_owner() == State::Initialized(msg_sender().unwrap()), AccessError::NotOwner); } -impl StorageKey { - /// Returns the owner. - /// - /// # Returns - /// - /// * [State] - The state of the ownership. - /// - /// # Number of Storage Accesses - /// - /// * Reads: `1` - /// - /// # Examples - /// - /// ```sway - /// use ownable::Ownership; - /// - /// storage { - /// owner: Ownership = Ownership::initalized(Identity::Address(Address::from(ZERO_B256))), - /// } - /// - /// fn foo() { - /// let stored_owner = storage.owner.owner(); - /// } - /// ``` - #[storage(read)] - pub fn owner(self) -> State { - self.read().state - } +/// Revokes ownership of the current owner and disallows any new owners. +/// +/// # Reverts +/// +/// * When the sender is not the owner. +/// +/// # Number of Storage Accesses +/// +/// * Reads: `1` +/// * Writes: `1` +/// +/// # Examples +/// +/// ```sway +/// use ownable::{_owner, renounce_ownership}; +/// +/// fn foo() { +/// assert(owner() == State::Initialized(Identity::Address(Address::from(ZERO_B256))); +/// renounce_ownership(); +/// assert(owner() == State::Revoked); +/// } +/// ``` +#[storage(read, write)] +pub fn renounce_ownership() { + only_owner(); + + let owner_key = StorageKey::new(OWNER, 0, OWNER); + owner_key.write(State::Revoked); + + log(OwnershipRenounced { + previous_owner: msg_sender().unwrap(), + }); } -impl StorageKey { - /// Ensures that the sender is the owner. - /// - /// # Reverts - /// - /// * When the sender is not the owner. - /// - /// # Number of Storage Accesses - /// - /// * Reads: `1` - /// - /// # Examples - /// - /// ```sway - /// use ownable::Ownership; - /// - /// storage { - /// owner: Ownership = Ownership::initalized(Identity::Address(Address::from(ZERO_B256))), - /// } - /// - /// fn foo() { - /// storage.owner.only_owner(); - /// // Do stuff here - /// } - /// ``` - #[storage(read)] - pub fn only_owner(self) { - require( - self - .owner() == State::Initialized(msg_sender().unwrap()), - AccessError::NotOwner, - ); - } +/// Sets the passed identity as the initial owner. +/// +/// # Arguments +/// +/// * `new_owner`: [Identity] - The `Identity` that will be the first owner. +/// +/// # Reverts +/// +/// * When ownership has been set before. +/// +/// # Number of Storage Acesses +/// +/// * Reads: `1` +/// * Write: `1` +/// +/// # Examples +/// +/// ```sway +/// use ownable::{_owner, initialize_ownership}; +/// +/// fn foo(owner: Identity) { +/// assert(_owner() == State::Uninitialized); +/// initialize_ownership(owner); +/// assert(_owner() == State::Initialized(owner)); +/// } +/// ``` +#[storage(read, write)] +pub fn initialize_ownership(new_owner: Identity) { + require(_owner() == State::Uninitialized, InitializationError::CannotReinitialized); + + let owner_key = StorageKey::new(OWNER, 0, OWNER); + owner_key.write(State::Initialized(new_owner)); + + log(OwnershipSet { new_owner }); } -impl StorageKey { - /// Revokes ownership of the current owner and disallows any new owners. - /// - /// # Reverts - /// - /// * When the sender is not the owner. - /// - /// # Number of Storage Accesses - /// - /// * Reads: `1` - /// * Writes: `1` - /// - /// # Examples - /// - /// ```sway - /// use ownable::Ownership; - /// - /// storage { - /// owner: Ownership = Ownership::initalized(Identity::Address(Address::from(ZERO_B256))), - /// } - /// - /// fn foo() { - /// assert(storage.owner.owner() == State::Initialized(Identity::Address(Address::from(ZERO_B256))); - /// storage.owner.renounce_ownership(); - /// assert(storage.owner.owner() == State::Revoked); - /// } - /// ``` - #[storage(read, write)] - pub fn renounce_ownership(self) { - self.only_owner(); - - self.write(Ownership::revoked()); - - log(OwnershipRenounced { - previous_owner: msg_sender().unwrap(), - }); - } - - /// Sets the passed identity as the initial owner. - /// - /// # Arguments - /// - /// * `new_owner`: [Identity] - The `Identity` that will be the first owner. - /// - /// # Reverts - /// - /// * When ownership has been set before. - /// - /// # Number of Storage Acesses - /// - /// * Reads: `1` - /// * Write: `1` - /// - /// # Examples - /// - /// ```sway - /// use ownable::Ownership; - /// - /// storage { - /// owner: Ownership = Ownership::uninitialized(), - /// } - /// - /// fn foo(owner: Identity) { - /// assert(storage.owner.owner() == State::Uninitialized); - /// storage.owner.set_ownership(owner); - /// assert(storage.owner.owner() == State::Initialized(owner)); - /// } - /// ``` - #[storage(read, write)] - pub fn set_ownership(self, new_owner: Identity) { - require( - self - .owner() == State::Uninitialized, - AccessError::CannotReinitialized, - ); - - self.write(Ownership::initialized(new_owner)); - - log(OwnershipSet { new_owner }); - } - - /// Transfers ownership to the passed identity. - /// - /// # Arguments - /// - /// * `new_owner`: [Identity] - The `Identity` that will be the next owner. - /// - /// # Reverts - /// - /// * When the sender is not the owner. - /// - /// # Number of Storage Acesses - /// - /// * Reads: `1` - /// * Write: `1` - /// - /// # Examples - /// - /// ```sway - /// use ownable::Ownership; - /// - /// storage { - /// owner: Ownership = OwnershipOwnership::initalized(Identity::Address(Address::from(ZERO_B256))), - /// } - /// - /// fn foo(new_owner: Identity) { - /// assert(storage.owner.owner() == State::Initialized(Identity::Address(Address::from(ZERO_B256))); - /// storage.owner.transfer_ownership(new_owner); - /// assert(storage.owner.owner() == State::Initialized(new_owner)); - /// } - /// ``` - #[storage(read, write)] - pub fn transfer_ownership(self, new_owner: Identity) { - self.only_owner(); - self.write(Ownership::initialized(new_owner)); - - log(OwnershipTransferred { - new_owner, - previous_owner: msg_sender().unwrap(), - }); - } +/// Transfers ownership to the passed identity. +/// +/// # Arguments +/// +/// * `new_owner`: [Identity] - The `Identity` that will be the next owner. +/// +/// # Reverts +/// +/// * When the sender is not the owner. +/// +/// # Number of Storage Acesses +/// +/// * Reads: `1` +/// * Write: `1` +/// +/// # Examples +/// +/// ```sway +/// use ownable::{_owner, transfer_ownership}; +/// +/// fn foo(new_owner: Identity) { +/// assert(_owner() == State::Initialized(Identity::Address(Address::from(ZERO_B256))); +/// transfer_ownership(new_owner); +/// assert(_owner() == State::Initialized(new_owner)); +/// } +/// ``` +#[storage(read, write)] +pub fn transfer_ownership(new_owner: Identity) { + only_owner(); + + let owner_key = StorageKey::new(OWNER, 0, OWNER); + owner_key.write(State::Initialized(new_owner)); + + log(OwnershipTransferred { + new_owner, + previous_owner: msg_sender().unwrap(), + }); } From cbd7842e2ea389641cea21ab01cc878d8c85b767 Mon Sep 17 00:00:00 2001 From: bitzoic Date: Thu, 16 Nov 2023 11:30:39 +0300 Subject: [PATCH 2/5] Update README and SPECIFICTION --- libs/ownership/README.md | 59 +++++++++++++++++---------------- libs/ownership/SPECIFICATION.md | 16 ++------- 2 files changed, 33 insertions(+), 42 deletions(-) diff --git a/libs/ownership/README.md b/libs/ownership/README.md index b515a77b..078a2e2b 100644 --- a/libs/ownership/README.md +++ b/libs/ownership/README.md @@ -7,7 +7,7 @@ # Overview -The Ownership library provides a way to block users other than a single "owner" or "admin" from calling functions. Ownership is often used when needing administrative calls on a contract. +The Ownership library provides a way to block users other than a single "owner" from calling functions. Ownership is often used when needing administrative calls on a contract. For more information please see the [specification](./SPECIFICATION.md). @@ -15,53 +15,56 @@ For more information please see the [specification](./SPECIFICATION.md). ## Getting Started -In order to use the Ownership library it must be added to the Forc.toml file and then imported into your Sway project. To add Sway-libs as a dependency to the Forc.toml file in your project please see the [README.md](../../README.md). +In order to use the Ownership library it must be added to the `Forc.toml` file and then imported into your Sway project. To add Sway-libs as a dependency to the `Forc.toml` file in your project please see the [README.md](../../README.md). > **NOTE** Until [Issue #5025](https://github.com/FuelLabs/sway/issues/5025) is resolved, in order to use the Ownership Library you must also add the [SRC-5](https://github.com/FuelLabs/sway-standards/tree/master/standards/src_5) standard as a dependencies. You may import the Ownership library's functionalities like so: -```rust -use ownership::Ownership; +```sway +use ownership::*; ``` -Once imported, the `Ownership` struct should be added to the storage block of your contract. There are two approaches when declaring ownership in storage. - -1. Initalize the owner on contract deployment by calling the `initialized()` function. - -```rust -storage { - owner: Ownership = Ownership::initialized(Identity::Address(Address::from(0x0000000000000000000000000000000000000000000000000000000000000000))), -} -``` - -2. Leave the owner uninitialized and call the `set_ownership()` function in your own constructor. - -```rust -storage { - owner: Ownership = Ownership::uninitialized(), -} +Once imported, the Ownership library's functions should be available. To use them, be sure to initialize the owner for your contract by calling the `initialize_ownership()` function in your own constructor. +```sway #[storage(read, write)] fn my_constructor(new_owner: Identity) { - storage.owner.set_ownership(new_owner); + initialize_ownership(new_owner); } ``` -> **Note** If this approach is taken, `set_ownership()` **MUST** be called to have a contract owner. - ## Basic Functionality To restrict a function to only the owner, call the `only_owner()` function. -```rust -storage.owner.only_owner(); +```sway +only_owner(); +// Only the contract's owner may reach this line. ``` -To return the owner from storage, call the `owner()` function. +To return the ownership state from storage, call the `_owner()` function. -```rust -let owner: Option = storage.owner.owner(); +```sway +let owner: State = _owner(); ``` +## Integrating the Ownership Library into the SRC-5 Standard + +To implement the SRC-5 standard with the Ownership library, be sure to add the [SRC-5](https://github.com/FuelLabs/sway-standards/tree/master/standards/src_5) abi to your contract. The following demonstrates the integration of the Ownership library with the SRC-5 standard. + +```sway +use ownership::_owner; +use src_5::{State, SRC5}; + +impl SRC5 for Contract { + #[storage(read)] + fn owner() -> State { + _owner() + } +} +``` + +> **NOTE** A constructor must be implemented to initialize the owner. + For more information please see the [specification](./SPECIFICATION.md). diff --git a/libs/ownership/SPECIFICATION.md b/libs/ownership/SPECIFICATION.md index 0fea1d5c..7de88528 100644 --- a/libs/ownership/SPECIFICATION.md +++ b/libs/ownership/SPECIFICATION.md @@ -14,7 +14,7 @@ The Ownership library can be used anytime a function should be restricted to a s This function will ensure that the current caller is the owner. -### `owner()` +### `_owner()` Returns the owner stored in storage. @@ -22,7 +22,7 @@ Returns the owner stored in storage. Only callable by the current owner, this function will remove the owner. -### `set_ownership()` +### `initialize_ownership()` This function will store a new owner if one has not been set. @@ -30,16 +30,4 @@ This function will store a new owner if one has not been set. Only callable by the current owner, this function will transfer ownership to another user. -### `uninitialized()` - -Creates a new ownership in the `Uninitialized` state. - -### `initialized()` - -Creates a new ownership in the `Initialized` state. - -### `revoked()` - -Creates a new ownership in the `Revoked` state. - > **Note** Once the ownership has been revoked it cannot be set or transferred again. From 1423e4d9efe5ef6d6b80fc26f415eb44c0e60e67 Mon Sep 17 00:00:00 2001 From: bitzoic Date: Thu, 16 Nov 2023 11:30:51 +0300 Subject: [PATCH 3/5] Update tests --- tests/src/ownership/src/main.sw | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/tests/src/ownership/src/main.sw b/tests/src/ownership/src/main.sw index faa3b03f..93c97e3e 100644 --- a/tests/src/ownership/src/main.sw +++ b/tests/src/ownership/src/main.sw @@ -3,10 +3,6 @@ contract; use ownership::*; use src_5::{SRC5, State}; -storage { - owner: Ownership = Ownership::uninitialized(), -} - abi OwnableTest { #[storage(read)] fn only_owner(); @@ -21,28 +17,28 @@ abi OwnableTest { impl SRC5 for Contract { #[storage(read)] fn owner() -> State { - storage.owner.owner() + _owner() } } impl OwnableTest for Contract { #[storage(read)] fn only_owner() { - storage.owner.only_owner(); + only_owner(); } #[storage(read, write)] fn renounce_ownership() { - storage.owner.renounce_ownership(); + renounce_ownership(); } #[storage(read, write)] fn set_ownership(new_owner: Identity) { - storage.owner.set_ownership(new_owner); + initialize_ownership(new_owner); } #[storage(read, write)] fn transfer_ownership(new_owner: Identity) { - storage.owner.transfer_ownership(new_owner); + transfer_ownership(new_owner); } } From 941ad567cf3925171863719bf7047507bd400550 Mon Sep 17 00:00:00 2001 From: bitzoic Date: Thu, 16 Nov 2023 11:39:11 +0300 Subject: [PATCH 4/5] Run formatteR --- libs/ownership/src/ownable.sw | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/libs/ownership/src/ownable.sw b/libs/ownership/src/ownable.sw index 95f848bf..0e1d1057 100644 --- a/libs/ownership/src/ownable.sw +++ b/libs/ownership/src/ownable.sw @@ -58,7 +58,10 @@ pub fn _owner() -> State { /// ``` #[storage(read)] pub fn only_owner() { - require(_owner() == State::Initialized(msg_sender().unwrap()), AccessError::NotOwner); + require( + _owner() == State::Initialized(msg_sender().unwrap()), + AccessError::NotOwner, + ); } /// Revokes ownership of the current owner and disallows any new owners. @@ -123,7 +126,10 @@ pub fn renounce_ownership() { /// ``` #[storage(read, write)] pub fn initialize_ownership(new_owner: Identity) { - require(_owner() == State::Uninitialized, InitializationError::CannotReinitialized); + require( + _owner() == State::Uninitialized, + InitializationError::CannotReinitialized, + ); let owner_key = StorageKey::new(OWNER, 0, OWNER); owner_key.write(State::Initialized(new_owner)); From fa67f339d31350f456e41ac2527f868996a0cdd9 Mon Sep 17 00:00:00 2001 From: bitzoic Date: Mon, 20 Nov 2023 11:45:57 +0300 Subject: [PATCH 5/5] Update README based on PR review comments --- libs/ownership/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/ownership/README.md b/libs/ownership/README.md index 078a2e2b..c4ca6aa9 100644 --- a/libs/ownership/README.md +++ b/libs/ownership/README.md @@ -25,7 +25,7 @@ You may import the Ownership library's functionalities like so: use ownership::*; ``` -Once imported, the Ownership library's functions should be available. To use them, be sure to initialize the owner for your contract by calling the `initialize_ownership()` function in your own constructor. +Once imported, the Ownership library's functions will be available. To use them initialize the owner for your contract by calling the `initialize_ownership()` function in your own constructor method. ```sway #[storage(read, write)] @@ -65,6 +65,6 @@ impl SRC5 for Contract { } ``` -> **NOTE** A constructor must be implemented to initialize the owner. +> **NOTE** A constructor method must be implemented to initialize the owner. For more information please see the [specification](./SPECIFICATION.md).