use cosmwasm_std::{
ensure, ensure_eq, Empty, Querier, QuerierWrapper, QueryRequest, StdResult, Storage, WasmQuery,
};
use cw2::{get_contract_version, ContractVersion};
use cw_storage_plus::Item;
use semver::Version;
use serde::{Deserialize, Serialize};
use super::dependency::{Dependency, DependencyResponse, StaticDependency};
use crate::AbstractError;
pub const MODULE: Item<ModuleData> = Item::new("module_data");
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ModuleData {
pub module: String,
pub version: String,
pub dependencies: Vec<Dependency>,
pub metadata: Option<String>,
}
#[cosmwasm_schema::cw_serde]
pub struct ModuleDataResponse {
pub module_id: String,
pub version: String,
pub dependencies: Vec<DependencyResponse>,
pub metadata: Option<String>,
}
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).map_err(Into::into)
}
pub fn assert_contract_upgrade(
storage: &dyn Storage,
to_contract: impl ToString,
to_version: Version,
) -> Result<(), AbstractError> {
let ContractVersion {
version: from_version,
contract,
} = get_contract_version(storage)?;
let to_contract = to_contract.to_string();
ensure_eq!(
contract,
to_contract,
AbstractError::ContractNameMismatch {
from: contract,
to: to_contract,
}
);
let from_version = from_version.parse().unwrap();
ensure!(
to_version > from_version,
AbstractError::CannotDowngradeContract {
contract,
from: from_version,
to: to_version,
}
);
let major_diff = to_version.major.checked_sub(from_version.major);
let minor_diff = to_version.minor.checked_sub(from_version.minor);
let no_skips = match (major_diff, minor_diff) {
(Some(1), _) if to_version.minor == 0 => true,
(Some(0), Some(1)) => true,
(Some(0), Some(0)) => true,
_ => false,
};
ensure!(
no_skips,
AbstractError::CannotSkipVersion {
contract,
from: from_version,
to: to_version,
}
);
Ok(())
}
pub fn assert_cw_contract_upgrade(
storage: &dyn Storage,
to_contract: impl ToString,
to_version: cw_semver::Version,
) -> Result<(), AbstractError> {
assert_contract_upgrade(
storage,
to_contract,
to_version.to_string().parse().unwrap(),
)
}
pub fn migrate_module_data(
store: &mut dyn Storage,
name: &str,
version: &str,
metadata: Option<String>,
) -> StdResult<()> {
let old_module_data = MODULE.may_load(store)?;
let val = old_module_data.map_or(
ModuleData {
module: name.into(),
version: version.into(),
dependencies: vec![],
metadata: None,
},
|data| ModuleData {
module: name.into(),
version: version.into(),
dependencies: data.dependencies,
metadata: metadata.or(data.metadata),
},
);
MODULE.save(store, &val).map_err(Into::into)
}
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)
.map_err(Into::into)
}
#[cfg(test)]
mod tests {
use cosmwasm_std::testing::MockStorage;
use super::*;
#[test]
fn set_works() {
let mut store = MockStorage::new();
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 = MODULE.load(&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);
}
#[test]
fn module_upgrade() {
let mut store = MockStorage::new();
let contract_name = "abstract:manager";
let contract_version = "0.19.2";
cw2::CONTRACT
.save(
&mut store,
&ContractVersion {
contract: contract_name.to_owned(),
version: contract_version.to_owned(),
},
)
.unwrap();
let to_version = "0.19.3".parse().unwrap();
let res = assert_contract_upgrade(&store, contract_name, to_version);
assert!(res.is_ok());
let to_version = "0.20.0".parse().unwrap();
let res = assert_contract_upgrade(&store, contract_name, to_version);
assert!(res.is_ok());
let to_version = "0.20.1".parse().unwrap();
let res = assert_contract_upgrade(&store, contract_name, to_version);
assert!(res.is_ok());
let to_version = "1.0.0".parse().unwrap();
let res = assert_contract_upgrade(&store, contract_name, to_version);
assert!(res.is_ok());
}
#[test]
fn module_upgrade_err() {
let mut store = MockStorage::new();
let contract_name = "abstract:manager";
let contract_version = "0.19.2";
cw2::CONTRACT
.save(
&mut store,
&ContractVersion {
contract: contract_name.to_owned(),
version: contract_version.to_owned(),
},
)
.unwrap();
let to_version: Version = "0.19.1".parse().unwrap();
let err = assert_contract_upgrade(&store, contract_name, to_version.clone()).unwrap_err();
assert_eq!(
err,
AbstractError::CannotDowngradeContract {
contract: contract_name.to_string(),
from: contract_version.parse().unwrap(),
to: to_version
}
);
let to_version: Version = "0.21.0".parse().unwrap();
let err = assert_contract_upgrade(&store, contract_name, to_version.clone()).unwrap_err();
assert_eq!(
err,
AbstractError::CannotSkipVersion {
contract: contract_name.to_string(),
from: contract_version.parse().unwrap(),
to: to_version
}
);
let to_version: Version = "2.0.0".parse().unwrap();
let err = assert_contract_upgrade(&store, contract_name, to_version.clone()).unwrap_err();
assert_eq!(
err,
AbstractError::CannotSkipVersion {
contract: contract_name.to_string(),
from: contract_version.parse().unwrap(),
to: to_version
}
);
}
}