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