abstract_app/
lib.rs

1#![cfg_attr(all(coverage_nightly, test), feature(coverage_attribute))]
2
3mod endpoints;
4pub mod error;
5pub mod features;
6pub(crate) mod handler;
7pub mod msgs;
8#[cfg(feature = "schema")]
9pub mod schema;
10pub mod state;
11pub(crate) use abstract_sdk::base::*;
12pub use error::AppError;
13
14pub use crate::state::AppContract;
15pub type AppResult<C = cosmwasm_std::Empty> = Result<cosmwasm_std::Response<C>, AppError>;
16
17// Useful re-exports
18pub use abstract_std as std;
19// re-export objects specifically
20pub use abstract_sdk as sdk;
21
22pub use crate::std::objects;
23pub mod traits {
24    pub use abstract_sdk::{features::*, prelude::*};
25}
26
27mod interface;
28pub use abstract_interface;
29#[cfg(feature = "test-utils")]
30pub use abstract_testing;
31
32#[cfg(feature = "test-utils")]
33pub mod mock {
34    use abstract_interface::{AppDeployer, DependencyCreation, RegisteredModule};
35    pub use abstract_std::app;
36    use abstract_std::{
37        account::ModuleInstallConfig,
38        objects::{dependency::StaticDependency, module::ModuleInfo},
39        IBC_CLIENT,
40    };
41    use cosmwasm_schema::QueryResponses;
42    pub(crate) use cosmwasm_std::testing::{message_info, mock_dependencies};
43    use cosmwasm_std::{to_json_binary, Response, StdError};
44    use cw_controllers::AdminError;
45    use cw_orch::prelude::*;
46    use cw_storage_plus::Item;
47
48    pub type AppTestResult = Result<(), MockError>;
49
50    crate::app_msg_types!(MockAppContract, MockExecMsg, MockQueryMsg);
51
52    #[cosmwasm_schema::cw_serde]
53    pub struct MockInitMsg {}
54
55    #[cosmwasm_schema::cw_serde]
56    #[derive(cw_orch::ExecuteFns)]
57    pub enum MockExecMsg {
58        DoSomething {},
59        DoSomethingAdmin {},
60    }
61
62    #[cosmwasm_schema::cw_serde]
63    #[derive(cw_orch::QueryFns, QueryResponses)]
64    pub enum MockQueryMsg {
65        #[returns(MockQueryResponse)]
66        GetSomething {},
67
68        #[returns(ReceivedIbcCallbackStatus)]
69        GetReceivedIbcCallbackStatus {},
70    }
71
72    #[cosmwasm_schema::cw_serde]
73    pub struct MockQueryResponse {}
74
75    #[cosmwasm_schema::cw_serde]
76    pub struct ReceivedIbcCallbackStatus {
77        pub received: bool,
78    }
79
80    #[cosmwasm_schema::cw_serde]
81    pub struct MockMigrateMsg;
82
83    #[cosmwasm_schema::cw_serde]
84    pub struct MockSudoMsg;
85
86    use abstract_sdk::{base::InstantiateEndpoint, features::Dependencies, AbstractSdkError};
87    use abstract_testing::{mock_env_validated, prelude::*};
88    use thiserror::Error;
89
90    use self::interface::MockAppWithDepI;
91    use crate::{AppContract, AppError};
92
93    #[derive(Error, Debug, PartialEq)]
94    pub enum MockError {
95        #[error(transparent)]
96        Std(#[from] StdError),
97
98        #[error(transparent)]
99        DappError(#[from] AppError),
100
101        #[error(transparent)]
102        Abstract(#[from] abstract_std::AbstractError),
103
104        #[error(transparent)]
105        AbstractSdk(#[from] AbstractSdkError),
106
107        #[error(transparent)]
108        Admin(#[from] AdminError),
109    }
110
111    pub type MockAppContract = AppContract<
112        // MockModule,
113        MockError,
114        MockInitMsg,
115        MockExecMsg,
116        MockQueryMsg,
117        MockMigrateMsg,
118        MockSudoMsg,
119    >;
120
121    pub const BASIC_MOCK_APP: MockAppContract =
122        MockAppContract::new(TEST_MODULE_ID, TEST_VERSION, None);
123
124    // Easy way to see if an ibc-callback was actually received.
125    pub const IBC_CALLBACK_RECEIVED: Item<bool> = Item::new("ibc_callback_received");
126
127    pub const MOCK_APP_WITH_DEP: MockAppContract =
128        MockAppContract::new(TEST_WITH_DEP_MODULE_ID, TEST_VERSION, None)
129            .with_instantiate(|deps, _, _, _, _| {
130                IBC_CALLBACK_RECEIVED.save(deps.storage, &false)?;
131                Ok(Response::new().set_data("mock_init".as_bytes()))
132            })
133            .with_execute(|_, _, _, _, _| Ok(Response::new().set_data("mock_exec".as_bytes())))
134            .with_query(|deps, _, _, msg| match msg {
135                MockQueryMsg::GetSomething {} => {
136                    to_json_binary(&MockQueryResponse {}).map_err(Into::into)
137                }
138                MockQueryMsg::GetReceivedIbcCallbackStatus {} => {
139                    to_json_binary(&ReceivedIbcCallbackStatus {
140                        received: IBC_CALLBACK_RECEIVED.load(deps.storage)?,
141                    })
142                    .map_err(Into::into)
143                }
144            })
145            .with_sudo(|_, _, _, _| Ok(Response::new().set_data("mock_sudo".as_bytes())))
146            .with_ibc_callback(|deps, _, _, _, _| {
147                IBC_CALLBACK_RECEIVED.save(deps.storage, &true).unwrap();
148
149                Ok(Response::new().add_attribute("mock_callback", "executed"))
150            })
151            .with_dependencies(&[
152                StaticDependency::new(TEST_MODULE_ID, &[TEST_VERSION]),
153                StaticDependency::new(IBC_CLIENT, &[abstract_std::constants::ABSTRACT_VERSION]),
154            ])
155            .with_replies(&[(1u64, |_, _, _, msg| {
156                #[allow(deprecated)]
157                Ok(Response::new().set_data(msg.result.unwrap().data.unwrap()))
158            })])
159            .with_migrate(|_, _, _, _| Ok(Response::new().set_data("mock_migrate".as_bytes())));
160
161    crate::cw_orch_interface!(MOCK_APP_WITH_DEP, MockAppContract, MockAppWithDepI);
162
163    // Needs to be in a separate module due to the `interface` module names colliding otherwise.
164    pub mod mock_app_dependency {
165        use abstract_testing::prelude::{TEST_MODULE_ID, TEST_VERSION};
166        use cosmwasm_std::{to_json_binary, Response};
167
168        use super::{MockAppContract, MockQueryResponse};
169
170        pub const MOCK_APP: MockAppContract =
171            MockAppContract::new(TEST_MODULE_ID, TEST_VERSION, None)
172                .with_instantiate(|_, _, _, _, _| {
173                    Ok(Response::new().set_data("mock_init".as_bytes()))
174                })
175                .with_execute(|_, _, _, _, _| Ok(Response::new().set_data("mock_exec".as_bytes())))
176                .with_query(|_, _, _, _| to_json_binary(&MockQueryResponse {}).map_err(Into::into));
177
178        crate::cw_orch_interface!(MOCK_APP, MockAppContract, MockAppI);
179    }
180
181    impl<Chain: CwEnv> DependencyCreation for MockAppWithDepI<Chain> {
182        type DependenciesConfig = Empty;
183        fn dependency_install_configs(
184            _configuration: Self::DependenciesConfig,
185        ) -> Result<Vec<ModuleInstallConfig>, abstract_interface::AbstractInterfaceError> {
186            let test_module = ModuleInstallConfig::new(
187                ModuleInfo::from_id(TEST_MODULE_ID, TEST_VERSION.into())?,
188                Some(to_json_binary(&MockInitMsg {})?),
189            );
190            let ibc_client = ModuleInstallConfig::new(
191                ModuleInfo::from_id(IBC_CLIENT, abstract_std::constants::ABSTRACT_VERSION.into())?,
192                None,
193            );
194            Ok(vec![test_module, ibc_client])
195        }
196    }
197
198    crate::export_endpoints!(MOCK_APP_WITH_DEP, MockAppContract);
199
200    pub fn app_base_mock_querier(mock_api: cosmwasm_std::testing::MockApi) -> MockQuerierBuilder {
201        let abstr = AbstractMockAddrs::new(mock_api);
202        MockQuerierBuilder::default()
203            .with_smart_handler(&abstr.module_factory, |_msg| panic!("unexpected message"))
204    }
205
206    /// Instantiate the contract with the default [`TEST_MODULE_FACTORY`].
207    /// This will set the [`abstract_testing::addresses::TEST_ACCOUNT`] as the admin.
208    pub fn mock_init() -> MockDeps {
209        let mut deps = mock_dependencies();
210        let abstr = AbstractMockAddrs::new(deps.api);
211        let info = message_info(&abstr.module_factory, &[]);
212        let env = mock_env_validated(deps.api);
213        let account = test_account(deps.api);
214
215        deps.querier = app_base_mock_querier(deps.api).build();
216
217        let msg = app::InstantiateMsg {
218            base: app::BaseInstantiateMsg { account },
219            module: MockInitMsg {},
220        };
221
222        BASIC_MOCK_APP
223            .instantiate(deps.as_mut(), env, info, msg)
224            .unwrap();
225
226        deps
227    }
228
229    type Exec = app::ExecuteMsg<MockExecMsg>;
230    type Query = app::QueryMsg<MockQueryMsg>;
231    type Init = app::InstantiateMsg<MockInitMsg>;
232    type Migrate = app::MigrateMsg<MockMigrateMsg>;
233
234    #[cw_orch::interface(Init, Exec, Query, Migrate)]
235    pub struct MockAppI<Chain>;
236
237    impl<Chain> RegisteredModule for MockAppI<Chain> {
238        type InitMsg = MockInitMsg;
239
240        fn module_id<'a>() -> &'a str {
241            BASIC_MOCK_APP.module_id()
242        }
243
244        fn module_version<'a>() -> &'a str {
245            BASIC_MOCK_APP.version()
246        }
247
248        fn dependencies<'a>() -> &'a [StaticDependency] {
249            BASIC_MOCK_APP.dependencies()
250        }
251    }
252
253    impl<T: cw_orch::prelude::CwEnv> AppDeployer<T> for MockAppI<T> {}
254
255    impl<T: cw_orch::prelude::CwEnv> Uploadable for MockAppI<T> {
256        fn wrapper() -> <Mock as ::cw_orch::environment::TxHandler>::ContractSource {
257            Box::new(
258                ContractWrapper::new_with_empty(self::execute, self::instantiate, self::query)
259                    .with_migrate(self::migrate),
260            )
261        }
262    }
263
264    #[macro_export]
265    macro_rules! gen_app_mock {
266    ($name:ident,$id:expr, $version:expr, $deps:expr) => {
267        use $crate::std::app;
268        use ::abstract_app::mock::{MockExecMsg, MockInitMsg, MockMigrateMsg, MockQueryMsg};
269        use ::cw_orch::prelude::*;
270        use $crate::sdk::base::Handler;
271        use $crate::sdk::features::AccountIdentification;
272        use $crate::sdk::{Execution, TransferInterface};
273
274
275        type Exec = app::ExecuteMsg<MockExecMsg>;
276        type Query = app::QueryMsg<MockQueryMsg>;
277        type Init = app::InstantiateMsg<MockInitMsg>;
278        type Migrate = app::MigrateMsg<MockMigrateMsg>;
279        const MOCK_APP_WITH_DEP: ::abstract_app::mock::MockAppContract = ::abstract_app::mock::MockAppContract::new($id, $version, None)
280        .with_dependencies($deps)
281        .with_execute(|deps, env, info, module, msg| {
282            match msg {
283                MockExecMsg::DoSomethingAdmin{} => {
284                    module.admin.assert_admin(deps.as_ref(), &env, &info.sender)?;
285                },
286                _ => {},
287            }
288            Ok(::cosmwasm_std::Response::new().set_data("mock_exec".as_bytes()))
289        })
290        .with_instantiate(|deps, env, info, module, msg| {
291            let mut response = ::cosmwasm_std::Response::new().set_data("mock_init".as_bytes());
292            // See test `create_sub_account_with_installed_module` where this will be triggered.
293            if module.info().0 == "tester:mock-app1" {
294                println!("checking address of adapter1");
295                let account = module.admin.get(deps.as_ref())?.unwrap();
296                // Check if the adapter has access to its dependency during instantiation.
297                let adapter1_addr = $crate::std::account::state::ACCOUNT_MODULES.query(&deps.querier,account, "tester:mock-adapter1")?;
298                // We have address!
299                ::cosmwasm_std::ensure!(
300                    adapter1_addr.is_some(),
301                    ::cosmwasm_std::StdError::generic_err("no address")
302                );
303                println!("adapter_addr: {adapter1_addr:?}");
304                // See test `install_app_with_account_action` where this transfer will happen.
305                let account_addr = module.account(deps.as_ref())?;
306                let balance = deps.querier.query_balance(account_addr.addr(), "TEST")?;
307                if !balance.amount.is_zero() {
308                println!("sending amount from account: {balance:?}");
309                    let action = module
310                        .bank(deps.as_ref())
311                        .transfer::<::cosmwasm_std::Coin>(
312                            vec![balance.into()],
313                            &adapter1_addr.unwrap(),
314                        )?;
315                    let msg = module.executor(deps.as_ref()).execute(vec![action])?;
316                    println!("message: {msg:?}");
317                    response = response.add_message(msg);
318                }
319                Ok(response)}
320            else {
321                Ok(response)}
322            });
323
324        fn mock_instantiate(
325            deps: ::cosmwasm_std::DepsMut,
326            env: ::cosmwasm_std::Env,
327            info: ::cosmwasm_std::MessageInfo,
328            msg: <::abstract_app::mock::MockAppContract as $crate::sdk::base::InstantiateEndpoint>::InstantiateMsg,
329        ) -> Result<::cosmwasm_std::Response, <::abstract_app::mock::MockAppContract as $crate::sdk::base::Handler>::Error> {
330            use $crate::sdk::base::InstantiateEndpoint;
331            MOCK_APP_WITH_DEP.instantiate(deps, env, info, msg)
332        }
333
334        /// Execute entrypoint
335        fn mock_execute(
336            deps: ::cosmwasm_std::DepsMut,
337            env: ::cosmwasm_std::Env,
338            info: ::cosmwasm_std::MessageInfo,
339            msg: <::abstract_app::mock::MockAppContract as $crate::sdk::base::ExecuteEndpoint>::ExecuteMsg,
340        ) -> Result<::cosmwasm_std::Response, <::abstract_app::mock::MockAppContract as $crate::sdk::base::Handler>::Error> {
341            use $crate::sdk::base::ExecuteEndpoint;
342            MOCK_APP_WITH_DEP.execute(deps, env, info, msg)
343        }
344
345        /// Query entrypoint
346        fn mock_query(
347            deps: ::cosmwasm_std::Deps,
348            env: ::cosmwasm_std::Env,
349            msg: <::abstract_app::mock::MockAppContract as $crate::sdk::base::QueryEndpoint>::QueryMsg,
350        ) -> Result<::cosmwasm_std::Binary, <::abstract_app::mock::MockAppContract as $crate::sdk::base::Handler>::Error> {
351            use $crate::sdk::base::QueryEndpoint;
352            MOCK_APP_WITH_DEP.query(deps, env, msg)
353        }
354
355        fn mock_migrate(
356            deps: ::cosmwasm_std::DepsMut,
357            env: ::cosmwasm_std::Env,
358            msg: <::abstract_app::mock::MockAppContract as $crate::sdk::base::MigrateEndpoint>::MigrateMsg,
359        ) -> Result<::cosmwasm_std::Response, <::abstract_app::mock::MockAppContract as $crate::sdk::base::Handler>::Error> {
360            use $crate::sdk::base::MigrateEndpoint;
361            MOCK_APP_WITH_DEP.migrate(deps, env, msg)
362        }
363
364        #[cw_orch::interface(Init, Exec, Query, Migrate)]
365        pub struct $name;
366
367        impl<T: cw_orch::prelude::CwEnv> ::abstract_interface::AppDeployer<T> for $name <T> {}
368
369        impl<Chain> $crate::abstract_interface::RegisteredModule for $name<Chain> {
370            type InitMsg = MockInitMsg;
371
372            fn module_id<'a>() -> &'a str {
373                MOCK_APP_WITH_DEP.module_id()
374            }
375
376            fn module_version<'a>() -> &'a str {
377                MOCK_APP_WITH_DEP.version()
378            }
379
380            fn dependencies<'a>() -> &'a [$crate::objects::dependency::StaticDependency] {
381                MOCK_APP_WITH_DEP.dependencies()
382            }
383        }
384
385        impl<T: cw_orch::prelude::CwEnv> Uploadable for $name<T> {
386            fn wrapper() -> <Mock as ::cw_orch::environment::TxHandler>::ContractSource {
387                Box::new(ContractWrapper::<
388                    Exec,
389                    _,
390                    _,
391                    _,
392                    _,
393                    _,
394                >::new_with_empty(
395                    self::mock_execute,
396                    self::mock_instantiate,
397                    self::mock_query,
398                ).with_migrate(self::mock_migrate))
399            }
400        }
401
402        impl<Chain: ::cw_orch::environment::CwEnv> $name <Chain> {
403            pub fn new_test(chain: Chain) -> Self {
404                Self(
405                    cw_orch::contract::Contract::new($id,chain),
406                )
407            }
408        }
409    };
410}
411}