abstract_sdk/apis/
modules.rs

1//! # Module
2//! The Module interface provides helper functions to execute functions on other modules installed on the Account.
3
4use abstract_std::{account::state::ACCOUNT_MODULES, objects::module::ModuleId};
5use cosmwasm_std::{Addr, Deps, QueryRequest, WasmQuery};
6use cw2::{ContractVersion, CONTRACT};
7
8use super::AbstractApi;
9use crate::{
10    features::{AccountIdentification, Dependencies, ModuleIdentification},
11    AbstractSdkResult,
12};
13
14/// Interact with other modules on the Account.
15pub trait ModuleInterface: AccountIdentification + Dependencies + ModuleIdentification {
16    /**
17        API for retrieving information about installed modules.
18
19        # Example
20        ```
21        use abstract_sdk::prelude::*;
22        # use cosmwasm_std::testing::mock_dependencies;
23        # use abstract_sdk::mock_module::MockModule;
24        # use abstract_testing::prelude::*;
25        # let deps = mock_dependencies();
26        # let account = admin_account(deps.api);
27        # let module = MockModule::new(deps.api, account);
28
29        let modules: Modules<MockModule>  = module.modules(deps.as_ref());
30        ```
31    */
32    fn modules<'a>(&'a self, deps: Deps<'a>) -> Modules<'a, Self> {
33        Modules { base: self, deps }
34    }
35}
36
37impl<T> ModuleInterface for T where T: AccountIdentification + Dependencies + ModuleIdentification {}
38
39impl<T: ModuleInterface> AbstractApi<T> for Modules<'_, T> {
40    const API_ID: &'static str = "Modules";
41
42    fn base(&self) -> &T {
43        self.base
44    }
45    fn deps(&self) -> Deps {
46        self.deps
47    }
48}
49
50/**
51    API for retrieving information about installed modules.
52
53    # Example
54    ```
55    use abstract_sdk::prelude::*;
56    # use cosmwasm_std::testing::mock_dependencies;
57    # use abstract_sdk::mock_module::MockModule;
58    # use abstract_testing::prelude::*;
59    # let deps = mock_dependencies();
60    # let account = admin_account(deps.api);
61    # let module = MockModule::new(deps.api, account);
62
63    let modules: Modules<MockModule>  = module.modules(deps.as_ref());
64    ```
65*/
66#[derive(Clone)]
67pub struct Modules<'a, T: ModuleInterface> {
68    base: &'a T,
69    deps: Deps<'a>,
70}
71
72impl<T: ModuleInterface> Modules<'_, T> {
73    /// Retrieve the address of an application in this Account.
74    /// This should **not** be used to execute messages on an `Api`.
75    /// Use `Modules::api_request(..)` instead.
76    pub fn module_address(&self, module_id: ModuleId) -> AbstractSdkResult<Addr> {
77        let account_addr = self.base.account(self.deps)?;
78        let maybe_module_addr =
79            ACCOUNT_MODULES.query(&self.deps.querier, account_addr.into_addr(), module_id)?;
80        let Some(module_addr) = maybe_module_addr else {
81            return Err(crate::AbstractSdkError::MissingModule {
82                module: module_id.to_string(),
83            });
84        };
85        Ok(module_addr)
86    }
87
88    /// Retrieve the version of an application in this Account.
89    /// Note: this method makes use of the Cw2 query and may not coincide with the version of the
90    /// module listed in Registry.
91    pub fn module_version(&self, module_id: ModuleId) -> AbstractSdkResult<ContractVersion> {
92        let module_address = self.module_address(module_id)?;
93        let req = QueryRequest::Wasm(WasmQuery::Raw {
94            contract_addr: module_address.into(),
95            key: CONTRACT.as_slice().into(),
96        });
97        self.deps
98            .querier
99            .query::<ContractVersion>(&req)
100            .map_err(Into::into)
101    }
102
103    /// Assert that a module is a dependency of this module.
104    pub fn assert_module_dependency(&self, module_id: ModuleId) -> AbstractSdkResult<()> {
105        let is_dependency = Dependencies::dependencies(self.base)
106            .iter()
107            .map(|d| d.id)
108            .any(|x| x == module_id);
109
110        match is_dependency {
111            true => Ok(()),
112            false => Err(crate::AbstractSdkError::MissingDependency {
113                module: module_id.to_string(),
114            }),
115        }
116    }
117}
118
119#[cfg(test)]
120mod test {
121    #![allow(clippy::needless_borrows_for_generic_args)]
122    use abstract_testing::prelude::*;
123
124    use super::*;
125    use crate::{apis::traits::test::abstract_api_test, mock_module::*};
126
127    mod assert_module_dependency {
128        use super::*;
129
130        #[coverage_helper::test]
131        fn should_return_ok_if_dependency() {
132            let (deps, _, app) = mock_module_setup();
133
134            let mods = app.modules(deps.as_ref());
135
136            let res = mods.assert_module_dependency(TEST_MODULE_ID);
137            assert!(res.is_ok());
138        }
139
140        #[coverage_helper::test]
141        fn should_return_err_if_not_dependency() {
142            let (deps, _, app) = mock_module_setup();
143
144            let mods = app.modules(deps.as_ref());
145
146            let fake_module = "lol_no_chance";
147            let res = mods.assert_module_dependency(fake_module);
148
149            assert!(res
150                .unwrap_err()
151                .to_string()
152                .contains(&format!("{fake_module} is not a dependency")));
153        }
154    }
155
156    #[coverage_helper::test]
157    fn abstract_api() {
158        let (deps, _, app) = mock_module_setup();
159        let modules = app.modules(deps.as_ref());
160
161        abstract_api_test(modules);
162    }
163}