abstract_core/objects/
module_version.rs

1/*!
2Most of the CW* specs are focused on the *public interfaces*
3of the module. The Adapters used for `ExecuteMsg` or `QueryMsg`.
4However, when we wish to migrate or inspect smart module info,
5we need some form of smart module information embedded on state.
6
7This is where ModuleData comes in. It specifies a special Item to
8be stored on disk by all contracts on `instantiate`.
9
10`ModuleInfo` must be stored under the `"module_info"` key which translates
11to `"636F6E74726163745F696E666F"` in hex format.
12Since the state is well-defined, we do not need to support any "smart queries".
13We do provide a helper to construct a "raw query" to read the ContractInfo
14of any CW2-compliant module.
15
16Additionally, it's worth noting that `ModuleData` is utilized by native
17abstract contracts.
18
19For more information on this specification, please check out the
20[README](https://github.com/CosmWasm/cw-plus/blob/main/packages/cw2/README.md).
21 */
22
23use cosmwasm_std::{
24    ensure, ensure_eq, Empty, Querier, QuerierWrapper, QueryRequest, StdResult, Storage, WasmQuery,
25};
26use cw2::{get_contract_version, ContractVersion};
27use cw_storage_plus::Item;
28use semver::Version;
29use serde::{Deserialize, Serialize};
30
31use super::dependency::{Dependency, DependencyResponse, StaticDependency};
32use crate::AbstractError;
33
34// ANCHOR: metadata
35pub const MODULE: Item<ModuleData> = Item::new("module_data");
36
37/// Represents metadata for abstract modules and abstract native contracts.
38#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
39pub struct ModuleData {
40    /// The name of the module, which should be composed of
41    /// the publisher's namespace and module id. eg. `cw-plus:cw20-base`
42    pub module: String,
43    /// Semantic version of the module's crate on release.
44    /// Is used for migration assertions
45    pub version: String,
46    /// List of modules that this module depends on
47    /// along with its version requirements.
48    pub dependencies: Vec<Dependency>,
49    /// URL to data that follows the Abstract metadata standard for
50    /// resolving off-chain module information.
51    pub metadata: Option<String>,
52}
53// ANCHOR_END: metadata
54
55#[cosmwasm_schema::cw_serde]
56pub struct ModuleDataResponse {
57    pub module_id: String,
58    pub version: String,
59    pub dependencies: Vec<DependencyResponse>,
60    pub metadata: Option<String>,
61}
62
63/// set_module_version should be used in instantiate to store the original version, and after a successful
64/// migrate to update it
65pub fn set_module_data<T: Into<String>, U: Into<String>, M: Into<String>>(
66    store: &mut dyn Storage,
67    name: T,
68    version: U,
69    dependencies: &[StaticDependency],
70    metadata: Option<M>,
71) -> StdResult<()> {
72    let val = ModuleData {
73        module: name.into(),
74        version: version.into(),
75        dependencies: dependencies.iter().map(Into::into).collect(),
76        metadata: metadata.map(Into::into),
77    };
78    MODULE.save(store, &val).map_err(Into::into)
79}
80
81/// Assert that the new version is greater than the stored version.
82pub fn assert_contract_upgrade(
83    storage: &dyn Storage,
84    to_contract: impl ToString,
85    to_version: Version,
86) -> Result<(), AbstractError> {
87    let ContractVersion {
88        version: from_version,
89        contract,
90    } = get_contract_version(storage)?;
91
92    let to_contract = to_contract.to_string();
93
94    // Must be the same contract
95    ensure_eq!(
96        contract,
97        to_contract,
98        AbstractError::ContractNameMismatch {
99            from: contract,
100            to: to_contract,
101        }
102    );
103
104    let from_version = from_version.parse().unwrap();
105
106    // Must be a version upgrade
107    ensure!(
108        to_version > from_version,
109        AbstractError::CannotDowngradeContract {
110            contract,
111            from: from_version,
112            to: to_version,
113        }
114    );
115    // Must be 1 major or 1 minor version bump, not more
116    // Patches we ignore
117    let major_diff = to_version.major.checked_sub(from_version.major);
118    let minor_diff = to_version.minor.checked_sub(from_version.minor);
119    let no_skips = match (major_diff, minor_diff) {
120        // 1) major upgrade - minor should stay the same (1.0.0 -> 2.0.0)
121        // 2) major upgrade - minor sub overflowed (0.1.0 -> 1.0.0)
122        (Some(1), _) if to_version.minor == 0 => true,
123        // minor upgrade - major should stay the same (1.0.0 -> 1.1.0)
124        (Some(0), Some(1)) => true,
125        // patch upgrade - minor and major stays the same (1.0.0 -> 1.0.1)
126        (Some(0), Some(0)) => true,
127        _ => false,
128    };
129    ensure!(
130        no_skips,
131        AbstractError::CannotSkipVersion {
132            contract,
133            from: from_version,
134            to: to_version,
135        }
136    );
137    Ok(())
138}
139
140/// Assert that the new version is greater than the stored version.
141pub fn assert_cw_contract_upgrade(
142    storage: &dyn Storage,
143    to_contract: impl ToString,
144    to_version: cw_semver::Version,
145) -> Result<(), AbstractError> {
146    assert_contract_upgrade(
147        storage,
148        to_contract,
149        to_version.to_string().parse().unwrap(),
150    )
151}
152
153/// Migrate the module data to the new state.
154/// If there was no moduleData stored, it will be set to the given values with an empty dependency array.
155/// If the metadata is `None`, the old metadata will be kept.
156/// If the metadata is `Some`, the old metadata will be overwritten.
157pub fn migrate_module_data(
158    store: &mut dyn Storage,
159    name: &str,
160    version: &str,
161    metadata: Option<String>,
162) -> StdResult<()> {
163    let old_module_data = MODULE.may_load(store)?;
164    let val = old_module_data.map_or(
165        ModuleData {
166            module: name.into(),
167            version: version.into(),
168            dependencies: vec![],
169            metadata: None,
170        },
171        |data| ModuleData {
172            module: name.into(),
173            version: version.into(),
174            dependencies: data.dependencies,
175            metadata: metadata.or(data.metadata),
176        },
177    );
178
179    MODULE.save(store, &val).map_err(Into::into)
180}
181
182/// This will make a raw_query to another module to determine the current version it
183/// claims to be. This should not be trusted, but could be used as a quick filter
184/// if the other module exists and claims to be a cw20-base module for example.
185/// (Note: you usually want to require *interfaces* not *implementations* of the
186/// contracts you compose with, so be careful of overuse)
187pub fn query_module_data<Q: Querier, T: Into<String>>(
188    querier: &Q,
189    contract_addr: T,
190) -> StdResult<ModuleData> {
191    let req = QueryRequest::Wasm(WasmQuery::Raw {
192        contract_addr: contract_addr.into(),
193        key: MODULE.as_slice().into(),
194    });
195    QuerierWrapper::<Empty>::new(querier)
196        .query(&req)
197        .map_err(Into::into)
198}
199
200#[cfg(test)]
201mod tests {
202    use cosmwasm_std::testing::MockStorage;
203
204    use super::*;
205
206    #[test]
207    fn set_works() {
208        let mut store = MockStorage::new();
209
210        // set and get
211        let contract_name = "crate:cw20-base";
212        let contract_version = "0.2.0";
213        let metadata = Some("https://example.com");
214        const REQUIREMENT: [&str; 1] = [">1"];
215
216        const DEPENDENCIES: &[StaticDependency; 1] = &[StaticDependency {
217            id: "abstact::dex",
218            version_req: &REQUIREMENT,
219        }];
220        set_module_data(
221            &mut store,
222            contract_name,
223            contract_version,
224            DEPENDENCIES,
225            metadata,
226        )
227        .unwrap();
228
229        let loaded = MODULE.load(&store).unwrap();
230        let expected = ModuleData {
231            module: contract_name.to_string(),
232            version: contract_version.to_string(),
233            dependencies: DEPENDENCIES.iter().map(Into::into).collect(),
234            metadata: metadata.map(Into::into),
235        };
236        assert_eq!(expected, loaded);
237    }
238
239    #[test]
240    fn module_upgrade() {
241        let mut store = MockStorage::new();
242        let contract_name = "abstract:manager";
243        let contract_version = "0.19.2";
244        cw2::CONTRACT
245            .save(
246                &mut store,
247                &ContractVersion {
248                    contract: contract_name.to_owned(),
249                    version: contract_version.to_owned(),
250                },
251            )
252            .unwrap();
253
254        // Patch upgrade
255        let to_version = "0.19.3".parse().unwrap();
256        let res = assert_contract_upgrade(&store, contract_name, to_version);
257        assert!(res.is_ok());
258
259        // Minor upgrade
260        let to_version = "0.20.0".parse().unwrap();
261        let res = assert_contract_upgrade(&store, contract_name, to_version);
262        assert!(res.is_ok());
263
264        // Minor with patch upgrade
265        let to_version = "0.20.1".parse().unwrap();
266        let res = assert_contract_upgrade(&store, contract_name, to_version);
267        assert!(res.is_ok());
268
269        // Major upgrade
270        let to_version = "1.0.0".parse().unwrap();
271        let res = assert_contract_upgrade(&store, contract_name, to_version);
272        assert!(res.is_ok());
273    }
274
275    #[test]
276    fn module_upgrade_err() {
277        let mut store = MockStorage::new();
278        let contract_name = "abstract:manager";
279        let contract_version = "0.19.2";
280        cw2::CONTRACT
281            .save(
282                &mut store,
283                &ContractVersion {
284                    contract: contract_name.to_owned(),
285                    version: contract_version.to_owned(),
286                },
287            )
288            .unwrap();
289
290        // Downgrade
291        let to_version: Version = "0.19.1".parse().unwrap();
292        let err = assert_contract_upgrade(&store, contract_name, to_version.clone()).unwrap_err();
293        assert_eq!(
294            err,
295            AbstractError::CannotDowngradeContract {
296                contract: contract_name.to_string(),
297                from: contract_version.parse().unwrap(),
298                to: to_version
299            }
300        );
301
302        // Minor upgrade
303        let to_version: Version = "0.21.0".parse().unwrap();
304        let err = assert_contract_upgrade(&store, contract_name, to_version.clone()).unwrap_err();
305        assert_eq!(
306            err,
307            AbstractError::CannotSkipVersion {
308                contract: contract_name.to_string(),
309                from: contract_version.parse().unwrap(),
310                to: to_version
311            }
312        );
313
314        // Major upgrade
315        let to_version: Version = "2.0.0".parse().unwrap();
316        let err = assert_contract_upgrade(&store, contract_name, to_version.clone()).unwrap_err();
317        assert_eq!(
318            err,
319            AbstractError::CannotSkipVersion {
320                contract: contract_name.to_string(),
321                from: contract_version.parse().unwrap(),
322                to: to_version
323            }
324        );
325    }
326}