abstract_std/objects/
module_version.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
/*!
Most of the CW* specs are focused on the *public interfaces*
of the module. The Adapters used for `ExecuteMsg` or `QueryMsg`.
However, when we wish to migrate or inspect smart module info,
we need some form of smart module information embedded on state.

This is where ModuleData comes in. It specifies a special Item to
be stored on disk by all contracts on `instantiate`.

`ModuleInfo` must be stored under the `"module_info"` key which translates
to `"636F6E74726163745F696E666F"` in hex format.
Since the state is well-defined, we do not need to support any "smart queries".
We do provide a helper to construct a "raw query" to read the ContractInfo
of any CW2-compliant module.

Additionally, it's worth noting that `ModuleData` is utilized by native
abstract contracts.

For more information on this specification, please check out the
[README](https://github.com/CosmWasm/cw-plus/blob/main/packages/cw2/README.md).
 */

use cosmwasm_std::{
    ensure, ensure_eq, Empty, Querier, QuerierWrapper, QueryRequest, StdResult, Storage, WasmQuery,
};
use cw2::{get_contract_version, ContractVersion};
use cw_storage_plus::Item;
use semver::Version;
use serde::{Deserialize, Serialize};

use super::{
    dependency::{Dependency, DependencyResponse, StaticDependency},
    storage_namespaces::MODULE_STORAGE_KEY,
};
use crate::AbstractError;

// ANCHOR: metadata
pub const MODULE: Item<ModuleData> = Item::new(MODULE_STORAGE_KEY);

/// Represents metadata for abstract modules and abstract native contracts.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ModuleData {
    /// The name of the module, which should be composed of
    /// the publisher's namespace and module id. eg. `cw-plus:cw20-base`
    pub module: String,
    /// Semantic version of the module's crate on release.
    /// Is used for migration assertions
    pub version: String,
    /// List of modules that this module depends on
    /// along with its version requirements.
    pub dependencies: Vec<Dependency>,
    /// URL to data that follows the Abstract metadata standard for
    /// resolving off-chain module information.
    pub metadata: Option<String>,
}
// ANCHOR_END: metadata

#[cosmwasm_schema::cw_serde]
pub struct ModuleDataResponse {
    pub module_id: String,
    pub version: String,
    pub dependencies: Vec<DependencyResponse>,
    pub metadata: Option<String>,
}

/// set_module_version should be used in instantiate to store the original version, and after a successful
/// migrate to update it
pub fn set_module_data<T: Into<String>, U: Into<String>, M: Into<String>>(
    store: &mut dyn Storage,
    name: T,
    version: U,
    dependencies: &[StaticDependency],
    metadata: Option<M>,
) -> StdResult<()> {
    let val = ModuleData {
        module: name.into(),
        version: version.into(),
        dependencies: dependencies.iter().map(Into::into).collect(),
        metadata: metadata.map(Into::into),
    };
    MODULE.save(store, &val).map_err(Into::into)
}

/// Assert that the new version is greater than the stored version.
pub fn assert_contract_upgrade(
    storage: &dyn Storage,
    to_contract: impl ToString,
    to_version: Version,
) -> Result<(), AbstractError> {
    let ContractVersion {
        version: from_version,
        contract,
    } = get_contract_version(storage)?;

    let to_contract = to_contract.to_string();

    // Must be the same contract
    ensure_eq!(
        contract,
        to_contract,
        AbstractError::ContractNameMismatch {
            from: contract,
            to: to_contract,
        }
    );

    let from_version = from_version.parse().unwrap();

    // Must be a version upgrade
    ensure!(
        to_version > from_version,
        AbstractError::CannotDowngradeContract {
            contract,
            from: from_version,
            to: to_version,
        }
    );
    // Must be 1 major or 1 minor version bump, not more
    // Patches we ignore
    let major_diff = to_version.major.checked_sub(from_version.major);
    let minor_diff = to_version.minor.checked_sub(from_version.minor);
    let no_skips = match (major_diff, minor_diff) {
        // 1) major upgrade - minor should stay the same (1.0.0 -> 2.0.0)
        // 2) major upgrade - minor sub overflowed (0.1.0 -> 1.0.0)
        (Some(1), _) => true,
        // minor upgrade - major should stay the same (1.0.0 -> 1.1.0)
        (Some(0), Some(1)) => true,
        // patch upgrade - minor and major stays the same (1.0.0 -> 1.0.1)
        (Some(0), Some(0)) => true,
        _ => false,
    };
    ensure!(
        no_skips,
        AbstractError::CannotSkipVersion {
            contract,
            from: from_version,
            to: to_version,
        }
    );
    Ok(())
}

/// Assert that the new version is greater than the stored version.
pub fn assert_cw_contract_upgrade(
    storage: &dyn Storage,
    to_contract: impl ToString,
    to_version: semver::Version,
) -> Result<(), AbstractError> {
    assert_contract_upgrade(
        storage,
        to_contract,
        to_version.to_string().parse().unwrap(),
    )
}

/// Migrate the module data to the new state.
/// If there was no moduleData stored, it will be set to the given values with an empty dependency array.
/// If the metadata is `None`, the old metadata will be kept.
/// If the metadata is `Some`, the old metadata will be overwritten.
pub fn migrate_module_data(
    store: &mut dyn Storage,
    name: &str,
    version: &str,
    metadata: Option<String>,
) -> StdResult<()> {
    let old_module_data = MODULE.may_load(store)?;
    let val = old_module_data.map_or(
        ModuleData {
            module: name.into(),
            version: version.into(),
            dependencies: vec![],
            metadata: None,
        },
        |data| ModuleData {
            module: name.into(),
            version: version.into(),
            dependencies: data.dependencies,
            metadata: metadata.or(data.metadata),
        },
    );

    MODULE.save(store, &val).map_err(Into::into)
}

/// This will make a raw_query to another module to determine the current version it
/// claims to be. This should not be trusted, but could be used as a quick filter
/// if the other module exists and claims to be a cw20-base module for example.
/// (Note: you usually want to require *interfaces* not *implementations* of the
/// contracts you compose with, so be careful of overuse)
pub fn query_module_data<Q: Querier, T: Into<String>>(
    querier: &Q,
    contract_addr: T,
) -> StdResult<ModuleData> {
    let req = QueryRequest::Wasm(WasmQuery::Raw {
        contract_addr: contract_addr.into(),
        key: MODULE.as_slice().into(),
    });
    QuerierWrapper::<Empty>::new(querier)
        .query(&req)
        .map_err(Into::into)
}

#[cfg(test)]
mod tests {
    use cosmwasm_std::testing::MockStorage;

    use super::*;

    #[coverage_helper::test]
    fn set_works() {
        let mut store = MockStorage::new();

        // set and get
        let contract_name = "crate:cw20-base";
        let contract_version = "0.2.0";
        let metadata = Some("https://example.com");
        const REQUIREMENT: [&str; 1] = [">1"];

        const DEPENDENCIES: &[StaticDependency; 1] = &[StaticDependency {
            id: "abstact::dex",
            version_req: &REQUIREMENT,
        }];
        set_module_data(
            &mut store,
            contract_name,
            contract_version,
            DEPENDENCIES,
            metadata,
        )
        .unwrap();

        let loaded = MODULE.load(&store).unwrap();
        let expected = ModuleData {
            module: contract_name.to_string(),
            version: contract_version.to_string(),
            dependencies: DEPENDENCIES.iter().map(Into::into).collect(),
            metadata: metadata.map(Into::into),
        };
        assert_eq!(expected, loaded);
    }

    #[coverage_helper::test]
    fn module_upgrade() {
        let mut store = MockStorage::new();
        let contract_name = "abstract:account";
        let contract_version = "0.19.2";
        cw2::CONTRACT
            .save(
                &mut store,
                &ContractVersion {
                    contract: contract_name.to_owned(),
                    version: contract_version.to_owned(),
                },
            )
            .unwrap();

        // Patch upgrade
        let to_version = "0.19.3".parse().unwrap();
        let res = assert_contract_upgrade(&store, contract_name, to_version);
        assert!(res.is_ok());

        // Minor upgrade
        let to_version = "0.20.0".parse().unwrap();
        let res = assert_contract_upgrade(&store, contract_name, to_version);
        assert!(res.is_ok());

        // Minor with patch upgrade
        let to_version = "0.20.1".parse().unwrap();
        let res = assert_contract_upgrade(&store, contract_name, to_version);
        assert!(res.is_ok());

        // Major upgrade
        let to_version = "1.0.0".parse().unwrap();
        let res = assert_contract_upgrade(&store, contract_name, to_version);
        assert!(res.is_ok());
    }

    #[coverage_helper::test]
    fn module_upgrade_err() {
        let mut store = MockStorage::new();
        let contract_name = "abstract:account";
        let contract_version = "0.19.2";
        cw2::CONTRACT
            .save(
                &mut store,
                &ContractVersion {
                    contract: contract_name.to_owned(),
                    version: contract_version.to_owned(),
                },
            )
            .unwrap();

        // Downgrade
        let to_version: Version = "0.19.1".parse().unwrap();
        let err = assert_contract_upgrade(&store, contract_name, to_version.clone()).unwrap_err();
        assert_eq!(
            err,
            AbstractError::CannotDowngradeContract {
                contract: contract_name.to_string(),
                from: contract_version.parse().unwrap(),
                to: to_version
            }
        );

        // Minor upgrade
        let to_version: Version = "0.21.0".parse().unwrap();
        let err = assert_contract_upgrade(&store, contract_name, to_version.clone()).unwrap_err();
        assert_eq!(
            err,
            AbstractError::CannotSkipVersion {
                contract: contract_name.to_string(),
                from: contract_version.parse().unwrap(),
                to: to_version
            }
        );

        // Major upgrade
        let to_version: Version = "2.0.0".parse().unwrap();
        let err = assert_contract_upgrade(&store, contract_name, to_version.clone()).unwrap_err();
        assert_eq!(
            err,
            AbstractError::CannotSkipVersion {
                contract: contract_name.to_string(),
                from: contract_version.parse().unwrap(),
                to: to_version
            }
        );
    }
}