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}