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, 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///
55/// # Arguments
56///
57/// * `e` - Access to the Soroban environment.
58/// * `new_owner` - The proposed new owner.
59/// * `live_until_ledger` - Ledger number until which the new owner can accept.
60/// A value of `0` cancels any pending transfer.
61///
62/// # Errors
63///
64/// * refer to [`transfer_role`] errors.
65/// * refer to [`enforce_owner_auth`] errors.
66///
67///
68/// # Events
69///
70/// * topics - `["ownership_transfer"]`
71/// * data - `[old_owner: Address, new_owner: Address]`
72///
73/// # Notes
74///
75/// * Authorization for the current owner is required.
76pub fn transfer_ownership(e: &Env, new_owner: &Address, live_until_ledger: u32) {
77 let owner = enforce_owner_auth(e);
78
79 transfer_role(e, new_owner, &OwnableStorageKey::PendingOwner, live_until_ledger);
80
81 emit_ownership_transfer(e, &owner, new_owner, live_until_ledger);
82}
83
84/// Completes the 2-step ownership transfer process.
85///
86/// # Arguments
87///
88/// * `e` - Access to the Soroban environment.
89///
90/// # Errors
91///
92/// * refer to [`accept_transfer`] errors.
93///
94/// # Events
95///
96/// * topics - `["ownership_transfer_completed"]`
97/// * data - `[new_owner: Address]`
98///
99/// # Notes
100///
101/// * Authorization for the pending owner is required.
102pub fn accept_ownership(e: &Env) {
103 let new_owner = accept_transfer(e, &OwnableStorageKey::Owner, &OwnableStorageKey::PendingOwner);
104
105 emit_ownership_transfer_completed(e, &new_owner);
106}
107
108/// Renounces ownership of the contract.
109///
110/// Once renounced, no one will have privileged access via `#[only_owner]`.
111///
112/// # Arguments
113///
114/// * `e` - Access to the Soroban environment.
115///
116/// # Errors
117///
118/// * [`OwnableError::TransferInProgress`] - If there is a pending ownership
119/// transfer.
120/// * refer to [`enforce_owner_auth`] errors.
121///
122/// # Events
123///
124/// * topics - `["ownership_renounced"]`
125/// * data - `[old_owner: Address]`
126///
127/// # Notes
128///
129/// * Authorization for the current owner is required.
130pub fn renounce_ownership(e: &Env) {
131 let owner = enforce_owner_auth(e);
132 let key = OwnableStorageKey::PendingOwner;
133
134 if e.storage().temporary().get::<_, Address>(&key).is_some() {
135 panic_with_error!(e, OwnableError::TransferInProgress);
136 }
137
138 e.storage().instance().remove(&OwnableStorageKey::Owner);
139 emit_ownership_renounced(e, &owner);
140}
141
142// ################## LOW-LEVEL HELPERS ##################
143
144/// Enforces authorization from the current owner.
145///
146/// This is used internally by the `#[only_owner]` macro expansion to gate
147/// access.
148///
149/// # Arguments
150///
151/// * `e` - Access to the Soroban environment.
152///
153/// # Errors
154///
155/// * [`OwnableError::OwnerNotSet`] - If the owner is not set.
156pub fn enforce_owner_auth(e: &Env) -> Address {
157 let Some(owner) = get_owner(e) else {
158 panic_with_error!(e, OwnableError::OwnerNotSet);
159 };
160 owner.require_auth();
161 owner
162}