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, 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.
49pub trait Ownable {
50    /// Returns `Some(Address)` if ownership is set, or `None` if ownership has
51    /// been renounced.
52    ///
53    /// # Arguments
54    ///
55    /// * `e` - Access to the Soroban environment.
56    fn get_owner(e: &Env) -> Option<Address>;
57
58    /// Initiates a 2-step ownership transfer to a new address.
59    ///
60    /// Requires authorization from the current owner. The new owner must later
61    /// call `accept_ownership()` to complete the transfer.
62    ///
63    /// # Arguments
64    ///
65    /// * `e` - Access to the Soroban environment.
66    /// * `new_owner` - The proposed new owner.
67    /// * `live_until_ledger` - Ledger number until which the new owner can
68    ///   accept. A value of `0` cancels any pending transfer.
69    ///
70    /// # Errors
71    ///
72    /// * [`OwnableError::OwnerNotSet`] - If the owner is not set.
73    /// * [`crate::role_transfer::RoleTransferError::NoPendingTransfer`] - If
74    ///   trying to cancel a transfer that doesn't exist.
75    /// * [`crate::role_transfer::RoleTransferError::InvalidLiveUntilLedger`] -
76    ///   If the specified ledger is in the past.
77    /// * [`crate::role_transfer::RoleTransferError::InvalidPendingAccount`] -
78    ///   If the specified pending account is not the same as the provided `new`
79    ///   address.
80    ///
81    /// # Notes
82    ///
83    /// * Authorization for the current owner is required.
84    fn transfer_ownership(e: &Env, new_owner: Address, live_until_ledger: u32);
85
86    /// Accepts a pending ownership transfer.
87    ///
88    /// # Arguments
89    ///
90    /// * `e` - Access to the Soroban environment.
91    ///
92    /// # Errors
93    ///
94    /// * [`crate::role_transfer::RoleTransferError::NoPendingTransfer`] - If
95    ///   there is no pending transfer to accept.
96    ///
97    /// # Events
98    ///
99    /// * topics - `["ownership_transfer_completed"]`
100    /// * data - `[new_owner: Address]`
101    fn accept_ownership(e: &Env);
102
103    /// Renounces ownership of the contract.
104    ///
105    /// Permanently removes the owner, disabling all functions gated by
106    /// `#[only_owner]`.
107    ///
108    /// # Arguments
109    ///
110    /// * `e` - Access to the Soroban environment.
111    ///
112    /// # Errors
113    ///
114    /// * [`OwnableError::TransferInProgress`] - If there is a pending ownership
115    ///   transfer.
116    /// * [`OwnableError::OwnerNotSet`] - If the owner is not set.
117    ///
118    /// # Notes
119    ///
120    /// * Authorization for the current owner is required.
121    fn renounce_ownership(e: &Env);
122}
123
124// ################## ERRORS ##################
125
126#[contracterror]
127#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
128#[repr(u32)]
129pub enum OwnableError {
130    OwnerNotSet = 2100,
131    TransferInProgress = 2101,
132    OwnerAlreadySet = 2102,
133}
134
135// ################## EVENTS ##################
136
137/// Event emitted when an ownership transfer is initiated.
138#[contractevent]
139#[derive(Clone, Debug, Eq, PartialEq)]
140pub struct OwnershipTransfer {
141    pub old_owner: Address,
142    pub new_owner: Address,
143    pub live_until_ledger: u32,
144}
145
146/// Emits an event when an ownership transfer is initiated.
147///
148/// # Arguments
149///
150/// * `e` - Access to the Soroban environment.
151/// * `old_owner` - The address of the current owner.
152/// * `new_owner` - The address of the proposed new owner.
153/// * `live_until_ledger` - The ledger number until which the new owner can
154///   accept the transfer.
155pub fn emit_ownership_transfer(
156    e: &Env,
157    old_owner: &Address,
158    new_owner: &Address,
159    live_until_ledger: u32,
160) {
161    OwnershipTransfer {
162        old_owner: old_owner.clone(),
163        new_owner: new_owner.clone(),
164        live_until_ledger,
165    }
166    .publish(e);
167}
168
169/// Event emitted when an ownership transfer is completed.
170#[contractevent]
171#[derive(Clone, Debug, Eq, PartialEq)]
172pub struct OwnershipTransferCompleted {
173    pub new_owner: Address,
174}
175
176/// Emits an event when an ownership transfer is completed.
177///
178/// # Arguments
179///
180/// * `e` - Access to the Soroban environment.
181/// * `new_owner` - The address of the new owner.
182pub fn emit_ownership_transfer_completed(e: &Env, new_owner: &Address) {
183    OwnershipTransferCompleted { new_owner: new_owner.clone() }.publish(e);
184}
185
186/// Event emitted when ownership is renounced.
187#[contractevent]
188#[derive(Clone, Debug, Eq, PartialEq)]
189pub struct OwnershipRenounced {
190    pub old_owner: Address,
191}
192
193/// Emits an event when ownership is renounced.
194///
195/// # Arguments
196///
197/// * `e` - Access to the Soroban environment.
198/// * `old_owner` - The address of the owner who renounced ownership.
199pub fn emit_ownership_renounced(e: &Env, old_owner: &Address) {
200    OwnershipRenounced { old_owner: old_owner.clone() }.publish(e);
201}