1use abstract_std::ibc::{Callback, IbcResult, ModuleIbcInfo};
2use cosmwasm_std::{Binary, Deps, DepsMut, Env, MessageInfo, Reply, Response, Storage};
3use cw2::{ContractVersion, CONTRACT};
4
5use super::handler::Handler;
6use crate::{std::objects::dependency::StaticDependency, AbstractSdkError, AbstractSdkResult};
7
8pub type ModuleId = &'static str;
10pub type VersionString = &'static str;
12pub type ModuleMetadata = Option<&'static str>;
14
15pub type InstantiateHandlerFn<Module, CustomInitMsg, Error> =
18 fn(DepsMut, Env, MessageInfo, Module, CustomInitMsg) -> Result<Response, Error>;
19pub type ExecuteHandlerFn<Module, CustomExecMsg, Error> =
24 fn(DepsMut, Env, MessageInfo, Module, CustomExecMsg) -> Result<Response, Error>;
25pub type QueryHandlerFn<Module, CustomQueryMsg, Error> =
30 fn(Deps, Env, &Module, CustomQueryMsg) -> Result<Binary, Error>;
31pub type IbcCallbackHandlerFn<Module, Error> =
36 fn(DepsMut, Env, Module, Callback, IbcResult) -> Result<Response, Error>;
37pub type ModuleIbcHandlerFn<Module, Error> =
42 fn(DepsMut, Env, Module, ModuleIbcInfo, Binary) -> Result<Response, Error>;
43pub type MigrateHandlerFn<Module, CustomMigrateMsg, Error> =
48 fn(DepsMut, Env, Module, CustomMigrateMsg) -> Result<Response, Error>;
49pub type SudoHandlerFn<Module, CustomSudoMsg, Error> =
54 fn(DepsMut, Env, Module, CustomSudoMsg) -> Result<Response, Error>;
55pub type ReplyHandlerFn<Module, Error> = fn(DepsMut, Env, Module, Reply) -> Result<Response, Error>;
60const MAX_REPLY_COUNT: usize = 2;
66
67pub struct AbstractContract<Module: Handler + 'static, Error: From<AbstractSdkError> + 'static> {
69 pub(crate) info: (ModuleId, VersionString, ModuleMetadata),
71 pub(crate) dependencies: &'static [StaticDependency],
73 pub(crate) instantiate_handler:
75 Option<InstantiateHandlerFn<Module, <Module as Handler>::CustomInitMsg, Error>>,
76 pub(crate) execute_handler:
78 Option<ExecuteHandlerFn<Module, <Module as Handler>::CustomExecMsg, Error>>,
79 pub(crate) query_handler:
81 Option<QueryHandlerFn<Module, <Module as Handler>::CustomQueryMsg, Error>>,
82 pub(crate) migrate_handler:
84 Option<MigrateHandlerFn<Module, <Module as Handler>::CustomMigrateMsg, Error>>,
85 pub(crate) sudo_handler: Option<SudoHandlerFn<Module, <Module as Handler>::SudoMsg, Error>>,
87 pub reply_handlers: [&'static [(u64, ReplyHandlerFn<Module, Error>)]; MAX_REPLY_COUNT],
89 pub(crate) ibc_callback_handler: Option<IbcCallbackHandlerFn<Module, Error>>,
91 pub(crate) module_ibc_handler: Option<ModuleIbcHandlerFn<Module, Error>>,
93}
94
95impl<Module, Error: From<AbstractSdkError>> AbstractContract<Module, Error>
96where
97 Module: Handler,
98{
99 pub const fn new(name: ModuleId, version: VersionString, metadata: ModuleMetadata) -> Self {
101 Self {
102 info: (name, version, metadata),
103 ibc_callback_handler: None,
104 reply_handlers: [&[], &[]],
105 dependencies: &[],
106 execute_handler: None,
107 migrate_handler: None,
108 sudo_handler: None,
109 instantiate_handler: None,
110 query_handler: None,
111 module_ibc_handler: None,
112 }
113 }
114 pub fn version(&self, store: &dyn Storage) -> AbstractSdkResult<ContractVersion> {
116 CONTRACT.load(store).map_err(Into::into)
117 }
118 pub fn info(&self) -> (ModuleId, VersionString, ModuleMetadata) {
120 self.info
121 }
122 pub const fn with_dependencies(mut self, dependencies: &'static [StaticDependency]) -> Self {
124 self.dependencies = dependencies;
125 self
126 }
127 pub const fn with_replies(
129 mut self,
130 reply_handlers: [&'static [(u64, ReplyHandlerFn<Module, Error>)]; MAX_REPLY_COUNT],
131 ) -> Self {
132 self.reply_handlers = reply_handlers;
133 self
134 }
135
136 pub const fn with_ibc_callback(
138 mut self,
139 callback: IbcCallbackHandlerFn<Module, Error>,
140 ) -> Self {
141 self.ibc_callback_handler = Some(callback);
142 self
143 }
144
145 pub const fn with_module_ibc(
147 mut self,
148 module_handler: ModuleIbcHandlerFn<Module, Error>,
149 ) -> Self {
150 self.module_ibc_handler = Some(module_handler);
151 self
152 }
153
154 pub const fn with_instantiate(
156 mut self,
157 instantiate_handler: InstantiateHandlerFn<
158 Module,
159 <Module as Handler>::CustomInitMsg,
160 Error,
161 >,
162 ) -> Self {
163 self.instantiate_handler = Some(instantiate_handler);
164 self
165 }
166
167 pub const fn with_migrate(
169 mut self,
170 migrate_handler: MigrateHandlerFn<Module, <Module as Handler>::CustomMigrateMsg, Error>,
171 ) -> Self {
172 self.migrate_handler = Some(migrate_handler);
173 self
174 }
175
176 pub const fn with_sudo(
178 mut self,
179 sudo_handler: SudoHandlerFn<Module, <Module as Handler>::SudoMsg, Error>,
180 ) -> Self {
181 self.sudo_handler = Some(sudo_handler);
182 self
183 }
184
185 pub const fn with_execute(
187 mut self,
188 execute_handler: ExecuteHandlerFn<Module, <Module as Handler>::CustomExecMsg, Error>,
189 ) -> Self {
190 self.execute_handler = Some(execute_handler);
191 self
192 }
193
194 pub const fn with_query(
196 mut self,
197 query_handler: QueryHandlerFn<Module, <Module as Handler>::CustomQueryMsg, Error>,
198 ) -> Self {
199 self.query_handler = Some(query_handler);
200 self
201 }
202}
203
204#[cfg(test)]
205mod test {
206 #![allow(clippy::needless_borrows_for_generic_args)]
207 use cosmwasm_std::Empty;
208
209 use super::*;
210
211 #[cosmwasm_schema::cw_serde]
212 struct MockInitMsg;
213
214 #[cosmwasm_schema::cw_serde]
215 struct MockExecMsg;
216
217 #[cosmwasm_schema::cw_serde]
218 struct MockQueryMsg;
219
220 #[cosmwasm_schema::cw_serde]
221 struct MockMigrateMsg;
222
223 #[cosmwasm_schema::cw_serde]
224 struct MockReceiveMsg;
225
226 #[cosmwasm_schema::cw_serde]
227 struct MockSudoMsg;
228
229 use thiserror::Error;
230
231 #[derive(Error, Debug, PartialEq)]
232 pub enum MockError {
233 #[error(transparent)]
234 Sdk(#[from] AbstractSdkError),
235 }
236
237 struct MockModule;
238
239 type MockAppContract = AbstractContract<MockModule, MockError>;
240
241 impl Handler for MockModule {
242 type Error = MockError;
243 type CustomInitMsg = MockInitMsg;
244 type CustomExecMsg = MockExecMsg;
245 type CustomQueryMsg = MockQueryMsg;
246 type CustomMigrateMsg = MockMigrateMsg;
247 type SudoMsg = MockSudoMsg;
248
249 fn contract(&self) -> &AbstractContract<Self, Self::Error> {
250 unimplemented!()
251 }
252 }
253
254 #[coverage_helper::test]
255 fn test_info() {
256 let contract = MockAppContract::new("test_contract", "0.1.0", ModuleMetadata::default());
257 let (name, version, metadata) = contract.info();
258 assert_eq!(name, "test_contract");
259 assert_eq!(version, "0.1.0");
260 assert_eq!(metadata, ModuleMetadata::default());
261 }
262
263 #[coverage_helper::test]
264 fn test_with_empty() {
265 let contract = MockAppContract::new("test_contract", "0.1.0", ModuleMetadata::default())
266 .with_dependencies(&[]);
267
268 assert!(contract.reply_handlers.iter().all(|x| x.is_empty()));
269
270 assert!(contract.dependencies.is_empty());
271 assert!(contract.ibc_callback_handler.is_none());
272 assert!(contract.instantiate_handler.is_none());
273 assert!(contract.execute_handler.is_none());
274 assert!(contract.query_handler.is_none());
275 assert!(contract.migrate_handler.is_none());
276 }
277
278 #[coverage_helper::test]
279 fn test_with_dependencies() {
280 const VERSION: &str = "0.1.0";
281 const DEPENDENCY: StaticDependency = StaticDependency::new("test", &[VERSION]);
282 const DEPENDENCIES: &[StaticDependency] = &[DEPENDENCY];
283
284 let contract = MockAppContract::new("test_contract", "0.1.0", ModuleMetadata::default())
285 .with_dependencies(DEPENDENCIES);
286
287 assert_eq!(contract.dependencies[0].clone(), DEPENDENCY);
288 }
289
290 #[coverage_helper::test]
291 fn test_with_instantiate() {
292 let contract = MockAppContract::new("test_contract", "0.1.0", ModuleMetadata::default())
293 .with_instantiate(|_, _, _, _, _| {
294 Ok(Response::default().add_attribute("test", "instantiate"))
295 });
296
297 assert!(contract.instantiate_handler.is_some());
298 }
299
300 #[coverage_helper::test]
301 fn test_with_sudo() {
302 let contract = MockAppContract::new("test_contract", "0.1.0", ModuleMetadata::default())
303 .with_sudo(|_, _, _, _| Ok(Response::default().add_attribute("test", "sudo")));
304
305 assert!(contract.sudo_handler.is_some());
306 }
307
308 #[coverage_helper::test]
309 fn test_with_execute() {
310 let contract = MockAppContract::new("test_contract", "0.1.0", ModuleMetadata::default())
311 .with_execute(|_, _, _, _, _| Ok(Response::default().add_attribute("test", "execute")));
312
313 assert!(contract.execute_handler.is_some());
314 }
315
316 #[coverage_helper::test]
317 fn test_with_query() {
318 let contract = MockAppContract::new("test_contract", "0.1.0", ModuleMetadata::default())
319 .with_query(|_, _, _, _| Ok(cosmwasm_std::to_json_binary(&Empty {}).unwrap()));
320
321 assert!(contract.query_handler.is_some());
322 }
323
324 #[coverage_helper::test]
325 fn test_with_migrate() {
326 let contract = MockAppContract::new("test_contract", "0.1.0", ModuleMetadata::default())
327 .with_migrate(|_, _, _, _| Ok(Response::default().add_attribute("test", "migrate")));
328
329 assert!(contract.migrate_handler.is_some());
330 }
331
332 #[coverage_helper::test]
333 fn test_with_reply_handlers() {
334 const REPLY_ID: u64 = 50u64;
335 const HANDLER: ReplyHandlerFn<MockModule, MockError> =
336 |_, _, _, _| Ok(Response::default().add_attribute("test", "reply"));
337 let contract = MockAppContract::new("test_contract", "0.1.0", ModuleMetadata::default())
338 .with_replies([&[(REPLY_ID, HANDLER)], &[]]);
339
340 assert_eq!(contract.reply_handlers[0][0].0, REPLY_ID);
341 assert!(contract.reply_handlers[1].is_empty());
342 }
343
344 #[coverage_helper::test]
345 fn test_with_ibc_callback_handlers() {
346 const HANDLER: IbcCallbackHandlerFn<MockModule, MockError> =
347 |_, _, _, _, _| Ok(Response::default().add_attribute("test", "ibc"));
348 let contract = MockAppContract::new("test_contract", "0.1.0", ModuleMetadata::default())
349 .with_ibc_callback(HANDLER);
350
351 assert!(contract.ibc_callback_handler.is_some());
352 }
353}