abstract_module_factory/
contract.rs

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    // Set up the admin
38    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}