1use abstract_std::{
2 account::state::{ACCOUNT_MODULES, DEPENDENTS},
3 objects::{
4 dependency::Dependency,
5 module_version::{ModuleData, MODULE},
6 },
7};
8use cosmwasm_std::{Deps, DepsMut, StdError, Storage};
9use semver::{Comparator, Version};
10
11use crate::{
12 contract::AccountResult,
13 error::AccountError,
14 modules::{load_module_addr, MIGRATE_CONTEXT},
15};
16
17pub fn assert_install_requirements(deps: Deps, module_id: &str) -> AccountResult<Vec<Dependency>> {
19 let module_dependencies = load_module_dependencies(deps, module_id)?;
20 assert_dependency_requirements(deps, &module_dependencies, module_id)?;
21 Ok(module_dependencies)
22}
23
24pub fn assert_install_requirements_standalone(
26 deps: Deps,
27 module_id: &str,
28) -> AccountResult<Vec<Dependency>> {
29 let module_dependencies = load_module_dependencies(deps, module_id).unwrap_or_default();
31 assert_dependency_requirements(deps, &module_dependencies, module_id)?;
32 Ok(module_dependencies)
33}
34
35pub fn assert_migrate_requirements(
38 deps: Deps,
39 module_id: &str,
40 new_version: Version,
41) -> AccountResult<()> {
42 let dependents = DEPENDENTS
44 .may_load(deps.storage, module_id)?
45 .unwrap_or_default();
46 let migrating = MIGRATE_CONTEXT.load(deps.storage)?;
47 for dependent_module in dependents {
49 if migrating.iter().any(|(m, _)| m == &dependent_module) {
51 continue;
52 }
53 let dependent_address = ACCOUNT_MODULES.load(deps.storage, &dependent_module)?;
54 let module_data = MODULE.query(&deps.querier, dependent_address)?;
55 let mut applicable_bounds = module_data
57 .dependencies
58 .iter()
59 .filter(|dep| dep.id == module_id);
60 applicable_bounds.try_for_each(|dep| {
62 assert_comparators(&dep.version_req, &new_version, module_id, false)
63 })?;
64 }
65 Ok(())
66}
67
68pub fn set_as_dependent(
73 store: &mut dyn Storage,
74 module_id: String,
75 dependencies: Vec<Dependency>,
76) -> AccountResult<()> {
77 for dep in dependencies {
78 DEPENDENTS.update(store, &dep.id, |dependents| {
79 let mut dependents = dependents.unwrap_or_default();
80 dependents.insert(module_id.clone());
81 Ok::<_, StdError>(dependents)
82 })?;
83 }
84 Ok(())
85}
86
87pub fn remove_as_dependent(
91 store: &mut dyn Storage,
92 module_id: &str,
93 dependencies: Vec<Dependency>,
94) -> AccountResult<()> {
95 for dep in dependencies {
96 DEPENDENTS.update(store, &dep.id, |dependents| {
97 let mut dependents = dependents.unwrap_or_default();
98 dependents.remove(module_id);
99 Ok::<_, StdError>(dependents)
100 })?;
101 }
102 Ok(())
103}
104
105fn assert_comparators(
106 bounds: &[Comparator],
107 version: &Version,
108 module_id: &str,
109 post_migration: bool,
110) -> AccountResult<()> {
111 bounds.iter().try_for_each(|comp: &Comparator| {
113 if comp.matches(version) {
114 Ok(())
115 } else {
116 Err(AccountError::VersionRequirementNotMet {
117 module_id: module_id.to_string(),
118 version: version.to_string(),
119 comp: comp.to_string(),
120 post_migration,
121 })
122 }
123 })?;
124 Ok(())
125}
126
127pub fn assert_dependency_requirements(
131 deps: Deps,
132 dependencies: &[Dependency],
133 dependent: &str,
134) -> AccountResult<()> {
135 for dep in dependencies {
136 let dep_addr = ACCOUNT_MODULES
137 .may_load(deps.storage, &dep.id)?
138 .ok_or_else(|| AccountError::DependencyNotMet(dep.id.clone(), dependent.to_string()))?;
139
140 let dep_version = cw2::CONTRACT.query(&deps.querier, dep_addr)?;
141 let version: Version = dep_version.version.parse().unwrap();
142 assert_comparators(&dep.version_req, &version, &dep.id, true)?;
144 }
145 Ok(())
146}
147
148pub fn load_module_data(deps: Deps, module_id: &str) -> AccountResult<ModuleData> {
149 let querier = &deps.querier;
150 let module_addr = load_module_addr(deps.storage, module_id)?;
151 let module_data = MODULE.query(querier, module_addr)?;
152 Ok(module_data)
153}
154
155pub fn load_module_dependencies(deps: Deps, module_id: &str) -> AccountResult<Vec<Dependency>> {
156 let module_data = load_module_data(deps, module_id)?;
157 Ok(module_data.dependencies)
158}
159
160pub fn maybe_remove_old_deps(
161 deps: DepsMut,
162 module_id: &str,
163 old_deps: &[Dependency],
164) -> AccountResult<()> {
165 let new_deps = load_module_dependencies(deps.as_ref(), module_id)?;
166 let removable_deps: Vec<&Dependency> =
169 old_deps.iter().filter(|d| !new_deps.contains(d)).collect();
170 for dep_to_remove in removable_deps {
171 DEPENDENTS.update(deps.storage, &dep_to_remove.id, |dependents| {
173 let mut dependents = dependents.unwrap();
175 dependents.remove(module_id);
176 Ok::<_, StdError>(dependents)
177 })?;
178 }
179 Ok(())
180}
181
182pub fn maybe_add_new_deps(
183 deps: DepsMut,
184 module_id: &str,
185 old_deps: &[Dependency],
186) -> AccountResult<Vec<Dependency>> {
187 let new_deps = load_module_dependencies(deps.as_ref(), module_id)?;
188 let to_be_added_deps: Vec<&Dependency> =
191 new_deps.iter().filter(|d| !old_deps.contains(d)).collect();
192 for dep_to_add in &to_be_added_deps {
193 DEPENDENTS.update(deps.storage, &dep_to_add.id, |dependents| {
196 let mut dependents = dependents.unwrap_or_default();
198 dependents.insert(module_id.to_string());
199 Ok::<_, StdError>(dependents)
200 })?;
201 }
202 Ok(new_deps)
203}
204
205#[cfg(test)]
206mod test {
207 #![allow(clippy::needless_borrows_for_generic_args)]
208 use std::collections::HashSet;
209
210 use cosmwasm_std::testing::mock_dependencies;
211
212 use super::*;
213
214 mod set_as_dependent {
215 use super::*;
216
217 #[coverage_helper::test]
219 fn add() {
220 let mut deps = mock_dependencies();
221 let new_module_id = "module";
222
223 let dependency = "dependency";
224 let dependencies = vec![Dependency {
225 id: dependency.to_string(),
226 version_req: vec![],
228 }];
229
230 set_as_dependent(&mut deps.storage, new_module_id.to_string(), dependencies).unwrap();
231
232 let dependents = DEPENDENTS.load(&deps.storage, dependency).unwrap();
233
234 assert_eq!(dependents.len(), 1);
235 assert!(dependents.contains(new_module_id));
236 }
237 }
238
239 mod remove_as_dependent {
240 use super::*;
241
242 fn initialize_dependents(deps: DepsMut, module_id: &str, dependents: Vec<String>) {
243 DEPENDENTS
244 .save(deps.storage, module_id, &HashSet::from_iter(dependents))
245 .unwrap();
246 }
247
248 #[coverage_helper::test]
252 fn remove() {
253 let mut deps = mock_dependencies();
254
255 let dex_adapter = "dex";
256 let autocompounder = "autocompounder";
257
258 initialize_dependents(deps.as_mut(), dex_adapter, vec![autocompounder.to_string()]);
260
261 let actual_dex_dependents = DEPENDENTS.load(&deps.storage, dex_adapter).unwrap();
262 assert_eq!(actual_dex_dependents.len(), 1);
263 assert!(actual_dex_dependents.contains(autocompounder));
264
265 let autocompounder_dependencies = vec![Dependency {
267 id: dex_adapter.to_string(),
268 version_req: vec![],
270 }];
271
272 let res = remove_as_dependent(
273 &mut deps.storage,
274 autocompounder,
275 autocompounder_dependencies,
276 );
277
278 assert!(res.is_ok());
279
280 let remaining_dex_dependents = DEPENDENTS.load(&deps.storage, dex_adapter).unwrap();
281 assert!(remaining_dex_dependents.is_empty());
282 }
283 }
284}