abstract_manager/
contract.rs

1use abstract_sdk::std::{
2    manager::{
3        state::{AccountInfo, Config, CONFIG, INFO, SUSPENSION_STATUS},
4        CallbackMsg, ExecuteMsg, InstantiateMsg, QueryMsg,
5    },
6    objects::validation::{validate_description, validate_link, validate_name},
7    proxy::state::ACCOUNT_ID,
8    MANAGER,
9};
10use abstract_std::{
11    manager::{state::ACCOUNT_MODULES, UpdateSubAccountAction},
12    objects::{gov_type::GovernanceDetails, ownership},
13    PROXY,
14};
15use cosmwasm_std::{
16    ensure_eq, wasm_execute, Binary, Deps, DepsMut, Env, MessageInfo, Reply, Response, StdError,
17    StdResult,
18};
19use cw2::set_contract_version;
20
21use crate::{
22    commands::{self, *},
23    error::ManagerError,
24    queries, versioning,
25};
26
27pub type ManagerResult<R = Response> = Result<R, ManagerError>;
28
29pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
30pub use crate::migrate::migrate;
31
32#[cfg_attr(feature = "export", cosmwasm_std::entry_point)]
33pub fn instantiate(
34    mut deps: DepsMut,
35    env: Env,
36    info: MessageInfo,
37    msg: InstantiateMsg,
38) -> ManagerResult {
39    set_contract_version(deps.storage, MANAGER, CONTRACT_VERSION)?;
40    let module_factory_address = deps.api.addr_validate(&msg.module_factory_address)?;
41    let version_control_address = deps.api.addr_validate(&msg.version_control_address)?;
42
43    // Save account id
44    ACCOUNT_ID.save(deps.storage, &msg.account_id)?;
45
46    // Save config
47    let config = Config {
48        version_control_address: version_control_address.clone(),
49        module_factory_address: module_factory_address.clone(),
50    };
51    CONFIG.save(deps.storage, &config)?;
52
53    // Verify info
54    validate_description(msg.description.as_deref())?;
55    validate_link(msg.link.as_deref())?;
56    validate_name(&msg.name)?;
57
58    let account_info = AccountInfo {
59        name: msg.name,
60        chain_id: env.block.chain_id,
61        description: msg.description,
62        link: msg.link,
63    };
64
65    INFO.save(deps.storage, &account_info)?;
66    MIGRATE_CONTEXT.save(deps.storage, &vec![])?;
67
68    // Add proxy to modules
69    ACCOUNT_MODULES.save(
70        deps.storage,
71        PROXY,
72        &deps.api.addr_validate(&msg.proxy_addr)?,
73    )?;
74
75    // Set owner
76    let cw_gov_owner = ownership::initialize_owner(
77        deps.branch(),
78        msg.owner,
79        config.version_control_address.clone(),
80    )?;
81
82    SUSPENSION_STATUS.save(deps.storage, &false)?;
83
84    let mut response = ManagerResponse::new(
85        "instantiate",
86        vec![
87            ("account_id".to_owned(), msg.account_id.to_string()),
88            ("owner".to_owned(), cw_gov_owner.owner.to_string()),
89        ],
90    );
91
92    if !msg.install_modules.is_empty() {
93        // Install modules
94        let (install_msgs, install_attribute) = _install_modules(
95            deps.branch(),
96            msg.install_modules,
97            config.module_factory_address,
98            config.version_control_address,
99            info.funds,
100        )?;
101        response = response
102            .add_submessages(install_msgs)
103            .add_attribute(install_attribute.key, install_attribute.value);
104    }
105
106    // Register on manager if it's sub-account
107    if let GovernanceDetails::SubAccount { manager, .. } = cw_gov_owner.owner {
108        response = response.add_message(wasm_execute(
109            manager,
110            &ExecuteMsg::UpdateSubAccount(UpdateSubAccountAction::RegisterSubAccount {
111                id: ACCOUNT_ID.load(deps.storage)?.seq(),
112            }),
113            vec![],
114        )?);
115    }
116
117    Ok(response)
118}
119
120#[cfg_attr(feature = "export", cosmwasm_std::entry_point)]
121pub fn execute(mut deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> ManagerResult {
122    match msg {
123        ExecuteMsg::UpdateStatus {
124            is_suspended: suspension_status,
125        } => update_account_status(deps, info, suspension_status),
126        msg => {
127            // Block actions if user is not subscribed
128            let is_suspended = SUSPENSION_STATUS.load(deps.storage)?;
129            if is_suspended {
130                return Err(ManagerError::AccountSuspended {});
131            }
132
133            match msg {
134                ExecuteMsg::UpdateInternalConfig(config) => {
135                    update_internal_config(deps, info, config)
136                }
137                ExecuteMsg::InstallModules { modules } => install_modules(deps, info, modules),
138                ExecuteMsg::UninstallModule { module_id } => {
139                    uninstall_module(deps, info, module_id)
140                }
141                ExecuteMsg::ExecOnModule {
142                    module_id,
143                    exec_msg,
144                } => exec_on_module(deps, info, module_id, exec_msg),
145                ExecuteMsg::CreateSubAccount {
146                    name,
147                    description,
148                    link,
149                    base_asset,
150                    namespace,
151                    install_modules,
152                    account_id,
153                } => create_sub_account(
154                    deps,
155                    info,
156                    env,
157                    name,
158                    description,
159                    link,
160                    base_asset,
161                    namespace,
162                    install_modules,
163                    account_id,
164                ),
165                ExecuteMsg::Upgrade { modules } => upgrade_modules(deps, env, info, modules),
166                ExecuteMsg::UpdateInfo {
167                    name,
168                    description,
169                    link,
170                } => update_info(deps, info, name, description, link),
171                ExecuteMsg::UpdateSubAccount(action) => {
172                    handle_sub_account_action(deps, info, action)
173                }
174                ExecuteMsg::Callback(CallbackMsg {}) => handle_callback(deps, env, info),
175                // Used to claim or renounce an ownership change.
176                ExecuteMsg::UpdateOwnership(action) => {
177                    // If sub-account related it may require some messages to be constructed beforehand
178                    let msgs = match &action {
179                        ownership::GovAction::TransferOwnership { .. } => vec![],
180                        ownership::GovAction::AcceptOwnership => {
181                            maybe_update_sub_account_governance(deps.branch())?
182                        }
183                        ownership::GovAction::RenounceOwnership => {
184                            remove_account_from_contracts(deps.branch())?
185                        }
186                    };
187
188                    let config = CONFIG.load(deps.storage)?;
189                    let new_owner_attributes = ownership::update_ownership(
190                        deps,
191                        &env.block,
192                        &info.sender,
193                        config.version_control_address,
194                        action,
195                    )?
196                    .into_attributes();
197                    Ok(
198                        ManagerResponse::new("update_ownership", new_owner_attributes)
199                            .add_messages(msgs),
200                    )
201                }
202                _ => panic!(),
203            }
204        }
205    }
206}
207
208#[cfg_attr(feature = "export", cosmwasm_std::entry_point)]
209pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult<Binary> {
210    match msg {
211        QueryMsg::ModuleVersions { ids } => queries::handle_contract_versions_query(deps, env, ids),
212        QueryMsg::ModuleAddresses { ids } => queries::handle_module_address_query(deps, env, ids),
213        QueryMsg::ModuleInfos { start_after, limit } => {
214            queries::handle_module_info_query(deps, start_after, limit)
215        }
216        QueryMsg::Info {} => queries::handle_account_info_query(deps),
217        QueryMsg::Config {} => queries::handle_config_query(deps),
218        QueryMsg::Ownership {} => {
219            cosmwasm_std::to_json_binary(&ownership::get_ownership(deps.storage)?)
220        }
221        QueryMsg::SubAccountIds { start_after, limit } => {
222            queries::handle_sub_accounts_query(deps, start_after, limit)
223        }
224        QueryMsg::TopLevelOwner {} => queries::handle_top_level_owner_query(deps, env),
225    }
226}
227
228pub fn handle_callback(mut deps: DepsMut, env: Env, info: MessageInfo) -> ManagerResult {
229    ensure_eq!(
230        info.sender,
231        env.contract.address,
232        StdError::generic_err("Callback must be called by contract")
233    );
234    let migrated_modules = MIGRATE_CONTEXT.load(deps.storage)?;
235
236    for (migrated_module_id, old_deps) in migrated_modules {
237        versioning::maybe_remove_old_deps(deps.branch(), &migrated_module_id, &old_deps)?;
238        let new_deps =
239            versioning::maybe_add_new_deps(deps.branch(), &migrated_module_id, &old_deps)?;
240        versioning::assert_dependency_requirements(deps.as_ref(), &new_deps, &migrated_module_id)?;
241    }
242
243    MIGRATE_CONTEXT.save(deps.storage, &vec![])?;
244    Ok(Response::new())
245}
246
247#[cfg_attr(feature = "export", cosmwasm_std::entry_point)]
248pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> ManagerResult {
249    match msg.id {
250        commands::REGISTER_MODULES_DEPENDENCIES => {
251            commands::register_dependencies(deps, msg.result)
252        }
253        commands::HANDLE_ADAPTER_AUTHORIZED_REMOVE => {
254            commands::adapter_authorized_remove(deps, msg.result)
255        }
256        _ => Err(ManagerError::UnexpectedReply {}),
257    }
258}
259
260#[cfg(test)]
261mod tests {
262    use cosmwasm_std::testing::*;
263    use semver::Version;
264    use speculoos::prelude::*;
265
266    use super::*;
267    use crate::{contract, test_common::mock_init};
268
269    mod migrate {
270        use abstract_std::{manager::MigrateMsg, AbstractError};
271        use cw2::get_contract_version;
272
273        use super::*;
274
275        #[test]
276        fn disallow_same_version() -> ManagerResult<()> {
277            let mut deps = mock_dependencies();
278            mock_init(deps.as_mut())?;
279
280            let version: Version = CONTRACT_VERSION.parse().unwrap();
281
282            let res = contract::migrate(deps.as_mut(), mock_env(), MigrateMsg {});
283
284            assert_that!(res)
285                .is_err()
286                .is_equal_to(ManagerError::Abstract(
287                    AbstractError::CannotDowngradeContract {
288                        contract: MANAGER.to_string(),
289                        from: version.clone(),
290                        to: version,
291                    },
292                ));
293
294            Ok(())
295        }
296
297        #[test]
298        fn disallow_downgrade() -> ManagerResult<()> {
299            let mut deps = mock_dependencies();
300            mock_init(deps.as_mut())?;
301
302            let big_version = "999.999.999";
303            set_contract_version(deps.as_mut().storage, MANAGER, big_version)?;
304
305            let version: Version = CONTRACT_VERSION.parse().unwrap();
306
307            let res = contract::migrate(deps.as_mut(), mock_env(), MigrateMsg {});
308
309            assert_that!(res)
310                .is_err()
311                .is_equal_to(ManagerError::Abstract(
312                    AbstractError::CannotDowngradeContract {
313                        contract: MANAGER.to_string(),
314                        from: big_version.parse().unwrap(),
315                        to: version,
316                    },
317                ));
318
319            Ok(())
320        }
321
322        #[test]
323        fn disallow_name_change() -> ManagerResult<()> {
324            let mut deps = mock_dependencies();
325            mock_init(deps.as_mut())?;
326
327            let old_version = "0.0.0";
328            let old_name = "old:contract";
329            set_contract_version(deps.as_mut().storage, old_name, old_version)?;
330
331            let res = contract::migrate(deps.as_mut(), mock_env(), MigrateMsg {});
332
333            assert_that!(res)
334                .is_err()
335                .is_equal_to(ManagerError::Abstract(
336                    AbstractError::ContractNameMismatch {
337                        from: old_name.parse().unwrap(),
338                        to: MANAGER.parse().unwrap(),
339                    },
340                ));
341
342            Ok(())
343        }
344
345        #[test]
346        fn works() -> ManagerResult<()> {
347            let mut deps = mock_dependencies();
348            mock_init(deps.as_mut())?;
349
350            let version: Version = CONTRACT_VERSION.parse().unwrap();
351
352            let small_version = Version {
353                minor: version.minor - 1,
354                ..version.clone()
355            }
356            .to_string();
357
358            set_contract_version(deps.as_mut().storage, MANAGER, small_version)?;
359
360            let res = contract::migrate(deps.as_mut(), mock_env(), MigrateMsg {})?;
361            assert_that!(res.messages).has_length(0);
362
363            assert_that!(get_contract_version(&deps.storage)?.version)
364                .is_equal_to(version.to_string());
365            Ok(())
366        }
367    }
368}