Skip to main content

abstract_account/
versioning.rs

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
17/// Assert the dependencies that this app or adapter relies on are installed.
18pub 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
24/// Assert the dependencies that this standalone relies on are installed.
25pub fn assert_install_requirements_standalone(
26    deps: Deps,
27    module_id: &str,
28) -> AccountResult<Vec<Dependency>> {
29    // For standalones dependencies in state are optional
30    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
35/// Assert that the new version of this app is supported by its dependents.
36/// Unless that dependent is also being migrated.
37pub fn assert_migrate_requirements(
38    deps: Deps,
39    module_id: &str,
40    new_version: Version,
41) -> AccountResult<()> {
42    // load all the modules that depend on this module
43    let dependents = DEPENDENTS
44        .may_load(deps.storage, module_id)?
45        .unwrap_or_default();
46    let migrating = MIGRATE_CONTEXT.load(deps.storage)?;
47    // for each module that depends on this module, check if it supports the new version.
48    for dependent_module in dependents {
49        // if the dependent is also being migrated, skip it.
50        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        // filter the dependencies and assert version comparison when applicable
56        let mut applicable_bounds = module_data
57            .dependencies
58            .iter()
59            .filter(|dep| dep.id == module_id);
60        // assert bounds
61        applicable_bounds.try_for_each(|dep| {
62            assert_comparators(&dep.version_req, &new_version, module_id, false)
63        })?;
64    }
65    Ok(())
66}
67
68/// Add module as dependent on its dependencies.
69/// For example, Autocompounder depends on dex.
70/// Therefore, autocompounder is added as a dependent on dex.
71/// dex -> Autocompounder
72pub 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
87/// Remove a module as dependent on its dependencies
88/// For example, Autocompounder depends on dex.
89/// We are uninstalling autocompounder, so we remove it from the dependents of dex.
90pub 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    // assert requirements
112    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
127/// Goes over all the provided dependencies and asserts that:
128/// 1. The dependency is installed
129/// 2. The dependency version fits the requirements
130pub 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 requirements
143        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    // find deps that are no longer required.
167    // ie. the old deps contain a deps that the new deps doesn't.
168    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        // Remove module from dependents on the removable dep
172        DEPENDENTS.update(deps.storage, &dep_to_remove.id, |dependents| {
173            // Migrating so hashset should be saved and contain the module ID.
174            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    // find deps that are new.
189    // the new deps contain deps that were not in the old deps.
190    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        // Add module as dependent on the new deps
194        // Will also run when a version requirement is changed.
195        DEPENDENTS.update(deps.storage, &dep_to_add.id, |dependents| {
196            // Adding new dep so might be the first entry, hence default to empty set in that case.
197            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        // This should add dependency -> [module] to the map
218        #[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                // no version requirements
227                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        // autocompounder depends on dex
249        // dex -> autocompounder
250        // to uninstall autocompounder, remove dex
251        #[coverage_helper::test]
252        fn remove() {
253            let mut deps = mock_dependencies();
254
255            let dex_adapter = "dex";
256            let autocompounder = "autocompounder";
257
258            // dex -> autocompounder
259            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            // the autocompounder depends on the dex
266            let autocompounder_dependencies = vec![Dependency {
267                id: dex_adapter.to_string(),
268                // no version requirements
269                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}