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 ACCOUNT_ID.save(deps.storage, &msg.account_id)?;
45
46 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 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 ACCOUNT_MODULES.save(
70 deps.storage,
71 PROXY,
72 &deps.api.addr_validate(&msg.proxy_addr)?,
73 )?;
74
75 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 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 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 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 ExecuteMsg::UpdateOwnership(action) => {
177 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}