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 {
pub contract: String,
pub version: String,
}
#[derive(Error, Debug)]
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 },
}
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(())
}
pub fn get_contract_version(store: &dyn Storage) -> StdResult<ContractVersion> {
CONTRACT.load(store)
}
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)
}
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();
assert!(get_contract_version(&store).is_err());
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";
let err = assert_contract_version(&store, EXPECTED_CONTRACT, EXPECTED_VERSION).unwrap_err();
assert!(matches!(err, VersionError::NotFound));
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!(
"Wrong contract: expecting `crate:mars-red-bank`, found `crate:cw20-base`",
err.to_string()
);
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!(
"Wrong contract version: expecting `1.0.0`, found `8.8.8`",
err.to_string()
);
set_contract_version(&mut store, EXPECTED_CONTRACT, EXPECTED_VERSION).unwrap();
assert!(assert_contract_version(&store, EXPECTED_CONTRACT, EXPECTED_VERSION).is_ok());
}
}