1use abstract_macros::with_abstract_event;
6use abstract_std::account::ExecuteMsg;
7use cosmwasm_std::{Binary, Coin, CosmosMsg, Deps, ReplyOn, Response, SubMsg};
8
9use super::AbstractApi;
10use crate::{
11 features::{AccountExecutor, ModuleIdentification},
12 AbstractSdkResult, AccountAction,
13};
14
15pub trait Execution: AccountExecutor + ModuleIdentification {
17 fn executor<'a>(&'a self, deps: Deps<'a>) -> Executor<'a, Self> {
37 Executor { base: self, deps }
38 }
39}
40
41impl<T> Execution for T where T: AccountExecutor + ModuleIdentification {}
42
43impl<T: Execution> AbstractApi<T> for Executor<'_, T> {
44 const API_ID: &'static str = "Executor";
45
46 fn base(&self) -> &T {
47 self.base
48 }
49 fn deps(&self) -> Deps {
50 self.deps
51 }
52}
53
54#[derive(Clone)]
74pub struct Executor<'a, T: Execution> {
75 base: &'a T,
76 deps: Deps<'a>,
77}
78
79impl<T: Execution> Executor<'_, T> {
80 fn execute_with_data(&self, msg: CosmosMsg) -> AbstractSdkResult<ExecutorMsg> {
82 let msg = self.base.execute_on_account(
83 self.deps,
84 &ExecuteMsg::ExecuteWithData { msg },
85 vec![],
86 )?;
87 Ok(ExecutorMsg(msg))
88 }
89
90 pub fn execute(
93 &self,
94 actions: impl IntoIterator<Item = impl Into<AccountAction>>,
95 ) -> AbstractSdkResult<ExecutorMsg> {
96 self.execute_with_funds(actions, vec![])
97 }
98
99 pub fn execute_with_funds(
103 &self,
104 actions: impl IntoIterator<Item = impl Into<AccountAction>>,
105 funds: Vec<Coin>,
106 ) -> AbstractSdkResult<ExecutorMsg> {
107 let msgs = actions
108 .into_iter()
109 .flat_map(|a| a.into().messages())
110 .collect();
111 let msg = self
112 .base
113 .execute_on_account(self.deps, &ExecuteMsg::Execute { msgs }, funds)?;
114 Ok(ExecutorMsg(msg))
115 }
116
117 pub fn execute_with_reply(
121 &self,
122 actions: impl IntoIterator<Item = impl Into<AccountAction>>,
123 reply_on: ReplyOn,
124 id: u64,
125 ) -> AbstractSdkResult<SubMsg> {
126 let msg = self.execute(actions)?;
127 let sub_msg = SubMsg {
128 id,
129 msg: msg.into(),
130 gas_limit: None,
131 reply_on,
132 payload: Binary::default(),
133 };
134 Ok(sub_msg)
135 }
136
137 pub fn execute_with_reply_and_data(
141 &self,
142 actions: CosmosMsg,
143 reply_on: ReplyOn,
144 id: u64,
145 ) -> AbstractSdkResult<SubMsg> {
146 let msg = self.execute_with_data(actions)?;
147 let sub_msg = SubMsg {
148 id,
149 msg: msg.into(),
150 gas_limit: None,
151 reply_on,
152 payload: Binary::default(),
153 };
154 Ok(sub_msg)
155 }
156
157 pub fn execute_with_response(
161 &self,
162 actions: impl IntoIterator<Item = impl Into<AccountAction>>,
163 action: &str,
164 ) -> AbstractSdkResult<Response> {
165 let msg = self.execute(actions)?;
166 let resp = Response::default();
167
168 Ok(with_abstract_event!(resp, self.base.module_id(), action).add_message(msg))
169 }
170}
171
172#[must_use = "ExecutorMsg should be provided to Response::add_message"]
174#[cfg_attr(not(target_arch = "wasm32"), derive(Debug, PartialEq, Eq))]
175pub struct ExecutorMsg(CosmosMsg);
176
177impl From<ExecutorMsg> for CosmosMsg {
178 fn from(val: ExecutorMsg) -> Self {
179 val.0
180 }
181}
182
183#[cfg(test)]
184mod test {
185 #![allow(clippy::needless_borrows_for_generic_args)]
186 use abstract_std::account::ExecuteMsg;
187 use abstract_testing::prelude::*;
188 use cosmwasm_std::*;
189
190 use super::*;
191 use crate::{apis::traits::test::abstract_api_test, mock_module::*};
192
193 fn mock_bank_send(amount: Vec<Coin>) -> AccountAction {
194 AccountAction::from(CosmosMsg::Bank(BankMsg::Send {
195 to_address: "to_address".to_string(),
196 amount,
197 }))
198 }
199
200 fn flatten_actions(actions: Vec<AccountAction>) -> Vec<CosmosMsg> {
201 actions.into_iter().flat_map(|a| a.messages()).collect()
202 }
203
204 mod execute {
205 use super::*;
206
207 #[coverage_helper::test]
209 fn empty_actions() {
210 let (deps, account, stub) = mock_module_setup();
211 let executor = stub.executor(deps.as_ref());
212
213 let messages = vec![];
214
215 let actual_res = executor.execute(messages.clone());
216 assert!(actual_res.is_ok());
217
218 let expected = ExecutorMsg(CosmosMsg::Wasm(WasmMsg::Execute {
219 contract_addr: account.addr().to_string(),
220 msg: to_json_binary(&ExecuteMsg::<Empty>::Execute {
221 msgs: flatten_actions(messages),
222 })
223 .unwrap(),
224 funds: vec![],
225 }));
226 assert_eq!(actual_res, Ok(expected));
227 }
228
229 #[coverage_helper::test]
230 fn with_actions() {
231 let (deps, account, stub) = mock_module_setup();
232 let executor = stub.executor(deps.as_ref());
233
234 let messages = vec![mock_bank_send(coins(100, "juno"))];
236
237 let actual_res = executor.execute(messages.clone());
238 assert!(actual_res.is_ok());
239
240 let expected = ExecutorMsg(CosmosMsg::Wasm(WasmMsg::Execute {
241 contract_addr: account.addr().to_string(),
242 msg: to_json_binary(&ExecuteMsg::<Empty>::Execute {
243 msgs: flatten_actions(messages),
244 })
245 .unwrap(),
246 funds: vec![],
248 }));
249 assert_eq!(actual_res, Ok(expected));
250 }
251 }
252
253 mod execute_with_reply {
254
255 use super::*;
256
257 #[coverage_helper::test]
259 fn empty_actions() {
260 let (deps, account, stub) = mock_module_setup();
261 let executor = stub.executor(deps.as_ref());
262
263 let empty_actions = vec![];
264 let expected_reply_on = ReplyOn::Success;
265 let expected_reply_id = 10952;
266
267 let actual_res = executor.execute_with_reply(
268 empty_actions.clone(),
269 expected_reply_on.clone(),
270 expected_reply_id,
271 );
272 assert!(actual_res.is_ok());
273
274 let expected = SubMsg {
275 id: expected_reply_id,
276 msg: CosmosMsg::Wasm(WasmMsg::Execute {
277 contract_addr: account.addr().to_string(),
278 msg: to_json_binary(&ExecuteMsg::<Empty>::Execute {
279 msgs: flatten_actions(empty_actions),
280 })
281 .unwrap(),
282 funds: vec![],
283 }),
284 gas_limit: None,
285 reply_on: expected_reply_on,
286 payload: Binary::default(),
287 };
288 assert_eq!(actual_res, Ok(expected));
289 }
290
291 #[coverage_helper::test]
292 fn with_actions() {
293 let (deps, account, stub) = mock_module_setup();
294 let executor = stub.executor(deps.as_ref());
295
296 let action = vec![mock_bank_send(coins(1, "denom"))];
298 let expected_reply_on = ReplyOn::Never;
300 let expected_reply_id = 1;
301
302 let actual_res = executor.execute_with_reply(
303 action.clone(),
304 expected_reply_on.clone(),
305 expected_reply_id,
306 );
307 assert!(actual_res.is_ok());
308
309 let expected = SubMsg {
310 id: expected_reply_id,
311 msg: CosmosMsg::Wasm(WasmMsg::Execute {
312 contract_addr: account.addr().to_string(),
313 msg: to_json_binary(&ExecuteMsg::<Empty>::Execute {
314 msgs: flatten_actions(action),
315 })
316 .unwrap(),
317 funds: vec![],
319 }),
320 gas_limit: None,
321 reply_on: expected_reply_on,
322 payload: Binary::default(),
323 };
324 assert_eq!(actual_res, Ok(expected));
325 }
326 }
327
328 mod execute_with_response {
329 use super::*;
330
331 #[coverage_helper::test]
333 fn empty_actions() {
334 let (deps, account, stub) = mock_module_setup();
335 let executor = stub.executor(deps.as_ref());
336
337 let empty_actions = vec![];
338 let expected_action = "THIS IS AN ACTION";
339
340 let actual_res = executor.execute_with_response(empty_actions.clone(), expected_action);
341
342 let expected_msg = CosmosMsg::Wasm(WasmMsg::Execute {
343 contract_addr: account.addr().to_string(),
344 msg: to_json_binary(&ExecuteMsg::<Empty>::Execute {
345 msgs: flatten_actions(empty_actions),
346 })
347 .unwrap(),
348 funds: vec![],
349 });
350
351 let expected = Response::new()
352 .add_event(
353 Event::new("abstract")
354 .add_attribute("contract", stub.module_id())
355 .add_attribute("action", expected_action),
356 )
357 .add_message(expected_msg);
358
359 assert_eq!(actual_res, Ok(expected));
360 }
361
362 #[coverage_helper::test]
363 fn with_actions() {
364 let (deps, account, stub) = mock_module_setup();
365
366 let executor = stub.executor(deps.as_ref());
367
368 let action = vec![mock_bank_send(coins(1, "denom"))];
370 let expected_action = "provide liquidity";
371
372 let actual_res = executor.execute_with_response(action.clone(), expected_action);
373
374 let expected_msg = CosmosMsg::Wasm(WasmMsg::Execute {
375 contract_addr: account.addr().to_string(),
376 msg: to_json_binary(&ExecuteMsg::<Empty>::Execute {
377 msgs: flatten_actions(action),
378 })
379 .unwrap(),
380 funds: vec![],
382 });
383 let expected = Response::new()
384 .add_event(
385 Event::new("abstract")
386 .add_attribute("contract", stub.module_id())
387 .add_attribute("action", expected_action),
388 )
389 .add_message(expected_msg);
390 assert_eq!(actual_res, Ok(expected));
391 }
392 }
393
394 #[coverage_helper::test]
395 fn abstract_api() {
396 let (deps, _, app) = mock_module_setup();
397 let executor = app.executor(deps.as_ref());
398
399 abstract_api_test(executor);
400 }
401}