1use crate::{get_account_contract, Abstract, AbstractInterfaceError, AdapterDeployer, Registry};
15pub use abstract_std::account::{ExecuteMsgFns as AccountExecFns, QueryMsgFns as AccountQueryFns};
16use abstract_std::{
17 account::{AccountModuleInfo, ModuleInstallConfig, *},
18 adapter::{self, AdapterBaseMsg},
19 ibc_host::{HelperAction, HostAction},
20 module_factory::SimulateInstallModulesResponse,
21 objects::{
22 gov_type::GovernanceDetails,
23 module::{ModuleInfo, ModuleStatus, ModuleVersion},
24 salt::generate_instantiate_salt,
25 AccountId, TruncatedChainId,
26 },
27 registry::{state::LOCAL_ACCOUNT_SEQUENCE, ExecuteMsgFns, ModuleFilter, QueryMsgFns},
28 ABSTRACT_EVENT_TYPE, ACCOUNT, IBC_CLIENT,
29};
30use cosmwasm_std::{from_json, to_json_binary};
31use cosmwasm_std::{Binary, Empty};
32use cw2::{ContractVersion, CONTRACT};
33use cw_orch::{environment::Environment, interface, prelude::*};
34use semver::{Version, VersionReq};
35use serde::Serialize;
36use std::{collections::HashSet, fmt::Debug};
37
38#[derive(Default)]
40pub struct AccountDetails {
41 pub name: String,
42 pub description: Option<String>,
43 pub link: Option<String>,
44 pub namespace: Option<String>,
45 pub install_modules: Vec<ModuleInstallConfig>,
46 pub account_id: Option<u32>,
47}
48
49#[interface(InstantiateMsg, ExecuteMsg, QueryMsg, MigrateMsg)]
50pub struct AccountI<Chain>;
51
52impl<Chain> AsRef<AccountI<Chain>> for AccountI<Chain> {
53 fn as_ref(&self) -> &AccountI<Chain> {
54 self
55 }
56}
57
58impl<Chain: CwEnv> AccountI<Chain> {
59 pub fn load_from(
60 abstract_deployment: &Abstract<Chain>,
61 account_id: AccountId,
62 ) -> Result<Self, AbstractInterfaceError> {
63 get_account_contract(&abstract_deployment.registry, account_id)
64 }
65
66 pub(crate) fn new_from_id(account_id: &AccountId, chain: Chain) -> Self {
67 let account_id = format!("{ACCOUNT}-{account_id}");
68 Self::new(account_id, chain)
69 }
70
71 pub fn create(
73 abstract_deployment: &Abstract<Chain>,
74 details: AccountDetails,
75 governance_details: GovernanceDetails<String>,
76 funds: &[cosmwasm_std::Coin],
77 ) -> Result<Self, AbstractInterfaceError> {
78 let chain = abstract_deployment.registry.environment().clone();
79
80 let salt = generate_instantiate_salt(&AccountId::local(details.account_id.unwrap_or(
82 chain.wasm_querier().item_query(
83 &abstract_deployment.registry.address()?,
84 LOCAL_ACCOUNT_SEQUENCE,
85 )?,
86 )));
87 let code_id = abstract_deployment.account.code_id().unwrap();
88
89 let account_addr = chain
90 .wasm_querier()
91 .instantiate2_addr(code_id, &chain.sender_addr(), salt.clone())
92 .map_err(Into::into)?;
93 let account_addr = Addr::unchecked(account_addr);
94
95 chain
96 .instantiate2(
97 code_id,
98 &InstantiateMsg::<Empty> {
99 code_id,
100 account_id: details.account_id.map(AccountId::local),
101 owner: Some(governance_details),
102 namespace: details.namespace,
103 install_modules: details.install_modules,
104 name: Some(details.name),
105 description: details.description,
106 link: details.link,
107 authenticator: None,
108 },
109 Some("Abstract Account"),
110 Some(&account_addr),
111 funds,
112 salt,
113 )
114 .map_err(Into::into)?;
115
116 let account_id = chain
117 .wasm_querier()
118 .item_query(&account_addr, state::ACCOUNT_ID)?;
119 let contract_id = format!("{ACCOUNT}-{account_id}");
120
121 let account = Self::new(contract_id, chain);
122 account.set_address(&account_addr);
123 Ok(account)
124 }
125
126 pub fn create_default_account(
127 abstract_deployment: &Abstract<Chain>,
128 governance_details: GovernanceDetails<String>,
129 ) -> Result<Self, AbstractInterfaceError> {
130 let details = AccountDetails {
131 name: "Default Abstract Account".into(),
132 ..Default::default()
133 };
134 Self::create(abstract_deployment, details, governance_details, &[])
135 }
136}
137
138impl<Chain: CwEnv> AccountI<Chain> {
140 pub fn upgrade_module<M: Serialize>(
141 &self,
142 module_id: &str,
143 migrate_msg: &M,
144 ) -> Result<(), crate::AbstractInterfaceError> {
145 self.upgrade(vec![(
146 ModuleInfo::from_id(module_id, ModuleVersion::Latest)?,
147 Some(to_json_binary(migrate_msg).unwrap()),
148 )])?;
149 Ok(())
150 }
151
152 pub fn replace_api(
153 &self,
154 module_id: &str,
155 funds: &[Coin],
156 ) -> Result<(), crate::AbstractInterfaceError> {
157 self.uninstall_module(module_id.to_string())?;
159
160 self.install_module::<Empty>(module_id, None, funds)?;
161 Ok(())
162 }
163 pub fn install_module<TInitMsg: Serialize>(
164 &self,
165 module_id: &str,
166 init_msg: Option<&TInitMsg>,
167 funds: &[Coin],
168 ) -> Result<Chain::Response, crate::AbstractInterfaceError> {
169 self.install_module_version(module_id, ModuleVersion::Latest, init_msg, funds)
170 }
171
172 pub fn install_modules_auto(
173 &self,
174 modules: Vec<ModuleInstallConfig>,
175 ) -> Result<Chain::Response, crate::AbstractInterfaceError> {
176 let config = self.config()?;
177 let module_infos = modules.iter().map(|m| m.module.clone()).collect();
178 let sim_response: SimulateInstallModulesResponse = self
179 .environment()
180 .query(
181 &abstract_std::module_factory::QueryMsg::SimulateInstallModules {
182 modules: module_infos,
183 },
184 &config.module_factory_address,
185 )
186 .map_err(Into::into)?;
187 self.install_modules(modules, sim_response.total_required_funds.as_ref())
188 .map_err(Into::into)
189 }
190
191 pub fn install_module_version<M: Serialize>(
192 &self,
193 module_id: &str,
194 version: ModuleVersion,
195 init_msg: Option<&M>,
196 funds: &[Coin],
197 ) -> Result<Chain::Response, crate::AbstractInterfaceError> {
198 self.install_modules(
199 vec![ModuleInstallConfig::new(
200 ModuleInfo::from_id(module_id, version)?,
201 init_msg.map(to_json_binary).transpose().unwrap(),
202 )],
203 funds,
204 )
205 .map_err(Into::into)
206 }
207 pub fn expect_modules(
210 &self,
211 module_addrs: Vec<String>,
212 ) -> Result<Vec<AccountModuleInfo>, crate::AbstractInterfaceError> {
213 let abstract_std::account::ModuleInfosResponse {
214 module_infos: account_modules,
215 } = self.module_infos(None, None)?;
216
217 let expected_module_addrs = module_addrs
218 .into_iter()
219 .map(Addr::unchecked)
220 .collect::<HashSet<_>>();
221
222 let actual_module_addrs = account_modules
223 .iter()
224 .map(|module_info| module_info.address.clone())
225 .collect::<HashSet<_>>();
226
227 assert_eq!(expected_module_addrs, actual_module_addrs);
229
230 Ok(account_modules)
231 }
232
233 pub fn is_module_installed(
234 &self,
235 module_id: &str,
236 ) -> Result<bool, crate::AbstractInterfaceError> {
237 let module = self.module_info(module_id)?;
238 Ok(module.is_some())
239 }
240
241 pub fn expect_whitelist(
243 &self,
244 expected_whitelisted_addrs: Vec<Addr>,
245 ) -> Result<(), crate::AbstractInterfaceError> {
246 let expected_whitelisted_addrs = expected_whitelisted_addrs
247 .into_iter()
248 .collect::<HashSet<_>>();
249
250 let abstract_std::account::ConfigResponse {
252 whitelisted_addresses: whitelist,
253 ..
254 } = self.config()?;
255
256 let actual_whitelist = HashSet::from_iter(whitelist);
257 assert_eq!(actual_whitelist, expected_whitelisted_addrs);
258
259 Ok(())
260 }
261
262 pub fn install_adapter<CustomInitMsg: Serialize, T: AdapterDeployer<Chain, CustomInitMsg>>(
264 &self,
265 module: &T,
266 funds: &[Coin],
267 ) -> Result<Addr, crate::AbstractInterfaceError> {
268 self.install_module_parse_addr::<Empty, _>(module, None, funds)
269 }
270
271 pub fn install_app<CustomInitMsg: Serialize, T: ContractInstance<Chain>>(
273 &self,
274 module: &T,
275 custom_init_msg: &CustomInitMsg,
276 funds: &[Coin],
277 ) -> Result<Addr, crate::AbstractInterfaceError> {
278 self.install_module_parse_addr(module, Some(&custom_init_msg), funds)
280 }
281
282 pub fn install_standalone<CustomInitMsg: Serialize, T: ContractInstance<Chain>>(
284 &self,
285 standalone: &T,
286 custom_init_msg: &CustomInitMsg,
287 funds: &[Coin],
288 ) -> Result<Addr, crate::AbstractInterfaceError> {
289 self.install_module_parse_addr(standalone, Some(&custom_init_msg), funds)
291 }
292
293 fn install_module_parse_addr<InitMsg: Serialize, T: ContractInstance<Chain>>(
294 &self,
295 module: &T,
296 init_msg: Option<&InitMsg>,
297 funds: &[Coin],
298 ) -> Result<Addr, crate::AbstractInterfaceError> {
299 let resp = self.install_module(&module.id(), init_msg, funds)?;
300 let module_address = resp.event_attr_value(ABSTRACT_EVENT_TYPE, "new_modules")?;
301 let module_address = Addr::unchecked(module_address);
302
303 module.set_address(&module_address);
304 Ok(module_address)
305 }
306
307 pub fn execute_on_module(
308 &self,
309 module: &str,
310 msg: impl Serialize,
311 funds: Vec<Coin>,
312 ) -> Result<<Chain as cw_orch::prelude::TxHandler>::Response, crate::AbstractInterfaceError>
313 {
314 <AccountI<Chain> as AccountExecFns<Chain, abstract_std::account::ExecuteMsg>>::execute_on_module(
315 self,
316 to_json_binary(&msg).unwrap(),
317 funds,
318 module,
319 &[],
320 )
321 .map_err(Into::into)
322 }
323
324 pub fn update_adapter_authorized_addresses(
325 &self,
326 module_id: &str,
327 to_add: Vec<String>,
328 to_remove: Vec<String>,
329 ) -> Result<(), crate::AbstractInterfaceError> {
330 self.admin_execute_on_module(
331 module_id,
332 to_json_binary(&adapter::ExecuteMsg::<Empty>::Base(
333 adapter::BaseExecuteMsg {
334 msg: AdapterBaseMsg::UpdateAuthorizedAddresses { to_add, to_remove },
335 account_address: None,
336 },
337 ))?,
338 &[],
339 )?;
340
341 Ok(())
342 }
343
344 pub fn module_info(
346 &self,
347 module_id: &str,
348 ) -> Result<Option<AccountModuleInfo>, crate::AbstractInterfaceError> {
349 let module_infos = self.module_infos(None, None)?.module_infos;
350 let found = module_infos
351 .into_iter()
352 .find(|module_info| module_info.id == module_id);
353 Ok(found)
354 }
355
356 pub fn module_address(
359 &self,
360 module_id: impl Into<String>,
361 ) -> Result<Addr, crate::AbstractInterfaceError> {
362 Ok(self.module_addresses(vec![module_id.into()])?.modules[0]
363 .1
364 .clone())
365 }
366}
367
368impl<Chain: CwEnv> AccountI<Chain> {
370 pub fn register_remote_account(
372 &self,
373 host_chain: TruncatedChainId,
374 ) -> Result<<Chain as cw_orch::prelude::TxHandler>::Response, crate::AbstractInterfaceError>
375 {
376 self.create_remote_account(
377 AccountDetails {
378 name: "No specified name".to_string(),
379 description: None,
380 link: None,
381 namespace: None,
382 install_modules: vec![ModuleInstallConfig::new(
383 ModuleInfo::from_id_latest(IBC_CLIENT)?,
384 None,
385 )],
386 account_id: None,
387 },
388 host_chain,
389 )
390 }
391
392 pub fn create_remote_account(
393 &self,
394 account_details: AccountDetails,
395 host_chain: TruncatedChainId,
396 ) -> Result<<Chain as cw_orch::prelude::TxHandler>::Response, crate::AbstractInterfaceError>
397 {
398 let AccountDetails {
399 namespace,
400 install_modules,
401 name: _,
403 description: _,
404 link: _,
405 account_id: _,
406 } = account_details;
407
408 self.execute_on_module(
409 IBC_CLIENT,
410 &abstract_std::ibc_client::ExecuteMsg::Register {
411 host_chain,
412 namespace,
413 install_modules,
414 },
415 vec![],
416 )
417 .map_err(Into::into)
418 }
419
420 pub fn set_ibc_status(
421 &self,
422 enabled: bool,
423 ) -> Result<Chain::Response, crate::AbstractInterfaceError> {
424 let response = if enabled {
425 self.install_module::<Empty>(IBC_CLIENT, None, &[])?
426 } else {
427 self.uninstall_module(IBC_CLIENT.to_string())?
428 };
429
430 Ok(response)
431 }
432
433 pub fn execute_on_remote(
434 &self,
435 host_chain: TruncatedChainId,
436 msg: ExecuteMsg,
437 ) -> Result<<Chain as cw_orch::prelude::TxHandler>::Response, crate::AbstractInterfaceError>
438 {
439 self.execute_on_module(
440 IBC_CLIENT,
441 abstract_std::ibc_client::ExecuteMsg::RemoteAction {
442 host_chain,
443 action: HostAction::Dispatch {
444 account_msgs: vec![msg],
445 },
446 },
447 vec![],
448 )
449 .map_err(Into::into)
450 }
451
452 pub fn execute_on_remote_module(
455 &self,
456 host_chain: TruncatedChainId,
457 module_id: &str,
458 msg: Binary,
459 funds: Vec<Coin>,
460 ) -> Result<<Chain as cw_orch::prelude::TxHandler>::Response, crate::AbstractInterfaceError>
461 {
462 self.execute_on_module(
463 IBC_CLIENT,
464 &(abstract_std::ibc_client::ExecuteMsg::RemoteAction {
465 host_chain,
466 action: HostAction::Dispatch {
467 account_msgs: vec![ExecuteMsg::ExecuteOnModule {
468 module_id: module_id.to_string(),
469 exec_msg: msg,
470 funds,
471 }],
472 },
473 }),
474 vec![],
475 )
476 .map_err(Into::into)
477 }
478
479 pub fn send_all_funds_back(
480 &self,
481 host_chain: TruncatedChainId,
482 ) -> Result<<Chain as cw_orch::prelude::TxHandler>::Response, crate::AbstractInterfaceError>
483 {
484 self.execute_on_module(
485 IBC_CLIENT,
486 &abstract_std::ibc_client::ExecuteMsg::RemoteAction {
487 host_chain,
488 action: HostAction::Helpers(HelperAction::SendAllBack),
489 },
490 vec![],
491 )
492 .map_err(Into::into)
493 }
494}
495
496impl<Chain: CwEnv> AccountI<Chain> {
497 pub fn register(
499 &self,
500 registry: &Registry<Chain>,
501 ) -> Result<(), crate::AbstractInterfaceError> {
502 registry.register_base(self)
503 }
504
505 pub fn id(&self) -> Result<AccountId, crate::AbstractInterfaceError> {
507 Ok(self.config()?.account_id)
508 }
509
510 pub fn create_and_return_sub_account(
511 &self,
512 account_details: AccountDetails,
513 funds: &[Coin],
514 ) -> Result<AccountI<Chain>, crate::AbstractInterfaceError> {
515 let AccountDetails {
516 name,
517 description,
518 link,
519 namespace,
520 install_modules,
521 account_id,
522 } = account_details;
523
524 let result = self.create_sub_account(
525 install_modules,
526 account_id,
527 description,
528 link,
529 Some(name),
530 namespace,
531 funds,
532 )?;
533
534 Self::from_tx_response(self.environment(), result)
535 }
536
537 pub(crate) fn from_tx_response(
540 chain: &Chain,
541 result: <Chain as TxHandler>::Response,
542 ) -> Result<AccountI<Chain>, crate::AbstractInterfaceError> {
543 let acc_id = &result.event_attr_value(ABSTRACT_EVENT_TYPE, "account_id")?;
545 let id: AccountId = acc_id.parse()?;
546 let account = Self::new_from_id(&id, chain.clone());
547
548 let account_address = result.event_attr_value(ABSTRACT_EVENT_TYPE, "account_address")?;
550 account.set_address(&Addr::unchecked(account_address));
551
552 Ok(account)
553 }
554
555 pub fn upload_and_register_if_needed(
556 &self,
557 registry: &Registry<Chain>,
558 ) -> Result<bool, AbstractInterfaceError> {
559 let migrated = if self.upload_if_needed()?.is_some() {
560 registry.register_account(
561 self.as_instance(),
562 ::account::contract::CONTRACT_VERSION.to_string(),
563 )?;
564 true
565 } else {
566 false
567 };
568
569 Ok(migrated)
570 }
571
572 pub fn upgrade_account(
575 &self,
576 abstract_deployment: &Abstract<Chain>,
577 ) -> Result<bool, AbstractInterfaceError> {
578 let mut one_migration_was_successful = false;
579
580 {
582 let mut sub_account_ids = vec![];
583 let mut start_after = None;
584 loop {
585 let sub_account_ids_page = self.sub_account_ids(None, start_after)?.sub_accounts;
586
587 start_after = sub_account_ids_page.last().cloned();
588 if sub_account_ids_page.is_empty() {
589 break;
590 }
591 sub_account_ids.extend(sub_account_ids_page);
592 }
593 for sub_account_id in sub_account_ids {
594 let abstract_account =
595 AccountI::load_from(abstract_deployment, AccountId::local(sub_account_id))?;
596 if abstract_account.upgrade_account(abstract_deployment)? {
597 one_migration_was_successful = true;
598 }
599 }
600 }
601
602 loop {
604 if self.upgrade_next_module_version(ACCOUNT)?.is_none() {
605 break;
606 }
607 one_migration_was_successful = true;
608 }
609
610 Ok(one_migration_was_successful)
611 }
612
613 fn upgrade_next_module_version(
616 &self,
617 module_id: &str,
618 ) -> Result<Option<Chain::Response>, AbstractInterfaceError> {
619 let chain = self.environment().clone();
620
621 let current_cw2_module_version: ContractVersion = if module_id == ACCOUNT {
623 let current_account_version = chain
624 .wasm_querier()
625 .raw_query(&self.address()?, CONTRACT.as_slice().to_vec())
626 .unwrap();
627 from_json(current_account_version)?
628 } else {
629 self.module_versions(vec![module_id.to_string()])?.versions[0].clone()
630 };
631 let current_module_version = Version::parse(¤t_cw2_module_version.version)?;
632
633 let module = ModuleInfo::from_id(module_id, current_module_version.to_string().into())?;
634
635 let abstr = Abstract::load_from(chain.clone())?;
637 let all_next_module_versions = abstr
638 .registry
639 .module_list(
640 Some(ModuleFilter {
641 namespace: Some(module.namespace.to_string()),
642 name: Some(module.name.clone()),
643 version: None,
644 status: Some(ModuleStatus::Registered),
645 }),
646 None,
647 Some(module.clone()),
648 )?
649 .modules
650 .into_iter()
651 .filter_map(|module| {
652 let version: Version = module.module.info.version.clone().try_into().unwrap();
653 if version > current_module_version {
655 Some(version)
656 } else {
657 None
658 }
659 })
660 .collect::<Vec<_>>();
661
662 let requirement = VersionReq::parse(current_module_version.to_string().as_str())?;
668
669 let non_compatible_versions = all_next_module_versions
671 .iter()
672 .filter(|version| !requirement.matches(version))
673 .collect::<Vec<_>>();
674
675 let maybe_min_non_compatible_version = non_compatible_versions.iter().min().cloned();
676
677 let selected_version = if let Some(min_non_compatible_version) =
678 maybe_min_non_compatible_version
679 {
680 let requirement = VersionReq::parse(min_non_compatible_version.to_string().as_str())?;
683
684 non_compatible_versions
685 .into_iter()
686 .filter(|version| requirement.matches(version))
687 .max()
688 .unwrap()
689 .clone()
690 } else {
691 let possible_version = all_next_module_versions
693 .into_iter()
694 .filter(|version| version != ¤t_module_version)
695 .max();
696
697 if possible_version.is_none() {
699 return Ok(None);
700 }
701 possible_version.unwrap()
702 };
703
704 Some(self.upgrade(vec![(
706 ModuleInfo::from_id(
707 module_id,
708 ModuleVersion::Version(selected_version.to_string()),
709 )?,
710 Some(to_json_binary(&Empty {})?),
711 )]))
712 .transpose()
713 .map_err(Into::into)
714 }
715
716 pub fn claim_namespace(
717 &self,
718 namespace: impl Into<String>,
719 ) -> Result<Chain::Response, AbstractInterfaceError> {
720 let abstr = Abstract::load_from(self.environment().clone())?;
721 abstr
722 .registry
723 .claim_namespace(self.id()?, namespace.into())
724 .map_err(Into::into)
725 }
726
727 pub fn update_whitelist(
728 &self,
729 to_add: Vec<String>,
730 to_remove: Vec<String>,
731 ) -> Result<(), AbstractInterfaceError> {
732 self.update_internal_config(InternalConfigAction::UpdateWhitelist { to_add, to_remove })?;
733 Ok(())
734 }
735}
736
737impl<Chain: CwEnv> Uploadable for AccountI<Chain> {
738 fn wrapper() -> <Mock as TxHandler>::ContractSource {
739 Box::new(
740 ContractWrapper::new_with_empty(
741 ::account::contract::execute,
742 ::account::contract::instantiate,
743 ::account::contract::query,
744 )
745 .with_migrate(::account::contract::migrate)
746 .with_reply(::account::contract::reply)
747 .with_sudo(::account::contract::sudo),
748 )
749 }
750
751 fn wasm(chain: &ChainInfoOwned) -> WasmPath {
752 artifacts_dir_from_workspace!()
753 .find_wasm_path_with_build_postfix(
754 "account",
755 cw_orch::build::BuildPostfix::ChainName(chain),
756 )
757 .unwrap()
758 }
759}
760
761impl<Chain: CwEnv> std::fmt::Display for AccountI<Chain> {
762 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
763 write!(
764 f,
765 "Account: {:?} ({:?})",
766 self.id(),
767 self.addr_str()
768 .or_else(|_| Result::<_, CwOrchError>::Ok(String::from("unknown"))),
769 )
770 }
771}
772
773impl<Chain: CwEnv> Debug for AccountI<Chain> {
774 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
775 write!(f, "Account: {:?}", self.id())
776 }
777}