1use abstract_macros::abstract_response;
2use abstract_sdk::{
3 feature_objects::{AnsHost, RegistryContract},
4 std::{module_factory::*, MODULE_FACTORY},
5};
6use abstract_std::{
7 native_addrs,
8 objects::{
9 module::{ModuleInfo, Monetization},
10 module_version::assert_contract_upgrade,
11 },
12};
13use cosmwasm_std::{
14 to_json_binary, Binary, Coins, Deps, DepsMut, Env, MessageInfo, Response, StdError, StdResult,
15};
16use cw2::set_contract_version;
17use semver::Version;
18
19use crate::{commands, error::ModuleFactoryError};
20
21pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
22
23#[abstract_response(MODULE_FACTORY)]
24pub struct ModuleFactoryResponse;
25
26pub type ModuleFactoryResult<T = Response> = Result<T, ModuleFactoryError>;
27
28#[cfg_attr(feature = "export", cosmwasm_std::entry_point)]
29pub fn instantiate(
30 deps: DepsMut,
31 _env: Env,
32 _info: MessageInfo,
33 msg: InstantiateMsg,
34) -> ModuleFactoryResult {
35 set_contract_version(deps.storage, MODULE_FACTORY, CONTRACT_VERSION)?;
36
37 cw_ownable::initialize_owner(deps.storage, deps.api, Some(&msg.admin))?;
39
40 Ok(ModuleFactoryResponse::action("instantiate"))
41}
42
43#[cfg_attr(feature = "export", cosmwasm_std::entry_point)]
44pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> ModuleFactoryResult {
45 match msg {
46 ExecuteMsg::InstallModules { modules, salt } => {
47 commands::execute_create_modules(deps, env, info, modules, salt)
48 }
49 ExecuteMsg::UpdateOwnership(action) => {
50 abstract_sdk::execute_update_ownership!(ModuleFactoryResponse, deps, env, info, action)
51 }
52 }
53}
54
55#[cfg_attr(feature = "export", cosmwasm_std::entry_point)]
56pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult<Binary> {
57 match msg {
58 QueryMsg::Config {} => to_json_binary(&query_config(deps, &env)?),
59 QueryMsg::SimulateInstallModules { modules } => {
60 to_json_binary(&query_simulate_install_modules(deps, &env, modules)?)
61 }
62 QueryMsg::Ownership {} => abstract_sdk::query_ownership!(deps),
63 }
64}
65
66pub fn query_config(deps: Deps, env: &Env) -> StdResult<ConfigResponse> {
67 let abstract_code_id =
68 native_addrs::abstract_code_id(&deps.querier, env.contract.address.clone())?;
69
70 let resp = ConfigResponse {
71 registry_address: RegistryContract::new(deps, abstract_code_id)
72 .map_err(|e| StdError::generic_err(e.to_string()))?
73 .address,
74 ans_host_address: AnsHost::new(deps, abstract_code_id)
75 .map_err(|e| StdError::generic_err(e.to_string()))?
76 .address,
77 };
78
79 Ok(resp)
80}
81
82pub fn query_simulate_install_modules(
83 deps: Deps,
84 env: &Env,
85 modules: Vec<ModuleInfo>,
86) -> StdResult<SimulateInstallModulesResponse> {
87 let abstract_code_id =
88 native_addrs::abstract_code_id(&deps.querier, env.contract.address.clone())?;
89
90 let registry = RegistryContract::new(deps, abstract_code_id)
91 .map_err(|e| StdError::generic_err(e.to_string()))?;
92
93 let module_responses = registry
94 .query_modules_configs(modules, &deps.querier)
95 .map_err(|e| cosmwasm_std::StdError::generic_err(e.to_string()))?;
96
97 let mut coins = Coins::default();
98 let mut install_funds = vec![];
99 let mut init_funds = vec![];
100 for module in module_responses {
101 if let Monetization::InstallFee(fee) = module.config.monetization {
102 coins.add(fee.fee())?;
103 install_funds.push((module.module.info.id(), fee.fee()))
104 }
105 if !module.config.instantiation_funds.is_empty() {
106 init_funds.push((
107 module.module.info.id(),
108 module.config.instantiation_funds.clone(),
109 ));
110
111 for init_coin in module.config.instantiation_funds {
112 coins.add(init_coin)?;
113 }
114 }
115 }
116 let resp = SimulateInstallModulesResponse {
117 total_required_funds: coins.into_vec(),
118 monetization_funds: install_funds,
119 initialization_funds: init_funds,
120 };
121 Ok(resp)
122}
123
124#[cfg_attr(feature = "export", cosmwasm_std::entry_point)]
125pub fn migrate(deps: DepsMut, env: Env, msg: MigrateMsg) -> ModuleFactoryResult {
126 match msg {
127 MigrateMsg::Instantiate(instantiate_msg) => {
128 abstract_sdk::cw_helpers::migrate_instantiate(deps, env, instantiate_msg, instantiate)
129 }
130 MigrateMsg::Migrate {} => {
131 let version: Version = CONTRACT_VERSION.parse().unwrap();
132
133 assert_contract_upgrade(deps.storage, MODULE_FACTORY, version)?;
134 set_contract_version(deps.storage, MODULE_FACTORY, CONTRACT_VERSION)?;
135
136 Ok(ModuleFactoryResponse::action("migrate"))
137 }
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use cosmwasm_std::testing::*;
144
145 use super::*;
146 use crate::{contract, test_common::*};
147
148 mod migrate {
149 use abstract_std::AbstractError;
150 use abstract_testing::mock_env_validated;
151 use cw2::get_contract_version;
152
153 use super::*;
154
155 #[coverage_helper::test]
156 fn disallow_same_version() -> ModuleFactoryResult<()> {
157 let mut deps = mock_dependencies();
158 let env = mock_env_validated(deps.api);
159 mock_init(&mut deps)?;
160
161 let version: Version = CONTRACT_VERSION.parse().unwrap();
162
163 let res = contract::migrate(deps.as_mut(), env, MigrateMsg::Migrate {});
164
165 assert_eq!(
166 res,
167 Err(ModuleFactoryError::Abstract(
168 AbstractError::CannotDowngradeContract {
169 contract: MODULE_FACTORY.to_string(),
170 from: version.clone(),
171 to: version,
172 },
173 ))
174 );
175
176 Ok(())
177 }
178
179 #[coverage_helper::test]
180 fn disallow_downgrade() -> ModuleFactoryResult<()> {
181 let mut deps = mock_dependencies();
182 let env = mock_env_validated(deps.api);
183 mock_init(&mut deps)?;
184
185 let big_version = "999.999.999";
186 set_contract_version(deps.as_mut().storage, MODULE_FACTORY, big_version)?;
187
188 let version: Version = CONTRACT_VERSION.parse().unwrap();
189
190 let res = contract::migrate(deps.as_mut(), env, MigrateMsg::Migrate {});
191
192 assert_eq!(
193 res,
194 Err(ModuleFactoryError::Abstract(
195 AbstractError::CannotDowngradeContract {
196 contract: MODULE_FACTORY.to_string(),
197 from: big_version.parse().unwrap(),
198 to: version,
199 },
200 ))
201 );
202
203 Ok(())
204 }
205
206 #[coverage_helper::test]
207 fn disallow_name_change() -> ModuleFactoryResult<()> {
208 let mut deps = mock_dependencies();
209 let env = mock_env_validated(deps.api);
210 mock_init(&mut deps)?;
211
212 let old_version = "0.0.0";
213 let old_name = "old:contract";
214 set_contract_version(deps.as_mut().storage, old_name, old_version)?;
215
216 let res = contract::migrate(deps.as_mut(), env, MigrateMsg::Migrate {});
217
218 assert_eq!(
219 res,
220 Err(ModuleFactoryError::Abstract(
221 AbstractError::ContractNameMismatch {
222 from: old_name.parse().unwrap(),
223 to: MODULE_FACTORY.parse().unwrap(),
224 },
225 ))
226 );
227
228 Ok(())
229 }
230
231 #[coverage_helper::test]
232 fn works() -> ModuleFactoryResult<()> {
233 let mut deps = mock_dependencies();
234 let env = mock_env_validated(deps.api);
235 mock_init(&mut deps)?;
236
237 let version: Version = CONTRACT_VERSION.parse().unwrap();
238
239 let small_version = Version {
240 minor: version.minor - 1,
241 ..version.clone()
242 }
243 .to_string();
244 set_contract_version(deps.as_mut().storage, MODULE_FACTORY, small_version)?;
245
246 let res = contract::migrate(deps.as_mut(), env, MigrateMsg::Migrate {})?;
247 assert!(res.messages.is_empty());
248
249 assert_eq!(
250 get_contract_version(&deps.storage)?.version,
251 version.to_string()
252 );
253 Ok(())
254 }
255 }
256}