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}