1use cosmwasm_schema::cw_serde;
21use cosmwasm_std::{
22 CustomQuery, QuerierWrapper, QueryRequest, StdError, StdResult, Storage, WasmQuery,
23};
24use cw_storage_plus::Item;
25use thiserror::Error;
26
27pub const CONTRACT: Item<ContractVersion> = Item::new("contract_info");
28
29#[cw_serde]
30pub struct ContractVersion {
31 pub contract: String,
34 pub version: String,
39}
40
41#[derive(Error, Debug, PartialEq)]
42pub enum VersionError {
43 #[error(transparent)]
44 Std(#[from] StdError),
45
46 #[error("Contract version info not found")]
47 NotFound,
48
49 #[error("Wrong contract: expecting `{expected}`, found `{found}`")]
50 WrongContract { expected: String, found: String },
51
52 #[error("Wrong contract version: expecting `{expected}`, found `{found}`")]
53 WrongVersion { expected: String, found: String },
54}
55
56pub fn assert_contract_version(
60 storage: &dyn Storage,
61 expected_contract: &str,
62 expected_version: &str,
63) -> Result<(), VersionError> {
64 let ContractVersion { contract, version } = match CONTRACT.may_load(storage)? {
65 Some(contract) => contract,
66 None => return Err(VersionError::NotFound),
67 };
68
69 if contract != expected_contract {
70 return Err(VersionError::WrongContract {
71 expected: expected_contract.into(),
72 found: contract,
73 });
74 }
75
76 if version != expected_version {
77 return Err(VersionError::WrongVersion {
78 expected: expected_version.into(),
79 found: version,
80 });
81 }
82
83 Ok(())
84}
85
86pub fn get_contract_version(store: &dyn Storage) -> StdResult<ContractVersion> {
88 CONTRACT.load(store)
89}
90
91pub fn set_contract_version<T: Into<String>, U: Into<String>>(
94 store: &mut dyn Storage,
95 name: T,
96 version: U,
97) -> StdResult<()> {
98 let val = ContractVersion {
99 contract: name.into(),
100 version: version.into(),
101 };
102 CONTRACT.save(store, &val)
103}
104
105pub fn query_contract_info<T, CQ>(
111 querier: &QuerierWrapper<CQ>,
112 contract_addr: T,
113) -> StdResult<ContractVersion>
114where
115 T: Into<String>,
116 CQ: CustomQuery,
117{
118 let req = QueryRequest::Wasm(WasmQuery::Raw {
119 contract_addr: contract_addr.into(),
120 key: CONTRACT.as_slice().into(),
121 });
122 querier.query(&req)
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128 use cosmwasm_std::testing::MockStorage;
129
130 #[test]
131 fn get_and_set_work() {
132 let mut store = MockStorage::new();
133
134 assert!(get_contract_version(&store).is_err());
136
137 let contract_name = "crate:cw20-base";
139 let contract_version = "0.2.0";
140 set_contract_version(&mut store, contract_name, contract_version).unwrap();
141
142 let loaded = get_contract_version(&store).unwrap();
143 let expected = ContractVersion {
144 contract: contract_name.to_string(),
145 version: contract_version.to_string(),
146 };
147 assert_eq!(expected, loaded);
148 }
149
150 #[test]
151 fn assert_work() {
152 let mut store = MockStorage::new();
153
154 const EXPECTED_CONTRACT: &str = "crate:mars-red-bank";
155 const EXPECTED_VERSION: &str = "1.0.0";
156
157 let err = assert_contract_version(&store, EXPECTED_CONTRACT, EXPECTED_VERSION).unwrap_err();
159 assert_eq!(err, VersionError::NotFound);
160
161 let wrong_contract = "crate:cw20-base";
163 set_contract_version(&mut store, wrong_contract, EXPECTED_VERSION).unwrap();
164 let err = assert_contract_version(&store, EXPECTED_CONTRACT, EXPECTED_VERSION).unwrap_err();
165 assert_eq!(
166 err,
167 VersionError::WrongContract {
168 expected: EXPECTED_CONTRACT.into(),
169 found: wrong_contract.into()
170 },
171 );
172
173 let wrong_version = "8.8.8";
175 set_contract_version(&mut store, EXPECTED_CONTRACT, wrong_version).unwrap();
176 let err = assert_contract_version(&store, EXPECTED_CONTRACT, EXPECTED_VERSION).unwrap_err();
177 assert_eq!(
178 err,
179 VersionError::WrongVersion {
180 expected: EXPECTED_VERSION.into(),
181 found: wrong_version.into()
182 },
183 );
184
185 set_contract_version(&mut store, EXPECTED_CONTRACT, EXPECTED_VERSION).unwrap();
187 assert!(assert_contract_version(&store, EXPECTED_CONTRACT, EXPECTED_VERSION).is_ok());
188 }
189}