1use abstract_sdk::std::account::state::WHITELISTED_MODULES;
2use abstract_std::{
3 account::state::{ACCOUNT_MODULES, CALLING_TO_AS_ADMIN},
4 objects::ownership,
5 ICA_CLIENT,
6};
7use cosmwasm_std::{
8 Addr, Binary, Coin, CosmosMsg, DepsMut, Empty, Env, MessageInfo, StdError, SubMsg, WasmMsg,
9 WasmQuery,
10};
11
12use crate::{
13 contract::{AccountResponse, AccountResult, ADMIN_ACTION_REPLY_ID, FORWARD_RESPONSE_REPLY_ID},
14 error::AccountError,
15 modules::load_module_addr,
16};
17
18pub(crate) fn assert_whitelisted_owner_or_self(
20 deps: &mut DepsMut,
21 env: &Env,
22 sender: &Addr,
23) -> AccountResult<()> {
24 let whitelisted_modules = WHITELISTED_MODULES.load(deps.storage)?;
25 if whitelisted_modules.0.contains(sender)
26 || ownership::assert_nested_owner(deps.storage, &deps.querier, sender).is_ok()
27 || sender == env.contract.address
28 {
29 Ok(())
30 } else {
31 Err(AccountError::SenderNotWhitelistedOrOwner {})
32 }
33}
34
35pub fn execute_msgs(
38 mut deps: DepsMut,
39 env: Env,
40 msg_sender: &Addr,
41 msgs: Vec<CosmosMsg<Empty>>,
42) -> AccountResult {
43 assert_whitelisted_owner_or_self(&mut deps, &env, msg_sender)?;
44
45 Ok(AccountResponse::action("execute_module_action").add_messages(msgs))
46}
47
48pub fn execute_msgs_with_data(
51 mut deps: DepsMut,
52 env: Env,
53 msg_sender: &Addr,
54 msg: CosmosMsg<Empty>,
55) -> AccountResult {
56 assert_whitelisted_owner_or_self(&mut deps, &env, msg_sender)?;
57
58 let submsg = SubMsg::reply_on_success(msg, FORWARD_RESPONSE_REPLY_ID);
59
60 Ok(AccountResponse::action("execute_module_action_response").add_submessage(submsg))
61}
62
63pub fn execute_on_module(
66 deps: DepsMut,
67 env: Env,
68 info: MessageInfo,
69 module_id: String,
70 exec_msg: Binary,
71 funds: Vec<Coin>,
72) -> AccountResult {
73 let module_addr = load_module_addr(deps.storage, &module_id)?;
74 execute_msgs(
75 deps,
76 env,
77 &info.sender,
78 vec![CosmosMsg::Wasm(WasmMsg::Execute {
79 contract_addr: module_addr.into(),
80 msg: exec_msg,
81 funds,
82 })],
83 )
84}
85
86pub fn admin_execute(
87 deps: DepsMut,
88 info: MessageInfo,
89 addr: Addr,
90 exec_msg: Binary,
91) -> AccountResult {
92 ownership::assert_nested_owner(deps.storage, &deps.querier, &info.sender)?;
93
94 if CALLING_TO_AS_ADMIN.exists(deps.storage) {
95 return Err(AccountError::CantChainAdminCalls {});
96 }
97 CALLING_TO_AS_ADMIN.save(deps.storage, &addr)?;
98
99 let msg = SubMsg::reply_on_success(
100 WasmMsg::Execute {
101 contract_addr: addr.to_string(),
102 msg: exec_msg,
103 funds: info.funds,
104 },
105 ADMIN_ACTION_REPLY_ID,
106 );
107
108 Ok(AccountResponse::action("admin_execute").add_submessage(msg))
109}
110
111pub fn admin_execute_on_module(
112 deps: DepsMut,
113 info: MessageInfo,
114 module_id: String,
115 exec_msg: Binary,
116) -> AccountResult {
117 let module_addr = load_module_addr(deps.storage, &module_id)?;
118 admin_execute(deps, info, module_addr, exec_msg)
119}
120
121pub fn add_auth_method(
122 _deps: DepsMut,
123 _env: Env,
124 _info: MessageInfo,
125 #[allow(unused_mut)] mut _auth: crate::msg::Authenticator,
126) -> AccountResult {
127 #[cfg(feature = "xion")]
128 {
129 ownership::assert_nested_owner(_deps.storage, &_deps.querier, &_info.sender)?;
130 abstract_xion::execute::add_auth_method(_deps, &_env, &mut _auth).map_err(Into::into)
131 }
132 #[cfg(not(feature = "xion"))]
133 {
134 Err(AccountError::NoAuthFeature {})
135 }
136}
137
138pub fn remove_auth_method(_deps: DepsMut, _env: Env, _info: MessageInfo, _id: u8) -> AccountResult {
139 #[cfg(feature = "xion")]
140 {
141 ownership::assert_nested_owner(_deps.storage, &_deps.querier, &_info.sender)?;
142 abstract_xion::execute::remove_auth_method(_deps, _env, _id).map_err(Into::into)
143 }
144 #[cfg(not(feature = "xion"))]
145 {
146 Err(AccountError::NoAuthFeature {})
147 }
148}
149
150pub fn ica_action(
158 mut deps: DepsMut,
159 env: Env,
160 msg_info: MessageInfo,
161 action_query: Binary,
162) -> AccountResult {
163 assert_whitelisted_owner_or_self(&mut deps, &env, &msg_info.sender)?;
164
165 let ica_client_address = ACCOUNT_MODULES
166 .may_load(deps.storage, ICA_CLIENT)?
167 .ok_or_else(|| {
168 StdError::generic_err(format!(
169 "ica_client not found on account. Add it under the {ICA_CLIENT} name."
170 ))
171 })?;
172
173 let res: abstract_ica::msg::IcaActionResult = deps.querier.query(
174 &WasmQuery::Smart {
175 contract_addr: ica_client_address.into(),
176 msg: action_query,
177 }
178 .into(),
179 )?;
180
181 Ok(AccountResponse::action("ica_action").add_messages(res.msgs))
182}
183
184#[cfg(test)]
185mod test {
186 use crate::contract::execute;
187 use crate::error::AccountError;
188 use crate::test_common::mock_init;
189 use abstract_sdk::namespaces::OWNERSHIP_STORAGE_KEY;
190 use abstract_std::account::{state::*, *};
191 use abstract_std::objects::gov_type::GovernanceDetails;
192 use abstract_std::objects::ownership::Ownership;
193 use abstract_std::{account, IBC_CLIENT};
194 use abstract_testing::prelude::*;
195 use cosmwasm_std::{coins, CosmosMsg, SubMsg};
196 use cosmwasm_std::{testing::*, Addr};
197 use cw_storage_plus::Item;
198
199 #[coverage_helper::test]
200 fn abstract_account_can_execute_on_itself() -> anyhow::Result<()> {
201 let mut deps = mock_dependencies();
202 deps.querier = abstract_mock_querier(deps.api);
203 mock_init(&mut deps)?;
204
205 let env = mock_env_validated(deps.api);
206 let ownership = Ownership {
209 owner: GovernanceDetails::AbstractAccount {
210 address: env.contract.address.clone(),
211 }
212 .verify(deps.as_ref())?,
213 pending_owner: None,
214 pending_expiry: None,
215 };
216 const OWNERSHIP: Item<Ownership<Addr>> = Item::new(OWNERSHIP_STORAGE_KEY);
217 OWNERSHIP.save(deps.as_mut().storage, &ownership)?;
218
219 let info = message_info(&env.contract.address, &[]);
221
222 let msg = ExecuteMsg::Execute { msgs: vec![] };
223
224 execute(deps.as_mut(), env.clone(), info, msg).unwrap();
225
226 Ok(())
227 }
228
229 mod execute_action {
230
231 use cosmwasm_std::{testing::MOCK_CONTRACT_ADDR, wasm_execute, CosmosMsg};
232
233 use super::*;
234
235 #[cfg(feature = "xion")]
236 #[coverage_helper::test]
237 fn admin_actions_not_chained() -> anyhow::Result<()> {
238 use crate::contract::AccountResult;
239 use abstract_sdk::namespaces::OWNERSHIP_STORAGE_KEY;
240 use abstract_std::objects::{gov_type::GovernanceDetails, ownership::Ownership};
241 use cosmwasm_std::{Addr, Binary, DepsMut, Empty, Env, Response, WasmMsg};
242 use cw_storage_plus::Item;
243
244 fn execute_from_res(deps: DepsMut, env: Env, res: Response) -> AccountResult<Response> {
245 let info = message_info(&env.contract.address, &[]);
247 if let CosmosMsg::Wasm(WasmMsg::Execute {
248 contract_addr: _,
249 msg,
250 funds: _,
251 }) = res.messages[0].msg.clone()
252 {
253 execute(deps, env.clone(), info, from_json(&msg)?).map_err(Into::into)
254 } else {
255 panic!("Wrong message received");
256 }
257 }
258 let mut deps = mock_dependencies();
259 deps.querier = abstract_mock_querier(deps.api);
260 mock_init(&mut deps)?;
261 let env = mock_env_validated(deps.api);
262
263 let ownership = Ownership {
266 owner: GovernanceDetails::AbstractAccount {
267 address: env.contract.address.clone(),
268 }
269 .verify(deps.as_ref())?,
270 pending_owner: None,
271 pending_expiry: None,
272 };
273
274 const OWNERSHIP: Item<Ownership<Addr>> = Item::new(OWNERSHIP_STORAGE_KEY);
275 OWNERSHIP.save(deps.as_mut().storage, &ownership)?;
276
277 let msg = ExecuteMsg::AdminExecute {
278 addr: env.contract.address.to_string(),
279 msg: to_json_binary(&ExecuteMsg::<Empty>::AdminExecute {
280 addr: env.contract.address.to_string(),
281 msg: Binary::new(vec![]),
282 })?,
283 };
284
285 let info = message_info(&env.contract.address, &[]);
286 let env = mock_env_validated(deps.api);
287
288 AUTH_ADMIN.save(deps.as_mut().storage, &true)?;
290 let res = execute(deps.as_mut(), env.clone(), info, msg).unwrap();
291 AUTH_ADMIN.save(deps.as_mut().storage, &true)?;
293 let res = execute_from_res(deps.as_mut(), env, res);
294 assert_eq!(res, Err(AccountError::CantChainAdminCalls {}));
295 Ok(())
296 }
297
298 #[coverage_helper::test]
299 fn only_whitelisted_can_execute() -> anyhow::Result<()> {
300 let mut deps = mock_dependencies();
301 deps.querier = abstract_mock_querier(deps.api);
302 mock_init(&mut deps)?;
303
304 let msg = ExecuteMsg::Execute { msgs: vec![] };
305
306 let info = message_info(&deps.api.addr_make("not_whitelisted"), &[]);
307 let env = mock_env_validated(deps.api);
308
309 let res = execute(deps.as_mut(), env, info, msg);
310 assert_eq!(res, Err(AccountError::SenderNotWhitelistedOrOwner {}));
311 Ok(())
312 }
313
314 #[coverage_helper::test]
315 fn forwards_action() -> anyhow::Result<()> {
316 let mut deps = mock_dependencies();
317 deps.querier = abstract_mock_querier(deps.api);
318 let env = mock_env_validated(deps.api);
319 mock_init(&mut deps)?;
320
321 let module_addr = deps.api.addr_make(TEST_MODULE_ID);
323 WHITELISTED_MODULES.save(
324 &mut deps.storage,
325 &WhitelistedModules(vec![module_addr.clone()]),
326 )?;
327
328 let action: CosmosMsg = wasm_execute(
329 MOCK_CONTRACT_ADDR.to_string(),
330 &<ExecuteMsg>::UpdateOwnership(
332 abstract_std::objects::gov_type::GovAction::RenounceOwnership,
333 ),
334 vec![],
335 )?
336 .into();
337
338 let msg = ExecuteMsg::Execute {
339 msgs: vec![action.clone()],
340 };
341
342 let res = execute(deps.as_mut(), env, message_info(&module_addr, &[]), msg);
344 assert!(res.is_ok());
345
346 let msgs = res?.messages;
347 assert_eq!(msgs.len(), 1);
348
349 let msg = &msgs[0];
350 assert_eq!(msg.msg, action);
351
352 Ok(())
353 }
354 }
355
356 mod execute_ibc {
357 use crate::modules::update_module_addresses;
358
359 use super::*;
360
361 #[coverage_helper::test]
362 fn add_module() -> anyhow::Result<()> {
363 let mut deps = mock_dependencies();
364 deps.querier = abstract_mock_querier(deps.api);
365 let env = mock_env_validated(deps.api);
366 mock_init(&mut deps)?;
367 let abstr = AbstractMockAddrs::new(deps.api);
368 account::state::WHITELISTED_MODULES.save(
370 &mut deps.storage,
371 &WhitelistedModules(vec![abstr.account.addr().clone()]),
372 )?;
373
374 let msg = ExecuteMsg::ExecuteOnModule {
375 module_id: IBC_CLIENT.to_owned(),
376 exec_msg: to_json_binary(&abstract_std::ibc_client::ExecuteMsg::Register {
377 host_chain: "juno".parse()?,
378 namespace: None,
379 install_modules: vec![],
380 })?,
381 funds: vec![],
382 };
383
384 let not_whitelisted_info = message_info(&deps.api.addr_make("not_whitelisted"), &[]);
385 execute(
386 deps.as_mut(),
387 env.clone(),
388 not_whitelisted_info,
389 msg.clone(),
390 )
391 .unwrap_err();
392
393 let account_info = message_info(abstr.account.addr(), &[]);
394 execute(
396 deps.as_mut(),
397 env.clone(),
398 account_info.clone(),
399 msg.clone(),
400 )
401 .unwrap_err();
402 let ibc_client_addr = deps.api.addr_make("ibc_client_addr");
404 deps.querier = MockQuerierBuilder::default()
405 .with_contract_map_entry(
406 abstr.account.addr(),
407 account::state::ACCOUNT_MODULES,
408 (IBC_CLIENT, ibc_client_addr.clone()),
409 )
410 .build();
411 update_module_addresses(
413 deps.as_mut(),
414 vec![(IBC_CLIENT.into(), ibc_client_addr.clone())],
415 vec![],
416 )?;
417
418 let res = execute(deps.as_mut(), env, account_info, msg)?;
419 assert_eq!(res.messages.len(), 1);
420 assert_eq!(
421 res.messages[0],
422 SubMsg::new(CosmosMsg::Wasm(cosmwasm_std::WasmMsg::Execute {
423 contract_addr: ibc_client_addr.to_string(),
424 msg: to_json_binary(&abstract_std::ibc_client::ExecuteMsg::Register {
425 host_chain: "juno".parse()?,
426 namespace: None,
427 install_modules: vec![],
428 })?,
429 funds: vec![],
430 },))
431 );
432 Ok(())
433 }
434
435 #[coverage_helper::test]
436 fn send_funds() -> anyhow::Result<()> {
437 let mut deps = mock_dependencies();
438 deps.querier = abstract_mock_querier(deps.api);
439 let env = mock_env_validated(deps.api);
440 mock_init(&mut deps)?;
441 let abstr = AbstractMockAddrs::new(deps.api);
442 account::state::WHITELISTED_MODULES.save(
444 &mut deps.storage,
445 &WhitelistedModules(vec![abstr.account.addr().clone()]),
446 )?;
447
448 let funds = coins(10, "denom");
449 let msg = ExecuteMsg::ExecuteOnModule {
450 module_id: IBC_CLIENT.to_owned(),
451 exec_msg: to_json_binary(&abstract_std::ibc_client::ExecuteMsg::SendFunds {
452 host_chain: "juno".parse()?,
453 memo: None,
454 receiver: None,
455 })?,
456 funds: funds.clone(),
457 };
458
459 let not_whitelisted_info = message_info(&deps.api.addr_make("not_whitelisted"), &[]);
460 execute(
461 deps.as_mut(),
462 env.clone(),
463 not_whitelisted_info,
464 msg.clone(),
465 )
466 .unwrap_err();
467
468 let account_info = message_info(abstr.account.addr(), &[]);
469 execute(
471 deps.as_mut(),
472 env.clone(),
473 account_info.clone(),
474 msg.clone(),
475 )
476 .unwrap_err();
477 let ibc_client_addr = deps.api.addr_make("ibc_client_addr");
479 deps.querier = MockQuerierBuilder::default()
480 .with_contract_map_entry(
481 &abstr.account.addr().clone(),
482 account::state::ACCOUNT_MODULES,
483 (IBC_CLIENT, ibc_client_addr.clone()),
484 )
485 .build();
486 update_module_addresses(
487 deps.as_mut(),
488 vec![(IBC_CLIENT.into(), ibc_client_addr.clone())],
489 vec![],
490 )?;
491
492 let res = execute(deps.as_mut(), env, account_info, msg)?;
493 assert_eq!(res.messages.len(), 1);
494 assert_eq!(
495 res.messages[0],
496 SubMsg::new(CosmosMsg::Wasm(cosmwasm_std::WasmMsg::Execute {
497 contract_addr: ibc_client_addr.into(),
498 msg: to_json_binary(&abstract_std::ibc_client::ExecuteMsg::SendFunds {
499 host_chain: "juno".parse()?,
500 memo: None,
501 receiver: None,
502 })?,
503 funds,
504 },))
505 );
506 Ok(())
507 }
508 }
509
510 mod ica_action {
511 use abstract_ica::msg::IcaActionResult;
512 use abstract_std::ICA_CLIENT;
513 use cosmwasm_std::{Binary, Empty};
514
515 use crate::modules::update_module_addresses;
516
517 use super::*;
518
519 #[coverage_helper::test]
520 fn ica_action() -> anyhow::Result<()> {
521 let mut deps = mock_dependencies();
522 deps.querier = abstract_mock_querier(deps.api);
523 let env = mock_env_validated(deps.api);
524 let abstr = AbstractMockAddrs::new(deps.api);
525 let ica_client_addr = deps.api.addr_make("ica_client_addr");
526 mock_init(&mut deps)?;
527 account::state::WHITELISTED_MODULES.save(
529 &mut deps.storage,
530 &WhitelistedModules(vec![abstr.account.addr().clone()]),
531 )?;
532
533 let action = Binary::from(b"some_action");
534 let msg = ExecuteMsg::IcaAction {
535 action_query_msg: action.clone(),
536 };
537
538 let not_whitelisted_info = message_info(&deps.api.addr_make("not_whitelisted"), &[]);
539 execute(
540 deps.as_mut(),
541 env.clone(),
542 not_whitelisted_info,
543 msg.clone(),
544 )
545 .unwrap_err();
546
547 let account_info = message_info(abstr.account.addr(), &[]);
548 execute(
550 deps.as_mut(),
551 env.clone(),
552 account_info.clone(),
553 msg.clone(),
554 )
555 .unwrap_err();
556 update_module_addresses(
558 deps.as_mut(),
559 vec![(ICA_CLIENT.into(), ica_client_addr.clone())],
560 vec![],
561 )?;
562
563 deps.querier = MockQuerierBuilder::default()
564 .with_smart_handler(&ica_client_addr, move |bin| {
565 if bin.eq(&action) {
566 Ok(to_json_binary(&IcaActionResult {
567 msgs: vec![CosmosMsg::Custom(Empty {})],
568 })
569 .unwrap())
570 } else {
571 Err("Unexpected action query".to_owned())
572 }
573 })
574 .build();
575
576 let res = execute(deps.as_mut(), env, account_info, msg)?;
577 assert_eq!(res.messages.len(), 1);
578 assert_eq!(res.messages[0], SubMsg::new(CosmosMsg::Custom(Empty {})));
579 Ok(())
580 }
581 }
582}