1pub use fadroma_proc_auth::*;
5
6use serde::{Serialize, Deserialize};
7
8use crate::{
9 dsl::*,
10 core::Canonize,
11 storage::SingleItem,
12 schemars::JsonSchema,
13 cosmwasm_std::{
14 self,
15 Deps, DepsMut, Response, MessageInfo,
16 CanonicalAddr, StdResult, StdError, Addr
17 }
18};
19
20crate::namespace!(pub AdminNs, b"ltp5P6sFZT");
21pub const STORE: SingleItem<CanonicalAddr, AdminNs> = SingleItem::new();
22
23crate::namespace!(pub PendingAdminNs, b"b5QaJXDibK");
24pub const PENDING_ADMIN: SingleItem<CanonicalAddr, PendingAdminNs> = SingleItem::new();
25
26#[interface]
27pub trait Admin {
28 type Error: std::fmt::Display;
29
30 #[execute]
31 fn change_admin(mode: Option<Mode>) -> Result<Response, Self::Error>;
32
33 #[query]
34 fn admin() -> Result<Option<Addr>, Self::Error>;
35}
36
37#[derive(Serialize, Deserialize, JsonSchema, PartialEq, Debug, Clone)]
38pub enum Mode {
39 Immediate { new_admin: String },
45 TwoStep { new_admin: String }
55}
56
57pub fn init(
62 deps: DepsMut,
63 address: Option<&str>,
64 info: &MessageInfo
65) -> StdResult<CanonicalAddr> {
66 let admin = if let Some(addr) = address {
67 &addr
68 } else {
69 info.sender.as_str()
70 };
71
72 let admin = admin.canonize(deps.api)?;
73 STORE.save(deps.storage, &admin)?;
74
75 Ok(admin)
76}
77
78pub fn assert(deps: Deps, info: &MessageInfo) -> StdResult<()> {
80 let admin = STORE.load_humanize(deps)?;
81
82 if let Some(admin) = admin {
83 if admin == info.sender {
84 return Ok(());
85 }
86 }
87
88 Err(StdError::generic_err("Unauthorized"))
89}
90
91#[derive(Clone, Copy, Debug)]
92pub struct DefaultImpl;
93
94impl Admin for DefaultImpl {
95 type Error = StdError;
96
97 #[execute]
98 fn change_admin(mode: Option<Mode>) -> StdResult<Response> {
99 if let Some(mode) = mode {
100 assert(deps.as_ref(), &info)?;
101
102 match mode {
103 Mode::Immediate { new_admin } =>
104 STORE.canonize_and_save(deps, new_admin.as_str())?,
105 Mode::TwoStep { new_admin } =>
106 PENDING_ADMIN.canonize_and_save(deps, new_admin.as_str())?,
107 }
108 } else {
109 if let Some(pending) = PENDING_ADMIN.load_humanize(deps.as_ref())? {
110 if pending == info.sender {
111 STORE.canonize_and_save(deps, pending.as_str())?;
112 } else {
113 return Err(StdError::generic_err("Unauthorized"));
114 }
115 } else {
116 return Err(StdError::generic_err("No address is currently expected to accept the admin role."));
117 }
118 }
119
120 Ok(Response::new())
121 }
122
123 #[query]
124 fn admin() -> StdResult<Option<Addr>> {
125 STORE.load_humanize(deps)
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132 use crate::{
133 admin,
134 cosmwasm_std::{
135 StdError,
136 testing::{mock_dependencies, mock_env, mock_info},
137 }
138 };
139
140 #[test]
141 fn test_init_admin() {
142 let ref mut deps = mock_dependencies();
143
144 let admin = DefaultImpl::admin(deps.as_ref(), mock_env()).unwrap();
145 assert!(admin.is_none());
146
147 let admin = "admin";
148 admin::init(deps.as_mut(), Some(admin), &mock_info("Tio Macaco", &[])).unwrap();
149
150 let stored_admin = DefaultImpl::admin(deps.as_ref(), mock_env()).unwrap();
151 assert_eq!(stored_admin.unwrap(), admin);
152 }
153
154 #[test]
155 fn test_init_default_admin() {
156 let ref mut deps = mock_dependencies();
157
158 let admin = DefaultImpl::admin(deps.as_ref(), mock_env()).unwrap();
159 assert!(admin.is_none());
160
161 let admin = "admin";
162 admin::init(deps.as_mut(), None, &mock_info(admin, &[])).unwrap();
163
164 let stored_admin = DefaultImpl::admin(deps.as_ref(), mock_env()).unwrap();
165 assert_eq!(stored_admin.unwrap(), admin);
166 }
167
168 #[test]
169 fn test_change_invariants_prior_to_change() {
170 let ref mut deps = mock_dependencies();
171
172 let admin = "admin";
173 admin::init(deps.as_mut(), None, &mock_info(admin, &[])).unwrap();
174
175 let new_admin = "new_admin";
176
177 let err = DefaultImpl::change_admin(
178 deps.as_mut(),
179 mock_env(),
180 mock_info("What about me?", &[]),
181 Some(Mode::Immediate { new_admin: new_admin.into() })
182 ).unwrap_err();
183 assert_unauthorized(&err);
184
185 let err = DefaultImpl::change_admin(
186 deps.as_mut(),
187 mock_env(),
188 mock_info("What about me?", &[]),
189 Some(Mode::TwoStep { new_admin: new_admin.into() })
190 ).unwrap_err();
191 assert_unauthorized(&err);
192
193 let err = DefaultImpl::change_admin(
194 deps.as_mut(),
195 mock_env(),
196 mock_info("What about me?", &[]),
197 None
198 ).unwrap_err();
199 assert_no_pending(&err);
200
201 let err = DefaultImpl::change_admin(
202 deps.as_mut(),
203 mock_env(),
204 mock_info(admin, &[]),
205 None
206 ).unwrap_err();
207 assert_no_pending(&err);
208
209 let stored_admin = DefaultImpl::admin(deps.as_ref(), mock_env()).unwrap();
210 assert_eq!(stored_admin.unwrap(), admin);
211 }
212
213 #[test]
214 fn test_change_admin_immediate() {
215 let ref mut deps = mock_dependencies();
216
217 let admin = "admin";
218 admin::init(deps.as_mut(), None, &mock_info(admin, &[])).unwrap();
219
220 let new_admin = "new_admin";
221
222 DefaultImpl::change_admin(
223 deps.as_mut(),
224 mock_env(),
225 mock_info(admin, &[]),
226 Some(Mode::Immediate { new_admin: new_admin.into() })
227 ).unwrap();
228
229 let err = DefaultImpl::change_admin(
230 deps.as_mut(),
231 mock_env(),
232 mock_info(admin, &[]),
233 Some(Mode::Immediate { new_admin: new_admin.into() })
234 ).unwrap_err();
235 assert_unauthorized(&err);
236
237 let err = DefaultImpl::change_admin(
238 deps.as_mut(),
239 mock_env(),
240 mock_info(admin, &[]),
241 Some(Mode::TwoStep { new_admin: new_admin.into() })
242 ).unwrap_err();
243 assert_unauthorized(&err);
244
245 let err = DefaultImpl::change_admin(
246 deps.as_mut(),
247 mock_env(),
248 mock_info(admin, &[]),
249 None
250 ).unwrap_err();
251 assert_no_pending(&err);
252
253 let err = DefaultImpl::change_admin(
254 deps.as_mut(),
255 mock_env(),
256 mock_info(new_admin, &[]),
257 None
258 ).unwrap_err();
259 assert_no_pending(&err);
260
261 let stored_admin = DefaultImpl::admin(deps.as_ref(), mock_env()).unwrap();
262 assert_eq!(stored_admin.unwrap(), new_admin);
263 }
264
265 #[test]
266 fn test_change_admin_two_step() {
267 let ref mut deps = mock_dependencies();
268
269 let admin = "admin";
270 admin::init(deps.as_mut(), None, &mock_info(admin, &[])).unwrap();
271
272 let new_admin = "new_admin";
273 let new_admin2 = "new_admin2";
274
275 DefaultImpl::change_admin(
276 deps.as_mut(),
277 mock_env(),
278 mock_info(admin, &[]),
279 Some(Mode::TwoStep { new_admin: new_admin.into() })
280 ).unwrap();
281
282 DefaultImpl::change_admin(
285 deps.as_mut(),
286 mock_env(),
287 mock_info(admin, &[]),
288 Some(Mode::TwoStep { new_admin: new_admin2.into() })
289 ).unwrap();
290
291 let err = DefaultImpl::change_admin(
292 deps.as_mut(),
293 mock_env(),
294 mock_info(new_admin, &[]),
295 None
296 ).unwrap_err();
297 assert_unauthorized(&err);
298
299 DefaultImpl::change_admin(
300 deps.as_mut(),
301 mock_env(),
302 mock_info(new_admin2, &[]),
303 None
304 ).unwrap();
305
306 let stored_admin = DefaultImpl::admin(deps.as_ref(), mock_env()).unwrap();
307 assert_eq!(stored_admin.unwrap(), new_admin2);
308
309 let err = DefaultImpl::change_admin(
310 deps.as_mut(),
311 mock_env(),
312 mock_info(admin, &[]),
313 Some(Mode::TwoStep { new_admin: new_admin.into() })
314 ).unwrap_err();
315 assert_unauthorized(&err);
316
317 let err = DefaultImpl::change_admin(
318 deps.as_mut(),
319 mock_env(),
320 mock_info(admin, &[]),
321 Some(Mode::Immediate { new_admin: new_admin.into() })
322 ).unwrap_err();
323 assert_unauthorized(&err);
324
325 DefaultImpl::change_admin(
326 deps.as_mut(),
327 mock_env(),
328 mock_info(new_admin2, &[]),
329 Some(Mode::Immediate { new_admin: new_admin.into() })
330 ).unwrap();
331
332 let stored_admin = DefaultImpl::admin(deps.as_ref(), mock_env()).unwrap();
333 assert_eq!(stored_admin.unwrap(), new_admin);
334 }
335
336 fn assert_unauthorized(err: &StdError) {
337 match err {
338 StdError::GenericErr { msg } => assert_eq!(msg, "Unauthorized"),
339 _ => panic!("Expected \"StdError::GenericErr\"")
340 };
341 }
342
343 fn assert_no_pending(err: &StdError) {
344 match err {
345 StdError::GenericErr { msg } => assert_eq!(msg, "No address is currently expected to accept the admin role."),
346 _ => panic!("Expected \"StdError::GenericErr\"")
347 };
348 }
349}