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
/*!
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.

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

use super::dependency::{Dependency, DependencyResponse, StaticDependency};
use crate::AbstractError;
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};

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

#[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,
        }
    );
    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: cw_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 super::*;
    use cosmwasm_std::testing::MockStorage;

    #[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);
    }
}