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
140    emit_ownership_renounced(e, &owner);
141}
142
143// ################## LOW-LEVEL HELPERS ##################
144
145/// Enforces authorization from the current owner.
146///
147/// This is used internally by the `#[only_owner]` macro expansion to gate
148/// access.
149///
150/// # Arguments
151///
152/// * `e` - Access to the Soroban environment.
153///
154/// # Errors
155///
156/// * [`OwnableError::OwnerNotSet`] - If the owner is not set.
157pub fn enforce_owner_auth(e: &Env) -> Address {
158    let Some(owner) = get_owner(e) else {
159        panic_with_error!(e, OwnableError::OwnerNotSet);
160    };
161    owner.require_auth();
162    owner
163}