stellar_access/ownable/
mod.rs

1//! # Ownable Contract Module.
2//!
3//! This module introduces a simple access control mechanism where a contract
4//! has an account (owner) that can be granted exclusive access to specific
5//! functions.
6//!
7//! The `Ownable` trait exposes methods for:
8//! - Getting the current owner
9//! - Transferring ownership
10//! - Renouncing ownership
11//!
12//! The helper `enforce_owner_auth()` is available to restrict access to only
13//! the owner. You can also use the `#[only_owner]` macro (provided elsewhere)
14//! to simplify this.
15//!
16//! ```ignore
17//! #[only_owner]
18//! fn set_config(e: &Env, new_config: u32) { ... }
19//! ```
20//!
21//! See `examples/ownable/src/contract.rs` for a working example.
22//!
23//! ## Note
24//!
25//! The ownership transfer is processed in 2 steps:
26//!
27//! 1. Initiating the ownership transfer by the current owner
28//! 2. Accepting the ownership by the designated owner
29//!
30//! Not providing a direct ownership transfer is a deliberate design decision to
31//! help avoid mistakes by transferring to a wrong address.
32
33mod storage;
34
35#[cfg(test)]
36mod test;
37
38use soroban_sdk::{contracterror, contractevent, contracttrait, Address, Env};
39
40pub use crate::ownable::storage::{
41    accept_ownership, enforce_owner_auth, get_owner, renounce_ownership, set_owner,
42    transfer_ownership, OwnableStorageKey,
43};
44
45/// A trait for managing contract ownership using a 2-step transfer pattern.
46///
47/// Provides functions to query ownership, initiate a transfer, or renounce
48/// ownership.
49#[contracttrait]
50pub trait Ownable {
51    /// Returns `Some(Address)` if ownership is set, or `None` if ownership has
52    /// been renounced.
53    ///
54    /// # Arguments
55    ///
56    /// * `e` - Access to the Soroban environment.
57    fn get_owner(e: &Env) -> Option<Address> {
58        get_owner(e)
59    }
60
61    /// Initiates a 2-step ownership transfer to a new address.
62    ///
63    /// Requires authorization from the current owner. The new owner must later
64    /// call `accept_ownership()` to complete the transfer.
65    ///
66    /// # Arguments
67    ///
68    /// * `e` - Access to the Soroban environment.
69    /// * `new_owner` - The proposed new owner.
70    /// * `live_until_ledger` - Ledger number until which the new owner can
71    ///   accept. A value of `0` cancels any pending transfer.
72    ///
73    /// # Errors
74    ///
75    /// * [`OwnableError::OwnerNotSet`] - If the owner is not set.
76    /// * [`crate::role_transfer::RoleTransferError::NoPendingTransfer`] - If
77    ///   trying to cancel a transfer that doesn't exist.
78    /// * [`crate::role_transfer::RoleTransferError::InvalidLiveUntilLedger`] -
79    ///   If the specified ledger is in the past.
80    /// * [`crate::role_transfer::RoleTransferError::InvalidPendingAccount`] -
81    ///   If the specified pending account is not the same as the provided `new`
82    ///   address.
83    ///
84    /// # Notes
85    ///
86    /// * Authorization for the current owner is required.
87    fn transfer_ownership(e: &Env, new_owner: Address, live_until_ledger: u32) {
88        transfer_ownership(e, &new_owner, live_until_ledger);
89    }
90
91    /// Accepts a pending ownership transfer.
92    ///
93    /// # Arguments
94    ///
95    /// * `e` - Access to the Soroban environment.
96    ///
97    /// # Errors
98    ///
99    /// * [`crate::role_transfer::RoleTransferError::NoPendingTransfer`] - If
100    ///   there is no pending transfer to accept.
101    ///
102    /// # Events
103    ///
104    /// * topics - `["ownership_transfer_completed"]`
105    /// * data - `[new_owner: Address]`
106    fn accept_ownership(e: &Env) {
107        accept_ownership(e);
108    }
109
110    /// Renounces ownership of the contract.
111    ///
112    /// Permanently removes the owner, disabling all functions gated by
113    /// `#[only_owner]`.
114    ///
115    /// # Arguments
116    ///
117    /// * `e` - Access to the Soroban environment.
118    ///
119    /// # Errors
120    ///
121    /// * [`OwnableError::TransferInProgress`] - If there is a pending ownership
122    ///   transfer.
123    /// * [`OwnableError::OwnerNotSet`] - If the owner is not set.
124    ///
125    /// # Notes
126    ///
127    /// * Authorization for the current owner is required.
128    fn renounce_ownership(e: &Env) {
129        renounce_ownership(e);
130    }
131}
132
133// ################## ERRORS ##################
134
135#[contracterror]
136#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
137#[repr(u32)]
138pub enum OwnableError {
139    OwnerNotSet = 2100,
140    TransferInProgress = 2101,
141    OwnerAlreadySet = 2102,
142}
143
144// ################## EVENTS ##################
145
146/// Event emitted when an ownership transfer is initiated.
147#[contractevent]
148#[derive(Clone, Debug, Eq, PartialEq)]
149pub struct OwnershipTransfer {
150    pub old_owner: Address,
151    pub new_owner: Address,
152    pub live_until_ledger: u32,
153}
154
155/// Emits an event when an ownership transfer is initiated.
156///
157/// # Arguments
158///
159/// * `e` - Access to the Soroban environment.
160/// * `old_owner` - The address of the current owner.
161/// * `new_owner` - The address of the proposed new owner.
162/// * `live_until_ledger` - The ledger number until which the new owner can
163///   accept the transfer.
164pub fn emit_ownership_transfer(
165    e: &Env,
166    old_owner: &Address,
167    new_owner: &Address,
168    live_until_ledger: u32,
169) {
170    OwnershipTransfer {
171        old_owner: old_owner.clone(),
172        new_owner: new_owner.clone(),
173        live_until_ledger,
174    }
175    .publish(e);
176}
177
178/// Event emitted when an ownership transfer is completed.
179#[contractevent]
180#[derive(Clone, Debug, Eq, PartialEq)]
181pub struct OwnershipTransferCompleted {
182    pub new_owner: Address,
183}
184
185/// Emits an event when an ownership transfer is completed.
186///
187/// # Arguments
188///
189/// * `e` - Access to the Soroban environment.
190/// * `new_owner` - The address of the new owner.
191pub fn emit_ownership_transfer_completed(e: &Env, new_owner: &Address) {
192    OwnershipTransferCompleted { new_owner: new_owner.clone() }.publish(e);
193}
194
195/// Event emitted when ownership is renounced.
196#[contractevent]
197#[derive(Clone, Debug, Eq, PartialEq)]
198pub struct OwnershipRenounced {
199    pub old_owner: Address,
200}
201
202/// Emits an event when ownership is renounced.
203///
204/// # Arguments
205///
206/// * `e` - Access to the Soroban environment.
207/// * `old_owner` - The address of the owner who renounced ownership.
208pub fn emit_ownership_renounced(e: &Env, old_owner: &Address) {
209    OwnershipRenounced { old_owner: old_owner.clone() }.publish(e);
210}