1use cosmwasm_std::{
24 ensure, ensure_eq, Empty, Querier, QuerierWrapper, QueryRequest, StdResult, Storage, WasmQuery,
25};
26use cw2::{get_contract_version, ContractVersion};
27use cw_storage_plus::Item;
28use semver::Version;
29use serde::{Deserialize, Serialize};
30
31use super::{
32 dependency::{Dependency, DependencyResponse, StaticDependency},
33 storage_namespaces::MODULE_STORAGE_KEY,
34};
35use crate::AbstractError;
36
37pub const MODULE: Item<ModuleData> = Item::new(MODULE_STORAGE_KEY);
39
40#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
42pub struct ModuleData {
43 pub module: String,
46 pub version: String,
49 pub dependencies: Vec<Dependency>,
52 pub metadata: Option<String>,
55}
56#[cosmwasm_schema::cw_serde]
59pub struct ModuleDataResponse {
60 pub module_id: String,
61 pub version: String,
62 pub dependencies: Vec<DependencyResponse>,
63 pub metadata: Option<String>,
64}
65
66pub fn set_module_data<T: Into<String>, U: Into<String>, M: Into<String>>(
69 store: &mut dyn Storage,
70 name: T,
71 version: U,
72 dependencies: &[StaticDependency],
73 metadata: Option<M>,
74) -> StdResult<()> {
75 let val = ModuleData {
76 module: name.into(),
77 version: version.into(),
78 dependencies: dependencies.iter().map(Into::into).collect(),
79 metadata: metadata.map(Into::into),
80 };
81 MODULE.save(store, &val).map_err(Into::into)
82}
83
84pub fn assert_contract_upgrade(
86 storage: &dyn Storage,
87 to_contract: impl ToString,
88 to_version: Version,
89) -> Result<(), AbstractError> {
90 let ContractVersion {
91 version: from_version,
92 contract,
93 } = get_contract_version(storage)?;
94
95 let to_contract = to_contract.to_string();
96
97 ensure_eq!(
99 contract,
100 to_contract,
101 AbstractError::ContractNameMismatch {
102 from: contract,
103 to: to_contract,
104 }
105 );
106
107 let from_version = from_version.parse().unwrap();
108
109 ensure!(
111 to_version > from_version,
112 AbstractError::CannotDowngradeContract {
113 contract,
114 from: from_version,
115 to: to_version,
116 }
117 );
118 let major_diff = to_version.major.checked_sub(from_version.major);
121 let minor_diff = to_version.minor.checked_sub(from_version.minor);
122 let no_skips = match (major_diff, minor_diff) {
123 (Some(1), _) => true,
126 (Some(0), Some(1)) => true,
128 (Some(0), Some(0)) => true,
130 _ => false,
131 };
132 ensure!(
133 no_skips,
134 AbstractError::CannotSkipVersion {
135 contract,
136 from: from_version,
137 to: to_version,
138 }
139 );
140 Ok(())
141}
142
143pub fn assert_cw_contract_upgrade(
145 storage: &dyn Storage,
146 to_contract: impl ToString,
147 to_version: semver::Version,
148) -> Result<(), AbstractError> {
149 assert_contract_upgrade(
150 storage,
151 to_contract,
152 to_version.to_string().parse().unwrap(),
153 )
154}
155
156pub fn migrate_module_data(
161 store: &mut dyn Storage,
162 name: &str,
163 version: &str,
164 metadata: Option<String>,
165) -> StdResult<()> {
166 let old_module_data = MODULE.may_load(store)?;
167 let val = old_module_data.map_or(
168 ModuleData {
169 module: name.into(),
170 version: version.into(),
171 dependencies: vec![],
172 metadata: None,
173 },
174 |data| ModuleData {
175 module: name.into(),
176 version: version.into(),
177 dependencies: data.dependencies,
178 metadata: metadata.or(data.metadata),
179 },
180 );
181
182 MODULE.save(store, &val).map_err(Into::into)
183}
184
185pub fn query_module_data<Q: Querier, T: Into<String>>(
191 querier: &Q,
192 contract_addr: T,
193) -> StdResult<ModuleData> {
194 let req = QueryRequest::Wasm(WasmQuery::Raw {
195 contract_addr: contract_addr.into(),
196 key: MODULE.as_slice().into(),
197 });
198 QuerierWrapper::<Empty>::new(querier)
199 .query(&req)
200 .map_err(Into::into)
201}
202
203#[cfg(test)]
204mod tests {
205 use cosmwasm_std::testing::MockStorage;
206
207 use super::*;
208
209 #[coverage_helper::test]
210 fn set_works() {
211 let mut store = MockStorage::new();
212
213 let contract_name = "crate:cw20-base";
215 let contract_version = "0.2.0";
216 let metadata = Some("https://example.com");
217 const REQUIREMENT: [&str; 1] = [">1"];
218
219 const DEPENDENCIES: &[StaticDependency; 1] = &[StaticDependency {
220 id: "abstact::dex",
221 version_req: &REQUIREMENT,
222 }];
223 set_module_data(
224 &mut store,
225 contract_name,
226 contract_version,
227 DEPENDENCIES,
228 metadata,
229 )
230 .unwrap();
231
232 let loaded = MODULE.load(&store).unwrap();
233 let expected = ModuleData {
234 module: contract_name.to_string(),
235 version: contract_version.to_string(),
236 dependencies: DEPENDENCIES.iter().map(Into::into).collect(),
237 metadata: metadata.map(Into::into),
238 };
239 assert_eq!(expected, loaded);
240 }
241
242 #[coverage_helper::test]
243 fn module_upgrade() {
244 let mut store = MockStorage::new();
245 let contract_name = "abstract:account";
246 let contract_version = "0.19.2";
247 cw2::CONTRACT
248 .save(
249 &mut store,
250 &ContractVersion {
251 contract: contract_name.to_owned(),
252 version: contract_version.to_owned(),
253 },
254 )
255 .unwrap();
256
257 let to_version = "0.19.3".parse().unwrap();
259 let res = assert_contract_upgrade(&store, contract_name, to_version);
260 assert!(res.is_ok());
261
262 let to_version = "0.20.0".parse().unwrap();
264 let res = assert_contract_upgrade(&store, contract_name, to_version);
265 assert!(res.is_ok());
266
267 let to_version = "0.20.1".parse().unwrap();
269 let res = assert_contract_upgrade(&store, contract_name, to_version);
270 assert!(res.is_ok());
271
272 let to_version = "1.0.0".parse().unwrap();
274 let res = assert_contract_upgrade(&store, contract_name, to_version);
275 assert!(res.is_ok());
276 }
277
278 #[coverage_helper::test]
279 fn module_upgrade_err() {
280 let mut store = MockStorage::new();
281 let contract_name = "abstract:account";
282 let contract_version = "0.19.2";
283 cw2::CONTRACT
284 .save(
285 &mut store,
286 &ContractVersion {
287 contract: contract_name.to_owned(),
288 version: contract_version.to_owned(),
289 },
290 )
291 .unwrap();
292
293 let to_version: Version = "0.19.1".parse().unwrap();
295 let err = assert_contract_upgrade(&store, contract_name, to_version.clone()).unwrap_err();
296 assert_eq!(
297 err,
298 AbstractError::CannotDowngradeContract {
299 contract: contract_name.to_string(),
300 from: contract_version.parse().unwrap(),
301 to: to_version
302 }
303 );
304
305 let to_version: Version = "0.21.0".parse().unwrap();
307 let err = assert_contract_upgrade(&store, contract_name, to_version.clone()).unwrap_err();
308 assert_eq!(
309 err,
310 AbstractError::CannotSkipVersion {
311 contract: contract_name.to_string(),
312 from: contract_version.parse().unwrap(),
313 to: to_version
314 }
315 );
316
317 let to_version: Version = "2.0.0".parse().unwrap();
319 let err = assert_contract_upgrade(&store, contract_name, to_version.clone()).unwrap_err();
320 assert_eq!(
321 err,
322 AbstractError::CannotSkipVersion {
323 contract: contract_name.to_string(),
324 from: contract_version.parse().unwrap(),
325 to: to_version
326 }
327 );
328 }
329}