abstract_version_control/
contract.rs

1use abstract_macros::abstract_response;
2use abstract_sdk::{execute_update_ownership, query_ownership};
3pub(crate) use abstract_std::objects::namespace::ABSTRACT_NAMESPACE;
4use abstract_std::{
5    objects::namespace::Namespace,
6    version_control::{state::NAMESPACES_INFO, Config},
7};
8use abstract_std::{
9    objects::ABSTRACT_ACCOUNT_ID,
10    version_control::{state::CONFIG, ConfigResponse, ExecuteMsg, InstantiateMsg, QueryMsg},
11    VERSION_CONTROL,
12};
13use cosmwasm_std::{to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response};
14
15use crate::{commands::*, error::VCError, queries};
16
17pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
18
19pub type VCResult<T = Response> = Result<T, VCError>;
20
21#[abstract_response(VERSION_CONTROL)]
22pub struct VcResponse;
23
24#[cfg_attr(feature = "export", cosmwasm_std::entry_point)]
25pub fn instantiate(deps: DepsMut, _env: Env, _info: MessageInfo, msg: InstantiateMsg) -> VCResult {
26    cw2::set_contract_version(deps.storage, VERSION_CONTROL, CONTRACT_VERSION)?;
27
28    let InstantiateMsg {
29        admin,
30        security_disabled,
31        namespace_registration_fee,
32    } = msg;
33
34    CONFIG.save(
35        deps.storage,
36        &Config {
37            // Account factory should be set by `update_config`
38            account_factory_address: None,
39            security_disabled: security_disabled.unwrap_or(false),
40            namespace_registration_fee,
41        },
42    )?;
43
44    // Set up the admin
45    cw_ownable::initialize_owner(deps.storage, deps.api, Some(&admin))?;
46
47    // Save the abstract namespace to the Abstract admin account
48    NAMESPACES_INFO.save(
49        deps.storage,
50        &Namespace::new(ABSTRACT_NAMESPACE)?,
51        &ABSTRACT_ACCOUNT_ID,
52    )?;
53
54    Ok(VcResponse::action("instantiate"))
55}
56
57#[cfg_attr(feature = "export", cosmwasm_std::entry_point)]
58pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> VCResult {
59    match msg {
60        ExecuteMsg::ProposeModules { modules } => propose_modules(deps, info, modules),
61        ExecuteMsg::ApproveOrRejectModules { approves, rejects } => {
62            approve_or_reject_modules(deps, info, approves, rejects)
63        }
64        ExecuteMsg::RemoveModule { module } => remove_module(deps, info, module),
65        ExecuteMsg::YankModule { module } => yank_module(deps, info, module),
66        ExecuteMsg::UpdateModuleConfiguration {
67            module_name,
68            namespace,
69            update_module,
70        } => update_module_config(deps, info, module_name, namespace, update_module),
71        ExecuteMsg::ClaimNamespace {
72            namespace,
73            account_id,
74        } => claim_namespace(deps, info, account_id, namespace),
75        ExecuteMsg::RemoveNamespaces { namespaces } => remove_namespaces(deps, info, namespaces),
76        ExecuteMsg::AddAccount {
77            account_id,
78            account_base: base,
79            namespace,
80        } => add_account(deps, info, account_id, base, namespace),
81        ExecuteMsg::UpdateConfig {
82            account_factory_address,
83            security_disabled,
84            namespace_registration_fee,
85        } => update_config(
86            deps,
87            info,
88            account_factory_address,
89            security_disabled,
90            namespace_registration_fee,
91        ),
92        ExecuteMsg::UpdateOwnership(action) => {
93            execute_update_ownership!(VcResponse, deps, env, info, action)
94        }
95    }
96}
97
98#[cfg_attr(feature = "export", cosmwasm_std::entry_point)]
99pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> VCResult<Binary> {
100    match msg {
101        QueryMsg::AccountBase { account_id } => {
102            to_json_binary(&queries::handle_account_address_query(deps, account_id)?)
103        }
104        QueryMsg::Modules { infos } => to_json_binary(&queries::handle_modules_query(deps, infos)?),
105        QueryMsg::Namespaces { accounts } => {
106            to_json_binary(&queries::handle_namespaces_query(deps, accounts)?)
107        }
108        QueryMsg::Namespace { namespace } => {
109            to_json_binary(&queries::handle_namespace_query(deps, namespace)?)
110        }
111        QueryMsg::Config {} => {
112            let config = CONFIG.load(deps.storage)?;
113            to_json_binary(&ConfigResponse {
114                account_factory_address: config.account_factory_address,
115                security_disabled: config.security_disabled,
116                namespace_registration_fee: config.namespace_registration_fee,
117            })
118        }
119        QueryMsg::ModuleList {
120            filter,
121            start_after,
122            limit,
123        } => to_json_binary(&queries::handle_module_list_query(
124            deps,
125            start_after,
126            limit,
127            filter,
128        )?),
129        QueryMsg::NamespaceList { start_after, limit } => {
130            let start_after = start_after.map(Namespace::try_from).transpose()?;
131            to_json_binary(&queries::handle_namespace_list_query(
132                deps,
133                start_after,
134                limit,
135            )?)
136        }
137        QueryMsg::Ownership {} => query_ownership!(deps),
138    }
139    .map_err(Into::into)
140}
141
142#[cfg(test)]
143mod tests {
144    use abstract_std::objects::ABSTRACT_ACCOUNT_ID;
145    use cosmwasm_std::testing::*;
146    use speculoos::prelude::*;
147
148    use super::*;
149    use crate::testing::*;
150
151    mod instantiate {
152        use super::*;
153
154        #[test]
155        fn sets_abstract_namespace() -> VCResult<()> {
156            let mut deps = mock_dependencies();
157            mock_init(deps.as_mut())?;
158
159            let account_id = NAMESPACES_INFO.load(
160                deps.as_ref().storage,
161                &Namespace::try_from(ABSTRACT_NAMESPACE)?,
162            )?;
163            assert_that!(account_id).is_equal_to(ABSTRACT_ACCOUNT_ID);
164
165            Ok(())
166        }
167    }
168
169    mod migrate {
170        use abstract_std::{version_control::MigrateMsg, AbstractError};
171        use cw_semver::Version;
172
173        use super::*;
174
175        #[test]
176        fn disallow_same_version() -> VCResult<()> {
177            let mut deps = mock_dependencies();
178            mock_old_init(deps.as_mut())?;
179
180            let version: Version = CONTRACT_VERSION.parse().unwrap();
181
182            let res = crate::migrate::migrate(deps.as_mut(), mock_env(), MigrateMsg {});
183
184            assert_that!(res).is_err().is_equal_to(VCError::Abstract(
185                AbstractError::CannotDowngradeContract {
186                    contract: VERSION_CONTROL.to_string(),
187                    from: version.to_string().parse().unwrap(),
188                    to: version.to_string().parse().unwrap(),
189                },
190            ));
191
192            Ok(())
193        }
194
195        #[test]
196        fn disallow_downgrade() -> VCResult<()> {
197            let mut deps = mock_dependencies();
198            mock_old_init(deps.as_mut())?;
199
200            let big_version = "999.999.999";
201            cw2::set_contract_version(deps.as_mut().storage, VERSION_CONTROL, big_version)?;
202
203            let version: Version = CONTRACT_VERSION.parse().unwrap();
204
205            let res = crate::migrate::migrate(deps.as_mut(), mock_env(), MigrateMsg {});
206
207            assert_that!(res).is_err().is_equal_to(VCError::Abstract(
208                AbstractError::CannotDowngradeContract {
209                    contract: VERSION_CONTROL.to_string(),
210                    from: big_version.parse().unwrap(),
211                    to: version.to_string().parse().unwrap(),
212                },
213            ));
214
215            Ok(())
216        }
217
218        #[test]
219        fn disallow_name_change() -> VCResult<()> {
220            let mut deps = mock_dependencies();
221            mock_old_init(deps.as_mut())?;
222
223            let old_version = "0.0.0";
224            let old_name = "old:contract";
225            cw2::set_contract_version(deps.as_mut().storage, old_name, old_version)?;
226
227            let res = crate::migrate::migrate(deps.as_mut(), mock_env(), MigrateMsg {});
228
229            assert_that!(res).is_err().is_equal_to(VCError::Abstract(
230                AbstractError::ContractNameMismatch {
231                    from: old_name.to_string(),
232                    to: VERSION_CONTROL.to_string(),
233                },
234            ));
235
236            Ok(())
237        }
238
239        #[test]
240        fn works() -> VCResult<()> {
241            let mut deps = mock_dependencies();
242            mock_old_init(deps.as_mut())?;
243
244            let version: Version = CONTRACT_VERSION.parse().unwrap();
245
246            let small_version = Version {
247                minor: version.minor - 1,
248                ..version.clone()
249            }
250            .to_string();
251            cw2::set_contract_version(deps.as_mut().storage, VERSION_CONTROL, small_version)?;
252
253            let res = crate::migrate::migrate(deps.as_mut(), mock_env(), MigrateMsg {})?;
254            assert_that!(res.messages).has_length(0);
255
256            assert_that!(cw2::get_contract_version(&deps.storage)?.version)
257                .is_equal_to(version.to_string());
258            Ok(())
259        }
260    }
261}