andromeda_std/ado_contract/
ownership.rs1use crate::common::MillisecondsExpiration;
2use crate::error::ContractError;
3use crate::{
4 ado_base::ownership::{ContractPotentialOwnerResponse, OwnershipMessage},
5 ado_contract::ADOContract,
6};
7use cosmwasm_std::{attr, ensure, Addr, DepsMut, Env, MessageInfo, Response, Storage};
8use cw_storage_plus::Item;
9
10const POTENTIAL_OWNER: Item<Addr> = Item::new("andr_potential_owner");
11const POTENTIAL_OWNER_EXPIRATION: Item<MillisecondsExpiration> =
12 Item::new("andr_potential_owner_expiration");
13
14impl<'a> ADOContract<'a> {
15 pub fn execute_ownership(
16 &self,
17 deps: DepsMut,
18 env: Env,
19 info: MessageInfo,
20 msg: OwnershipMessage,
21 ) -> Result<Response, ContractError> {
22 match msg {
23 OwnershipMessage::UpdateOwner {
24 new_owner,
25 expiration,
26 } => self.update_owner(deps, info, new_owner, expiration),
27 OwnershipMessage::RevokeOwnershipOffer => self.revoke_ownership_offer(deps, info),
28 OwnershipMessage::AcceptOwnership => self.accept_ownership(deps, env, info),
29 OwnershipMessage::Disown => self.disown(deps, info),
30 }
31 }
32
33 pub fn update_owner(
35 &self,
36 deps: DepsMut,
37 info: MessageInfo,
38 new_owner: Addr,
39 expiration: Option<MillisecondsExpiration>,
40 ) -> Result<Response, ContractError> {
41 ensure!(
42 self.is_contract_owner(deps.storage, info.sender.as_str())?,
43 ContractError::Unauthorized {}
44 );
45 ensure!(
46 !self.is_contract_owner(deps.storage, new_owner.as_str())?,
47 ContractError::Unauthorized {}
48 );
49 let new_owner_addr = deps.api.addr_validate(new_owner.as_ref())?;
50 POTENTIAL_OWNER.save(deps.storage, &new_owner_addr)?;
51
52 if let Some(exp) = expiration {
53 POTENTIAL_OWNER_EXPIRATION.save(deps.storage, &exp)?;
54 } else {
55 POTENTIAL_OWNER_EXPIRATION.remove(deps.storage);
57 }
58
59 Ok(Response::new().add_attributes(vec![
60 attr("action", "update_owner"),
61 attr("value", new_owner),
62 ]))
63 }
64
65 pub fn revoke_ownership_offer(
67 &self,
68 deps: DepsMut,
69 info: MessageInfo,
70 ) -> Result<Response, ContractError> {
71 ensure!(
72 self.is_contract_owner(deps.storage, info.sender.as_str())?,
73 ContractError::Unauthorized {}
74 );
75 POTENTIAL_OWNER.remove(deps.storage);
76 POTENTIAL_OWNER_EXPIRATION.remove(deps.storage);
77 Ok(Response::new().add_attributes(vec![attr("action", "revoke_ownership_offer")]))
78 }
79
80 pub fn accept_ownership(
82 &self,
83 deps: DepsMut,
84 env: Env,
85 info: MessageInfo,
86 ) -> Result<Response, ContractError> {
87 let new_owner_addr = POTENTIAL_OWNER.load(deps.storage)?;
88 ensure!(
89 info.sender == new_owner_addr,
90 ContractError::Unauthorized {}
91 );
92 let expiration = POTENTIAL_OWNER_EXPIRATION.may_load(deps.storage)?;
93 if let Some(exp) = expiration {
94 ensure!(!exp.is_expired(&env.block), ContractError::Unauthorized {});
95 }
96
97 self.owner.save(deps.storage, &new_owner_addr)?;
98 POTENTIAL_OWNER.remove(deps.storage);
99 POTENTIAL_OWNER_EXPIRATION.remove(deps.storage);
100 Ok(Response::new().add_attributes(vec![
101 attr("action", "accept_ownership"),
102 attr("value", new_owner_addr.to_string()),
103 ]))
104 }
105
106 pub fn disown(&self, deps: DepsMut, info: MessageInfo) -> Result<Response, ContractError> {
108 ensure!(
109 self.is_contract_owner(deps.storage, info.sender.as_str())?,
110 ContractError::Unauthorized {}
111 );
112 self.owner.save(deps.storage, &Addr::unchecked("null"))?;
113 Ok(Response::new().add_attributes(vec![attr("action", "disown")]))
114 }
115
116 pub fn owner(&self, storage: &dyn Storage) -> Result<Addr, ContractError> {
120 let owner = self.owner.load(storage)?;
121 Ok(owner)
122 }
123
124 pub fn is_contract_owner(
128 &self,
129 storage: &dyn Storage,
130 addr: &str,
131 ) -> Result<bool, ContractError> {
132 let owner = self.owner.load(storage)?;
133 Ok(addr == owner)
134 }
135
136 pub fn is_owner_or_operator(
140 &self,
141 storage: &dyn Storage,
142 addr: &str,
143 ) -> Result<bool, ContractError> {
144 self.is_contract_owner(storage, addr)
145 }
146
147 pub fn ownership_request(
148 &self,
149 storage: &dyn Storage,
150 ) -> Result<ContractPotentialOwnerResponse, ContractError> {
151 let potential_owner = POTENTIAL_OWNER.may_load(storage)?;
152 let expiration = POTENTIAL_OWNER_EXPIRATION.may_load(storage)?;
153 Ok(ContractPotentialOwnerResponse {
154 potential_owner,
155 expiration,
156 })
157 }
158}
159
160#[cfg(test)]
161mod test {
162 use cosmwasm_std::{
163 testing::{mock_dependencies, mock_env, mock_info},
164 Addr, DepsMut,
165 };
166
167 use crate::{
168 ado_contract::{
169 ownership::{POTENTIAL_OWNER, POTENTIAL_OWNER_EXPIRATION},
170 ADOContract,
171 },
172 common::MillisecondsExpiration,
173 };
174
175 fn init(deps: DepsMut, owner: impl Into<String>) {
176 ADOContract::default()
177 .owner
178 .save(deps.storage, &Addr::unchecked(owner))
179 .unwrap();
180 }
181
182 #[test]
183 fn test_update_owner() {
184 let mut deps = mock_dependencies();
185 let contract = ADOContract::default();
186 let new_owner = Addr::unchecked("new_owner");
187 init(deps.as_mut(), "owner");
188
189 let res = contract.update_owner(
190 deps.as_mut(),
191 mock_info("owner", &[]),
192 new_owner.clone(),
193 None,
194 );
195 assert!(res.is_ok());
196 let saved_new_owner = POTENTIAL_OWNER.load(deps.as_ref().storage).unwrap();
197 assert_eq!(saved_new_owner, new_owner);
198
199 let res = contract.update_owner(
200 deps.as_mut(),
201 mock_info("owner", &[]),
202 Addr::unchecked("owner"),
203 None,
204 );
205 assert!(res.is_err());
206 let res =
207 contract.update_owner(deps.as_mut(), mock_info("new_owner", &[]), new_owner, None);
208 assert!(res.is_err());
209 }
210
211 #[test]
212 fn test_revoke_ownership_offer() {
213 let mut deps = mock_dependencies();
214 let contract = ADOContract::default();
215 init(deps.as_mut(), "owner");
216
217 let res = contract.revoke_ownership_offer(deps.as_mut(), mock_info("owner", &[]));
218 assert!(res.is_ok());
219 let saved_new_owner = POTENTIAL_OWNER.may_load(deps.as_ref().storage).unwrap();
220 assert!(saved_new_owner.is_none());
221 }
222
223 #[test]
224 fn test_accept_ownership() {
225 let mut deps = mock_dependencies();
226 let contract = ADOContract::default();
227 let new_owner = Addr::unchecked("new_owner");
228 init(deps.as_mut(), "owner");
229 POTENTIAL_OWNER
230 .save(deps.as_mut().storage, &new_owner)
231 .unwrap();
232
233 let res = contract.accept_ownership(deps.as_mut(), mock_env(), mock_info("owner", &[]));
234 assert!(res.is_err());
235 let res = contract.accept_ownership(deps.as_mut(), mock_env(), mock_info("new_owner", &[]));
236 assert!(res.is_ok());
237 let saved_owner = contract.owner.load(deps.as_ref().storage).unwrap();
238 assert_eq!(saved_owner, new_owner);
239 let saved_new_owner = POTENTIAL_OWNER.may_load(deps.as_ref().storage).unwrap();
240 assert!(saved_new_owner.is_none());
241 }
242
243 #[test]
244 fn test_accept_ownership_expired() {
245 let mut deps = mock_dependencies();
246 let contract = ADOContract::default();
247 let new_owner = Addr::unchecked("new_owner");
248 init(deps.as_mut(), "owner");
249 POTENTIAL_OWNER
250 .save(deps.as_mut().storage, &new_owner)
251 .unwrap();
252 POTENTIAL_OWNER_EXPIRATION
253 .save(
254 deps.as_mut().storage,
255 &MillisecondsExpiration::from_nanos(1),
256 )
257 .unwrap();
258
259 let mut env = mock_env();
260 env.block.time = MillisecondsExpiration::from_nanos(2).into();
261 let res = contract.accept_ownership(deps.as_mut(), env, mock_info("new_owner", &[]));
262 assert!(res.is_err());
263 let saved_owner = contract.owner.load(deps.as_ref().storage).unwrap();
264 assert_eq!(saved_owner, Addr::unchecked("owner"));
265 }
266
267 #[test]
268 fn test_disown() {
269 let mut deps = mock_dependencies();
270 let contract = ADOContract::default();
271 init(deps.as_mut(), "owner");
272
273 let res = contract.disown(deps.as_mut(), mock_info("owner", &[]));
274 assert!(res.is_ok());
275 let saved_owner = contract.owner.load(deps.as_ref().storage).unwrap();
276 assert_eq!(saved_owner, Addr::unchecked("null"));
277 }
278}