cougr_core/accounts/
device_storage.rs1use soroban_sdk::{Address, BytesN, Env, Symbol, Vec};
7
8use super::error::AccountError;
9use super::multi_device::{DeviceKey, DevicePolicy};
10
11const DEVICES_PREFIX: &str = "dev_keys";
12const POLICY_PREFIX: &str = "dev_policy";
13
14pub struct DeviceStorage;
16
17impl DeviceStorage {
18 pub fn store_devices(env: &Env, account: &Address, devices: &Vec<DeviceKey>) {
22 let key = Self::devices_key(env, account);
23 env.storage().persistent().set(&key, devices);
24 }
25
26 pub fn load_devices(env: &Env, account: &Address) -> Vec<DeviceKey> {
28 let key = Self::devices_key(env, account);
29 env.storage()
30 .persistent()
31 .get(&key)
32 .unwrap_or_else(|| Vec::new(env))
33 }
34
35 pub fn store_policy(env: &Env, account: &Address, policy: &DevicePolicy) {
39 let key = Self::policy_key(env, account);
40 env.storage().persistent().set(&key, policy);
41 }
42
43 pub fn load_policy(env: &Env, account: &Address) -> Option<DevicePolicy> {
45 let key = Self::policy_key(env, account);
46 env.storage().persistent().get(&key)
47 }
48
49 pub fn update_device(
54 env: &Env,
55 account: &Address,
56 key_id: &BytesN<32>,
57 updater: impl FnOnce(&mut DeviceKey),
58 ) -> Result<(), AccountError> {
59 let devices = Self::load_devices(env, account);
60 let mut new_devices: Vec<DeviceKey> = Vec::new(env);
61 let mut found = false;
62 let mut updater = Some(updater);
63
64 for i in 0..devices.len() {
65 if let Some(mut d) = devices.get(i) {
66 if &d.key_id == key_id {
67 found = true;
68 if let Some(f) = updater.take() {
69 f(&mut d);
70 }
71 }
72 new_devices.push_back(d);
73 }
74 }
75
76 if !found {
77 return Err(AccountError::DeviceNotFound);
78 }
79
80 Self::store_devices(env, account, &new_devices);
81 Ok(())
82 }
83
84 fn devices_key(env: &Env, account: &Address) -> (Symbol, Address) {
87 (Symbol::new(env, DEVICES_PREFIX), account.clone())
88 }
89
90 fn policy_key(env: &Env, account: &Address) -> (Symbol, Address) {
91 (Symbol::new(env, POLICY_PREFIX), account.clone())
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98 use soroban_sdk::{contract, contractimpl, testutils::Address as _, Env};
99
100 #[contract]
101 pub struct TestContract;
102
103 #[contractimpl]
104 impl TestContract {}
105
106 fn make_device(env: &Env, id_byte: u8, name: &str) -> DeviceKey {
107 DeviceKey {
108 key_id: BytesN::from_array(env, &[id_byte; 32]),
109 device_name: Symbol::new(env, name),
110 registered_at: 0,
111 last_used: 0,
112 is_active: true,
113 }
114 }
115
116 #[test]
117 fn test_store_and_load_devices() {
118 let env = Env::default();
119 let contract_id = env.register(TestContract, ());
120 let addr = Address::generate(&env);
121
122 env.as_contract(&contract_id, || {
123 let mut devices = Vec::new(&env);
124 devices.push_back(make_device(&env, 1, "phone"));
125 devices.push_back(make_device(&env, 2, "laptop"));
126
127 DeviceStorage::store_devices(&env, &addr, &devices);
128 let loaded = DeviceStorage::load_devices(&env, &addr);
129 assert_eq!(loaded.len(), 2);
130 });
131 }
132
133 #[test]
134 fn test_load_devices_empty() {
135 let env = Env::default();
136 let contract_id = env.register(TestContract, ());
137 let addr = Address::generate(&env);
138
139 env.as_contract(&contract_id, || {
140 let loaded = DeviceStorage::load_devices(&env, &addr);
141 assert_eq!(loaded.len(), 0);
142 });
143 }
144
145 #[test]
146 fn test_store_and_load_policy() {
147 let env = Env::default();
148 let contract_id = env.register(TestContract, ());
149 let addr = Address::generate(&env);
150
151 env.as_contract(&contract_id, || {
152 let policy = DevicePolicy {
153 max_devices: 5,
154 auto_revoke_after: 1000,
155 };
156 DeviceStorage::store_policy(&env, &addr, &policy);
157
158 let loaded = DeviceStorage::load_policy(&env, &addr).unwrap();
159 assert_eq!(loaded.max_devices, 5);
160 assert_eq!(loaded.auto_revoke_after, 1000);
161 });
162 }
163
164 #[test]
165 fn test_load_policy_none() {
166 let env = Env::default();
167 let contract_id = env.register(TestContract, ());
168 let addr = Address::generate(&env);
169
170 env.as_contract(&contract_id, || {
171 assert!(DeviceStorage::load_policy(&env, &addr).is_none());
172 });
173 }
174
175 #[test]
176 fn test_update_device() {
177 let env = Env::default();
178 let contract_id = env.register(TestContract, ());
179 let addr = Address::generate(&env);
180
181 env.as_contract(&contract_id, || {
182 let mut devices = Vec::new(&env);
183 devices.push_back(make_device(&env, 1, "phone"));
184
185 DeviceStorage::store_devices(&env, &addr, &devices);
186
187 let key_id = BytesN::from_array(&env, &[1u8; 32]);
188 DeviceStorage::update_device(&env, &addr, &key_id, |d| {
189 d.last_used = 999;
190 })
191 .unwrap();
192
193 let loaded = DeviceStorage::load_devices(&env, &addr);
194 assert_eq!(loaded.get(0).unwrap().last_used, 999);
195 });
196 }
197}