stellar_access/role_transfer/
storage.rs

1use soroban_sdk::{panic_with_error, Address, Env, IntoVal, Val};
2
3use crate::role_transfer::RoleTransferError;
4
5/// Initiates the role transfer. If `live_until_ledger == 0`, cancels the
6/// pending transfer.
7///
8/// Does not emit any events.
9///
10/// # Arguments
11///
12/// * `e` - Access to the Soroban environment.
13/// * `new` - The proposed new role holder.
14/// * `pending_key` - Storage key for the pending role holder.
15/// * `live_until_ledger` - Ledger number until which the new role holder can
16///   accept. A value of `0` cancels the pending transfer. If the specified
17///   ledger is in the past or exceeds the maximum allowed TTL extension for a
18///   temporary storage entry, the function will panic.
19///
20/// # Errors
21///
22/// * [`RoleTransferError::NoPendingTransfer`] - If trying to cancel a transfer
23///   that doesn't exist.
24/// * [`RoleTransferError::InvalidLiveUntilLedger`] - If the specified ledger is
25///   in the past, or exceeds the maximum allowed TTL extension for a temporary
26///   storage entry.
27/// * [`RoleTransferError::InvalidPendingAccount`] - If the specified pending
28///   account is not the same as the provided `new` address.
29///
30/// # Notes
31///
32/// * This function does not enforce authorization. Ensure that authorization is
33///   handled at a higher level.
34/// * The period during which the transfer can be accepted is implicitly
35///   timebound by the maximum allowed storage TTL value which is a network
36///   parameter, i.e. one cannot set `live_until_ledger` for a longer period.
37/// * There is also a default minimum TTL and if the computed period is shorter
38///   than it, the entry will outlive `live_until_ledger`.
39pub fn transfer_role<T>(e: &Env, new: &Address, pending_key: &T, live_until_ledger: u32)
40where
41    T: IntoVal<Env, Val>,
42{
43    if live_until_ledger == 0 {
44        let Some(pending) = e.storage().temporary().get::<T, Address>(pending_key) else {
45            panic_with_error!(e, RoleTransferError::NoPendingTransfer);
46        };
47        if pending != *new {
48            panic_with_error!(e, RoleTransferError::InvalidPendingAccount);
49        }
50        e.storage().temporary().remove(pending_key);
51
52        return;
53    }
54
55    let current_ledger = e.ledger().sequence();
56    if live_until_ledger > e.ledger().max_live_until_ledger() || live_until_ledger < current_ledger
57    {
58        panic_with_error!(e, RoleTransferError::InvalidLiveUntilLedger);
59    }
60
61    let live_for = live_until_ledger - current_ledger;
62    e.storage().temporary().set(pending_key, new);
63    e.storage().temporary().extend_ttl(pending_key, live_for, live_for);
64}
65
66/// Completes the role transfer if authorization is provided by the pending role
67/// holder. Pending role holder is retrieved from the storage.
68///
69/// # Arguments
70///
71/// * `e` - Access to the Soroban environment.
72/// * `active_key` - Storage key for the current role holder.
73/// * `pending_key` - Storage key for the pending role holder.
74///
75/// # Errors
76///
77/// * [`RoleTransferError::NoPendingTransfer`] - If there is no pending transfer
78///   to accept.
79pub fn accept_transfer<T, U>(e: &Env, active_key: &T, pending_key: &U) -> Address
80where
81    T: IntoVal<Env, Val>,
82    U: IntoVal<Env, Val>,
83{
84    let pending = e
85        .storage()
86        .temporary()
87        .get::<U, Address>(pending_key)
88        .unwrap_or_else(|| panic_with_error!(e, RoleTransferError::NoPendingTransfer));
89
90    pending.require_auth();
91
92    e.storage().temporary().remove(pending_key);
93    e.storage().instance().set(active_key, &pending);
94
95    pending
96}