abstract_interface/
account.rs

1//! # Functionality we want to implement on an `Account`
2//!
3//! ## Queries
4//! - module address
5//! - module asserts
6//! - account balance
7//! - account asserts
8//! ## Actions
9//! - get
10//! - install module
11//! - uninstall module
12//! - upgrade module
13
14use 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/// A helper struct that contains fields from [`abstract_std::account::state::AccountInfo`]
39#[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    /// Create account, `b"abstract_account"` used as a salt
72    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        // Generate salt from account id(or)
81        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
138// Module related operations
139impl<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        // this should check if installed?
158        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    /// Assert that the Account has the expected modules with the provided **expected_module_addrs** installed.
208    /// Returns the `Vec<AccountModuleInfo>` from the account
209    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 that these modules are installed
228        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    /// Checks that the account's whitelist includes the expected module addresses.
242    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        // check account config
251        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    /// Installs an adapter from an adapter object
263    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    /// Installs an app from an app object
272    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        // retrieve the deployment
279        self.install_module_parse_addr(module, Some(&custom_init_msg), funds)
280    }
281
282    /// Installs an standalone from an standalone object
283    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        // retrieve the deployment
290        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    /// Return the module info installed on the account
345    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    /// Get the address of a module
357    /// Will err when not installed.
358    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
368// Remote accounts related operations
369impl<Chain: CwEnv> AccountI<Chain> {
370    /// Helper to create remote accounts
371    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            // Unused fields
402            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    /// Execute action on remote module.
453    /// Funds attached from remote account to the module
454    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    /// Register the account core contracts in the registry
498    pub fn register(
499        &self,
500        registry: &Registry<Chain>,
501    ) -> Result<(), crate::AbstractInterfaceError> {
502        registry.register_base(self)
503    }
504
505    /// Gets the account ID of the
506    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    // Parse account from events
538    // It's restricted to parse 1 account at a time
539    pub(crate) fn from_tx_response(
540        chain: &Chain,
541        result: <Chain as TxHandler>::Response,
542    ) -> Result<AccountI<Chain>, crate::AbstractInterfaceError> {
543        // Parse data from events
544        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        // set addresses
549        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    /// Attempts to upgrade the Account
573    /// returns `true` if any migrations were performed.
574    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        // upgrade sub accounts first
581        {
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        // We upgrade the account to the latest version through all the versions
603        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    /// Attempt to upgrade a module to its next version.
614    /// Will return `Ok(None)` if the module is on its latest version already.
615    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        // We start by getting the current module version
622        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(&current_cw2_module_version.version)?;
632
633        let module = ModuleInfo::from_id(module_id, current_module_version.to_string().into())?;
634
635        // We query all the module versions above the current one
636        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                // We add this check because the ModuleFilter doesn't work properly with beta versions
654                if version > current_module_version {
655                    Some(version)
656                } else {
657                    None
658                }
659            })
660            .collect::<Vec<_>>();
661
662        // Two cases now.
663        // 1. If there exists a higher non-compatible version, we want to update to the next breaking version
664        // 2. If there are only compatible versions we want to update the highest compatible version
665
666        // Set current version as version requirement (`^x.y.z`)
667        let requirement = VersionReq::parse(current_module_version.to_string().as_str())?;
668
669        // Find out the lowest next major version
670        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            // Case 1
681            // There is a next breaking version, we want to get the highest minor version associated with it
682            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            // Case 2
692            let possible_version = all_next_module_versions
693                .into_iter()
694                .filter(|version| version != &current_module_version)
695                .max();
696
697            // No version upgrade required
698            if possible_version.is_none() {
699                return Ok(None);
700            }
701            possible_version.unwrap()
702        };
703
704        // Actual upgrade to the next version
705        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}