What is the motivation to use ownership from sway-libs?

What is the motivation to use ownership from sway-libs?

Hi friends, I have decided to integrate the ownership library with the src-5 standard and I have a question what is the advantage of using this approach over my previous one?

My previous approach

Forc.toml

[project]
authors = ["Alex"]
entry = "main.sw"
license = "Apache-2.0"
name = "ownership-example"
[dependencies]

main.sw

contract;
use std::constants::{ZERO_B256};

configurable {
    OWNER: b256 = ZERO_B256,
}

storage {
    /// Keeps track of users with admin access to the contract.
    admins: StorageMap<Address, bool> = StorageMap {},
}


/*...SOME CODE HERE...*/
impl OwnershipExampleContract for Contract {

    /*...SOME CODE HERE...*/

    /// Adds an admin to the platform, can only be called by the owner.
    ///
    /// # Arguments
    ///
    /// * `address`: [Address] - The address of the new admin to be added.
    #[storage(write)]
    fn add_admin(address: Address){
        verify_owner(); 
        storage.admins.insert(address, true);
    }

    /// Removes an admin from the platform, can only be called by the owner.
    ///
    /// # Arguments
    ///
    /// * `address`: [Address] - The address of the admin to be removed.
    #[storage(write)]
    fn remove_admin(address: Address){
        verify_owner();
        storage.admins.insert(address, false);
    }

}
/// Verifies if the caller is the owner.
fn verify_owner() {
    require(OWNER != ZERO_B256 && msg_sender_address().into() == OWNER, "Access denied");
}

/// Verifies if the caller is the owner or an admin.
#[storage(read)]
fn verify_owner_or_admin() {
    let caller = msg_sender_address();
    let is_owner = OWNER != ZERO_B256 && caller.into() == OWNER;
    let is_admin = storage.admins.get(caller).try_read().unwrap_or(false);
    require(is_owner || is_admin, "Access denied");
}

src-5 + owbership approach

Forc.toml

[project]
authors = ["Alex"]
entry = "main.sw"
license = "Apache-2.0"
name = "ownership-example"
[dependencies]
ownership = { git = "https://github.com/FuelLabs/sway-libs" }
src_5 = { git = "https://github.com/FuelLabs/sway-standards" }

main.sw

contract;
use src_5::{Ownership, State};
use ownership::*;

// fixme: setup OWNER: Address in rust sdk 
// https://forum.fuel.network/t/no-configurable-field-in-abi-pyth-oracle-contract/3397
// https://github.com/FuelLabs/sway/issues/5242
configurable {
    OWNER: Address = Address::from(0x09c0b2d1a486c439a87bcba6b46a7a1a23f3897cc83a94521a96da5c23bc58db),
}

storage {
    /// Ownership information for controlling access to Spark Contracts management functions.
    owner: Ownership = Ownership::initialized(Identity::Address(OWNER)),
    /// Keeps track of users with admin access to the contract.
    admins: StorageMap<Address, bool> = StorageMap {},
}


/*...SOME CODE HERE...*/
impl OwnershipExampleContract for Contract {

    /*...SOME CODE HERE...*/

    /// Adds an admin to the platform, can only be called by the owner.
    ///
    /// # Arguments
    ///
    /// * `address`: [Address] - The address of the new admin to be added.
    #[storage(write)]
    fn add_admin(address: Address){
        storage.owner.only_owner(); 
        storage.admins.insert(address, true);
    }

    /// Removes an admin from the platform, can only be called by the owner.
    ///
    /// # Arguments
    ///
    /// * `address`: [Address] - The address of the admin to be removed.
    #[storage(write)]
    fn remove_admin(address: Address){
        storage.owner.only_owner(); 
        storage.admins.insert(address, false);
    }

}
/// Retrieves the message sender's address, reverting if not found or not an address.
///
/// # Returns
///
/// * [Address] - address of the message sender.
pub fn msg_sender_address() -> Address {
    match std::auth::msg_sender().unwrap() {
        Identity::Address(identity) => identity,
        _ => revert(0),
    }
}

/// Verifies if the message sender is either the owner or an admin of the contract.
#[storage(read)]
fn verify_owner_or_admin() {
    let caller = msg_sender_address();
    let is_owner = match storage.owner.owner() {
        State::Initialized(owner) => owner == Identity::Address(caller),
        _ => false,
    };
    let is_admin = storage.admins.get(caller).try_read().unwrap_or(false);
    require(is_owner || is_admin, "Access denied");
}

You can compare these 2 snippets. For me personally I realized that using src-5 approach is not so convenient because I have to connect 2 additional dependencies and there is no possibility to install OWNER via configurable

Let’s please discuss here how we can realize implementation of ownership simpler and please pay attention to these issues:

1 Like

Hey @fuel! :slight_smile:

The example does not follow the standard. The standard states that there must be a function fn owner() → State where state is an enum with 3 variants and revert with a NotOwner error. The intent of the standard it to enable compatibility BETWEEN contracts, not limit the implementation. Your first example can follow the standard by implementing the SRC5 abi like this:

impl SRC_5 for Contract {
     fn owner() -> State {
          if OWNER == ZERO_B256 {
               State::Uninitialized
          } else {
               State::Initialized(Identity::Address(Address::from(OWNER))
          }
      }
}