stellar_access/ownable/storage.rs
1use soroban_sdk::{contracttype, panic_with_error, Address, Env};
2
3use crate::{
4 ownable::{
5 emit_ownership_renounced, emit_ownership_transfer, emit_ownership_transfer_completed,
6 OwnableError,
7 },
8 role_transfer::{accept_transfer, has_active_pending_transfer, transfer_role},
9};
10
11/// Storage keys for `Ownable` utility.
12#[contracttype]
13pub enum OwnableStorageKey {
14 Owner,
15 PendingOwner,
16}
17
18// ################## QUERY STATE ##################
19
20/// Returns `Some(Address)` if ownership is set, or `None` if ownership has been
21/// renounced or has never been set.
22///
23/// # Arguments
24///
25/// * `e` - Access to the Soroban environment.
26pub fn get_owner(e: &Env) -> Option<Address> {
27 e.storage().instance().get::<_, Address>(&OwnableStorageKey::Owner)
28}
29
30// ################## CHANGE STATE ##################
31
32/// Sets owner role.
33///
34/// # Arguments
35///
36/// * `e` - Access to Soroban environment.
37/// * `owner` - The account to grant the owner privilege.
38///
39/// # Errors
40///
41/// * [`OwnableError::OwnerAlreadySet`] - If the owner is already set.
42///
43/// **IMPORTANT**: this function lacks authorization checks.
44/// It is expected to call this function only in the constructor!
45pub fn set_owner(e: &Env, owner: &Address) {
46 // Check if owner is already set
47 if e.storage().instance().has(&OwnableStorageKey::Owner) {
48 panic_with_error!(e, OwnableError::OwnerAlreadySet);
49 }
50 e.storage().instance().set(&OwnableStorageKey::Owner, &owner);
51}
52
53/// Initiates a 2-step ownership transfer to a new owner.
54/// Owner privileges for the current owner are not revoked until the
55/// recipient accepts the transfer.
56/// Overrides the previous pending transfer if there is one.
57///
58/// # Arguments
59///
60/// * `e` - Access to the Soroban environment.
61/// * `new_owner` - The proposed new owner.
62/// * `live_until_ledger` - Ledger number until which the new owner can accept.
63/// A value of `0` cancels any pending transfer.
64///
65/// # Errors
66///
67/// * refer to [`transfer_role`] errors.
68/// * refer to [`enforce_owner_auth`] errors.
69///
70///
71/// # Events
72///
73/// * topics - `["ownership_transfer"]`
74/// * data - `[old_owner: Address, new_owner: Address]`
75///
76/// # Notes
77///
78/// * Authorization for the current owner is required.
79pub fn transfer_ownership(e: &Env, new_owner: &Address, live_until_ledger: u32) {
80 let owner = enforce_owner_auth(e);
81
82 transfer_role(e, new_owner, &OwnableStorageKey::PendingOwner, live_until_ledger);
83
84 emit_ownership_transfer(e, &owner, new_owner, live_until_ledger);
85}
86
87/// Completes the 2-step ownership transfer process.
88///
89/// # Arguments
90///
91/// * `e` - Access to the Soroban environment.
92///
93/// # Errors
94///
95/// * refer to [`accept_transfer`] errors.
96///
97/// # Events
98///
99/// * topics - `["ownership_transfer_completed"]`
100/// * data - `[new_owner: Address]`
101///
102/// # Notes
103///
104/// * Authorization for the pending owner is required.
105pub fn accept_ownership(e: &Env) {
106 let new_owner = accept_transfer(e, &OwnableStorageKey::Owner, &OwnableStorageKey::PendingOwner);
107
108 emit_ownership_transfer_completed(e, &new_owner);
109}
110
111/// Renounces ownership of the contract.
112///
113/// Once renounced, no one will have privileged access via `#[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/// * refer to [`enforce_owner_auth`] errors.
124///
125/// # Events
126///
127/// * topics - `["ownership_renounced"]`
128/// * data - `[old_owner: Address]`
129///
130/// # Notes
131///
132/// * Authorization for the current owner is required.
133pub fn renounce_ownership(e: &Env) {
134 let owner = enforce_owner_auth(e);
135 let key = OwnableStorageKey::PendingOwner;
136
137 if has_active_pending_transfer(e, &key) {
138 panic_with_error!(e, OwnableError::TransferInProgress);
139 }
140
141 e.storage().instance().remove(&OwnableStorageKey::Owner);
142
143 emit_ownership_renounced(e, &owner);
144}
145
146// ################## LOW-LEVEL HELPERS ##################
147
148/// Enforces authorization from the current owner.
149///
150/// This is used internally by the `#[only_owner]` macro expansion to gate
151/// access.
152///
153/// # Arguments
154///
155/// * `e` - Access to the Soroban environment.
156///
157/// # Errors
158///
159/// * [`OwnableError::OwnerNotSet`] - If the owner is not set.
160pub fn enforce_owner_auth(e: &Env) -> Address {
161 let Some(owner) = get_owner(e) else {
162 panic_with_error!(e, OwnableError::OwnerNotSet);
163 };
164 owner.require_auth();
165 owner
166}