abstract_sdk/apis/
modules.rs1use 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
14pub trait ModuleInterface: AccountIdentification + Dependencies + ModuleIdentification {
16 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#[derive(Clone)]
67pub struct Modules<'a, T: ModuleInterface> {
68 base: &'a T,
69 deps: Deps<'a>,
70}
71
72impl<T: ModuleInterface> Modules<'_, T> {
73 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 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 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}