abstract_interface/
deployers.rs

1use abstract_std::{
2    account::ModuleInstallConfig,
3    objects::{
4        dependency::StaticDependency,
5        module::{ModuleInfo, ModuleVersion},
6        AccountId,
7    },
8};
9use cosmwasm_std::to_json_binary;
10use cw_orch::{
11    environment::Environment,
12    prelude::{CwOrchError::StdErr, *},
13};
14use semver::Version;
15use serde::Serialize;
16
17use crate::Abstract;
18
19/// Trait to access module information tied directly to the type.
20pub trait RegisteredModule {
21    /// The init message for the module.
22    type InitMsg: Serialize;
23    /// The id of the module.
24    fn module_id<'a>() -> &'a str;
25    /// The version of the module.
26    fn module_version<'a>() -> &'a str;
27    /// Create the unique contract ID for a module installed on an Account.
28    fn installed_module_contract_id(account_id: &AccountId) -> String {
29        format!("{}-{}", Self::module_id(), account_id)
30    }
31    /// Dependencies of the module
32    fn dependencies<'a>() -> &'a [StaticDependency];
33}
34
35/// Trait to access module dependency information tied directly to the type.
36pub trait DependencyCreation {
37    /// Type that exposes the dependencies's configurations if that's required.
38    type DependenciesConfig;
39
40    /// Function that returns the [`ModuleInstallConfig`] for each dependent module.
41    #[allow(unused_variables)]
42    fn dependency_install_configs(
43        configuration: Self::DependenciesConfig,
44    ) -> Result<Vec<ModuleInstallConfig>, crate::AbstractInterfaceError> {
45        Ok(vec![])
46    }
47}
48
49/// Trait to make it easier to construct [`ModuleInfo`] and [`ModuleInstallConfig`] for a
50/// [`RegisteredModule`].
51pub trait InstallConfig: RegisteredModule {
52    /// Constructs the [`ModuleInfo`] by using information from [`RegisteredModule`].
53    fn module_info() -> Result<ModuleInfo, crate::AbstractInterfaceError> {
54        ModuleInfo::from_id(Self::module_id(), Self::module_version().into()).map_err(Into::into)
55    }
56
57    /// Constructs the [`ModuleInstallConfig`] for an App Interface
58    fn install_config(
59        init_msg: &Self::InitMsg,
60    ) -> Result<ModuleInstallConfig, crate::AbstractInterfaceError> {
61        Ok(ModuleInstallConfig::new(
62            Self::module_info()?,
63            Some(to_json_binary(init_msg)?),
64        ))
65    }
66}
67
68// Blanket implemention.
69impl<T> InstallConfig for T where T: RegisteredModule {}
70
71/// Strategy for deploying
72#[derive(Debug, Clone, Copy, PartialEq, Eq)]
73pub enum DeployStrategy {
74    /// Error if already present
75    Error,
76    /// Ignore if already present
77    Try,
78    /// Force deployment
79    Force,
80}
81
82/// Trait for deploying Adapters
83pub trait AdapterDeployer<Chain: CwEnv, CustomInitMsg: Serialize>: ContractInstance<Chain>
84    + CwOrchInstantiate<Chain, InstantiateMsg = abstract_std::adapter::InstantiateMsg<CustomInitMsg>>
85    + Uploadable
86    + Sized
87    + RegisteredModule
88{
89    /// Deploys the adapter. If the adapter is already deployed, it will return an error.
90    /// Use [`DeployStrategy::Try`]  if you want to deploy the adapter only if it is not already deployed.
91    fn deploy(
92        &self,
93        version: Version,
94        custom_init_msg: CustomInitMsg,
95        strategy: DeployStrategy,
96    ) -> Result<(), crate::AbstractInterfaceError> {
97        // retrieve the deployment
98        let abstr = Abstract::load_from(self.environment().to_owned())?;
99
100        abstr
101            .registry
102            .assert_dependencies_deployed(Self::dependencies())?;
103
104        // check for existing version, if not force strategy
105        let vc_has_module = || {
106            abstr
107                .registry
108                .registered_or_pending_module(
109                    ModuleInfo::from_id(&self.id(), ModuleVersion::from(version.to_string()))
110                        .unwrap(),
111                )
112                .and_then(|module| module.reference.unwrap_adapter().map_err(Into::into))
113        };
114
115        match strategy {
116            DeployStrategy::Error => {
117                if vc_has_module().is_ok() {
118                    return Err(StdErr(format!(
119                        "Adapter {} already exists with version {}",
120                        self.id(),
121                        version
122                    ))
123                    .into());
124                }
125            }
126            DeployStrategy::Try => {
127                if vc_has_module().is_ok() {
128                    return Ok(());
129                }
130            }
131            DeployStrategy::Force => {}
132        }
133
134        self.upload_if_needed()?;
135        let init_msg = abstract_std::adapter::InstantiateMsg {
136            module: custom_init_msg,
137            base: abstract_std::adapter::BaseInstantiateMsg {
138                registry_address: abstr.registry.addr_str()?,
139            },
140        };
141        self.instantiate(&init_msg, None, &[])?;
142
143        abstr
144            .registry
145            .register_adapters(vec![(self.as_instance(), version.to_string())])?;
146
147        Ok(())
148    }
149}
150
151/// Trait for deploying APPs
152pub trait AppDeployer<Chain: CwEnv>:
153    Sized + Uploadable + ContractInstance<Chain> + RegisteredModule
154{
155    /// Deploys the app. If the app is already deployed, it will return an error.
156    /// Use [`DeployStrategy::Try`]  if you want to deploy the app only if it is not already deployed.
157    fn deploy(
158        &self,
159        version: Version,
160        strategy: DeployStrategy,
161    ) -> Result<(), crate::AbstractInterfaceError> {
162        // retrieve the deployment
163        let abstr = Abstract::<Chain>::load_from(self.environment().to_owned())?;
164
165        abstr
166            .registry
167            .assert_dependencies_deployed(Self::dependencies())?;
168
169        // check for existing version
170        let vc_has_module = || {
171            abstr
172                .registry
173                .registered_or_pending_module(
174                    ModuleInfo::from_id(&self.id(), ModuleVersion::from(version.to_string()))
175                        .unwrap(),
176                )
177                .and_then(|module| module.reference.unwrap_app().map_err(Into::into))
178        };
179
180        match strategy {
181            DeployStrategy::Error => {
182                if vc_has_module().is_ok() {
183                    return Err(StdErr(format!(
184                        "App {} already exists with version {}",
185                        self.id(),
186                        version
187                    ))
188                    .into());
189                }
190            }
191            DeployStrategy::Try => {
192                if vc_has_module().is_ok() {
193                    return Ok(());
194                }
195            }
196            DeployStrategy::Force => {}
197        }
198
199        self.upload_if_needed()?;
200        abstr
201            .registry
202            .register_apps(vec![(self.as_instance(), version.to_string())])?;
203
204        Ok(())
205    }
206}
207
208/// Trait for deploying Standalones
209pub trait StandaloneDeployer<Chain: CwEnv>:
210    Sized + Uploadable + ContractInstance<Chain> + RegisteredModule
211{
212    /// Deploys the app. If the app is already deployed, it will return an error.
213    /// Use [`DeployStrategy::Try`] if you want to deploy the app only if it is not already deployed.
214    fn deploy(
215        &self,
216        version: Version,
217        strategy: DeployStrategy,
218    ) -> Result<(), crate::AbstractInterfaceError> {
219        // retrieve the deployment
220        let abstr = Abstract::<Chain>::load_from(self.environment().to_owned())?;
221
222        abstr
223            .registry
224            .assert_dependencies_deployed(Self::dependencies())?;
225
226        // check for existing version
227        let vc_has_module = || {
228            abstr
229                .registry
230                .registered_or_pending_module(
231                    ModuleInfo::from_id(&self.id(), ModuleVersion::from(version.to_string()))
232                        .unwrap(),
233                )
234                .and_then(|module| module.reference.unwrap_standalone().map_err(Into::into))
235        };
236
237        match strategy {
238            DeployStrategy::Error => {
239                if vc_has_module().is_ok() {
240                    return Err(StdErr(format!(
241                        "Standalone {} already exists with version {}",
242                        self.id(),
243                        version
244                    ))
245                    .into());
246                }
247            }
248            DeployStrategy::Try => {
249                if vc_has_module().is_ok() {
250                    return Ok(());
251                }
252            }
253            DeployStrategy::Force => {}
254        }
255
256        self.upload_if_needed()?;
257        abstr
258            .registry
259            .register_standalones(vec![(self.as_instance(), version.to_string())])?;
260
261        Ok(())
262    }
263}
264
265/// Trait for deploying Services
266pub trait ServiceDeployer<Chain: CwEnv>:
267    Sized + Uploadable + ContractInstance<Chain> + CwOrchInstantiate<Chain>
268{
269    /// Deploys the module. If the module is already deployed, it will return an error.
270    /// Use [`DeployStrategy::Try`] if you want to deploy the module only if it is not already deployed.
271    fn deploy(
272        &self,
273        version: Version,
274        custom_init_msg: &<Self as InstantiableContract>::InstantiateMsg,
275        strategy: DeployStrategy,
276    ) -> Result<(), crate::AbstractInterfaceError> {
277        // retrieve the deployment
278        let abstr = Abstract::<Chain>::load_from(self.environment().to_owned())?;
279
280        // check for existing version
281        let vc_has_module = || {
282            abstr
283                .registry
284                .registered_or_pending_module(
285                    ModuleInfo::from_id(&self.id(), ModuleVersion::from(version.to_string()))
286                        .unwrap(),
287                )
288                .and_then(|module| module.reference.unwrap_standalone().map_err(Into::into))
289        };
290
291        match strategy {
292            DeployStrategy::Error => {
293                if vc_has_module().is_ok() {
294                    return Err(StdErr(format!(
295                        "Service {} already exists with version {}",
296                        self.id(),
297                        version
298                    ))
299                    .into());
300                }
301            }
302            DeployStrategy::Try => {
303                if vc_has_module().is_ok() {
304                    return Ok(());
305                }
306            }
307            DeployStrategy::Force => {}
308        }
309
310        self.upload_if_needed()?;
311        self.instantiate(custom_init_msg, None, &[])?;
312        abstr
313            .registry
314            .register_services(vec![(self.as_instance(), version.to_string())])?;
315
316        Ok(())
317    }
318}