Ownable

Overview

The Ownable module provides a simple access control mechanism where a contract has a single account (owner) that can be granted exclusive access to specific functions. This pattern is useful for contracts that need a straightforward authorization system with a single privileged account.

Key Concepts

Ownership Management

The system designates a single owner with exclusive access to functions marked with the #[only_owner] macro. The initial owner must be ideally set during contract initialization for the module to function properly.

Like the Access Control module, ownership transfers are implemented as a two-step process to prevent accidental or malicious takeovers:

  1. The current owner initiates the transfer by specifying the new owner and an expiration time (live_until_ledger).

  2. The designated new owner must explicitly accept the transfer to complete it.

Until the transfer is accepted, the original owner retains full control and can override or cancel the transfer by initiating a new one or using a live_until_ledger of 0.

Ownership Renunciation

The Ownable module allows the owner to permanently renounce ownership of the contract. This is a one-way operation that cannot be undone. After ownership is renounced, all functions marked with #[only_owner] become permanently inaccessible.

This feature is useful for contracts that need to become fully decentralized after an initial setup phase.

Procedural Macro

The module includes a procedural macro to simplify owner authorization checks:

@only_owner

Ensures the caller is the owner before executing the function:

#[only_owner]
pub fn restricted_function(e: &Env, other_param: u32) {
    // Function body - only accessible to owner
}

This expands to code that retrieves the owner from storage and requires authorization before executing the function body.

Usage Example

Here’s a simple example of using the Ownable module:

use soroban_sdk::{contract, contractimpl, Address, Env};
use stellar_ownable::{self as ownable, Ownable};
use stellar_ownable_macro::only_owner;

#[contract]
pub struct MyContract;

#[contractimpl]
impl MyContract {
    pub fn __constructor(e: &Env, initial_owner: Address) {
        // Set the contract owner
        ownable::set_owner(e, &initial_owner);
    }

    #[only_owner]
    pub fn update_config(e: &Env, new_value: u32) {
        // Only the owner can call this function
        // Implementation...
    }

    // This function is accessible to anyone
    pub fn get_config(e: &Env) -> u32 {
        // Implementation...
        42
    }
}

Benefits and Trade-offs

Benefits

  • Simple and straightforward ownership model

  • Secure two-step ownership transfer process

  • Option to permanently renounce ownership

  • Easy integration with procedural macro

  • Event emission for important actions

Trade-offs

  • Limited to a single privileged account (compared to role-based systems)

  • Once ownership is renounced, privileged functions become permanently inaccessible