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
/*!
Most of the CW\* specs are focused on the _public interfaces_
of the contract. The APIs used for `ExecuteMsg` or `QueryMsg`.
However, when we wish to migrate or inspect smart contract info,
we need some form of smart contract information embedded on state.
This is where CW2 comes in. It specifies a special Item to
be stored on disk by all contracts on `instantiate`.
`ContractInfo` must be stored under the `"contract_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 contract.
For more information on this specification, please check out the
[README](https://github.com/CosmWasm/cw-minus/blob/main/packages/cw2/README.md).
*/
use cosmwasm_schema::cw_serde;
use cosmwasm_std::{
CustomQuery, QuerierWrapper, QueryRequest, StdError, StdResult, Storage, WasmQuery,
};
use cw_storage_plus::Item;
use thiserror::Error;
mod migrate;
pub use migrate::ensure_from_older_version;
pub const CONTRACT: Item<ContractVersion> = Item::new("contract_info");
#[cw_serde]
pub struct ContractVersion {
/// contract is the crate name of the implementing contract, eg. `crate:cw20-base`
/// we will use other prefixes for other languages, and their standard global namespacing
pub contract: 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 contract (and is tied to it's implementation somehow)
pub version: String,
}
#[derive(Error, Debug, PartialEq)]
pub enum VersionError {
#[error(transparent)]
Std(#[from] StdError),
#[error("Contract version info not found")]
NotFound,
#[error("Wrong contract: expecting `{expected}`, found `{found}`")]
WrongContract { expected: String, found: String },
#[error("Wrong contract version: expecting `{expected}`, found `{found}`")]
WrongVersion { expected: String, found: String },
}
/// Assert that the stored contract version info matches the given value.
/// This is useful during migrations, for making sure that the correct contract
/// is being migrated, and it's being migrated from the correct version.
pub fn assert_contract_version(
storage: &dyn Storage,
expected_contract: &str,
expected_version: &str,
) -> Result<(), VersionError> {
let ContractVersion { contract, version } = match CONTRACT.may_load(storage)? {
Some(contract) => contract,
None => return Err(VersionError::NotFound),
};
if contract != expected_contract {
return Err(VersionError::WrongContract {
expected: expected_contract.into(),
found: contract,
});
}
if version != expected_version {
return Err(VersionError::WrongVersion {
expected: expected_version.into(),
found: version,
});
}
Ok(())
}
/// get_contract_version can be use in migrate to read the previous version of this contract
pub fn get_contract_version(store: &dyn Storage) -> StdResult<ContractVersion> {
CONTRACT.load(store)
}
/// set_contract_version should be used in instantiate to store the original version, and after a successful
/// migrate to update it
pub fn set_contract_version<T: Into<String>, U: Into<String>>(
store: &mut dyn Storage,
name: T,
version: U,
) -> StdResult<()> {
let val = ContractVersion {
contract: name.into(),
version: version.into(),
};
CONTRACT.save(store, &val)
}
/// This will make a raw_query to another contract 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 contract exists and claims to be a cw20-base contract 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_contract_info<T, CQ>(
querier: &QuerierWrapper<CQ>,
contract_addr: T,
) -> StdResult<ContractVersion>
where
T: Into<String>,
CQ: CustomQuery,
{
let req = QueryRequest::Wasm(WasmQuery::Raw {
contract_addr: contract_addr.into(),
key: CONTRACT.as_slice().into(),
});
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_contract_version(&store).is_err());
// set and get
let contract_name = "crate:cw20-base";
let contract_version = "0.2.0";
set_contract_version(&mut store, contract_name, contract_version).unwrap();
let loaded = get_contract_version(&store).unwrap();
let expected = ContractVersion {
contract: contract_name.to_string(),
version: contract_version.to_string(),
};
assert_eq!(expected, loaded);
}
#[test]
fn assert_work() {
let mut store = MockStorage::new();
const EXPECTED_CONTRACT: &str = "crate:mars-red-bank";
const EXPECTED_VERSION: &str = "1.0.0";
// error if contract version is not set
let err = assert_contract_version(&store, EXPECTED_CONTRACT, EXPECTED_VERSION).unwrap_err();
assert_eq!(err, VersionError::NotFound);
// wrong contract name
let wrong_contract = "crate:cw20-base";
set_contract_version(&mut store, wrong_contract, EXPECTED_VERSION).unwrap();
let err = assert_contract_version(&store, EXPECTED_CONTRACT, EXPECTED_VERSION).unwrap_err();
assert_eq!(
err,
VersionError::WrongContract {
expected: EXPECTED_CONTRACT.into(),
found: wrong_contract.into()
},
);
// wrong contract version
let wrong_version = "8.8.8";
set_contract_version(&mut store, EXPECTED_CONTRACT, wrong_version).unwrap();
let err = assert_contract_version(&store, EXPECTED_CONTRACT, EXPECTED_VERSION).unwrap_err();
assert_eq!(
err,
VersionError::WrongVersion {
expected: EXPECTED_VERSION.into(),
found: wrong_version.into()
},
);
// correct name and version
set_contract_version(&mut store, EXPECTED_CONTRACT, EXPECTED_VERSION).unwrap();
assert!(assert_contract_version(&store, EXPECTED_CONTRACT, EXPECTED_VERSION).is_ok());
}
}