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:
-
The current owner initiates the transfer by specifying the new owner and an expiration time (
live_until_ledger
). -
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
}
}