cw22/
lib.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
//! CW22 defines a way for a contract to declare which interfaces do the contract implement
//! This standard is inspired by the EIP-165 from Ethereum. Originally it was proposed to
//! be merged into CW2: Contract Info, then it is splitted to a separated cargo to keep CW2
//! being backward compatible.

//! Each supported interface contains a string value pointing to the corresponding cargo package
//! and a specific release of the package. There is also a function to check whether the contract
//! support a specific version of an interface or not.

//! The version string for each interface follows Semantic Versioning standard. More info is in:
//! https://docs.rs/semver/latest/semver/
use cosmwasm_schema::cw_serde;
use cosmwasm_std::{QuerierWrapper, QueryRequest, StdError, StdResult, Storage, WasmQuery};
use cw_storage_plus::Map;
use semver::{Version, VersionReq};
use std::borrow::Cow;

pub const INTERFACE_NAMESPACE: &str = "supported_interfaces";
pub const SUPPORTED_INTERFACES: Map<&str, String> = Map::new(INTERFACE_NAMESPACE);

#[cw_serde]
pub struct ContractSupportedInterface<'a> {
    /// supported_interface is the name of an interface that the contract support.
    /// This is inspired by the EIP-165 from Ethereum.
    /// Interface names should follow a common standard such as <Registry Domain>:<Crate Name> in Rust crate registry.
    /// e.g. "crates.io:cw2"
    /// NOTE: this is just a hint for the caller to adapt on how to interact with this contract.
    /// There is no guarantee that the contract actually implement these interfaces.
    pub supported_interface: Cow<'a, str>,
    /// semantic version on release tags of the interface package following SemVer guideline.
    /// e.g.  "0.16.0"
    pub version: Cow<'a, str>,
}

/// set_contract_supported_interface should be used in instantiate to store the original version
/// of supported interfaces. It should also be used after every migration.
pub fn set_contract_supported_interface(
    store: &mut dyn Storage,
    supported_interfaces: &[ContractSupportedInterface],
) -> StdResult<()> {
    for item in supported_interfaces {
        let ver = Version::parse(&item.version);
        match ver {
            Ok(_) => {
                SUPPORTED_INTERFACES.save(
                    store,
                    &item.supported_interface,
                    &item.version.to_string(),
                )?;
            }
            Err(_) => {
                return Err(StdError::generic_err("Version's format is invalid"));
            }
        }
    }
    Ok(())
}


pub fn query_supported_interface_version(
    querier: &QuerierWrapper,
    contract_addr: &str, 
    interface_name: &str,
) -> StdResult<Option<String>> {

    let key = cosmwasm_std::storage_keys::namespace_with_key(
        &[INTERFACE_NAMESPACE.as_bytes()], 
        interface_name.as_bytes()
    );

    let raw_query = WasmQuery::Raw { 
        contract_addr: contract_addr.into(),
        key: key.into()
    };

    querier.query(&QueryRequest::Wasm(raw_query))
}



pub fn minimum_version(version: &str, required: &str) -> bool {
    if let Ok(ver) = Version::parse(version) {
        if let Ok(req) = VersionReq::parse(format!(">={}", required).as_str()) {
            return req.matches(&ver);
        }
    }
    false
}

/// query_supported_interface show if contract supports an interface with version following SemVer query
/// query example">=1.2.3, <1.8.0"
pub fn require_version(version: &str, request: &str) -> bool {
    if let Ok(ver) = Version::parse(version) {
        if let Ok(req) = VersionReq::parse(request) {
            return req.matches(&ver);
        }
    }
    false
}

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

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

        let interface2 = "crates.io:cw2";
        let interface22 = "crates.io:cw22";
        let _interface721 = "crates.io:cw721";
        let contract_interface2 = ContractSupportedInterface {
            supported_interface: Cow::Borrowed(interface2),
            version: Cow::from("0.16.0"),
        };
        let contract_interface22 = ContractSupportedInterface {
            supported_interface: Cow::Borrowed(interface22),
            version: "0.1.0".into(),
        };
        let contract_interface721 = ContractSupportedInterface {
            supported_interface: Cow::Borrowed(interface22),
            version: Cow::from("v0.1.0"),
        };

        // set supported_interface error
        let supported_interface = &[contract_interface721];

        let rs_error =
            set_contract_supported_interface(&mut store, supported_interface).unwrap_err();
        let expected = StdError::generic_err("Version's format is invalid");
        assert_eq!(expected, rs_error);

        // set supported_interface
        let supported_interface = &[contract_interface2, contract_interface22];

        set_contract_supported_interface(&mut store, supported_interface).unwrap();
/*         // get version of not supported interface
        let loaded = query_supported_interface_version(&store, interface721).unwrap();
        assert_eq!(None, loaded);

        // get version of supported interface
        let loaded = query_supported_interface_version(&store, interface2).unwrap();
        let expected = String::from("0.16.0"); 
        assert_eq!(Some(expected), loaded);
        */
    }

    #[test]
    fn test_require_version() {
        let version_req = ">=0.1.0";
        let result = require_version("0.16.0", version_req);
        assert!(result);

        let version_req = ">=0.16.0";
        let result = require_version("0.1.0", version_req);
        assert!(!result);

        let version_req = ">=1.2.3, <1.8.0";
        let result = require_version("0.16.0", version_req);
        assert!(!result);

        let version_req = ">=0.2.3";
        let result = require_version("v0.16.0", version_req);
        assert!(!result);

        let version_req = "!=0.2.3";
        let result = require_version("0.16.0", version_req);
        assert!(!result);
    }

    #[test]
    fn test_minimum_version() {
        let result = minimum_version("0.16.0", "0.2.3");
        assert!(result);

        let result = minimum_version("0.2.0", "0.2.3");
        assert!(!result);

        let result = minimum_version("v0.16.0", "0.2.3");
        assert!(!result);

        let result = minimum_version("0.16.0", "v0.2.3");
        assert!(!result);
    }
}