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
/*!
Most of the CW* specs are focused on the *public interfaces*
of the module. The APIs 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 cosmwasm_std::{Empty, Querier, QuerierWrapper, QueryRequest, StdResult, Storage, WasmQuery};
use cw_storage_plus::Item;
use serde::{Deserialize, Serialize};

use super::dependency::{Dependency, StaticDependency};

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

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ModuleData {
    /// module is the crate name of the implementing module, eg. `crate:cw20-base`
    /// we will use other prefixes for other languages, and their standard global namespacing
    pub module: String,
    /// version is any string that this implementation knows. It may be simple counter "1", "2".
    /// or semantic version on release tags "v0.7.0", or some custom feature flag list.
    /// the only code that needs to understand the version parsing is code that knows how to
    /// migrate from the given module (and is tied to it's implementation somehow)
    pub version: String,
    /// dependencies store a 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>,
}

/// get_module_version can be use in migrate to read the previous version of this module
pub fn get_module_data(store: &dyn Storage) -> StdResult<ModuleData> {
    MODULE.load(store)
}

/// 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)
}

/// 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)
}

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

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

        // error if not set
        assert!(get_module_data(&store).is_err());

        // 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 = get_module_data(&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);
    }
}