1use schemars::JsonSchema;
2use std::fmt;
3use thiserror::Error;
4
5use cosmwasm_schema::cw_serde;
6use cosmwasm_std::{
7 attr, Addr, CustomQuery, Deps, DepsMut, MessageInfo, Response, StdError, StdResult,
8};
9use cw_storage_plus::{Item, Namespace};
10
11#[cw_serde]
14pub struct AdminResponse {
15 pub admin: Option<String>,
16}
17
18#[derive(Error, Debug, PartialEq)]
20pub enum AdminError {
21 #[error("{0}")]
22 Std(#[from] StdError),
23
24 #[error("Caller is not admin")]
25 NotAdmin {},
26}
27
28pub struct Admin(Item<Option<Addr>>);
30
31impl Admin {
33 pub const fn new(namespace: &'static str) -> Self {
34 Admin(Item::new(namespace))
35 }
36
37 pub fn new_dyn(storage_key: impl Into<Namespace>) -> Self {
38 Admin(Item::new_dyn(storage_key))
39 }
40
41 pub fn set<Q: CustomQuery>(&self, deps: DepsMut<Q>, admin: Option<Addr>) -> StdResult<()> {
42 self.0.save(deps.storage, &admin)
43 }
44
45 pub fn get<Q: CustomQuery>(&self, deps: Deps<Q>) -> StdResult<Option<Addr>> {
46 self.0.load(deps.storage)
47 }
48
49 pub fn is_admin<Q: CustomQuery>(&self, deps: Deps<Q>, caller: &Addr) -> StdResult<bool> {
52 match self.0.load(deps.storage)? {
53 Some(owner) => Ok(caller == owner),
54 None => Ok(false),
55 }
56 }
57
58 pub fn assert_admin<Q: CustomQuery>(
61 &self,
62 deps: Deps<Q>,
63 caller: &Addr,
64 ) -> Result<(), AdminError> {
65 if !self.is_admin(deps, caller)? {
66 Err(AdminError::NotAdmin {})
67 } else {
68 Ok(())
69 }
70 }
71
72 pub fn execute_update_admin<C, Q: CustomQuery>(
73 &self,
74 deps: DepsMut<Q>,
75 info: MessageInfo,
76 new_admin: Option<Addr>,
77 ) -> Result<Response<C>, AdminError>
78 where
79 C: Clone + fmt::Debug + PartialEq + JsonSchema,
80 {
81 self.assert_admin(deps.as_ref(), &info.sender)?;
82
83 let admin_str = match new_admin.as_ref() {
84 Some(admin) => admin.to_string(),
85 None => "None".to_string(),
86 };
87 let attributes = vec![
88 attr("action", "update_admin"),
89 attr("admin", admin_str),
90 attr("sender", info.sender),
91 ];
92
93 self.set(deps, new_admin)?;
94
95 Ok(Response::new().add_attributes(attributes))
96 }
97
98 pub fn query_admin<Q: CustomQuery>(&self, deps: Deps<Q>) -> StdResult<AdminResponse> {
99 let admin = self.get(deps)?.map(String::from);
100 Ok(AdminResponse { admin })
101 }
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107
108 use cosmwasm_std::testing::{mock_dependencies, mock_info};
109 use cosmwasm_std::Empty;
110
111 #[test]
112 fn set_and_get_admin() {
113 let mut deps = mock_dependencies();
114 let control = Admin::new("foo");
115
116 let admin = Some(Addr::unchecked("admin"));
118 control.set(deps.as_mut(), admin.clone()).unwrap();
119 let got = control.get(deps.as_ref()).unwrap();
120 assert_eq!(admin, got);
121
122 control.set(deps.as_mut(), None).unwrap();
124 let got = control.get(deps.as_ref()).unwrap();
125 assert_eq!(None, got);
126 }
127
128 #[test]
129 fn admin_checks() {
130 let mut deps = mock_dependencies();
131
132 let control = Admin::new("foo");
133 let owner = Addr::unchecked("big boss");
134 let imposter = Addr::unchecked("imposter");
135
136 control.set(deps.as_mut(), Some(owner.clone())).unwrap();
138 assert!(control.is_admin(deps.as_ref(), &owner).unwrap());
139 assert!(!(control.is_admin(deps.as_ref(), &imposter).unwrap()));
140 control.assert_admin(deps.as_ref(), &owner).unwrap();
141 let err = control.assert_admin(deps.as_ref(), &imposter).unwrap_err();
142 assert_eq!(AdminError::NotAdmin {}, err);
143
144 control.set(deps.as_mut(), None).unwrap();
146 assert!(!(control.is_admin(deps.as_ref(), &owner).unwrap()));
147 assert!(!(control.is_admin(deps.as_ref(), &imposter).unwrap()));
148 let err = control.assert_admin(deps.as_ref(), &owner).unwrap_err();
149 assert_eq!(AdminError::NotAdmin {}, err);
150 let err = control.assert_admin(deps.as_ref(), &imposter).unwrap_err();
151 assert_eq!(AdminError::NotAdmin {}, err);
152 }
153
154 #[test]
155 fn test_execute_query() {
156 let mut deps = mock_dependencies();
157
158 let control = Admin::new("foo");
160 let owner = Addr::unchecked("big boss");
161 let imposter = Addr::unchecked("imposter");
162 let friend = Addr::unchecked("buddy");
163 control.set(deps.as_mut(), Some(owner.clone())).unwrap();
164
165 let res = control.query_admin(deps.as_ref()).unwrap();
167 assert_eq!(Some(owner.to_string()), res.admin);
168
169 let info = mock_info(imposter.as_ref(), &[]);
171 let new_admin = Some(friend.clone());
172 let err = control
173 .execute_update_admin::<Empty, Empty>(deps.as_mut(), info, new_admin.clone())
174 .unwrap_err();
175 assert_eq!(AdminError::NotAdmin {}, err);
176
177 let info = mock_info(owner.as_ref(), &[]);
179 let res = control
180 .execute_update_admin::<Empty, Empty>(deps.as_mut(), info, new_admin)
181 .unwrap();
182 assert_eq!(0, res.messages.len());
183
184 let res = control.query_admin(deps.as_ref()).unwrap();
186 assert_eq!(Some(friend.to_string()), res.admin);
187 }
188}