abstract_cw2/
lib.rs

1/*!
2Most of the CW* specs are focused on the *public interfaces*
3of the contract. The APIs used for `ExecuteMsg` or `QueryMsg`.
4However, when we wish to migrate or inspect smart contract info,
5we need some form of smart contract information embedded on state.
6
7This is where CW2 comes in. It specifies a special Item to
8be stored on disk by all contracts on `instantiate`.
9
10`ContractInfo` must be stored under the `"contract_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 contract.
15
16For more information on this specification, please check out the
17[README](https://github.com/CosmWasm/cw-plus/blob/main/packages/cw2/README.md).
18*/
19
20use cosmwasm_schema::cw_serde;
21use cosmwasm_std::{
22    CustomQuery, QuerierWrapper, QueryRequest, StdError, StdResult, Storage, WasmQuery,
23};
24use cw_storage_plus::Item;
25use thiserror::Error;
26
27pub const CONTRACT: Item<ContractVersion> = Item::new("contract_info");
28
29#[cw_serde]
30pub struct ContractVersion {
31    /// contract is the crate name of the implementing contract, eg. `crate:cw20-base`
32    /// we will use other prefixes for other languages, and their standard global namespacing
33    pub contract: String,
34    /// version is any string that this implementation knows. It may be simple counter "1", "2".
35    /// or semantic version on release tags "v0.7.0", or some custom feature flag list.
36    /// the only code that needs to understand the version parsing is code that knows how to
37    /// migrate from the given contract (and is tied to it's implementation somehow)
38    pub version: String,
39}
40
41#[derive(Error, Debug, PartialEq)]
42pub enum VersionError {
43    #[error(transparent)]
44    Std(#[from] StdError),
45
46    #[error("Contract version info not found")]
47    NotFound,
48
49    #[error("Wrong contract: expecting `{expected}`, found `{found}`")]
50    WrongContract { expected: String, found: String },
51
52    #[error("Wrong contract version: expecting `{expected}`, found `{found}`")]
53    WrongVersion { expected: String, found: String },
54}
55
56/// Assert that the stored contract version info matches the given value.
57/// This is useful during migrations, for making sure that the correct contract
58/// is being migrated, and it's being migrated from the correct version.
59pub fn assert_contract_version(
60    storage: &dyn Storage,
61    expected_contract: &str,
62    expected_version: &str,
63) -> Result<(), VersionError> {
64    let ContractVersion { contract, version } = match CONTRACT.may_load(storage)? {
65        Some(contract) => contract,
66        None => return Err(VersionError::NotFound),
67    };
68
69    if contract != expected_contract {
70        return Err(VersionError::WrongContract {
71            expected: expected_contract.into(),
72            found: contract,
73        });
74    }
75
76    if version != expected_version {
77        return Err(VersionError::WrongVersion {
78            expected: expected_version.into(),
79            found: version,
80        });
81    }
82
83    Ok(())
84}
85
86/// get_contract_version can be use in migrate to read the previous version of this contract
87pub fn get_contract_version(store: &dyn Storage) -> StdResult<ContractVersion> {
88    CONTRACT.load(store)
89}
90
91/// set_contract_version should be used in instantiate to store the original version, and after a successful
92/// migrate to update it
93pub fn set_contract_version<T: Into<String>, U: Into<String>>(
94    store: &mut dyn Storage,
95    name: T,
96    version: U,
97) -> StdResult<()> {
98    let val = ContractVersion {
99        contract: name.into(),
100        version: version.into(),
101    };
102    CONTRACT.save(store, &val)
103}
104
105/// This will make a raw_query to another contract to determine the current version it
106/// claims to be. This should not be trusted, but could be used as a quick filter
107/// if the other contract exists and claims to be a cw20-base contract for example.
108/// (Note: you usually want to require *interfaces* not *implementations* of the
109/// contracts you compose with, so be careful of overuse)
110pub fn query_contract_info<T, CQ>(
111    querier: &QuerierWrapper<CQ>,
112    contract_addr: T,
113) -> StdResult<ContractVersion>
114where
115    T: Into<String>,
116    CQ: CustomQuery,
117{
118    let req = QueryRequest::Wasm(WasmQuery::Raw {
119        contract_addr: contract_addr.into(),
120        key: CONTRACT.as_slice().into(),
121    });
122    querier.query(&req)
123}
124
125#[cfg(test)]
126mod tests {
127    use super::*;
128    use cosmwasm_std::testing::MockStorage;
129
130    #[test]
131    fn get_and_set_work() {
132        let mut store = MockStorage::new();
133
134        // error if not set
135        assert!(get_contract_version(&store).is_err());
136
137        // set and get
138        let contract_name = "crate:cw20-base";
139        let contract_version = "0.2.0";
140        set_contract_version(&mut store, contract_name, contract_version).unwrap();
141
142        let loaded = get_contract_version(&store).unwrap();
143        let expected = ContractVersion {
144            contract: contract_name.to_string(),
145            version: contract_version.to_string(),
146        };
147        assert_eq!(expected, loaded);
148    }
149
150    #[test]
151    fn assert_work() {
152        let mut store = MockStorage::new();
153
154        const EXPECTED_CONTRACT: &str = "crate:mars-red-bank";
155        const EXPECTED_VERSION: &str = "1.0.0";
156
157        // error if contract version is not set
158        let err = assert_contract_version(&store, EXPECTED_CONTRACT, EXPECTED_VERSION).unwrap_err();
159        assert_eq!(err, VersionError::NotFound);
160
161        // wrong contract name
162        let wrong_contract = "crate:cw20-base";
163        set_contract_version(&mut store, wrong_contract, EXPECTED_VERSION).unwrap();
164        let err = assert_contract_version(&store, EXPECTED_CONTRACT, EXPECTED_VERSION).unwrap_err();
165        assert_eq!(
166            err,
167            VersionError::WrongContract {
168                expected: EXPECTED_CONTRACT.into(),
169                found: wrong_contract.into()
170            },
171        );
172
173        // wrong contract version
174        let wrong_version = "8.8.8";
175        set_contract_version(&mut store, EXPECTED_CONTRACT, wrong_version).unwrap();
176        let err = assert_contract_version(&store, EXPECTED_CONTRACT, EXPECTED_VERSION).unwrap_err();
177        assert_eq!(
178            err,
179            VersionError::WrongVersion {
180                expected: EXPECTED_VERSION.into(),
181                found: wrong_version.into()
182            },
183        );
184
185        // correct name and version
186        set_contract_version(&mut store, EXPECTED_CONTRACT, EXPECTED_VERSION).unwrap();
187        assert!(assert_contract_version(&store, EXPECTED_CONTRACT, EXPECTED_VERSION).is_ok());
188    }
189}