1use soroban_sdk::{Address, Env, Symbol, Vec};
7
8use super::recovery::{Guardian, RecoveryConfig, RecoveryRequest};
9
10const GUARDIANS_PREFIX: &str = "rcv_guard";
11const CONFIG_PREFIX: &str = "rcv_conf";
12const REQUEST_PREFIX: &str = "rcv_req";
13
14pub struct RecoveryStorage;
16
17impl RecoveryStorage {
18 pub fn store_guardians(env: &Env, account: &Address, guardians: &Vec<Guardian>) {
22 let key = Self::guardians_key(env, account);
23 env.storage().persistent().set(&key, guardians);
24 }
25
26 pub fn load_guardians(env: &Env, account: &Address) -> Vec<Guardian> {
28 let key = Self::guardians_key(env, account);
29 env.storage()
30 .persistent()
31 .get(&key)
32 .unwrap_or_else(|| Vec::new(env))
33 }
34
35 pub fn store_config(env: &Env, account: &Address, config: &RecoveryConfig) {
39 let key = Self::config_key(env, account);
40 env.storage().persistent().set(&key, config);
41 }
42
43 pub fn load_config(env: &Env, account: &Address) -> Option<RecoveryConfig> {
45 let key = Self::config_key(env, account);
46 env.storage().persistent().get(&key)
47 }
48
49 pub fn store_request(env: &Env, account: &Address, request: &RecoveryRequest) {
53 let key = Self::request_key(env, account);
54 env.storage().persistent().set(&key, request);
55 }
56
57 pub fn load_request(env: &Env, account: &Address) -> Option<RecoveryRequest> {
59 let key = Self::request_key(env, account);
60 env.storage().persistent().get(&key)
61 }
62
63 pub fn remove_request(env: &Env, account: &Address) {
65 let key = Self::request_key(env, account);
66 env.storage().persistent().remove(&key);
67 }
68
69 fn guardians_key(env: &Env, account: &Address) -> (Symbol, Address) {
72 (Symbol::new(env, GUARDIANS_PREFIX), account.clone())
73 }
74
75 fn config_key(env: &Env, account: &Address) -> (Symbol, Address) {
76 (Symbol::new(env, CONFIG_PREFIX), account.clone())
77 }
78
79 fn request_key(env: &Env, account: &Address) -> (Symbol, Address) {
80 (Symbol::new(env, REQUEST_PREFIX), account.clone())
81 }
82}
83
84#[cfg(test)]
85mod tests {
86 use super::*;
87 use soroban_sdk::{contract, contractimpl, testutils::Address as _, Env};
88
89 #[contract]
90 pub struct TestContract;
91
92 #[contractimpl]
93 impl TestContract {}
94
95 fn make_config() -> RecoveryConfig {
96 RecoveryConfig {
97 threshold: 2,
98 timelock_period: 100,
99 max_guardians: 5,
100 }
101 }
102
103 #[test]
104 fn test_store_and_load_guardians() {
105 let env = Env::default();
106 let contract_id = env.register(TestContract, ());
107 let addr = Address::generate(&env);
108
109 env.as_contract(&contract_id, || {
110 let mut guardians = Vec::new(&env);
111 guardians.push_back(Guardian {
112 address: Address::generate(&env),
113 added_at: 0,
114 });
115 guardians.push_back(Guardian {
116 address: Address::generate(&env),
117 added_at: 10,
118 });
119
120 RecoveryStorage::store_guardians(&env, &addr, &guardians);
121 let loaded = RecoveryStorage::load_guardians(&env, &addr);
122 assert_eq!(loaded.len(), 2);
123 });
124 }
125
126 #[test]
127 fn test_load_guardians_empty() {
128 let env = Env::default();
129 let contract_id = env.register(TestContract, ());
130 let addr = Address::generate(&env);
131
132 env.as_contract(&contract_id, || {
133 let loaded = RecoveryStorage::load_guardians(&env, &addr);
134 assert_eq!(loaded.len(), 0);
135 });
136 }
137
138 #[test]
139 fn test_store_and_load_config() {
140 let env = Env::default();
141 let contract_id = env.register(TestContract, ());
142 let addr = Address::generate(&env);
143
144 env.as_contract(&contract_id, || {
145 let config = make_config();
146 RecoveryStorage::store_config(&env, &addr, &config);
147
148 let loaded = RecoveryStorage::load_config(&env, &addr).unwrap();
149 assert_eq!(loaded.threshold, 2);
150 assert_eq!(loaded.timelock_period, 100);
151 assert_eq!(loaded.max_guardians, 5);
152 });
153 }
154
155 #[test]
156 fn test_load_config_none() {
157 let env = Env::default();
158 let contract_id = env.register(TestContract, ());
159 let addr = Address::generate(&env);
160
161 env.as_contract(&contract_id, || {
162 assert!(RecoveryStorage::load_config(&env, &addr).is_none());
163 });
164 }
165
166 #[test]
167 fn test_store_and_load_request() {
168 let env = Env::default();
169 let contract_id = env.register(TestContract, ());
170 let addr = Address::generate(&env);
171
172 env.as_contract(&contract_id, || {
173 let request = RecoveryRequest {
174 new_owner: Address::generate(&env),
175 approvals: Vec::new(&env),
176 initiated_at: 50,
177 timelock_until: 150,
178 cancelled: false,
179 };
180
181 RecoveryStorage::store_request(&env, &addr, &request);
182 let loaded = RecoveryStorage::load_request(&env, &addr).unwrap();
183 assert_eq!(loaded.initiated_at, 50);
184 assert_eq!(loaded.timelock_until, 150);
185 assert!(!loaded.cancelled);
186 });
187 }
188
189 #[test]
190 fn test_remove_request() {
191 let env = Env::default();
192 let contract_id = env.register(TestContract, ());
193 let addr = Address::generate(&env);
194
195 env.as_contract(&contract_id, || {
196 let request = RecoveryRequest {
197 new_owner: Address::generate(&env),
198 approvals: Vec::new(&env),
199 initiated_at: 50,
200 timelock_until: 150,
201 cancelled: false,
202 };
203
204 RecoveryStorage::store_request(&env, &addr, &request);
205 assert!(RecoveryStorage::load_request(&env, &addr).is_some());
206
207 RecoveryStorage::remove_request(&env, &addr);
208 assert!(RecoveryStorage::load_request(&env, &addr).is_none());
209 });
210 }
211}